一、内阁第一笔,慢得出了名

徐砚秋是内阁公认的第一笔。

他批的奏章,措辞之精准、轻重之得宜,当朝无人能及。皇上拿到票拟,十份里有九份只批三个字——"知道了"——意思是不用改,照办就行。剩下那一份,最多改一两个字。满朝文武都知道,徐学士笔下出来的东西,闭着眼用就行。

但大家也知道另一件事:他太慢了。

一份奏章摆到他面前,他先读三遍——第一遍看大意,第二遍看门道,第三遍看藏在字缝里的心思。读透了,才提笔。每个字都要从肚子里搜——这个词是不是轻了,那个词是不是重了,这句和上句能不能接上气。一份奏章一盏茶,一天从日出坐到掌灯,最多批二十份。

而内阁每天收进来的奏章不下百份。边关军报、河道汛情、各省赋税、百官升迁——案头永远堆着一座小山。剩下的八十份怎么办?交给底下的小吏去批。小吏批出来的东西,皇上那三个字——"知道了"——变成了一长串朱批:"此处措辞不妥""再议""尔等办事怎如此草率"。一道驳回,就得重写、重递、重批,一来一回折腾四五天。

协办大学士刘敏行不止一次找徐砚秋谈过这事。

"砚秋,不是我说你——是你的笔太好,把你的手绑住了。"刘敏行指着案头那座奏章小山,"天下的事不能等你一个字一个字地磨。"

徐砚秋搁下笔:"奏章不是文章。写错一个字,下面办错一件事。我不快,是因为——"

"是因为你怕错。"刘敏行替他说完,"怕错没错。但误了时辰也是错。边关三百里加急的军报在你这儿压了三天——你写得再好,仗已经打完了。"

徐砚秋望着自己刚批完的那份奏章——笔画工整、措辞考究。他忽然觉得每一个字都在硌他的眼睛。

第二天,内阁新来了一个年轻书吏,姓沈,单名一个青字。二十出头,笔头极快。刘敏行把他安排在徐砚秋对面的案子上。

"你的帮手。"刘敏行丢下这句话就走了。

徐砚秋看着对面这个年轻人——袖子挽到肘弯,笔已经在砚台里蘸饱了墨,正对着他咧嘴笑。

"徐学士,您只管读折子。我来写。"

二、先写五个,一眼核完

头三天,徐砚秋没让沈青碰一份真奏章。他给沈青看的是旧档——半年前已经批完的奏章,批语都在,当范文看。

沈青翻了两天,第三天就开始写。他的笔快得出奇——徐砚秋刚读完一份折子说出大概意思,沈青那边已经刷刷刷写了半行字。但问题也是明摆着的:写到第三个字就开始偏,写到第五个字经常偏得没边。

"太急了。"徐砚秋看他写的批语,眉头皱成一团,"你急着抢时间,写到后面连前面说的是什么都忘了。"

"那我慢一点?"

"你慢下来,跟我自己写有什么区别?"

沈青把笔搁下,想了很久。第二天一早他进值房的时候,眼睛是红的,手里攥着三张纸。

"徐学士,我换了个写法。"

"什么写法?"

"我不从头写到尾了。"沈青把三张纸铺在案上,"每份奏章,我只写五个字。五个字写完,推给您——您看一眼,圈出能用的,划掉不能用的。不能用的那个字,您自己改。改完之后我从改的那个字后面,再接着写五个。"

徐砚秋拿起第一张纸。上面是一份关于浙江漕运的奏章批语,沈青只写了五个字——"着该省速查"。

"后面呢?"

"后面等您核完这五个字再说。"沈青把朱笔递过去,"您先核。"

徐砚秋扫了一眼。"着"字——对。"该"字——对。"省"字——对。"速"字——他顿了一下,提笔改成了"详"。

"'速查'太急,会让下面觉得出了大事。'详查'——意思是仔细查,但不催命。"他把改过的纸推回去,"前面三个字没问题。第四个开始改。"

沈青从他改的"详"字后面续写了五个字——"明实情据实"。

徐砚秋接过来,又是扫一眼。"明"——对。"实情"——对。"据实"——他划掉"实"字,旁边写了"奏"。"据实"和"实情"里有两个"实"字,读起来硌嘴——改成"据实具奏"。

"改一个字。后面五个全对。"

沈青咧嘴笑了。他从前三个字到第五个字,再到后面五个字——一份批语,往常徐砚秋自己写要一盏茶。这回沈青先写五字,徐砚秋核完;他续五字,徐砚秋再核——两个来回。徐砚秋核十个字的功夫,只花了他自己写一个字的时间。

因为徐砚秋自己写一个字,要在脑子里翻来覆去地搜——搜出来了,落笔;再搜下一个字。十个字就是十次搜索。

核稿不一样。核稿的时候,十个字已经摆在眼前了。他不需要搜——他只需要判断。十个字扫一眼,好的圈,坏的改——扫一眼的时间,比搜一个字还短。

"再试一份。"徐砚秋把第二份奏折推过去。

当天下午,他们用这种"五个字一推"的法子批了四十份奏章。比徐砚秋平常一天的量多了一倍。

刘敏行在值房门口站了好一会儿,看完整个过程,转身走了。临走只说了一句:"明天继续。"

三、写几个,推几个

过了三天,沈青发现了问题。

"徐学士,写五个有时候不够,有时候又太多。"

他翻出一叠批语记录——他用小字在旁边记了每一轮核稿的结果。前三轮写五个字,十五个字里用了十三个——只改了两个字,效率很高。但碰到一份关于云南改土归流的棘手奏章,他连推了两轮五个字,两轮里的第四个第五个字全被驳回了。等于写了十个字,只有六个能用。

"这种奏章我不熟——改土归流的措辞、典故、分寸,我没怎么练过。"沈青指着记录,"写到第四个第五个字就开始乱写。您全给我驳回来了。"

"那你推三个字试试。"

沈青试了十份"三个字一推"——三字一轮,核得极快,几乎没有驳回。但新的问题又出现了:核完之后,徐砚秋得空着手等他写下一轮三个字。等待的时间虽然短,十份加起来就不短了。

"你看——"沈青在纸上画了一张表,"三个字的驳回率不到一成,但核和写之间有间隙,您干等着。五个字的驳回率高一些——大概两到三成——但间隙小,您核完一轮我下一轮已经写好了。七个字我试过六份,后面三个字被打回来的概率超过了一半。"

徐砚秋把那页纸拿过来看了很久。

"不是推得越多越好。"他说,"也不是推得越少越好。是找一个数——你的驳回率和我的等待时间,两个加在一起最小的那个数。"

他们试了四天。把五十份旧奏章当试验品,一个字一个字地记。三个字、四个字、五个字、六个字、七个字——每种推法都试了不下百轮。

结论是五个字。不是理论最优,是实际最优。

五个字一轮,沈青的通过率大概七成——前两三个基本全过,第四第五偶尔被改。而且五字一轮正好让两个人形成了节奏:沈青写五字的时间,刚好够徐砚秋核完上一轮推过来的五字。笔没停过,人没等过。

"五个字。"徐砚秋最后在纸上画了一个圈,"定了。"

四、有些折子,干脆别写

第二个月,沈青在记录里发现了一件怪事。

户部和兵部的奏章,他的通过率最高——十轮五字里只被驳回两三个字。因为这些折子格式固定,用语规范,"着该部议奏""准如所请""交部从优议叙"——翻来覆去就那么几十个句式。他写了两个月,闭着眼都能拟。

但礼部的祭天祝文、翰林的诗词批语、刑部引用前朝判例的复杂文书——他的通过率掉到了不到一半。有时候第一轮五个字就被驳回三四个,徐砚秋基本等于自己从头写了一遍。

"这种折子,"沈青把记录摊在徐砚秋面前,"我写了还不如不写。您核我写错的字,比您自己从头写还慢。"

徐砚秋翻着那份记录,忽然说了一句沈青没想到的话。

"那就不写。"

"什么?"

"这种折子,你一个字都别动。我自己写。"徐砚秋把那几份折子单拎出来,堆在一边,"你那五个字轮法,适合咱们两个都熟的活。你半生不熟的活,你写了是添乱——我核你的错字花了时间,最后还得自己写。"

沈青想了想:"那怎么判断哪种折子我熟、哪种不熟?"

"你记了两个月记录了。"徐砚秋拿起沈青那本厚厚的记录册,"这里头全写着——你自己的驳回率。驳回超过一半的——不写。驳回不到三成的——照常写。在中间的——"

"写少一点。三个字。"

"对。"

从那以后,沈青学会了先翻记录。看到一种类型的奏章,先看自己以前干得怎么样。干得好的——全速推五个字。干得差的——一个字不动,让徐砚秋自己来。在中间的——推三个字,保守着写。

五、年底汇算,一百八十七份

腊月二十六。各省汇算的奏章像雪崩一样涌进了内阁。

每年这一天,是一整年里奏章最多的一天——河南的赋税、江苏的漕粮、四川的边防、两广的盐课——从天不亮开始,送折子的小太监排着队往里进。到辰时三刻,徐砚秋案头的奏章已经堆到了肩膀。

"一百八十七份。"刘敏行亲自来数了一遍,脸色铁青,"按你从前的速度,一天二十份——一百八十七份,你得批九天。但明天是封印的日子。今天必须全部批完。"

徐砚秋看了看对面案上的沈青。沈青已经把袖子挽起来了。笔在砚池里蘸饱了墨,正对着他咧嘴笑。

"开始。"

徐砚秋拿起第一份折子——河南今年的收成。三遍读完,心里有了数。沈青那边已经在写第一个五字了。

"着—该—抚—据—实——"

"等等。"徐砚秋按住纸,"第一个'着'字不对。不是'着'——是说河南今年收成好。第一句要'知道了'——先让皇上放心。"

沈青划掉,重写:"知—道—了—该—抚"。

徐砚秋扫一眼。"知道了"三个字一笔圈过——全对。"该抚"——对。第五个字——通过。

下一轮五个字,全部通过。再一轮,改一个字。再一轮,全过。

一份河南收成的折子,七个回合,三十五个字,总共改了两个字。

第一份批完,沈青立刻续上第二份——江苏漕运。八月发过一次水,漕船迟了半个月。徐砚秋读完,拍了拍桌子:"口气要缓。漕运的事急不得。"

沈青写了前五个字,徐砚秋改了两个字——"从速"改成"从容"。后面几轮基本全过。

到了第三份四川边防——沈青一看是边防的折子,先翻了翻自己的记录册。边防折子他驳回过半。他把笔放下了。

"这份您自己来。"

徐砚秋二话没说,拿起笔从头写起。写完之后两人接着下一份。

从卯时三刻开始,两个人像一对咬合得严丝合缝的齿轮。徐砚秋读折子定方向,沈青写头五个字推过去,徐砚秋扫一眼——好的圈,坏的改——推回来,沈青从改字处续写再推。碰到沈青不熟的,一个字不写,徐砚秋自己来。

值房外面不知道什么时候飘起了雪。铜壶漏了又加,加了又漏。案上的奏章一座山接一座山地矮下去。

掌灯时分,刘敏行推门进来。

桌上只剩最后一份折子了。徐砚秋正在核沈青推过来的最后一轮五个字——批的是广东盐课的事。他改了一个字,圈了四个。笔搁下的时候,手在微微发抖。

"多少?"刘敏行问。

"全部。"沈青替他答了,"一百八十七份。"

刘敏行拿起最上面的那份批语看了三遍。又从底下抽了一份,又看。

"用词、分寸、规矩——"他把两份批语并排放在桌上,"跟砚秋自己写的没区别。"

"就是他自己写的。"沈青说。

"你写的拟稿——"

"我写的拟稿,是他一个字一个字核过的。他圈的等于他选的。他改的当然是他写的。"

刘敏行望着这一老一少坐在灯下,忽然想到一个问题。

"小沈,你现在写五个字,他能圈几个?"

"看折子类型。兵部户部的——五个里面能圈四个。礼部刑部的——大概三个。边防的干脆不写。"

"跟两个月前比呢?"

沈青想了想:"多了大概一成。"

"你在变好。"

"对。我写过的每一份折子,他改过的每一个字——我都记在脑子里。他改什么、为什么改——改多了我就不会再错了。"沈青站起来,把那叠批好的奏章码整齐,"我在学他。他不是在核我一个人的稿——他是在用核稿教我怎么变成他。"

屋外雪还在下。值房里的灯又续了一盏油。徐砚秋靠在椅背上,看着沈青在整理奏章,忽然想起两个月前刘敏行说的那句话:"你的笔太好,把你的手绑住了。"

他对面现在有另一支笔。不那么好,但足够快。足够快到把他的双手解开。

一个人写,快不了。两个人写——一个在前面先跑,一个在后面一眼核完——一个人就干出了两个人的活。世上最快的笔,从来不是写出来的,是核出来的。

技术解读

投机解码(Speculative Decoding)是 2023 年由两个独立团队——Google 的 Leviathan 等人和 CMU 的 Chen 等人——同时提出的大语言模型推理加速技术。它的核心思路巧得让人拍案:不用把大模型变小,也不用把大模型剪枝,而是引入一个"小模型"来猜大模型会输出什么——小模型快速生成 K 个候选 token,大模型只需要做一次并行前向传播来验证这些候选,正确的直接采纳,错误的按大模型自己的概率分布重新采样。因为大模型"判断对错"比"自己写"快得多——验证一个 token 和生成一个 token 的计算量几乎相同,但验证可以并行批处理 K 个——理论上可以在不损失任何输出质量的前提下获得 2-3 倍的推理加速。这项技术已被广泛部署在 vLLM、TensorRT-LLM 等推理框架中,是当前 LLM 推理加速的核心手段之一。

核心概念回顾

概念 通俗解释
目标模型(Target Model) 最终负责输出的大模型,精度高但推理慢
草稿模型(Draft Model) 用于快速生成候选 token 的小模型,参数量通常是目标模型的 1/10 到 1/20
K 值(Lookahead) 每轮草稿模型预先生成的候选 token 数量,典型取 3-7
并行验证 目标模型一次前向传播同时计算 K+1 个位置的输出概率分布
接受准则 比较草稿 token 在目标模型下的概率与草稿模型下的概率,决定采纳或驳回
拒绝采样 草稿 token 被驳回时,从目标模型分布与草稿模型分布的差值中重新采样
无损加速 投机解码的数学保证:输出的概率分布与目标模型自回归生成完全一致
领域对齐 草稿模型和目标模型在具体任务领域上的匹配程度,决定接受率
接受率 草稿 token 被目标模型采纳的比例,直接影响加速效果
树形推测 同时草稿多个候选序列(不止一条链),在分支点用目标模型并行验证,进一步提高接受率
自推测解码 跳过独立的草稿模型,用目标模型自身的部分层作为草稿引擎

故事中的隐喻对照

故事元素 映射的技术概念 解释
徐砚秋——内阁第一笔,措辞精准但慢得出名 目标模型(Target Model) 大参数量的 LLM,自回归逐 token 生成,质量极高但每次前向传播只产出一个 token
沈青——年轻书吏,笔头极快但措辞偶尔偏 草稿模型(Draft Model) 小参数量的模型,快速生成候选 token,速度快但准确率不如大模型
"先写五个字,推给您核一眼" 投机解码的 K 值 草稿模型每次生成 K 个候选 token,目标模型并行验证 K+1 个位置
徐砚秋扫一眼→好的画圈、坏的改字 并行验证 + 接受/拒绝 目标模型一次前向计算 K+1 个位置的 logits,按概率逐一判断接受或拒绝每个草稿 token
"核十个字的时间,只花了自己写一个字的时间" 验证远快于生成 大模型验证 K 个 token 只需一次前向传播,自回归生成 K 个 token 需要 K 次前向传播
五字一轮——不是最多也不是最少 K 值调优 K 太小→目标模型空等,K 太大→后段接受率暴跌,典型最优取 3-7
沈青的通过率七成,前三字基本全过 接受率递减规律 越靠后的草稿 token 接受率越低,因为误差随草稿步数累积
驳回的字→徐砚秋自己改 拒绝采样 被拒绝位置的 token 从目标模型修正分布中重采样,保证输出分布一致性
兵部户部折子通过率高,祭文祝词通过率低 领域对齐 草稿与目标模型在训练数据域上的匹配度决定接受率,域内高、域外低
沈青翻记录——驳回过半的不写 自适应投机 动态判断是否启用投机解码,低接受率任务上跳过以避免负加速
"改多了我就不会再错了" 草稿模型的持续微调 草稿模型可在线更新,用被目标模型修正的 token 作为训练信号
一百八十七份从早到晚全部批完 2-3 倍推理加速 投机解码在保持输出质量无损的前提下实现 2-3 倍吞吐量提升
皇上分不出哪份是谁写的 输出分布一致性 投机解码的数学保证:最终输出的概率分布与目标模型自回归生成完全相同

为什么这个故事对应投机解码?

  1. "验证十个字比写一个字还快"是投机解码的物理基础:大语言模型的自回归生成——每产出一个 token 需要一次完整的前向传播,计算量巨大。但如果 K 个候选 token 已经摆在你面前,大模型进行一次前向传播就能同时验证全部 K+1 个位置(草稿的 K 个位置 + 自己的输出位置)。核心节约在于:K 次生成被压缩为 1 次验证。

  2. 接受/拒绝机制保证输出无损:徐砚秋"好的画圈,坏的改字"映射投机解码的接受准则。当草稿 token 在目标模型下的概率大于等于草稿模型下的概率时——直接采纳。否则以一定概率驳回,从目标模型的分布中重新采样。这一拒绝采样算法在数学上严格保证了最终输出分布与目标模型自回归生成完全一致——"质量没有打折扣"。

  3. K 值是速度与接受率的平衡:沈青实验了三字到七字的推送方案,发现五个字最优——不多不少。K 值的选择是投机解码最关键的调参:K 太小浪费目标模型的并行验证能力(核完空等),K 太大会因草稿模型累积误差导致后段接受率暴跌(写了也白写)。实际部署中 K=3 到 7 是常见的 sweet spot。

  4. 领域对齐决定实际加速效果:兵部户部的格式化折子(通用文本)通过率高达九成,祭文祝词(专业、罕见文体)不到一半。投机解码的加速效果高度依赖草稿和目标模型的分布匹配度——两者在训练域上越接近,接受率越高,加速越显著。

  5. 自适应跳过体现了工程务实:沈青翻看记录册,"驳回过半的不写"——现代推理引擎同样如此。当检测到草稿模型在当前上下文上接受率过低时,自动退回目标模型的纯自回归模式。这种自适应策略避免了在"负加速"场景下浪费计算资源。

  6. 草稿模型随合作不断进步:沈青"改多了我就不会再错了"——这是投机解码最优雅的长远效应。草稿模型可以被目标模型的拒绝采样结果微调(蒸馏),在接受率低的领域逐步对齐目标模型的分布,形成良性循环。

  7. C++ 是高性能推理引擎的基石:vLLM 和 TensorRT-LLM 的投机解码内核用 C++/CUDA C++ 实现——草稿模型的 KV cache 管理、目标模型的并行验证、接受/拒绝采样的高效实现,全部依赖 C++ 对 GPU 内存和计算资源的精细控制。从 KV cache 的页式管理到草稿 token 的批量验证,每一纳秒的性能提升背后都是 C++ 模板和指针运算在底层支撑。

后记:一个人想把每件事做到最好——结果是被自己的笔绑住了手。两个人就不一样了——一个在前面先跑,摔了也不怕,后面那个人会把他拽回来。投机解码的优雅在于它没有让大模型变快,也没有让小模型变好——它只是让它们待在同一间屋子里,面对面坐着,一支笔交过来递过去。世上最快的笔,从来不是写出来的——是核出来的。