大模型推理优化实战:PD分离、Radix Attention 与 Continuous Batching,算力成本能省多少

发布时间:2026/10/13 6:54:34
大模型推理优化实战:PD分离、Radix Attention 与 Continuous Batching,算力成本能省多少
1. 推理服务的钱到底烧在哪TTFT 慢、吞吐上不去、显存被 KV Cache 吃光如果你正在用 vLLM 或 SGLang 部署大模型推理服务大概率遇到过这三个现象首 token 延迟TTFT忽高忽低、并发一上来吞吐就塌、显存监控里 KV Cache 一路飙红。这三个现象背后其实是同一件事——GPU 没被喂饱算力在空转。大模型推理优化要解决的核心问题就是让每一张卡在单位时间里多吐 token、少等空转。PD 分离、Radix Attention、Continuous Batching 这三类手段分别对应等待消除重复计算消除排队消除叠加起来能把算力成本压下来一大截。先算一笔账。一台 8 卡 H200 节点按云上按量价格粗算每小时几十美元一个月跑满就是几万美元。如果 GPU 利用率只有 50%等于一半的钱在机房里以电费和折旧的形式蒸发。推理服务的成本公式很朴素单位 token 成本 硬件小时成本 / 每小时产出 token 数。想降成本只有两条路要么换更便宜的硬件要么在同样硬件上把吞吐拉高。前者受限于预算和供给后者才是工程能发力的地方。三个坑具体长什么样。坑一是推理税Agent 场景里同一段 system prompt 和工具描述被反复调用几十上百次模型每次从头算一遍同样的输入算一百遍结果一模一样这部分算力纯浪费。多轮对话更明显每一轮都带着前几轮全部上下文重算。坑二是 KV Cache 显存爆炸70B 模型 8K 上下文单请求 KV Cache 约 4GB并发一上来显存告急传统分配方式碎片化严重实际利用率更低。坑三是 Prefill 和 Decode 互相拖累Prefill 读 prompt 是计算密集型要快Decode 逐字生成是内存带宽密集型塞在同一张卡上Prefill 抢算力把 Decode 的 TPOT 拉长Decode 占带宽把 Prefill 的 TTFT 拖差两头不讨好。Continuous Batching 解决的是排队问题。早期推理引擎像排队打饭一个 batch 全部算完才轮到下一批GPU 在等最慢那个请求时是闲着的。连续批处理把 GPU 变成随时上下客的公车新请求随时插入、完成的请求随时离开吞吐能提升 2 到 4 倍现在是所有主流引擎的标配。但它没解决 Prefill 和 Decode 混跑时的相互干扰也没解决重复前缀的重复计算所以还需要另外两把刀。Radix Attention 解决的是重复计算。它用基数树radix tree组织所有请求的 KV Cache相同前缀的请求直接共享一段计算结果只有内容分叉时才长出新枝叶。配合缓存调度把相似请求集中连续处理长上下文场景算力成本能暴降。PD 分离解决的是相互干扰把 Prefill 和 Decode 拆到不同 GPU 实例Prefill 卡专门吃大段输入算完通过高速网络把 KV Cache 传给 Decode 卡集群Decode 卡专心逐字生成。三者不是替代关系而是叠加关系Radix Attention 和 PD 分离可以同时开。这篇写给正在部署推理服务的人。我会用 TaoToken 统一 Key/API 通道做请求侧演示把三类优化的配置、压测脚本、验证步骤和踩坑记录讲透最后给一张按优先级排的调优清单。读完你能回答一个问题我该不该上 PD 分离上了到底能省多少。适合谁手里有 vLLM 或 SGLang 部署、正在被 TTFT 和吞吐折磨、想量化算力成本节省幅度的工程师。2. TaoToken 前置统一 Key/API 通道怎么接为什么推理压测需要它做推理优化压测最烦的不是调参是请求侧的环境不一致。今天用 A 家的 key 测一组明天换 B 家的 endpoint 再测一组base_url、鉴权头、模型名全不一样压出来的数据根本没法横向对比。TaoToken 在这里的角色是统一入口一个 Key、一个 Base URL、一套 OpenAI 兼容协议把不同模型的调用收敛到同一条通道上压测脚本只改 model 字段就能切换后端对比实验的变量就干净了。它的定位是 API 聚合与统一调用通道不是替代你的推理引擎。你的 vLLM 或 SGLang 实例照常跑在本地或自己的机器上TaoToken 负责的是请求侧的统一管理和模型路由。对于做推理优化的人来说这带来两个实际好处一是压测脚本可以复用同一份 bench 代码换个 model 名就能测不同模型二是可以把本地自建实例和托管模型放在同一套脚本里对比量化自建省了多少这件事。接入前先明确三件套这是后面所有配置的基础Base URL 用https://taotoken.net/apiAPI Key 在控制台的 API Keys 页面生成Model ID 按你要调的模型填。这三样缺一不可后面 §3 的配置片段和 §5 的报错排查都围绕它们展开。控制台地址是 https://taotoken.net/console API Keys 管理在 https://taotoken.net/api-keys 接入文档在 https://taotoken.net/doc 需要先看文档确认模型名和参数支持情况。为什么压测要用统一通道而不是直连本地。本地实例直连当然可以但当你需要对比优化前 vs 优化后或者自建 vs 托管时请求侧的任何差异都会污染数据。统一通道把鉴权、重试、超时这些请求侧逻辑标准化你的压测脚本只需要关心并发数、输入长度、输出长度这些真正的自变量。我试过用同一份脚本分别打本地 SGLang 和 TaoToken 通道上的模型除了 base_url 和 model 两行其余代码完全一致对比出来的吞吐和延迟差异才能归因到推理引擎本身。还有一个容易被忽略的点压测时的连接复用。OpenAI SDK 默认会复用 HTTP 连接如果你每次请求都新建 client握手开销会混进延迟数据里。用统一通道时把 client 初始化放在循环外面整个压测过程复用一个 client 实例这样测出来的 TTFT 才是推理引擎的真实表现而不是被 TCP 握手污染的假数据。这一点在 §4 的压测脚本里会体现。关于成本归因统一通道还有个隐性价值请求侧的 token 计数和耗时统计是标准化的。你可以在通道侧看到每个模型的调用量、token 消耗、平均延迟再结合本地实例的 GPU 利用率监控就能算出每美元产出多少 token这个核心指标。优化前跑一组基线优化后跑一组对比两组数据的差值就是省下来的算力成本。这个归因链条要成立前提是请求侧口径一致这正是统一通道解决的问题。最后提醒一句TaoToken 是调用通道不改变你推理引擎的内部行为。PD 分离、Radix Attention、Continuous Batching 这些优化是在引擎侧做的通道侧只负责把请求稳定地送进去、把结果准确地取回来。两者配合的方式是引擎侧调优提升单卡吞吐通道侧统一入口让压测和对比可复现。下一节进入可复制的配置先给引擎启动参数再给请求侧的三件套配置。3. 可复制配置SGLang 启动参数、Radix Attention 开关与请求侧三件套这一节给可直接复制的配置片段。分三块推理引擎启动参数、请求侧的三件套配置Base URL Key Model ID、以及压测脚本的骨架。所有路径和参数名以 2026-08 实测环境为准版本不同可能有差异以官方文档为准。先看引擎侧。SGLang 的 Radix Attention 默认开启不需要额外配置但有两个参数必须显式加上才能吃满收益。启动命令如下python3 -m sglang.launch_server \ --model-path Qwen/Qwen3-32B \ --port 30000 \ --tp 8 \ --mem-fraction-static 0.85 \ --enable-mixed-chunk \ --chunked-prefill-size 8192--enable-mixed-chunk允许 prefill 请求分块执行避免单个大请求把 GPU 占死高并发下平均响应时间能提升约 2 倍。--chunked-prefill-size控制每块的大小8192 是个稳妥起点输入普遍很长可以调大输入短可以调小。--mem-fraction-static 0.85把 85% 显存留给模型权重和 KV Cache剩下留给临时缓冲这个值太高会 OOM太低浪费显存0.85 到 0.9 之间比较稳。vLLM 侧的对照配置注意--enable-prefix-caching在老版本需要手动开python3 -m vllm.entrypoints.openai.api_server \ --model Qwen/Qwen3-32B \ --port 30001 \ --tensor-parallel-size 8 \ --gpu-memory-utilization 0.9 \ --enable-prefix-caching \ --enable-chunked-prefill--enable-prefix-caching是 Radix Attention 在 vLLM 里的对应实现0.6 之后默认开老版本必须显式加否则共享前缀场景完全不缓存对比数据会失真。这个参数我一开始漏了对比出来的数据直接没法看——不是 SGLang 快是 vLLM 根本没开缓存。请求侧的三件套配置。用 OpenAI SDK 时Base URL、API Key、Model ID 三个字段这样填from openai import OpenAI client OpenAI( base_urlhttps://taotoken.net/api, api_keysk-你的Key, # 在 https://taotoken.net/api-keys 生成 ) resp client.chat.completions.create( modelQwen/Qwen3-32B, # Model ID按文档填 messages[{role: user, content: 你好}], max_tokens128, ) print(resp.choices[0].message.content)如果用配置文件管理可以写成 JSON 或 TOML。JSON 版本{ base_url: https://taotoken.net/api, api_key: sk-你的Key, model: Qwen/Qwen3-32B, timeout: 60, max_retries: 2 }TOML 版本适合放在项目根目录的config.toml[llm] base_url https://taotoken.net/api api_key sk-你的Key model Qwen/Qwen3-32B timeout 60 max_retries 2如果你用 Claude Code 或 Cline 这类工具配置方式类似核心还是三件套。Claude Code 的 settings 里填 Base URL 和 KeyModel ID 按文档选Cline 的 MCP 配置里同样三件套齐全。CC Switch 这类切换工具也是围绕 Base URL Key Model ID 三个字段做文章配好之后切换模型只改 Model ID 一行。压测脚本骨架控制前缀共享率来测 Radix Attention 收益import random import time from openai import OpenAI client OpenAI(base_urlhttps://taotoken.net/api, api_keysk-你的Key) SYSTEM_PROMPT 你是一个严谨的代码审查助手回答要给出理由和反例。 POOLS [ 解释一下什么是闭包。, 这段代码有什么问题, 如何优化这个查询, 写一个 Python 装饰器。, 对比 list 和 tuple 的差异。, ] def run_batch(share_prefix: bool, n: int 100): latencies [] for i in range(n): if share_prefix: messages [ {role: system, content: SYSTEM_PROMPT}, {role: user, content: random.choice(POOLS)}, ] else: messages [ {role: system, content: f你是助手{i}专注{i}号领域}, {role: user, content: random.choice(POOLS)}, ] start time.time() client.chat.completions.create( modelQwen/Qwen3-32B, messagesmessages, max_tokens128, temperature0.7, ) latencies.append(time.time() - start) return sum(latencies) / len(latencies) print(共享前缀 avg latency:, run_batch(True)) print(随机前缀 avg latency:, run_batch(False))注意 client 初始化放在函数外整个压测复用一个实例避免握手开销污染延迟数据。共享前缀那组的平均时延通常能低 20% 到 40%前缀越长、复用率越高差距越大。如果两个数字几乎一样先别怀疑脚本去查 radix cache 的命中日志大概率是缓存被 eviction 挤掉了。PD 分离的配置需要至少两组实例加一个 Router。Prefill worker 和 Decode worker 分别启动Router 负责分发# Prefill worker python3 -m sglang.launch_server \ --model-path Qwen/Qwen3-32B --tp 2 \ --port 30010 --pd-mode prefill # Decode worker python3 -m sglang.launch_server \ --model-path Qwen/Qwen3-32B --tp 2 \ --port 30020 --pd-mode decode # Router python3 -m sglang.launch_server \ --model-path Qwen/Qwen3-32B \ --port 30000 --pd-mode router \ --prefill-node-url http://node-a:30010 \ --decode-node-url http://node-b:30020生产环境 Router 本身要扩容请求量大了单 Router 会成瓶颈需要多 Router 加负载均衡。这是很多人上了 PD 分离之后才踩到的坑官方文档写得很隐晦。4. 验证请求与成功结果从基线到优化后的吞吐与成本对比配置写完要验证不然不知道优化有没有生效。这一节给验证步骤和预期结果包括单请求连通性验证、压测对比、以及成本换算。所有验证都用 §3 的配置请求侧走 TaoToken 统一通道。第一步连通性验证。先发一个最小请求确认三件套配置正确from openai import OpenAI client OpenAI(base_urlhttps://taotoken.net/api, api_keysk-你的Key) resp client.chat.completions.create( modelQwen/Qwen3-32B, messages[{role: user, content: 回复 OK 两个字母}], max_tokens8, ) print(resp.choices[0].message.content) print(usage:, resp.usage)成功的话会打印模型回复和 token 用量。如果报 401说明 Key 不对或没带上如果报 model not found说明 Model ID 填错去文档核对。这一步过了再进压测不然压测脚本跑半天全是错误请求浪费时间。第二步基线压测。用 §3 的脚本先跑随机前缀那组记录平均延迟和吞吐。同时开一个终端看 GPU 利用率nvidia-smi dmon -s u -d 1-s u只看利用率-d 1每秒刷新。基线跑的时候你会看到 GPU 利用率在请求间隙掉下来这就是排队和空转的证据。记录下平均利用率和峰值利用率后面优化后对比。第三步开 Radix Attention 后压测。同一份脚本跑共享前缀那组对比平均延迟。预期结果是共享前缀组延迟明显低于随机前缀组。如果引擎侧日志能看到 radix cache 命中率确认命中率在合理区间。SGLang 启动时加--log-level info能看到缓存相关日志。第四步Continuous Batching 验证。把并发数从 1 逐步加到 32、64、128观察吞吐曲线。连续批处理生效的话吞吐会随并发上升而上升直到 GPU 打满才平缓。如果并发加到某个点吞吐反而下降说明显存或调度到了瓶颈需要调--mem-fraction-static或--chunked-prefill-size。第五步PD 分离对比。这是收益和场景强相关的一步。AWS 博客公开的一组 100K 长上下文实测数据MiMo-V2-Flash-FP8 on H200值得参考方案输入长度Output token/sMean TTFT (s)Mean TPOT (ms)结果2P2D NIXL100K47.366.6651.2满足 SLO2P2D Mooncake TE100K9.69-244不满足 SLONon-PD 4 节点128K97.477.33240满足 SLO注意看单论 output token/sNon-PD 单节点方案反而更高97.47 vs 47.36。但 Non-PD 的 TPOT 是 240msPD 分离只有 51.2ms。如果业务对 TPOT 有硬性要求比如实时对话和流式输出体验PD 分离就是必须的如果只是离线批量任务Non-PD 反而更划算。别信任何PD 分离一定更快的说法case by case 压测。第六步成本换算。把吞吐数据换算成单位 token 成本。公式是单位 token 成本 硬件小时成本 / 每小时产出 token 数。假设 8 卡 H200 节点每小时成本为 C优化前每小时产出 T1 个 token优化后产出 T2 个 token成本节省比例就是 1 - T1/T2。如果 Radix Attention 让共享前缀场景吞吐翻倍成本直接砍半如果 PD 分离让 TPOT 从 240ms 降到 51ms 但吞吐降了就要看业务更在意哪个指标。我自己的压测感受前缀共享率是 Radix Attention 收益的决定性因素。Agent 场景下 system prompt 占比高收益明显纯随机短对话场景缓存命中率低收益打折。PD 分离的收益则取决于业务对 TTFT 和 TPOT 的双 SLO 要求两者都要才值得上。得物技术实测 Radix Attention 比 vLLM 0.5.0 快 30%、吞吐 1.5 倍SGLang 官方给的共享前缀场景吞吐提升 5 倍以上这些数据可以作为量级参考但具体到你的业务还是要自己压。验证通过的标准连通性请求返回正常、共享前缀组延迟低于随机前缀组、并发吞吐曲线随并发上升、PD 分离场景下 TTFT 和 TPOT 都满足 SLO。四条都过了说明优化生效可以进生产灰度。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth 报错怎么解压测和接入过程中会撞到各种报错这一节按真实报错信息给排查路径。每个报错先给现象再给原因最后给解法。排查顺序建议从请求侧往引擎侧走先确认三件套配置对再看引擎日志。401 Unauthorized。现象是请求返回 401body 里通常带 invalid api key 或 missing authorization。原因有三类Key 没填、Key 填错、Key 没带上。排查步骤先确认api_key字段非空且以sk-开头再确认 Key 是从 https://taotoken.net/api-keys 生成的没有多余空格最后确认请求头里确实带了 Authorization。用 curl 快速验证curl -X POST https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer sk-你的Key \ -H Content-Type: application/json \ -d {model:Qwen/Qwen3-32B,messages:[{role:user,content:hi}],max_tokens:8}curl 通了说明 Key 没问题问题在 SDK 配置curl 也 401 说明 Key 本身有问题重新生成一个。local proxy failed。现象是请求发不出去报连接失败或代理错误。原因通常是环境变量里残留了代理配置或者 SDK 读到了系统代理。排查检查HTTP_PROXY、HTTPS_PROXY、ALL_PROXY这几个环境变量有的话清掉检查 SDK 初始化时有没有显式传 proxy 参数有的话去掉。统一通道的 Base URL 是标准 HTTPS不需要额外代理配置直连即可。reading choices 报错。现象是KeyError: choices或reading choices失败。原因是返回体结构不符合预期通常是请求打到了错误的 endpoint 或者返回了错误页。排查先打印完整响应体看结构确认choices字段存在如果返回的是 HTML 错误页说明 Base URL 拼错了检查是不是漏了/v1或者多了斜杠。OpenAI SDK 的 base_url 填https://taotoken.net/apiSDK 会自动拼/v1/chat/completions不要手动再加/v1。OAuth 相关报错。现象是提示 token 过期或鉴权失败常见于 Claude Code 这类工具的配置。原因是工具侧的鉴权方式和 API Key 方式不同混用了。排查确认工具用的是 API Key 模式而不是 OAuth 模式Claude Code 的 settings 里 Base URL 和 Key 要配套填Model ID 按文档选如果工具支持多种鉴权明确选 API Key。三件套Base URL Key Model ID必须齐全缺一个就会报鉴权类错误。model not found。现象是 404 或 model does not exist。原因是 Model ID 填错或者该模型在当前通道不可用。排查去 https://taotoken.net/doc 核对可用模型列表确认 Model ID 拼写完全一致大小写敏感。切换模型时只改 model 字段其他不动。CUDA out of memory。现象是引擎启动或压测中途 OOM。原因是--mem-fraction-static或--gpu-memory-utilization设太高或者max_tokens留了太大余量导致 KV Cache 预留过多。排查先把显存比例降到 0.8 试再把max_tokens收紧到业务真实值。很多人习惯给 max_tokens 留 2 到 3 倍余量结果 KV Cache 按最大可能值预留显存白白空转。把 max-token 收紧到业务真实值有时候比折腾 PD 分离省得还多。radix cache 命中率低。现象是共享前缀场景延迟没降下来。原因是缓存被 eviction 挤掉了或者请求到达间隔太长缓存已失效。排查看引擎日志里的 cache hit 相关指标把相似请求集中连续发送提高时间局部性检查 radix cache 容量配置必要时调大。SGLang 的 GitHub issue 里讨论过彻底关闭 KV 缓存复用的场景默认开着极端场景可能需要手动调。Router 成为瓶颈。现象是上了 PD 分离后延迟反而升高。原因是所有请求先过 Router单 Router 在高 QPS 下成单点。排查多 Router 加负载均衡监控 Router 的 CPU 和连接数。这个在官方文档里写得很隐晦生产环境被教育之后才明白。排查通用原则先确认三件套配置对再看引擎日志最后看硬件监控。请求侧的问题占一半以上别一上来就怀疑引擎。每次改一个变量改完压一组数据不然归因不了。6. 调优清单与下一步按优先级抄以及什么时候该上 PD 分离把前面所有内容压成一张按优先级排的清单这是我在多套环境验证过的顺序。P0 是必做且成本最低的P3 是收益大但成本高的按预算和场景选。优先级动作预期收益成本P0确认 chunked prefill 已开启高并发 RT 提升约 2 倍改一个参数P0检查前缀缓存开关vLLM 老版本需手动开共享前缀场景吞吐翻倍改一个参数P1max-token 设成业务真实需要别留余量显存占用和 TPOT 同步下降改代码P1FP8/INT4 量化显存减半吞吐提升换权重文件P2Radix Attention 命中率监控定位缓存被挤掉的问题加监控P3PD 分离严格 SLO 场景收益巨大多机部署 网络改造P0 两件事先做都是改一个参数的事收益立竿见影。chunked prefill 在 vLLM 新版本默认开SGLang 要手动加--enable-mixed-chunk前缀缓存在 vLLM 老版本要手动加--enable-prefix-cachingSGLang 默认开。这两件事没做后面所有优化都是空中楼阁。P1 两件事性价比最高。max-token 收紧到业务真实值显存和 TPOT 同步下降有时候比折腾 PD 分离省得还多。量化用 FP8 或 INT4显存减半吞吐提升代价是精度略降多数业务能接受。这两件事做完成本通常已经降了一大截。P2 加 Radix Attention 命中率监控定位缓存被挤掉的问题。没有监控就是盲调不知道收益从哪来、为什么没生效。SGLang 启动加--log-level info能看到缓存日志生产环境建议接监控系统。P3 才是 PD 分离。上之前先想清楚业务是否同时要求严格 TTFT 和 TPOT。两者都要PD 分离是正解只要一个先别折腾Non-PD 可能更划算。AWS 那组数据里 Non-PD 的 output token/s 反而更高但 TPOT 是 240msPD 分离只有 51.2ms选哪个取决于业务对延迟的敏感度。别信任何PD 分离一定更快的说法case by case 压测。已经在用 vLLM 或 SGLang 的PagedAttention 和 Continuous Batching 默认就是开着的不用折腾。先把 Radix Attention 的收益吃满检查业务有没有高共享率的前缀比如 system prompt、多轮对话、Agent 工具描述有的话收益立竿见影。预算有限的P0 加 P1 三样chunked prefill、优化输出长度、量化性价比最高做完再评估要不要上 P3。下一步值得关注的方向是投机采样speculative decoding和更激进的低精度推理这是哪些算力没吃满这个问题的答案2026 下半年会有更多工程化落地。投机采样用小模型草稿加目标模型验证能在不损失精度的前提下提升吞吐适合对精度敏感又想要吞吐的场景。最后留个问题你现在的推理服务GPU 利用率是多少有没有遇到过看起来吞吐很高、但用户体验很差的情况把 TTFT 的 P90 和 TPOT 的 P99 打出来看往往能发现吞吐数字背后的真相。压测数据发到评论区我后面整理成一篇完整的调优案例。需要动手的可以从这里开始模型对话 https://taotoken.net/model-chat 验证模型可用性Coding Plan https://taotoken.net/coding-plan 看长期编码场景的套餐API Keys https://taotoken.net/api-keys 生成 Key接入文档 https://taotoken.net/doc 核对模型名和参数。三件套配好压测脚本跑起来数据会告诉你该上哪个优化。