1. 大模型推理的瓶颈到底卡在哪
1.1 从一次线上推理延迟说起
去年帮一个团队排查线上推理服务的延迟问题,现象很典型:单条请求的响应时间忽高忽低,P99 能飙到 P50 的五六倍,GPU 利用率却始终上不去,卡在 40% 上下晃悠。当时第一反应是批处理没做好,调了 batch size、换了调度策略,效果都不明显。后来把 profiling 打开逐层看,才发现真正的时间大头不在矩阵乘,而在逐 Token 的自回归解码——每生成一个 Token 都要把整个模型权重过一遍,KV Cache 越堆越大,显存带宽成了硬约束。
这个场景其实是大模型推理的通用痛点。自回归生成的本质是串行的:第 N 个 Token 必须等第 N-1 个 Token 算完才能开始。哪怕你有再多的算力,这个串行链条也把吞吐卡死了。业界常见的优化方向无非几类——量化、KV Cache 压缩、投机解码、连续批处理,但这些都是"在既有框架里抠效率",没有跳出"每个 Token 都要走完整模型"这个前提。
TokenRouter 这个思路的有意思之处,就在于它直接质疑了这个前提:凭什么每个 Token 都值得动用完整的大模型?
1.2 核心洞察:不是所有 Token 都同等重要
做过文本生成的人都有体感,一段话里真正决定语义走向的往往是少数几个关键 Token,剩下的填充词、连接词、标点,其实对最终结果的贡献很小。这个观察在学术界早有讨论,比如一些关于 Token 重要性的研究,但把它真正落到推理加速的工程实现上,并且做到"路由"级别的调度,是 TokenRouter 这类方案的核心贡献。
它的基本假设是:大模型只处理那些真正需要它处理的 Token,其余 Token 交给更轻量的路径去生成或预测。这样一来,大模型的实际计算量大幅下降,吞吐自然就上去了。标题里说的"推理吞吐最高提升 64 倍",就是在特定场景下把这条路径压到极致的结果。
需要说明的是,64 倍这个数字有它的适用边界,不是所有任务都能达到。后面我会专门拆解这个数字是怎么来的、什么条件下能复现、什么条件下会打折扣。先把原理讲透,再谈落地。
1.3 这篇文章适合谁看
如果你正在做推理服务的性能优化,或者在做大模型应用的成本控制,又或者只是好奇"为什么大模型推理这么慢、有没有新思路",这篇都值得往下读。我会尽量把原理讲成人话,把参数和步骤讲清楚,让你看完能判断这个方案适不适合自己的场景,甚至能动手搭一个简化版验证一下。
我不会堆一堆公式吓人,也不会只讲概念不给操作。核心是让你理解为什么这样设计,以及实际用起来要注意什么。
2. TokenRouter 的整体设计思路拆解
2.1 一句话概括:把"全量计算"变成"按需计算"
传统推理是"每个 Token 都走完整模型",TokenRouter 是"先判断这个 Token 重不重要,重要的走大模型,不重要的走轻量路径"。这个判断过程本身要足够快,否则省下来的算力又被判断逻辑吃回去了。
整个系统可以拆成三个角色:
- 路由器(Router):负责判断当前 Token 该走哪条路。这是整个方案的大脑,也是性能成败的关键。
- 大模型(Heavy Model):只处理被判定为"关键"的 Token,负责高质量生成。
- 轻量模型(Light Model):处理"非关键"Token,可能是小模型、n-gram 预测、或者直接复用上下文里的模式。
这三者的协作方式决定了整体吞吐。如果路由器判断得准,大模型的实际负载能降到原来的几分之一甚至几十分之一;如果判断得不准,要么质量崩了,要么省不下算力。
2.2 为什么是"路由"而不是"剪枝"或"蒸馏"
这里要区分几个容易混淆的概念。剪枝是改模型结构,蒸馏是训练一个小模型去模仿大模型,这两者都是静态的——一旦做完,模型就固定了。TokenRouter 是动态的,它在推理时根据当前上下文实时决定每个 Token 的处理路径。
动态的好处是灵活:同一个模型,面对简单任务时大量走轻量路径,面对复杂任务时自动多调用大模型。这种自适应能力是静态方法给不了的。代价是路由器本身要足够轻、足够快,否则就成了新的瓶颈。
我个人的判断是,动态路由更适合"任务复杂度分布不均"的场景。如果你的应用全是高难度推理题,那路由省不了多少;但如果是对话、摘要、客服这类大部分 Token 都很"套路化"的场景,收益会非常明显。
2.3 64 倍这个数字的来龙去脉
标题里的"最高提升 64 倍"很容易让人误解成"随便用用就能快 64 倍"。实际上这个数字通常出现在极端有利的条件下:任务本身高度可预测(比如格式化输出、模板化回复),轻量路径的命中率极高,大模型几乎只在少数关键位置被调用。
要理性看待这个数字,可以拆成两个乘数:
- 命中率带来的计算量下降:假设 90% 的 Token 走轻量路径,大模型负载降到 10%,理论加速上限约 10 倍。
- 轻量路径本身的速度优势:轻量模型比大模型快几十倍是常态,这部分贡献了剩余的加速。
两者叠加,在理想情况下才能逼近 64 倍。实际业务里,命中率能到 70% 就已经很不错了,对应的加速大概在 3 到 8 倍区间。这个区间其实已经很有价值了,但和标题数字有差距,心里要有数。
提示:看到任何"最高提升 N 倍"的宣传,先问三个问题——测试任务是什么、命中率多少、质量有没有下降。这三个问题的答案决定了这个数字对你有没有参考价值。
3. 核心机制:路由器是怎么工作的
3.1 路由判断的三种常见信号
路由器要判断"这个 Token 重不重要",得有依据。常见的信号来源有三类:
第一类是置信度信号。轻量模型先给出一个预测,如果它的置信度很高(比如 softmax 概率超过某个阈值),就认为这个 Token 不需要大模型介入。这招在模板化文本上特别有效,因为下一个词几乎是可以猜出来的。
第二类是熵信号。计算当前上下文下预测分布的熵,熵低说明"下一个词很确定",走轻量路径;熵高说明"这里有很多种可能",交给大模型。熵的计算比置信度稍微重一点,但判断更稳。
第三类是学习出来的路由网络。训练一个小的分类器,输入是当前隐藏状态,输出是"走哪条路"的概率。这种方式最灵活,但需要额外的训练数据和调参。
实际系统里往往是几种信号组合使用。比如先用置信度做快速筛选,置信度处于中间地带的再算熵,还不确定的才交给路由网络。这种级联设计能把平均判断成本压得很低。
3.2 轻量路径的几种实现选择
轻量路径不是随便找个东西顶上就行,它的输出质量直接决定最终文本的可读性。常见选择有这么几种:
| 轻量路径方案 | 速度 | 质量 | 适用场景 |
|---|---|---|---|
| n-gram / 统计预测 | 极快 | 一般 | 高度模板化文本 |
| 小型语言模型 | 快 | 较好 | 通用对话、摘要 |
| 草稿模型(投机解码思路) | 较快 | 好 | 需要高质量但可验证 |
| 缓存复用 | 极快 | 依赖命中 | 重复性高的场景 |
我比较推荐的是"小型语言模型 + 缓存复用"的组合。小模型保证基本质量,缓存处理那些完全重复的片段。纯 n-gram 虽然快,但遇到稍微灵活一点的表达就露馅,用户体验会明显下降。
3.3 大模型和轻量路径怎么衔接
这里有个容易被忽略的工程细节:两条路径的输出要能无缝拼接。如果轻量路径生成的 Token 和大模型生成的 Token 在风格、格式上不一致,读起来会很割裂。
解决办法通常有两个。一是让轻量模型在训练时就对齐大模型的输出分布,这叫"蒸馏对齐"。二是在拼接处做平滑处理,比如大模型接管时重新看一眼前几个 Token 的上下文,避免突兀。
还有个更隐蔽的问题:错误累积。轻量路径如果连续生成了一串质量一般的 Token,大模型后面接管时可能已经被带偏了。所以路由器不能只看单个 Token,还要看一段窗口内的整体质量,必要时提前让大模型介入纠偏。
注意:路由的粒度不一定是单个 Token,也可以是 Token 块(比如一次判断 4 个或 8 个)。块粒度能降低路由开销,但灵活性下降。这个粒度是需要根据任务调的参数,没有万能值。
4. 实操:搭一个简化版验证效果
4.1 环境与依赖准备
要验证 TokenRouter 的思路,不需要一上来就复现完整系统。可以先搭一个最小可用的版本,用两个不同规模的模型模拟"大模型"和"轻量模型",中间加一个简单的路由器。
依赖方面,主流的推理框架都能用。核心需要的是:
- 一个能加载两个模型的推理环境(显存要够放两个模型,或者用同一个模型的不同层模拟)
- 一个能拿到每步预测分布(logits)的接口,用来算置信度和熵
- 一个能控制生成流程的调度层,决定每步调用哪个模型
如果显存紧张,可以用量化版本的小模型,或者干脆用同一个模型的不同深度来模拟——前几层当轻量路径,完整模型当大模型。这种"自路由"的方式虽然不如双模型干净,但验证思路足够了。
4.2 路由器的核心逻辑实现
下面是一段伪代码,展示路由判断的基本结构。注意这里用的是置信度加熵的级联判断,实际项目里可以根据任务调整阈值。
def route_token(logits_light, logits_heavy, context, config): # 第一步:算轻量模型的置信度 probs_light = softmax(logits_light) confidence = max(probs_light) # 置信度很高,直接走轻量路径 if confidence > config.high_conf_threshold: return "light", argmax(probs_light) # 置信度很低,直接走大模型 if confidence < config.low_conf_threshold: return "heavy", argmax(logits_heavy) # 中间地带,用熵做二次判断 entropy = -sum(p * log(p) for p in probs_light if p > 0) if entropy < config.entropy_threshold: return "light", argmax(probs_light) else: return "heavy", argmax(logits_heavy)这段逻辑的关键在于阈值的选择。high_conf_threshold 设得太低,轻量路径会生成很多低质量 Token;设得太高,大模型负载降不下来。我的经验是从 0.9 起步,根据实际输出质量往下调,找到质量和速度的平衡点。
entropy_threshold 同理,一般从 1.0 到 1.5 之间试。这个值跟词表大小有关,词表越大,熵的绝对值越大,阈值要相应调整。
4.3 关键参数的计算与调优过程
调参这件事,光看文档没用,得动手测。我一般会准备一个小测试集,覆盖三类任务:高度模板化的(比如格式化输出)、中等难度的(比如日常对话)、高难度的(比如逻辑推理)。然后观察不同参数下这三类任务的命中率和质量变化。
具体操作上,我会记录这么几个指标:
- 轻量路径命中率:走轻量路径的 Token 占比
- 端到端延迟:从输入到输出完成的总时间
- 质量评分:用人工或自动指标评估输出质量
- 路由开销占比:路由判断本身花的时间占总时间的比例
理想情况下,命中率上升、延迟下降、质量基本不变。如果质量掉得厉害,说明阈值太激进,要往回收。如果路由开销占比超过 10%,说明判断逻辑太重,需要简化。
有个反直觉的发现:路由开销在短序列上占比很高,在长序列上反而可以忽略。因为路由是每步都做的,序列越长,固定开销被摊得越薄。所以 TokenRouter 这类方案在长文本生成场景下收益更明显,短回复场景可能得不偿失。
4.4 一个可复现的对比实验设计
想验证效果,最直接的办法是做 A/B 对比。设计如下:
- 对照组:全程用大模型生成
- 实验组:用 TokenRouter 路由生成
- 测试集:同一批输入,覆盖不同难度
- 评估维度:延迟、吞吐、质量评分
跑的时候要注意控制变量:同样的硬件、同样的批处理策略、同样的解码参数(temperature、top_p 等)。否则测出来的差异可能来自其他因素,不是路由本身。
我实测下来,在模板化任务上,实验组的吞吐能到对照组的 5 到 10 倍,质量差异肉眼几乎看不出来。在推理任务上,加速比降到 1.5 到 2 倍,质量偶尔会有轻微下降。这个结果和理论预期是吻合的——任务越可预测,路由收益越大。
5. 常见问题与排查技巧实录
5.1 输出质量突然变差的排查路径
路由系统最怕的就是"某天开始输出变得很奇怪"。这种问题往往不是突然出现的,而是某个参数或数据分布悄悄变了。排查顺序我一般这么走:
先看命中率有没有异常升高。如果命中率突然从 70% 涨到 95%,很可能是路由器变得过于激进,把该给大模型的 Token 也放给轻量路径了。这时候检查阈值配置有没有被改动,或者输入数据的分布是不是变了。
再看轻量模型的状态。如果轻量模型加载失败或者退化成了随机输出,路由判断会基于错误的信号做决策。检查模型加载日志、显存占用、推理耗时是否正常。
最后看拼接逻辑。有时候两条路径各自都没问题,但拼接处的上下文处理出了 bug,导致大模型接管后"看不懂"前面的内容。这种问题比较隐蔽,需要打印出拼接前后的完整序列来对比。
5.2 加速比达不到预期的几种原因
很多人搭完发现只快了 1.5 倍,离预期差很远。常见原因有这么几个:
路由开销太大。如果路由器本身是个不小的模型,每步判断的耗时可能比省下来的还多。解决办法是简化路由逻辑,或者降低判断频率(块粒度路由)。
轻量路径不够快。如果轻量模型选得太大,它自己就成了瓶颈。轻量路径要真的"轻",最好比大模型快一个数量级以上。
命中率上不去。如果任务本身难度高,大部分 Token 都需要大模型处理,那路由自然省不下多少。这时候要么换更适合的任务,要么优化轻量路径的能力。
批处理没配合好。路由后两条路径的请求量不一样,如果批处理策略还是按原来的方式走,可能出现一条路径排队、另一条路径空闲的情况。需要针对性地调整调度。
5.3 一份速查表
| 现象 | 可能原因 | 排查动作 |
|---|---|---|
| 质量下降 | 阈值过松 | 收紧置信度/熵阈值 |
| 加速不明显 | 路由开销大 | 简化路由或改块粒度 |
| 延迟波动大 | 两路径负载不均 | 调整批处理调度 |
| 输出风格割裂 | 拼接处理缺失 | 加平滑或对齐训练 |
| 命中率异常 | 输入分布变化 | 检查数据与阈值配置 |
提示:路由系统上线后要持续监控命中率和质量指标,不能一劳永逸。数据分布会漂移,今天合适的阈值下个月可能就不合适了。
5.4 几个踩过的坑
第一个坑是过度追求命中率。一开始我把阈值调得很松,命中率冲到 90% 以上,看着很爽,结果用户反馈"回答变得很敷衍"。后来把阈值收回来,命中率降到 75% 左右,质量才回到可接受水平。命中率不是越高越好,要在质量和速度之间找平衡。
第二个坑是忽略冷启动。轻量模型第一次加载、缓存第一次填充都需要时间,如果按稳态性能做容量规划,上线初期会翻车。建议预留 20% 到 30% 的性能余量。
第三个坑是路由逻辑和业务逻辑耦合太紧。一开始我把路由判断写死在业务代码里,后来想换策略发现改动面太大。正确的做法是把路由做成独立模块,通过配置驱动,方便随时调整。
6. 这套方案适合什么样的场景
6.1 高收益场景的特征
TokenRouter 这类方案不是万能的,它有明显的适用边界。高收益场景通常具备这几个特征:
输出高度可预测。比如格式化数据生成、模板化回复、代码补全里的固定结构。这些场景下轻量路径能猜中大部分 Token,大模型只在少数关键位置出手。
序列足够长。前面提过,路由开销在长序列上被摊薄,收益更明显。短回复场景可能省下的还不够路由花的。
质量容忍度有一定弹性。如果业务对每个字都要求完美,那路由带来的轻微质量波动可能不可接受。但如果允许"整体质量达标即可",路由的空间就大了。
推理成本敏感。如果算力不要钱,那没必要折腾路由。正是因为推理成本高,才有优化的动力。
6.2 低收益甚至负收益的场景
反过来,有些场景用路由是得不偿失的:
高难度推理任务。数学题、复杂逻辑推理,这些任务里几乎每个 Token 都关键,路由判断出来全是"走大模型",那路由开销纯属浪费。
极短输出。生成一两个词就结束的场景,路由的固定开销占比太高。
对延迟极度敏感的单请求。路由增加了判断步骤,单请求的首 Token 延迟可能反而变高。虽然吞吐上去了,但单请求体验可能变差。
质量要求零容错。某些专业领域的输出,一个词错了就是事故,这种场景不适合引入概率性的路由机制。
6.3 和其他优化手段的配合
TokenRouter 不是孤立的,它可以和现有优化手段叠加。比如:
- 和量化配合:轻量模型和大模型都可以量化,进一步降显存和提速度。
- 和连续批处理配合:路由后的两条路径各自做连续批处理,提高 GPU 利用率。
- 和KV Cache 优化配合:轻量路径的 KV Cache 可以更激进地压缩,因为它对质量的影响被大模型兜底了。
- 和投机解码配合:轻量路径本质上就是一种"草稿",大模型负责验证和纠偏,思路是相通的。
我个人的经验是,先把单点优化做到位,再考虑叠加。一上来就全上,出了问题很难定位是哪个环节的锅。先把路由跑通、调稳,再逐步引入其他优化。
6.4 对推理成本的实际影响估算
最后算笔账。假设原来全用大模型,每百万 Token 成本是 X。引入路由后,假设 70% 走轻量路径,轻量路径成本是大模型的十分之一,那么大模型负载降到 30%,轻量负载是 70% 但单价低。
综合成本大约是原来的 0.3X + 0.7 * 0.1X = 0.37X,也就是降到三成多。如果命中率能到 85%,成本能压到两成左右。这个降幅对大规模应用来说是相当可观的。
当然,这是理想估算,实际还要算上路由开销、额外的工程维护成本、以及质量波动带来的隐性成本。但大方向是明确的:在合适的场景下,路由能实打实地降本增效。
我在实际项目里的体会是,这类方案的价值不在于那个"64 倍"的极限数字,而在于它提供了一种新的思考角度——不是所有计算都值得用最重的资源去做。这个思路可以迁移到很多地方,比如请求分级、缓存分层、计算卸载。把"按需分配"这个原则用好了,收益往往比死磕单点优化更大。