
1. 从一次线上推理抖动说起为什么需要 PD 分离如果你正在做推理服务大概率遇到过这种场景白天流量平稳TTFT 和 TPOT 都挺好看一到晚高峰或者某个批量任务触发首 token 延迟突然从几百毫秒飙到好几秒而 decode 阶段的吐字速度也跟着抖。排查下来 GPU 利用率并不低但就是“忙得没效率”。这个现象背后往往就是 prefill 和 decode 两种完全不同负载被硬塞进同一个 batch 里互相拖累。Mooncake 这篇论文Kimi 采用的 PD 分离架构把这个问题拆得很清楚。传统 continuous batching 解决了“一个 batch 里短请求等长请求”的浪费但没解决另一个矛盾新请求的 prefill 是全量计算输入 token 可能几千个老请求的 decode 是增量计算输入 token 通常只有 1 个。两者组进同一个 batch计算图里就会出现大量空泡算力被浪费在 padding 上。PD 分离的核心思路就一句话把 prefill首 token 推理计算密集型和 decode增量推理存储/带宽密集型放到不同的计算资源上P 算完把 KVCache 通过高速网络传给 DD 接着吐字。这样 P 节点可以专心堆算力、做 prefix cache 复用D 节点专心优化吞吐和 TBT。代价是 KVCache 要跨节点传输所以论文花了很大篇幅论证传输成本可控并给出了调度算法。这篇文章不是论文翻译而是面向“想落地 PD 分离”的工程视角我会给出可复制的配置骨架prefill/decode 节点参数、验证请求的动作、常见报错排查以及用统一 Key/API 通道做接入验证的示例。适合已经跑过 vLLM/SGLang、想进一步做推理性能优化的同学。2. 前置准备TaoToken 统一 Key 与 API 通道在动手配 PD 分离之前建议先把模型调用通道统一掉。原因很实际PD 分离的验证阶段你需要频繁切换不同模型、对比不同参数下的 TTFT/TPOT如果每个模型都要单独配 Key、单独记 endpoint调试成本会很高。TaoToken 提供的是统一 Key 统一 API 通道兼容 OpenAI 风格的接口适合做这类对比验证。你需要准备的东西一个 TaoToken 账号在控制台创建一个 API Key记录下 API 基地址https://taotoken.net/api注意这个地址不带任何查询参数直接作为 base_url 使用如果你要跑长期编码或 Agent 类任务可以了解下 Coding Plan如果只是验证模型对话效果用模型对话入口即可。创建 Key 的路径是控制台里的 API Keys 页面模型对话入口和接入文档分别在对应 deep link 里。这里不展开注册流程重点是把 Key 拿到手后面配置里会用到。提示统一通道的好处是你可以在同一套代码里通过改model字段切换模型而不用改 base_url 和鉴权逻辑。做 PD 分离压测时这一点能省很多事。3. 可复制的配置骨架prefill/decode 节点参数下面给出一份 config.toml 的骨架模拟 PD 分离场景下 P 节点和 D 节点的关键参数。不同框架vLLM、SGLang、Mooncake 官方实现字段名会有差异但核心参数是相通的P 节点关注 prefix cache、TTFT、MFUD 节点关注 TBT、显存、吞吐。# config.toml - PD 分离配置骨架示意字段名按实际框架调整 [cluster] # 调度器地址P/D 节点都向它注册 scheduler_endpoint http://127.0.0.1:8998 # KVCache 传输引擎Mooncake 用分布式 KVCache pool kv_transfer_engine mooncake # 传输后端论文强调 RDMA实测 100 Gbps 以上有正收益 transfer_backend rdma transfer_bandwidth_gbps 100 [prefill] # P 节点计算密集型目标最大化 cache reuse node_count 2 gpu_memory_utilization 0.90 max_num_batched_tokens 8192 # prefill 阶段可以吃大 batch enable_prefix_caching true # 核心prefix cache 复用 prefix_cache_block_size 16 max_ttft_ms 2000 # SLO首 token 延迟上限 min_mfu 0.35 # 最低算力利用率约束 kv_cache_dtype fp8 # 省显存注意精度影响 [decode] # D 节点存储/带宽密集型目标优化吞吐 node_count 2 gpu_memory_utilization 0.85 max_num_batched_tokens 2048 # decode 阶段 batch 不宜过大 max_tbt_ms 50 # SLOtoken 间隔上限 max_concurrent_requests 256 kv_cache_dtype fp8 [scheduler] # 调度算法相关阈值 kv_cache_balancing_threshold 0.8 # best_len/prefix_len 低于此值才考虑拷贝 estimate_model empirical # 经验模型估算排队/计算/传输时间 reject_on_slo_violation true # TTFT 或 TBT 不满足 SLO 时拒绝请求几个参数值得单独说enable_prefix_caching是 P 节点的灵魂。论文里 P 节点的优化目标就是最大化 cache reuse新请求的输入去匹配旧请求的输入匹配上就直接复用之前算好的 KVCache避免重复计算。这个开关不开PD 分离的收益会打对折。kv_cache_balancing_threshold对应论文调度算法的 step2。当某个 P 节点的 prefix 匹配长度太短best_len/prefix_len threshold说明在这个节点重新算 prefix 比从最佳节点拷贝 KVCache 更贵这时才估算传输时间否则直接本地算传输时间为 0。transfer_bandwidth_gbps对应论文那个不等式B/G 2ds/[gqa×(apdbd²)]。工程上的结论是带宽达到 100 Gbps 量级KVCache 传输就不会成为瓶颈。如果你的环境达不到PD 分离的收益可能被传输吃掉。4. 验证请求确认 P/D 解耦真的生效配置写完不代表生效必须用请求验证。下面给一段 Python 验证脚本通过统一 API 通道发请求同时打印 TTFT 和 TPOT用来对比 PD 分离前后的差异。import time import requests API_BASE https://taotoken.net/api API_KEY 你的_TaoToken_API_Key headers { Authorization: fBearer {API_KEY}, Content-Type: application/json, } payload { model: 你的模型名, messages: [ {role: user, content: 用三句话解释什么是 PD 分离。} ], stream: True, max_tokens: 256, } start time.time() first_token_time None token_count 0 with requests.post( f{API_BASE}/v1/chat/completions, headersheaders, jsonpayload, streamTrue, timeout60, ) as resp: resp.raise_for_status() for line in resp.iter_lines(): if not line: continue line line.decode(utf-8) if line.startswith(data: ) and line ! data: [DONE]: if first_token_time is None: first_token_time time.time() token_count 1 end time.time() ttft (first_token_time - start) * 1000 if first_token_time else -1 tpot ((end - first_token_time) / max(token_count - 1, 1)) * 1000 if first_token_time else -1 print(fTTFT: {ttft:.1f} ms) print(fTPOT: {tpot:.1f} ms) print(f总 token 数: {token_count})验证动作分三步第一步先在单节点P/D 不分离模式下跑这个脚本记录 TTFT 和 TPOT 基线。建议跑 20 次取中位数避免单次抖动误导。第二步切到 PD 分离配置P 节点和 D 节点分别启动确认调度器日志里能看到请求被分配到不同节点。重点看 P 节点的 prefix cache 命中率如果命中率明显上升说明 cache reuse 生效了。第三步对比两组数据。论文的实验结论是 PD 分离相比 vLLM 框架 TPOT 更短、TTFT 更短、prefix cache 命中率更高。你实测下来如果 TTFT 没降反升大概率是 KVCache 传输拖了后腿回去检查带宽和kv_cache_balancing_threshold。注意验证时尽量用相同 prompt 前缀的请求这样才能触发 prefix cache。如果每次请求都是全新内容P 节点的 cache reuse 优势体现不出来。5. 本篇常见错排查PD 分离落地时踩的坑比较集中这里列几个高频的。报错一KVCache transfer timeout。现象是 P 节点算完了D 节点迟迟收不到 KVCache请求卡住直到超时。排查顺序先确认 RDMA 链路是否通ibstat看端口状态再确认transfer_bandwidth_gbps配置是否和实际网卡匹配配高了会误判最后看 KVCache pool 的注册信息是否互通Mooncake 的分布式 KVCache pool 要求各节点内存池信息可见。报错二prefix cache 命中率始终为 0。检查enable_prefix_caching是否真的开了有些框架需要同时设置 block size 和 cache 容量。另外确认请求的 prompt 前缀是否一致如果系统提示词每次都变前缀匹配自然失败。报错三TTFT 不降反升。这通常不是 PD 分离本身的问题而是调度算法选错了 P 节点。论文调度算法的 step2 逻辑是如果best_len/prefix_len threshold说明该节点 prefix 太短重新计算比拷贝便宜此时T_transfer0。如果你的 threshold 设得太低会导致频繁跨节点拷贝 KVCacheTTFT 反而变差。建议从 0.8 开始调。报错四D 节点显存 OOM。decode 阶段虽然单次计算量小但并发请求多时 KVCache 累积很快。调低max_concurrent_requests或者把kv_cache_dtype设成 fp8。注意 fp8 对精度有影响需要评估你的业务能否接受。报错五SLO 拒绝率过高。论文 step6 提到TTFT 或 TBT 不满足 SLO 时会拒绝请求。如果拒绝率异常高先看 P/D 节点配比是否合理。论文实验结论里明确说了P 和 D 节点数量配比会影响 PD 系统吞吐2/2 不一定适合你的负载需要按实际压测调整。6. 接入与排障通道统一 Key 的收尾动作PD 分离的验证和排障过程中你会反复做一件事发请求、看延迟、换模型对比。如果每个模型都单独配通道排障效率会很低。用 TaoToken 的统一 Key 和 API 通道可以把这部分固定下来。具体动作排障和接入相关的去 API Keys 页面确认 Key 状态接入文档里有完整的请求示例和错误码说明验证模型对话效果用模型对话入口直接试长期跑编码或 Agent 任务看 Coding Plan 是否匹配你的用量。把https://taotoken.net/api作为 base_url 固定到你的压测脚本里后面无论换哪个模型做 PD 分离对比只改model字段就行。这样你的 TTFT/TPOT 对比数据才有一致性不会因为通道差异引入噪声。最后留一个实用技巧PD 分离的调参顺序建议是先固定 P/D 配比调kv_cache_balancing_threshold让 prefix cache 命中率稳定再调max_num_batched_tokens平衡 P 节点吞吐和 TTFT最后根据 D 节点的 TBT 表现调max_concurrent_requests。一次只动一个变量否则你分不清是哪个参数带来的变化。