MRB FIFO / MSR 放置
地址适用于
libtpu-0.0.40-cp314wheel 中的libtpu.so(BuildID md589edbbe81c5b328a958fe628a9f2207d,未剥离符号 -- 下文每个符号都是已 demangle 的 C++ 名称)。节映射:.text/.rodataVMA == 文件偏移;.data.rel.roVMA - 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 表面 -- 六个为放置定价的
Targetvtable slot,以及该路径在 v0.0.40 中为什么按 generation gate 掉(已发布的每个 TensorCore target 都返回MatmulResultBufferEntries() == 0)。
| FIFO 放置 | MxuAssigner::AllocateMrbEntriesAsFifo(Target const&, Span<unique_ptr<MxuSequence>>) @0x10f3ef80 |
| MSR bounce | MxuAssigner::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 enum | MsrToString @0x1d629720:0 -> "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-local 且 quadrant-local。MxuAssigner::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。
结构
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: …算法
// 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 wrapGOTCHA -- 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 slot | vptr offset | Base impl | 此路径上的角色 |
|---|---|---|---|
MatmulResultBufferEntries | +0x390 | 0x1d61da40 | MRB 支持 gate:set_mrb_address 除非 > 0 否则 abort |
SupportsMatmulResultPack | +0x398 | 0x1d61da80 | 兄弟项;不在此 hot path 上 |
MinMrbWriteBlockSize | +0x5e0 | 0x1d490d80 (LogFatal) | FIFO push 向上舍入粒度(idiv 除数) |
MrbRelativeAddressFromOffset | +0x5e8 | 0x1d490dc0 (LogFatal) | pop 一侧的 (op, offset) -> relative MRB address |
MxuResultEntriesPushed | +0x5f0 | 0x1d61e9c0 | matmul push 的 entry 数;推进 write_ptr |
MxuResultEntriesPopped | +0x5f8 | 0x1d61ea00 | matres pop 的 entry 数;推进 matres_index |
NOTE --
Target[+0x4ac]是num_mxus,Target[+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 ordinal | Generation | ResultFifoEntryCount(kMrf0, ver) |
|---|---|---|
| 0 | Jellyfish | 16 |
| 1 | Dragonfish | 16 |
| 2 | Pufferfish | 16 |
| 3 | Viperfish | 48 |
| 4 | Ghostlite | 224 |
| 5 | 6acc60406 (TPU7x) | 256 |
版本 ordinal >= 6 走 LogMessageFatal("invalid platform type:") 分支;该表正好有六个 live 行。这就是每个游标回绕的模数。
MxuResultEntriesPushed / Popped -- 逐 Op Entry 数
即使在 gate 掉 MRB 的 generation 上,这两个计数 accessor 也按 generation override。Viperfish override 是逐字节精确的参考:
// 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 OKNOTE -- bank 会盖到 latch 列表和
matmuls[0],而不是 matres。 bounce 把 bank 写到 sequence 的 latch 列表([seq+0x18]/[seq+0x20],stationary-operand staging ops0x8d..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 register(Xmr 空间)读取其 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、只跳过0xa5sequence 的重新实现,会给 binary 保持未分配的非 LMR sequence 分配 MSR bank。
set_matrix_staging_register -- 目标字节
bank 字节会根据 opcode 写入不同的逐 op 家族字段。分发是一串范围测试(LloOpcode 是 16-bit *a1):
// 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 中 Target 级 MsrA/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) * 0x70,stride 0x70,按 chunk_id x 0x70 索引(SplitAccumulationChain 0x10f59941:v13 = base + 112 * chunk_id)。每个元素都是它自己的 boost::multi_index bimap(chain allocator 的 chains_unevictable_until_ 时间线)-- 即 reservation timeline 是逐 MXU 的,而不是一个全局结构。逐元素 ctor(每次迭代 r14 += 0x70):
| 字段 | 偏移 | 值 / 角色 |
|---|---|---|
| multi_index self/sentinel | +0x00 | = &[+0x18] |
| node-header ptr | +0x10 | operator new(0xa8) |
| ordered-index head sentinel | +0x18 | = self |
| size | +0x28 | 0 |
| bucket count | +0x38 | 0x36 = 54 |
| bucket array | +0x40 | operator 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 0x10f5fa18:v7 = (int16)chunk_id << 6)。每条记录是一个 absl::container_internal::raw_hash_set,其元素是 linked_hash_set<MrbEntry>(policy @0x2181c730):
| 字段 | 偏移 | 角色 |
|---|---|---|
| capacity mask | +0x00 | hash-set capacity |
| size / SOO-state | +0x08 | element count + SOO flag |
| ctrl ptr | +0x10 | control-byte array |
| slots ptr | +0x18 | slot array |
| SOO inline slot | +0x20 | single-element fast path |
| free-list head | +0x28 | doubly-linked recycled-entry list |
| list count | +0x38 | recycled-entry count |
ReleaseMrbReservation 对 MrbEntry 进行 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。
AccumulationChainAfterSplit(0x70 字节)
当 SplitAccumulationChain 在当前程序顺序切开一条链时,tail 会作为 unique_ptr<AccumulationChainAfterSplit> 延迟保存,按程序顺序作为 key 插入 btree_set_container<btree<map_params_impl<long, unique_ptr<...>>>>(root 在 MrbChainAllocator[+0x70...],insert @0x10f5d2e0)。记录如下:
| 字段 | 偏移 | 类型 | 含义 |
|---|---|---|---|
| span base | +0x00 | AccumulationWithOriginalChain* | tail span begin |
| split program order | +0x08 | s64 | 与 alloc[+0x70] current_time 比较(0x10f598ff) |
| span ptr | +0x10 | AccumulationWithOriginalChain* | cut cursor;+= consumed * 0x60(AWOC stride 0x60) |
| span len | +0x18 | s64 | -= consumed = current_order - begin |
MrbEntry.chunk_id | +0x20 | s16 | 在 0x10f599e2 / AdvanceTimeTo 读取 |
MrbEntry.fifo_index | +0x28 | s32 | (也在 +0x24) |
| has-MRB-entry flag | +0x2c | byte | gate ReleaseMrbReservation(0x10f5990d/0x10f599d6) |
| matres program order | +0x68 | s64 | bimap 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:
// 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)