2026-09 · 想画一条"上下文长度 vs 吞吐"的曲线,结果大部分时间花在怎么让测量本身可信上
想知道的是:上下文从 10K 涨到 190K,解码速度会掉多少。听起来就是循环发请求、记数字。 但第一次跑出来的曲线明显不对——短上下文的吞吐高得不合理,长上下文的衰减又太平滑。
问题出在四个地方,每一个都会让曲线变形。
推理服务通常会把 prompt 的 KV 缓存留下来。如果第二次请求的 prompt 是第一次的延长版(构造长上下文时很自然就是这么做的), 那么"新增部分"才是真正被计算的,测出来的 prefill 速度是假的,快得离谱。
对策是两件事一起做:关掉 prompt 缓存,并且给每次请求加一段随机前缀 (我用的是一串随机小写词,约 200 个 token,专门用来打断任何前缀匹配)。
"我发了 100K token"这句话,取决于谁在数。用字符数除以经验系数是方便的构造方式,但要拿真实 token 数当横轴, 就得读服务端返回的用量字段。另外如果模型会输出思考过程,那部分 token 也算解码量—— 把它排除在外会凭空"变快"。
我用的是"随机词数 → token"的标定值(约 3.4 字符词/token,实测标定过),但画图和比较一律用服务端返回的真实 token 数。
端到端耗时里含 prefill。如果直接用"总耗时去除以输出 token 数",长上下文那一段会被 prefill 拖成一条假的下坡线。 正确的窗口是从第一个 token 之后开始算。
为了确认这个窗口取对了,我把客户端算出的速度和服务端自己报的 predicted_per_second 做双时钟对照,
两者差超过 10% 就把那个点标成可疑。实际跑下来大部分点差在几个百分点内, outliers 恰好都是网络抖动的时刻。
这条是我最后才意识到的。同一份配置、三条不同的测试文本(写代码 / 讲技术概念 / 中文描写), 单轮吞吐能从 80 涨到 138——差异全部来自任务本身,跟参数没什么关系。
所以最后的方法固定成:
在 200K 上下文配置下,两个点的实测:
| 上下文规模 | prefill (tok/s) | decode (tok/s) | 墙钟 |
|---|---|---|---|
| 约 100K | 1875.7 | 83.3 | 54.3 s |
| 约 190K | 1287.6 | 70.1 | 150.4 s |
两个点都做了"在长文里塞一根针再要求原样复述"的检索校验,命中了才算这条数据有效—— 不然根本不知道模型是不是压根没读进去、只是把长度混过去了。
prefill 从 1875 掉到 1287 是注意力开销随长度上升的正常形状; 但如果哪一天你看到长上下文的 prefill 反而更快,第一反应应该是怀疑缓存没关干净,而不是模型变强了。