Opening

When profiling large models on Hygon DCU, the hardest part is usually not whether a trace can be generated, but how to read it after it has been generated.

A local sampling run of Qwen3.5-27B BF16 produced:

1
2
3
4
总事件:       235,964
GPU 事件: 61,804
kernel 事件: 60,124
GPU event 总时长: 5773.493 ms

Sorting directly by total kernel time only shows a long list of Tensile, Triton, Attention, and custom kernel names. It does not proactively tell you:

  • which ones belong to Prefill;
  • which ones belong to Decode;
  • how many times a kernel is called per token;
  • whether the call counts can map back to the number of model layers;
  • which time bucket is the real bottleneck worth optimizing.

This article does not discuss a new kernel. Instead, using this measured Hygon DCU trace, it demonstrates how to reconstruct the complete inference structure from event context and call counts.


1. First, Limit the Scope of the Profile

This sampling covers only one request with clear boundaries:

1
2
3
3 个 q=4096 Prefill chunk
1 个 q=1685 Prefill tail
60 个 Decode step

This is a very important precondition.

If the profile covers multiple concurrent requests, model loading, first compilation, and long generation, the number of events will expand rapidly, and it will be difficult to attribute a given kernel to a specific phase.

For Hygon DCU large-model services, a more reliable workflow is:

1
2
3
4
5
服务先完成加载和必要 warmup
-> 开启 profiler
-> 只发送一个有代表性的请求
-> 等请求完整结束
-> 立即停止并保存 trace

The goal of profiling is to answer “where is the time spent,” not to reproduce maximum throughput.


2. First Cut: Split Prefill and Decode by Execution Context

Each batch of GPU events in the trace has an upper-level execution context. Using the token count and generation state, they can first be divided into:

Context Total GPU event time
Three q=4096 Prefill chunks 2720.145 ms
One q=1685 Prefill tail 464.801 ms
60 Decode steps 2586.460 ms

Decode average per step:

1
2
2586.460 ms / 60
= 43.108 ms/token

This step is more important than looking directly at the top kernels.

The same GEMM name may appear in both Prefill and Decode, but the M dimension, parallelism, and optimization methods are completely different in the two phases. Only after splitting by context do the later statistics have physical meaning.


3. Second Cut: Identify Model Structure Using Call Counts

3.1 LLMM1 kernel

In the Decode interval, LLMM1 totals:

1
2
10,560 launches
1,408.219 ms

Divided by 60 Decode steps:

1
2
10,560 / 60 = 176 launches/token
1,408.219 / 60 = 23.470 ms/token

176 is not a random number; it maps completely back to the model structure:

1
2
3
4
5
6
64 次 MLP gate/up
48 次 GDN qkvz
48 次 GDN BA
16 次 Full-Attention QKV
-------------------------
176 次/token

When the trace call count and the model layer count fully close, the kernel attribution is basically credible.

3.2 rocBLAS/Tensile GEMV

In the same Decode interval, rocBLAS/Tensile GEMV is:

1
2
7,740 launches
876.431 ms

After conversion:

1
2
7,740 / 60 = 129 launches/token
876.431 / 60 = 14.607 ms/token

129 can likewise be mapped back to modules:

1
2
3
4
5
6
64 次 MLP down projection
48 次 GDN output projection
16 次 Attention output projection
1 次 lm_head
--------------------------------
129 次/token

This kind of “call-count closure” is very valuable. It not only tells us whether a kernel is hot, but also checks:

  • whether some type of Linear was missed in the statistics;
  • whether a fusion actually reduced launches;
  • whether runtime markers are consistent with the actual call count;
  • whether the model structure and the trace come from the same version.

4. Third Cut: Convert Time into a Per-Token Budget

LLMM1 and rocBLAS combined:

1
2
23.470 + 14.607
= 38.078 ms/token

Relative to 43.108 ms/token for Decode:

1
2
38.078 / 43.108
= 88.33%

This shows that in this Hygon DCU trace, the main Decode bottleneck is Linear, not some scattered pointwise kernel.

Other major Decode time includes:

Module Time per token Decode share
Page-contiguous Attention 1.630 ms 3.78%
Packed GDN recurrent 0.623 ms 1.44%

After seeing this ratio, optimization priorities become clear:

  • a small fusion that saves only 2-3 us per layer is unlikely to change total TPOT;
  • an implementation covering hundreds of Linear calls may accumulate endpoint gains even if each call is only a few percent faster;
  • continuing to optimize a bucket that accounts for less than 2% requires first proving a sufficiently large absolute saving.

The role of profiling is not to automatically provide a solution, but to constrain the physical upper bound of the solution.


5. Prefill Cannot Reuse Decode’s Conclusions

The main time items for the three q=4096 Prefill chunks are:

Time item Total time Share of q=4096 Prefill
aten::mm 1840.596 ms 67.67%
Attention 238.718 ms 8.78%

The same model presents different shapes in Prefill and Decode:

1
2
3
4
5
Prefill:
大 M GEMM + 长序列 Attention

Decode:
M=1 Linear/GEMV + 状态读取

Therefore, in trace analysis, “Linear accounts for 88.33% of Decode” cannot be understood as a unified share for the entire request, nor can Decode’s M=1 kernels be used to explain q=4096 Prefill.

The correct approach is to establish two separate budgets:

1
2
TTFT 预算 -> Prefill 上下文
TPOT 预算 -> Decode 上下文

6. Why Looking Only at Kernel Rankings Misleads

Suppose the first place in the top kernel list is LLMM1 and the second place is some Tensile kernel.

Without context splitting, you will encounter at least three problems.

6.1 The Same Name May Correspond to Different Shapes

The same library kernel may be reused by multiple Linear modules. You need to combine M, N, K, call count, and parent events to map it to a model layer.

6.2 High Total Time May Just Mean Many Calls

A 130 us kernel called ten thousand times will rank ahead of a 5 ms kernel. When optimizing, look at all of:

1
2
3
4
单次耗时
调用次数
覆盖层数
端点时间占比

6.3 The Profile Itself Is Not an Endpoint Benchmark

The profiler introduces recording, synchronization, and buffering overhead. The 43.108 ms/token in the trace is used to establish the time composition and cannot directly replace the service TPOT without a profiler.


7. A Real Profiler Shutdown Failure

The target request completed normally, and the trace file was successfully written; but after stop_profile was called, the ROCm runtime triggered a VMFault, and EngineCore subsequently exited.

Therefore, this run was handled as follows:

1
2
3
4
5
6
7
8
已写出的 trace:
作为诊断证据保留

profiled request 的吞吐和 TPOT:
不作为端点性能证据

发生故障后的服务:
不继续复用做 benchmark

This boundary is very important.

“A service crash when stopping the profiler” does not mean that all previously written events are invalid; but it does mean that the profiler lifecycle has changed the service state, and the same process cannot continue to be used for performance A/B.

When collecting long traces on Hygon DCU, it is recommended to treat the profiler service as a one-time diagnostic instance.


8. A Reusable Hygon DCU Trace Analysis Workflow

Step 1: Fix the Request

Choose a single request that can cover the target Prefill shape and enough Decode steps.

Step 2: Record the Coverage Scope

Explicitly write down:

1
2
3
4
Prefill chunk 数量与 token 数
Decode step 数量
是否包含首次编译
是否包含模型加载

Step 3: Bucket by Context

At minimum, divide into:

  • large Prefill;
  • tail Prefill;
  • Decode;
  • sampling and copies outside the execute context.

Step 4: Count Kernel Calls and Total Time

Do not keep only the top-N times; also keep the count and average time.

Step 5: Divide by Decode Steps

Convert the total call count and total time into:

1
2
launches/token
ms/token

Step 6: Map Back to Model Layers

Check whether the call count can be fully summed from the MLP, GDN, Attention, and lm_head layer counts.

Step 7: Calculate the Physical Upper Bound

Estimate for candidate optimizations:

1
2
3
maximum endpoint saving
= current bucket time
× removable fraction

Step 8: Start a Clean Service for A/B

Profiling is responsible for locating; the endpoint in the same environment without a profiler is responsible for confirming real throughput, TTFT, TPOT, and output consistency.


9. What This Means for Hygon DCU Developers

This trace proves that in the gfx936 local software stack, the PyTorch/ROCm-compatible trace path can simultaneously observe:

  • Tensile GEMM;
  • custom HIP kernels;
  • Triton kernels;
  • CUDAGraph replay;
  • Attention and GDN paths;
  • Prefill and Decode contexts.

But the data volume is large. What is truly useful is not generating a trace file, but compressing 60,124 kernel events into a verifiable model structure:

1
2
3
4
176 次 LLMM1/token
129 次 rocBLAS GEMV/token
38.078 ms Linear/token
88.33% Decode 占比

Only when the call count, layer count, and time all close does the profile turn from a “pretty timeline” into engineering evidence that can guide Hygon DCU optimization.


10. Conclusion

From 235,964 trace events, the local reconstruction yielded:

  • three q=4096 Prefill chunks;
  • one q=1685 tail;
  • 60 Decode steps;
  • 176 LLMM1 calls per token;
  • 129 rocBLAS GEMV calls per token;
  • Linear totaling 38.078 ms/token;
  • Linear accounting for 88.33% of Decode.

The core of this article is not a kernel ranking, but a Hygon DCU profiling method:

First split phases by execution context, then reconstruct the model structure using call counts, and finally convert total time into a per-token physical budget.

Profiling is used to decide where it is worth doing work; endpoint A/B is used to decide whether the work is actually effective. The two kinds of evidence cannot be mixed.


The data in this article comes from a local gfx936 diagnostic trace with a clear coverage scope. Profiler execution has additional overhead; the profile times in this article are only used for operator attribution and budget analysis and do not represent the final throughput of a service without a profiler.