Skip to content

MRB FIFO / MSR 放置

地址适用于 libtpu-0.0.40-cp314 wheel 中的 libtpu.so(BuildID md5 89edbbe81c5b328a958fe628a9f2207d剥离符号 -- 下文每个符号都是已 demangle 的 C++ 名称)。节映射:.text/.rodata VMA == 文件偏移;.data.rel.ro VMA - 0x200000 == 文件偏移。其他版本会不同。

摘要

这是 Stage 2 矩阵资源分配的物理放置层MRB chain allocator 在程序顺序的预约时间线上决定每条 matmul 累加链占用哪个 matrix-result-buffer (MRB) chunk 和 FIFO 索引 -- 它给每条链交付一个 MrbEntry = {chunk_id, fifo_index, format}。随后两个 MxuAssigner 方法把这些抽象预约转换成每代 encoder 序列化进 bundle word 的两个具体属性:AllocateMrbEntriesAsFifo (0x10f3ef80) 为每个 matmul 和每个 matres 分配一个物理 result-FIFO slot address,而 BounceBetweenMsrs (0x10f3fae0) 为每个 MxuSequence 分配一个物理 matrix-staging-register (MSR) bank -- msra (0) 或 msrb (1)。二者都在每个 MXU quadrant 上运行一次,由 MxuAssigner::VisitRegion 在一个 array<Span<MxuSequence>,4>(一个 region 的四个 MXU 实例)上分发。

熟悉的参考框架是一个硬件环形缓冲区配上双缓冲的 staging latch。result FIFO 是逐 MXU 的环形缓冲区:AllocateMrbEntriesAsFifo 为每个 MXU 维护两个游标 -- 写游标(在 matmul/push 一侧推进)和读游标(在 matres/pop 一侧推进)-- 每个游标都按每个 op 的 entry 数推进,向上舍入到写块粒度,并按 FIFO 深度取模回绕。MSR bounce 是 weight-stationary array 的经典 ping-pong:当 msra 喂给一个序列的 matmul 时,下一个序列的 gain matrix stage 到 msrb,随后 bank 交换;BounceBetweenMsrs 通过在每个序列开头切换一位 bank 索引,并经 set_matrix_staging_register 把它盖到每个 op 上来实现这一点。唯一的特殊点是 LMR/XMR 路径:vector-matmul-lmr(opcode 0xa5)直接从 load-matrix register 读取 gain matrix,而不通过 MSR stage,所以 bounce 对它没有意义,整个 pass 会提前返回。

本页逐字节记录这两个放置函数、allocator 携带给它们的两个 struct layout(MrbChunkState -- 实际上是两个不同的逐 chunk 数组 -- 以及 0x70 字节的 AccumulationChainAfterSplit 记录),以及下游消费者(LloInstruction::set_mrb_address -> set_mrb_address_unrestricted,matmul 家族在 +0x42 的字段,由 mrb_address 读回)。它重新推导预约时间线(那属于 chain allocator),也不重新推导 MSR/MRB 字段的 bundle bit 位置(那些见 MXU slot 和每代 bundle 页面)。

重新实现时,契约是:

  • FIFO 纪律 -- 两个按 num_mxus 定长的 int32 游标数组,逐 MXU 选择 unit_id & 3,按 MxuResultEntriesPushed/Popped 推进,向上舍入到 MinMrbWriteBlockSize,按 ResultFifoEntryCount(kMrf0, version) 取模回绕,并由 set_mrb_address 提交。
  • MSR bounce -- 0xa5 提前退出的预扫描、逐序列的 msr ^= 1 切换,以及 set_matrix_staging_register 的逐 op 家族目标字节(+0x46/+0x44/+0x42/+0x41)。
  • 两个 struct layout -- 0x70 字节的逐 MXU 时间线元素与 0x40 字节的逐 chunk free-pool 记录,以及 0x70 字节的 AccumulationChainAfterSplit,全部由 MrbEntry.chunk_id 索引。
  • 每代 accessor 表面 -- 六个为放置定价的 Target vtable slot,以及该路径在 v0.0.40 中为什么按 generation gate 掉(已发布的每个 TensorCore target 都返回 MatmulResultBufferEntries() == 0)。
FIFO 放置MxuAssigner::AllocateMrbEntriesAsFifo(Target const&, Span<unique_ptr<MxuSequence>>) @0x10f3ef80
MSR bounceMxuAssigner::BounceBetweenMsrs(Target const&, Span<unique_ptr<MxuSequence>>) @0x10f3fae0
逐 quadrant 分发MxuAssigner::VisitRegion @0x10f3a640 -> array<Span,4> lambdas $_5 @0x10f40a80, $_6 @0x10f40b60
物理 FIFO 写入LloInstruction::set_mrb_address(int) @0x1d4d93c0 -> set_mrb_address_unrestricted(int) @0x1d4e9780(字段 +0x42
MSR 写入LloInstruction::set_matrix_staging_register(Msr) @0x1d4d7d40(字段 +0x46/+0x44/+0x42/+0x41
FIFO 深度ResultFifoEntryCount(kMrf0=8, TpuVersion) @0x1d631520;表 @0xb53e240 = {16,16,16,48,224,256}
Msr enumMsrToString @0x1d6297200 -> "msra"1 -> "msrb"
承载位置MrbChainAllocator 逐 MXU 时间线 @alloc+0xf0(stride 0x70),逐 chunk free pool @alloc+0x7a0(stride 0x40
来源platforms/xla/service/jellyfish/mxu_assigner.cc, mxu_accumulation.cc, llo_instruction.cc
置信度CONFIRMED(字节锚定),除非某行或 callout 另有说明

逐 Quadrant 分发

目的

两个放置函数都是 region-localquadrant-localMxuAssigner::VisitRegion (0x10f3a640) 按物理 MXU 把 region 的 MxuSequence 分成一个 array<Span<unique_ptr<MxuSequence>>,4> -- 每个 MXU 实例一个 span -- 并把该数组送入两个 InvokeObject lambda:$_5 (0x10f40a80) 调用 AllocateMrbEntriesAsFifo 四次(每个 quadrant span 一次,把 [rcx+0/0x10/0x20/0x30] 指针与 [rcx+8/0x18/0x28/0x38] 长度配对);$_6 (0x10f40b60) 以同样方式调用 BounceBetweenMsrs 四次。每个 lambda 如果某次调用返回非 OK Status 就提前退出。每个 lambda 捕获的第一个 word 是 Target& proxy。

结构

text
MxuAssigner::VisitRegion                          0x10f3a640
  build array<Span<unique_ptr<MxuSequence>>, 4>   ── one span per MXU instance (quadrant)
  ├─ $_5  InvokeObject(AllocateMrbEntriesAsFifo)   0x10f40a80
  │     AllocateMrbEntriesAsFifo(target, quadrant[0])     0x10f40abe
  │     AllocateMrbEntriesAsFifo(target, quadrant[1])     0x10f40ad2
  │     AllocateMrbEntriesAsFifo(target, quadrant[2])     0x10f40ae6
  │     AllocateMrbEntriesAsFifo(target, quadrant[3])     0x10f40afc
  │       └─ early-out if Status != OK (1)
  └─ $_6  InvokeObject(BounceBetweenMsrs)          0x10f40b60
        BounceBetweenMsrs(target, quadrant[q]) × 4        0x10f40f91…
```text

> **QUIRK -- FIFO 游标是在一次 quadrant 调用内部逐 MXU 的,而不是全局的。** `AllocateMrbEntriesAsFifo` 在函数入口分配两个游标数组,并在函数返回时释放,所以四次 quadrant 调用都会让两个游标从零开始。数组大小为 `num_mxus`,每个元素的索引来自 `unit_id & 3`(逐 op 的 MXU 实例,`HIBYTE(WORD[+0xb]) & 3`),因此在一次调用内部,游标按 MXU 独立。把游标提升到 region 作用域(跨 quadrant 携带 FIFO 写位置)的重新实现会发散 -- binary 会在每次调用重置它们。硬件究竟期望每个 quadrant region 都有一个新的 FIFO,还是跨 region 延续,这里**未追踪**(此处游标生命周期严格限于调用内)。

---

## FIFO 放置 -- `AllocateMrbEntriesAsFifo`

### 目的

按程序顺序遍历一个 quadrant 的 `MxuSequence`,并通过 `set_mrb_address` 为每个 matmul 和每个 matres 分配一个具体的 result-FIFO slot address。result FIFO 被建模成一个逐 MXU 的环形缓冲区,包含 `ResultFifoEntryCount(kMrf0, version)` 个 entry;matmul 向其中 push(推进写游标),matres 从其中 pop(推进单独的读游标)。

### 入口点

```text
AllocateMrbEntriesAsFifo(Target const& [rdi], Span<unique_ptr<MxuSequence>> [rsi=ptr, rdx=count])
  num_mxus      = Target[+0x4ac]                         ── 0x10f3ef9b  (movsxd; same field as LatchLhs)
  write_ptr[]   = operator new(4 * num_mxus); memset 0   ── 0x10f3efc4  ([rbp-0x48], matmul/push side)
  read_ptr[]    = operator new(4 * num_mxus); memset 0   ── 0x10f3efe0  ([rbp-0x68], matres/pop side)
  fifo_depth    = ResultFifoEntryCount(8, Target[+0x398])── 0x10f3f019
  for each MxuSequence in span: …

算法

c
// MxuAssigner::AllocateMrbEntriesAsFifo @0x10f3ef80
//   a1 = Target const&  (the MxuAssigner `this` is dead; rdi carries the Target&)
//   span = Span<unique_ptr<MxuSequence>>  (one MXU quadrant)
function AllocateMrbEntriesAsFifo(target, span):
    num_mxus = target[+0x4ac]                       // CONFIRMED Target field (== LatchLhs num_mxus read)
    if num_mxus > 0:
        write_ptr = new int32[num_mxus]; memset(write_ptr, 0)   // matmul/push cursor, [rbp-0x48]
        read_ptr  = new int32[num_mxus]; memset(read_ptr,  0)   // matres/pop cursor,  [rbp-0x68]
    fifo_depth = ResultFifoEntryCount(kMrf0 /*=8*/, target[+0x398] /*DeepseaVersion*/)  // @0x1d631520

    for seq in span:
        RET_CHECK(!seq->matmuls.empty())            // mxu_assigner.cc:881  ([seq+0x50] count)
        // per-MXU quadrant select: bits 8..9 of LloValue WORD[+0xb], gated by the 0x400 "has mxu" bit
        seq_unit = (seq->matmuls[0].word[0xb] & 0x400) ? ((HIBYTE(word) & 3) | HAS_VALUE) : 0
        RET_CHECK(seq->matmuls[0]->unit_id().has_value())       // mxu_assigner.cc:882

        matres_index = 0
        for matmul in seq->matmuls:                 // [seq+0x48] ptr, [seq+0x50] count
            fmt  = matmul.matmul_data_format()      // @0x1d4e8440
            push = target.MxuResultEntriesPushed(matmul_op, fmt)    // vtbl[+0x5f0]
            unit = matmul->unit_id()
            RET_CHECK(unit.has_value())             // mxu_assigner.cc:890
            RET_CHECK(unit == seq_unit)             // mxu_assigner.cc:893 "same unit id"
            mxu  = unit & 3                          // per-MXU index into the cursor arrays

            // ---- THE PHYSICAL WRITE (matmul push side) ----
            set_mrb_address(matmul, write_ptr[mxu])             // @0x1d4d93c0  ← FIFO slot
            write_ptr[mxu] += push                              // advance by entries this matmul pushes
            g = target.MinMrbWriteBlockSize()                   // vtbl[+0x5e0], a byte (idiv divisor)
            write_ptr[mxu] = ceil_div(write_ptr[mxu], g) * g    // round UP to the write-block granule
            write_ptr[mxu] %= fifo_depth                        // wrap into the FIFO

            // ---- THE MATRES POP SIDE (only when this matmul pushes entries) ----
            if push > 0:
                base = read_ptr[mxu]
                acc  = 0
                while acc < push:                   // drain `push` worth of result entries
                    matres = seq->matreses[matres_index]        // [seq+0x60] ptr, [seq+0x68] count
                    RET_CHECK(matres_index < seq->matreses.size())   // mxu_assigner.cc:902 / :913
                    RET_CHECK(matres->unit_id().has_value())         // mxu_assigner.cc:924
                    RET_CHECK(*matres->unit_id() == unit)            // mxu_assigner.cc:925
                    rel = target.MrbRelativeAddressFromOffset(matres_op, acc)   // vtbl[+0x5e8]
                    set_mrb_address(matres, (base + rel) % fifo_depth)          // @0x1d4d93c0
                    acc += target.MxuResultEntriesPopped(matres_op, fmt)        // vtbl[+0x5f8]
                    matres_index += 1
            read_ptr[mxu] = ceil_div((read_ptr[mxu] + push), g_pop) * g_pop % fifo_depth   // symmetric

        RET_CHECK(matres_index == seq->matreses.size())          // mxu_assigner.cc:949 "too many matreses"
    return OK
```text

向上舍入算术被生成为有符号 `idiv` 加上标准的“是否向零截断”修正,正如 decompiler 在 `0x10f3f218` 处渲染的那样:

```c
// write_ptr[mxu] = ceil(write_ptr[mxu] / g) * g, for a signed dividend (0x10f3f1f8..0x10f3f234)
q = write_ptr[mxu] / g;                                  // idiv
write_ptr[mxu] = g * (q + ((write_ptr[mxu] > g*q) & (q >= 0)));   // bump quotient if there was a remainder
write_ptr[mxu] %= fifo_depth;                            // final wrap

GOTCHA -- matmul 游标和 matres 游标彼此独立。 write_ptr(matmul push)和 read_ptr(matres pop)分别推进;读游标不是从写游标派生的。matres 地址是 (read_base + MrbRelativeAddressFromOffset(op, acc)) % fifo_depth,其中 acc 累加 MxuResultEntriesPopped,直到达到该 matmul 的 push 数。把 matres result 放在产生它的 matmul 相同 slot(假定单个共享游标)的重新实现会把每个 result-pop 都编址错误;读/写偏移正是 chain allocator 的 reservation timeline 所定价的 result-FIFO occupancy model。

六个 Target Accessor

AllocateMrbEntriesAsFifo 完全通过 Target virtual 取得每代 geometry。这六个 slot 解析了此前 chain allocator 和 cost model 留下未命名的、推断出的 Target[+0x390]/[+0x5e0] slot。

Target vtable slotvptr offsetBase impl此路径上的角色
MatmulResultBufferEntries+0x3900x1d61da40MRB 支持 gate:set_mrb_address 除非 > 0 否则 abort
SupportsMatmulResultPack+0x3980x1d61da80兄弟项;不在此 hot path 上
MinMrbWriteBlockSize+0x5e00x1d490d80 (LogFatal)FIFO push 向上舍入粒度(idiv 除数)
MrbRelativeAddressFromOffset+0x5e80x1d490dc0 (LogFatal)pop 一侧的 (op, offset) -> relative MRB address
MxuResultEntriesPushed+0x5f00x1d61e9c0matmul push 的 entry 数;推进 write_ptr
MxuResultEntriesPopped+0x5f80x1d61ea00matres pop 的 entry 数;推进 matres_index

NOTE -- Target[+0x4ac]num_mxusTarget[+0x398] 是 version。 num_mxus (movsxd rcx,[rdi+0x4ac]) 与 MxuAssigner::LatchLhs (0x10f3b791, imul rcx, Target::ChunksPerTile()) 中读取的完全相同;Target[+0x398]tpu::TpuVersion[0,6) 中的 unsigned),用于索引 FIFO-depth 表。二者都经跨函数 CONFIRMED。

FIFO 深度表

ResultFifoEntryCount(ResultFifo fifo, TpuVersion ver) (0x1d631520) 对 fifo 参数做 switch;matmul-result FIFO kMrf0 = 8 落入从 .rodata 0xb53e240 读取 table[ver] 的分支。直接从 ELF 读取(struct.unpack):

TpuVersion ordinalGenerationResultFifoEntryCount(kMrf0, ver)
0Jellyfish16
1Dragonfish16
2Pufferfish16
3Viperfish48
4Ghostlite224
56acc60406 (TPU7x)256

版本 ordinal >= 6LogMessageFatal("invalid platform type:") 分支;该表正好有六个 live 行。这就是每个游标回绕的模数。

MxuResultEntriesPushed / Popped -- 逐 Op Entry 数

即使在 gate 掉 MRB 的 generation 上,这两个计数 accessor 也按 generation override。Viperfish override 是逐字节精确的参考:

c
// ViperfishTarget::MxuResultEntriesPushed @0x1d499ae0  (a2 = LloOpcode, a3 = MatmulDataFormat)
if a2 != 0xa5 /*vector-matmul-lmr*/:
    CHECK(a3-1 < 8)                                      // valid MatmulDataFormat
    return push_table[a3-1]   // @0xa2e6180 = {2,4,8,8,4,4,4,4}
else:
    CHECK((a3-2) in {0,3,4,5,6})                         // 0xa5 valid-format mask 0x79 (bits 0,3,4,5,6)
    return lmr_push_table[a3-2] // @0xb5307c0 = {2,0,0,1,1,1,1,0}

// ViperfishTarget::MxuResultEntriesPopped @0x1d499ba0
return 2 - MatmulDataFormatIsIntegral(fmt)              // @0x1d629240 : integral fmt → pop 1, float → pop 2
```text

两个 push 表都是直接从 `.rodata` 读取的:`0xa2e6180 = {2,4,8,8,4,4,4,4}`(索引 `fmt-1`)和 `0xb5307c0 = {2,0,0,1,1,1,1,0}`(`0xa5` 路径,索引 `fmt-2`)。Jellyfish base(`MxuResultEntriesPushed` `@0x1d492360`)是小型 `fmt1->1, fmt2->2` 规则;popped(`@0x1d492420`)是 `fmt1/2->1`。

### Result Check

`AllocateMrbEntriesAsFifo` 中密集分布着 `RET_CHECK`(`mxu_assigner.cc` 行号是 `RetCheckFailSlowPath` 的字面第三参数):

|| 条件 | 失败字符串 |
|---|---|---|
| 881 | `!sequence->matmuls.empty()` | (每个 sequence 开头的 empty-matmuls guard) |
| 882 | `sequence->matmuls[0]->unit_id().has_value()` | sequence unit id 存在 |
| 890 | `matmul->unit_id().has_value()` | 逐 matmul unit id 存在 |
| 893 | `unit_id == sequence_unit_id` | "All matmuls in a sequence must have the same unit id." |
| 902 / 913 | `matres_index < sequence->matreses.size()` | matres index 在范围内 |
| 924 / 925 | `matres->unit_id().has_value()` / `*matres->unit_id() == unit_id` | matres 属于同一 MXU |
| 949 | `matres_index == sequence->matreses.size()` | "Sequence had too many matreses for the number of matmuls" |
| -- | (matres-drain under-run) | "Sequence had too few matreses for the number of matmuls" (`@0x8547285`/`@0x854724c`) |

---

## MSR Bounce -- `BounceBetweenMsrs`

### 目的

为 quadrant 中的每个 `MxuSequence` 分配一个 matrix-staging-register (MSR) bank -- `msra` (0) 或 `msrb` (1) -- 并把它盖到每个 staging op 上。连续序列交替使用 bank,使得序列 *n+1* 的 gain matrix 能在一个 MSR 中 stage,同时序列 *n* 的 matmul 从另一个 MSR drain -- 即双缓冲的 weight-stationary ping-pong。每个 op 携带的 MSR 正是 [cost model 的 `GetResourceUsage`](../cost/mxu-latency-overview.md) 读取后用于收取逐 bank latch 预约的属性。

### 算法

```c
// MxuAssigner::BounceBetweenMsrs @0x10f3fae0  (a1 = Target const&; span = quadrant)
function BounceBetweenMsrs(target, span):
    // ---- PRE-SCAN: abort if any sequence uses the LMR/XMR matmul path ----
    for seq in span:
        RET_CHECK(!seq->matmuls.empty())                // mxu_assigner.cc:1063  ([seq+0x50])
        for op in seq->matmuls:                         // [seq+0x48] ptr, [seq+0x50] count
            if op.opcode == 0xa5 /*vector-matmul-lmr*/:
                return OK                                // 0xa5 reads gains from LMR, bypasses the MSRs

    // ---- BOUNCE: toggle the bank at the top of every sequence ----
    msr = 1
    for seq in span:
        msr ^= 1                                         // alternate: msra(0) ↔ msrb(1)
        for latch in seq->latches:                       // [seq+0x18] ptr, [seq+0x20] count (0x8d..0x96)
            set_matrix_staging_register(latch, msr)      // @0x1d4d7d40
        // first matmul of the sequence also carries the bank
        set_matrix_staging_register(seq->matmuls[0], msr)   // [seq+0x48][0]
    return OK

NOTE -- bank 会盖到 latch 列表和 matmuls[0],而不是 matres。 bounce 把 bank 写到 sequence 的 latch 列表([seq+0x18]/[seq+0x20],stationary-operand staging ops 0x8d..0x96)和 matmuls[0][seq+0x48][0])上。[seq+0x48]/[seq+0x50] 数组是 matmuls vector,这由 mxu_assigner.cc:1063 处守卫 [seq+0x50]RET_CHECK(!sequence->matmuls.empty()) 字符串钉住。完整 MxuSequence 子 vector 映射(与 MxuSequence struct deleter 和 SetLatchIndices 交叉检查)是:latches +0x18/+0x20,matmuls +0x48/+0x50,matreses +0x60/+0x68

为什么 0xa5 会中止整个 Pass

vector-matmul-lmr (0xa5) 从 load-matrix registerXmr 空间)读取其 stationary operand,而不是从两个 MSR staging bank 之一读取。Xmr enum 是 {0 -> "gmra", 1 -> "lmr"}XmrToString @0x1d629740),而 XmrToMsr (@0x1d629780) 只对 gmra (0) 返回 OK -- 把 lmr (1) 转成 MSR 是错误路径 "Cannot convert Xmr %s to Msr."matrix_register.cc:100)。因此 0xa5 op 没有可 bounce 的 MSR,整个 quadrant 的 bounce 会被跳过,而不是部分应用。

QUIRK -- quadrant 中任意位置的一个 0xa5 会为所有 sequence 抑制 bounce。 预扫描先遍历每个 sequence 的每个 matmul;单个 0xa5 就会让 BounceBetweenMsrs 在分配任何 bank 之前返回。逐 sequence bounce、只跳过 0xa5 sequence 的重新实现,会给 binary 保持未分配的非 LMR sequence 分配 MSR bank。

set_matrix_staging_register -- 目标字节

bank 字节会根据 opcode 写入不同的逐 op 家族字段。分发是一串范围测试(LloOpcode 是 16-bit *a1):

c
// LloInstruction::set_matrix_staging_register(Msr) @0x1d4d7d40  (a2 = msr byte 0/1)
op = *a1;                                       // LloOpcode (uint16)
if      ((uint16)(op - 0x9b) <= 0xA):  a1[+0x46] = msr;   // 0x9b..0xa5  vector-matmul / -lmr
else if ((uint16)(op - 0x8d) <= 0x9):  a1[+0x44] = msr;   // 0x8d..0x96  vector-latch family (kVectorLatch*)
else if ((op & 0xFFFE) == 0xAA):       a1[+0x42] = msr;   // 0xaa/0xab   vector-load-lmr / -bf16
else if (op == 0xA8):                  a1[+0x41] = msr;   // 0xa8        vector-done-with-gains
else:  FATAL("msr unsupported for opcode: ");             // llo_instruction.cc:3414
```text

| `LloOpcode` 范围 | Op 家族 | 目标字段 |
|---|---|---|
| `0x9b..0xa5` | vector-matmul / vector-matmul-lmr | `+0x46` |
| `0x8d..0x96` | vector-latch family (`kVectorLatch*`) | `+0x44` |
| `0xaa`, `0xab` | vector-load-lmr / -lmr-bf16 | `+0x42` |
| `0xa8` | vector-done-with-gains | `+0x41` |
| else | -- | FATAL |

### `Msr` Enum

`MsrToString(Msr)` (`0x1d629720`) 通过整数算术写入一个 4 字节字符串,确认 enum 正好有两个值:

```c
// MsrToString @0x1d629720  (a2 = Msr ordinal)
*(uint32*)out = ((a2 != 0) << 24) + 0x6172736D;   // 0x6172736D == "msra" (LE: m,s,r,a)
// a2 != 0 adds 0x01000000 → last byte 'a'(0x61) → 'b'(0x62) ⇒ "msrb"
out[4] = 0; out.len = 4;

所以 Msr{0} = "msra"Msr{1} = "msrb"。这是 BounceBetweenMsrs 切换的 bank index,也是 cost model 中 TargetMsrA/MsrB 资源(latch ports)被计费的 bank。


Struct Layout

两个放置函数从 MrbChainAllocator 的逐 chunk state 读入它们的 MrbEntry 输入,而 split 逻辑把尾部记录存储在 btree 中。下面三个逐字节精确的 layout 解码自 ReleaseMrbReservation (0x10f5f9e0) 和 SplitAccumulationChain (0x10f598e0):

MrbChunkState -- 两个不同的逐 Chunk 数组

allocator 携带由 MrbEntry.chunk_id 索引的两个 flat array,而不是一个。它们用途不同,stride 也不同:

(a) 逐 MXU reservation-timeline 数组 -- MrbChainAllocator[+0xf0],经 __size_returning_new 分配 clamp(num_mxus, >=8 when ver>=5) * 0x70stride 0x70,按 chunk_id x 0x70 索引(SplitAccumulationChain 0x10f59941v13 = base + 112 * chunk_id)。每个元素都是它自己的 boost::multi_index bimap(chain allocatorchains_unevictable_until_ 时间线)-- 即 reservation timeline 是逐 MXU 的,而不是一个全局结构。逐元素 ctor(每次迭代 r14 += 0x70):

字段偏移值 / 角色
multi_index self/sentinel+0x00= &[+0x18]
node-header ptr+0x10operator new(0xa8)
ordered-index head sentinel+0x18= self
size+0x280
bucket count+0x380x36 = 54
bucket array+0x40operator new(0x1b0) = 54 * 8
max load factor+0x48(int)(54 * 1.0f) (0x3f800000)

(boost prime-bucket sizes {53,97,193,389,769,...} 位于 0xac092a0;max load factor 1.0f。)

(b) 逐 chunk free-entry pool -- MrbChainAllocator[+0x7a0],SOO bit 位于 MrbChainAllocator[+0x798],是一个 0x40 字节记录的 flat array,按 chunk_id x 0x40 索引(ReleaseMrbReservation 0x10f5fa18v7 = (int16)chunk_id << 6)。每条记录是一个 absl::container_internal::raw_hash_set,其元素是 linked_hash_set<MrbEntry>(policy @0x2181c730):

字段偏移角色
capacity mask+0x00hash-set capacity
size / SOO-state+0x08element count + SOO flag
ctrl ptr+0x10control-byte array
slots ptr+0x18slot array
SOO inline slot+0x20single-element fast path
free-list head+0x28doubly-linked recycled-entry list
list count+0x38recycled-entry count

ReleaseMrbReservationMrbEntry 进行 hash(MixingHashState::kSeed CRC32 覆盖 {uint16 chunk_id, int fifo_index, byte format}),在该 chunk 的 set 中查找/插入它,然后 operator new(0x20) 分配一个 node {+0x10 = MrbEntry key, +0x18 = format byte} 并把它推入 free-list([record+0x28] head,++[record+0x38])。这会把释放的 FIFO entry 回收到逐 chunk pool。

AccumulationChainAfterSplit0x70 字节)

SplitAccumulationChain 在当前程序顺序切开一条链时,tail 会作为 unique_ptr<AccumulationChainAfterSplit> 延迟保存,按程序顺序作为 key 插入 btree_set_container<btree<map_params_impl<long, unique_ptr<...>>>>(root 在 MrbChainAllocator[+0x70...],insert @0x10f5d2e0)。记录如下:

字段偏移类型含义
span base+0x00AccumulationWithOriginalChain*tail span begin
split program order+0x08s64alloc[+0x70] current_time 比较(0x10f598ff
span ptr+0x10AccumulationWithOriginalChain*cut cursor;+= consumed * 0x60(AWOC stride 0x60
span len+0x18s64-= consumed = current_order - begin
MrbEntry.chunk_id+0x20s160x10f599e2 / AdvanceTimeTo 读取
MrbEntry.fifo_index+0x28s32(也在 +0x24
has-MRB-entry flag+0x2cbytegate ReleaseMrbReservation0x10f5990d/0x10f599d6
matres program order+0x68s64bimap LEFT key(unevictable-until)

span 切分是 lea r14,[rax+rax*2]; shl r14,5 (0x10f59ab6) = consumed * 0x60,所以 AccumulationWithOriginalChain 元素 stride 是 0x60。在 operator new(0x30) 处构建的 btree node 写入 {+0x00 span_base, +0x08 latest_matmul/program_order, +0x10 span_ptr, +0x18 len}。(来源:mxu_accumulation.cc:1017 "Cannot split an AccumulationChain in the past",:1033 "Freeing MRB entry ..."。)


下游消费者 -- set_mrb_address

AllocateMrbEntriesAsFifo 计算出的 FIFO slot 由 LloInstruction::set_mrb_address(int) (0x1d4d93c0) 提交。它先 gate MRB 支持、做边界检查,然后委托给 set_mrb_address_unrestricted

c
// LloInstruction::set_mrb_address(this, addr) @0x1d4d93c0
target = this->parent()->target();
if (target.MatmulResultBufferEntries() <= 0)            // vtbl[+0x390] gate
    UpdateStatus("parent()->target() supports …");      // llo_instruction.cc:3660
RET_CHECK(addr < ResultFifoEntryCount(kMrf0, target.DeepseaVersion()))   // :3663
        // "Expected MRB address in the range 0-512, found: <addr>"
return set_mrb_address_unrestricted(this, addr);        // @0x1d4e9780

// set_mrb_address_unrestricted(this, addr) @0x1d4e9780
RET_CHECK(addr >= 0);                                   // :3642 "mrb_address >= 0"
if ((uint16)(op - 0x9b) < 0xB || (op & 0xFFFE) == 0x152) // matmul 0x9b..0xa5, or matres 0x152/0x153
    *(uint16*)(this + 0x42) = addr;                     // ← the physical MRB-address field
else
    FATAL("Unsupported opcode: <op>");                  // :3655
```text

因此放置后的 slot 落在 `LloInstruction+0x42` 的一个 16-bit 字段中,适用于 matmul 家族(`0x9b..0xa5`)和 matres 家族(`0x152`/`0x153`);`mrb_address()` (`@0x1d4e8860`) 会把它读回(`lea ecx,[op-0x9b]; cmp cx,0xb`)。每代 `Encoder*::EncodeBundle` 随后把该字段 -- 以及来自 `set_matrix_staging_register` 的 MSR 字节 -- 序列化进 bundle word(bit 位置见 [MXU slot](../isa/slot-mxu.md) 和 [encoder latch serialization](encoder-latch-serialization.md) 页面)。

> **GOTCHA -- 错误字符串写的是 "range 0-512",但实际边界按版本而定。** 硬边界是 `ResultFifoEntryCount(kMrf0, version)` -- `{16,16,16,48,224,256}` -- *不是*固定的 512"0-512" 字面量是一条陈旧消息;运行时检查与逐 generation 表值比较。硬编码 512 的重新实现会接受 binary 拒绝的越界 FIFO 地址(并且只有在更大 generation 上做低位 clamp 时才会拒绝有效地址)。

---

## 为什么该路径在 v0.0.40 中按 Gen Gate 掉

> **NOTE -- 此 build 中每个 TensorCore `Target` 都返回 `MatmulResultBufferEntries() == 0`。** `JellyfishTarget` (`0x1d4902c0`)、`PufferfishTarget` (`0x1d493b...`)、`ViperfishTarget` (`0x1d49ac60`) 和 `GhostliteTarget` 都返回 `0`(`xor eax,eax; ret`),而 base `Target::MinMrbWriteBlockSize` / `MrbRelativeAddressFromOffset` 是 `LogFatal` stub。v0.0.40 编译进来的任何 subclass 都没有把它们 override 成 live value。因此 `set_mrb_address` 的 `MatmulResultBufferEntries() > 0` gate 会失败,chain allocator 的 "Cannot use MRB" guard 会触发 -- FIFO-address 路径在 MRB disabled 的情况下运行。**算术、游标递推、accessor 表面以及两个 struct layout 都是逐字节精确 CONFIRMED**;缺失的只是启用 MRB 的 generation 的*具体数值 geometry*(live `MinMrbWriteBlockSize` 粒度和 `MrbRelativeAddressFromOffset` map)-- 它需要等待更新 generation 的 `Target`。值得注意的是,即使 `MatmulResultBufferEntries == 0`,`MxuResultEntriesPushed`/`Popped` *仍然*按 generation override(Viperfish `{2,4,8,8,4,4,4,4}` push table)。

---

## 置信度摘要

| 断言 | 证据 |
|---|---|
| 两个逐 MXU `int32` 游标(write/read),大小为 `num_mxus = Target[+0x4ac]`,memset 0 | `0x10f3efc4`/`0x10f3efe0`;`num_mxus` 与 `LatchLhs` `0x10f3b791` 交叉检查 |
| 逐 MXU 选择 `unit_id & 3`,由 WORD[+0xb] 上的 `0x400` "has mxu" bit gate | `0x10f3f069` |
| 按 `MxuResultEntriesPushed`/`Popped` 推进,向上舍入到 `MinMrbWriteBlockSize`,按 `ResultFifoEntryCount` 取模回绕 | `idiv` `@0x10f3f218`,mod `@0x10f3f234`;vtbl `+0x5f0`/`+0x5f8`/`+0x5e0` |
| 每版本 FIFO 深度 `{16,16,16,48,224,256}` | 表 `@0xb53e240`(`struct.unpack` 直接读 ELF) |
| 物理写入经 `set_mrb_address` -> `set_mrb_address_unrestricted` 到 `+0x42` | `0x1d4d93c0` / `0x1d4e9780` decompiled |
| `0xa5` 预扫描会中止整个 quadrant bounce | `0x10f3fb34`(`cmp ...,165`) |
| 从初始 `msr = 1` 开始逐序列 `msr ^= 1` 切换 | `0x10f3fb60` |
| MSR 字节按 op 家族写到 `+0x46`/`+0x44`/`+0x42`/`+0x41` | `set_matrix_staging_register` `0x1d4d7d40` decompiled |
| Bounce 盖到 latches(`+0x18`)和 `matmuls[0]`(`+0x48`),而不是 matres | `[seq+0x50]` 由 `:1063` 的 "!sequence->matmuls.empty()" guard |
| `Msr{0}="msra"`,`Msr{1}="msrb"` | `MsrToString` `0x1d629720` 算术 |
| 逐 MXU timeline element stride `0x70`;逐 chunk free-pool record stride `0x40` | `0x10f59941`(`×0x70`),`0x10f5fa18`(`<<6`) |
| `AccumulationChainAfterSplit` `0x70` 字节字段图;AWOC stride `0x60` | `SplitAccumulationChain` `0x10f598e0`;`0x10f59ab6` |
| 六个 `Target` accessor 位于 `+0x390`/`+0x398`/`+0x5e0`/`+0x5e8`/`+0x5f0`/`+0x5f8` | vtable `@0x21cce6a0`,`.rela.dyn` resolved |
| MRB 路径按 generation gate 掉(所有 shipped Targets 都返回 `MatmulResultBufferEntries==0`) | `JellyfishTarget` `0x1d4902c0` 等(`xor eax,eax; ret`) |
| `MrbRelativeAddressFromOffset` 是 matres 一侧的 relative-address map | vtbl `+0x5e8` 用于 `0x10f3f313`;base 是 `LogFatal` |
| 逐 MXU 游标在每次 quadrant 调用时重置(无跨 region carry) | cursor `new`/`free` 包围函数体 |

---

## 相关组件

| 组件 | 关系 |
|---|---|
| `MrbChainAllocator::SplitAccumulationChain` `0x10f598e0` | `0x70` 字节 split record + 逐 MXU timeline index 的来源 |
| `MrbChainAllocator::ReleaseMrbReservation` `0x10f5f9e0` | `0x40` 字节 free-pool record 和 MrbEntry recycle |
| `MxuAssigner::VisitRegion` `0x10f3a640` | 两个函数的逐 quadrant `array<Span,4>` 分发 |
| `MxuAssigner::SetLatchIndices` `0x10f3b4c0` / `LatchLhs` `0x10f3b5e0`(`num_mxus` 读取 `@0x10f3b791`) | LHS gain-matrix latch 一侧(在这里之前运行) |
| `LloInstruction::mrb_address` `0x1d4e8860` | 读回这里写入的 `+0x42` 字段 |
| `ViperfishTarget::MxuResultEntriesPushed`/`Popped` `0x1d499ae0`/`0x1d499ba0` | 游标按其推进的逐 generation push/pop count |

## 交叉引用

- [MRB Chain Allocator](mrb-chain-allocator.md) -- 程序顺序 reservation timeline,给每条链交付本页转换为物理 FIFO slot + MSR bank 的 `MrbEntry`;拥有 `0x70` 字节 timeline element 中携带的逐 MXU `boost::multi_index`。
- [MxuSequence / SequenceInfo](mxu-sequence-struct.md) -- 两个函数索引的逐 sequence 记录,包括 latches(`+0x18`)、matmuls(`+0x48`)和 matreses(`+0x60`)。
- [MXU Assignment Bin-Packer](mxu-assignment-binpacker.md) -- 构建这里所放置的 `MxuSequence` 的 `AssignMxusForSequenceGroup`。
- [Latch Assignment & Overrun](latch-assignment-overrun.md) -- `VisitRegion` 中在 FIFO/MSR 放置之前运行的 `SetLatchIndices`/`LatchLhs` gain-latch 一侧。
- [TPU Scheduling Pipeline](overview.md) -- Stage 2 在四阶段 scheduler 栈中的位置;本页是 Stage 2 的 result-FIFO/MSR 行。
- [MXU Slot](../isa/slot-mxu.md) -- bundle slot,其 `mrb_address`(`+0x42`)和 matrix-staging-register 字段由这里放置的值序列化;`GainLatchMode` MSR-A/MSR-B bank model。
- [Matprep / IAR / Latch](../isa/slot-matprep-iar-latch.md) -- MSR bounce 盖章的 latch ops(`0x8d..0x96`)。
- [LLO Opcode Enum](../isa/llo-opcode-enum.md) -- `0x9b..0xa5` matmul、`0x8d..0x96` latch(`kVectorLatch*`)、`0xaa`/`0xab` load-lmr、`0xa8` done-with-gains、`0x152` matres 数值空间。
- [MXU Latency Overview](../cost/mxu-latency-overview.md) -- 消费已放置 MSR bank(`MatpushModifier` staging-register key)来收取逐 bank reservation cycle 的 cost model。
- [MatmulMode & Modifiers](../cost/matmul-mode-modifiers.md) -- 为 `MxuResultEntriesPushed`/`Popped` 定 key 的 `MatmulDataFormat`/`GainLatchMode` ordinal。
- [MXU Latency: VF](../cost/mxu-latency-vf.md) -- Viperfish reservation matrix,按 bounce 分配的 bank/format 索引。
- **Binary:** `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)