资讯详情

拆开大模型推理引擎:vLLM 与 SGLang 的 Prefill/Decode 分离架构与 TaoToken 统一接入实践

📅 2026/10/26 15:42:00 | 华诺云谱 👁 阅读
拆开大模型推理引擎:vLLM 与 SGLang 的 Prefill/Decode 分离架构与 TaoToken 统一接入实践
1. 深夜告警TTFT 飙到 3 秒问题不在模型线上 AI 助手白天跑得好好的晚上八点流量一上来用户开始反馈转圈圈首 token 迟迟不出来或者打字打到一半突然卡住。翻监控能看到两个指标同时恶化——TTFT首 token 延迟从 400ms 涨到 3sTPOT每 token 生成时间从 30ms 抖到 150ms。模型没换、权重没动、GPU 也没坏问题出在推理引擎的调度策略上。传统单引擎把 Prefill读题和 Decode作答混在同一批 GPU 上调度而这两件事的计算特征完全相反。Prefill 把整段 prompt 一次性做 forward计算量随输入长度线性增长是典型的计算密集型任务直接决定 TTFTDecode 逐 token 生成每步只处理最新一个 token却要反复把模型权重从 HBM 搬到寄存器是典型的内存带宽密集型任务直接决定 TPOT。一个吃算力一个吃带宽塞进同一个引擎就像让短跑运动员和马拉松选手跑同一场比赛互相拖累。具体表现为两个痛点。第一是 Prefill Interruption新到的 Prefill 批次频繁打断正在进行的 Decode 批次Decode 刚算完一个 token 又被拉去处理长 promptTPOT 剧烈抖动用户体感就是打字卡顿。第二是 DP Attention Imbalance数据并行部署下一个 DP Worker 可能在处理 Prefill另一个在处理 Decode负载不均把整体 Decode 延迟拉高。SGLang 官方文档把这两个问题列为 PD 分离要解决的核心痛点。PD 分离Prefill/Decode Disaggregation的思路很直接把 Prefill 和 Decode 拆成两套独立实例中间通过高速网络传 KV Cache。Prefill 实例负责读题算完把 KV Cache 交给 Decode 实例Decode 实例只负责作答从 KV Cache 续写。收益有三点ITL 稳定Decode Worker 不再被 Prefill 打断token 间延迟可预测独立扩展Prefill 多配算力、Decode 多配带宽各自按需伸缩硬件利用率提升每类负载针对性优化不再互相迁就。vLLM 与 AMD 合作的 MORI-IO 文章给过一组直观数据单节点 8×MI300XQwen3-235B-A22B-FP8ISL2000、OSL1000、8 req/s标准 2×TP4 部署的 goodput同时满足 TTFT1s 且 ITL50ms 的请求占比只有 30/100而 MORI-IO Write 模式达到 73/100相对提升 2.5 倍。这就是为什么 2025 年以来主流引擎都在押注这个方向。本文面向自建推理服务的开发者给出 vLLM 与 SGLang 两套引擎的启动参数、分离部署配置模板以及通过 TaoToken 统一 Key/API 通道接入后的连通性验证与压测对比动作目标是让你复现一套可运行的分离架构基线。适合已经能单机跑起 vLLM 或 SGLang、正在被 TTFT 和 ITL 双指标困扰的同学。2. TaoToken 前置统一 Key 与 API 通道准备在动手拆 PD 之前先把调用入口这件事收敛掉。自建推理服务常见的麻烦是vLLM 一套 OpenAI 兼容地址、SGLang 一套地址、线上业务又要接别家模型Key 散落在各个配置文件里压测脚本还得为每个后端写一份。TaoToken 在这里扮演的是统一接入层——一个 Key、一个 Base URL把自建引擎和托管模型放在同一条通道后面压测和灰度都省事。先把账号和 Key 拿到。打开官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册进入控制台后到 API Keys 页面创建密钥。控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsole 创建时建议按用途命名比如pd-bench-prefill、pd-bench-decode后面压测出问题好定位是哪个 Key 在打流量。API 基地址统一用 https://taotoken.net/api 注意这个地址不带任何查询参数SDK 里填 Base URL 时不要自作主张加斜杠或路径。模型对话的调试入口在 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodels 接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdoc 遇到 401 或路径 404 先翻文档里的路径规范比在群里问快。这里要说清楚一件事TaoToken 是统一接入通道不是让你把自建 vLLM/SGLang 的推理算力托管出去。你的 Prefill 实例和 Decode 实例仍然跑在自己的 GPU 上TaoToken 负责的是请求入口、Key 管理和多后端路由这一层。压测时你可以让压测客户端统一打 TaoToken 的 Base URL由它转发到你的自建引擎也可以直连引擎做对照两种方式都保留数据才可信。环境变量建议这样组织避免 Key 硬编码进脚本export TAOTOKEN_BASE_URLhttps://taotoken.net/api export TAOTOKEN_API_KEYsk-你的密钥 export VLLM_PREFILL_URLhttp://127.0.0.1:8100/v1 export VLLM_DECODE_URLhttp://127.0.0.1:8200/v1 export SGLANG_ROUTER_URLhttp://127.0.0.1:30000如果你打算长期跑编码类或 Agent 类负载可以顺带看一下 Coding Plan 的入口 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-plan 它和按量 Key 是两条计费路径压测阶段用按量 Key 更灵活稳定后再评估套餐。Claude Code 这类工具的接入说明在 https://taotoken.net/claudecode?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecode 和本文的 PD 分离是两件事别混在一起配。最后提醒一个容易踩的坑TaoToken 的 Key 权限和自建引擎的鉴权是两层。自建 vLLM 如果加了--api-key压测客户端要同时带 TaoToken 的 Key 和引擎的 Key别只配一个然后对着 401 发呆。下一节的配置模板里我会把这两层都写清楚。3. 可复制配置vLLM 与 SGLang 分离部署模板这一节给的是能直接落地的启动命令和配置文件。先说明硬件前提PD 分离的 KV Cache 转移必须走高速互联单机多卡用 NVLink跨节点用 RDMARoCE/InfiniBand。如果你只有一张卡或者卡间走 PCIe 且没有 P2P分离的收益会被转移开销吃掉建议先别上。3.1 vLLM 的 PD 分离启动参数vLLM 在单节点场景用 MORI-IO 做 KV Cache 转移支持 Read 和 Write 两种模式。Write 是默认模式Prefill 逐层通过 RDMA Write 直接写入 Decode 的预分配内存Decode 可以尽早开始生成流水线化程度更高。先起 Prefill 实例vllm serve Qwen/Qwen3-32B \ --host 0.0.0.0 --port 8100 \ --tensor-parallel-size 4 \ --kv-transfer-config { kv_connector: MoriIOConnector, kv_role: kv_producer, kv_connector_extra_config: { proxy_addr: 127.0.0.1, proxy_port: 9000, http_port: 8100, transfer_mode: write } } \ --api-key prefill-internal-key再起 Decode 实例注意kv_role换成kv_consumer端口和 TP 可以独立设置vllm serve Qwen/Qwen3-32B \ --host 0.0.0.0 --port 8200 \ --tensor-parallel-size 4 \ --kv-transfer-config { kv_connector: MoriIOConnector, kv_role: kv_consumer, kv_connector_extra_config: { proxy_addr: 127.0.0.1, proxy_port: 9000, http_port: 8200, transfer_mode: write } } \ --api-key decode-internal-key两边的proxy_addr/proxy_port必须一致这是 ZMQ 元数据交换的通道用来交换 KV 缓存基地址、块大小、层张量步幅建立 RDMA 会话。transfer_mode选read时Proxy 等 Prefill 完成后把 KV 块位置转发给 DecodeDecode 主动通过 RDMA 读取适合 Prefill 和 Decode 不在同一信任域的部署。调度侧还要配 chunked prefill 相关参数否则长 prompt 会把 token budget 吃满--max-num-batched-tokens 8192 \ --long-prefill-token-threshold 4096 \ --scheduler-reserve-full-isl \ --watermark 0.01max_num_batched_tokens是单次 iteration 最大 token 数默认 2048压测时按显存往上调long_prefill_token_threshold超过阈值视为长请求默认 0 表示禁用scheduler_reserve_full_isl在接纳前检查完整序列能否放进 KV cache防止过度接纳导致 cache thrashingwatermark预留 KV cache 块比例减少频繁 eviction 和 preemption。3.2 SGLang 的 Router Mooncake 配置SGLang 通过--pd-disaggregation组合 Prefill 与 Decode 实例传输引擎支持 Mooncake 和 NIXL 两个后端。先起 Prefillpython -m sglang.launch_server \ --model-path Qwen/Qwen3-32B \ --host 0.0.0.0 --port 30001 \ --tp-size 4 \ --pd-disaggregation \ --prefill-only \ --disaggregation-transfer-backend mooncake \ --disaggregation-bootstrap-port 8998再起 Decodepython -m sglang.launch_server \ --model-path Qwen/Qwen3-32B \ --host 0.0.0.0 --port 30002 \ --tp-size 4 \ --pd-disaggregation \ --decode-only \ --disaggregation-transfer-backend mooncake \ --disaggregation-bootstrap-port 8999最后起 Router把两类实例注册进去python -m sglang_router.launch_router \ --pd-disaggregation \ --prefill http://127.0.0.1:30001 \ --decode http://127.0.0.1:30002 \ --host 0.0.0.0 --port 30000 \ --policy cache_awareMooncake 支持 NVLink 传输、KV 转移队列和心跳健康检查队列大小用SGLANG_DISAGGREGATION_QUEUE_SIZE控制默认值在高并发下可能偏小压测时调到 512 以上观察。NIXL 后端默认用 UCX需要换 LibFabric 时设SGLANG_DISAGGREGATION_NIXL_BACKENDLIBFABRIC。SGLang 还有一个值得用的特性异构 TP 的 GPU Staging Buffer。当 Prefill 和 Decode 的 TP 大小不同比如 Prefill 用 TP4、Decode 用 TP8Mooncake 会通过 staging buffer 把 KV 头切片到连续缓冲区做批量 RDMA 传输官方称高并发下比默认逐 token 切片有 2-5 倍吞吐提升接近同构 TP 基线。配置方式是在启动参数里显式声明两边的 TPRouter 会自动协商。Router 的负载均衡策略支持round_robin、total_tokens、total_requests以及基于请求历史近似 Radix 树的 cache-aware 路由。在线对话场景建议用cache_aware把请求路由到前缀匹配最高的 Worker命中 KV 前缀缓存能显著降 TTFT。3.3 通过 TaoToken 统一入口的 settings 片段如果你希望压测客户端只认一个 Base URL可以在 TaoToken 侧配置后端路由把模型名映射到自建引擎。客户端配置保持 OpenAI 兼容格式{ base_url: https://taotoken.net/api, api_key: sk-你的TaoToken密钥, model: qwen3-32b-pd, extra_headers: { X-Backend-Hint: vllm-pd } }对应的 Python 客户端最小片段from openai import OpenAI import os client OpenAI( base_urlos.environ[TAOTOKEN_BASE_URL], api_keyos.environ[TAOTOKEN_API_KEY], ) resp client.chat.completions.create( modelqwen3-32b-pd, messages[{role: user, content: 用三句话解释 PD 分离}], max_tokens256, streamTrue, ) for chunk in resp: delta chunk.choices[0].delta.content if delta: print(delta, end, flushTrue)注意model字段要和 TaoToken 控制台里配置的模型名一致不是引擎侧的原始模型路径。这一步配错是最常见的 404 来源。4. 验证请求与压测确认分离架构真的生效配置写完不等于跑通必须用请求验证 KV 转移链路和调度行为。先做连通性冒烟再做压测对比。4.1 冒烟验证直连 Prefill 实例发一个短请求确认引擎本身活着curl -s http://127.0.0.1:8100/v1/completions \ -H Authorization: Bearer prefill-internal-key \ -H Content-Type: application/json \ -d {model:Qwen/Qwen3-32B,prompt:hello,max_tokens:8}再打 Router 或 TaoToken 入口确认请求能穿过分离链路curl -s https://taotoken.net/api/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d {model:qwen3-32b-pd,messages:[{role:user,content:ping}],max_tokens:16}返回里choices[0].message.content有内容、usage字段完整说明链路通了。如果返回 200 但内容是空的去 Decode 实例日志里找kv_transfer相关行大概率是 KV 没接上Decode 拿不到上下文只能吐空。4.2 压测脚本与指标采集用 vLLM 自带的 benchmark 或 evalscope 都行关键是同时盯 TTFT 和 ITL 两个指标。下面是一个最小压测脚本模拟 ISL2000、OSL1000 的在线对话负载import asyncio, time, statistics from openai import AsyncOpenAI client AsyncOpenAI(base_urlhttps://taotoken.net/api, api_keysk-你的密钥) PROMPT 请详细说明 推理引擎调度 * 200 # 约 2000 token async def one_request(idx): t0 time.perf_counter() ttft None tokens 0 stream await client.chat.completions.create( modelqwen3-32b-pd, messages[{role: user, content: PROMPT}], max_tokens1000, streamTrue, ) async for chunk in stream: if chunk.choices[0].delta.content: if ttft is None: ttft time.perf_counter() - t0 tokens 1 total time.perf_counter() - t0 itl (total - ttft) / max(tokens - 1, 1) return ttft, itl async def main(): results await asyncio.gather(*[one_request(i) for i in range(100)]) ttfts [r[0] for r in results] itls [r[1] for r in results] good sum(1 for t, i in results if t 1.0 and i 0.05) print(fTTFT p50{statistics.median(ttfts):.3f}s p99{sorted(ttfts)[98]:.3f}s) print(fITL p50{statistics.median(itls)*1000:.1f}ms p99{sorted(itls)[98]*1000:.1f}ms) print(fgoodput{good}/100) asyncio.run(main())跑三组对照标准单引擎不开 PD、vLLM PD 分离、SGLang PD 分离。每组跑 100 个请求记录 TTFT p50/p99、ITL p50/p99、goodput。参考 vLLM 官方数据标准 2×TP4 的 goodput 大约 30/100MORI-IO Write 模式能到 73/100你的硬件和模型不同绝对值会有差异但相对趋势应该一致。4.3 结果解读如果 PD 分离组的 ITL p99 明显低于单引擎组说明 Decode 不再被 Prefill 打断分离生效。如果 TTFT 反而变差检查 KV 转移是否走了 TCP——用nvidia-smi topo -m确认卡间是 NVLink 还是 PCIe用ibstat确认 RDMA 网卡状态。转移开销吃掉收益是 PD 分离最常见的翻车方式。如果 goodput 提升不明显但 ITL 改善了可能是 Prefill 和 Decode 的实例比例不对。长 prompt 多的场景RAG 问答Prefill 压力大要多配 Prefill 实例对话轮次深的场景 Decode 压力大要多配 Decode 实例。这个比例没有银弹靠压测数据迭代。5. 常见报错排查401、local proxy failed 与 reading choices分离架构的报错比单引擎多一层因为多了 KV 转移和 Router 转发。下面按真实报错逐条拆。5.1 401 Unauthorized最常见两层鉴权只配了一层。自建 vLLM 加了--api-key时直连引擎的请求要带引擎 Key走 TaoToken 的请求要带 TaoToken Key。压测脚本里如果同时打两个入口检查环境变量有没有串。另外 TaoToken 的 Key 有权限范围创建时如果只勾了部分模型请求未授权模型也会返回 401 而不是 403别被状态码误导。5.2 local proxy failed / proxy connection refusedvLLM 的 MORI-IO 依赖 ZMQ proxy 做元数据交换。报这个错说明 Prefill 和 Decode 的proxy_addr/proxy_port不一致或者 proxy 进程没起来。检查两边kv_connector_extra_config里的proxy_port是否相同防火墙是否放行该端口。跨节点部署时proxy_addr不能填127.0.0.1要填实际可达的 IP。5.3 reading choices 相关报错客户端解析响应时抛reading choices或choices is undefined通常是上游返回了错误 JSON 而不是标准 completion 结构。先用 curl 看原始响应体常见原因是模型名不匹配TaoToken 侧配置的模型名和请求里的model字段不一致或者后端引擎返回了 503KV 转移队列满。SGLang 侧把SGLANG_DISAGGREGATION_QUEUE_SIZE调大能缓解队列满导致的 503。5.4 OAuth / token 过期类错误如果你在压测脚本里用了短期 token 或者从 Claude Code 那类工具复制的配置可能遇到 token 过期。TaoToken 的 API Key 是长期有效的但如果你在客户端里配了 OAuth 流程比如某些 SDK 的默认认证会走到错误的认证路径。确认客户端用的是api_key而不是 OAuth bearer 流程Base URL 是https://taotoken.net/api而不是带/oauth的路径。5.5 KV 转移超时日志里出现kv_transfer timeout或RDMA connection failed先确认ibstat里网卡是 Active再确认 Prefill 和 Decode 的transfer_mode一致一边 write 一边 read 会握手失败。跨节点场景检查 RoCE 的 PFC 配置丢包会导致重传把转移延迟拉高。5.6 三件套自查清单凡是涉及 CC Switch、Cline MCP、Codex auth.json 这类工具的接入配置里必须同时出现三件套Base URL、Key、Model ID。缺任何一个都会报鉴权或路由错误。以 Codex 的auth.json为例{ base_url: https://taotoken.net/api, api_key: sk-你的密钥, model: qwen3-32b-pd }Cline 的 MCP 配置同理baseUrl、apiKey、modelId三个字段一个不能少。CC Switch 切换配置时注意别把自建引擎的 Key 和 TaoToken 的 Key 混在同一个 profile 里。6. 继续往下走把基线跑成日常PD 分离不是开箱即用的银弹它要求集群有高速互联、运维能独立扩缩容两套实例、压测体系能同时盯住 TTFT 和 ITL。但一旦跑通同样一批 GPU 的 goodput 能翻倍以上。本文给的配置模板和压测脚本可以直接复现基线接下来要做的是把这个基线变成日常监控的一部分。具体动作建议这样排先把 vLLM 或 SGLang 的分离部署在测试环境跑通用第 4 节的脚本拿到第一组 TTFT/ITL 数据然后把 Prefill 和 Decode 的实例比例按业务流量特征调一轮长 prompt 场景多配 Prefill深对话场景多配 Decode最后把压测脚本接进 CI每次改调度参数都跑一遍回归防止 chunked prefill 的 watermark 或 token budget 调错把 ITL 打回原形。接入层这边TaoToken 的 API Key 管理入口在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keys 建议给压测、灰度、生产各建一个 Key出问题能按 Key 维度看流量。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdoc 路径规范和错误码都在里面遇到 401 或 404 先翻文档再动手改配置。模型对话调试用 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodels 改完 Router 策略后在这里发几个请求确认路由生效。长期跑编码类或 Agent 类负载的话Coding Plan 入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-plan 和按量 Key 是两条计费路径压测阶段用按量更灵活。Claude Code 的接入说明在 https://taotoken.net/claudecode?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecode 和 PD 分离是两套东西别在同一个配置文件里混着改。最后一个实操技巧调max_num_batched_tokens时别一次跳太大从 4096 开始每次加 2048每档跑 100 个请求看 ITL p99 有没有拐点。拐点出现的位置就是你这套硬件的甜点区比抄别人的参数靠谱。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑