Nsight Systems(nsys)
Nsight Systems 用于观察 CPU 与 GPU 的执行时间线,定位 CUDA API 调用、kernel、通信、同步及空闲区间。本文先介绍 nsys 的命令、采集参数和报告,再给出 SGLang 的固定 batch 与 server 实践。正式延迟和吞吐测量见 SGLang Benchmark。
1. 命令与参数
1.1 基础采集
在安装了 Nsight Systems 的 GPU 机器上,将待分析程序放在 nsys profile 参数之后。以下以 Python 脚本为例,将 your_script.py 替换为实际脚本及其参数:
nsys profile \
--sample=none \
--trace=cuda,nvtx \
--cuda-graph-trace=node \
--force-overwrite=true \
-o report \
python3 your_script.py程序正常退出后生成 report.nsys-rep。该命令没有限定 capture range,会包含启动、初始化和正式计算;需要排除初始化时,再使用应用提供的 cudaProfilerStart/Stop 控制采集边界。
1.2 常用参数
| 参数 | 作用 |
|---|---|
--sample=none | 关闭 CPU 指令指针与调用栈采样,保留 CUDA trace。 |
--trace=cuda,nvtx | 记录 CUDA API、GPU 活动和程序已有的 NVTX 标记。 |
--cuda-graph-trace=node | 展开 CUDA Graph 内部节点,便于观察 kernel;有额外采集开销。 |
--cuda-graph-trace=graph | 按整个 graph 记录,开销较低,但不展开内部节点。 |
--capture-range=cudaProfilerApi | 等待应用显式调用 cudaProfilerStart() 才开始采集。 |
--capture-range-end=stop | 第一次范围结束时停止采集,应用继续运行,后续范围被忽略。 |
--capture-range-end=none | 忽略范围结束信号;短任务可一直采集到进程退出。 |
--capture-range-end=repeat | 允许多次 start/stop,每段形成独立采集,适合长期服务。 |
--force-overwrite=true | 允许覆盖同名报告;重复范围模式的文件命名以 Nsight 输出为准。 |
-o report | 指定输出报告路径前缀。 |
--capture-range 决定何时开始,--capture-range-end 决定范围结束后的行为。仅配置 cudaProfilerApi 而没有应用端触发,不会自动采集正式计算。none 适合会自行退出的短任务;持续运行的 server 应使用 stop 或 repeat,并显式结束采集。
3. SGLang 固定 Batch 实践
| 场景 | 入口 | 适用范围 |
|---|---|---|
| 固定 BS 的模型执行分析,日常推荐 | sglang.bench_one_batch | 精确控制一次 prefill 和 decode 步数,不经过 HTTP 调度。 |
| 真实服务的调度与执行分析 | sglang.launch_server + sglang.bench_one_batch_server | 一组请求经过 server 调度,实际 GPU batch 可能变化。 |
以下示例适用于普通自回归解码,不开启 speculative decoding。Prefill 生成首 token,10 步 decode 再生成 10 个 token,所以总输出长度是 11。 “BS=32”指 32 条序列;prefill 每条输入 INPUT_LEN 个 token,decode 每步每条处理 1 个新增 token。
3.1 环境配置
在安装了 SGLang 和 nsys 的 GPU 机器上使用 Bash 执行,先停止占用同组 GPU 的 server:
export MODEL_PATH=/path/to/model
export CUDA_VISIBLE_DEVICES=0
export TP_SIZE=1
export INPUT_LEN=1024
export DECODE_STEPS=10
export PROFILE_DIR=profiles/sglang_run1
mkdir -p "$PROFILE_DIR"替换模型路径,并按可见 GPU 数量设置 TP。调整 INPUT_LEN 即可改变 prefill 长度;保持同一长度比较不同 BS。确认输入加输出长度符合模型上下文限制,并有足够 KV Cache 容量。
3.2 单个 Batch
定义一次函数,再指定 BS。第二个参数控制从哪个阶段开始采集:默认 prefill 包含 prefill 和后续 decode,decode 则跳过 prefill 的采集。
profile_batch() {
local BS="$1"
local START_STAGE="${2:-prefill}"
local OUTPUT_LEN=$((DECODE_STEPS + 1))
local PREFIX="${PROFILE_DIR}/bs${BS}_in${INPUT_LEN}_from_${START_STAGE}_dec${DECODE_STEPS}"
nsys profile \
--sample=none \
--trace=cuda,nvtx \
--cuda-graph-trace=node \
--capture-range=cudaProfilerApi \
--capture-range-end=none \
--force-overwrite=true \
-o "$PREFIX" \
python3 -m sglang.bench_one_batch \
--model-path "$MODEL_PATH" \
--tp-size "$TP_SIZE" \
--mem-fraction-static 0.7 \
--batch-size "$BS" \
--input-len "$INPUT_LEN" \
--output-len "$OUTPUT_LEN" \
--profile \
--profile-activities CUDA_PROFILER \
--profile-stage "$START_STAGE" \
--profile-start-step 0 \
--run-name "bs${BS}_in${INPUT_LEN}_from_${START_STAGE}_dec${DECODE_STEPS}" \
--result-filename "${PREFIX}.jsonl"
}
# BS=1,每条输入 1024 tokens,采集 prefill + 10 步 decode
INPUT_LEN=1024 profile_batch 1
# BS=64,每条输入 8192 tokens,只采集随后的 10 步 decode
INPUT_LEN=8192 profile_batch 64 decode两种命令择需运行,分别生成 bs1_in1024_from_prefill_dec10.nsys-rep 和 bs64_in8192_from_decode_dec10.nsys-rep,位于 PROFILE_DIR 下。相同配置再次执行会覆盖报告,独立实验应修改 PROFILE_DIR;JSONL 计时结果则追加写入。
初始化和工具预热均发生在采集开始之前。两种模式的区别是采集起点:
--profile-stage prefill:在正式 prefill 前触发cudaProfilerStart()。--capture-range-end=none忽略 prefill 后的停止信号,继续记录后续 10 步 decode,直到测试进程退出。--profile-stage decode --profile-start-step 0:先执行真实 prefill 建立 KV Cache,在第 1 步 decode 前触发采集。0是从零开始的 decode 步编号;不指定它时,本地实现默认从output_len // 2开始,会漏掉前面的 decode。
--output-len 11 保证正式测试只执行 10 步 decode;--capture-range-end=none 持续记录到进程退出,因此两种报告尾部都会包含结果处理和退出清理。分析时选中模型执行区间,不把整份报告总时长当成推理延迟。包含 prefill 的模式不能改成 --capture-range-end=stop,否则会在 prefill 后结束,漏掉 decode。
BS=64、INPUT_LEN=8192 对应 64 条各含 8192 tokens 的序列,prefill 合计处理 524288 个输入 tokens。只抓 decode 会减小报告,但不会省掉 prefill 计算和 KV Cache 显存;容量不足时应调整 TP、BS 或长度,并检查工具是否跳过该配置。
4. SGLang Server 实践
4.1 服务启动
需要观察实际 scheduler、overlap 或多请求行为时使用 server 模式。终端 A 先执行第 3.1 节环境配置,再启动:
export PORT=30000
export GRAPH_BS_FLAG=--cuda-graph-max-bs-decode
nsys profile \
--sample=none \
--trace=cuda,nvtx \
--cuda-graph-trace=node \
--capture-range=cudaProfilerApi \
--capture-range-end=repeat \
-o "${PROFILE_DIR}/server" \
python3 -m sglang.launch_server \
--model-path "$MODEL_PATH" \
--host 0.0.0.0 --port "$PORT" \
--tp-size "$TP_SIZE" \
--chunked-prefill-size -1 \
--disable-radix-cache \
--max-running-requests 128 \
"$GRAPH_BS_FLAG" 128 \
--mem-fraction-static 0.7旧版若只有 --cuda-graph-max-bs,将 GRAPH_BS_FLAG 改成该名称。两个 128 分别是运行请求数和 decode graph 捕获的上限,并不固定 GPU batch;测试更大的 BS 时需要同步调整。关闭 radix cache 是为了避免预热命中前缀缓存,关闭 chunked prefill 是为了便于观察完整输入处理。
4.2 批请求采集
服务 ready 后,终端 B 同样设置第 3.1 节变量和 PORT=30000。以下函数向已有服务提交一组固定长度的请求,默认忽略 EOS,输出 11 个 token。每档先执行一次不采集的预热,再采集同样的一组请求:
export PORT=30000
BS_LIST=(1 8 32 64 128)
run_batch() {
python3 -m sglang.bench_one_batch_server \
--model-path "$MODEL_PATH" \
--base-url "http://127.0.0.1:${PORT}" \
--batch-size "$1" \
--input-len "$INPUT_LEN" \
--output-len "$((DECODE_STEPS + 1))" \
--skip-warmup \
--result-filename "${PROFILE_DIR}/server_batch.jsonl"
}
for BS in "${BS_LIST[@]}"; do
echo "Profiling BS=${BS}, input=${INPUT_LEN}, decode=${DECODE_STEPS}"
run_batch "$BS" || break
curl --fail --silent --show-error \
-X POST "http://127.0.0.1:${PORT}/start_profile" \
-H 'Content-Type: application/json' \
-d '{"activities":["CUDA_PROFILER"]}' || break
run_batch "$BS"
curl --fail --silent --show-error \
-X POST "http://127.0.0.1:${PORT}/stop_profile" || break
done只测一个 BS 时将列表改成 BS_LIST=(32)。这里由 curl 控制 Nsight,不给客户端加 --profile,避免引入 Torch Profiler 或另一套按 step 截断的逻辑。当前源码中 bench_one_batch_server --profile-activities 不接受 CUDA_PROFILER,因此使用显式服务接口更直接。
--base-url 连接已有服务,--skip-warmup 关闭客户端自带预热;循环已经在采集前以相同长度手动预热。JSONL 文件包含预热和采集两次结果,它仅作运行记录,正式 benchmark 使用 Benchmark 中不带 profiler 的流程。
Server 中的 BS 是提交的请求组大小,不能保证只执行一个同样大小的 GPU prefill batch。 即使关闭 chunked prefill,token 预算、KV Cache 容量和调度仍可能分批接纳请求。每条请求输出 11 个 token,不等于整台 server 总共只运行 11 次 forward;要求固定一个 prefill batch 和 10 个 decode batch 时使用第 3 节。
repeat 允许多次 start/stop,每段对应一份报告。报告按采集顺序与 BS_LIST 对应,以 Nsight 日志中的路径为准。全部完成后在终端 A 按一次 Ctrl+C,等待报告导出和进程正常退出;不要用 kill -9。此流程应在没有其他请求流量时执行,否则其他请求也会进入采集窗口。