https://zhuanlan.zhihu.com/p/1981436859470074335?share_code=bZtoKsWUIpcD&utm_psn=1981881255978096583

我直接照搬过来备份一下,老师写的还是很好,只不过真正看起来确实比较麻烦。

这篇 blog 主要是看 序列图如何分析,包括:

我们会在其中穿插一些内容。

  1. GPU场景

GPU + Qwen(Dense) + tracing场景。

首先用 vllm 收集 profiling:

from vllm import LLM, SamplingParams
import torch
import torch.profiler as profiler
import os
os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn"


if __name__ == "__main__":
    with profiler.profile(
        activities=[profiler.ProfilerActivity.CPU, profiler.ProfilerActivity.CUDA],
        record_shapes=True,          # 记录张量形状
        profile_memory=True,         # 记录内存分配/释放
        with_stack=False              # close 调用栈
        ) as prof:
        model_name = "/home/kaiyuan/models/Qwen2.5-7B-Instruct" 
        llm = LLM(model=model_name, dtype='float16', tensor_parallel_size=4)
        prompts = [
        "Hello, my name is",
        "The capital of France is",
        "The future of AI is",
        "Please introduce vLLM framework"
        ]
    # 设置采样参数
        sampling_params = SamplingParams(
        temperature=0.8,  # 控制生成文本的随机性,值越高越随机
        top_p=0.95,  # 控制采样范围,值越高生成文本越多样化
        max_tokens=50,  # 生成的最大 token 数量
        n=1
        )

        outputs = llm.generate(prompts, sampling_params)
    prof.export_chrome_trace("trace.json")

上述是 PyTorch Profiler,是在 pytorch 级别的 CPU 算子/ CUDA kernel 级别的分析,而 nsys/ncu/nvprof 是在 CUDA Driver 级别的分析,Linux perf/ftrace 等是在 cpu 级别的分析。

正常运行后,能够获得"trace.json"文件,直接拖拽到浏览器的https://ui.perfetto.dev/ (推荐)、如果是chrome浏览器可用chrome://tracing/

profiling导入之后,可找到主机端(python层)的执行时序、GPU端(stream)的执行时序:

Image

先看一下python层执行情况。在profiling中找到序列图进行放大,我们可以定位一个完整的层的位置,比如用耗时较长的attention进行隔断,截取一段内容进行分析

Image

Qwen2.5 dense的模型主体是GQA+FFN,这些层的运算在profiling中可找到对应的位置。

Image

通过放大profiling,找到GAQ在python中的时序图位置。有几个细节:

Timeline 上 kernel 之间的空白不等价于 GPU 停机。很多时候 GPU 正在执行其他 stream 的 kernel、进行 DMA 拷贝、等待 sync、或者 profiler 没显示某些事件。只有当 SM 利用率下降到 0 时,才能判断 GPU 真正 idle。

这里有些需要澄清的地方,在 TP(Tensor Parallel,相对于 DP/PP) 中,TP 发生在 大矩阵维度,不是在单个 head 内部。Multi-head attention 是模型设计上的逻辑维度切分;Tensor Parallel 是运行时为了利用多 GPU 资源而进行的权重张量物理切分。两者互不重叠,一个负责表示能力,一个负责计算并行性。
QKV projection 的权重按列拆分后,每个 GPU 计算部分 heads;O projection 的权重按行拆分,因此每个 GPU 对最终 hidden_dim 输出贡献一部分;这些部分结果需要 All-Reduce(sum) 来恢复完整的 hidden_dim 输出。

Image

KV共用一个线性层,所以后面有个split操作(一个大矩阵 concat 权重 qkv);Q与K在算attention前需要进行RoPE运算。

Image

放大attention(图片中的unified attention)会看到许多细节操作,比如split、view、empty等,也能找到两个主要操作:KV cache保存、paged attention运算。

Image

FFN层是在attention计算之后,找到RMS Norm并进行放大,看到的细节:

Image

stream层的执行情况主要是反映GPU的kernel下发下去之后的执行逻辑,通过放大同样能够找到每个上层模块触发的底层操作。如下所示为FFN的线性运算的kernel时序。

Image

线性层的计算调用的是cutlass的kernel:

Image

silu的计算kernel如下所示,可看到输入数据的类型。

Image

通过测算可以捕获多个层的执行时间,如下所示捕获到一个GQA+FFN在python层的总执行耗时近七百多us。

Image

  1. NPU 场景

NPU是华为昇腾的芯片,搭配看profiling的专用工具是Insight,GPU有对应的Nsight。用专用工具的能够看到更加细粒度的时序信息。在Insight中导入一份Profiling后可以显示多个层的时序:

还有辅助的时序:通信层Communication、通算重叠度OverlapAnalysis、AI core Freq、HBM、LLC、QoS等。

Image

模型时序分析:如果开了堆栈采集,能看到火焰图,类似py-spy的采集,内容比较详细:

Image

当然也可以采集不带堆栈的,数据更接近实际数据。以DeepSeekV3的模型为例,一个MLA+MoE层运算的时序图如下:

python层的MoE与MLA之间有大段的to/iterm时序占用大量时间,实际这些是一些流的同步等待(与GPU例子不同)。
操作的名称大多以npu开头。

Image

可以找关键ops,比如attention运算。辅助能够找到一个MLA模块的如下图所示,类似地能够找到其它模块。

Image

一般而言上层调用到底层执行会存在一定的时间滞后,这个可以通过选中具体的ops观测,如集群通信,从python层发起到CANN再到通信时序之间有个时间差异。

Image

单看NPU芯片上的流执行,会看到许多event wait,一般是流的操作之间排队使用片上资源:

Image

检查某个算子的计算时间和输入、输出shape:

Image

如果采集了多个step,还能看到step与step之间的主机下发/尾端处理时间,若没有多流掩盖硬件侧能找到一个较长的空闲(free)时间。

Image

当完成一些优化后,我们可以利用时序图比照优化前后的计算差异。

操作优化:比如,在MoE计算完成后如果有一个AllReduce操作,能够拆解为Reducescatter + allgather,并且allgather位置可以移动到比较靠后的位置。

Image

重叠度优化:在硬件层观测时,可以看到一些优化手段带来的时序变化,这里以micro batch双流运算示例,能够看到两个stream分别执行了完整的MoE+MLA,只是时间上面有错位。

Image

这样带来的好处是计算通信掩盖比例得到提升,通过Overlap Analysis时序能够看到变化:

Image

Insight工具除了提供时序(timeline)分析,还可看更详细的内存占比、操作占比等分析,随着分析的进行,这些数据都能够比较好的帮助开发人员更好的挖掘一款硬件的潜力、或者调整模型算法的设计。