Skip to content

Matprep、IAR 和 Latch 子槽

地址适用于 libtpu-0.0.40-cp314 wheel 中的 libtpu.so(BuildID md5 89edbbe81c5b328a958fe628a9f2207d,未 stripped,含完整 C++ 符号)。其他版本会不同。

摘要

MXU slot 的脉动阵列能够执行一次乘法时钟步之前,它必须先被喂数。喂给阵列包含三个不同的工作,本页覆盖这些工作;它们都通过矩阵乘法本身使用的同一个 VectorExtended bundle slot 运行:matprep 暂存移动操作数(activations)并准备静态增益矩阵;IAR(Index Address Register)是每个 TensorCore 的索引寄存器文件,用来驱动进入操作数池的 indexed(gather)内存访问;而 latch op 会把准备好的增益矩阵加载到阵列的权重 bank 中。矩阵乘法步骤本身是可见的 op,但 latch/matprep/IAR 机制才是让它正确且低成本的关键。

如果你读过 LLVM NVPTX 后端,和 latch 最接近的类比是 wgmma accumulator group 之前隐式发生的寄存器文件写入,而和 IAR 最接近的类比是 base+index 寻址模式;不同的是 TPU 把索引寄存器公开为架构对象IAR0/IAR1),由 SetIar op 显式加载,并由 VectorLoadIndexed op 隐式消费。这里没有逐元素 index 操作数;IAR 就是 index。latch 的 weight-stationary 方式与 wgmma 的 accumulator-stationary 类似:一次 latch 会在多次 matmul step 间摊销。

本页由三个单元构成,对应这三个工作,另加把它们串起来的逐代 cost-model 处理。每个单元都会列出构造该 op 的 builder 函数、它写入的精确 LloInstruction 字段偏移,以及逐代字段/计数表。

对重新实现而言,契约是:

  • IAR 字段布局LloInstruction+0x50→+0x6c 处的 64-bit 值,低 32 位 = index,bit 32 = present;由 CreateVectorSetIarHelper 构建,受 IarsPerTensorCore(每代都是 2)限制。Lane/Sublane/Raw 的划分存在于 ISA bundle-slot opcode 中,而不是该值中。
  • latch-op family0x8d..0x96)及其 LloInstruction 字段映射:GainLatchMode @+0x40,latch index @+0x42,MSR @+0x44(opcode-multiplexed),unit-id / MXU-quadrant + source-bus 打包进 control word @+0x0b
  • 逐代 matprep 机制:GL/GF 给每个 matprep variant 一个固定的二分搜索 perf row;VF 将它折叠进 matmul-format 表外加 modifier-keyed reservation;PF 将它折叠进 latch ops;JF 将 gains 的转置折叠进 matmul opcode。
  • first-latch overrun handshakeGainLatchModeHasOverrunChecks,vtable +0x358),它决定序列中的第一个 latch 是否会被编号;只有 Viperfish 上才非平凡。
Latch op familykVectorLatchLsf 0x8dkVectorLatch3Msk 0x96(10 个 opcode)
Matprep op familykVectorMatprepSubr 0x97/0x98kVectorMatprepMubr 0x99/0x9a
IAR op familykVectorReadIar 0x01kVectorSetIarLane/Raw/Sublane 0x02/0x03/0x04
IAR value fieldQWORD[LloInstruction+0x50] → +0x6c;bit 32 present,低 32 index
IarsPerTensorCoreDWORD[Target+0x4a8] = 所有世代均为 2IAR0/IAR1
Latch builderLloInstruction::CreateVectorLatchLsf sub_1D4D7AA0CreateVectorLatchHelper sub_1D4D8360
IAR setterLloInstruction::CreateVectorSetIarHelper sub_1D4DF080
Latch sequencingMxuAssigner::SetLatchIndices sub_10F3B4C0LatchLhs sub_10F3B5E0
Sequence recordMxuSequence,sizeof 0x78,五个 {ptr,count,cap} 列表
Home slotVectorExtended(与 matmul 共享);matres → VectorResult

IAR — Index Address Register

用途

IAR 是 TensorCore 的 gather-index 寄存器。VectorLoadIndexed op 读取 base + IAR * stride 处的操作数池,但它不携带显式 index 字段;IAR 寄存器就是逐元素 gather index。使用时,SetIar op 将一个 index 值加载到两个架构寄存器之一(IAR0 / IAR1);后续 VectorLoadIndexed0 / Indexed1 消费所选寄存器;ReadIar0 / ReadIar1 将其排回 vector register。这是 SparseCore/TensorCore embedding-gather 路径下面的寻址原语。它不同于 SparseCore TEC TileSpmemLoadIndexed,后者确实携带显式逐元素 Index VREG。

IAR Value Field Layout

IAR 值位于 LloInstruction 的 "modifier" 子对象 +0x50(同一个子对象也在 +0x68 保存 precision_type)。iar() 将它作为单个 64-bit 字段读取:

c
function LloInstruction::iar(this):       // sub_1D4E7120
    sub = *(QWORD*)(this + 0x50);          // the modifier sub-object
    if (sub == nullptr) return 0;          // no IAR present
    return *(QWORD*)(sub + 0x6c);          // 64-bit packed field
```text

builder 是 `CreateVectorSetIarHelper`。它把 index 写入低 32 位,并在同一个 qword 的 byte 4 写入 present-byte,从而设置 bit 32

```c
function CreateVectorSetIarHelper(opcode, iar_value, source, region):   // sub_1D4DF080
    // bound the index against the per-TensorCore register count
    check(iar_value < Target::IarsPerTensorCore())    // sub_1D617280 → DWORD[Target+0x4a8]
    check(opcode_produced_register_type[source.opcode] == 4)  // source must be a VREG producer
    op = LloInstruction::New(opcode, {source}, region)
    sub = op + 0x50                       // allocate the 0xf0 modifier sub-object if absent (zeroed)
    *(DWORD*)(sub + 0x6c) = iar_value      // sub_1D4DF169 — low 32 bits = the index value
    *(BYTE *)(sub + 0x70) = 1              // sub_1D4DF16C — byte 4 of the qword ⇒ BIT 32 = present
    return op

所以 iar() == (1u64 << 32) | iar_value。 "IAR present" 位是 bit 32;index 值是低 32 位。CreateVectorSetIarRawsub_1D4DF260)只是 CreateVectorSetIarHelper(opcode = 0x03, …)SetIarLane/SetIarSublane/SetIarRaw 全部路由到同一个 helper。

QUIRK — Lane / Sublane / Raw 的区别不在 64-bit 值中。三种形式的 value field 完全相同。硬件写入模式编码在 ISA bundle-slot opcode 中(见下文);LLO-opcode 编号(0x02/0x03/0x04)和 bundle-slot opcode(2/3/4)并不对齐:LLO SetIarRaw = 0x03 映射到 slot opcode 4,而 LLO SetIarSublane = 0x04 映射到 slot opcode 3。若重新实现时假设 LLO-opcode == slot-opcode,就会把 Sublane 与 Raw 编码交叉弄错。

Bundle-Slot Encoding (PxC / TensorCoreVectorStore)

在 ISA 层面,三个 SetIar 形式只在 word@0x18 的一个 5-bit opcode 子字段中不同;它们的 operand accessor 按字节完全相同。IAR register select 是单个 bit,这也把 IarsPerTensorCore 固定为 2。

NOTE — bit 编号。 本页所有绝对 bit 位置都是 LSB-first,与 Bundle Model 中记录的通用 v5+ packer 约定一致:bit 0 是 byte 0 的最低有效位,因此 word@0x18 bit 13 是 byte 0x18 处 8-byte little-endian word 的 bit 13,而 predicate-mask word@0x18 & 0x3e0000000 选择同一个 word 的五个 bit 33..37。encode/decode 路径中没有任何 MSB-first 顺序。

形式匹配 predicate (word@0x18 & 0x3e0000000)slot opcodeaccessor sub_ADDR
SetIarLane== 0x400000002sub_1EE390E0
SetIarSublane== 0x600000003sub_1EE39100
SetIarRaw== 0x800000004sub_1EE39120
字段Bit 位置宽度已确认 accessor
IarField(哪个 IAR 寄存器)word@0x18 bit 131 bit(IAR0/IAR1sub_1EE3B380>>13 & 1
VsrcField(source VREG)byte@0x1b5 bitssub_1EE3B360& 0x1f

read/use 侧位于 TensorCoreVectorLoad family(major opcode word@0x10 bits[60:62]=7)中,word@0x18 bit 11 处的 2-bit 子字段选择形式:VectorLoadIndexed0 = 0,VectorLoadIndexed1 = 1,ReadIar0 = 2(ReadIar1 是 complement form,这里未 bit-resolved 到单个整数)。indexed-load 携带 DestVreg(6-bit @bit5)、Stride(4-bit @byte0x17)、跨 word 的 2-bit BaseAddress,以及 4-bit SublaneMask,但没有 Index 字段。

IarsPerTensorCore — 寄存器数量

IarsPerTensorCore() 是单条指令的 accessor,且其值不是代码常量:

c
function Target::IarsPerTensorCore(this):   // sub_1D617280
    return *(uint32*)(this + 0x4a8);
```text

`Target+0x4a8` 的唯一写入者是共享的 `Target::Init`(`sub_1D60FC20`);没有逐代 `*Target` 构造函数会写它。`Init` 从嵌入的逐代 `*_chip_parts.binarypb` proto 的 `VectorIsa` 字段 7 读取它(运行时通过 `embed://tpu_chip_parts/<version>_chip_parts.binarypb` 加载)。从嵌入 blob 抽取出的值在**每一代都是 2**,IAR 文件因此跨代稳定,并与 1-bit `IarField` 上限匹配。

| Gen(codename) | IarsPerTensorCore(`Target+0x4a8`) | 来源 |
|---|---|---|
| v2 Jellyfish / v3 Dragonfish | 2 | `jellyfish`/`dragonfish` blob `VectorIsa.f7` |
| v4 Pufferfish | 2 | `pufferfish` blob |
| v5p Viperfish | 2 | `viperfish` blob |
| v6e Ghostlite | 2 | `ghostlite` blob |
| v7x (6acc60406) | 2 | `6acc60406` blob |

> **NOTE —** 这个值是数据,不是代码,因此上面的版本 pin 必要但不充分:未来某个芯片的 `VectorIsa.f7` 若不同,就会拓宽 `IarField`。1-bit `IarField` accessor(`sub_1EE3B380`)和计数 2 只对这个 binary 的 chip-parts blob 相互一致。加载路径见 [Chip-Parts Binarypb](../targets/chip-parts-binarypb.md)。

### IAR Cost — Perf-Row Sentinel 划分

cost classifier 以 sentinel `S = ((iar & 0x1ffffffff) == 0x100000000)` 为键选择 indexed-memory perf row;也就是 *IAR present 且 index 值为零*(对齐 base gather,较便宜的 row)与非零 index 值(offset-add row)的区别。Ghostlite classifier `GetGhostliteInstruction`(`sub_1C8B1740`)的七个 IAR-class arm 按字节精确如下;四个 `ReadIar`/`SetIar` arm 还会在 bit 32 未设置时额外 `FATAL`(`"iar.has_value()"`),而三个 indexed-memory arm(`LoadIndexed`、`StoreIndexed`、`StoreIndexedMsk`)会无条件读取 `iar()`,并容忍缺失的 IAR。

```c
// the IAR-class arms of GetGhostliteInstruction, verified in the decompile
case 0x01 ReadIar:        if (!(iar & 1<<32)) FATAL;  return 2*((u32)iar != 0) + 0x18c;  // 0x18c / 0x18e
case 0x02 SetIarLane:     if (!(iar & 1<<32)) FATAL;  return 0x1d5 - ((u32)iar == 0);     // 0x1d4 / 0x1d5
case 0x03 SetIarRaw:      if (!(iar & 1<<32)) FATAL;  return 0x1d9 - ((u32)iar == 0);     // 0x1d8 / 0x1d9
case 0x04 SetIarSublane:  if (!(iar & 1<<32)) FATAL;  return 0x1d7 - ((u32)iar == 0);     // 0x1d6 / 0x1d7
case 0x32 LoadIndexed:    /* no bit-32 gate */         return 2*((iar & 0x1ffffffff) != S) + 0x188; // 0x188 / 0x18a
case 0x40 StoreIndexed:   /* no bit-32 gate */         return ((iar & 0x1ffffffff) == S) ^ 0x1d1;    // 0x1d0 / 0x1d1
case 0x44 StoreIndexedMsk:/* no bit-32 gate */         return ((iar & 0x1ffffffff) == S) ^ 0x1d3;    // 0x1d2 / 0x1d3
LLO op名称arm sub_ADDRsentinel-S rownon-S row
0x01kVectorReadIarsub_1C8B1A940x18c(low32==0)0x18e
0x02kVectorSetIarLanesub_1C8B19DE0x1d40x1d5
0x03kVectorSetIarRawsub_1C8B1A710x1d80x1d9
0x04kVectorSetIarSublanesub_1C8B19590x1d60x1d7
0x32kVectorLoadIndexedsub_1C8B19260x1880x18a
0x40kVectorStoreIndexedsub_1C8B19AD0x1d00x1d1
0x44kVectorStoreIndexedMaskedsub_1C8B197C0x1d20x1d3

这些 GhPerf::Instruction ordinal 的符号化 enumerator names 不在 binary 中(没有 ToString);ordinal 按字节精确,但未命名(这是与 matmul perf row 共享的统一空缺)。


Latch — 加载增益矩阵

用途

latch op 将准备好的静态增益(权重)矩阵加载到 MXU 的逐象限 weight bank 中。加载是 weight-stationary 的:一次 latch 会在多次 matmul step 间摊销。增益以何种方式加载(transpose、dtype packing、byte-plane staging)由 GainLatchMode(GLM)操作数决定。这个 family 有十个 opcode:一对 "load-stationary-from-FIFO"(Lsf),一对普通形式,以及三对 indexed sub-bank 形式。

Op Family 和 Builder

每个 LloRegionBuilder::Vlatch* wrapper 都路由到一个 LloInstruction::Create* constructor,后者再路由到 CreateVectorLatchLsf(LSF 特例)或 CreateVectorLatchHelper(通用)。

LloOpcode名称builderconstructor
0x8dkVectorLatchLsfVlatchLsfCreateVectorLatchLsfsub_1D4D7AA0
0x8ekVectorLatchLsfMskVlatchLsfMskCreateVectorLatchLsfMaskedsub_1D4D8140
0x8fkVectorLatchVlatchCreateVectorLatchsub_1D4D8900
0x90kVectorLatchMskVlatchMskCreateVectorLatchMaskedsub_1D4D8C40
0x91kVectorLatch1Vlatch1CreateVectorLatch1sub_1D4D8940
0x92kVectorLatch1MskVlatch1MskCreateVectorLatch1Maskedsub_1D4D8C80
0x93kVectorLatch2Vlatch2CreateVectorLatch2sub_1D4D8A80
0x94kVectorLatch2MskVlatch2MskCreateVectorLatch2Maskedsub_1D4D8CC0
0x95kVectorLatch3Vlatch3CreateVectorLatch3sub_1D4D8B60
0x96kVectorLatch3MskVlatch3MskCreateVectorLatch3Maskedsub_1D4D8D00

VlatchI(value, long idx, glm)sub_1D574580)把它的 long idx 分派到 Vlatch10x91)或 Vlatch20x93);indexed latch 选择一个 sub-bank。通用 Create* 路径都经过 CreateVectorLatchHelper

LloInstruction Field Layout

构造出的 latch op 在以下字段中携带操作数;这些字段由 setter 及其对称 reader 按字节精确恢复:

偏移字段Setter / reader含义
WORD[+0x00]LloOpcodeNew()0x8d..0x96
BYTE[+0x0a]register_numberset_register_number / sub_1D5A8E20gain-source VREG 编号
WORD[+0x0b]control wordset_unit_id / ValidateAndSetMxuAndSourceBusunit-id + source-bus(见下)
BYTE[+0x40]latch_mode(GLM)set_latch_mode sub_1D4D7C20 / latch_mode sub_1D4E7500GainLatchMode
WORD[+0x42]latch_index_in_sequenceset_latch_index_in_sequence sub_1D4E7960SetLatchIndices 分配
BYTE[+0x44]matrix_staging_register(Msr)set_matrix_staging_register sub_1D4D7D40latch-bank / MSR destination

control word WORD[+0x0b] 是两个打包 bitfield:

c
// LloValue::set_unit_id (sub_12698C00) — the GMR / MXU-quadrant pack
WORD[v+0x0b] = (WORD[v+0x0b] & 0xf8ff) + ((unit & 3) << 8) + 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)
WORD[v+0x0b] = (WORD[v+0x0b] & 0xc7ff) + ((bus & 3) << 11) + 0x2000;
//   bits 11-12 : VEX source-bus (0..3)
//   bit  13    : has-source-bus flag (0x2000)
```text

> **GOTCHA —** `WORD[op+0x42]` 是 latch family(`0x8d..0x96`)的 latch index,但 `BYTE[op+0x42]` 是 *load-LMR* family(`0xaa`/`0xab`)的 MSR。它们共享字节地址,但适用于**互不相交**的 opcode family,因此单个 op 内不存在 aliasing。若重新实现时不先检查 opcode family 就读取 `+0x42`,会误解码其中一类。

MSR setter 是 opcode-multiplexed;同一个字段名会落在四个不同偏移:

```c
function set_matrix_staging_register(op, msr):   // sub_1D4D7D40
    switch opcode_family(op):
        case 0x9b..0xa5 (matmul):        BYTE[op+0x46] = msr
        case 0x8d..0x96 (latch):         BYTE[op+0x44] = msr   // ← the latch family
        case 0xaa/0xab  (load-LMR):      BYTE[op+0x42] = msr
        case 0xa8       (done-with-gains): BYTE[op+0x41] = msr
        default: FATAL "msr unsupported for opcode"

CreateVectorLatchLsf — LSF 构建序列

CreateVectorLatchLsf 是规范的 latch constructor(VlatchLsf 是 append 它的 wrapper)。它检查 gain source 和 GLM,然后写入字段:

c
function CreateVectorLatchLsf(gain_src, glm, unit_id, region):   // sub_1D4D7AA0
    if (opcode_produced_register_type[gain_src.opcode] != 4)     // gain source must be reg-type 4
        UpdateStatus("chunk->ProducesVreg()")                    // slow diagnostic path otherwise
    if (glm > 0x33 || !bittest(0xf0000003c0c03, glm))            // LSF GLM-validity mask
        FATAL "LSF latch mode not expected."
    op = LloInstruction::New(0x8d /*kVectorLatchLsf*/, {gain_src}, region)
    set_latch_mode(op, glm)                                      // BYTE[op+0x40]
    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
```text

`VprepareForLatch`(`sub_1D573BA0`)在 constructor 前运行:如果某代并不原生支持 `SupportsGainLatchMode(glm)`(vtable `+0x368`),它会在重新检查前把 gain source 改写成软件 byte-plane 表示。两个 constructor 接受不同的 GLM 集:

| Constructor | 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(`>= 0`,`< MxusPerTensorCore()` = `Target+0x4ac`),写入 unit-id,并且仅当 `HasVexSourceBuses()`(vtable `+0x408`,**仅 Pufferfish 为 true**)且 `LloOpcodeUsesSourceBus(op)`(`0x8f..0x96` 为 true,`0x8d`/`0x8e` LSF 形式为 false)时写入 source-bus。因此 VEX source-bus 字段只在 Pufferfish(v4)上填充,并且只用于非 LSF latch op。

### First-Latch Overrun Handshake

`SetLatchIndices` 为序列中的每个 latch op 分配程序顺序 index,但**第一个** latch 只有在其 GLM 携带 overrun checks 时才会编号。该 gate 是逐代 `GainLatchModeHasOverrunChecks`(vtable `+0x358`):

```c
function SetLatchIndices(span<MxuSequence*>):    // sub_10F3B4C0
    for each seq in span:
        for idx = 0 .. seq.latches.count - 1:        // latches list @ seq+0x18, count @ seq+0x20
            op = seq.latches[idx]
            check LloOpcodeIsVectorLatch(op)         // (opcode - 0x8d) < 0xa, else FATAL
            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

五个世代中有四个是平坦 FALSE;它们的第一个 latch 永远不会编号。Viperfish 是唯一具有该 handshake 的世代,并且只针对宽的非 bf16 NO_XPOSE mode:

c
function ViperfishTarget::GainLatchModeHasOverrunChecks(glm):   // sub_1D49AB20
    if (LatchModeIsTranspose(glm)) return false;                // transpose ⇒ no overrun
    fmt = GainLatchModeToMatmulDataFormat(glm);
    return MatmulDataFormatIsIntegral(fmt) | ((fmt - 3) < 2);   // ⇒ fmt ∈ {3,4,5,6,7,8}
```text

| Gen | `GainLatchModeHasOverrunChecks`(`+0x358`) | `HasMsrOverrunChecks` |
|---|---|---|
| Jellyfish(`sub_1D4925E0`) | `FALSE`(always) | `FALSE` |
| Dragonfish(`sub_1D4901C0`) | `FALSE` | `FALSE` |
| Pufferfish(`sub_1D494880`) | `FALSE` | `FALSE` |
| Viperfish(`sub_1D49AB20`) | non-transpose AND fmt∈{3..8} → GLM `{14,16,18,20,22,24}` | **`TRUE`**(`sub_1D49AAC0`) |
| Ghostlite(`sub_1D497940`) | `FALSE` | `FALSE` |
| base Target(`sub_1D61D8C0`) | `LogFatal` stub | `LogFatal` |

> **NOTE —** Viperfish(TPU v5p)是唯一在 gen 级别拥有 MSR/first-latch overrun handshake 的世代,这正是它的逐 GLM `+0x358` override 是唯一非平凡主体、且 overrun-cost reservation 位于 Viperfish namespace 中的原因。完整 overrun 行为(first-latch index assignment、MSR reservation cost)见 [Latch Assignment & Overrun](../sched/latch-assignment-overrun.md)。

---

## Matprep — 逐代暂存操作数

### 用途

Matprep 暂存移动操作数(activations)并为 latching 准备增益矩阵。*真正的* matprep opcode 是 `kVectorMatprepSubr`(`0x97`/`0x98`,sub-row 形式)和 `kVectorMatprepMubr`(`0x99`/`0x9a`,block-row 形式),再加上 `kVectorMatmulLmr` / `kVectorDoneWithGains` / `kVectorLoadGmr` helper(`0xa5`/`0xa8`/`0xa9`)。它们不同于 gain-LATCH family(`0x8d..0x96`)。

关键的重新实现事实是,matprep **没有统一的 cost 表示**;每一代都以不同方式表达它,而重新实现者必须复现这种分歧:

| Family | Jellyfish/Dragonfish (v2/v3) | Pufferfish (v4) | Viperfish (v5p) | Ghostlite/GF (v6e/v7) |
|---|---|---|---|---|
| matmul | flat cell(LUT collapse 到 5 instrs) | raw 2-bit plane + base | `matmul_data_format → a2d05c0` | `matmul_data_format → a2d05d0` |
| matprep `0x97..0x9a` | 折叠进 matmul(transpose-of-gains) | 经由 Latch ops `0xdc`/`0xe6` | matprep ops **FATAL**;经由 matmul-fmt + modifier reservation | 固定二分搜索 rows |
| transpose accepted | `{B32}` only | `{B32,CompB16,SegB32,SegB16}` | `{B32,CompB16,SegB32,SegB16}` | `{B32,CompB16,CompB8}` |

### GL / GF — 固定二分搜索 Rows

在 Ghostlite/GF 上,matprep opcode 不在 classifier jump table 中(`opcode-10x96` 会落到 default-FATAL),所以它们通过 258-entry 二分搜索 remap(`@0x4067dc8`)解析为**固定** perf row;每个 matprep variant 一个 row,*不会*按 data format 展开:

| LLO op | 名称 | `GhPerf::Instruction` | GF flat latency |
|---|---|---|---|
| `0x97` | `kVectorMatprepSubr` | `0x120` | 1 |
| `0x98` | `kVectorMatprepSubrMsk` | `0x121` | (matprep) |
| `0x99` | `kVectorMatprepMubr` | `0x11c` | 1 |
| `0x9a` | `kVectorMatprepMubrMsk` | `0x11d` | 1 |
| `0xa5` | `kVectorMatmulLmr` | `0x154` |1 default(grid-priced) |
| `0xa8`/`0xa9` | `kVectorDoneWithGains` / `kVectorLoadGmr` | `0x157`(shared) |1 default |

matprep band `0x11c..0x121` 位于 matmul band `0x124..` 正下方;这些 row 携带 flat latency 1,并通过 resource grid 进行 throughput 计价。flat-latency-1 值来自 GF perf constructor `sub_1C8D3740`。

### VF — 折叠进 Matmul-Format 表 + Modifier Reservation

在 Viperfish 上,matprep opcode `0x97..0x9a` 会在 `GetViperfishInstruction`(`sub_1C8A3300`,default arm `sub_1C8A3E6A`)中 **FATAL**。matmul opcode `0x9b` 读取 `matmul_data_format()` 并索引新的 VF 表 `a2d05c0`:

| `MatmulDataFormat` | dtype | VFinstr ordinal | flat latency | grid(r2 prep / r3 throughput) |
|---|---|---|---|---|
| 1 | f32 | `0xd4` | 131 | r2:7 r3:8 |
| 2 | bf16 | `0xda` | 131 | r2:7 r3:16 |
| 3 | f8e5m2→bf16 | `0xf8` | 131 | r2:7 r3:32 |
| 4 | f8e4m3b11→bf16 | `0xfe` | 131 | r2:7 r3:32 |
| 5 | u8 | `0xe0` | 121 | r3:16 |
| 6 | s8 | `0xe6` | 121 | r3:16 |
| 7 | u4 | `0xec` | 121 | r3:16 |
| 8 | s4 | `0xf2` | 121 | r3:16 |

throughput port `r3` 是逐格式 reservation width:f32=8、bf16=16、fp8=32、int8/int4=16。bf16-class(`0xd4..0xfe`)携带独立 prep port(`r2:7`)和 base latency 131int-class(`0xe0..0xf7`)去掉 `r2`,使用 121。每个 matmul-format ordinal 后面跟着一组 matprep-stage ordinal(例如 `0xd4` 和 `0xda` 之间的 `0xd5/0xd6/0xd8/0xd9`),它们添加 4-stage systolic-feed pipeline `r4:4 r5:12 r6:20 r7:28`。

matprep stage 不携带独立 classifier ordinal;它们由 `MxuLatencyTable::GetResourceUsage`(`sub_1C8AE5C0`)生成,该函数构建 `MatpushModifier { MatmulDataFormat, is_transpose, Msr }` key,并在 `FlatHashMap<Modifier, array<int,19>>` reservation table 中查找(matprep `r4..r7` stage 是 19 个 `MxuResource` port 中的 4 个)。见 [Matmul-Mode Modifiers](../cost/matmul-mode-modifiers.md)。

### PF — 折叠进 Latch Ops

在 Pufferfish 上,matprep opcode `0x97..0x9a` 同样 FATAL(default arm `sub_1C8A2A08`)。PF 通过单 ordinal 的 Latch / LatchMsk arm 表达 matprep,并由 `SupportsGainLatchMode`(vtable `+0x368`)gate:

| LLO op | 名称 | arm `sub_ADDR` | PFinstr |
|---|---|---|---|
| `0x8f` | `kVectorLatch` | `sub_1C8A2781` | `0xdc` |
| `0x90` | `kVectorLatchMsk` | `sub_1C8A226F` | `0xe6` |

`0xdc`/`0xe6` 是 PF "MXU matprep band" 的入口;PF 的 matprep 就是这一对单独的 Latch/LatchMsk,通过 resource-grid port 进行 throughput 计价。

### JF / DF — Transpose-of-Gains 折叠进 Matmul

在 Jellyfish/Dragonfish 上没有独立的 matprep classifier;`CycleTableInstruction`(`sub_1C89CA80`)通过 LUT 将 11 个 `MatmulDataFormat` 值折叠为 5 条 instruction,而 gain 的转置折叠进 matmul opcode:`EmitVectorMatmul`(`sub_140B92C0`)基于 `DoneWithGainsMode == 2 (TRANSPOSED)` 分派 VEOpcode:`0x9b0/4`、`0x9d2/6`、`0x9e1/5`。独立 transpose 只接受 `VxposeMode 0`(B32)→ VEOpcode `0xf`;其他所有 mode 都 FATAL("JFC/DFC only support B32 transpose instructions")。

> **QUIRK —** matprep 的*表示*在各代之间迁移:JF 将它吸收到 matmul 中,PF 吸收到 Latch ops 中,VF 吸收到 matmul-format 表外加 modifier reservation 中,只有 GL/GF 给每个 matprep variant 一个专用固定 perf row。若重新实现时假设各代共享同一种 matprep cost model,会错误计价五代中的四代。

---

## MxuSequence Record

### 用途

`MxuSequence` 是 `MxuAssigner` 迭代的逐序列记录:`SetLatchIndices` 对 latch 排序,`LatchLhs` 划分增益矩阵并发出 latch+matmul+matres ops,而 `AllocateMrb`/`Bounce`(输出侧)分配 result-FIFO 地址和 MSR bank。它持有五个 instruction list;逐 instruction 的 "latch state" 分布在成员 `LloInstruction` 上,而不是存成平坦 scalar。

### 布局(sizeof `0x78`)

从 deleter `default_delete<MxuSequence>::operator()`(`sub_14504C00`)恢复;它释放五个 `{ptr, count, cap}` 列表,然后 `free(seq, 0x78)`:

| 偏移 | 列表 | 元素 opcode / consumer |
|---|---|---|
| `+0x00` | list0(setup/head latches) | head-of-sequence(按类别推断) |
| `+0x18` | latches / matpushes | `0x8d..0x96` — `SetLatchIndices` count @`+0x20`;`Bounce` MSR stamp |
| `+0x30` | list2(prep / xpose aux) | matprep/transpose `0xa6`/`0xa7`(按类别推断) |
| `+0x48` | matreses | `0x152` — `AllocateMrb` pop;`LatchLhs` ΣPackingFactor,count @`+0x50` |
| `+0x60` | matmuls | `0x9b`/`0xa3` — `AllocateMrb` push;`LatchLhs` balance,count @`+0x68` |
| `0x78` | sizeof | `free(seq, 0x78)` |

`+0x18`、`+0x48`、`+0x60` list 身份按字节精确(由 deleter 和三个独立 consumer 确认);`+0x00` 和 `+0x30` 身份按类别推断。完整 record 和 `set_mxu` commit 位于 [MxuSequence / SequenceInfo](../sched/mxu-sequence-struct.md)。

### LatchLhs — 增益矩阵划分

`LatchLhs`(`sub_10F3B5E0`)是 latch+matmul+matres ops 的生产者,`SetLatchIndices` 稍后会给这些 op 编号。它按 transpose op 对 LHS 分组,运行逐 MXU capacity guard,然后用每个 op 的 MXU quadrant 标记重建序列:

```c
function LatchLhs(target, lhs_span, sequences):   // sub_10F3B5E0
    xpose = BuildXposeSequences(lhs_span)          // vec1 = {0xa6,0xa7}, vec2 = {0x154}
    // capacity guard per sequence
    acc = Σ over matreses of MatmulDataFormatPackingFactor(matmul_data_format(op))
    check( ChunksPerTile() * num_mxus >= acc )     // ChunksPerTile = hwcfg[+0x198]/hwcfg[+0x1a0]
    check( acc % ChunksPerTile() == 0 )            // tile-aligned, num_mxus = Target+0x4ac
    // rebuild per quadrant
    for each matmul:
        q   = program_order & 3                    // the MXU quadrant (0..3)
        glm = GLM_byte_table[matmul_op - 0x9b]     // @0xac0913e: {0×8, 0xb, 0xb} ⇒ plain→0, packed→0xb
        VlatchLsf(value, glm, 0)                    // emit kVectorLatchLsf (sub_1D573EC0)
        WORD[emit+0x0b] = (WORD[+0x0b] & 0xf8ff) | ((q<<8)+0x400)   // set_unit_id(q)
        repeat Vmatmul / Vmatres PackingFactor(fmt)× (K-tile split), each unit_id-stamped

MatmulDataFormatPackingFactorsub_1D629300)索引表 @0xb53c6bc = {1,2,4,4,4,4,8,8,4,4}(fmt 1..10);这是驱动 K-tile loop count 的 column-pack factor。unit_id(= MXU quadrant)是 gain-matrix-register bank;GLM 是 latch mode;MSR(输出侧)是 staging bank。


相关组件

名称关系
MXU Slot消费已 latch 的 gains 和 matprep 后的 operand;matmul step 本身
Jellyfish 41-Byte Bundlev3 VectorExtended 编码,用于序列化这些字段
Latch Assignment & OverrunSetLatchIndices + 逐代 overrun handshake(调度侧)
MxuSequence / SequenceInfo完整 sequence record 和 set_mxu commit
Matmul-Mode ModifiersVF Modifier → array<19> matprep reservation table

交叉引用