SGLang 在 AMD Instinct GPU 上的性能剖析实战:RPD 与 PyTorch Profiler 全流程指南
【免费下载链接】sglangSGLang is a high-performance serving framework for large language models and multimodal models.项目地址: https://gitcode.com/GitHub_Trending/sg/sglang
本文档配套脚本与补丁位于仓库 3rdparty/amd/profiling 目录,是 AMD 官方为 SGLang 推理系统(Infer System)编写的一篇性能剖析(Profiling)应用说明(AppNote)。它面向搭载 AMD Instinct GPU 的 ROCm 环境,同时也兼容 NVIDIA/CUDA 环境,详细给出 RPD(ROCM Profile Data)与 PyTorch Profiler 两种剖析工具从安装、代码改造(patch)、服务启动到压测触发、结果可视化的完整闭环。读完本文,你将掌握:如何为 SGLang 服务端打上剖析补丁、如何用
loadTracer.sh预加载 RPD 追踪器、如何通过/start_profile与/stop_profileHTTP 接口精确控制剖析窗口,以及如何把 RPD 文件转换成 Perfetto 可加载的 JSON 并定位 GPU kernel、CPU 活动与负载不均衡等性能问题。
一、概述:两种剖析方法的选择
SGLang 是一个面向大语言模型与多模态模型的高性能推理服务框架。当需要在 AMD Instinct GPU 上定位性能瓶颈(例如 GPU kernel 耗时、跨进程负载不均衡、调度空隙 bubbles)时,3rdparty/amd/profiling/PROFILING.md 提供了两条主路径:
- RPD Profiler(rocmProfileData):低开销、跨平台的分析器。同一套代码注入不仅能在 ROCm/AMD GPU 上工作,也能在 CUDA/NVIDIA GPU 上工作,适合大规模、长时间采样。
- PyTorch Profiler:Torch 官方内置的剖析工具,适合快速查看算子级耗时,默认只记录 TP rank 0,可自行修改补丁扩大记录范围。
两种方法共用同一套服务端剖析控制接口(/start_profile、/stop_profile),且文档明确提示:不要把 RPD 与 PyTorch Profiler 同时启用,以免互相干扰。
与当前仓库源码的对应关系
值得说明的是,原文档对应的是一套较早期的 SGLang 代码结构(patch 直接修改scheduler.py、tokenizer_manager.py、server.py)。在当前仓库中,SGLang 已将剖析能力收敛为独立的 SchedulerProfilerManager,并通过activities参数同时支持CPU、GPU、RPD、MEM、CUDA_PROFILER等多种活动类型——其中"RPD"分支正是对本文所述 RPD 工作流的内建化封装(见 profiler_manager.py),rpdTracerControl、RocpdSchema、rpd_to_chrome_trace等关键符号与文档中的手工注入代码一一对应。因此本文既保留原文档可直接复现的手工 patch 流程,也会在相应小节对照当前源码说明演进后的用法。
二、RPD Profiler 剖析 SGLang:完整分步流程
2.1 安装 RPD(带 rpd.patch)
首先在容器内安装系统依赖,并用补丁修正 RPD 的构建配置。执行 install_rpd.sh 中的命令:
# download and install RPD apt update && apt install -y sqlite3 libsqlite3-dev libfmt-dev # install rpd module git clone https://github.com/ROCmSoftwarePlatform/rocmProfileData cd rocmProfileData git checkout 976899e9c6dbc6dd2bccf770818e4e44125590ac git apply rpd.patch make && make install cd rocpd_python && python setup.py install && cd .. cd rpd_tracer && make clean;make install && python setup.py install && cd ..其中git checkout 976899e9c6dbc6dd2bccf770818e4e44125590ac将 rocmProfileData 固定到已验证的提交,保证补丁可干净应用。rpd.patch的内容如下,它把rpd_tracer的 ROCm 构建源文件从RoctracerDataSource.cpp RocmSmiDataSource.cpp缩减为仅保留RoctracerDataSource.cpp:
diff --git a/rpd_tracer/Makefile b/rpd_tracer/Makefile index e9d9feb..b2e9e1a 100644 --- a/rpd_tracer/Makefile +++ b/rpd_tracer/Makefile @@ -16,7 +16,7 @@ ifneq (,$(HIP_PATH)) $(info Building with roctracer) RPD_LIBS += -L/opt/rocm/lib -lroctracer64 -lroctx64 -lamdhip64 -lrocm_smi64 RPD_INCLUDES += -I/opt/rocm/include -I/opt/rocm/include/roctracer -I/opt/rocm/include/hsa - RPD_SRCS += RoctracerDataSource.cpp RocmSmiDataSource.cpp + RPD_SRCS += RoctracerDataSource.cpp RPD_INCLUDES += -D__HIP_PLATFORM_AMD__ endif去掉RocmSmiDataSource.cpp意味着编译时不再引入 ROCm SMI 数据源,从而规避该组件在特定环境下导致的构建或运行问题。安装完成后,rocpd.schema(python -m rocpd.schema)、rpdTracerControl与librpd_tracer.so将可供调用。
2.2 部署 loadTracer.sh 预加载追踪器
将 loadTracer.sh 放到python/sglang目录(原文档为/sglang/python/sglang,在当前仓库中对应python/sglang)。该脚本的核心职责是:创建 RPD 追踪数据库、设置环境变量、并通过LD_PRELOAD预加载librpd_tracer.so,使目标进程在启动瞬间即被注入追踪能力:
#!/bin/bash ################################################################################ # Copyright (c) 2021 - 2023 Advanced Micro Devices, Inc. All rights reserved. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. ################################################################################ OUTPUT_FILE="trace.rpd" if [ "$1" = "-o" ] ; then OUTPUT_FILE=$2 shift shift fi if [ -e ${OUTPUT_FILE} ] ; then rm ${OUTPUT_FILE} fi python3 -m rocpd.schema --create ${OUTPUT_FILE} if [ $? != 0 ] ; then echo "Error: Could not create rpd file. Please run 'python setup.py install' from the rocpd_python dir" exit fi export RPDT_FILENAME=${OUTPUT_FILE} export RPDT_AUTOSTART=0 LD_PRELOAD=librocm-smi_64:librpd_tracer.so "$@"要点说明:
OUTPUT_FILE默认trace.rpd,可用loadTracer.sh -o my.trace.rpd ...覆盖;python3 -m rocpd.schema --create负责按 RPD 的 SQLite schema 初始化追踪数据库文件,若失败会提示先执行rocpd_python目录下的python setup.py install;RPDT_AUTOSTART=0表示追踪不随进程启动而自动开始,而是等待代码中的rpdTracerControl显式触发——这正是后面start_profile/stop_profile控制剖析窗口的机制基础;LD_PRELOAD同时预加载librocm-smi_64与librpd_tracer.so。
2.3 打补丁:注入 RPD 剖析代码(基础版)
使用git apply rpd_profile_server_enable.patch应用补丁。该补丁的核心目的是让 SGLang 原有的start_profile/stop_profile剖析钩子改走 RPD 路径,主要修改 python/sglang/srt/managers/scheduler.py:
diff --git a/python/sglang/srt/managers/scheduler.py b/python/sglang/srt/managers/scheduler.py index 62d1ff9..9021c01 100644 --- a/python/sglang/srt/managers/scheduler.py +++ b/python/sglang/srt/managers/scheduler.py @@ -71,6 +71,8 @@ from sglang.srt.utils import ( suppress_other_loggers, ) from sglang.utils import get_exception_traceback +from rpdTracerControl import rpdTracerControl +rpdTracerControl.skipCreate() + logger = logging.getLogger(__name__) @@ -245,6 +247,7 @@ class Scheduler: ], with_stack=True, ) + self.rpd = rpdTracerControl() @torch.inference_mode() def event_loop(self): @@ -1027,15 +1030,24 @@ class Scheduler: def start_profile(self) -> None: if self.profiler is None: raise RuntimeError("Profiler is not enabled.") - self.profiler.start() + #self.profiler.start() #block pytorch profiler for rpd profiler enabling + if self.tp_rank == 0 or self.tp_rank == 1: + self.rpd.start() + self.rpd.rangePush("", "rpd profile range", "") + logger.info("rpd is enabled") def stop_profile(self) -> None: if self.profiler is None: raise RuntimeError("Profiler is not enabled.") - self.profiler.stop() - self.profiler.export_chrome_trace( - self.torch_profiler_trace_dir + "/" + str(time.time()) + ".trace.json.gz" - ) + #self.profiler.stop() + #self.profiler.export_chrome_trace( + # self.torch_profiler_trace_dir + "/" + str(time.time()) + ".trace.json.gz" + #) + if self.tp_rank ==0 or self.tp_rank ==1: + self.rpd.rangePop() + self.rpd.stop() + self.rpd.flush() + logger.info("rpd is done") logger.info("Profiler is done")代码注入要点:
- 模块加载时执行
rpdTracerControl.skipCreate(),跳过 RPD 默认的自动创建逻辑,避免与loadTracer.sh预先创建的trace.rpd冲突; - Scheduler 初始化时创建
self.rpd = rpdTracerControl(); start_profile中注释掉self.profiler.start()(即禁用 PyTorch Profiler),改为仅在tp_rank == 0 or tp_rank == 1两个 rank 上执行self.rpd.start()并rangePush一个命名区间"rpd profile range";stop_profile中注释掉 PyTorch Profiler 的 stop 与 chrome trace 导出,改为在这两个 rank 上执行rangePop()、stop()、flush()。
Common Notes 1:为什么只记录 2 个 rank?
文档特别强调:示例使用 TP=8,但补丁故意只在 tp_rank=0/1 上记录 RPD 剖析。原因是 Perfetto 的流式加载模式最多只能可视化约 8GB 的 JSON 文件;只记录 2 个 rank,一方面仍能检查 rank 之间是否存在问题(例如负载不均衡 load imbalance、NCCL 通信问题),另一方面可以在 JSON 文件达到 8GB 之前记录相对更长的剖析时长。
2.4 高级调试:启用 CPU/Python 活动追踪
当需要定位跨 GPU 进程的负载不均衡根因、调度空隙(bubbles)等棘手问题时,仅靠 GPU kernel 信息不够,需要捕获更多 CPU 与 Python 活动。此时应改用git apply rpd_profile_server_enable_wCPU_activities.patch,该补丁共修改 3 个文件,分别为 python/sglang/srt/managers/scheduler.py、python/sglang/srt/managers/tokenizer_manager.py 与 server 入口(对应历史版本中的 python/sglang/srt/server.py 位置,当前仓库已重构,参见下文演进说明):
diff --git a/python/sglang/srt/managers/scheduler.py b/python/sglang/srt/managers/scheduler.py index 62d1ff9..2edb427 100644 --- a/python/sglang/srt/managers/scheduler.py +++ b/python/sglang/srt/managers/scheduler.py @@ -71,6 +71,8 @@ from sglang.srt.utils import ( suppress_other_loggers, ) from sglang.utils import get_exception_traceback +from rpdTracerControl import rpdTracerControl +rpdTracerControl.skipCreate() logger = logging.getLogger(__name__) @@ -245,6 +247,7 @@ class Scheduler: ], with_stack=True, ) + self.rpd = rpdTracerControl() @torch.inference_mode() def event_loop(self): @@ -1027,15 +1030,26 @@ class Scheduler: def start_profile(self) -> None: if self.profiler is None: raise RuntimeError("Profiler is not enabled.") - self.profiler.start() + #self.profiler.start() + logger.info("torch profiler is disabled") + if self.tp_rank == 0 or self.tp_rank == 1: + self.rpd.setPythonTrace(True) + self.rpd.start() + self.rpd.rangePush("", "scheduler", "") + logger.info("rpd is enabled inside scheduler profiling") def stop_profile(self) -> None: if self.profiler is None: raise RuntimeError("Profiler is not enabled.") - self.profiler.stop() - self.profiler.export_chrome_trace( - self.torch_profiler_trace_dir + "/" + str(time.time()) + ".trace.json.gz" - ) + #self.profiler.stop() + #self.profiler.export_chrome_trace( + # self.torch_profiler_trace_dir + "/" + str(time.time()) + ".trace.json.gz" + #) + if self.tp_rank ==0 or self.tp_rank ==1: + self.rpd.rangePop() + self.rpd.stop() + self.rpd.flush() + logger.info("rpd is done inside scheduler") logger.info("Profiler is done")与基础版的差异在于调用self.rpd.setPythonTrace(True)开启 Python 调用栈追踪,并将 range 命名为"scheduler"。同时该补丁在 tokenizer_manager 与 server 入口也注入追踪,形成server → tokenizer_manager → scheduler三层 span:
diff --git a/python/sglang/srt/managers/tokenizer_manager.py b/python/sglang/srt/managers/tokenizer_manager.py index 2621ccd..181df85 100644 --- a/python/sglang/srt/managers/tokenizer_manager.py +++ b/python/sglang/srt/managers/tokenizer_manager.py @@ -58,6 +58,10 @@ from sglang.srt.sampling.sampling_params import SamplingParams from sglang.srt.server_args import PortArgs, ServerArgs from sglang.srt.utils import is_generation_model, is_multimodal_model +from rpdTracerControl import rpdTracerControl +rpdTracerControl.skipCreate() + + asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) logger = logging.getLogger(__name__) @@ -514,10 +518,20 @@ class TokenizerManager: self.send_to_scheduler.send_pyobj(req) def start_profile(self): + rpd = rpdTracerControl() + rpd.setPythonTrace(True) + rpd.start() + rpd.rangePush("", "tokenizer_manager", "") + logger.info("tokenizer_manager rpd profiling started!") req = ProfileReq.START_PROFILE self.send_to_scheduler.send_pyobj(req) def stop_profile(self): + rpd = rpdTracerControl() + rpd.rangePop() + rpd.stop() + rpd.flush() + logger.info("rpd profiling is done inside tokenizer_manager!") req = ProfileReq.STOP_PROFILE self.send_to_scheduler.send_pyobj(req)server 入口部分(历史版本 python/sglang/srt/server.py 中的 HTTP 处理器,当前仓库对应 http_server.py 的/start_profile、/stop_profile路由):
diff --git a/python/sglang/srt/server.py b/python/sglang/srt/server.py index 7111c93..2bd722c 100644 --- a/python/sglang/srt/server.py +++ b/python/sglang/srt/server.py @@ -30,6 +30,8 @@ import threading import time from http import HTTPStatus from typing import Dict, List, Optional, Union +from rpdTracerControl import rpdTracerControl +rpdTracerControl.skipCreate() # Fix a bug of Python threading setattr(threading, "_register_atexit", lambda *args, **kwargs: None) @@ -152,6 +154,11 @@ async def flush_cache(): @app.post("/start_profile") async def start_profile(): """Start profiling.""" + rpd = rpdTracerControl() + rpd.setPythonTrace(True) + rpd.start() + rpd.rangePush("", "server rpd profile range", "") + logger.info("rpd profiling started in server.py!") tokenizer_manager.start_profile() return Response( content="Start profiling.\n", @@ -164,6 +171,11 @@ async def start_profile(): async def stop_profile(): """Stop profiling.""" tokenizer_manager.stop_profile() + rpd = rpdTracerControl() + rpd.rangePop() + rpd.stop() + rpd.flush() + logger.info("rpd profiling is done in server.py!") return Response( content="Stop profiling. This will take some time.\n", status_code=200,注意:三个进程/协程各自创建rpdTracerControl()实例并开启追踪,意味着 CPU 追踪会覆盖整个请求链路——从 HTTP 入口、tokenizer 预处理到 scheduler 的 batch 调度与模型前向。这在排查"瓶颈到底出在调度还是 kernel 执行"时非常有用。
2.5 准备示例模型:dummy_grok1
文档以 grok1 剖析为例,在3rdparty/amd/profiling目录内创建一个dummy_grok1目录,内含config.json(若使用仓库提供的 server.sh,需把该目录复制到--model-path指向的路径):
cat ../dummy_grok1/config.json { "architectures": [ "Grok1ModelForCausalLM" ], "embedding_multiplier_scale": 78.38367176906169, "output_multiplier_scale": 0.5773502691896257, "vocab_size": 131072, "hidden_size": 6144, "intermediate_size": 32768, "max_position_embeddings": 8192, "num_experts_per_tok": 2, "num_local_experts": 8, "num_attention_heads": 48, "num_hidden_layers": 64, "num_key_value_heads": 8, "head_dim": 128, "rms_norm_eps": 1e-05, "rope_theta": 10000.0, "model_type": "mixtral", "torch_dtype": "bfloat16" }该配置文件描述了一个 MoE 结构的 64 层 Transformer(model_type为mixtral,8 个本地专家、每 token 激活 2 个专家),配合--load-format dummy与--quantization fp8使用,无需真实权重即可启动服务用于剖析验证。
2.6 启动服务端(server.sh)
在 docker 容器的第一个终端中,用 RPD 启用的脚本启动服务:server.sh
#!/bin/bash # export SGLANG_TORCH_PROFILER_DIR=/data/sglang/ export SGLANG_TORCH_PROFILER_DIR=/sgl-workspace/sglang/profile/ # Get the current timestamp TIMESTAMP=$(date +"%Y%m%d_%H%M%S") # Define the log file with a timestamp LOGFILE="sglang_server_log_$TIMESTAMP.json" # Run the Python command and save the output to the log file loadTracer.sh python3 -m sglang.launch_server \ --model-path /sgl-workspace/sglang/dummy_grok1 \ --tokenizer-path Xenova/grok-1-tokenizer \ --load-format dummy \ --quantization fp8 \ --tp 8 \ --port 30000 \ --disable-radix-cache 2>&1 | tee "$LOGFILE"关键启动参数说明:
| 参数 | 值 | 含义 |
|---|---|---|
loadTracer.sh | 前置包装 | 通过LD_PRELOAD注入 RPD 追踪器,是 RPD 剖析的必需环节 |
--model-path | dummy_grok1 目录 | 使用步骤 2.5 的虚拟模型目录(记得改成实际路径) |
--tokenizer-path | Xenova/grok-1-tokenizer | grok1 的分词器 |
--load-format | dummy | 不加载真实权重,快速拉起服务 |
--quantization | fp8 | 启用 FP8 量化路径 |
--tp | 8 | 8 卡张量并行 |
--port | 30000 | 服务端口,供后续 curl 剖析接口与压测使用 |
--disable-radix-cache | — | 关闭 radix cache,简化剖析场景 |
同时导出SGLANG_TORCH_PROFILER_DIR指定默认剖析输出目录(该环境变量在 environ.py 中定义,默认值为/tmp,当前 profiler 实现中output_dir is None时会回退读取该变量,见 profiler_manager.py)。
Common Notes 2
- 记得把
--model-path改为正确的模型路径; - RPD 剖析必须通过
loadTracer.sh启动; SGLANG_TORCH_PROFILER_DIR供默认 PyTorch Profiler 使用;- 如果使用 PyTorch Profiler,则不要使用 loadTracer.sh,直接
python3 -m sglang.launch_server即可。
2.7 压测客户端触发剖析(client.sh)
等到服务端终端出现The server is fired up and is ready to roll!后,在同一容器的另一个终端运行 client.sh:
#!/bin/bash # Start profiling via API curl http://localhost:30000/start_profile -H "Content-Type: application/json" # Benchmark serving using sglang with random dataset and tokenizer # Define the log file with a timestamp TIMESTAMP=$(date +%Y%m%d_%H%M%S) LOGFILE="sglang_client_log_$TIMESTAMP.json" # Run the benchmark with specified parameters and save logs python3 -m sglang.bench_serving \ --backend sglang \ --tokenizer Xenova/grok-1-tokenizer \ --dataset-name random \ --random-input 1024\ --random-output 1024 \ --num-prompts 240 \ --request-rate 8 \ --output-file online.jsonl 2>&1 | tee "$LOGFILE" # Stop profiling via API curl http://localhost:30000/stop_profile -H "Content-Type: application/json" # Convert tracing file to csv & json sqlite3 trace.rpd ".mode csv" ".header on" ".output trace.csv" "select * from top;" ".output stdout" python3 /sgl-workspace/rocmProfileData/tools/rpd2tracing.py trace.rpd trace.json流程拆解:
curl /start_profile通过 HTTP 接口开启剖析(服务端收到后,注入的 RPD 代码在指定 rank 上start()+rangePush);- 运行 sglang.bench_serving 发起在线压测:
random数据集、输入输出各 1024 token、240 个请求、请求速率 8 req/s,日志与结果分别存入带时间戳的sglang_client_log_*.json与online.jsonl; - 压测结束后
curl /stop_profile关闭剖析并 flush 数据; - 用
sqlite3把trace.rpd的top表导出为 CSV,再用rocmProfileData/tools/rpd2tracing.py将 RPD 文件转换为 Perfetto 可加载的trace.json。
Common Notes 3
- 使用
curl http://localhost:30000/start_profile与curl http://localhost:30000/stop_profile控制剖析起止;更多细节可查看 python/sglang/srt/managers/scheduler.py(当前仓库中的剖析逻辑已迁移至 profiler_manager.py); - 不要将 RPD Profiler 与 PyTorch Profiler 一起使用,避免相互干扰;
rocmProfileData/tools/rpd2tracing.py负责从 RPD 文件生成 JSON 文件。
2.8 可视化:Perfetto 加载大 JSON
文档第 7 步建议参照 Perfetto 的大 trace 可视化指南加载超大 JSON 文件,并尽量调整参数使 trace.json 小于 9GB。实践中可通过减少--num-prompts、缩短剖析窗口、或沿用"只记录 2 个 rank"的策略来控制 JSON 体积。
三、PyTorch Profiler 剖析 SGLang:分步流程
当只需要算子级耗时统计、无需跨进程 CPU/Python 调用栈时,PyTorch Profiler 是更轻量的选择。步骤如下:
3.1 应用 torch_profiler.patch
diff --git a/python/sglang/srt/managers/scheduler.py b/python/sglang/srt/managers/scheduler.py index 62d1ff9..6ecd78c 100644 --- a/python/sglang/srt/managers/scheduler.py +++ b/python/sglang/srt/managers/scheduler.py @@ -240,7 +240,6 @@ class Scheduler: ) self.profiler = torch.profiler.profile( activities=[ - torch.profiler.ProfilerActivity.CPU, torch.profiler.ProfilerActivity.CUDA, ], with_stack=True, @@ -1033,9 +1032,11 @@ class Scheduler: if self.profiler is None: raise RuntimeError("Profiler is not enabled.") self.profiler.stop() - self.profiler.export_chrome_trace( - self.torch_profiler_trace_dir + "/" + str(time.time()) + ".trace.json.gz" - ) + if self.tp_rank == 0: + with open(f"stats_repro_{int(time.time())}.txt", "w") as f: + print(self.profiler.key_averages(group_by_input_shape=True).table(sort_by="cuda_time_total", row_limit=-1), file=f) + print("Profiling stats done.") + logger.info("Profiler is done")补丁作用:
- 从
torch.profiler.profile的activities中移除 CPU 活动,只保留ProfilerActivity.CUDA(降低剖析开销与数据量); - 不再导出 chrome trace JSON,改为在TP rank 0上将
key_averages(group_by_input_shape=True)的统计表(按cuda_time_total排序,全量行)写入stats_repro_<时间戳>.txt。
文档提示:可以修改补丁中的if self.tp_rank == 0,让更多 rank 参与记录。
3.2~3.4 其余步骤
- 创建模型路径目录(复用 2.5 的 dummy_grok1 配置),复制到
--model-path指定位置; - 修改 server.sh,去掉
loadTracer.sh(改为直接python3 -m sglang.launch_server ...),在容器内一个终端启动; - 参照 RPD 一节第 6 步运行修改后的 client.sh,但删除最后两行(RPD 转 CSV/JSON 的命令),因为本次产物是
stats_repro_*.txt统计表而非 RPD 文件。
四、当前仓库中的内建剖析实现(源码级对照)
原文档的 patch 基于早期代码。在当前仓库中,SGLang 已把上述工作流内建到 SchedulerProfilerManager,剖析请求经 HTTP 路由(http_server.py 的/start_profile、/stop_profile)下发到 scheduler 的_profile()分发(profiler_manager.py)。
- RPD 内建支持:当
activities包含"RPD"时,当前实现自动完成rpdTracerControl.skipCreate()、在 TP rank 0 用RocpdSchema初始化trace.rpd、setPythonTrace(True)+start()+rangePush,停止时rangePop/stop/flush,并由 rank 0 调用rpd_to_chrome_trace输出rpd-<时间戳>-TP-<rank>.trace.json.gz(见 profiler_manager.py 与_stop_profile的 RPD 分支 L361-L372)。这与文档手工注入的代码一一对应,只是改由框架统一管理; - PyTorch Profiler 内建支持:默认
activities = ["CPU", "GPU"],映射到torch.profiler.ProfilerActivity.CPU/CUDA,支持with_stack、record_shapes,并按DP/TP/PP/EP秩命名输出<profile_id>-TP-<rank>.trace.json.gz(profiler_manager.py 与 L335-L358); - 按阶段剖析:
profile_by_stage模式可分别捕获 prefill(EXTEND)与 decode 阶段的 trace(profiler_manager.py),并支持SGLANG_PROFILE_BY_STAGE_DECODE_MIN_BS等环境变量控制 decode 捕获的最小 batch; - 内存剖析:
activities含"MEM"时启用torch.cuda.memory._record_memory_history并导出 memory snapshot(profiler_manager.py)。
因此,在较新的 SGLang 版本上做剖析时,可优先尝试通过启动参数/请求体直接传入activities=["RPD"]或["CPU","GPU"],无需手工打补丁;原文档的 patch 流程则适用于对旧版本或定制化剖析需求做深度改造。
五、常见问题与注意事项汇总
- RPD 必须由 loadTracer.sh 启动,否则
librpd_tracer.so未被预加载,rpdTracerControl无法工作;PyTorch Profiler 场景则必须去掉它; - 两种剖析器不要混用,避免重复注入与数据互相干扰;
- 控制 trace 体积:Perfetto 流式加载上限约 8GB,JSON 建议小于 9GB;通过只记录 tp_rank 0/1、缩短剖析窗口、减少请求数来控制;
- 检查 RPD 文件可读性:若提示
Error: Could not create rpd file,请确认已在rocpd_python目录执行python setup.py install; - 模型路径:
--model-path指向的目录需包含有效config.json(如 dummy_grok1),并注意 tokenizer 路径的可用性; - trace 文件转换:RPD 产物使用
rocmProfileData/tools/rpd2tracing.py trace.rpd trace.json转换,PyTorch Profiler 产物为stats_repro_<时间戳>.txt统计表,两者后续处理方式不同。
六、参考资料
- 本目录所有脚本与补丁:3rdparty/amd/profiling(含 install_rpd.sh、rpd.patch、loadTracer.sh、rpd_profile_server_enable.patch、rpd_profile_server_enable_wCPU_activities.patch、server.sh、client.sh、torch_profiler.patch)
- 剖析控制接口入口:http_server.py
- 剖析调度实现:profiler_manager.py
- 剖析输出目录环境变量
SGLANG_TORCH_PROFILER_DIR:environ.py - 压测工具:bench_serving.py
【免费下载链接】sglangSGLang is a high-performance serving framework for large language models and multimodal models.项目地址: https://gitcode.com/GitHub_Trending/sg/sglang
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考