Featured image of post Strands Decider 源码级拆解:砍掉输出头换成指针头,亚马逊内部用它替掉 Sonnet 4.6,那笔「省 98%」的账我重算了一遍

Strands Decider 源码级拆解:砍掉输出头换成指针头,亚马逊内部用它替掉 Sonnet 4.6,那笔「省 98%」的账我重算了一遍

先说清楚这篇跟站内已有的那几篇的分工。我们从九月底起写过这个品类的不少东西:《TypeSafe AI Jev 深度解析》讲的是「System One 模型」这个范式本身,《决策模型 + LLM 混合架构》把当时四条技术路线横着比了一遍,《14 天,OpenAI 就抄了个 Jev!》和《微软也下场做决策模型了》讨论的是这个品类的资本与竞争格局。那些讲的是形态、生态和谁在抢谁。

这篇不重复那些。这里只做两件事:把亚马逊自己的那个 2B 模型拆到源码级,然后把 11 月 9 日那篇「换掉 Sonnet 4.6、成本降 98%」的账重算一遍。

第二件事是重点,因为那个 98% 里有一部分不是模型的功劳。

它到底是什么:一次前向,没有解码循环

Strands Decider 是 Strands Agents 团队(也就是 AWS 那边做 Strands Agents SDK 的那批人)在 10 月 1 日放出来的开源模型。仓库是 strands-labs/strands-decider,Apache-2.0,9 月 29 日建库,到 11 月 9 日有 1,012 颗星、123 个 fork、59 个未关的 issue,整仓 32 MB 左右。

它的架构改动只有一句话:拿一个预训练的解码器 LLM,把语言建模头扔掉。

官方架构文档 docs/architecture.md 给了完整的打分公式。整段推理路径是这样的:

logit_k = <q(h_answer), k(h_option_k)> / sqrt(dim)

h_answer 是 <answer> 那个位置的隐状态,当 query;h_option_k 是每个选项那一行的最后一个 token 的隐状态,当 key。之所以取每个选项的最后一个 token,是因为在因果注意力下,那个位置已经读完了整个选项。然后对 K 个 logit 做 softmax,就是答案。

没有生成,没有解码循环,没有重试,没有 JSON 解析。一次前向出结果。

读出头(pointer head)大概 100 万参数,dim=256;除此之外只训了挂在躯干投影上的 rank-16 LoRA。v14 的 checkpoint 只有 88 MB,就是适配器加读出头加 tokenizer,底座权重加载时从 HuggingFace 拉。躯干是 Qwen3.5-2B-Base,1.9B 参数,而且它是个混合体:24 层里 18 层是 Gated DeltaNet(带循环状态的线性注意力),只有 6 层是全注意力。仓库里 mps_kernels.py 和 cpu_kernels.py 这两个文件就是在为 Apple 芯片和 CPU 补 Gated DeltaNet 的算子,因为 macOS 上没有 fla。

架构文档里有一句我认为是这个设计最关键的推论,原文是「An option’s score depends on what it says, not where it sits」。因为模型里根本没有任何按选项位置索引的参数,所以它学不到「第一个选项通常是正确答案」这种先验,也就没有选项数量上限,schema 里的 255 是唯一的限制。同一个权重可以回答两选的 yes/no,也可以回答三十选的工单路由,标签集在训练里从没见过也没关系,因为选项的含义完全由 prompt 建立。

代价也在同一份文档里写着。它承认「Reversing the option list moves the distribution by ~0.016」,也就是把选项顺序倒过来,分布会动大约 0.016。原因是在因果注意力下选项之间仍然互相看得见。要彻底消掉得做逐选项的注意力隔离,没实现。

这个 0.016 后面还会出现,而且会变成整篇最有意思的一个对比。

置信度是算出来的,不是预测出来的

这是我读这个仓库最大的收获,站内之前几篇都没写到这一层。

决策模型卖的一个核心点就是「每个决策都带一个可信度分」。但 Strands Decider 的 confidence 并不来自一个专门的输出头,它是从概率分布里现算的。

选一(choice)用的是归一化最大概率:

confidence = (N · p_max − 1) / (N − 1)

均匀分布得 0,one-hot 得 1,而且这个式子的关键是它与选项数 N 无关。所以一个 0.9 的阈值,在三个选项的路由问题上和十个选项的路由问题上,含义是同一个。如果你自己拿 max-prob 当置信度,这两者会不一样:N 越大 max-prob 越低,阈值就废了。

二选一(noul)干脆没有 confidence 字段,理由写得很直接,只有两个结果的时候,概率本身就是不确定性。

打分(score)用的是另一套,归一化标准差:

sigma = sqrt( Σ p_i · (i − mean)² )        confidence = 1 − sigma / sigma_max

这里修了一个真的排序 bug。如果用最大概率当置信度,一个两端都有质量的双峰分布会排在均匀分布前面,但对有序量表来说,双峰恰恰是最坏情况,因为返回的期望值恰好落在模型认为答案不在的位置上。换成标准差之后这个倒挂没有了。

还有一个更细的坑,我觉得是整份文档里最工程的一句话。训练时会故意把目标质量的一小部分放到相邻档位(序数平滑),所以一个拟合得完美的模型,它的 sigma 也不会是 0,而是大概 sqrt(eps)。如果不把这个地板修正掉,一个三档的 score 问题的置信度上限会被卡在 0.68,也就是「0.9 以上直接自动执行」这一档在结构上永远到不了。

这是那种只有真跑过、真被卡过才会发现的问题。文档把它写出来了。

训练里那条最反直觉的细则

训练目标有三部分。对金标签的交叉熵(score 目标有 10% 质量挪到相邻档)、对冻结躯干自身读出头的 KL(权重 0.3,让模型留在预训练已知的地方)、以及对一个冻结教师模型的 KL(权重 1.0,只在多步推理的行上)。教师是 Qwen3.5-4B,不做任何训练直接读,而且只在它确实比学生强的行上用。短分类上它并不更强,蒸馏那里「moved nothing」。

但文档说,有一条细节比任何一个损失项都重要:

每个样本、每个 epoch,选项顺序都重新打乱,标签跟着重映射。

不这么做,读出头会悄悄地记住「slot 0 倾向于正类」,通用性就没了。有了这一条,能在梯度下降里活下来的唯一策略就是真去读选项文本。打分题是例外,因为它的槽位是有序的,所以只做整体反转,不做置换。

他们自己承认的部分:新版本在没见过的短分类上不如老版本

这部分我建议所有做模型的人看一遍。

evaluation/results.md 里有一张泛化表,列的是「在训练混合里」和「四个从未训练过的任务」上的准确率。总体一行是这样:

版本 训练混合内 从未见过
v7 0.866 0.653
v14 0.888 0.637
v16 – 0.629
v17 – 0.640
v18 – 0.641
v19 – 0.647

文档自己的结论写得很直白:v14 在训练混合上每一个原语都比 v7 强,但「none of v14 and v16 to v19 generalises to unseen classification tasks better than v7 overall」。连续五个版本,在没见过的短分类任务上,全都没有超过最早的 v7。它们的增益落在长文档和多步推理上,不在短分类上。

同一份文件里,校准那一节也自己划了边界:「These bands are measured on short classification, and that is the only place they hold.」在长文档和多步判断上,v19 是过度不自信的,说 0.64 的时候实际对 0.83,说 0.40 到 0.51 的时候实际对 0.72 到 0.86。而且重拟合温度会把短分类搞坏,「no single temperature per primitive serves both」。

我先说一句我的看法:一个模型发布方主动把「新版本在某一类任务上不如三代前」写进结果文件,这件事本身比它的准确率数字更值得注意。我不知道有几个团队会这么写。

那笔 98% 的账,我拆了一遍

现在进正题。

11 月 9 日 Strands 官方博客登了一篇两位亚马逊工程师(Carr 和 Mehul)的案例。他们在一个内部工单系统里,把 Claude Sonnet 4.6 换成了 Strands Decider 的 E4B(Gemma 4 底座那一版)。标题里的数字是「98% less cost」,正文说「performed just as well」,并称一年能省下数百万美元的 token 成本。工单有 20 个分类,量极大。

官方给的原始数字我照抄在这里:

Claude Sonnet 4.6 Strands Decider E4B
每次调用平均耗时 4,215 ms 897 ms
每单输入 token 6,554 2,009
每单输出 token 170 1
单价 $3.00/M 输入,$15.00/M 输出 $1.408/实例小时(ml.g5.xlarge)
每单成本 $0.0222 约 $0.0005
每百万单 约 $22,200 约 $500

22,200 除以 500 是 44.4,所以是「about 44 times cheaper」;再换成降幅就是 97.7%,写成了 98%。这些数字我复算过,都能对上。

下面是我的拆分。这几步来源里没有,是我自己算的,读者可以自己核。

第一刀:Sonnet 的钱几乎全花在输入上

$0.0222 拆开看:

  • 输入:6,554 × $3.00/M = $0.019662
  • 输出:170 × $15.00/M = $0.00255

输入占 88.5%,输出占 11.5%。

这个比例很重要。那篇文章把「输出从 170 token 掉到 1 token」放在了很显眼的位置(表格里唯一没填百分比改进的就是输出行),可这一项在 Sonnet 的账单里只值 11.5%。真要动 Sonnet 的成本,动的是输入,不是输出。

第二刀:输入砍下来这件事,Sonnet 自己也能吃到

输入从 6,554 掉到 2,009,少了 69%。为什么掉的?文章自己交代了原因:生产的标签描述本身超过了 Decider 的 4,096 token 窗口,所以他们把 20 个标签每个重写成一句到两句的判据,再加一个「none」选项,作为一个单选的选项发过去。长工单还会丢掉最旧的评论来塞进窗口。

省下来的这些 token 跟模型无关,是他们把题面重写短了。

那如果同样的短题面喂给 Sonnet 呢?

2,009 × $3.00/M = $0.006027

  • 170 × $15.00/M = $0.00255 = $0.008577 每单,也就是 $8,577 每百万单

比原来的 $22,200 便宜了 2.59 倍。这 2.59 倍跟决策模型一点关系都没有,任何人只要把标签描述写短一点就能拿到。

第三刀:乘起来看,这个 98% 是怎么分配的

总倍数 44.4 = 2.59(题面重写)× 17.15(换成决策模型及其计费口径)。

换成对数来分摊:log(44.4) = 3.793,其中 log(2.59) = 0.952 占 25.1%,log(17.15) = 2.842 占 74.9%。

所以那 98% 里大约四分之一来自把提示词改短,这一份 Sonnet 也能拿;剩下约四分之三来自换模型和换计费口径。

这不影响「该换」的结论,但它影响你抄的时候抄什么。如果你照着这个案例去立项,你真正拿到的是那份标签重写,而那个不需要决策模型。

第四刀:44 倍是吞吐曲线上的一个点,不是常数

这一点文章一个字没提,但它决定了那个 44 倍能不能搬走。

决策模型的每单成本算的是除法:$1.408 × 实例小时 ÷ 处理单数,它不是一个模型的固有属性。

在 Sonnet 的 $0.0222/单 面前,盈亏平衡点是每实例每小时 63 单(1.408 ÷ 0.0222 = 63.4)。按他们自己测到的 897 ms 一次调用,一台实例的容量是每小时 4,013 单,也就是只要跑到单机容量的 1.6%,自托管就比 Sonnet 便宜。

所以对这个用例,结论是稳的。量足够大,怎么算都赢。

反过来说:同一个模型、同一套代码,把量降到每小时 63 单,那个 44 倍就变成 1 倍。那篇文章给的是一个吞吐极高的工单系统的快照,也就是曲线上的一个点。你要是把「省 98%」当成决策模型的固有属性搬到自己的低流量场景里,会翻车。

第五刀:他们自己的两个时间数字对不上

回头看那张表,E4B 每次调用平均 897 ms。

再看成本那一节:「At about 640 ms per request, one instance can handle about 5,600 tickets an hour, which would be $250 per million tickets at full use.」

5,625 单/小时 × 640 ms 确实对得上。但他们量到的平均值是 897 ms,不是 640 ms。

按 897 ms 重算:3600 ÷ 0.897 = 4,013 单/小时,$1.408 ÷ 4,013 × 100 万 = $351 每百万单。

所以那个「满负荷下的 $250 每百万」的理论地板,按他们自己测到的延迟应该是 $351。而他们实际用的口径是 $500,比这个地板高了 42%。

我不是说他们在粉饰,留给突发流量余量是正常做法,文章也写明了「We keep headroom for bursts」。但这两个数字(897 和 640)出现在同一篇文章的成本推导里,没有一句话解释为什么算钱的时候用快的那个。

第六刀:98% 之外还有哪些没算进去

文章自己交代了一句:「These are published list prices. They leave out the Lambda, which also runs for less time per ticket now.」这是个诚实的补充,方向也是对省钱有利。

除此之外还有几块不在这个数里:

  • Lambda 之外的那一圈,SQS、SageMaker endpoint 的常驻、AppConfig、CloudWatch、S3、CDK 栈,单看都不大,但方向和多寡文章没说。
  • 把它跑起来的人力成本,一整套 SageMaker endpoint 加自动扩缩、一套带 Beta/Gamma/Prod 三阶段的 CDK、把 20 个标签重写、还有那套三路开关(下面讲)。这些不便宜,而且不换算成百分比就看不见。
  • Bedrock 的企业价格。对照用的是「Bedrock, global endpoint」的公开目录价。真实企业拿到的可能是预留吞吐或承诺折扣,那样 Sonnet 那一侧会便宜下来,差距会缩小多少不知道。

我算完这一圈,对那篇文章的整体判断是:数字没有造假,但标题里的 98% 是一堆不同性质的收益被加总后的口径,其中只有一部分是决策模型带来的。

倒是文章里有一套做法我觉得比 98% 更值得抄,下面单独说。

被埋在括号里的那个数

文章中间有这么一句,夹在一段话的末尾:

One thing that surprised us was that JSON key order changed the answer. Sending the same content with keys in a different order moved the output probabilities by up to 0.19. We now send every request with sorted keys.

键的顺序换一下,输出概率最多动 0.19。

这句话被放在了一个不显眼的位置,但它是整篇文章里最重的一句话。因为 0.19 不是个小数字,它是驱动那个 0.9 阈值的东西。概率动 0.19,意味着同一个请求,换个 key 顺序,可能就从「直接执行」跳到「送人工复核」。

现在把它和仓库里另外两个数并排看:

  • 发布检查里有一条设备一致性。同一个 checkpoint,CPU 和 CUDA 上「probabilities within 0.006」,而且在 231 个任务上答案完全相同;和训练时的 A100 硬件比,231 个任务里 230 个答案一致,剩下那一个还是接近平局(0.346 对 0.345)。
  • 架构文档里量了选项顺序的敏感性,约 0.016。

于是就有了这个对比:

扰动来源 概率偏移
换硬件(CPU ↔ CUDA) 0.006
反转选项列表 ~0.016
换 JSON 键序 0.19

按倍数看,键序的影响是硬件差异的约 32 倍,是选项顺序的约 12 倍。

一个把「同一个输入在不同芯片上答案一致到 0.006」写进发布检查的团队,没有把「同一个输入换一种序列化方式」写进同一个检查。而部署方在真实系统里撞上的,恰好是后者。

我没有资格替他们判断这是不是疏漏,键序影响的是 prompt 渲染,属于调用方那一侧的输入构造。但作为一个读者,我认为这里藏着一条比「省 98%」更有用的经验:你在给一个概率驱动的阈值系统做确定性工程时,要先把输入的所有变换都编目。硬件、选项顺序、键序、空白、换行,全都算变换。只对其中一类做了测试,剩下那些就是没有护栏的。

4,096 是一道硬墙,而且没有旋钮

前面说到亚马逊那个团队必须把标签描述重写成一两句话,因为生产的描述本身就超过窗口了。这个窗口有多硬,仓库里写得很清楚。

training/AGENTS.md 里那句话是:「The recipe saves a 4096-token window and strands-decider serve has no flag to change it.」

没有 flag 可以改。4,096 就是服务端的天花板,想用更长的上下文得去改代码。

这解释了那个团队的处境。重写标签并不是他们的主动选择,窗口摆在那里,不改就跑不起来。而这件事对用户的影响比看起来大。如果你的标签体系本身就依赖长描述(比如每个类目要写清楚边界和排除条件),那这个模型现在装不下。文章自己的结论部分也把这条列为不适用的场景之一:「if your labels need explanation text… a decision model probably can’t replace the LLM’s job」。

仓库在窗口处理上有个讲究,值得单独提。超窗的行是丢弃,不是截断。文档的理由是,如果 prompt 从选项列表中间被切断,某个选项就会从邻居的表示里被打分。所以训练和评估直接丢掉超窗的行;服务时先保留问题的 token,再从前部切掉状态(infer.py 里的 _fit)。这跟亚马逊那篇里的「drops the oldest comments to fit」是一致的。

那套三路开关,我认为才是这篇的真正干货

98% 会被记住,三路开关会被忽略,但从工程上讲后者更有用。

他们没有直接切换。标签的 Lambda 每收到一条消息,都从 AWS AppConfig 读一个三值开关:

  • BEDROCK:Sonnet 给每张单打标,Decider 不被调用
  • DUAL:两个模型并行调用,Sonnet 的结果照常生效,同时和 Decider 的结果对比
  • DECIDER:Decider 给每张单打标,Bedrock 不被调用

先在真实流量上并行跑,然后逐条判分歧,最后才切。文章里两句话我觉得该抄下来。一句是「Running both on real traffic, then judging every disagreement, told us more than any offline eval would have」,在真实流量上并行跑再逐条判分歧,比任何离线评测告诉他们的都多。另一句是回滚是一个 45 秒的配置修改。

这也顺手解释了为什么那套栈里东西那么多:SQS 队列、Lambda、SageMaker endpoint、AppConfig、CloudWatch、CDK 三阶段。复杂度花在了能回滚上,而不是花在省钱上。我认为这笔投入是值的,而且它不依赖你用什么模型。

想自己跑一遍的话,门槛在哪

我把仓库量了一遍,顺手记几个数,方便判断要不要动手。

推理包 src/ 一共 3,532 行 Python,测试 5,063 行,示例 363 行;整仓 334 个文件,其中 121 个 .py。Apache-2.0,权重在 HuggingFace 上(StrandsAgents/ 和 amazon/ 两边都放了同一份),训练数据、生成脚本、preregistration 全在仓库里。

训练侧的实际开销文档写得很具体:v19 是 1.9B 躯干,12.4 GiB 峰值,单张 RTX 3090 在 WSL2 下跑 123k 行一个 epoch 约 6 小时,加上它父代的约 5 小时。底座权重下载约 4.5 GB。这个门槛对个人研究者是够得着的。

有两个地方让我有点意外。一是 skills/ 目录里直接放了两个 Agent Skills(benchmark-decider 和 customize-decider),带脚本和 references,等于把「怎么评测」和「怎么定制」都封装成技能交出去了。二是 research/preregistrations/ 里从 v9 到 v20 每个版本都有一份预注册文件,预测在跑之前就固定下来,结果事后追加。这在模型发布里不常见。

至于要不要用,结合它自己写的适用条件和那份泛化表,我的判断是这样。

适合的场景是选项封闭(标签集稳定、不需要解释文本)、量大、老路径的延迟真的影响体验。不适合的场景是标签需要长长地解释边界、类目每周都变、或者有用的上下文塞不进几千个 token。

另外那个 score 原语要留个心眼。它的从未见过任务的准确率在 0.43 到 0.50 之间(v14 是 0.432,v19 是 0.499),比 choice 的 0.73 低一大截。让一个 2B 模型给主观的东西打有序分,现在还不是它的强项。

我的结论

那篇 98% 的文章,我认为它写得比大部分厂商案例更诚实。它给了完整的分项表,主动说了「标题数字依赖利用率」,主动说了标签描述超过窗口所以要重写,主动把 JSON 键序那个坑写了出来,还在最后清楚地列了不适用场景。

但它把一堆不同性质的收益合成了一个 98%。拆开之后,这个数里约四分之一来自把题面写短(Sonnet 也能拿),剩下的主要来自把按 token 计费换成按实例小时计费,然后靠极大的吞吐把固定成本摊薄。决策模型在这个案例里是省钱的必要条件,不是充分条件。少了那个量,44 倍会掉到 1 倍。

而真正能搬走的东西是另外两样:那套能 45 秒回滚的三路开关,和那个被埋在括号里的 0.19。前者是方法,后者是教训。

相关阅读:《TypeSafe AI Jev 深度解析》那篇讲的是这个范式的起点,《微软也下场做决策模型了》那篇讲的是这个品类怎么被大厂接手。

By AI博士 万戈