SGLang Torch Profiler 源码地图:从入口到分布式 Trace 的完整剖析链路
【免费下载链接】sglangSGLang is a high-performance serving framework for large language models and multimodal models.项目地址: https://gitcode.com/GitHub_Trending/sg/sglang
SGLang 内置了一套完整的在线性能剖析(live profiling)能力,允许在服务运行期间对 prefill、decode 等 forward 阶段进行采样,并输出 Chrome Trace 格式(.trace.json.gz)的分布式 profile 文件。本文以仓库内.claude/skills/llm-torch-profiler-analysis/references/source-map.md为骨架,结合当前仓库源码逐层拆解"客户端入口 → Scheduler 侧 trace 写入 → 分布式合并"的完整调用链,并给出可直接复制的命令与参数说明。读完本文,你将掌握如何通过三种客户端入口发起剖析、如何理解TP/DP/PP/EP与阶段后缀组成的输出文件名、如何用SGLANG_PROFILE_V2获得按阶段(stage-scoped)的独立 trace,以及如何基于真实测试用例验证整个流程。
一、概览:剖析功能的源码分层
从源码结构看,SGLang 的剖析功能大致分为三层:
- 客户端入口层:负责构造参数并向服务端 HTTP 接口发起请求,包括 python/sglang/profiler.py(交互式 CLI)、python/sglang/test/send_one.py(最小请求路径)、python/sglang/benchmark/serving.py(基准测试路径,对应旧入口
python/sglang/bench_serving.py)。 - Scheduler 侧 trace 写入层:负责真正的 profiler 启停与文件落盘,包括 python/sglang/srt/managers/scheduler_components/profiler_manager.py(v1 管理器)、python/sglang/srt/utils/profile_utils.py(v2 阶段式管理器)、python/sglang/srt/utils/profile_merger.py(多卡 trace 合并)。
- 验证与文档层:docs/docs/developer_guide/benchmark_and_profiling.mdx 为官方规范文档,test/registered/profiling/test_start_profile.py 验证
/start_profile行为。
需要说明的是:source-map 中记录的
python/sglang/srt/managers/scheduler_profiler_mixin.py在当前仓库中已迁移为python/sglang/srt/managers/scheduler_components/profiler_manager.py,bench_serving.py也已重构为 python/sglang/benchmark/serving.py 的兼容入口,阅读源码时请以迁移后的路径为准。
二、客户端入口一:sglang.profiler交互式 CLI
python/sglang/profiler.py 是面向人工交互的最小剖析入口,用法为:
python3 -m sglang.profiler其核心逻辑集中在run_profile()函数中,完整参数如下:
| 参数 | 默认值 | 说明 |
|---|---|---|
--url | http://localhost:30000 | 服务端地址 |
--output-dir | $SGLANG_TORCH_PROFILER_DIR(默认/tmp) | trace 输出目录 |
--num-steps | 5 | 剖析的 forward 步数 |
--profile-by-stage | False | 是否对 prefill 与 decode 分别剖析 |
--profile-prefix | 无 | 输出文件名的前缀 |
--cpu | True | 是否采集 CPU 活动 |
--gpu | True | 是否采集 GPU 活动 |
--mem | False | 是否记录内存快照(torch.cuda.memory 快照) |
--rpd | False | 是否使用 ROCm 的 rpd profiler(rocmProfileData) |
--merge-profiles | False | 是否将各 rank 的 trace 合并为单个文件 |
从源码看,run_profile()的执行流程是:
- 输出目录取
--output-dir,未指定时回退到环境变量SGLANG_TORCH_PROFILER_DIR(默认/tmp),并在其后追加time.time()子目录(profiler.py第 31-35 行); - 调用
GET {url}/server_info拉取服务端参数,落盘为server_args.json,方便后续复现启动配置(第 42-49 行); - 构造 JSON 请求体(含
output_dir、num_steps、activities、profile_by_stage、merge_profiles、profile_prefix,可选start_step),POST {url}/start_profile(第 53-64 行)。接口会阻塞到指定步数处理完毕、文件生成后才返回,因此命令结束即可直接到目录取 trace。
由于 CLI 的--cpu/--gpu/--mem/--rpd会被组装成activities列表传给后端(第 140-148 行),这也决定了后端实际启动哪些 profiler(详见第四节)。
三、客户端入口二:sglang.test.send_one最小请求路径
当你想"一条命令同时完成发请求与剖析"时,python/sglang/test/send_one.py 是最合适的入口。它的 docstring 直接给出了三个剖析示例:
python3 -m sglang.test.send_one --profile --profile-steps 5 python3 -m sglang.test.send_one --profile --profile-by-stage python3 -m sglang.test.send_one --stop "<|separator|>" "<|eos|>" --max-new-tokens 2048关键机制(send_one.py第 199-208 行):当--profile开启时,脚本会先调用sglang.profiler中的run_profile()启动剖析(默认采集["CPU", "GPU"]),再向/generate发送真实请求。由于/start_profile会阻塞到num_steps个 forward 步完成,剖析窗口天然覆盖了随后的生成过程。可用--random-input-len生成指定 token 长度的随机 prompt(避免 radix cache 命中),确保完整的 prefill 被采集到。
四、客户端入口三:sglang.benchmark.serving基准测试路径
在基准测试场景下,使用 python/sglang/benchmark/serving.py(旧路径python/sglang/bench_serving.py仅为兼容转发表,见其第 1-19 行)。它对应的剖析参数族(第 2485-2524 行附近)包括:
| 参数 | 说明 |
|---|---|
--profile | 开启剖析;注释明确提示需配合SGLANG_TORCH_PROFILER_DIR使用 |
--profile-activities | 采集活动,可选CPU GPU CUDA_PROFILER XPU MEM,默认["CPU","GPU"];MEM会导出 torch.cuda.memory 快照 |
--profile-start-step | 经过多少个 forward 步后开始剖析,用于跳过 warmup |
--profile-steps/--profile-num-steps | 剖析步数;指定后剖析会自动停止 |
--profile-by-stage | 对 prefill / decode 分别剖析 |
--profile-stages | 配合按阶段剖析时,指定感兴趣的阶段(如prefill decode) |
--profile-output-dir | trace 输出目录 |
--profile-prefix | 文件名前缀 |
基准脚本内部通过async_request_profile()(第 843-896 行)构造请求体,转发activities、profile_by_stage、profile_stages、profile_prefix等字段,与 source-map 的描述一致。特别地,PD 分离(prefill/decode disaggregation)模式下,可通过--profile-prefill-url/--profile-decode-url分别指定 prefill worker 与 decode worker 的地址,脚本会用_build_profile_urls()与_call_profile_pd()(第 898-936 行)对两类 worker 独立执行 start/stop。
五、Scheduler 侧:真正的 trace 启停与落盘
无论从哪个入口发起,请求最终都落到 python/sglang/srt/managers/scheduler_components/profiler_manager.py 的SchedulerProfilerManager。它通过_init_profile/_start_profile/_stop_profile三个方法完成状态管理,并由_profile_batch_predicate在每个 forward 批处理时驱动启停判定(第 408-450 行):
start_step语义:profiler_start_forward_ct = max(start_step, get_forward_ct() + 1)(第 142 行),即在 warmup 若干步后才开始;num_steps语义:若同时给了start_step,则target = start + num_steps;否则target = 当前步数 + num_steps + 1(第 144-155 行);- 按阶段剖析:
profile_by_stage=True时分别维护profiler_prefill_ct与profiler_decode_ct,prefill 采样结束后强制 flush(避免 prefill 采集吸收 decode 步,第 424-425 行),decode 阶段则支持SGLANG_PROFILE_BY_STAGE_DECODE_MIN_BS环境变量——当批大小低于该阈值时等待满载再开始采集(第 426-429 行)。
5.1 输出文件名模式:TP/DP/PP/EP 与阶段后缀
这是理解输出结果的关键。_stop_profile中(第 338-354 行)按如下规则构造文件名:
[{profile_prefix}-]{profile_id}-TP-{tp_rank}[-DP-{dp_rank}][-PP-{pp_rank}][-EP-{ep_rank}][-{stage}].trace.json.gz其中profile_id由run_profile传入(time.time()目录名),DP/PP/EP段仅在对应并行度大于 1 时追加(保持向后兼容),stage段仅在按阶段剖析时出现(如-EXTEND、-DECODE)。每张卡的 trace 通过export_chrome_trace()落盘,随后torch.distributed.barrier(cpu_group)确保所有 rank 完成写盘(第 359 行)。
5.2 活动类型与对应 profiler
activities列表决定启动哪些底层 profiler(_start_profile第 180-276 行):
| 活动 | 底层实现 | 输出 |
|---|---|---|
CPU/GPU | torch.profiler.profile | .trace.json.gz(Chrome Trace) |
MEM | torch.cuda.memory._record_memory_history | -memory.pickle(可用 torch memory_viz 可视化) |
CUDA_PROFILER | torch.cuda.cudart().cudaProfilerStart/Stop | 供nsys等外部工具配合使用的 CUDA profiler 开关 |
RPD | ROCmrpdTracerControl | rpd-{timestamp}-TP-{rank}.trace.json.gz(先写trace.rpd再转换) |
XPU | torch.profiler.ProfilerActivity.XPU | 同上 |
CUDA_PROFILER活动在 test/registered/profiling/test_start_profile.py 中通过TestStartProfileWithNsys单独验证(该测试类会先检查nsys是否可用),并注意 ROCm 平台因 HIP runtime 在 CUDA graph replay 下可能死锁,测试启动时默认追加--disable-cuda-graph(测试第 50-64 行)。_start_profile中 torch profiler 的with_stack与record_shapes默认值分别来自环境变量SGLANG_PROFILE_WITH_STACK(默认True)与SGLANG_PROFILE_RECORD_SHAPES(默认True),定义于 python/sglang/srt/environ.py 第 433-439 行。
5.3 直接调用 HTTP API
不依赖任何客户端脚本,也可直接发 HTTP 请求(官方文档benchmark_and_profiling.mdx中的 HTTP 端点章节):
# 立即开始剖析 10 步(步数到达后自动停止并落盘) curl -X POST http://localhost:30000/start_profile \ -H 'Content-Type: application/json' \ -d '{"num_steps": "10", "activities": ["CPU", "GPU"]}' # 先 warmup 5 步再剖析 10 步 curl -X POST http://localhost:30000/start_profile \ -H 'Content-Type: application/json' \ -d '{"start_step": "5", "num_steps": "10"}' # 不指定 num_steps:手动停止 curl -X POST http://localhost:30000/start_profile curl -X POST http://localhost:30000/stop_profile # 开启详细标注(在 step span 中折叠各阶段聚合指标) curl -X POST http://localhost:30000/start_profile \ -H 'Content-Type: application/json' \ -d '{"num_steps": "10", "detailed_annotations": true}'后端入口位于 python/sglang/srt/entrypoints/http_server.py(start_profile请求经SchedulerProfilerManager._profile分发,见profiler_manager.py第 452-485 行)。
六、分布式 trace 合并:ProfileMerger
多卡并行(TP/DP/PP/EP)时每张 rank 各产出一个 trace 文件,逐文件分析非常不便。python/sglang/srt/utils/profile_merger.py 的ProfileMerger解决这一问题:--merge-profiles开启后,_merge_profile_traces()(profiler_manager.py第 281-311 行)会在 rank 0(且 DP/PP/EP rank 均为 0)上执行合并,产出merged-{profile_id}.trace.json.gz。
合并逻辑要点:
- 文件发现:用
{profile_id}*.trace.json.gz通配并排除合并产物与-memory.pickle(profile_merger.py第 84-101 行); - rank 标注:从文件名正则提取
TP/DP/PP/EP信息,将每个事件的pid重写为[DP00-EP00-PP00-TP00]形式的标签(第 103-122、143-159 行),方便在 trace viewer 中区分各 rank; - 排序权重:通过
sort_index让 DP 优先级最高、TP 最低(乘数依次为1e8 / 1e6 / 1e4 / 1e2,第 29-34 行),保证 viewer 中的视觉顺序稳定。
source-map 特别提醒:合并后的 trace 与单 rank 的 trace 应区别对待——合并文件聚合了多 rank 事件并改写了 PID,做单卡性能归因时仍应以 rank-local trace 为准。
七、Profile v2:按阶段输出独立 trace
设置环境变量SGLANG_PROFILE_V2=1后,SchedulerProfilerManager会切换到 python/sglang/srt/utils/profile_utils.py 中的ProfileManager(v1/v2 分支见profiler_manager.py第 59-64 行)。两者核心差异:
- v2 采用
_StageBasedTrigger(profile_utils.py第 191-241 行)按 prefill/decode 阶段分别计数与启停:每个阶段独立达到num_steps目标后触发on_stop,并允许通过profile_stages只关注感兴趣阶段(默认["prefill", "decode"],第 133 行); - 每个阶段通过
output_suffix=f"-{stage}"生成带阶段后缀的独立文件(第 160 行); - v2 的
configure()当前断言start_step is None、profile_by_stage=True、merge_profiles=False(第 111-115 行),即该路径聚焦于阶段式剖析,尚未支持部分 v1 参数; - 阶段判定由
_get_stage_from_forward_mode完成:is_prefill()→prefill,is_decode()→decode,idle 返回None(第 177-185 行)。
此外,profile_utils.py还包含两个独立能力:
- CUDA graph 捕获 trace:设置
SGLANG_ENABLE_CUDA_GRAPH_CAPTURE_TRACE=1后,export_cuda_graph_capture_trace()会把 CUDA graph 捕获期的 profiler 记录(要求record_shapes=True)导出为graph_capture_profile/cuda_graph_capture-{runner_name}-TP-{tp_rank}.json.gz(第 52-70 行),按 runner 类与 TP rank 命名以避免并发覆盖; - step span 命名:
build_step_span_name()生成step[EXTEND bs={bs} toks={toks}]或step[{mode.name} bs={bs}]形式的 span 名(第 463-488 行),开启detailed_annotations时还会折叠各阶段聚合指标,直接对应/start_profile请求体中的detailed_annotations字段。
八、验证与测试:/start_profile的行为契约
test/registered/profiling/test_start_profile.py 是剖析链路的官方行为验证,覆盖了 v1 路径的关键语义:
test_start_profile_1:以start_step="15", num_steps=5发起,验证"延迟到第 15 步后开始、采集 5 步"并落盘;test_start_profile_2:不带参数启动,验证停止前目录为空、/stop_profile后目录非空;test_start_profile_3:仅num_steps=5,验证自动停止语义;TestStartProfileWithNsys.test_start_profile_cuda_profiler:验证CUDA_PROFILER活动(依赖nsys可用性)。
测试通过envs.SGLANG_TORCH_PROFILER_DIR.set(OUTPUT_DIR)把输出导向测试目录,运行方式(test_start_profile.py第 1-11 行):
cd test/srt python3 -m unittest test_start_profile.TestStartProfile python3 -m unittest test_start_profile.TestStartProfileWithNsys.test_start_profile_cuda_profiler九、完整工作流与实用建议
综合上述链路,一次典型的剖析流程是:
# 1. 启动服务(trace 输出目录由环境变量控制,默认 /tmp) SGLANG_TORCH_PROFILER_DIR=./profiles python3 -m sglang.launch_server \ --model-path <model> --host 0.0.0.0 --port 30000 # 2. 方式 A:最小验证(单请求 + 剖析) python3 -m sglang.test.send_one --profile --profile-steps 5 --random-input-len 2048 # 方式 B:按阶段分别采样 python3 -m sglang.test.send_one --profile --profile-by-stage # 方式 C:基准负载 + 剖析(多卡合并) python3 -m sglang.benchmark.serving --model <model> --num-prompts 100 \ --profile --profile-activities CPU GPU --profile-num-steps 10 \ --merge-profiles --profile-output-dir ./profiles # 3. 分析产物 ls ./profiles/*/ # server_args.json + *.trace.json.gz # 用 chrome://tracing 或 Perfetto 打开 .trace.json.gz实用要点:
- 保留
server_args.json:它记录了服务启动参数(profiler.py自动写入),复现与比对实验必备; - prefill/decode 分别采样:二者批大小、计算模式差异巨大,混采难以定位瓶颈,优先
--profile-by-stage;decode 阶段可用SGLANG_PROFILE_BY_STAGE_DECODE_MIN_BS控制最小批大小,等待负载满载再采样; - warmup 与随机 prompt:用
--profile-start-step跳过首段不稳定步,用--random-input-len规避 radix cache 命中,确保真实计算被采样; - 多卡场景:先分析 rank-local trace 做单卡归因,再用
--merge-profiles查看全局时间线; - ROCm/NPU 适配:ROCm 用
RPD活动(--rpd),NPU 平台会自动 patch 到torch_npu.profiler(见profiler_manager.py第 33-43 行)。
十、结语
从profiler.py的run_profile()到SchedulerProfilerManager的步数判定,再到ProfileMerger的多 rank 合并与ProfileManager的阶段式 v2 路径,SGLang 的剖析链路是一条完整、可观测、可验证的闭环。官方文档 docs/docs/developer_guide/benchmark_and_profiling.mdx 是总入口,本文所引源码与测试即为该文档的落地实现。遇到 trace 文件无法解释的问题时,建议对照 python/sglang/srt/managers/scheduler_components/profiler_manager.py 与 python/sglang/srt/utils/profile_utils.py 的启停逻辑逐段核对,通常能快速定位是采样窗口、活动类型还是文件名解析的问题。
【免费下载链接】sglangSGLang is a high-performance serving framework for large language models and multimodal models.项目地址: https://gitcode.com/GitHub_Trending/sg/sglang
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考