SGLang · Prefill / Decode Disaggregation

PD 分离请求生命周期

单请求 100k token、bs1、DeepSeek-V4.1-Flash、B300 3P1D 现网配置。 图中粗线为当前场景的关键路径,灰色为可并行或被掩盖的部分;队列以 有界 隐式 标注在对应步骤上。

对数刻度 / 单请求估算
01 · 请求时序 Client → Gateway → P / D,SVG 时序图

架构与队列 02队列在系统中的物理位置 · 状态来自实测

实测卡过(积压 / 成为瓶颈) 实测容量触顶(拒绝 / 抢占) 实测未见积压 未启用 / 无量化数据 琥珀色外圈 = 当前场景最可能触及

02.1 队列卡点 按路径位置排列,点密度控制信息量

队列卡点 颜色 = 实测状态;圆点 = 压力等级(3 最高)

队列实测记录 来自巡检 / 压测 / trace 复盘

队列状态观测证据时间
Q1容量触顶H20 4P3D 峰窗重放:窗口自然并发 ~1360 vs cap 256,synth429 87.4%;g9i 侧 p-queue-reqs-cap=5 / hold 10s2026-10-21
Q2实测卡过t15 多轮 cache-hit 回放 TTFT p50 8.3s,P/D 各 100% 单核,栈在 apply_jinja_template → tokenizer.encode;--tokenizer-worker-num 8 后 3.9s(-53%)2026-09-21
Q5实测卡过突发窗口 P #queue-req 52–93;H20 4P3D TTFT p50 89.9s 几乎全是排队;L3 wait_complete 期间请求停在此队列09-30 / 10-21
Q6未见积压#inflight-req 6–29,随 P 计算同步起伏;传输完成后即出队,未独立成为瓶颈2026-09-30
Q7未见积压车道排队 p50 1.2ms / p99 20ms,实际传输 p50 3.6ms,忙碌 ≤5%。但单 D 实例下 hash 退化,90 分钟内仅 2/16 车道有 span2026-09-30
Q8容量触顶max_running=256 把 decode 批量钳在 ~190(TPOT 假饱和);10-18 扩到 300;KV 残血时启动即 leaves no room 崩溃循环10-11 / 10-18
Q9堆高非积压峰值 49 条,同期 prealloc=0、传输 p50 3.6ms、排队 p50 1.2ms;确认是「P 还没算完」的镜像2026-09-30
Q11容量触顶3 台 D 合计仅 192 个 decode 槽,成为峰窗硬瓶颈;出现 retraction2026-10-21
Q3无数据IPC 静默,未单独量化;scheduler 长 step 时会滞后—
Q4无数据未观测到握手队列深度(10-13 p2 楔死是 forward 卡死,不是 Q4 积压)—
Q10未启用V4.1 hisparse 与 decode radix 互斥,当前 D 无 hicache—
Q12无数据单进程 detok,无独立指标;高输出速率下是否滞后未做实验—
Q13无数据未做慢客户端反压实验—

关键路径耗时 03对数刻度,右侧为线性占比

队列清单 04Q1–Q13

#位置队列容量 / 上限指标对应时序100k 单请求占用100k 下的表现

理论瓶颈 05100k token,单请求关键路径

场景关键路径瓶颈队列数值占比单实例吞吐上限
冷启动≈ 1.71 sP prefill 计算Q51.55 s91%0.62 req/s(2 GPU)
L3 命中≈ 0.60 sL3 → L2 prefetchQ50.43 s(8 lane 0.15 s)72%2.3 req/s;8 lane 6.7
L1/L2 命中≈ 0.17 stokenize + 模板Q2120 ms(60–200)71%8.3 req/s per worker
L1/L2 + optimistic≈ 0.15 stokenize + 模板Q2120 ms78%同上
高并发(命中)—KV 传输带宽Q7190 MB ÷ 26 GB/s—≈ 137 req/s
高并发(冷启)—P 队列 / chunk 预算Q4Q5100k > 65536—由 max_prefill_tokens 与 KV 池决定
瓶颈随命中层级左移:冷启动看计算(1.55s,91%)→ L3 命中看 prefetch(0.43s,72%)→ L1/L2 命中看 tokenize(120ms,71%)。传输在任何单请求场景都不是瓶颈; 只有命中吞吐接近 ~137 req/s(单 P 实例)时才会成为上限。
L3 命中不是直达 device:路径是 storage → host 池(L2) → device(L1)。 HiCacheController.prefetch() 的注释即 “storage backend to host memory”, 之后才由 init_load_back() 做 H2D。100k 的 0.43s 中,L3→host 占 ~430ms, L2→L1 的 DMA 只有 ~2–3ms。因此 L2 必须有足够空间,否则 prefetch 回来的页会在 load_back 前被驱逐。

Jaeger trace 粒度 06request 级 + 阶段级,无 kernel 级

Jaeger 覆盖矩阵 每行阶段 × 每列可观测信号
✅ 完整可用△ 间接 / 需侧写— 无 详细档会在格子里显示 span 名与 level
prefill Req 9f3a1c2d              root · role=prefill
├─ tokenize                      L1  含 chat template / 请求转换
├─ api_server_dispatch           L2
├─ prefill_prepare               L1
├─ prefill_bootstrap             L1  ← Q4
├─ prefill_waiting               L1  ← Q5(含 L3 prefetch 等待)
├─ prefill_forward               L1
│  └─ chunked_prefill            L3  每 chunk 一条
├─ prefill_transfer_kv_cache     L1  ← Q6
│  └─ mooncake_send              L1
│     └─ mooncake_worker_send    L1
│        └─ mooncake_worker_send_session L2  ★ 真实传输
└─ (Sender Req 9f3a1c2d)         独立 trace · role=Sender
decode Req 9f3a1c2d               root · role=decode
├─ decode_prepare                L1  ← Q8
├─ decode_bootstrap              L1  ← Q4 注册
├─ decode_waiting                L1
├─ decode_transferred            L1  ← Q9
├─ decode_forward                L1
├─ decode_loop                   L3  ★ 每个 token 一条
│  ├─ spec_draft                 L2  dspark draft
│  └─ spec_verify                L2  dspark verify
└─ fake_output                   L3  首 token 打底
粒度:请求级 + 阶段级,最小到「一个 scheduler step / 一个 chunk」;没有 kernel、没有算子。 要看 kernel 用 nsys / torch profiler,不要指望 Jaeger。
  • 量:decode_loop 每个 decode step 一条。4k 输出无投机时 ≈ 4k span/请求;dspark 下约 1k step ×(loop+draft+verify)≈ 3k span/请求。高 QPS 下是 badger 的主要压力来源。
  • 默认级别:SGLANG_TRACE_LEVEL=3;level 4(run_batch_cpu)默认被丢弃,要看得显式调高。
  • 采样:没有采样率参数,--enable-trace 即全量;SGLANG_TRACE_SAMPLER_E2E 是 sampler 的调试打印,不是采样开关。
  • 关联:P 与 D 共用同一 trace_id(网关注入同一 traceparent);但 mooncake 的 Sender Req 是独立 trace(未透传 header),实际跨端 join 用 bootstrap_room。
  • 缺口:L3 prefetch 没有 span,只能看 prefill_waiting 或 SGLANG_HICACHE_TIMING=1 日志;tokenize 只进 trace,不进 per_stage_req_latency_seconds 指标。
  • 噪声:健康检查请求(pt=1、无 room)也会产生 prefill span,约占 30%,统计时必须过滤。
  • 保留:我们线上 Jaeger 用 badger,TTL 6h —— 只能回看最近 6 小时。

数据来源 07

参数取值类型来源
V4.1 稀疏 KV 体积≈ 1900 B/token源码 + trace 交叉按 kv_source_layer_ids / fp4 indexer 计算,用 160k ≈ 304MB 校验
P 单流 prefill62k tok/s @160k实测日志bench_serving / aiperf 基线
L3 prefetch233k / 668k tok/s实测日志2026-10-10 mc1 / mc8 测试轮次
KV 传输160k ≈ 15msJaegermooncake_worker_send_session 子 span 汇总
decode TPOT4.3–6ms实测日志aiperf 稳态基准
tokenize + 模板60–200ms(取 120)估算由 76k ctx 单核打满反推,按文本量外推
握手≈ 15ms估算ZMQ 注册 + 全 rank 同步 + 一个 scheduler step

只有「KV 传输」一项直接来自 Jaeger trace;其余来自 bench 日志、启动日志/manifest 或估算。 stage 分位数用 Prometheus sglang:per_stage_req_latency_seconds{stage=...}; tokenize 是 trace-only,L3 prefetch 无独立 span。