并非所有预填充都相同:TaoToken 多轮 LLM 服务 PPD 分离配置实战
发布时间:2026/9/26 14:23:30来源:尧图网络
1. 多轮对话里那个让人抓狂的首 token 延迟如果你正在做多轮 LLM 服务大概率遇到过这个现象第一轮响应挺快到了第三、第四轮明明用户只补了一句话首 token 却要等两三秒。你去看监控发现 Prefill 阶段耗时暴涨GPU 利用率却不高网络带宽倒是快打满了。这个问题的根子在于传统 PD 分离架构的单向 KV 传输协议。Prefill 节点是生产者Decode 节点是消费者没有反向通道。上一轮生成的 KV 缓存明明躺在 Decode 节点的显存里Prefill 节点却拿不到只能把整个对话历史重新算一遍再把 KV 传回去。有实测数据显示这种重计算能占多轮 Prefill 成本的 99%。更细一点看Prefill 其实分两种。全预填充Full Prefill处理没有任何缓存的新提示词注意力复杂度是 O(n²)对 Decode 的干扰极大批量 200 时 TPOT 减速能到 48%。追加预填充Append-Prefill只处理新增的那几个 token复用已有 KV复杂度是 O(m(nm))干扰只有 2% 左右。差了一个数量级。所以思路就出来了第 2 轮及以后的追加预填充完全可以放到 Decode 节点本地做省掉重计算和 KV 传输。但静态地把所有追加预填充都丢给 Decode 也不是万能药P 节点稀缺时它是最优解P 节点充足时反而拖累 TPOT。这就是 PPDPrefill-capable Decode动态路由要解决的问题。这篇不讲论文推导讲怎么落地。我会用 TaoToken 统一 Key/API 通道接入给你一份可复制的 config.toml 和 settings.json 骨架然后演示怎么验证多轮会话下 Prefill 阶段耗时和吞吐的变化。适合正在调多轮服务延迟、想搞清楚 PPD 分离怎么配的工程师。2. 用 TaoToken 打通统一接入通道PPD 分离架构的验证需要一个稳定的模型调用入口。多轮会话测试要反复发请求、对比不同路由策略下的 TTFT 和 TPOT如果每个模型、每个环境都要单独配 Key光切换就够烦的。TaoToken 在这里的角色是统一 Key/API 通道。你申请一个 Key就能通过同一套 API 格式访问不同模型base_url 指向https://taotoken.net/api即可。对于 PPD 验证场景这意味着你可以把 Prefill 节点和 Decode 节点的模型调用都收敛到同一个通道日志和耗时统计也好对齐。具体操作先到控制台创建 API Key。地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 登录后在 API Keys 页面新建一个复制出来存好。这个 Key 后面会写进 settings.json。如果你还没决定用哪个模型做验证可以先去模型对话页面试一下手感确认模型在多轮上下文里的表现符合预期https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。选一个上下文窗口够大、支持前缀缓存的模型PPD 的效果会更明显。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 里面有完整的请求格式和参数说明。API 端点本身不带 UTMhttps://taotoken.net/api 。有一点要注意TaoToken 是统一接入通道不是让你绕过什么。它的价值在于把多模型、多环境的调用收敛成一套配置减少 PPD 验证时的变量干扰。你该做的缓存策略、路由逻辑一样都不能少。3. 可复制的 PPD 分离配置骨架下面这份配置分两部分config.toml 管服务端的 PPD 路由和节点分配settings.json 管客户端的 TaoToken 接入。你可以直接拿去改。3.1 config.tomlPPD 路由与节点池# config.toml - PPD 分离服务配置骨架 [server] host 0.0.0.0 port 8000 model your-model-name max_model_len 32768 [ppd] # 启用 PPD 动态路由 enabled true # 第 1 轮强制走 Prefill 节点无缓存上下文 first_turn_route prefill # 第 2 轮及以后dynamic 表示按查找表决策 append_prefill_route dynamic # 决策权重wtft 调大偏向降低首 token 延迟wtpot 调大偏向稳定生成速度 wtft 1.0 wtpot 1.0 # 查找表离线校准文件 lookup_table ./ppd_lookup.json # 决策延迟上限毫秒超过则回退到默认路由 decision_timeout_ms 1 [prefill_pool] # Prefill 节点列表格式 host:port nodes [127.0.0.1:8101, 127.0.0.1:8102] # 每个节点的最大并发预填充数 max_concurrent 4 [decode_pool] nodes [127.0.0.1:8201, 127.0.0.1:8202, 127.0.0.1:8203] max_concurrent 8 # 允许 Decode 节点本地执行追加预填充 local_append_prefill true [kv_transfer] # KV 传输协议nccl 或 rdma protocol nccl # 传输超时秒 timeout_s 30 # 传输队列上限超过则触发背压 max_queue 64 [metrics] # 开启 TTFT / TPOT / 吞吐量采集 enable true export_interval_s 5几个关键参数说明。append_prefill_route设成dynamic才会走 PPD 查找表如果你只想做静态对比实验可以改成prefill全走 P 节点等价传统 PD或decode全本地等价 x1。wtft和wtpot是单旋钮控制调大wtft会让路由更偏向 Decode 本地执行以降低 TTFT调大wtpot则更偏向 Prefill 节点以稳定 TPOT。lookup_table指向离线校准生成的 JSON。校准方法在 x0 和 x1 两个极端下分别测量不同上下文长度、输入输出比、QPS 组合的 TTFT 和 TPOT算出决策分数存表。在线阶段每个请求查表1ms 内返回路由决策。3.2 settings.jsonTaoToken 客户端接入{ api_base: https://taotoken.net/api, api_key: sk-你的TaoToken密钥, model: your-model-name, default_headers: { Content-Type: application/json }, timeout_s: 60, retry: { max_attempts: 3, backoff_s: 1.5 }, session: { enable_multi_turn: true, max_history_tokens: 16384, cache_prefix: true }, ppd_client: { report_ttft: true, report_tpot: true, route_hint: auto } }api_base固定指向 TaoToken 的 API 端点。session.cache_prefix打开前缀缓存这样客户端侧也能复用部分 KV和服务端的 PPD 路由形成配合。ppd_client.route_hint设成auto时客户端会把每轮的上下文长度和输入输出比上报给服务端辅助查找表决策。如果你用的是 Coding Plan 做长期编码或 Agent 场景配置结构类似只是模型和会话参数按 Agent 的调用模式调整https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。4. 验证多轮会话下的 Prefill 耗时与吞吐配置写好了接下来要证明 PPD 确实起作用。验证的核心是对比三种路由策略在多轮会话下的表现全走 Prefillx0、全走 Decode 本地x1、PPD 动态路由。4.1 构造多轮测试请求写一个简单的 Python 脚本模拟 5 轮对话每轮追加新内容记录每轮的 TTFT 和 Prefill 耗时。import time import json import requests API_BASE https://taotoken.net/api API_KEY sk-你的TaoToken密钥 MODEL your-model-name def multi_turn_test(turns5, rounds10): history [] results [] for r in range(rounds): session [] for t in range(turns): user_msg f第{t1}轮请基于上文继续分析补充第{t1}个要点。 session.append({role: user, content: user_msg}) payload { model: MODEL, messages: session, max_tokens: 128, stream: False } headers { Authorization: fBearer {API_KEY}, Content-Type: application/json } start time.perf_counter() resp requests.post( f{API_BASE}/v1/chat/completions, headersheaders, jsonpayload, timeout60 ) elapsed time.perf_counter() - start data resp.json() reply data[choices][0][message][content] session.append({role: assistant, content: reply}) results.append({ round: r, turn: t 1, ttft_ms: round(elapsed * 1000, 2), prompt_tokens: data.get(usage, {}).get(prompt_tokens, 0) }) return results if __name__ __main__: res multi_turn_test() for item in res[:15]: print(json.dumps(item, ensure_asciiFalse))这个脚本每轮把完整历史发过去服务端会根据 PPD 配置决定第 2 轮及以后走 Prefill 还是 Decode 本地。你可以在 config.toml 里切换append_prefill_route的值跑三组对比。4.2 采集 Prefill 阶段耗时服务端 metrics 开启后每 5 秒导出一次。重点看两个指标prefill_duration_ms和prefill_queue_depth。前者是 Prefill 阶段实际计算耗时后者是排队深度。# 拉取 metrics 端点 curl -s http://127.0.0.1:8000/metrics | grep -E prefill_duration|prefill_queue|ttft|tpot预期结果x0 模式下第 2 轮及以后prefill_duration_ms随轮次线性增长因为每轮都在重算全部历史。x1 模式下第 2 轮及以后prefill_duration_ms大幅下降因为只算新增 token。PPD 动态路由在 P 节点负载低时可能把部分请求分给 Prefill负载高时切到 Decode 本地整体 TTFT 和 TPOT 的帕累托前沿最优。4.3 吞吐量对比吞吐量用每秒完成的 token 数衡量。在相同 QPS 下跑三组配置记录成功率和平均吞吐。路由策略第2轮 TTFTTPOT 减速成功率吞吐量x0 全 Prefill高随轮次增长低高负载下可能低于 95%受 KV 传输带宽限制x1 全 Decode 本地低约降 68%略高稳定较高PPD 动态接近 x1接近 x0100%最优或接近最优这张表是预期趋势你的实际数字会因模型、硬件、网络不同而变化。关键看相对关系PPD 应该在 TTFT 上接近 x1在 TPOT 上接近 x0同时成功率保持 100%。4.4 网络变慢时的鲁棒性PPD 的优势在网络变慢时会扩大。因为第 2 轮及以后的请求不再走 P→D 的 KV 传输通道网络带宽下降对它的影响小。你可以在测试环境里用tc命令模拟带宽限制观察 x0 和 PPD 的 TTFT 差距是否拉大。# 在 Prefill 节点上模拟 100GbE 带宽示例按实际网卡调整 sudo tc qdisc add dev eth0 root tbf rate 10gbit burst 32kbit latency 400ms跑完记得删掉规则sudo tc qdisc del dev eth0 root。5. 本篇常见错排查5.1 第 2 轮 TTFT 没降下来先检查append_prefill_route是不是真的设成了dynamic或decode。如果还是prefill那第 2 轮照样走 Prefill 节点重算。再看lookup_table文件是否存在且格式正确查找表缺失时系统会回退到默认路由。另一个可能是客户端没开cache_prefix。服务端 PPD 路由依赖前缀缓存命中如果客户端每次发完整历史但没标记缓存前缀服务端可能无法正确识别追加预填充。5.2 成功率掉到 95% 以下大概率是 KV 传输队列打满。检查kv_transfer.max_queue和timeout_s。在高 QPS 下如果 P 节点稀缺x0 模式的 KV 传输会饱和带宽导致请求排队超时。把append_prefill_route切到dynamic让部分追加预填充走 Decode 本地能显著降低传输负载。5.3 TPOT 波动大PPD 动态路由在 P 节点和 D 节点之间切换时如果wtpot设得太小可能频繁把追加预填充丢给 Decode 本地增加 Decode 节点的计算负担导致 TPOT 抖动。把wtpot调大一点让路由更偏向 Prefill 节点TPOT 会稳一些。代价是 TTFT 可能略升。5.4 TaoToken 请求返回 401检查 settings.json 里的api_key是否和控制台创建的一致。注意 Key 有有效期过期了要去 API Keys 页面重新生成https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。另外确认api_base是https://taotoken.net/api不要多加路径后缀。5.5 查找表决策延迟超过 1ms查找表太大或者索引结构不合理。离线校准时控制网格粒度三个轴上下文长度、输入输出比、QPS各分 8 到 10 档就够了太细会导致查表变慢。如果还是超时把decision_timeout_ms调大或者优化查找表的索引方式比如用有序数组加二分。6. 把 PPD 验证跑起来整套流程走下来核心动作就三个配好 config.toml 的 PPD 路由参数用 settings.json 接入 TaoToken 统一通道然后跑多轮测试脚本对比三种策略的 TTFT 和吞吐。我自己的经验是先在单机双卡环境把 x0 和 x1 的差距跑出来确认前缀缓存和 KV 复用逻辑没问题再上 PPD 动态路由。这样出问题时容易定位是配置问题还是路由逻辑问题。如果你要长期跑多轮 Agent 或编码场景Coding Plan 的接入方式可以省掉不少 Key 管理的麻烦https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。模型对话页面适合快速验证模型在多轮上下文里的表现https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。接入文档里有完整的 API 参数说明https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。最后提醒一句PPD 的查找表是离线校准的硬件或工作负载分布大变时它会优雅降级但不再最优。生产环境里最好加一个在线监控观察 TTFT 和 TPOT 的 P99偏离校准集太多时重新跑一遍离线校准。
网站建设高端定制企业官网