80 分钟
0 次观看

11|机制实验与完整源码

导读 · 上一篇:前沿型号的公开证据

用固定输入做一次可定位的改动,看哪些输出该变、哪些不该变。本页把“跑得通”推进到独立计算路径和故障注入。

图 1|只修改未来两项,前三项输出保持不变(原创教学示意)

图 1|只修改未来两项,前三项输出保持不变(原创教学示意)

阅读路线:先确认输入、断言与结果记录的关系,再依次检查监督位置、attention 与缓存,最后检查偏好目标、组内优势和概率守恒。只需使用源码时可跳到第五章,阅读机制时按 1→4 顺序进行。

本页目录:1. 入口、环境与证据边界 · 2. 损失与监督边界 · 3. Attention 与缓存状态 · 4. 训练目标与概率守恒 · 5. 结果与完整源码

前面各篇把模型训练拆成了数据、概率、梯度与推理状态。这一篇让它们落到同一套可以独立运行的实验中:不下载权重,不调用模型服务,只用 NumPy 检验八个容易说错的机制。完整源码在文末,保存为 mechanism_labs.py 即可运行。实验实际执行于 2026 年 10 月 8 日,运行环境为 Python 3.12.14、NumPy 2.3.5、Windows 11,数组采用 float64。

实验对象是机制,不是模型能力。 随机初始化的微型 decoder 可以证明同一计算的两种写法等价,不能证明它会写代码、推理或回答问题。本篇没有完成预训练、RLHF 或 GRPO 训练,也没有测量商业模型的吞吐。前文中的教学代码片段应分别理解;本篇实际运行的是这里给出的 NumPy 源码。

1. 入口、环境与证据边界

1.1 从输入、断言到可复查结果

代码有三层:稳定的 softmax、交叉熵、attention 等基础函数;调用这些函数的八个实验;汇总环境与结果的 JSON 报告。每个实验先构造可解释的输入,再比较数学上应相等的结果,最后返回实际误差。断言失败会中断程序,只有八组全部通过才会输出 status: passed。

教学示意图 11-1

图 2|检查失败会停止程序;通过记录同时保留环境、输入和误差。

在已有 Python 的环境中安装依赖并运行:

python -m pip install numpy==2.3.5
python mechanism_labs.py --output mechanism-results.json --samples 200000

此次执行使用已有的 NumPy,没有临时安装包。--output 指定报告位置,--samples 只控制 speculative sampling 的蒙特卡洛次数。默认结果写入脚本上一级目录的 qa/mechanism-results.json,复制单文件运行时建议显式传入输出位置。代码不访问网络,也没有需要准备的训练集。

1.2 原留存运行记录与数值容差

以下环境、终端输出、误差和抽样频率保留自 2026-10-08 的原实验记录。2026-10-19 又在 Python 3.12.14、NumPy 2.3.5、Windows 11、float64 下运行了同一份源码,八组实验全部通过。此次两层 KV cache 的最大 logit 误差为 4.441×10−164.441\times10^{-16},原记录为 6.661×10−166.661\times10^{-16},均远低于 10−1210^{-12} 的断言容差;其余原文显示精度下的数值与抽样频率一致。下面保留原运行日志,便于区分两次记录。

实际终端输出中的关键内容为:

PASS: 8 mechanism labs
CE finite-difference error: 6.189e-12
Cache logit max error: 6.661e-16
Speculative proportions: [0.599475, 0.300205, 0.10032]

每个数值断言都有容差。浮点数经过不同矩阵运算路径,最后几位可能不同,因此“相同”通常表示数值等价,不要求二进制逐位相同。NumPy 的 assert_allclose 用绝对与相对容差共同判断,具体语义见其官方文档。固定种子有助于重现输入,但不同 NumPy 版本、平台或底层线性代数库仍可能改变最后几位。

2. 损失与监督边界

2.1 交叉熵梯度与有限差分

图 3|解析梯度与损失扰动的独立检查路径

图 3|解析梯度与损失扰动的独立检查路径

图中右侧曲线改变目标 logit,绿色点对应原输入;局部斜率为 −0.75。数值检查分别构造正负扰动输入,再由两次损失差估计导数。

第一个实验的词表只有三个 token,logits 是 [ln 2, 0, 0],目标 token 的下标是 2。softmax 后概率为 [0.5, 0.25, 0.25],所以交叉熵为 −ln 0.25 = ln 4,对三个 logits 的梯度分别为 [0.5, 0.25, −0.75]。这也是第 03 篇的手算例。

检查梯度时,程序没有再写一遍解析公式,而是对每个 logit 做微小扰动,通过损失差分估计导数:

ginum=L(z+hei)−L(z−hei)2h.g_i^{\mathrm{num}}=\frac{L(z+h e_i)-L(z-h e_i)}{2h}.
本篇数值检查的符号、形状与单位
符号含义形状或单位
zz预测 logitsN×VN\times V,无量纲
LL有效目标位置的平均负对数似然标量,nat/token
eie_i仅第 ii 个元素为 1 的扰动方向与 zz 同形状
hh中心差分步长,本实验为 10−510^{-5}与 logit 同单位
gnumg^{\mathrm{num}}数值梯度与 zz 同形状
H,T,DhH,T,D_hattention 头数、序列长度、每头宽度正整数
p,qp,q目标分布、草稿或参考分布长度 VV,概率和为 1
AiA_i第 ii 个回答的组内优势标量,无量纲
αi\alpha_i草稿 token ii 的接受概率标量,范围 [0,1][0,1]

中心差分在函数足够光滑时的截断误差随 h2h^2 缩小,但过小的步长又会使两个接近的浮点数相减,放大舍入误差。因此不应把 hh 无止境缩小。这次采用 float64 与 10−510^{-5},解析梯度和数值梯度的最大绝对误差为 6.189×10−126.189\times10^{-12},低于断言使用的绝对容差 10−910^{-9}。

这项检查能发现常见错误:忘记减去目标 one-hot、平均分母错误、softmax 轴错误。它检查的是对 logits 的导数;并未检查一个完整网络每层的参数梯度。脚本没有自动微分引擎,也没有实现 decoder 的反向传播。

2.2 三种 mask 管三个不同的边界

第二组实验同时检查 loss mask、文档 attention mask 与 SFT 的目标位置偏移。它们都常被写成布尔数组,但决定的事情不同。

2.2.1 Loss mask:监督哪些预测位置

图 4|被忽略预测行的直接 logit 梯度为零

图 4|被忽略预测行的直接 logit 梯度为零

图中第二行变灰,表示这一行的预测不进入损失;分母只统计另外两行。

先看 loss mask。三行 logits 中,第二行不参与监督,损失只平均第一行与第三行。程序把第二行的 logits 改成 [100, −100, 0, 0],损失仍然相同;这一行的直接 logit 梯度严格为零。有效位置的梯度则被有效目标数 2 归一化,而不是被全部位置数 3 归一化。

用户 token 不计入损失,不等于用户内容没有影响梯度。 在真实 decoder 中,assistant 的预测依赖用户上下文,损失仍会沿 attention 与残差路径传回用户位置的隐藏状态。这里证明的仅是被忽略预测位置的直接 logit 梯度为零。

2.2.2 Attention mask:独立文档之间不读取

图 5|两个独立文档的因果块对角可见关系

图 5|两个独立文档的因果块对角可见关系

图中两个蓝色三角属于两个文档;位置 4 只保留自身蓝格,不能穿过文档边界读位置 0—3。

再把两个四 token 文档放在同一行,文档编号为 [0,0,0,0,1,1,1,1]。如果它们是独立样本,那么 attention 必须同时满足“key 位置不晚于 query”和“两个位置来自同一文档”。第二个文档的第一个 token 只能看见自己,不能读取前面四个 token。边界处第一个文档末尾预测第二个文档开头的目标也被忽略,七个预测位置的有效 mask 是:

[1, 1, 1, 0, 1, 1, 1]

这是本实验选择的独立文档 packing 语义。有些实际预训练流程允许拼接文档互相可见,两种方式不能仅凭存在 EOS 来互相推断。EOS 是一个 token;硬隔离由 attention mask 决定。

2.2.3 SFT 目标 mask:监督位置前移一格

图 6|assistant 目标与预测它的 logits 相差一格

图 6|assistant 目标与预测它的 logits 相差一格

图中的箭头从下排预测位置指向上排目标 token;绿色目标 4、5、6,对应蓝色预测位置 3、4、5。

SFT 的检查则针对最容易出现的偏移:原序列位置 4、5、6 属于 assistant 目标,对应预测它们的 logits 位于 3、4、5。程序对原目标 mask 取 [1:],结果确实为 3、4、5。如果把原 mask 直接贴到已经移位的 logits 上,就会监督错位置。

3. Attention 与缓存状态

3.1 缩放点积的数值路径

图 7|两组 key/value 的权重与加权输出

图 7|两组 key/value 的权重与加权输出

图中 score 经 softmax 变成两个权重;每个权重只与对应 value 相乘,然后相加。

第三组实验先复现 attention 的手算:query 是 [1,0],两个 key 是 [1,0] 与 [0,1],value 是 [2,0] 与 [0,4]。缩放点积除以 2\sqrt2 后,权重约为 [0.669762,0.330238],输出约为 [1.339523,1.320954]。

3.2 因果方向与未来扰动

首屏示意图固定前三项,只修改未来两项;虚线是观察边界,图只画出部分允许的因果连接,实际矩阵方向由本实验检查。

随后程序构造五位置 attention,把未来位置 3、4 的输入加上 100,前面三个位置的输出变化为零;上三角未来位置权重也严格为零。这个测试验证 causal mask 的方向:如果误把下三角写成上三角,扰动未来输入就会泄漏到前面的输出。

3.3 两层 decoder 的三条等价前向路径

图 8|整段、增长前缀与缓存路径比较同一组词表 logits

图 8|整段、增长前缀与缓存路径比较同一组词表 logits

图中三行都对应七个固定位置,比较的是每个位置的 17 维输出,不是三次独立随机生成的答案。

第四组把检查推进到完整的两层微型 decoder。它包含独立的层参数、RMSNorm、两头 MHA、逐对 RoPE、SwiGLU、残差连接与词表输出投影。参数随机初始化,词表 17、宽度 8、每头宽度 4、FFN 中间宽度 12。它是教学结构,不代表任何未公开商业型号的实际架构。

同一串七个 token 分别走三条计算路径:一次输入全部 token;对每个增长中的前缀重新计算;每次只输入一个新 token 并读取每层 KV cache。比较的是所有位置的词表 logits:

Δcache=max⁡t,v∣zt,vfull−zt,vcache∣.\Delta_{\mathrm{cache}}=\max_{t,v}\left|z^{\mathrm{full}}_{t,v}-z^{\mathrm{cache}}_{t,v}\right|.

教学示意图 11-2

图 9|两层各维护自己的 cache;三条路径使用相同位置与参数(原有教学图)。

实测整段与 cache 的最大误差为 6.661×10−166.661\times10^{-16};逐前缀重新计算与 cache 的误差同样如此。两层最终的 K 与 V 各自形状都是 [2,7,4],含义是 [头数,已有 token 数,每头宽度]。程序另外确认两层的 K 不相同,避免“缓存一个数组给所有层共用”这样的伪实现通过简单检查。

缓存路径中新 token 的 RoPE 位置必须使用全局位置,而不是每次都从零开始。当前 query 是已有序列的最后一个位置,因而可以读全部缓存 key;它的 attention mask 是 [1,T] 的全真矩阵。若机械地对这个长方形矩阵使用从左上角开始的下三角,最后一个 query 反而只能读第一个 key。

cache 的核心性质是复用过去已经算出的 K/V,并保持下一步 logits 数值等价。 这里没有 dropout,没有量化,没有训练时更新权重,也没有修改历史上下文。测试没有实现 paged attention、GQA 或跨请求 cache 管理,更没有展示速度提升;这些工程性质需要另外的实验。

3.4 故障注入:形状全对,为什么logits仍然错误

图 10|三种语义错误可以保留同样的七位置缓存(原创教学示意)

图 10|三种语义错误可以保留同样的七位置缓存(原创教学示意)

先比较图中每一排的长度:它们都保留七个位置。第二排重置的是位置编号,历史仍允许被读取;第三排把第一层的K、V分别错复制给第二层;最后一排才是把长方形mask误写成左上角下三角,只让当前query读第一项。它们破坏了三个不同的约定,不能只靠 shape == [2,7,4] 判断正确。

为检验这些区别,补充实验直接导入本页原样的 mechanism_labs.py,使用相同七个token、同一随机参数和float64。基准输出先由正确的 model.full(ids) 保存;再在逐token路径中一次只注入一种错误。原始完整脚本不改,实验之后也恢复被临时替换的函数。

逐token实现两层缓存形状最大logit绝对误差
正确实现均为 [2,7,4]4.441×10−164.441\times10^{-16}
每步RoPE位置归零均为 [2,7,4]0.247728
把Cache1复制给Cache2均为 [2,7,4]0.944921
对 [1,T] 用左上角下三角均为 [2,7,4]1.551164

这不是把“错误程序崩溃”当作检查。三条错误路径都能完成前向,且都通过形状检查;它们却全部不能通过 atol=rtol=1e-12 的同输入logits比较。逐项观察错误,就能知道该检查哪条不变量:

  1. 位置归零改变旋转关系。 cache长度仍随 position 增长,拼接也正常;但新Q/K的RoPE角度没有使用真实全局位置。没有上三角泄露,也不代表位置语义正确。
  2. 串层复制改变值的来源。 第二层使用了第一层隐藏状态投影出的历史。它们形状相同,不代表含义相同。原脚本的“两层K不能全部相同”检查能捕获这次特定故障;一般情况下,跨路径logits比较仍是必要的独立证据。
  3. 长矩形mask改变可见集合。 np.tril(np.ones((1,T)))只有第一个位置为真。当前query明明位于真实序列末尾,却被当成矩阵第一行的时间起点。源代码的全真 [1,T] 在“只有一个最新query、无padding”的条件下才是正确选择。

补充实验于2026-10-19在Python 3.12.14、NumPy 2.3.5、Windows11、float64下实际执行;这些误差只属于这套固定微型模型。它们没有测量真实模型质量,也不否定第10篇那种明确定义了共享结构的CSA2:故障是违反当前独立层配置,而不是“任何共享都错误”。

保留三个检查层次很有用:shape检查对象能否接上;位置、mask、所有权检查它表示什么;同输入下的独立计算路径检查组合后是否仍等价。单个通过信号不能替代另外两个。

4. 训练目标与概率守恒

4.1 DPO 的局部梯度与相对关系

图 11|两个回答都降概率,差值与 DPO 目标仍可能改善

图 11|两个回答都降概率,差值与 DPO 目标仍可能改善

图中绿点和红点都向更小的 log probability 移动,红点移动更远,所以两点间距增大。这个反例针对目标函数的局部输入,不表示真实网络参数会按两条独立旋钮更新。

第五组实验把 DPO 的四个序列 log probability 当作输入:当前策略胜答与败答为 −2、−3,参考策略二者均为 −2.5,β=0.2\beta=0.2。因此 preference margin 为 0.20.2,损失为 log⁡(1+e−0.2)≈0.598139\log(1+e^{-0.2})\approx0.598139。解析梯度对胜答 log probability 为 −0.0900332,对败答为 +0.0900332,与中心差分的最大误差为 1.848×10−121.848\times10^{-12}。

沿这两个输入的负梯度走一步,损失下降到 0.596519。再构造一个容易忽略的反例:把胜答 log probability 减少 1,败答减少 2,两者概率都下降,但相对 margin 增大,DPO 损失仍下降到 0.513015。它说明偏好对目标主要约束相对关系。

这里把两个 log probability 看成局部独立变量,方便检查第 04 篇推导的表达式。真实训练中的 log probability 来自共享网络、归一化词表概率与整段序列求和,不能把这个两变量下降步骤视为网络参数更新。DPO 的推导与训练目标见原论文。

4.2 GRPO 的组内优势

图 12|同一奖励组在两种标准差约定下的优势

图 12|同一奖励组在两种标准差约定下的优势

先观察奖励到组均值的差,再看分母的标准差;更改 ddof 就改变优势幅度,不能在复现时省略这个约定。

第六组检查 GRPO 的组内归一化。对同一问题采样四个回答,奖励为 [0,0,1,1],均值为 0.5,总体标准差为 0.5,于是:

Ai=ri−rˉs+ε.A_i=\frac{r_i-\bar r}{s+\varepsilon}.

本实验采用总体标准差 ddof=0、ε=10−8\varepsilon=10^{-8},优势约为 [−1,−1,1,1]。若改成样本标准差 ddof=1,同一组优势约为 [−0.866025,−0.866025,0.866025,0.866025]。两种标准差都存在于软件接口与算法实现中,复现时必须写明选择,NumPy 的定义见std 文档。

所有回答都得 1 或都得 0 时,组内差值全为零,归一化优势也全为零。这说明单靠这一项信号,整组全对与整组全错都没有相对排序梯度;有 KL、熵或其他辅助项的训练仍可能产生更新。本实验还验证了打乱回答顺序只会同样打乱优势,不改变其数值。

4.2.1 Clipped 项的正负优势

图 13|四个固定样本逐项比较未裁剪与裁剪目标

图 13|四个固定样本逐项比较未裁剪与裁剪目标

图中最后一行分别从上面两行取较小值;第二列和第三列显示为什么“只裁剪 ratio”不是同一个操作。

PPO 风格 clipped 项也必须考虑优势正负。取 ratio [1.5,1.5,0.5,0.5]、优势 [2,−2,2,−2]、clip 范围 [0.8,1.2],逐元素计算 min(ratio*A, clip(ratio)*A),得到 [2.4,−3,1,−1.6]。不能只把 ratio 裁剪后乘优势,这会改变保守目标在负优势时的行为。GRPO 的源头可查DeepSeekMath,clipped surrogate 可查PPO 原论文。代码只检查冻结的数值优势与目标,没有 rollout 或 actor 训练循环。

4.3 完整离散分布上的 KL

图 14|相同两组分布,KL 的两个方向不同

图 14|相同两组分布,KL 的两个方向不同

图中蓝条与橙条具有不同的概率质量;交换方向时,参与平均的权重也交换,结果不会由距离对称性保证相同。

第七组对完整的小词表分布直接求 KL。当 p=[0.8,0.2]p=[0.8,0.2]、q=[0.5,0.5]q=[0.5,0.5] 时,DKL(p∥q)=0.192745D_{\mathrm{KL}}(p\|q)=0.192745 nat,反向为 0.2231440.223144 nat,二者不同。相同分布的 KL 为零;若 pp 某项为正而 qq 同项为零,前向 KL 为无穷大。真实大词表训练常使用采样估计,本实验没有把单次 log ratio 误称为总是非负的 KL。

4.4 Speculative sampling 的概率守恒

4.4.1 接受与残差的精确质量分解

图 15|接受质量与拒绝补偿恢复目标分布

图 15|接受质量与拒绝补偿恢复目标分布

绿色部分是直接接受的质量,红色部分是拒绝概率;补偿只流向 target 相对 draft 缺失的第一个 token。

最后一组回答 speculative sampling 最关键的问题:从草稿分布 qq 提议 token,如何保持最终 token 服从目标分布 pp?对提议的 token ii,接受概率为:

αi=min⁡(1,piqi).\alpha_i=\min\left(1,\frac{p_i}{q_i}\right).

当 qi=0q_i=0 时,草稿根本不会提议这一项,代码无需执行这个除法。直接接受路径分配给 token ii 的概率质量为 qiαi=min⁡(pi,qi)q_i\alpha_i=\min(p_i,q_i)。剩下的质量必须补给目标分布相对草稿不足的部分:

R=1−∑imin⁡(pi,qi),ui=max⁡(pi−qi,0)∑jmax⁡(pj−qj,0).\begin{aligned} R&=1-\sum_i\min(p_i,q_i),\\ u_i&=\frac{\max(p_i-q_i,0)}{\sum_j\max(p_j-q_j,0)}. \end{aligned}

当 R>0R>0 时,拒绝后从 uu 采样;当两个分布完全相同时,拒绝概率为零,不需要定义可采样的残差分布。最终分布为:

P(output=i)=min⁡(pi,qi)+Rui=pi.\begin{aligned} P(\mathrm{output}=i) &=\min(p_i,q_i)+R u_i\\ &=p_i. \end{aligned}

代码使用 p=[0.6,0.3,0.1]p=[0.6,0.3,0.1]、q=[0.4,0.4,0.2]q=[0.4,0.4,0.2]。接受概率为 [1,0.75,0.5],直接接受质量为 [0.4,0.3,0.1],拒绝概率为 0.2,残差分布为 [1,0,0]。拒绝后全部补到第一项,最终正好回到 [0.6,0.3,0.1],精确枚举的浮点误差为 1.110×10−161.110\times10^{-16}。

4.4.2 有限样本频率与标准误差

图 16|原留存频率偏差在标准误差尺度上的位置

图 16|原留存频率偏差在标准误差尺度上的位置

图中圆点接近零线,横轴是各 token 的标准误差单位。图上界限检查的是有限样本频率,不能代替前面的精确质量证明。

再用种子 73 实际采样 200000 次,观察到 [0.599475,0.300205,0.100320],实际接受率为 0.801205。每个 token 边际频率的标准误差按 pi(1−pi)/n\sqrt{p_i(1-p_i)/n} 估计,最大偏差只有 0.4793 个标准误差,低于代码预先声明的六倍标准误差边界。频率检查是有限样本验证;精确质量分解才解释为什么分布守恒。

拒绝后直接从原目标分布采样,一般会改变输出分布。 在这个例子中,它会得到 [0.52,0.36,0.12],因为直接接受路径已经覆盖了目标的部分质量,剩余部分需要按正残差补偿。这一机制可查Fast Inference from Transformers via Speculative Decoding与Accelerating Large Language Model Decoding with Speculative Sampling。

本实验只验证固定前缀下的一步接受律。完整多 token 算法还需要按前缀逐个接受、首次拒绝后停止草稿分支,以及处理全部接受后的额外 token。若使用 temperature、top-k、top-p,公式里的 pp 与 qq 必须对应实际使用的归一化采样分布;不能一个用截断后分布、另一个用原始概率。这里未实现整套多 token decoder,也未测量其加速收益。

4.4.3 错误补偿:为什么重新从target采样仍会偏离target

图 17|上排只填缺口,下排把概率又分给已经覆盖的项(原创教学示意)

图 17|上排只填缺口,下排把概率又分给已经覆盖的项(原创教学示意)

看上排:青色已接受质量先占据三个容器的一部分,只有第一项尚未到达目标线,琥珀色正残差只补第一项。下排则把拒绝后的质量重新按完整target分给三个容器,第二、三项重复得到本来已经覆盖的质量。容器刻度只作缺口关系示意,精确概率用下面的数值计算,不从插画量高度。

仍取 p=[0.6,0.3,0.1]p=[0.6,0.3,0.1]、q=[0.4,0.4,0.2]q=[0.4,0.4,0.2]。直接接受质量已经是 a=[0.4,0.3,0.1]a=[0.4,0.3,0.1],总拒绝率为0.2。正确残差 u=[1,0,0]u=[1,0,0],因此补偿项为 0.2u=[0.2,0,0]0.2u=[0.2,0,0]。如果错误地让拒绝分支重新从 pp 采样,新增项会变成 0.2p=[0.12,0.06,0.02]0.2p=[0.12,0.06,0.02]:

Pwrong=a+0.2p=[0.52,0.36,0.12].\begin{aligned} P_{\mathrm{wrong}}&=a+0.2p\\ &=[0.52,0.36,0.12]. \end{aligned}

第一项少了0.08,第二项多0.06,第三项多0.02。分布仍然非负、总和仍然为1,所以“概率和等于1”也检查不出这个错误。逐项比较得到 ℓ1\ell_1 距离 0.08+0.06+0.02=0.160.08+0.06+0.02=0.16,总变差距离为0.08;这是系统性偏差,不是200000次采样的随机抖动。

V2补充实验实际枚举了这条错误分支,并断言正确补偿恢复 pp、错误补偿得到上述分布。它还检查 qi=0q_i=0 和相同分布的原有分支,没有改写或删减原脚本的断言。由此可把调试顺序分清:先检验精确概率质量,再做有限样本频率检查;若精确质量已经错了,增加抽样次数只会更精确地看见错误分布。

5. 结果与完整源码

5.1 原留存结果与各自验证范围

2026-10-08原留存八项结果摘要
检查实际结果验证范围
CE 梯度有限差分最大误差 6.189×10−126.189\times10^{-12}对 logits 的导数
Mask 后梯度被忽略行全为零;有效行误差 1.685×10−111.685\times10^{-11}loss mask、packing 边界、SFT 偏移
因果 attention未来扰动引起的前缀变化为 0因果方向与归一化
两层 KV cache最大 logit 误差 6.661×10−166.661\times10^{-16}固定权重的前向等价
DPO 局部梯度最大误差 1.848×10−121.848\times10^{-12}四个 log probability 的目标表达式
GRPO 组内优势混合组约为 ±1\pm1,同奖组为 0标准差约定、排列等价、clip 符号
KL前向 0.192745、反向 0.223144 nat完整离散分布与零支持
Speculative sampling精确误差 1.110×10−161.110\times10^{-16},200000 次抽样通过单前缀接受律与残差分布

5.2 调用路线、接口与完整文件

完整代码使用固定种子,不需要前文 PyTorch 片段。展开后复制保存即可;后续修改时,应保留独立比较路径和断言,避免把“实际输出”写死成“正确答案”。若想继续扩展,优先验证一个明确问题,例如 GQA 的 cache 头数、跨文档 mask 或量化后的容差,再根据结果判断是否需要扩大实验。

完整 NumPy 实验源码:mechanism_labs.py
python383 行
"""Deterministic NumPy mechanism labs, not a production LLM trainer.

Run: python mechanism_labs.py --output ../qa/mechanism-results.json
All numerical equivalence checks use fixed inputs, float64 and explicit tolerances.
"""

from __future__ import annotations

import argparse
from datetime import datetime, timezone, timedelta
import json
from pathlib import Path
import platform
import sys
import time

import numpy as np


def softmax(x: np.ndarray, axis: int = -1) -> np.ndarray:
    shifted = x - np.max(x, axis=axis, keepdims=True)
    exp = np.exp(shifted)
    return exp / exp.sum(axis=axis, keepdims=True)


def log_softmax(x: np.ndarray) -> np.ndarray:
    shifted = x - np.max(x, axis=-1, keepdims=True)
    return shifted - np.log(np.exp(shifted).sum(axis=-1, keepdims=True))


def finite_diff(fn, x: np.ndarray, eps: float = 1e-5) -> np.ndarray:
    grad = np.empty_like(x, dtype=np.float64)
    for index in np.ndindex(x.shape):
        plus, minus = x.copy(), x.copy()
        plus[index] += eps
        minus[index] -= eps
        grad[index] = (fn(plus) - fn(minus)) / (2 * eps)
    return grad


def masked_ce(logits, targets, valid):
    """[N,V] logits, [N] targets and Boolean valid mask; returns mean and gradient."""
    count = int(valid.sum())
    if count == 0:
        raise ValueError("No supervised targets")
    lp = log_softmax(logits)
    loss = -float(lp[np.arange(len(targets)), targets][valid].mean())
    grad = np.exp(lp)
    grad[np.arange(len(targets)), targets] -= 1
    grad *= valid[:, None] / count
    return loss, grad


def lab_ce_gradient():
    logits = np.array([[np.log(2.0), 0.0, 0.0]], dtype=np.float64)
    loss, gradient = masked_ce(logits, np.array([2]), np.array([True]))
    numeric = finite_diff(lambda z: masked_ce(z, np.array([2]),
                                               np.array([True]))[0], logits)
    error = float(np.max(np.abs(gradient - numeric)))
    np.testing.assert_allclose(gradient, [[0.5, 0.25, -0.75]], atol=1e-14)
    np.testing.assert_allclose(gradient, numeric, atol=1e-9, rtol=1e-8)
    assert np.isclose(loss, np.log(4.0))
    return {"loss_nat": loss, "gradient": gradient.tolist(),
            "finite_difference_step": 1e-5, "max_abs_gradient_error": error,
            "abs_tolerance": 1e-9, "relative_tolerance": 1e-8}


def lab_masks():
    rng = np.random.default_rng(3)
    logits = rng.normal(size=(3, 4))
    targets = np.array([2, 1, 3])
    valid = np.array([True, False, True])
    loss, grad = masked_ce(logits, targets, valid)
    numeric = finite_diff(lambda z: masked_ce(z, targets, valid)[0], logits)
    np.testing.assert_allclose(grad, numeric, atol=1e-9, rtol=1e-8)
    changed = logits.copy()
    changed[1] = [100, -100, 0, 0]
    assert masked_ce(changed, targets, valid)[0] == loss
    np.testing.assert_array_equal(grad[1], np.zeros(4))

    # Two independent four-token documents: A EOS -> B BOS is not supervised.
    document_ids = np.array([0, 0, 0, 0, 1, 1, 1, 1])
    prediction_valid = document_ids[:-1] == document_ids[1:]
    positions = np.arange(8)
    allowed = ((positions[None, :] <= positions[:, None]) &
               (document_ids[:, None] == document_ids[None, :]))
    assert not prediction_valid[3]
    assert allowed[4].sum() == 1
    assert allowed[7].sum() == 4
    assert not allowed[4:, :4].any()

    # Original token positions 4,5,6 are assistant targets; prediction positions 3,4,5.
    assistant_target = np.array([False, False, False, False, True, True, True])
    sft_prediction_mask = assistant_target[1:]
    np.testing.assert_array_equal(np.flatnonzero(sft_prediction_mask), [3, 4, 5])
    return {"masked_loss_nat": loss, "masked_row_gradient": grad[1].tolist(),
            "max_abs_gradient_error": float(np.max(np.abs(grad - numeric))),
            "packing_prediction_mask": prediction_valid.astype(int).tolist(),
            "packing_attention_allowed": allowed.astype(int).tolist(),
            "sft_prediction_positions": np.flatnonzero(sft_prediction_mask).tolist(),
            "masked_logits_perturbation_delta": 0.0}


def attention(q, k, value, allowed):
    """q:[H,Tq,Dh], k/value:[H,Tk,Dh], allowed:[Tq,Tk]."""
    score = q @ k.transpose(0, 2, 1) / np.sqrt(q.shape[-1])
    score = np.where(allowed[None], score, -np.inf)
    if not allowed.any(axis=-1).all():
        raise ValueError("Every query must have an allowed key")
    weights = softmax(score)
    return weights @ value, weights


def lab_attention():
    q = np.array([[[1.0, 0.0]]])
    k = np.array([[[1.0, 0.0], [0.0, 1.0]]])
    value = np.array([[[2.0, 0.0], [0.0, 4.0]]])
    out, weight = attention(q, k, value, np.ones((1, 2), dtype=bool))

    rng = np.random.default_rng(4)
    x = rng.normal(size=(1, 5, 4))
    triangular = np.tril(np.ones((5, 5), dtype=bool))
    full, full_weights = attention(x, x, x, triangular)
    changed = x.copy()
    changed[:, 3:] += 100.0
    perturbed, _ = attention(changed, changed, changed, triangular)
    prefix_error = float(np.max(np.abs(full[:, :3] - perturbed[:, :3])))
    assert prefix_error == 0.0
    assert np.max(np.abs(full_weights[:, np.triu_indices(5, 1)[0],
                                             np.triu_indices(5, 1)[1]])) == 0.0
    np.testing.assert_allclose(full_weights.sum(axis=-1), 1.0, atol=1e-14)
    return {"hand_example_weights": weight[0, 0].tolist(),
            "hand_example_output": out[0, 0].tolist(),
            "future_perturbation_prefix_max_error": prefix_error,
            "upper_triangle_max_weight": 0.0}


def rms_norm(x):
    return x / np.sqrt(np.mean(x * x, axis=-1, keepdims=True) + 1e-6)


def rope(x, positions):
    """x:[H,T,Dh]; positions:[T], Dh even; pairwise RoPE with base 10000."""
    frequency = 10000.0 ** (-np.arange(0, x.shape[-1], 2) / x.shape[-1])
    angles = positions[:, None] * frequency[None, :]
    c, s = np.cos(angles)[None], np.sin(angles)[None]
    result = np.empty_like(x)
    even, odd = x[..., 0::2], x[..., 1::2]
    result[..., 0::2] = even * c - odd * s
    result[..., 1::2] = even * s + odd * c
    return result


class TinyDecoder:
    """Two independent pre-norm layers, MHA, RoPE, SwiGLU; no trained checkpoint."""

    def __init__(self):
        rng = np.random.default_rng(11)
        self.width, self.heads, self.head_dim, self.vocab = 8, 2, 4, 17
        self.embedding = rng.normal(scale=0.3, size=(self.vocab, self.width))
        self.output = rng.normal(scale=0.2, size=(self.width, self.vocab))
        self.layers = []
        for _ in range(2):
            layer = {name: rng.normal(scale=0.2, size=(8, 8))
                     for name in ("q", "k", "v", "o")}
            layer.update({"gate": rng.normal(scale=0.2, size=(8, 12)),
                          "up": rng.normal(scale=0.2, size=(8, 12)),
                          "down": rng.normal(scale=0.2, size=(12, 8))})
            self.layers.append(layer)

    def split_heads(self, x):
        return x.reshape(-1, self.heads, self.head_dim).transpose(1, 0, 2)

    def feed_forward(self, x, layer):
        z = rms_norm(x)
        gate = z @ layer["gate"]
        silu = gate / (1 + np.exp(-gate))
        return x + (silu * (z @ layer["up"])) @ layer["down"]

    def full(self, ids):
        x = self.embedding[ids].copy()
        t = len(ids)
        allowed = np.tril(np.ones((t, t), dtype=bool))
        for layer in self.layers:
            z = rms_norm(x)
            q = rope(self.split_heads(z @ layer["q"]), np.arange(t))
            k = rope(self.split_heads(z @ layer["k"]), np.arange(t))
            v = self.split_heads(z @ layer["v"])
            result, _ = attention(q, k, v, allowed)
            x = x + result.transpose(1, 0, 2).reshape(t, self.width) @ layer["o"]
            x = self.feed_forward(x, layer)
        return rms_norm(x) @ self.output

    def step(self, token, position, caches):
        x = self.embedding[[token]].copy()
        for layer_index, layer in enumerate(self.layers):
            z = rms_norm(x)
            q = rope(self.split_heads(z @ layer["q"]), np.array([position]))
            k_new = rope(self.split_heads(z @ layer["k"]), np.array([position]))
            v_new = self.split_heads(z @ layer["v"])
            cache = caches[layer_index]
            if cache is None:
                k, v = k_new, v_new
            else:
                assert cache[0].shape[1] == position
                k = np.concatenate([cache[0], k_new], axis=1)
                v = np.concatenate([cache[1], v_new], axis=1)
            caches[layer_index] = (k, v)
            # Current query is the LAST position: every existing cached key is allowed.
            result, _ = attention(q, k, v, np.ones((1, position + 1), dtype=bool))
            x = x + result.transpose(1, 0, 2).reshape(1, self.width) @ layer["o"]
            x = self.feed_forward(x, layer)
        return (rms_norm(x) @ self.output)[0]


def lab_cache_equivalence():
    model = TinyDecoder()
    ids = np.array([1, 5, 3, 8, 2, 9, 4])
    full = model.full(ids)
    caches = [None] * len(model.layers)
    cached = np.stack([model.step(int(token), i, caches)
                       for i, token in enumerate(ids)])
    recomputed = np.stack([model.full(ids[:i + 1])[-1] for i in range(len(ids))])
    error = float(np.max(np.abs(full - cached)))
    np.testing.assert_allclose(full, cached, atol=1e-12, rtol=1e-12)
    np.testing.assert_allclose(recomputed, cached, atol=1e-12, rtol=1e-12)
    assert not np.allclose(caches[0][0], caches[1][0])
    changed = ids.copy()
    changed[4:] = [10, 11, 12]
    future_error = float(np.max(np.abs(full[:4] - model.full(changed)[:4])))
    assert future_error == 0.0
    return {"configuration": {"layers": 2, "heads": 2, "head_dim": 4,
                              "width": 8, "ffn_width": 12, "vocab": 17,
                              "tokens": 7, "dtype": "float64",
                              "position": "pairwise RoPE", "ffn": "SwiGLU"},
            "full_vs_cached_max_abs_logit_error": error,
            "prefix_vs_cached_max_abs_logit_error": float(np.max(np.abs(recomputed - cached))),
            "future_perturbation_prefix_max_error": future_error,
            "per_layer_cache_shapes": [list(c[0].shape) for c in caches],
            "abs_tolerance": 1e-12, "relative_tolerance": 1e-12}


def lab_dpo():
    reference = np.array([-2.5, -2.5])
    policy = np.array([-2.0, -3.0])
    beta = 0.2

    def fn(x):
        margin = beta * ((x[0] - reference[0]) - (x[1] - reference[1]))
        return float(np.logaddexp(0, -margin))

    margin = beta * ((policy[0] - reference[0]) - (policy[1] - reference[1]))
    scale = -beta / (1 + np.exp(margin))
    analytic = np.array([scale, -scale])
    numeric = finite_diff(fn, policy)
    np.testing.assert_allclose(analytic, numeric, atol=1e-9, rtol=1e-8)
    improved = fn(policy - 0.1 * analytic)
    assert improved < fn(policy)
    # Counterexample: both answers become less probable, but rejected falls more.
    shifted = policy + [-1.0, -2.0]
    assert fn(shifted) < fn(policy)
    return {"policy_log_probs": policy.tolist(), "reference_log_probs": reference.tolist(),
            "beta": beta, "margin": float(margin), "loss_nat": fn(policy),
            "analytic_gradient": analytic.tolist(),
            "max_abs_gradient_error": float(np.max(np.abs(analytic - numeric))),
            "loss_after_gradient_step": improved,
            "both_probabilities_lower_counterexample_loss": fn(shifted)}


def group_advantage(rewards, ddof=0, eps=1e-8):
    return ((rewards - rewards.mean(axis=-1, keepdims=True)) /
            (rewards.std(axis=-1, keepdims=True, ddof=ddof) + eps))


def lab_grpo():
    rewards = np.array([[0.0, 0.0, 1.0, 1.0], [1.0, 1.0, 1.0, 1.0],
                        [0.0, 0.0, 0.0, 0.0]])
    population = group_advantage(rewards)
    sample = group_advantage(rewards, ddof=1)
    np.testing.assert_allclose(population[0], [-1, -1, 1, 1], atol=3e-8)
    np.testing.assert_array_equal(population[1:], np.zeros((2, 4)))
    order = [3, 0, 2, 1]
    np.testing.assert_allclose(group_advantage(rewards[:, order]), population[:, order])
    advantage = np.array([2.0, -2.0, 2.0, -2.0])
    ratio = np.array([1.5, 1.5, 0.5, 0.5])
    objective = np.minimum(ratio * advantage, np.clip(ratio, 0.8, 1.2) * advantage)
    np.testing.assert_allclose(objective, [2.4, -3.0, 1.0, -1.6])
    return {"rewards": rewards.tolist(), "population_std": rewards.std(axis=-1).tolist(),
            "population_advantage": population.tolist(), "sample_advantage": sample.tolist(),
            "epsilon": 1e-8, "ppo_clip_objectives": objective.tolist(),
            "note": "advantages are frozen numeric values; this is not an actor training loop"}


def kl_divergence(p, q):
    if np.any(p < 0) or np.any(q < 0) or not np.isclose(p.sum(), 1) or not np.isclose(q.sum(), 1):
        raise ValueError("Inputs must be probability distributions")
    positive = p > 0
    if np.any(q[positive] == 0):
        return float("inf")
    return float(np.sum(p[positive] * np.log(p[positive] / q[positive])))


def lab_kl():
    p, q = np.array([0.8, 0.2]), np.array([0.5, 0.5])
    forward, reverse = kl_divergence(p, q), kl_divergence(q, p)
    assert forward >= 0 and reverse >= 0 and not np.isclose(forward, reverse)
    assert kl_divergence(p, p) == 0
    assert np.isinf(kl_divergence(np.array([0.5, 0.5]), np.array([1.0, 0.0])))
    return {"p": p.tolist(), "q": q.tolist(), "forward_kl_nat": forward,
            "reverse_kl_nat": reverse, "identical_kl_nat": 0.0,
            "zero_reference_support": "positive p on zero q gives infinity"}


def speculative_components(p, q):
    # Never divide by q=0: such a token cannot be drawn as a draft candidate.
    alpha = np.ones_like(q)
    np.divide(p, q, out=alpha, where=q > 0)
    alpha = np.minimum(alpha, 1.0)
    accepted_mass = q * alpha
    rejection_mass = float(1 - accepted_mass.sum())
    positive = np.maximum(p - q, 0.0)
    residual = positive / positive.sum() if positive.sum() > 0 else np.zeros_like(p)
    final = accepted_mass + rejection_mass * residual
    return alpha, accepted_mass, rejection_mass, residual, final


def lab_speculative(samples):
    p, q = np.array([0.6, 0.3, 0.1]), np.array([0.4, 0.4, 0.2])
    alpha, accepted, rejection, residual, final = speculative_components(p, q)
    np.testing.assert_allclose(final, p, atol=1e-14)
    # Support differences and equality exercise the zero-denominator branches.
    for target, draft in [(p, p), (np.array([0.0, 0.7, 0.3]), np.array([0.3, 0.7, 0.0]))]:
        np.testing.assert_allclose(speculative_components(target, draft)[-1], target, atol=1e-14)

    rng = np.random.default_rng(73)
    candidates = rng.choice(len(q), size=samples, p=q)
    accept = rng.random(samples) < alpha[candidates]
    result = candidates.copy()
    result[~accept] = rng.choice(len(p), size=int((~accept).sum()), p=residual)
    observed = np.bincount(result, minlength=len(p)) / samples
    sigma = np.sqrt(p * (1 - p) / samples)
    max_z = float(np.max(np.abs(observed - p) / sigma))
    assert max_z < 6.0, "Empirical proportions exceeded an explicit six-sigma bound"
    return {"target": p.tolist(), "draft": q.tolist(), "accept_probability": alpha.tolist(),
            "accepted_output_mass": accepted.tolist(), "reject_probability": rejection,
            "residual_distribution": residual.tolist(), "exact_output_distribution": final.tolist(),
            "exact_max_abs_error": float(np.max(np.abs(final - p))),
            "monte_carlo_seed": 73, "samples": samples, "observed_distribution": observed.tolist(),
            "observed_acceptance_rate": float(accept.mean()), "max_standard_error_units": max_z,
            "standard_error_bound_units": 6.0,
            "note": "single-prefix acceptance law; not a full multi-token decoder or speed benchmark"}


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--output", type=Path,
                        default=Path(__file__).resolve().parents[1] / "qa" / "mechanism-results.json")
    parser.add_argument("--samples", type=int, default=200_000)
    args = parser.parse_args()
    if args.samples < 10_000:
        parser.error("--samples must be >= 10000 for the statistical check")
    start = time.perf_counter()
    labs = {"ce_gradient": lab_ce_gradient(), "masks": lab_masks(),
            "causal_attention": lab_attention(), "cache_equivalence": lab_cache_equivalence(),
            "dpo": lab_dpo(), "grpo": lab_grpo(), "kl": lab_kl(),
            "speculative_sampling": lab_speculative(args.samples)}
    report = {"status": "passed", "scope": "8 NumPy mechanism labs; no model training",
              "timestamp_shanghai": datetime.now(timezone(timedelta(hours=8))).isoformat(),
              "runtime": {"python": platform.python_version(), "numpy": np.__version__,
                          "platform": platform.platform(), "dtype": "float64"},
              "elapsed_seconds": time.perf_counter() - start, "labs": labs}
    args.output.parent.mkdir(parents=True, exist_ok=True)
    args.output.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
    print(f"PASS: {len(labs)} mechanism labs")
    print(f"CE finite-difference error: {labs['ce_gradient']['max_abs_gradient_error']:.3e}")
    print(f"Cache logit max error: {labs['cache_equivalence']['full_vs_cached_max_abs_logit_error']:.3e}")
    print(f"Speculative proportions: {labs['speculative_sampling']['observed_distribution']}")
    print(f"Result: {args.output.resolve()}")
    return 0


if __name__ == "__main__":
    sys.exit(main())

5.3 V2补充实验:从算术对照到故障注入

2026-10-19实际运行的补充实验复用上方完整脚本,增加第08篇的带宽预算、第09篇的标量梯度对照,以及本篇三类cache故障与错误残差枚举。原脚本逐字保留。先把两份代码保存在同一目录,再运行:

python fault_injection.py --output v2-results.json

结果文件分别记录环境、各步中间数值和断言。这里的标量更新只演示归一化目标;cache故障是固定参数的前向检查,没有训练LLM、运行多卡或测量GPU吞吐。

完整V2补充源码:fault_injection.py
python135 行
"""Supplemental mechanism checks; original mechanism_labs.py remains unchanged.

Run beside the original script: python fault_injection.py --output v2-results.json
Only scalar arithmetic and tiny fixed NumPy forward passes; no model training.
"""
from __future__ import annotations
import argparse
from datetime import datetime, timedelta, timezone
import json
from pathlib import Path
import platform
import numpy as np
import mechanism_labs as labs


def bandwidth_budget():
    weight_bytes = 7_000_000_000 * 2
    kv_bytes = 2 * 32 * 8192 * 8 * 128 * 2
    assumed_bandwidth = 1_000_000_000_000
    result = []
    for batch in (1, 8):
        read_bytes = weight_bytes + batch * kv_bytes
        lower_seconds = read_bytes / assumed_bandwidth
        result.append({"batch": batch, "read_bytes": read_bytes,
                       "lower_bound_ms": lower_seconds * 1000,
                       "aggregate_token_rate_ceiling": batch / lower_seconds,
                       "per_request_token_rate_ceiling": 1 / lower_seconds})
    assert kv_bytes == 2**30
    return {"scope": "self-defined streaming-read lower bound, not measured throughput",
            "weight_bytes": weight_bytes, "per_request_kv_bytes": kv_bytes,
            "assumed_bandwidth_bytes_per_second": assumed_bandwidth, "cases": result}


def unequal_rank_gradients():
    # All features equal 1. Two targets are 1; the other six targets are 0.
    targets = [np.ones(2), np.zeros(6)]
    w, rate = 0.0, 0.4
    p = 1 / (1 + np.exp(-w))
    sums = np.array([(p - y).sum() for y in targets])
    counts = np.array([len(y) for y in targets])
    naive = float((sums / counts).mean())
    correct = float(sums.sum() / counts.sum())
    compensated_local = sums * len(targets) / counts.sum()
    ddp_result = float(compensated_local.mean())
    assert naive == 0 and correct == 0.25 and ddp_result == correct
    new_w = w - rate * correct
    new_p = 1 / (1 + np.exp(-new_w))
    old_loss = float(np.log(2))
    new_loss = float(-(2*np.log(new_p)+6*np.log1p(-new_p))/8)
    assert new_loss < old_loss
    return {"rank_token_counts": counts.tolist(), "rank_gradient_sums": sums.tolist(),
            "rank_mean_gradients": (sums/counts).tolist(),
            "naive_average": naive, "global_token_average": correct,
            "compensated_local_gradients": compensated_local.tolist(),
            "ddp_average_after_compensation": ddp_result,
            "learning_rate": rate, "old_w": w, "correct_new_w": new_w,
            "new_probability": float(new_p), "old_global_loss": old_loss,
            "correct_new_global_loss": new_loss,
            "naive_new_global_loss": old_loss,
            "scope": "one scalar logistic-regression step, not a multigpu execution"}


def cache_faults():
    ids = np.array([1, 5, 3, 8, 2, 9, 4])
    model = labs.TinyDecoder()
    expected = model.full(ids)

    def run(mode):
        caches = [None, None]
        outputs = []
        original_rope, original_attention = labs.rope, labs.attention
        if mode == "reset_rope_position":
            labs.rope = lambda x, positions: original_rope(x, np.zeros_like(positions))
        elif mode == "top_left_rectangular_mask":
            labs.attention = lambda q,k,v,allowed: original_attention(
                q,k,v,np.tril(np.ones((q.shape[1],k.shape[1]),dtype=bool)))
        try:
            for pos, token in enumerate(ids):
                outputs.append(model.step(int(token), pos, caches))
                if mode == "copy_layer1_cache_to_layer2":
                    caches[1] = (caches[0][0].copy(), caches[0][1].copy())
        finally:
            labs.rope, labs.attention = original_rope, original_attention
        actual = np.stack(outputs)
        shape_ok = all(c[0].shape == (2,7,4) and c[1].shape == (2,7,4) for c in caches)
        error = float(np.max(np.abs(expected-actual)))
        ownership_ok = not np.allclose(caches[0][0], caches[1][0])
        return {"mode": mode, "shape_check_passed": shape_ok,
                "per_layer_cache_shapes": [list(c[0].shape) for c in caches],
                "max_absolute_logit_error": error,
                "logit_allclose_passed": bool(np.allclose(expected,actual,atol=1e-12,rtol=1e-12)),
                "distinct_layer_keys_passed": ownership_ok}

    cases = [run(m) for m in ("correct", "reset_rope_position",
                              "copy_layer1_cache_to_layer2", "top_left_rectangular_mask")]
    assert cases[0]["logit_allclose_passed"]
    assert all(c["shape_check_passed"] for c in cases)
    assert all(not c["logit_allclose_passed"] for c in cases[1:])
    assert not cases[2]["distinct_layer_keys_passed"]
    return {"tokens": ids.tolist(), "atol":1e-12,"rtol":1e-12,"cases":cases}


def wrong_rejection_fallback():
    p,q = np.array([0.6,0.3,0.1]),np.array([0.4,0.4,0.2])
    alpha,accepted,reject,residual,correct = labs.speculative_components(p,q)
    wrong = accepted + reject*p
    difference = wrong-p
    assert np.allclose(correct,p,atol=1e-14)
    assert np.allclose(wrong,[0.52,0.36,0.12])
    for target,draft in ((p,p),(np.array([0.0,0.7,0.3]),np.array([0.3,0.7,0.0]))):
        np.testing.assert_allclose(labs.speculative_components(target,draft)[-1],target,atol=1e-14)
    return {"target":p.tolist(),"draft":q.tolist(),"accepted_mass":accepted.tolist(),
            "reject_mass":reject,"correct_residual":residual.tolist(),
            "correct_output":correct.tolist(),"wrong_output":wrong.tolist(),
            "wrong_minus_target":difference.tolist(),
            "wrong_l1_distance":float(np.abs(difference).sum()),
            "wrong_total_variation_distance":float(np.abs(difference).sum()/2),
            "scope":"exact single-prefix mass enumeration, not Monte Carlo"}


def main():
    parser=argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--output',type=Path,default=Path('v2-results.json'))
    args=parser.parse_args()
    report={"status":"passed","timestamp_shanghai":datetime.now(timezone(timedelta(hours=8))).isoformat(),
            "runtime":{"python":platform.python_version(),"numpy":np.__version__,
                       "platform":platform.platform(),"dtype":"float64"},
            "bandwidth_budget":bandwidth_budget(),"unequal_rank_gradients":unequal_rank_gradients(),
            "cache_faults":cache_faults(),"wrong_rejection_fallback":wrong_rejection_fallback()}
    args.output.parent.mkdir(parents=True,exist_ok=True)
    args.output.write_text(json.dumps(report,ensure_ascii=False,indent=2),encoding='utf-8')
    print(json.dumps(report,ensure_ascii=False,indent=2))

if __name__=='__main__':
    main()

返回导读

正在连接 GitHub 评论…