# 流式中段卡顿对冲与"净生成时间"口径调研 > 调研日期: 2026-09-11。对应缺口: ①对冲触发判据只盯"首 token 未到",真实故障是"首 token 快(中位 1.6s)但流中段长时间沉默(中位 55s)";②`generation_ms` 把中段沉默包进墙钟,下游需要"净生成时间"。 ## 〇、总览: 结论先行 ### 主题 A: 流式中段卡顿(stream started but stalls mid-way)的对冲/恢复范式 | 范式名称 | 出处 | 与我们问题的对应关系 | 可直接借鉴的做法 | | --- | --- | --- | --- | | Hedged/Tied requests(原始定义) | Dean & Barroso, CACM 2013 | 论文语境是一元(unary)完整响应的 leaf 扇出,"first result"指整个响应,**未讨论增量/流式响应** | 延迟派发(等 p95 再发第二路)+ 首个结果到达即取消其余 + 额外负载可低至 ~2%;tied request 的跨副本取消协议 | | 自适应对冲(TTFT 竞速) | llms.blog 生产架构文;bhope/hedge; InfoQ | LLM 网关语境把赢家裁定从"响应头"前移到"首 token";但该文断言"卡顿几乎只发生在排队/prefill"——**与我们实测(中段沉默中位 55s)相矛盾**,SGLang decode 饿死 issue 证明中段卡顿真实存在 | 按模型×渠道维护滚动 P90/P95 TTFT(DDSketch)作动态对冲阈值;token bucket 限制对冲放大率 | | Envoy hedge policy | Envoy 官方文档 | 对冲赢家裁定在**响应头**("first response with acceptable headers"),一旦裁定即 commit;**响应体中段停顿不会重新对冲**,只能靠 stream idle timeout 兜底 | "判赢即 commit、不回头"的语义参考;`hedge_on_per_try_timeout` 命名;per_try_timeout 只在响应开始前有效(PR #10082) | | gRPC hedging | gRPC A6 提案 + 官方指南 | hedgingDelay 以"收到非错误响应(头)"为节拍;收到 nonFatal 状态码立即补发下一路;server pushback(`grpc-retry-pushback-ms`)可推迟/禁止对冲;retryThrottling 用 token bucket 压对冲率 | `maxAttempts`/`hedgingDelay`/`nonFatalStatusCodes` 配置三元组;上限钳到 5;deadline 覆盖整个对冲链 | | 流内不活跃看门狗(inactivity watchdog) | LiteLLM PR #29767、LangChain PR #36949(已合并) | **业界对中段沉默的主流应对是"看门狗→超时→抛错/重试",不是并发副本**;关键细节: 以**解析后的内容 chunk 间隔**计时,SSE keepalive 注释不重置计时器 | `stream_chunk_timeout`(LangChain, 默认 120s) / `LITELLM_STREAM_INACTIVITY_TIMEOUT_SECONDS`(LiteLLM 提案,默认关);首 chunk 不受看门狗约束(TTFT 由请求超时管) | | 沉默检测→整路重试 | OpenAI Codex CLI issue #33051 | Responses 流静默 300s 后判定 `stream disconnected - retrying sampling request (1/5)`,是"中段沉默触发重试"的真实生产先例 | 沉默超阈值 → 归为 Transient → 走既有重试/换源链,而非保活等待 | | serving 层 ITL 劣化成因 | vLLM disagg prefill 文档、vLLM PR #33743、SGLang #32549/#29879 | 中段卡顿的服务端根因: prefill 与 decode 同卡互踩、chunked prefill 调度饿死 decode(实测 decode 被压到 ~1 batch/24s) | 中段沉默是**结构性现象**而非异常个例,网关层必须有对策;观测指标参考 `sglang:decode_step_latency_seconds` | ### 主题 B: "净生成时间"(扣除沉默段)观测口径范式 | 范式名称 | 出处 | 与我们问题的对应关系 | 可直接借鉴的做法 | | --- | --- | --- | --- | | TTFT / ITL(=TPOT) / E2EL 标准定义 | NVIDIA NIM/AIPerf 指标文档、NVIDIA 技术博客 | E2EL = TTFT + Generation_time(首 token→末 token,剔除 `[DONE]`);ITL = (E2EL−TTFT)/(输出 token 数−1);**排队、prefill、网络全部计入,无任何沉默扣除** | 口径分层: 排队/prefill 归 TTFT,纯解码段单独计量;工具间定义不一致(TTFT 是否计入 ITL 均值)必须显式声明 | | TPOT vs ITL 聚合权重 | Anyscale 指标文档 | 单请求 TPOT = ITL 均值;多请求聚合时 avg TPOT 按请求等权、avg ITL 按 token 加权 | 库内聚合统计要声明权重口径,否则跨实现不可比 | | vLLM 阶段化计时 | vLLM metrics 设计文档 + per-request metrics 特性 | `request_queue_time / prefill_time / decode_time` 三段直方图;响应体 `metrics` 对象给 `generation_time_ms`(= decode time,排除排队与 prefill/TTFT) | **generation_time_ms 命名先例**(注意: vLLM 的 decode time 仍含引擎侧中段停顿,沉默扣除在服务端的口径也没人做) | | off-CPU / wall-time 分解 | Brendan Gregg off-CPU analysis | 墙钟 = on-CPU + off-CPU(阻塞);方法论要求给阻塞段**归因**(stack/原因),而非只减总量 | 沉默段扣除必须同时记录沉默总量与归属(哪段、多长、几次),否则下游无法审计 | | 沉默扣除实战先例 | pi-tps(pi 编辑器扩展) | **唯一直接对应"从生成速率里扣除沉默段"的实现**: gap ≥500ms 判为 stall,累计 stallMs/stallCount,从 streaming window 中扣除后算 TPS;连续停顿算一次事件;burst 交付/样本不足时输出 `—` 不硬算 | 阈值式扣除(减所有超阈值的段,而非只减最大一段);三分支防御(主口径/保守兜底/不可辨识时返回 null) | | OpenRouter 供应商口径 | OpenRouter 路由/延迟文档 | 供应商对比口径: latency = TTFT(含网络+排队+prefill),throughput = 解码 TPS;Total = TTFT + tokens/TPS | "延迟=TTFT、速率=解码段"的二元口径是用户侧最广泛认知的形态 | ## 一、主题 A 详细发现 ### A1. Tail at Scale 原始定义与流式后续工作 - 原始定义: "客户端先向其认为最合适的副本发送请求,短暂延迟后再发第二路;首个结果返回后取消其余在途请求"。额外负载可控: 等 95 分位预期延迟再发对冲,只惠及少量请求;更激进的对冲需要更快的取消(跨服务器取消的 tied requests)。([CACM 原文](https://cacm.acm.org/research/the-tail-at-scale/)、[作者 PDF](https://barroso.org/publications/TheTailAtScale.pdf)) - 论文场景是搜索 leaf 扇出的一元响应(各 leaf 返回 top-k 部分结果给聚合器),**"partial"在文中指分片结果,不是增量 token 流**;全文无 streaming/incremental 响应的对冲讨论。([semicolony 批注版](https://semicolony.dev/papers/tail-at-scale)) - 后续工作的共识补充: 高负载下对冲会级联放大(每个慢请求都对冲→负载↑→更慢),生产系统需要熔断/自适应阈值等反馈回路。([semicolony 批注版·批评节](https://semicolony.dev/papers/tail-at-scale)) - LLM 语境的适配文(llms.blog): 把对冲裁定前移到 TTFT 竞速,主张"首 token 一旦产出,prefill 已完成、decode 高度确定,卡顿几乎只发生在排队/prefill"。**此断言与我们实测不符**(首 token 中位 1.6s、中段沉默中位 55s),且被 serving 层证据推翻(见 A4),引用时须警惕。([llms.blog](https://www.llms.blog/posts/request-hedging-in-production-llm-serving-architecture-tail-latency-mitigation-and-cancellation-protocols)) - bhope/hedge(Go 库): 按 host 用 DDSketch 学延迟分布,主路超 p90 估计即发备路,token bucket 压对冲率(防故障期放大),p99 从 64ms→17ms(~74%),开销 ~9%;**明确支持流式负载(按 TTFT 而非响应头计量)**。([GitHub](https://github.com/bhope/hedge)、[InfoQ 解读](https://www.infoq.com/articles/adaptive-hedged-requests-p99-latency/)) ### A2. 基础设施层: Envoy / gRPC / NGINX - **Envoy**: 启用 hedge policy 后"竞速多个并发上游请求,把**首个带可接受响应头的响应**返回下游;由 retry policy 判定响应是否可接受"。当前对冲**只能由 per-try timeout 触发**(`hedge_on_per_try_timeout`: per-try 超时不取消原请求,直接补发)。([HTTP routing 文档](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/http/http_routing.html)、[HedgePolicy API](https://javadoc.io/static/io.envoyproxy.controlplane/api/1.0.37/io/envoyproxy/envoy/config/route/v3/HedgePolicy.html)、[PR #6228](https://github.com/envoyproxy/envoy/pull/6228)) - **Envoy 对"响应已开始但中途停顿"**: per_try_timeout "只在响应开始前有效"(PR #10082 明确允许全局 route timeout 禁用时配 per_try_timeout 给流式端点用)——即**判赢发生在响应头,中段停顿不会触发新的对冲**;流式响应的停顿只能靠 stream idle timeout 兜底(route timeout 默认 15s 与流式不兼容,须禁用改用 idle_timeout)。([PR #10082](https://github.com/envoyproxy/envoy/pull/10082)、[超时 FAQ](https://www.envoyproxy.io/docs/envoy/latest/faq/configuration/timeouts.html)) - **gRPC(A6)**: `hedgingPolicy = {maxAttempts, hedgingDelay, nonFatalStatusCodes}`;原请求立即发,每过 hedgingDelay 未收到非错误响应就补发一路;收到 nonFatal 状态码立即补发下一路;任一请求收到非错误响应→取消全部其余并返回该响应;fatal 状态码→取消全部并报错;server pushback(`grpc-retry-pushback-ms`,负值=禁止再发)可推迟/终止对冲;`retryThrottling{maxTokens, tokenRatio}` 按失败/成功比用 token bucket 压制对冲与重试;maxAttempts 客户端钳到 5;deadline 覆盖整条对冲链。([A6 提案](https://github.com/grpc/proposal/blob/master/A6-client-retries.md)、[官方指南](https://grpc.io/docs/guides/request-hedging/)) - gRPC 对冲对 server-streaming: listener 把 `onNext/onClose` 转发给所有在途对冲 RPC——流式场景赢家仍按"首个非错误响应(头)"裁定,**没有按消息流中段进度重新裁定的语义**。([A6 提案](https://github.com/grpc/proposal/blob/master/A6-client-retries.md)) - **NGINX**: 无对冲原语;`ngx_http_mirror_module` 只做多副本镜像且**忽略镜像响应**(fire-and-forget),不构成 FIRST_COMPLETED 竞速。([NGINX mirror 模块文档](https://nginx.org/en/docs/http/ngx_http_mirror_module.html)) ### A3. LLM 网关/库层: 中段 stall 的真实实现与讨论 | 项目 | 机制 | 关键语义 | 来源 | | --- | --- | --- | --- | | LiteLLM(提案, PR 已 stale 关闭) | `_StreamInactivityGuard` + `LITELLM_STREAM_INACTIVITY_TIMEOUT_SECONDS`(默认关) | 以 `asyncio.wait_for` 约束 chunk 间隔;**首 chunk 不受看门狗约束**(TTFT 归请求 timeout, reasoning 模型首 token 慢);触发抛 `litellm.Timeout`(408) | [PR #29767](https://github.com/BerriAI/litellm/pull/29767) | | LiteLLM(已发) | `ttft_timeout` 配置;`stream_timeout` 修为"整流墙钟上限" | `ttft_timeout`: 无首 token 超时即 `litellm.Timeout`(非流式内部提升为流式实现);httpx read timeout 被 keepalive 字节重置,**对"有字节无内容"的卡顿完全失效** | [config 文档](https://docs.litellm.ai/docs/proxy/config_settings)、[PR #31238](https://github.com/BerriAI/litellm/pull/31238) | | LiteLLM(bug 证据) | 中段超时在 200 已发后抛在生成器内 | 被误记为成功(遥测语义教训);另有反向机制: 上游沉默时主动注入 SSE ping 保活(PR #36024, Anthropic 每 ~30s ping, Claude Code 20s 看门狗) | [Issue #29602](https://github.com/BerriAI/litellm/issues/29602)、[PR #36024](https://github.com/BerriAI/litellm/pull/36024) | | LangChain(已合并, 1.2.0) | `stream_chunk_timeout`(默认 **120s**) + `LANGCHAIN_OPENAI_STREAM_CHUNK_TIMEOUT_S` | 计**解析后内容 chunk 的间隔**, SSE keepalive 不重置;触发抛 `StreamChunkTimeoutError`(TimeoutError 子类,携带 timeout_s/model/chunks_received 结构化字段);配套 TCP keepalive/`TCP_USER_TIMEOUT` socket 选项 | [PR #36949](https://github.com/langchain-ai/langchain/pull/36949)、[API 参考](https://reference.langchain.com/python/langchain-openai/chat_models/base/BaseChatOpenAI/stream_chunk_timeout) | | LangChain JS(诉求) | 用户要"不活跃超时"而非"总时长超时" | 总时长超时误杀长流;间隔计时才是正确语义 | [langchainjs #9088](https://github.com/langchain-ai/langchainjs/issues/9088) | | OpenAI Codex CLI | 流静默检测 + 整路重试 | 静默 300s 判 `stream disconnected`,重试 `retrying sampling request (1/5)`;用户报告 5–10 分钟级卡顿 | [issue #33051](https://github.com/openai/codex/issues/33051) | | Portkey | request_timeout 曾用 `AbortSignal.timeout()` | bug: 流已开始仍被总超时杀掉;修复为收到首个响应即清定时器——"总时长 vs 不活跃"同一教训 | [Issue #471](https://github.com/Portkey-AI/gateway/issues/471)、[streaming 文档](https://portkey-ai-gateway.mintlify.app/features/streaming) | | Bifrost / Helicone | 重试+fallback+自适应 LB | **未见公开的中段沉默触发重试/对冲实现**;Bifrost 生态文章把 "parallel hedged requests" 列为路由模式之一但无流式细节 | [Bifrost retries](https://bifrost.mintlify.app/features/retries-and-fallbacks)、[Helicone retries](https://docs.helicone.ai/features/advanced-usage/retries) | **结论**: 业界对"流中段沉默"的落地形态收敛为 **间隔式不活跃看门狗 → 抛错(Timeout 子类)→ 交给既有重试/换源链**;"中段沉默触发并发副本(hedging)"在任何主流网关/库中**均未找到公开实现**——我们的 FIRST_COMPLETED 异源对冲若覆盖中段沉默,属于超业界现状的能力,但语义上可组合"看门狗 + 已取消输家不可复活"来实现。 ### A4. serving 层: inter-token stall 的观测与成因 - **成因**: 连续批处理引擎把新请求的 prefill(算力密集)与在途请求的 decode(带宽密集)交错;vLLM 官方文档承认未分离时"decode 中插入 prefill 会导致 tail ITL 尖刺",故推 P/D 分离分别调 TTFT 与 ITL。([vLLM disagg prefill](https://docs.vllm.ai/en/v0.12.0/features/disagg_prefill/)) - **实测极端案例**: SGLang 严格 prefill-first 调度下,持续 prompt 负载使 decode 饿死到 **~1 batch/24s**——即中段沉默数十秒是调度器结构性产物,非异常。([SGLang #32549](https://github.com/sgl-project/sglang/issues/32549)) - **观测指标先例**: vLLM `vllm:inter_token_latency_seconds` 直方图(llm-d 运维文档建议用 `histogram_quantile` 盯分位,"ITL 高→输出断断续续");SGLang 新增 `forward_pass_duration_seconds`/`decode_step_latency_seconds`(按 prefill 桶×decode 请求数打标,专测 decode 被干扰)。([vLLM metrics](https://docs.vllm.ai/en/stable/design/metrics/)、[llm-d 观测文档](https://github.com/llm-d/llm-d/blob/3d74f4a6/docs/operations/observability/metrics.md)、[SGLang PR #29879](https://github.com/sgl-project/sglang/pull/29879)) - **缓解手段**: chunked prefill(vLLM 默认预算 2048 tokens/迭代,防长 prompt 冻结在途会话)、inter-prefill-budget(中位 TTFT 降 37%,防 HoL 阻塞)。([LeCompute 调度调优](https://lecompute.fr/en/runtimes/latency-against-latency/)、[vLLM PR #33743](https://github.com/vllm-project/vllm/pull/33743)) ## 二、主题 B 详细发现 ### B1. LLM 推理基准的标准时间口径 - **NVIDIA NIM/AIPerf 官方定义**: TTFT = 提交查询→收到首个 token(忽略空内容的首个响应;含排队+prefill+网络);E2E 请求延迟 = TTFT + Generation_time(首 token→末 token,剔除末尾 `[DONE]`/空响应);ITL =(E2E−TTFT)/(输出 token 数−1),**显式排除 TTFT**,分母减 1 使其只刻画 decode 段。([NVIDIA NIM metrics](https://docs.nvidia.com/nim/benchmarking/llm/latest/metrics.html)) - NVIDIA 技术博客同口径: "TTFT 含请求排队、prefill 与网络延迟";"E2E = TTFT + 生成时间;ITL 为相邻输出 token 间隔均值(不含首 token)"。([NVIDIA 博客](https://developer.nvidia.com/blog/llm-benchmarking-fundamental-concepts/)) - **排队与网络如何处理**: 基准口径**不扣除**——排队计入 TTFT/E2EL,网络计入 TTFT;跨工具可比的前提是定义对齐("Tool implementations vary, so compare results only when definitions align")。([NVIDIA NIM metrics](https://docs.nvidia.com/nim/benchmarking/llm/latest/metrics.html)) - **Anyscale/vLLM 口径**: 单请求 TPOT = ITL 均值 =(E2E−TTFT)/(token 数−1);聚合时 avg TPOT 按请求等权、avg ITL 按 token 加权——两者回答不同问题(单请求体感 vs 系统稳态流率)。([Anyscale metrics](https://docs.anyscale.com/llm/serving/benchmarking/metrics.md)) - **MLPerf Inference**: LLM server 场景收 TTFT + TPOT 两指标,TPOT 定义为"所有生成 token 间的平均间隔";延迟自 LoadGen 调度时刻起算(含排队)。([inference_rules.adoc](https://github.com/mlcommons/inference_policies/blob/master/inference_rules.adoc)、[MLPerf 5.1 说明](https://mlcommons.org/2025/09/small-llm-inference-5-1/)) - **陷阱先例**: vLLM bench serve 的 ITL 在 chunk 与 token 非 1:1 时(reasoning/tool-call parser 缓冲)算错——**chunk 边界 ≠ token 边界**,任何按 chunk 计时的口径都要声明这一点。([vLLM #27485](https://github.com/vllm-project/vllm/issues/27485));另有"ITL 低于 TPOT"的历史 bug(均值口径混淆)。([vLLM #6531](https://github.com/vllm-project/vllm/issues/6531)) ### B2. wall-time vs on-CPU: 扣除外部阻塞时间的标准做法与陷阱 - **off-CPU analysis**(Brendan Gregg): CPU profiling 只看在 CPU 上执行的线程;off-CPU 分析测量线程被阻塞(off-CPU)的时间并带上下文(stack trace),两者互补以解释 100% 线程时间。off-CPU 时间 = 调度器 off-cpu→on-cpu 探针的时间戳差。([Off-CPU Analysis](https://www.brendangregg.com/offcpuanalysis.html)、[offcputime 工具](https://github.com/brendangregg/bpf-perf-tools-book/blob/master/originals/Ch06_CPUs/offcputime.bt)) - **方法论要点**: ①墙钟分解必须成对报告(on-CPU + off-CPU flame graph 同看),只报"净时间"会丢失归因;②阻塞时间若发生在请求的同步路径上,则直接等比影响延迟——即"扣除"是分析手段,不改事实;③归因需要上下文(stack/wakeup stack),单给一个总数无法行动。([Off-CPU Flame Graphs](https://www.brendangregg.com/FlameGraphs/offcpuflamegraphs.html)、[Wakeup/Off-Wake](https://www.brendangregg.com/blog/2016-02-01/linux-wakeup-offwake-profiling.html)) - 映射到我们的场景: `generation_ms`(墙钟)= 活跃生成(on-CPU 类比)+ 中段沉默(off-CPU 类比);Gregg 范式要求的"带归因的分解"对应"沉默总量 + 沉默次数 + 各段位置",而非只给一个净数。 ### B3. 从 token 到达序列扣沉默段的计量学先例与陷阱 - **唯一找到的成体系先例 — pi-tps**: 每个流式更新测量与上一更新的间隔;**gap ≥ 500ms 判为 stall**,整段累计为 `stallMs`、连续停顿算一次 `stallCount`;算 TPS 时把 stall 从 streaming window 中扣除;每个 message 边界重置 stall 时钟(工具调用间隙不误计);三分支防御: 主口径(≥5 次更新、平均间隔 ≥1ms、stall < 活跃时间)、保守兜底(用含 TTFT 的全窗口,宁可低估)、**结构性不可辨识时返回 null 显示 `—`**(burst 交付/样本不足)。([pi-tps 包页](https://pi.dev/packages/@logicouo/pi-tps?page=49)) - **vLLM 服务端口径**: `generation_time_ms` = 首 token→末 token 的 decode 时间,**已排除排队与 prefill/TTFT,但仍包含引擎侧中段停顿**(preempt/调度等待);`mean_itl_ms` 单 token 响应为 null;`n>1` 时整个 metrics 抑制(无法归因)——口径排除条款的范本。([vLLM per-request metrics](https://docs.vllm.ai/en/stable/features/per_request_metrics/)) - **OpenRouter 用户侧口径**: latency = TTFT(网络+排队+prefill),throughput = 解码 TPS;`Total = TTFT + tokens/TPS`——二元口径,不做沉默扣除,但把"等"与"生成"分开呈现是行业用户心智。([OpenRouter 延迟指南](https://openrouter.ai/docs/guides/best-practices/latency-and-performance)、[供应商评估](https://openrouter.ai/blog/insights/evaluate-llm-provider-performance/)) - **三种扣除形态的判定**(综合以上先例): | 形态 | 评价 | | --- | --- | | 只减最大一段 | 无任何先例;多段沉默场景系统性高估活跃时间,不推荐 | | 减所有超阈值段(pi-tps 式) | 有唯一直接先例;阈值外小停顿计入活跃时间,偏差有界且方向保守,**推荐** | | 减所有段(纯 token 间隔求和) | 等价于重定义 ITL;chunk≠token 时不可算(vLLM #27485),放弃 | - **尾部归属**: AIPerf 显式剔除末尾 `[DONE]`/空响应(E2E 止于末 token);pi-tps 的 stream span 止于最后一次 message_update;**最后一个 token 的间隔属于生成段内,流终止信号之后的时间一律不计**。vLLM 单 token 响应 `mean_itl_ms = null` 提示: token 数 <2 时一切间隔类口径应输出 null 而非 0。 ## 三、对库设计的具体建议 ### 3.1 中段对冲的赢家裁定语义 1. **裁定事件分两级**: 现行"首 token 竞速"保留(命名对齐业界: TTFT race);新增**中段沉默竞速**——主路沉默超过 `stall_after_s` 触发异源副路,副路同样按"首个内容 chunk"裁定。 2. **commit 点**: 借鉴 Envoy/gRPC"首个可接受响应即 commit":一旦某一路的**首个内容 chunk 已向下游交付**,该路成为 committed winner,其余路取消、不得复活——chat completion 的增量内容**不可拼接**(两路文本分叉,缝合即结果污染),这是 Env名"判赢不回头"语义在 LLM 场景的必然推论。 3. **commit 前摇摆**: 若副路先出 chunk 而主路恰好恢复,以**先交付者**为准(FIRST_DELIVERED),不做内容比车。 4. **committed winner 再沉默**: 不再对冲(无副本可换语义),走看门狗 → `stall_timeout` 抛错 → 错误分类 Transient → 交既有重试/换源链(Codex CLI 先例);已在下游的增量内容如实保留并标记截断。 5. **成本与放大控制**: 借鉴 gRPC `retryThrottling`(token bucket 压对冲率)与 Tail at Scale 级联警告: 对冲预算独立计量,故障期自动收敛;输家 est 不喂熔断的现行设计保持。 ### 3.2 沉默扣除口径的推荐形态 1. **记录原语**: 传输层记录每个内容 chunk 的单调时间戳序列(空 chunk/SSE keepalive 不计,LangChain 语义),遥测侧由时间戳派生,不在热路径做判定。 2. **派生字段**(CallStats 只增不删): | 字段 | 口径 | | --- | --- | | `generation_ms`(已有) | 首内容 chunk→末内容 chunk 墙钟(vLLM `generation_time_ms` 同口径) | | `stall_ms` | Σ 所有 > `stall_threshold_ms` 的相邻 chunk 间隔(pi-tps 式,非只减最大段) | | `stall_count` | 超阈值间隔个数(连续停顿合并为一次) | | `active_generation_ms` | `generation_ms − stall_ms`,**派生值、永不覆盖墙钟原值**(Gregg 分解原则) | 3. **阈值形态**: 固定默认值(pi-tps 用 500ms,Anthropic ping 间隔 ~30s 提示阈值需 ≫ keepalive 周期,建议默认 2–5s 量级、可配),不做自适应分位阈值(避免与对冲阈值耦合出反馈环)。 4. **排除条款照抄 vLLM**: chunk 数 <2 时间隔类字段为 null;非流式调用三个新字段全 null;对冲输家的沉默数据保留在输路记录但不进赢家 CallStats。 5. **对冲与沉默的关系**: `stall_after_s`(对冲触发)与 `stall_threshold_ms`(计量口径)是**两个独立参数**——前者触发动作、后者只做观测,命名与语义都必须分开。 ### 3.3 配置命名先例对照 | 语义 | 业界先例 | 建议沿用风格 | | --- | --- | --- | | 首 token 超时 | LiteLLM `ttft_timeout`、`stream_timeout` | `HEDGE__AFTER_S` 已符合;看门狗类用 `STREAM__TTFT_TIMEOUT_S` | | 中段不活跃看门狗 | LangChain `stream_chunk_timeout`(默认 120s)、LiteLLM 提案 `...STREAM_INACTIVITY_TIMEOUT_SECONDS` | `STREAM__STALL_TIMEOUT_S` | | 中段对冲触发阈值 | 无直接先例(gRPC `hedgingDelay` 是按响应头的节拍) | `HEDGE__STALL_AFTER_S` | | 沉默计量阈值 | pi-tps 固定 500ms(代码常量) | `STREAM__STALL_THRESHOLD_MS` | | 对冲并发上限 | gRPC `maxAttempts`(钳 5) | 现有单副路设计已等价 maxAttempts=2 | ## 四、来源清单 **保留(关键证据)** - [The Tail at Scale (CACM 2013)](https://cacm.acm.org/research/the-tail-at-scale/) / [作者 PDF](https://barroso.org/publications/TheTailAtScale.pdf) — 对冲原始定义 - [semicolony 批注版 Tail at Scale](https://semicolony.dev/papers/tail-at-scale) — 论文未覆盖流式响应、级联放大批评 - [Envoy HTTP routing 文档](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/http/http_routing.html) / [超时 FAQ](https://www.envoyproxy.io/docs/envoy/latest/faq/configuration/timeouts.html) / [PR #10082](https://github.com/envoyproxy/envoy/pull/10082) — 判赢在响应头、流式停顿不重新对冲 - [gRPC A6 提案](https://github.com/grpc/proposal/blob/master/A6-client-retries.md) / [官方 hedging 指南](https://grpc.io/docs/guides/request-hedging/) — hedgingDelay/非致命码/pushback/throttle 语义 - [LiteLLM PR #29767](https://github.com/BerriAI/litellm/pull/29767) / [config_settings](https://docs.litellm.ai/docs/proxy/config_settings) / [Issue #29602](https://github.com/BerriAI/litellm/issues/29602) / [PR #36024](https://github.com/BerriAI/litellm/pull/36024) — 中段看门狗形态、keepalive 重置 read timeout 失效、遥测误记教训 - [LangChain PR #36949](https://github.com/langchain-ai/langchain/pull/36949) / [stream_chunk_timeout 参考](https://reference.langchain.com/python/langchain-openai/chat_models/base/BaseChatOpenAI/stream_chunk_timeout) — 已合并的内容 chunk 间隔看门狗 - [OpenAI Codex issue #33051](https://github.com/openai/codex/issues/33051) — 沉默检测→整路重试生产先例 - [Portkey issue #471](https://github.com/Portkey-AI/gateway/issues/471) — 总时长超时误杀流式的反面教材 - [bhope/hedge](https://github.com/bhope/hedge) / [InfoQ 自适应对冲](https://www.infoq.com/articles/adaptive-hedged-requests-p99-latency/) — DDSketch 动态阈值 + token bucket 压放大率 - [llms.blog 生产 LLM 对冲](https://www.llms.blog/posts/request-hedging-in-production-llm-serving-architecture-tail-latency-mitigation-and-cancellation-protocols) — TTFT 竞速架构(其"卡顿只在 prefill"断言须警惕) - [vLLM disagg prefill](https://docs.vllm.ai/en/v0.12.0/features/disagg_prefill/) / [SGLang #32549](https://github.com/sgl-project/sglang/issues/32549) / [SGLang PR #29879](https://github.com/sgl-project/sglang/pull/29879) / [llm-d 观测文档](https://github.com/llm-d/llm-d/blob/3d74f4a6/docs/operations/observability/metrics.md) — 中段卡顿的 serving 层成因与指标 - [NVIDIA NIM metrics](https://docs.nvidia.com/nim/benchmarking/llm/latest/metrics.html) / [NVIDIA 博客](https://developer.nvidia.com/blog/llm-benchmarking-fundamental-concepts/) — TTFT/ITL/E2EL 官方口径 - [Anyscale metrics](https://docs.anyscale.com/llm/serving/benchmarking/metrics.md) — TPOT/ITL 聚合权重 - [MLPerf inference_rules](https://github.com/mlcommons/inference_policies/blob/master/inference_rules.adoc) — 基准延迟自调度时刻起算 - [vLLM per-request metrics](https://docs.vllm.ai/en/stable/features/per_request_metrics/) / [metrics 设计](https://docs.vllm.ai/en/stable/design/metrics/) — generation_time_ms 命名与排除条款范本 - [vLLM #27485](https://github.com/vllm-project/vllm/issues/27485) — chunk≠token 口径陷阱 - [Brendan Gregg Off-CPU Analysis](https://www.brendangregg.com/offcpuanalysis.html) / [Off-CPU Flame Graphs](https://www.brendangregg.com/FlameGraphs/offcpuflamegraphs.html) — 墙钟分解与归因原则 - [pi-tps](https://pi.dev/packages/@logicouo/pi-tps?page=49) — 沉默扣除唯一直接先例(500ms 阈值、stallMs/stallCount、三分支防御) - [OpenRouter 延迟指南](https://openrouter.ai/docs/guides/best-practices/latency-and-performance) — 用户侧 TTFT/TPS 二元口径 **弃用(及原因)** - learnaivisually、hivenet、clickhouse、themainthread 等科普稿 — 定义与 NVIDIA/Anyscale 一手文档重复且更浅 - macnica(代理商博客)、AWS Neuron/营销页、acecloud、saraswatmks 博客 — 营销/转载性质 - Portkey/Helicone/Bifrost 营销文档 — 只证实"无中段沉默对冲实现",不承载技术语义 - Solana/Rust hedged RPC crates — 与 LLM 流式无关的一元 RPC 竞速 - eunomia eBPF 教程 — Gregg 一手文已覆盖 ## 五、未能确认的事项 1. **"中段沉默触发并发副本"是否有任何已投产实现**——未找到;不排除闭源网关(如各云厂 AI 网关)内部存在。建议以"看门狗→重试"为基线交付,把中段对冲作为可选增强。 2. pi-tps 的 500ms 阈值是否经生产校准(单个开源扩展,样本未知);我们的阈值应基于自有遥测分布定,不照抄数值。 3. Tail at Scale 之后是否有**学术**流式对冲后续工作(本次检索未见;如需可再查 IEEE/ACM 数据库)。