锁存分配与越界覆盖
地址适用于 libtpu-0.0.40-cp314 wheel 中的 libtpu.so(BuildID md5
89edbbe81c5b328a958fe628a9f2207d,未 strip — 完整 C++ 符号)。其他版本会不同。
摘要
MXU 是权重驻留的:在一个 matmul 步骤能够开始计时前,驻留的增益(权重)矩阵必须被锁存到阵列每象限的某个权重 bank 中,而一次锁存会在许多 matmul 步骤之间分摊。调度流水线的第 2 阶段 — MXU/MRB 分配 pass — 是编译器为每条累加链决定如下事项的位置:哪个 latch op 在其 MxuSequence 中获得哪个索引,以及一个序列的第一个 latch 是否完全需要越界覆盖保护索引。该决策是 MxuAssigner::SetLatchIndices(sub_10F3B4C0),并由单个按代际区分的虚谓词 Target::GainLatchModeHasOverrunChecks(vtable +0x358)门控。
“序列中的 latch 索引”存在的原因,是脉动阵列无法仅用周期表达的一种冒险。latch 会在一个先前的 matmul 可能仍在通过阵列从某个 bank 排出增益时,把新的权重矩阵推入该 bank。如果新的加载过早落下,它会越界覆盖飞行中的 matmul 的增益 — 硬件会破坏仍在读取的权重。大多数代际完全绕开这一点(它们的 first-latch 索引从不分配,调度器依赖 bundle packer 的 slot 合法性),但 Viperfish(TPU v5)在代际层面携带显式的 MSR/first-latch 越界覆盖握手,并且只针对宽的非 bf16 权重格式,因为其更宽的保留占用让越界覆盖变得可达。
本页是调度侧三个事项的权威说明。第一,SetLatchIndices 遍历及其 first-latch 门控 — 算法。第二,按代际的 GainLatchModeHasOverrunChecks 真值表及其耦合的代际层面兄弟 HasMsrOverrunChecks — 策略。第三,CreateVectorLatchLsf latch-op 字段布局,作为 SetLatchIndices 读取(latch_mode @+0x40)和写入(latch_index_in_sequence @+0x42)的数据契约来查看。latch op 到 bundle word 的位级编码属于 Matprep / IAR / Latch ISA 页面的职责;本页只在分配 pass 触及 op 字段的范围内处理它们。
对于重新实现,契约是:
- 一个
MxuSequence的第一个 latch 只有在其 GLM “具有越界覆盖检查”时才会被索引。 后续每个 latch 总是会被索引。first-latch 门控是idx == 0 && !GainLatchModeHasOverrunChecks(glm)→ 放弃该序列(break)。 - 五个代际中的四个是固定
FALSE。 Jellyfish、Dragonfish、Pufferfish、Ghostlite 从不索引其第一个 latch。只有 Viperfish 的+0x358函数体是非平凡的:!LatchModeIsTranspose(glm) && fmt ∈ {3,4,5,6,7,8},这解析为 GLM{14,16,18,20,22,24}— 六个 NO_XPOSE 宽模式。 - 门控谓词以 latch op 的
GainLatchMode(BYTE[op+0x40])为键,而不是以 matmul 为键。SetLatchIndices通过latch_mode(op)读取它,并传给 vtable slot。 - latch 索引是
WORD[op+0x42]处的 16 位字段,边界为≤ 65535,由set_latch_index_in_sequence写入。它与一个不相交 opcode 家族的 load-LMR MSR 使用相同字节地址 — 只有在 opcode-family 检查之后再读取它。
| 分配驱动器 | MxuAssigner::SetLatchIndices sub_10F3B4C0(第 2 阶段提交) |
| First-latch 门控 | idx == 0 && !GainLatchModeHasOverrunChecks(latch_mode(op)) → break |
| 按代际谓词 | Target::GainLatchModeHasOverrunChecks(glm) vtable +0x358 |
| 代际层面兄弟 | Target::HasMsrOverrunChecks() — 仅 Viperfish 上为 TRUE(sub_1D49AAC0) |
| Viperfish 函数体 | sub_1D49AB20 = !LatchModeIsTranspose && fmt∈{3..8} → GLM {14,16,18,20,22,24} |
| 索引字段 | WORD[op+0x42],set_latch_index_in_sequence sub_1D4E7960,边界 ≤ 0xFFFF |
| GLM 字段(门控键) | BYTE[op+0x40],latch_mode sub_1D4E7500 / set_latch_mode sub_1D4D7C20 |
| Latch 构建器 | LloInstruction::CreateVectorLatchLsf sub_1D4D7AA0;通用 CreateVectorLatchHelper sub_1D4D8360 |
| 序列记录 | MxuSequence — latches 列表 @+0x18,计数 @+0x20 |
| 置信度 | CONFIRMED(字节锚定),除非某一行或标注另有说明 |
越界覆盖冒险与索引
“overrun” 的含义
MXU 是按权重驻留方式计时的脉动阵列。latch op 将驻留的增益矩阵写入每象限的权重 bank;随后 matmul op 将移动操作数流过阵列,并在每个脉动步骤读取这些已锁存的权重。因为一个 matmul 不会在一个周期内完成 — 其结果会在 op 发出许多周期后从 matrix-result buffer 排出 — 它读取的权重会在整个排出期间保持在 bank 中存活。如果下一个 latch op 在前一个 matmul 完成消耗旧权重之前,把新的权重矩阵写入同一个 bank,新的写入就会越界覆盖飞行中的读取。
CPU/GPU 后端不会看到这种冒险,因为寄存器写入与消费指令按流水线 interlock 所定义的顺序 retire。TPU MXU 对权重 bank 没有 interlock:调度必须静态保证新的 latch 不会落到仍在排出的 matmul 正在读取的 bank 上。编译器提供该保证的工具是序列中的 latch 索引 — 一个按序列的序号,下游 bundle packer 和按代际的编码器使用它来让 latch 与读取它们的 matmul 在时间上不发生碰撞。
为什么第一个 latch 特殊
在一个 MxuSequence(一条累加链)内,matmul 是有序的,供给它们的 latch 也以相同方式有序。第一个之后的每个 latch 总是会获得索引 — 按构造,它是在一个 matmul 之后的重新加载,因此总是需要排序序号。一个序列的第一个 latch 是链的头部:在这个序列中没有先前的 matmul 可供它越界覆盖。第一个 latch 是否仍然需要索引,完全取决于硬件代际是否在 bank 层面强制越界覆盖握手 — 即,来自前一个序列遗留的 matmul 是否可能仍在排出第一个 latch 目标 bank。
在没有该握手的四个代际上,答案是“不,从不索引第一个 latch”:bundle packer 的 slot 合法性加上自然程序顺序已经足够,并且 SetLatchIndices 一旦到达未索引的第一个 latch 就放弃该序列。在 Viperfish 上,答案是“是,当权重格式足够宽、其保留占用让跨序列越界覆盖变得可达时。”
SetLatchIndices — 分配遍历
SetLatchIndices(sub_10F3B4C0)是第 2 阶段的提交步骤。它接受一个序列组中的 MxuSequence span,并对每个序列遍历其 latch 列表,分配索引,直到 latch 用完或遇到一个未索引的第一个 latch。
function MxuAssigner::SetLatchIndices(span<unique_ptr<MxuSequence>> seqs): // sub_10F3B4C0
for each seq in seqs:
count = seq.latches.count // QWORD[seq+0x20]
if (count == 0) continue
idx = 0
do:
op = seq.latches[idx] // ptr list @ QWORD[seq+0x18], 8*idx
opc = WORD[op] // LloOpcode
check((opc - 0x8d) < 0xa) // LloOpcodeIsVectorLatch — else FATAL line 420
tgt = op.region.module.target // [[op+0x10]+0x38]+0x10
glm = latch_mode(op) // BYTE[op+0x40]
has_overrun = tgt.vtbl[+0x358](glm) // GainLatchModeHasOverrunChecks
if (idx == 0 && !has_overrun): break // first latch, no overrun ⇒ abandon sequence
set_latch_index_in_sequence(op, idx) // WORD[op+0x42] = idx
idx = (uint32)(idx + 1)
while (idx < count)
```text
反编译(`sub_10F3B4C0`)是字节精确的:latch 列表基址是 `QWORD[*v2 + 24]`(`+0x18`),计数是 `QWORD[*v2 + 32]`(`+0x20`),opcode 边界是 `(uint16)(opcode - 141) >= 0xA` → `mxu_assigner.cc:420` 处 FATAL `"LloOpcodeIsVectorLatch(opcode)"`,target 跳转是 `*(QWORD*)(*((QWORD*)op+2)+56)+16`(= `[[op+0x10]+0x38]+0x10`),GLM 读取是 `latch_mode(op)`,门控是 `+856` 字节(`0x358`)虚调用,而 break 是 `if (!(DWORD)idx && !has_overrun) break`。
> **注意点 — 门控是 `break`,不是 `continue`。** 当一个序列的第一个 latch 没有越界覆盖检查时,`SetLatchIndices` 会*放弃整个序列* — 它不会只跳过第一个 latch 然后索引其余部分。这是正确的,因为在固定 `FALSE` 的代际上,第一个 latch 总是未索引的头部,且内部循环还没有越过它(`idx` 仍为 0),所以还没有别的内容需要索引。在这里使用 `continue` 的重新实现会尝试索引一个头部从未被索引的序列的后续 latch — 形成畸形链。
### 它读取和写入的字段
`SetLatchIndices` 正好触及两个 latch-op 字段,外加序列记录:
| 访问 | 字段 | 偏移 | 访问器 | 说明 |
|---|---|---|---|---|
| 读取 | latches 列表基址 | `QWORD[seq+0x18]` | — | `LloInstruction*` 数组 |
| 读取 | latches 计数 | `QWORD[seq+0x20]` | — | 循环边界 |
| 读取 | `LloOpcode` | `WORD[op+0x00]` | — | `0x8d..0x96` 检查 |
| 读取 | `latch_mode`(GLM) | `BYTE[op+0x40]` | `latch_mode` `sub_1D4E7500` | 门控键 |
| 写入 | `latch_index_in_sequence` | `WORD[op+0x42]` | `set_latch_index_in_sequence` `sub_1D4E7960` | 该分配 |
`set_latch_index_in_sequence`(`sub_1D4E7960`)重新检查 `LloOpcodeIsVectorLatch(opcode())`(`(uint16)(opcode-141) >= 0xA` → `llo_instruction.cc:3399` 处诊断),约束 `index <= 65535`(`LloCheckOp 3`,`llo_instruction.cc:3400`),然后存储 `WORD[op + 33*2]` = `WORD[op+0x42]`。`latch_mode`(`sub_1D4E7500`)对 `(uint16)(opcode-141) <= 9` 或 Matprep-subr opcode 读取 `BYTE[op+0x40]`,否则路由到 `Unsupported opcode` 诊断。
> **说明 — `WORD[op+0x42]` 与 `BYTE[op+0x42]`。** latch 家族(`0x8d..0x96`)在 `+0x42` 存储其 16 位 `latch_index_in_sequence`;load-LMR 家族(`0xaa`/`0xab`)通过下面 opcode 多路复用的 `set_matrix_staging_register`,在同一字节地址存储一个 8 位 MSR。两个家族不相交,所以在一个 op 内没有别名 — 但如果读取器未先检查 opcode family 就触及 `+0x42`,会把其中一个误解码为另一个。
---
## 按代际的越界覆盖谓词
### GainLatchModeHasOverrunChecks(vtable +0x358)
门控是 `Target` 上按代际的虚函数。基类函数体是一个 `Unimplemented` `LogFatal` stub(`target.h:2472`);每个具体代际都会 override 它。四个 override 返回固定 `FALSE`;只有 Viperfish 有真实函数体。
| 代际(TPU) | `GainLatchModeHasOverrunChecks`(`+0x358`) | 函数体 |
|---|---|---|
| Jellyfish(v2) | `sub_1D4925E0` | `return 0` ⇒ 始终 FALSE |
| Dragonfish(v3) | `sub_1D4901C0` | `return 0` ⇒ 始终 FALSE |
| Pufferfish(v4) | `sub_1D494880` | `return 0` ⇒ 始终 FALSE |
| Viperfish(v5,v5e+v5p) | `sub_1D49AB20` | 非平凡(见下) |
| Ghostlite(v6e) | `sub_1D497940` | `return 0` ⇒ 始终 FALSE |
| 基类 `Target` | `sub_1D61D8C0` | `LogFatal "Unimplemented"`(`target.h:2472`) |
Viperfish 函数体(`sub_1D49AB20`)是字节精确的:
```c
function ViperfishTarget::GainLatchModeHasOverrunChecks(glm): // sub_1D49AB20
if (LatchModeIsTranspose(glm)) return false; // transpose ⇒ no overrun
fmt = GainLatchModeToMatmulDataFormat(glm); // sub_1D629260
return MatmulDataFormatIsIntegral(fmt) | ((uint8)(fmt - 3) < 2);
// ^ fmt ∈ {5,6,7,8} ^ fmt ∈ {3,4} ⇒ TRUE ⇔ fmt ∈ {3,4,5,6,7,8}三个小 helper 固定了格式和 transpose 分类,全部字节精确:
function LatchModeIsTranspose(glm): // sub_1D628EA0
return bittest(0xAAAAAAAAAA6AA, glm); // odd-GLM mask (+ bit 0xa)
function MatmulDataFormatIsIntegral(fmt): // sub_1D629240
return (uint8)(fmt - 5) < 4; // fmt ∈ {5,6,7,8}
function GainLatchModeToMatmulDataFormat(glm): // sub_1D629260 (switch; FATAL on a gap)
0,1→1 10,11→2 14,15→3 16,17→4 18,19→5
20,21→6 22,23→7 24,25→8 48,49→9 50,51→10 default→FATAL (matmul_data_format.cc:164)
```text
### Viperfish first-latch 真值表
在活动 GLM 和 `GainLatchModeToMatmulDataFormat` 表上运行该谓词,第一个 latch 只会针对六个 NO_XPOSE 宽(非 bf16)模式被索引:
| GLM | hex | fmt | transpose? | first-latch 越界覆盖 | 权重格式(NO_XPOSE) |
|---|---|---|---|---|---|
| 0 | `0x00` | 1 | no | FALSE(bf16) | bf16 |
| 1 | `0x01` | 1 | yes | FALSE | bf16 XPOSE |
| 10 | `0x0a` | 2 | yes(在 mask 中) | FALSE | bf16-alt / f32-pair |
| 11 | `0x0b` | 2 | no | FALSE(fmt2) | packed bf16 |
| 14 | `0x0e` | 3 | no | **TRUE** | F8E4M3FN |
| 16 | `0x10` | 4 | no | **TRUE** | F32 / F8E4M3B11 |
| 18 | `0x12` | 5 | no | **TRUE** | F8E5M2 |
| 20 | `0x14` | 6 | no | **TRUE** | S8 / int8 |
| 22 | `0x16` | 7 | no | **TRUE** | U4 nibble |
| 24 | `0x18` | 8 | no | **TRUE** | S4 nibble |
| 15/17/19/21/23/25 | odd | 3..8 | yes | FALSE | (transpose 形式) |
| 48/50 | `0x30`/`0x32` | 9/10 | no | FALSE(fmt9/10) | fp8-conv / fp8-fnuz |
因此,Viperfish first-latch 越界覆盖会在 **GLM ∈ `{14,16,18,20,22,24}`** 时触发。bf16 模式(fmt 1/2)、fp8-conversion 模式(fmt 9/10)以及*每个* transpose GLM 都不会触发 — transpose 被直接排除,而 bf16/fp8-conv 格式位于 `fmt ∈ {3..8}` 窗口之外。
### HasMsrOverrunChecks — 代际层面耦合
上面的按 GLM 谓词是一个更粗粒度、无参数的代际层面兄弟 `Target::HasMsrOverrunChecks()` 的细化。它仅在 Viperfish 上为 `TRUE`:
| 代际 | `HasMsrOverrunChecks` | 函数体 |
|---|---|---|
| Jellyfish | `sub_1D4925C0` | `return 0`(FALSE) |
| Dragonfish | `sub_1D490160` | `return 0`(FALSE) |
| Pufferfish | `sub_1D494820` | `return 0`(FALSE) |
| Viperfish | `sub_1D49AAC0` | `return 1`(**TRUE**) |
| Ghostlite | `sub_1D4978E0` | `return 0`(FALSE) |
| 基类 `Target` | `sub_1D61D800` | `LogFatal "Unimplemented"` |
> **说明 — Viperfish(TPU v5)是唯一具有 MSR/first-latch 越界覆盖握手的代际。** 这正是为什么它的按 GLM `+0x358` override 是唯一非平凡函数体:代际层面的 `HasMsrOverrunChecks` 为 `TRUE` 是*启用*条件,而 `GainLatchModeHasOverrunChecks` 是该握手实际必须触发时机的按格式*细化*。indexed first latch 的额外保留成本会计入 Viperfish cost model — 见 [MatmulMode 和 Modifiers](../cost/matmul-mode-modifiers.md),其中的 `AddOverrunCheckReservations`(`{Msr:2/6}`)位于 Viperfish 命名空间,并且是同一门控在 cost 侧的消费者。这里触发的宽 fmt-3..8 NO_XPOSE 格式,正是其 matpush 绘制最宽保留值集合的格式,使越界覆盖变得可达。
---
## Latch-Op 字段契约
`SetLatchIndices` 操作的是 LHS 分区步骤(下面的 `LatchLhs`)已经构造好的 latch op。构造函数 `CreateVectorLatchLsf`(`sub_1D4D7AA0`)是规范生产者;本节固定分配 pass 及其门控所读取的字段。这些字段的位级 *bundle encoding* 位于 [Matprep / IAR / Latch ISA 页面](../isa/slot-matprep-iar-latch.md);这里它们是 LLO-IR 数据契约。
### latch 家族
十个 `LloOpcode`,`0x8d..0x96`,全部路由到两个构造函数之一:
| `LloOpcode` | 名称 | 构建器 | 构造函数 |
|---|---|---|---|
| `0x8d` | `kVectorLatchLsf` | `VlatchLsf` | `CreateVectorLatchLsf`(`sub_1D4D7AA0`) |
| `0x8e` | `kVectorLatchLsfMsk` | `VlatchLsfMsk` | `CreateVectorLatchLsfMasked`(`sub_1D4D8140`) |
| `0x8f` | `kVectorLatch` | `Vlatch` | `CreateVectorLatch`(`sub_1D4D8900`) |
| `0x90` | `kVectorLatchMsk` | `VlatchMsk` | `CreateVectorLatchMasked`(`sub_1D4D8C40`) |
| `0x91` | `kVectorLatch1` | `Vlatch1` | `CreateVectorLatch1`(`sub_1D4D8940`) |
| `0x92` | `kVectorLatch1Msk` | `Vlatch1Msk` | `CreateVectorLatch1Masked`(`sub_1D4D8C80`) |
| `0x93` | `kVectorLatch2` | `Vlatch2` | `CreateVectorLatch2`(`sub_1D4D8A80`) |
| `0x94` | `kVectorLatch2Msk` | `Vlatch2Msk` | `CreateVectorLatch2Masked`(`sub_1D4D8CC0`) |
| `0x95` | `kVectorLatch3` | `Vlatch3` | `CreateVectorLatch3`(`sub_1D4D8B60`) |
| `0x96` | `kVectorLatch3Msk` | `Vlatch3Msk` | `CreateVectorLatch3Masked`(`sub_1D4D8D00`) |
`VlatchI(value, long idx, glm)`(`sub_1D574580`)将其 `long idx` 分发到 `Vlatch1`(`0x91`)或 `Vlatch2`(`0x93`)— indexed latch 子 bank 选择。全部十个 opcode 都满足 `SetLatchIndices` 强制的谓词 `LloOpcodeIsVectorLatch`(`(opcode - 0x8d) < 0xa`)。
### LloInstruction 字段布局
构造出的 latch op 在这些字段中携带其操作数,来自 setter 及其对称 reader 的字节精确结果:
| 偏移 | 字段 | Setter / reader | 含义 |
|---|---|---|---|
| `WORD[+0x00]` | `LloOpcode` | `New()` arg | `0x8d..0x96` |
| `BYTE[+0x0a]` | `register_number` | `set_register_number` / `sub_1D5A8E20` | gain-source VREG 编号 |
| `WORD[+0x0b]` | 控制字 | `set_unit_id` / `ValidateAndSetMxuAndSourceBus` | unit-id + source-bus(见下) |
| `BYTE[+0x40]` | `latch_mode`(GLM) | `set_latch_mode` `sub_1D4D7C20` / `latch_mode` `sub_1D4E7500` | 门控键 |
| `WORD[+0x42]` | `latch_index_in_sequence` | `set_latch_index_in_sequence` `sub_1D4E7960` | 由 `SetLatchIndices` 分配 |
| `BYTE[+0x44]` | `matrix_staging_register`(Msr) | `set_matrix_staging_register` `sub_1D4D7D40` | latch-bank / MSR 目标 |
控制字 `WORD[+0x0b]` 打包两个 bitfield(`set_unit_id` `sub_12698C00` 用于 unit-id;source-bus 内联在 `ValidateAndSetMxuAndSourceBus` 中):
```c
// LloValue::set_unit_id (sub_12698C00) — the gain-matrix-register / MXU-quadrant pack
WORD[v+0x0b] = ((unit & 3) << 8) + (WORD[v+0x0b] & 0xF8FF) + 0x400; // check unit <= 3
// bits 8-9 : unit_id = which MXU quadrant (0..3) the gain matrix latches into
// bit 10 : has-mxu flag (0x400)
// source-bus pack (ValidateAndSetMxuAndSourceBus, sub_1D4D7E80) — Pufferfish-only, non-LSF only
WORD[v+0x0b] = ((bus & 3) << 11) + (WORD[v+0x0b] & 0xC7FF) + 0x2000; // check bus <= 3
// bits 11-12 : VEX source-bus (0..3)
// bit 13 : has-source-bus flag (0x2000)matrix_staging_register setter 是 opcode 多路复用的 — 同一个逻辑字段会根据 opcode family 落到四个偏移之一(字节精确来自 sub_1D4D7D40,reader sub_1D4E7B80 中有相同 mux):
function set_matrix_staging_register(op, msr): // sub_1D4D7D40
if ((uint16)(opcode - 0x9b) <= 0xa): BYTE[op+0x46] = msr // matmul 0x9b..0xa5
elif ((uint16)(opcode - 0x8d) <= 9): BYTE[op+0x44] = msr // latch 0x8d..0x96 ← the latch family
elif ((opcode & 0xfffe) == 0xaa): BYTE[op+0x42] = msr // load-LMR 0xaa/0xab
elif (opcode == 0xa8): BYTE[op+0x41] = msr // done-with-gains
else: FATAL "msr unsupported for opcode" (llo_instruction.cc:3414)
```text
### CreateVectorLatchLsf — 构建序列
`CreateVectorLatchLsf`(`sub_1D4D7AA0`)是 `VlatchLsf` 包装的构造函数。它守护 gain source 和 GLM,然后按顺序盖写字段:
```c
function CreateVectorLatchLsf(gain_src, glm, unit_id, region): // sub_1D4D7AA0
if (gain_src.opcode >= 0x1cd) trap // opcode bound (ud1)
if (opcode_produced_register_type[gain_src.opcode] != 4) // gain source must produce reg-type 4
UpdateStatus("chunk->ProducesVreg()") // slow diagnostic path (llo_instruction.cc:1073)
if (glm > 0x33 || !bittest(0xf0000003c0c03, glm)) // LSF GLM-validity mask
FATAL "LSF latch mode not expected." (llo_instruction.cc:1089)
op = LloInstruction::New(0x8d /*kVectorLatchLsf*/, {gain_src}, region)
set_latch_mode(op, glm) // BYTE[op+0x40] = glm
set_matrix_staging_register(op, 1) // BYTE[op+0x44] = 1 (LSF staging slot)
ValidateAndSetMxuAndSourceBus(unit_id, op) // WORD[op+0x0b] unit-id (+ src-bus)
return op两个构造函数接受不同 GLM 集合(字节精确 movabs mask):
| 构造函数 | GLM-validity mask | 接受的 GLM |
|---|---|---|
CreateVectorLatchLsf | 0xf0000003c0c03 | {0,1,10,11,18,19,20,21,48,49,50,51}(bf16、F8E5M2、S8、fp8-conv) |
CreateVectorLatchHelper | 0xf000003fffc3f | {0-5,10-25,48-51}(完整集合,含 F8E4M3FN/F32 和 nibble fmt7/8) |
ValidateAndSetMxuAndSourceBus(sub_1D4D7E80)约束 MXU id(mxu_id >= 0,mxu_id < MxusPerTensorCore() = DWORD[Target+0x4ac],与 LatchLhs 使用的同一个 +0x4ac 索引),盖写 unit-id,并且 — 只有当 HasVexSourceBuses()(vtable +0x408,仅 Pufferfish 上为 TRUE,sub_1D494B40)且 LloOpcodeUsesSourceBus(op) 时 — 盖写 source bus。
特性 —
LloOpcodeUsesSourceBus对 LSF op 为 FALSE。LloOpcodeUsesSourceBus(sub_10C0D420)对0x8f..0x96(普通/indexedVlatch形式)返回TRUE,但对0x8d/0x8e(LSF 形式)不返回。因此VlatchLsf永远不会写入 source-bus 字段,即使在 Pufferfish 上也是如此;VEX source-bus latch 字段只在 Pufferfish 上、且只针对非 LSF latch op0x8f..0x96填充。在 LSF latch 上盖写 source bus 的重新实现会偏离二进制。opcode 还门控哪些 op 完全能到达 MXU:LloOpcodeUsesMxu(sub_10A433E0)为TRUE当且仅当opcode ∈ [0x8d,0xa5] ∪ [0xa8,0xab] ∪ [0x152,0x153]。GAIN-SOURCE 守护(推断)— 入口守护
opcode_produced_register_type[gain_src.opcode] != 4在gain source op 产生 register-type 4 时选择 fast path(否则走慢速LloModule::UpdateStatus("chunk->ProducesVreg()")路径)。opcode_produced_register_type表(@0x223a16c0,.data.rel.ro)按生产者 opcode 索引,而不是 latch opcode;latch 家族自身条目是 0,因为 latch 是消费者。哪些生产者 opcode 产出 type-4 — 即 LSF latch 消费的精确 gain/matrix register 类 — 没有逐格枚举;类身份根据上下文推断为 MXU gain/matrix register 类。LOW。
LatchLhs — 第一个 Latch 的来源
SetLatchIndices 索引的是 LatchLhs(sub_10F3B5E0)已经产生的 latch。LatchLhs 是 gain-matrix 分区步骤:它按 transpose op 对 LHS 分组,运行按 MXU 的容量守护,然后用按其 MXU 象限标记的每个已发出 latch+matmul+matres op 重建序列。
function LatchLhs(target, lhs_span, sequences): // sub_10F3B5E0
// per-sequence capacity guard
acc = Σ over matreses of MatmulDataFormatPackingFactor(matmul_data_format(op))
check( ChunksPerTile() * num_mxus >= acc ) // num_mxus = DWORD[Target+0x4ac]
check( acc % ChunksPerTile() == 0 ) // tile-aligned
for each matmul:
q = program_order & 3 // the MXU quadrant (0..3)
glm = byte_AC0913E[matmul_op - 0x9b] // GLM byte table @0xac0913e
emit = VlatchLsf(value, glm, /*unit_id=*/0) // kVectorLatchLsf
WORD[emit+0x0b] = ((q << 8) + 0x400) | (WORD[emit+0x0b] & 0xF8FF) // overwrite unit-id with q
repeat Vmatmul / Vmatres PackingFactor(fmt)×, each unit_id-stamped // K-tile split
```text
`0xac0913e` 处的 GLM byte table 读作 `{0,0,0,0,0,0,0,0,0xb,0xb}`(10 个条目,按 `matmul_op - 0x9b` 索引):普通 matmul opcode 映射到 GLM 0(bf16 NO_XPOSE),最后两个(`0xa3`/`0xa4`)映射到 GLM `0xb`(packed bf16)。因此 `SetLatchIndices` 遍历的 latch 携带 GLM 0 或 `0xb` — 两者都是 fmt 1/2,都位于 Viperfish `fmt ∈ {3..8}` 越界覆盖窗口之外。因此,`LatchLhs` 发出的 bf16/packed-bf16 第一个 latch 从不会触发越界覆盖门控;该门控只有在一个更宽格式的 latch(GLM `{14,16,18,20,22,24}`)通过另一条路径位于 Viperfish 序列头部时才会触发。
`MatmulDataFormatPackingFactor`(`sub_1D629300`,表 `@0xb53c6bc` = `{1,2,4,4,4,4,8,8,4,4}`,对应 fmt 1..10)是驱动 K-tile 循环次数的列打包因子;`ChunksPerTile` 和 `num_mxus`(`Target+0x4ac`)约束按序列的容量。完整的 `MxuSequence` 记录和 `LatchLhs` 分区见 [MxuSequence / SequenceInfo](mxu-sequence-struct.md)。
---
## 置信度摘要
| 断言 | 证据 |
|---|---|
| `SetLatchIndices` 遍历每个序列的 latch 列表并索引直到 break | `sub_10F3B4C0` — 列表 `@+0x18`,计数 `@+0x20`,opcode 检查 `(op-141)<0xa` |
| First-latch 门控是 `idx==0 && !GainLatchModeHasOverrunChecks(glm)` → `break` | `sub_10F3B4C0` — `vtbl[+0x358]` 调用后 `if (!(DWORD)idx && !has_overrun) break` |
| 门控以 `latch_mode(op)` = `BYTE[op+0x40]` 为键 | `latch_mode` `sub_1D4E7500` 作为 `+0x358` 参数被调用 |
| Jellyfish/Dragonfish/Pufferfish/Ghostlite `+0x358` 固定 `FALSE` | `sub_1D4925E0`/`1D4901C0`/`1D494880`/`1D497940` = `return 0` |
| Viperfish `+0x358` = `!transpose && fmt∈{3..8}` → GLM `{14,16,18,20,22,24}` | `sub_1D49AB20` 函数体 + `GainLatchModeToMatmulDataFormat` `sub_1D629260` switch |
| `LatchModeIsTranspose` mask `0xAAAAAAAAAA6AA`;`IsIntegral` = `(fmt-5)<4` | `sub_1D628EA0` / `sub_1D629240` |
| `HasMsrOverrunChecks` 仅 Viperfish 上为 `TRUE`;基类 `LogFatal` | `sub_1D49AAC0` = `return 1`;其他 `return 0`;`sub_1D61D800` LogFatal |
| `latch_index_in_sequence` 位于 `WORD[op+0x42]`,边界 `≤ 0xFFFF` | `set_latch_index_in_sequence` `sub_1D4E7960`(`index <= 65535`,`WORD[op+33*2]`) |
| MSR opcode-mux:latch→`+0x44`,matmul→`+0x46`,load-LMR→`+0x42`,dwg→`+0x41` | `set_matrix_staging_register` `sub_1D4D7D40` |
| `CreateVectorLatchLsf` GLM mask `0xf0000003c0c03`;helper `0xf000003fffc3f` | `sub_1D4D7AA0` / `sub_1D4D8360` `movabs` + `_bittest64` |
| `CreateVectorLatchLsf` 盖写 `New(0x8d)`、GLM`@+0x40`、Msr=1`@+0x44`、unit-id`@+0x0b` | `sub_1D4D7AA0` 构建序列 |
| unit-id 打包 `bits 8-9 + 0x400`;source-bus 打包 `bits 11-12 + 0x2000` | `set_unit_id` `sub_12698C00`;`ValidateAndSetMxuAndSourceBus` `sub_1D4D7E80` |
| Source bus 仅在 `HasVexSourceBuses`(仅 Pufferfish)且 `UsesSourceBus`(`0x8f..0x96`)时盖写 | `sub_1D494B40`=1,其他 0;`LloOpcodeUsesSourceBus` `sub_10C0D420` |
| `num_mxus` = `DWORD[Target+0x4ac]`(索引 299) | `ValidateAndSetMxuAndSourceBus` `v6[299]`;`LatchLhs` `*((int*)v32+299)` |
| `LatchLhs` GLM byte table `@0xac0913e` = `{0×8, 0xb, 0xb}` | file off `0xac0913e` 处的二进制读取 |
| Gain-source reg-type-4 生产者类身份 | `opcode_produced_register_type[…]` 表按生产者索引;类未隔离 |
---
## 交叉引用
- [TPU 调度流水线](overview.md) — 四阶段栈;`SetLatchIndices` 是第 2 阶段提交步骤,是第 3 阶段 bundle packing 的前置条件。
- [MxuSequence / SequenceInfo](mxu-sequence-struct.md) — 字节精确的 `MxuSequence` 记录(latches 列表 `@+0x18`,计数 `@+0x20`)以及产生这里被索引 latch 的 `LatchLhs` 分区。
- [MRB 链分配器](mrb-chain-allocator.md) — 在同一个第 2 阶段 pass 中运行的累加链保留时间线;latch 分配的输出侧类比。
- [Matprep、IAR 和 Latch 子 Slot](../isa/slot-matprep-iar-latch.md) — latch-op 家族及其来自 ISA / encoding 侧的 `LloInstruction` 字段偏移;本页是这些相同字段在调度侧的读写者。
- [MXU Slot](../isa/slot-mxu.md) — 已锁存增益供给的脉动阵列 matmul op 家族;越界覆盖门控保护的飞行中排出消费者。
- [MatmulMode 和 Modifiers](../cost/matmul-mode-modifiers.md) — indexed first latch 触发时收取的 Viperfish `AddOverrunCheckReservations`(`{Msr:2/6}`)成本;`HasMsrOverrunChecks` 在 cost 侧的消费者。
- [MXU 延迟概览](../cost/mxu-latency-overview.md) — 越界覆盖检查成本背后的按代际保留矩阵。
- **二进制:** `extracted/libtpu-0.0.40-cp314-cp314-manylinux_2_31_x86_64/libtpu/libtpu.so`(build-id `89edbbe81c5b328a958fe628a9f2207d`)
- **索引条目:** Part VIII — Instruction Scheduling & Bundle Packing — [返回索引](../index.md)