Profiling TorchNeuron (Part 1): System-level profiling on AWS Trainium. AWS published Part 1 of a technical series on profiling TorchNeuron, its open source native PyTorch backend for AWS Trainium AI chips, showing practitioners how to configure torch.profiler with the Neuron backend and read the key_averages() table to identify which operations consume Neuron device time. The walkthrough covers setting NeuronConfig with modes such as ProfileMode.DEVICE and ProfileMode.RUNTIME and correlate_device_time=True, then following a single operation through Neuron Explorer from framework dispatch to runtime scheduling to execution on a NeuronCore. TorchNeuron registers Neuron as a PyTorch backend via the PrivateUse1 extension point and integrates with the Neuron Kernel Interface (NKI) for custom operators. Viewer • Updated • 1 https://huggingface.co/datasets/jlongen-aws/blog-assets Profiling TorchNeuron Part 1 : System-level profiling on AWS Trainium. Enterprise Article https://huggingface.co/blog Many ML practitioners have some intuition about how to make models run fast, but fewer know how to analyze why a model is slow. In Part 1 of this series, you will learn how to systematically analyze inference performance and identify where the time is spent. This post starts from scratch and assumes no prior profiling experience. The walkthrough uses AWS Trainium, a purpose-built AI chip for deep learning training and inference workloads, and TorchNeuron https://github.com/aws-neuron/torch-neuronx , its open source native PyTorch backend. TorchNeuron supports standard PyTorch workflows on Trainium, including eager execution, torch.compile , and standard PyTorch APIs, with minimal code changes. Additionally, TorchNeuron integrates with Neuron Kernel Interface NKI https://awsdocs-neuron.readthedocs-hosted.com/en/latest/nki/index.html to enable custom operators and performance optimization for Trainium hardware. By the end, you should know: - How to configure torch.profiler with the Neuron backend and capture a clean trace. - How to read the key averages table to identify which operations consume Neuron device time and compare their Self CPU time. - How to follow one operation through Neuron Explorer, from framework dispatch through runtime scheduling to hardware execution on a NeuronCore. - How these signals change across three implementations of the same operation. Neuron as a PyTorch Backend Before you profile a workload, it helps to understand how Neuron https://awsdocs-neuron.readthedocs-hosted.com/en/latest/index.html integrates with PyTorch. The Neuron backend uses PyTorch's PrivateUse1 extension point. This mechanism lets an accelerator integrate with the framework without adding a dedicated dispatch key such as cuda . The torch neuronx package works with upstream PyTorch to register Neuron as a native backend for PyTorch code. For details about this extension mechanism, see Facilitating New Backend Integration by PrivateUse1 https://docs.pytorch.org/tutorials/advanced/privateuseone.html . Set Up the Profiler You use two objects to profile a Neuron workload: - NeuronConfig controls which Neuron data streams and NeuronCores to capture and where to write the output. You pass it to torch.profiler.profile as experimental config . - NeuronProfiler takes a NeuronConfig and provides an export trace callback. The callback writes the framework trace, trace.json , alongside the Neuron system traces. nc = NeuronConfig modes= ProfileMode.DEVICE, ProfileMode.RUNTIME , profile output dir=OUTPUT DIR, capture enabled for nc='0', correlate device time=True, exporter = NeuronProfiler nc The modes parameter selects the data streams to capture, such as device instructions and runtime traces. The capture enabled for nc parameter selects the NeuronCores to trace. Setting correlate device time=True propagates device time from the trace into the key averages table, where you can compare CPU and Neuron device time. For the complete parameter reference and available ProfileMode values, see the NeuronConfig Profile Modes documentation http://awsdocs-neuron.readthedocs-hosted.com/en/latest/tools/neuron-explorer/how-to-profile-workload.html neuronconfig-profile-modes . Tooling: Neuron Explorer Neuron Explorer is a browser-based viewer for the system trace, device timeline, and per-engine breakdown. You can access it through an SSH tunnel or the Visual Studio Code extension. In this post, you use Explorer to confirm what key averages reports numerically and inspect what happens between dispatches. After profiling, the output directory contains the system trace and, when you capture device data, matched .neff and .ntff files. The Neuron Executable File Format NEFF contains the compiled Neuron executable, while the matching NTFF contains its hardware instruction trace. The files share a numeric hash that identifies them as a pair. To open the profile in Neuron Explorer, upload the complete output directory for a system or combined profile. For a device-only profile, upload the matching .neff and .ntff files individually. See Step 5: Upload your profile https://awsdocs-neuron.readthedocs-hosted.com/en/latest/tools/neuron-explorer/get-started.html step-5-upload-your-profile for the UI and command-line instructions. Profile RMSNorm For this first analysis, you profile RMSNorm, a small operation with a clear sequence of arithmetic and reduction operations. You profile three implementations of the same operation: 1. Naive RMSNorm with individual PyTorch operations: multiply, mean, add, reciprocal square root, and multiply. 2. torch.rms norm , the PyTorch built-in lowered to a single fused Neuron kernel. 3. Naive RMSNorm with torch.compile , using the same operations as the first implementation. The scripts use the same inputs, profiler configuration, and schedule so the results are directly comparable. All scripts to reproduce this work are available in the torch-neuronx examples directory https://github.com/aws-neuron/torch-neuronx/tree/main/examples/hf profiling blog pt1 . Profile 1: Naive PyTorch Operations Start by writing RMSNorm with individual PyTorch operations. Each line maps to one or more ATen operations and can create intermediate tensors. device = torch.device 'neuron' x = torch.randn 4, 512, dtype=torch.bfloat16, device=device weight = torch.ones 512, dtype=torch.bfloat16, device=device def rms norm fn t : x sq = t t square mean sq = x sq.mean dim=-1, keepdim=True mean of squares rms = torch.rsqrt mean sq + 1e-6 1 / sqrt mean sq + eps return t rms weight normalize + scale Call the profiler with two arguments required for the Neuron backend: 1. Include ProfilerActivity.PrivateUse1 to collect activity from the Neuron device. 2. Pass NeuronConfig as experimental config to enable the Neuron-specific Kineto features. The schedule uses wait=2 , warmup=1 , and active=1 across four prof.step calls. This records one warm iteration without including initial setup overhead. Call torch.neuron.synchronize inside the profiler loop. Without this synchronization, the profiler can export before the device finishes and leave the Neuron time column empty. with torch.profiler.profile activities= ProfilerActivity.CPU, ProfilerActivity.PrivateUse1 , experimental config=nc, record shapes=False, on trace ready=exporter.export trace, schedule=torch.profiler.schedule wait=2, warmup=1, active=1 , as prof: for step in range 4 : with torch.no grad : = rms norm fn x torch.neuron.synchronize prof.step After profiling finishes, inspect the key averages table. Sort by self privateuse1 time total to place the operations with the highest device cost first. The table shown here is filtered to the RMSNorm operations that report Neuron device time. The full key averages output also includes CPU-side events such as allocations and host-to-device transfers. print prof.key averages .table sort by="self privateuse1 time total", row limit=4 The key averages table shows that three aten::mul calls account for the largest share of Neuron device time. The table shows which ATen operations ran, how often they ran, and how much time they spent on the CPU and Neuron device. aten::mul has the highest total device time because the function performs three multiplications. | Name | Calls | Self CPU | Self NEURON | % NEURON | |---|---|---|---|---| | aten::mul | 3 | 60.9 μs | 1,164.5 μs | 49.5% | | aten::add | 1 | 26.6 μs | 434.6 μs | 18.5% | | aten::rsqrt | 1 | 15.6 μs | 415.0 μs | 17.6% | | aten::mean | 1 | 21.1 μs | 340.8 μs | 14.5% | Self NEURON total: 2.355 ms Each row maps directly to the function. aten::mul runs for x x , x rms , and the final multiplication by weight . aten::mean reduces the last dimension. aten::add adds the epsilon value and triggers a host-to-device copy because the scalar starts on the CPU. aten::rsqrt computes the reciprocal square root. Neuron Explorer follows an aten::mul operation from framework dispatch through runtime scheduling to execution on the Neuron hardware lanes. Read the Trace in Explorer In Explorer, time flows from left to right and the event lifecycle flows from top to bottom. Each ATen operation follows the same pattern: creation in the framework lane, scheduling in the runtime lane as nrta execute schedule , and device execution in the hardware lane as nc exec running . The system trace view organizes events into lanes. In the Default grouping mode, labels follow the pattern instance/PID/subsystem/identifier: | Lane | Label suffix | What it shows | |---|---|---| | Framework dispatch | framework/TID: