先看一个假设场景,下面的数字用于说明风险,并非真实事故或 Kimi 实测:一个团队把 70% 的线上请求切到了高速档。P95 延迟从 6 秒掉到 3 秒,大盘上看不出任何问题。两周之后客服开始收到反馈:回答变短,像是没想清楚就说了。他们回去查质量指标,指标没掉——因为质量评测用的是固定探针集,探针请求只走稳档,从来没进过快档的链路。他们测到的延迟和实际服务的质量,来自两条不同的链路。
这里的错误不是「切快了」,而是把一个关于服务配置的假设当成了关于模型质量的结论。同一个模型名挂两个速度档,很容易让人默认「权重一样,质量一样」。这里连“权重一样”也需要证据;同一产品系列不能替代对具体版本、能力和请求配置的核对。
本文只处理一个问题:给定一批线上请求,怎么判断哪些该走快档、哪些必须留在稳档。不处理 API 怎么调、不做厂商横评,也不预测快档在某个具体业务上会掉多少分——那个数字只能自己测。下文以 fast 指高速版,以 stable 指普通版作为基线;stable 只是本文变量名,不表示官方承诺普通版更加稳定。
截至 2026 年 10 月 26 日,官方文档列出的两个模型 ID 分别为 kimi-k3.1-pro 与 kimi-k3.1-pro-highspeed。官方将 Highspeed 描述为同等智能、输出速度约为普通版的 2~3 倍。 因此本文的评测目标,是验证这项产品承诺在自己的任务和负载下能否成立,而不是预设高速版会降低质量。官方模型说明
还有一个必须单独控制的变量:reasoning_effort。官方支持 low、high、max,默认 max,并且 K3.1 Pro 始终开启思考。比较两个服务版本时应固定这个值,同时固定输出上限、提示词、工具权限和会话历史。拿“高速版 + low”和“普通版 + max”比较,得到的是组合配置差异,不能归因于 Highspeed 本身。官方推理强度说明

图1:评测只改变服务版本,其余影响答案的配置保持一致。图中不推断 Kimi 未公开的服务端实现。
先划边界:本文讨论可完成同一业务任务的两种服务配置。评测本身不要求用户能证明后台权重逐字节相同,但必须核对版本、上下文上限和工具能力,并记录所有已知差异。能力不一致时,仍可比较最终任务成效,只是不能把差异全部解释为“速度档”带来的影响。
1. 先区分服务加速与减少计算,再谈机制归因
“快”可能来自更短的排队、更快的硬件、缓存命中、推理内核优化、批处理策略,也可能来自用户主动降低推理强度或输出上限。这些路径的业务含义不同:有些主要节省等待,有些改变回答能够使用的计算预算。评测时要先排除自己引入的配置差异,再讨论服务版本。
不能把投机解码直接归为“砍思考预算”。 标准精确投机解码通过目标模型验证和分布校正,可以在保持目标采样分布的同时加速;调整草稿长度本身不证明质量下降。只有具体实现改变了目标分布、停止条件或预算时,才需要分析相应影响。原始论文
对 Kimi Highspeed,本文没有掌握足以确认其内部加速路径的公开证据。以下信号用于定位值得排查的差异,不用来反推厂商一定采用了某种实现。
输出长度分布。 如果高速版中位输出长度明显更短,应检查输出上限、停止原因、提示词和答案完整性。短答案可能完整,也可能截断;仅凭长度不能判定少思考,更不能判定质量更低。解释型回答看必要论据是否齐全,抽取任务则看字段覆盖与值是否正确。
推理用量与结束原因。 API 能返回推理相关用量时,同时记录总输出、推理用量及 finish_reason,并核对计费字段是否存在包含关系,避免重复相加。若没有相关字段,就明确标注不可观测,不能把最终可见答案长度当作内部推理 token 的替代证据。
负载分层。 在低、中、高并发分别测量 TTFT、完成时间和错误率。优先队列的优势可能在拥堵时扩大,也可能因独立配额、限流或容量争用缩小,没有统一方向。需要用曲线判断容量行为,不能从一次低负载结果外推上线后的收益。
任务难度与成功率。 短问答、多步推理、长文归纳可以分层,但难题掉分只能证明这些任务需要进一步调查。还要排除采样波动、截断和工具失败,才能讨论计算预算是否可能参与其中。对不可观测的机制,结论停在“存在相关现象”,不要写成确定因果。
记录实验配置时,至少保存模型 ID、运行时间、reasoning_effort、输出上限、输入与历史消息摘要、工具版本、并发和缓存状态。业务请求含个人信息时,先脱敏并控制回放权限;工具有发送、支付、删除等副作用时,影子调用必须使用沙箱或只读替身,不能让同一个操作执行两次。
厂商提供的输出速度倍数也不等于端到端任务提速倍数。假设普通版首 token 等待 4 秒、后续输出 6 秒,即便后续输出缩短为 2 秒,完成时间也是从 10 秒降到 6 秒,约为 1.67 倍;这只是计算示例。必须注明比较的是首 token、持续输出速率,还是包含工具调用的完整任务。
2. 为什么不能靠一次对比、也不能靠全局平均
第二个常见错误是拿两个档位各跑一遍固定评测集,看总分差多少,差得不多就上线。这个做法在三处会失效。
采样噪声会吃掉小差异。 同一个提示跑两次,结果本来就可能不同。如果两档各只跑一次,你看到的「差 3 分」很可能只是采样波动。要判断一个差值是不是真的存在,需要重复采样和区间估计,而不是点估计相减。
平均分会稀释掉结构性差异。 快档可能在 80% 的简单请求上完全无损,在 20% 的复杂请求上掉 15 个百分点。加权平均下来只差 3 分,看上去可以接受,但那 20% 恰好是最贵的用户、最长的会话、最需要准确率的场景。用平均值做决策,等于让简单请求替复杂请求背书。
配对关系会丢失。 如果快档和稳档跑的不是同一批请求,你比较的是两个不同样本的均值,方差更大,而且无法控制请求难度这个混杂因素。同一个请求走两档,差值才有明确的物理含义:这一个请求,因为换了档位,质量变了多少、时间省了多少。
配对设计还有一个隐藏前提:同一档位内部的多次运行要取平均或中位数,而不是取最好的一次。取最好的一次等于在估计「上限」而不是「典型表现」,会系统性地高估两个档位,并且会让方差较大的一方更容易因偶然的高分获益;哪一档方差更大需要实测。
指标选择上也要小心。生成类任务不适合用精确匹配或字面相似度当质量分,因为同一个意思可以有多种正确写法。更稳的做法是给每个请求类别配一套可判定的 rubric 或者断言:结构化抽取看字段是否齐全且值正确,事实问答看关键实体是否命中,代码任务看单元测试是否通过。如果只能用 LLM 打分,至少要做两件事:同一对答案交换顺序各评一次,分歧样本进入复核并保留在总样本中;以及在正式测量前用人工标注的一小批样本校准评委,确认它没有系统性偏袒「更长」或「更简洁」的答案。
最后一点:质量差的容差不是统计量,是业务量。统计只回答「这个差值是不是噪声」,业务回答「这个差值能不能接受」。客服问答也许能容忍 1 个百分点的下降,合同条款抽取可能一点都不能让。所以容差必须按请求类别分别设定,而且要在看数据之前定好,不能等看到结果再回头调门槛。
3. 测量设计:每类请求一条质量-延迟散点
把上面的要求落成流程,大概是这个样子:

图2:复制请求用于离线回放或影子评测;真正返回给用户的答案仍由线上策略决定。
这张图里有两个约束值得单独说。
配对只在回放或影子阶段成立。 线上不可能把同一个用户请求真的执行两遍再返回两份答案。所以配对数据的来源只有两种:拿历史请求做离线回放,或者在影子模式下让 fast 档跑一遍但结果不返回给用户、只记录。前者可控但可能和当前流量分布有偏差,后者更真实但需要额外的推理成本——这笔成本要算进测量预算,不能假装它不存在。
顺序会污染延迟。 如果两档共享前缀缓存或语义缓存,先跑的那一份会把缓存焐热,后跑的那一份白捡便宜。所以要么给两档分配独立的缓存命名空间,要么随机化执行顺序,并且样本量足够大让两种顺序大致均衡。否则你测出来的 ΔT 里混着一个和档位无关的缓存效应。
样本量应按业务容差、配对差方差和需要识别的最小差异规划。可以先以每类 30 个不同请求、每请求每档 3 次做试测起点,但这不能保证统计功效,尤其不能证明低频严重错误足够安全。重复运行用于估计请求内波动,重采样单位仍是不同请求;若同一会话内请求高度相关,应按会话或用户做聚类重采样。类别划分不要按「模型能理解的自然语言主题」分,要按「工程上会影响决策的维度」分:输入长度区间、是否需要多步推理、输出是否有严格结构要求、用户是否在同步等待。这四个维度里任意一个不同,质量-延迟曲线都可能不同。
测完之后,每个类别在平面上是一个点:横轴是 ΔT(稳档耗时减快档耗时,正数表示快档省时),纵轴是 ΔQ(快档质量减稳档质量,负数表示快档掉分)。点要带误差棒。四个象限的含义很直接:
右上(快且不降质):质量和延迟的区间过线后,再检查成本与失败率。
右下(快但掉质):看掉多少,和容差比。
左上(更慢但质量较高):可能是真实结果,也可能受负载、缓存和噪声影响;先查区间与环境,不能直接断言测错。
左下(更慢又掉质):当前配置下保留普通版;环境或版本变化后可以重新评估。
点落在哪个象限只是初步描述,真正的门槛要看整条置信区间。本文固定 ΔQ = Q_fast − Q_stable,因此允许最多损失 tol 时,应要求 dq_lo ≥ −tol;再要求 dt_lo > 0。例如均值为 −0.01、区间为 [−0.20, 0.18] 时,不能因为上界大于 −0.02 就放行:数据还允许存在远超容差的损失。

图3:构造数据,容差统一为 2 个百分点。A 的质量区间过线,B 仍不确定,C 显示明显损失;A 也仍需通过延迟和成本门槛。
4. 配对测量与置信区间:把「差多少分」变成可判定的数
下面这段代码只做一件事:把配对样本压成每个类别的 ΔQ 和 ΔT,并给出 bootstrap 区间。它不负责调用模型,也不负责质量打分——那两部分和具体业务强耦合,硬塞进示例只会变成不可复用的样板。
from __future__ import annotations
import random
from math import isfinite
from dataclasses import dataclass
from statistics import mean, median
@dataclass(frozen=True)
class Sample:
request_id: str
request_class: str
tier: str # "fast" | "stable"
run_idx: int
quality: float # 0.0 ~ 1.0,任务级 rubric 或断言产出
ttft_ms: float
total_ms: float
out_tokens: int
fallback: bool = False
@dataclass(frozen=True)
class Pair:
request_id: str
request_class: str
q_fast: float
q_stable: float
ms_fast: float
ms_stable: float
fast_shortened: bool # 快档输出是否显著短于稳档
def pair_by_request(samples: list[Sample]) -> tuple[list[Pair], dict[str, int]]:
"""同一 request_id 的两档样本配对;同一档多次运行取均值/中位数。"""
buckets: dict[tuple[str, str], list[Sample]] = {}
seen: set[tuple[str, str, int]] = set()
classes: dict[str, str] = {}
for s in samples:
key = (s.request_id, s.tier, s.run_idx)
if not s.request_id or not s.request_class or s.tier not in {"fast", "stable"}:
raise ValueError("请求标识、类别或档位无效")
if key in seen or (s.request_id in classes and classes[s.request_id] != s.request_class):
raise ValueError("重复运行编号或请求类别不一致")
if not all(isfinite(v) for v in (s.quality, s.ttft_ms, s.total_ms, s.out_tokens)):
raise ValueError("测量值必须有限")
if not 0 <= s.quality <= 1 or not 0 <= s.ttft_ms <= s.total_ms or s.out_tokens < 0:
raise ValueError("质量、时间或 token 数超出范围")
seen.add(key)
classes[s.request_id] = s.request_class
buckets.setdefault((s.request_id, s.tier), []).append(s)
pairs: list[Pair] = []
missing = {"fast": 0, "stable": 0}
request_ids = {rid for rid, _ in buckets}
for rid in sorted(request_ids):
fast = buckets.get((rid, "fast"), [])
stable = buckets.get((rid, "stable"), [])
if not fast:
missing["fast"] += 1
continue
if not stable:
missing["stable"] += 1
continue
cls = fast[0].request_class
tok_fast = median(s.out_tokens for s in fast)
tok_stable = median(s.out_tokens for s in stable)
pairs.append(Pair(
request_id=rid,
request_class=cls,
q_fast=mean(s.quality for s in fast),
q_stable=mean(s.quality for s in stable),
ms_fast=median(s.total_ms for s in fast),
ms_stable=median(s.total_ms for s in stable),
fast_shortened=tok_fast < 0.9 * tok_stable,
))
return pairs, missing
def bootstrap_ci(values: list[float], n_boot: int = 2000, alpha: float = 0.05,
seed: int = 20261019) -> tuple[float, float, float]:
"""请求级 percentile bootstrap;极小样本返回无信息区间。"""
if not values or not all(isfinite(v) for v in values):
raise ValueError("需要非空、有限的配对差")
if not isinstance(n_boot, int) or n_boot < 100 or not 0 < alpha < 1:
raise ValueError("n_boot 至少 100,alpha 必须在 (0, 1) 内")
if len(values) < 5:
return mean(values), float("-inf"), float("inf")
rng = random.Random(seed)
n = len(values)
boots: list[float] = []
for _ in range(n_boot):
resampled = [values[rng.randrange(n)] for _ in range(n)]
boots.append(mean(resampled))
boots.sort()
lo = boots[int(n_boot * alpha / 2)]
hi = boots[int(n_boot * (1 - alpha / 2)) - 1]
return mean(values), lo, hi
def by_class(pairs: list[Pair], missing: dict[str, int]) -> dict[str, dict[str, float]]:
"""按请求类别统计;缺失配对时停止,避免只评估幸存样本。"""
if any(missing.values()) or not pairs:
raise ValueError("配对记录不完整或没有请求,不发布路由")
groups: dict[str, list[Pair]] = {}
for p in pairs:
groups.setdefault(p.request_class, []).append(p)
out: dict[str, dict[str, float]] = {}
for cls, ps in groups.items():
dq = [p.q_fast - p.q_stable for p in ps]
dt_ms = [p.ms_stable - p.ms_fast for p in ps] # 正数表示快档省时
q_mean, q_lo, q_hi = bootstrap_ci(dq)
t_mean, t_lo, t_hi = bootstrap_ci(dt_ms)
out[cls] = {
"n": float(len(ps)),
"dq": q_mean, "dq_lo": q_lo, "dq_hi": q_hi,
"dt_ms": t_mean, "dt_lo": t_lo, "dt_hi": t_hi,
"shortened_ratio": sum(p.fast_shortened for p in ps) / len(ps),
}
return out
def demo() -> None:
# 构造数据,不是实测结果:A 类质量几乎无损且明显更快,B 类快档掉分且输出被截短
rng = random.Random(11)
samples: list[Sample] = []
for i in range(40):
rid = f"a-{i}"
for tier, base_ms, base_q in (("fast", 900, 0.94), ("stable", 1800, 0.95)):
for run in range(3):
samples.append(Sample(
request_id=rid, request_class="short_qa", tier=tier, run_idx=run,
quality=min(1.0, max(0.0, base_q + rng.gauss(0, 0.02))),
ttft_ms=base_ms * 0.2, total_ms=base_ms + rng.gauss(0, 80),
out_tokens=180,
))
for i in range(40):
rid = f"b-{i}"
for tier, base_ms, base_q, tok in (
("fast", 4200, 0.71, 300), ("stable", 5200, 0.86, 620),
):
for run in range(3):
samples.append(Sample(
request_id=rid, request_class="multi_hop", tier=tier, run_idx=run,
quality=min(1.0, max(0.0, base_q + rng.gauss(0, 0.06))),
ttft_ms=base_ms * 0.2, total_ms=base_ms + rng.gauss(0, 400),
out_tokens=tok,
))
pairs, missing = pair_by_request(samples)
assert missing == {"fast": 0, "stable": 0}, missing
stats = by_class(pairs, missing)
assert stats["short_qa"]["dq_lo"] >= -0.02, stats["short_qa"]
assert stats["short_qa"]["dt_ms"] > 500, stats["short_qa"]
assert stats["multi_hop"]["dq_hi"] < 0, stats["multi_hop"]
assert stats["multi_hop"]["shortened_ratio"] > 0.5, stats["multi_hop"]
for cls, s in stats.items():
print(cls, {k: round(v, 3) for k, v in s.items()})
if __name__ == "__main__":
demo()
先核对原始回放清单中的预期请求与运行次数。missing 只能发现至少留下一档记录的请求,两档都没有日志的请求仍需清单对账才能发现;示例让已知配对缺失时停止统计,避免把故障请求从分母里丢掉。真实超时、截断应落成带结束原因、耗时和任务评分的记录,而非直接删除。
这里有三处设计决定值得解释。
这里用 percentile bootstrap 展示区间的计算。 质量有界、延迟可能长尾,重采样便于直接估计配对差均值的不确定性,但不表示 t 方法一定不能用,也不表示 bootstrap 没有前提。它依赖样本有代表性以及重采样单位适当;小样本、退化分布和极少见失败可能得到误导区间。SciPy 还提供 basic 与 BCa 方法,正式分析应检查覆盖表现并做敏感性分析。区间宽度直接告诉你样本够不够:如果 dt_lo 和 dt_hi 跨过 0,说明延迟收益在这个样本量下还不可信,不该拿去做路由决策。
先对同一请求的重复耗时取中位数,再对不同请求的配对差取均值。 这才是代码实际估计的量,它不是总体 P50,也不是 P95 的差。重复运行中位数可减少偶发抖动,但也会弱化偶发失败的影响,因此超时、失败率、原始耗时均值以及 P95/P99 仍要另报;容量和成本评估尤其不能丢掉长尾。
单独统计 shortened_ratio。 这个比例只是长度诊断信号。如果它接近 1,应进一步检查结束原因、任务完整性和输出上限。可见输出短不能证明推理预算减少;完整的简洁答案也不应扣分。fallback 字段在本段只保留日志信息,没有实现故障策略;测原始版本差异时应禁止自动回退,测上线策略时则要按原始任务汇总所有尝试与最终结果,不能把两种统计单位混用。
5. 延迟收益怎么折算成钱:等待成本不是全局常数
拿到 ΔT 之后,很多人会直接乘一个统一的「时间价值」系数。这个做法在跨类别聚合时几乎必然出错,因为等待的成本取决于谁在等、等在哪一步。
影响等待成本的至少有四个变量。
同步还是异步。 用户在对话框里等 8 秒和在后台批处理里等 8 秒,成本可能相差很大,具体幅度需要业务数据。批处理任务里延迟几乎不值钱,值钱的是单位时间吞吐;交互式问答里延迟直接对应流失。
等的是首 token 还是全部完成。 流式输出下,用户对首 token 的敏感度远高于尾 token。如果快档的优势全部集中在尾段(比如解码更快但预填充没变),用户感知到的改善会远小于 ΔT 暗示的那么多。反过来,如果快档主要省在首 token,那它的实际价值被 ΔT 低估了。这也是为什么测量时必须同时记录 ttft_ms 和 total_ms。
等待发生在流程的第几步。 一个 Agent 任务可能有十几次模型调用,只有关键路径上的串行调用提速才直接累加到完成时间。并行调用要看最慢分支,工具等待也可能占据大头。最好在完整任务层面统计耗时变化,不能既把时间收益按步数累加,又把等待成本系数乘一次步数。
超过阈值之后的边际成本。 用户对延迟的容忍不是线性的。对某些产品,3 秒到 4 秒影响较小,而 9 秒到 10 秒可能显著增加放弃率;这些阈值应由本产品数据验证。如果你的请求分布里有相当一部分落在临界区间附近,那 ΔT 的价值要按「跨过阈值的请求比例」加权,而不是按平均秒数折算。
把这些变量收成一个参数:每个请求类别的等待成本 w,单位是元/秒。它的估计方法因业务而异——有明确流失阈值的可以用放弃率对延迟的弹性反推,内部工具可以用人力成本除以任务时长,批处理任务可以近似取 0。关键不是估得多准,而是不同类别用不同的值,并且把这个值写进配置而不是散在代码里。
有了 w,先在同一批原始请求上定义实际策略成本,再计算净收益:
C_tier = 该策略所有尝试的实际账单总额 / 原始请求数
净收益 = w × ΔT(秒) − (C_fast_policy − C_stable_policy)
成本必须分别使用两档自己的实际用量。输入未命中、缓存命中、缓存写入、输出等计费项先按账单规则分别计算,再汇总;推理 token 若已包含在输出中,不得另加一遍。“两档单价之差乘一个共同 token 数”只在两档用量和计费分类完全相同时成立,不能当通用公式。
截至 2026 年 10 月 26 日,官方首页展示普通版输入/输出为每百万 token 13/68 元,高速版为 26/136 元;这些是当日价格快照,缓存等项目另按定价规则处理。下面的演算只考虑未命中输入和输出,不包含缓存写入、工具或其他费用,实际结算以账单为准。官方价格入口、官方首页价格展示
假设一次请求两档均消耗 2000 个未命中输入 token 和 1000 个输出 token,那么这两项费用分别为 0.094 元与 0.188 元,差额为 0.094 元。如果快档在该业务中节省 2 秒,盈亏平衡的等待价值为 0.094 / 2 = 0.047 元/秒。这是用价格快照构造的账单演算,2 秒不是 Kimi 实测结果,也不能推广为所有请求的盈亏点。
回退要按完整策略记账。 高速调用失败后再调用普通版,一个原始请求的费用可能包含两次调用,耗时包含失败等待、退避和后续调用。只有真正计费的失败调用才进入账单,不能机械假定所有失败都收费。质量成功率也必须以原始请求为分母,不能因为最终回退成功就隐藏高速版的失败率。
还可以另报“每成功任务成本”,其分子包含所有尝试,分母为最终成功的原始任务数。但它和上面的每原始请求净收益不是同一口径,不能直接混用。若某策略成功率更低,必须先通过质量护栏;少完成任务带来的低开销不算节省。等待价值不确定时,报告多个 w 的敏感性结果比给出一个看似精确的金额更诚实。
6. 路由表:用置信区间和净收益两类门槛做决定
把质量判据和经济判据合到一起,就得到每个类别的路由决策。这里的实现刻意做得保守:质量差用区间下界判断,延迟用区间下界判断,两个都过线才允许走快档。
from dataclasses import dataclass
from math import isfinite
@dataclass(frozen=True)
class ClassPolicy:
request_class: str
route: str
net_benefit_cny: float
reason: str
def decide_routes(
stats: dict[str, dict[str, float]],
premium_per_request_cny: dict[str, float], # 两套策略实测平均成本之差
waiting_cost_cny_per_s: dict[str, float],
max_quality_loss: dict[str, float],
min_net_benefit: float = 0.0,
) -> list[ClassPolicy]:
if not isfinite(min_net_benefit) or min_net_benefit < 0:
raise ValueError("净收益门槛必须是非负有限值")
policies = []
for cls, s in stats.items():
configs = (premium_per_request_cny, waiting_cost_cny_per_s, max_quality_loss)
if any(cls not in config for config in configs):
policies.append(ClassPolicy(cls, "stable", 0.0, "缺少类别配置"))
continue
premium, w, tol = (config[cls] for config in configs)
values = [s.get(k, float("nan")) for k in
("n", "dq", "dq_lo", "dq_hi", "dt_ms", "dt_lo", "dt_hi")]
if not all(isfinite(v) for v in [*values, premium, w, tol]):
policies.append(ClassPolicy(cls, "stable", 0.0, "数据不完整或非有限"))
continue
if not 0 <= tol <= 1 or w < 0:
raise ValueError("质量容差须在 [0,1] 内,等待成本不得为负")
net = w * s["dt_ms"] / 1000 - premium
# shortcut: 30 只是试测门槛,正式上线须按容差与方差规划样本量。
if s["n"] < 30:
reason = "请求样本不足"
elif s["dq_lo"] > s["dq_hi"] or s["dt_lo"] > s["dt_hi"]:
reason = "区间顺序无效,需复核统计结果"
elif s["dq_lo"] < -tol:
reason = "质量差下界低于负容差"
elif s["dt_lo"] <= 0:
reason = "延迟收益下界不为正"
elif net <= min_net_benefit:
reason = "净收益未达门槛"
else:
reason = "质量、延迟与净收益均过线"
route = "fast" if reason == "质量、延迟与净收益均过线" else "stable"
policies.append(ClassPolicy(cls, route, net, reason))
return policies
def demo_routes() -> None:
# 全部为构造值,费用差不是 Kimi 定价或实测账单。
stats = {
"short_qa": {"n": 40, "dq": -.008, "dq_lo": -.015, "dq_hi": .004,
"dt_ms": 880, "dt_lo": 700, "dt_hi": 1060},
"multi_hop": {"n": 40, "dq": -.148, "dq_lo": -.170, "dq_hi": -.128,
"dt_ms": 920, "dt_lo": 300, "dt_hi": 1540},
}
premium = {"short_qa": .0048, "multi_hop": .012}
waiting = {"short_qa": .01, "multi_hop": .0005}
tolerance = {"short_qa": .02, "multi_hop": .02}
result = {p.request_class: p for p in decide_routes(stats, premium, waiting, tolerance)}
assert result["short_qa"].route == "fast"
assert result["multi_hop"].route == "stable"
uncertain = {"short_qa": {**stats["short_qa"], "dq_lo": -.20, "dq_hi": .18}}
assert decide_routes(uncertain, premium, waiting, tolerance)[0].route == "stable"
for p in result.values():
print(p.request_class, p.route, round(p.net_benefit_cny, 4), p.reason)
if __name__ == "__main__":
demo_routes()
这里真正保守的选择是 dq_lo >= -tol。质量差定义为快档减普通版,越负越差,因此要检查最差的合理范围,而不是最乐观的上界。点估计过线但下界越界时继续留在普通版;这表示尚未证明损失可接受,不等于已经证明高速版更差。数据不全、配置缺失或样本不足也都不放行。
代码只演示单轮固定样本的门槛,不承诺多类别、每日重复检查后的总体误报率。正式应用要预先安排复核周期,必要时使用多重比较校正或序贯方法;若各指标相关,还应在同一次请求级重采样中联合计算质量、延迟与净收益,以免将多个独立的 95% 区间误当成联合 95% 保证。净收益这里采用点估计,离盈亏线很近时应保留基线并补样本。
另一个选择是门槛按类别分别设。max_quality_loss 和 waiting_cost_cny_per_s 都是字典而不是标量。这不是为了代码好看——如果把容差做成全局常量,你实际上是在强迫「合同抽取」和「闲聊」接受同一个质量标准。
7. 上线之后的失败模式
测量做完、路由表上线,真正的麻烦才刚开始。以下六种情况在实践中最常见,每一种都会让上线前的结论失效。
延迟收益在流量迁移后消失。 快档的 ΔT 很可能是在低负载下测出来的,用的是平台闲置的加速容量。当大量流量真的迁过去,快档可能开始排队,ΔT 也可能缩小;是否发生取决于容量与调度。所以路由表上线之后,必须用迁移后的稳态流量重测 ΔT,而不是复用离线值。如果重测后发现 dt_lo 掉到 0 附近,只能说明当前样本尚不足以支持确定的正收益。先排查请求结构、缓存、采样波动及容量证据;确认拥塞后再加并发上限或按比例限流。
缓存共享污染配对。 如果两档共用前缀缓存,回放阶段先跑的档位会吃亏。这在多轮会话、长系统提示、同一模板批量生成的场景里尤其明显。解决办法是给两档分配独立的缓存键前缀,并让回放顺序随机化。如果平台侧不支持隔离,至少要在分析时按「先跑 / 后跑」分组,检查 ΔT 在两组之间是否一致。
质量信号被「简洁偏好」带偏。 如果 rubric 里有「回答简洁」这样的加分项,快档因为输出更短会拿到更高的分,测量结果就会系统性高估快档。这在用 LLM 当评委时尤其容易发生,因为评委本身可能偏好更短的答案。防御方法是:质量分只看任务是否完成、关键信息是否齐全,把长度作为单独的诊断指标,不进入总分。
探针集和真实流量脱节。 固定探针集的好处是可比较,坏处是它不会跟着流量分布变。新上线的请求类型如果不属于任何已知类别,必须有明确兜底:默认走稳档。这不仅是质量问题,也是成本问题——未分类请求走快档,等于给未知风险开了口子。
厂商侧静默变更。 模型名和档位名不变,服务端的调度策略、批处理参数、甚至后端硬件都可能变。这类变化不会发公告,但可能使部分历史结论不再适用。一种持续发现变化的办法是常驻探针:定期用固定基线加滚动真实样本做两档对比,并记录版本、负载与缓存状态。漂移先触发复核,不把每天重复检验中的单次显著当成确定退化;告警应有预先设定的连续窗口或误报控制。
会话内混档导致体验跳变。 如果按单次请求路由,同一个会话里可能前几轮走快档、后几轮走稳档,用户会感知到风格和能力的不连续。可以使用会话粘性减少无谓切换,但首轮简单不代表后续一直简单。后续出现更高要求时,应允许按明确定义的规则切回基线,并完整保留历史消息。首个版本也可以只对独立单轮请求开放高速路由,等多轮专项评测通过后再扩大范围。
还有一个成本口径的坑值得单列:用「每 token 单价」比较会得出错误结论。即使快档单价更高,不同的实际用量和缓存命中仍可能改变每任务费用;不能把用量下降直接归因于预算被砍。反过来,如果快档因为质量下降导致重试率上升,实际成本会显著高于单价暗示的值。建议同时报告每原始请求成本与每成功任务成本:前者与本文净收益公式一致,后者反映交付效率。后者的分子包含所有尝试,分母只算最终成功的原始任务;两个分母不能混用。
8. 验证顺序与判定口径
测量和上线应该分三段走,每一段回答一个不同的问题。
第一段:离线回放,回答「质量差有多大」。 用历史请求样本,两档各跑一遍,得到配对数据。这一段便于控制输入和配置,但影子阶段同样可以获得配对区间,可靠性取决于采样、干扰控制和测量条件。代价是它反映的是历史流量分布,对新增请求类型没有覆盖。
第二段:影子流量,回答「在真实负载下延迟收益还在不在」。 快档真实执行但不返回给用户,只记录结果和耗时。这一段能暴露负载耦合、缓存竞争、限流这些离线测不出来的问题。它的成本是双份推理,所以通常只跑几天、只覆盖部分流量。
第三段:小流量 A/B,回答「用户侧有没有感知到变化」。 按类别分流,快档结果真的返回给用户。这一段失去了配对关系,只能看类级别的护栏指标:任务成功率、重试率、用户反馈、会话时长。如果第一段的结论是「质量差在容差内」,这一段应该看到护栏指标没有显著恶化;如果看到了,说明离线的质量分没有捕捉到用户真正在意的东西,要回去改 rubric。
判定口径建议固定成四条,每条都要有明确的数值门槛和复查周期:
| 判据 | 门槛来源 | 复查触发 |
|---|---|---|
| 质量 | ΔQ 区间下界 ≥ −类别容差 | 探针集日检偏移 |
| 延迟 | 迁移后 ΔT 区间下界 > 0 | 流量结构变化 |
| 经济 | 每原始请求净收益 > 门槛 | 单价调整或 token 分布变化 |
| 稳定性 | 回退率低于设定值 | 错误率上升 |
四条里任何一条不满足,该类就回退到稳档。这不是「降级」,而是把不确定性关进一个已知的笼子里。
最后是适用边界。这套方法成立的前提有四个:两个配置能够完成相同的目标任务,已知能力差异被明确记录;你能拿到足够的历史请求做配对;质量可以用任务级判据衡量,而不是只能靠主观偏好;延迟和成本的变化可以被归因到档位本身,而不是被其他同时上线的改动掩盖。任何一个前提不成立,结论的可靠性都会打折扣,需要在报告里明确写出来,而不是假装它们都满足。
9. 把输出长度与解码速度分开,避免“少回答”等于“更快”
同一个输入的配对比较可以回答切换配置后的整体效果,但不能自动解释效果来自哪里。输出长度是结果的一部分:快档若更短,可能是表达更简洁,也可能是漏答或被截断。质量 rubric 应先判断任务是否完成,再把长度作为诊断指标。按长度匹配后的子集分析只能辅助定位,不能替代所有原始请求上的总体质量评估。
客户端至少记录三个时间戳:发出请求、首个有效输出 token 到达、流结束。统一定义 TTFT = t_first − t_send、decode_s = t_end − t_first,于是 T = TTFT + decode_s。结束帧等协议开销也包含在这个后半段区间内;若使用 SDK 给出的其他口径,必须先对齐。这里 TTFT 已包含首 token 前的排队、网络、预填充等等待,不能再额外加一次排队时间。
对思考模型还要区分“第一个推理增量”和“第一个最终答案增量”。如果界面只展示最终答案,用户等待应以第一个可展示的 content 为准;若定义 TTFT 为任意有效模型输出,则优先记录独立的答案首 token 时间。心跳包、空增量不算 token。不同口径都可以保留,但两档必须一致。
为了做一个可复算的账面分解,令 r = N / decode_s 为输出后半段的有效平均速率,而不是硬件峰值。固定 ΔT = T_stable − T_fast,可写成:
ΔT = (TTFT_stable − TTFT_fast)
+ (N_stable − N_fast) / r_stable
+ N_fast × (1 / r_stable − 1 / r_fast)
三项分别是首输出前的收益、以普通版速率计算的长度效应,以及以快档长度计算的速率效应。这个等式依赖选定的分解顺序,换一个参考速率会重新分配后两项,因此它是描述性分解,不能当成严格的因果实验。若 N 只统计最终答案而计时包含推理输出,两个量也不匹配,必须另行分段或停止做此分解。

图4:构造数据。普通版 3.98 秒、高速版 2.11 秒,节省 1.87 秒;其中 0.68 秒来自输出更短,不能全称为计算加速。
from math import isfinite
def split_latency(rec_f, rec_s):
"""客户端区间拆解,stable - fast,正数表示节省;不重复添加排队。"""
for rec in (rec_f, rec_s):
if not all(isfinite(rec[k]) for k in ("ttft_s", "out_tokens", "decode_s")):
raise ValueError("需要有限的测量值")
if rec["ttft_s"] < 0 or rec["out_tokens"] <= 0 or rec["decode_s"] <= 0:
raise ValueError("首 token 时间非负,输出与后半段时间须为正")
n_f, n_s = rec_f["out_tokens"], rec_s["out_tokens"]
r_f, r_s = n_f / rec_f["decode_s"], n_s / rec_s["decode_s"]
parts = {
"ttft": rec_s["ttft_s"] - rec_f["ttft_s"],
"length": (n_s - n_f) / r_s,
"rate": n_f * (1 / r_s - 1 / r_f),
}
return {**parts, "total": sum(parts.values())}
# 构造数据;与图4一致,不代表 Kimi 的实测性能。
fast = {"ttft_s": .31, "out_tokens": 120, "decode_s": 1.80}
stable = {"ttft_s": .58, "out_tokens": 150, "decode_s": 3.40}
parts = split_latency(fast, stable)
assert abs(parts["total"] - 1.87) < 1e-9
assert abs(parts["length"] - .68) < 1e-9
assert abs(parts["rate"] - .92) < 1e-9
assert abs(parts["total"] - (stable["ttft_s"] + stable["decode_s"]
- fast["ttft_s"] - fast["decode_s"])) < 1e-9
若回答被 max_completion_tokens 截断,未完成任务本身就是服务配置造成的实际损失。应按事先定义的任务 rubric 计失败或部分得分,同时单独报告截断率。不能把截断样本统统标成 invalid 后剔除,否则可能只剩下高速版成功的容易请求,形成幸存者偏差。可以额外报告“完整输出条件下”的质量,但必须同时展示全部请求的成功率;修正输出上限后的配置要重跑评测。
10. 延迟收益随负载变化,观察点要落在整条任务链
上面的客户端日志足以分解 TTFT 与后半段时间,却不能单独辨识服务端队列、网络、预填充和内部推理各占多少。服务端若额外提供排队时长或追踪 span,可以进一步诊断;只有入队/出队时间也仍不能把其余耗时全部认定为网络。无法观测的字段应写成未知,不要从一个总时长倒推出多个内部阶段。
探针在低并发下跑,测到的是该环境下的延迟差。上线后并发上升,限流、缓存争用、重试和工具排队都可能改变 P95。报告应保留各档并发、QPS、输入长度与错误率;灰度阶段对比迁移前后负载,必要时给高速版设置并发上限。若两档共享容量,影子调用本身也可能干扰原始请求,所以影子比例需要受控。
如果收益集中在 TTFT,它更像一笔每次调用固定的等待节省,其相对完成时间收益通常在长输出中摊薄;若收益集中在后半段有效速率,长输出更可能受益。但思考型模型的“首推理 token”并不代表用户开始看到答案,Agent 任务还受工具调用和串并行依赖影响,所以最终仍要测可展示答案首字时间和整条任务完成时间。
诊断时可以用普通版输出长度做分桶,观察长输出任务的成功率与长度效应。这个分桶依据是运行后才出现的结果,只能用于分析,不能直接成为请求进入系统时的路由条件。上线特征必须来自调用前可得的信息,例如输入长度、任务类型、输出格式要求和工具需求;如果需要预测输出长度,还要单独验证预测误差带来的路由风险。
总结
Kimi K3.1 Pro Highspeed 的官方定位是同等智能下的输出加速。业务接入要检验这项承诺在自己的请求、推理强度和负载下能否成立,不预设高速版降质,也不把输出速率倍数当成整条任务的提速倍数。
决策要落在三个数上:每类请求的质量差及其区间、迁移后仍然成立的延迟收益、以及把延迟折算成钱之后的净收益。三个都过线才走快档,任何一个不过线就留在稳档。容差和等待成本必须按类别设,因为「合同条款抽错一个字」和「闲聊慢两秒」本来就不该用同一个标准。
最后一条边界:这套测量有保质期。模型版本、服务配置、价格、流量结构,任何一个变了,之前的结论都要重验。把探针集常驻下来,比把这次的结论写进文档更重要。
参考资料
- Kimi API 官方 K3.1 Pro 说明(2026-10-26 核对,模型 ID 与 Highspeed 定位) — https://platform.kimi.com/docs/guide/kimi-k3-1-pro-quickstart
- Kimi API 官方定价入口与首页价格展示(2026-10-26 核对) — https://platform.kimi.com/docs/pricing/chat 、 https://platform.kimi.com/
- Kimi API 官方推理强度说明(2026-10-26 核对) — https://platform.kimi.com/docs/guide/use-reasoning-effort
- Kwon et al., Efficient Memory Management for Large Language Model Serving with PagedAttention, SOSP 2023 — https://arxiv.org/abs/2309.06180
- Yu et al., Orca: A Distributed Serving System for Transformer-Based Generative Models, OSDI 2022 — https://www.usenix.org/conference/osdi22/presentation/yu
- Leviathan et al., Fast Inference from Transformers via Speculative Decoding, ICML 2023 — https://arxiv.org/abs/2211.17192
- Dean & Barroso, The Tail at Scale, Communications of the ACM, 2013 — https://research.google/pubs/pub40801/
- Zheng et al., Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena, NeurIPS 2023 — https://arxiv.org/abs/2306.05685
- Chiang et al., Chatbot Arena: An Open Platform for Evaluating LLMs by Human Preference, ICML 2024 — https://arxiv.org/abs/2403.04132
- Efron & Tibshirani, Bootstrap Methods for Standard Errors, Confidence Intervals, and Other Measures of Statistical Accuracy, Statistical Science, 1986 — https://projecteuclid.org/journals/statistical-science/volume-1/issue-1/Bootstrap-Methods-for-Standard-Errors-Confidence-Intervals-and-Other/10.1214/ss/1032280214.full
- SciPy
scipy.stats.bootstrap参考实现 — https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.bootstrap.html - Google SRE Book, Service Level Objectives — https://sre.google/sre-book/service-level-objectives/
本文针对Kimi K3.1 Pro普通版与Highspeed版的选型问题,提出基于配对测量、置信区间和净收益的科学路由决策框架。核心包括:控制变量下的质量-延迟散点分析、按请求类别划分的结构化评测、bootstrap置信区间判定质量差、延迟收益按业务场景折算为等待成本、以及上线后缓存污染、静默变更等失败模式应对策略。强调不能依赖全局平均或单次对比,必须以任务级rubric、真实负载验证和持续探针监控保障决策可靠性。

21

被折叠的 条评论
为什么被折叠?



