MxuSequence / SequenceInfo
地址适用于 libtpu-0.0.40-cp314 wheel 中的 libtpu.so(BuildID md5
89edbbe81c5b328a958fe628a9f2207d,未 strip,包含完整 C++ 符号)。其他版本会有所不同。
摘要
MxuSequence 是 TPU 后端在把 dot / convolution 累加链降低到脉动阵列时,为每个 matmul 组构建的记录。每条链,也就是一次权重 latch、消费它的 matmul 步骤,以及排空 accumulator 的结果 pop,都会成为一个 MxuSequence;MXU 分配阶段会遍历 Span<unique_ptr<MxuSequence>>,把这些链装箱到物理 MXU pass 上。这个记录本身是扁平且很小的:五个 {ptr, count, cap} 指令列表,总计 0x78 字节。有意思的逐指令状态,也就是 latch mode、latch index、MSR bank、MXU-quadrant unit-id,不存放在 MxuSequence 上。它们分散在成员 LloInstruction 上,由分配 pass 在遍历列表时写入。
熟悉 LLVM 的读者可以把 MxuSequence 想成一个 MachineInstr bundle,它唯一的 payload 是哪些指令属于同一组,再加上调度器盖到每个成员上的一小组字段。latch-index 分配(SetLatchIndices)类似于对单一架构资源做寄存器分配:序列中的每个 latch 都获得一个按程序顺序单调递增的索引,并且第一个 latch 有一个由每代 overrun handshake 控制的特殊情况。LatchLhs 是生产者,它把 LHS gain matrix 划分到每个 MXU 的 latch grid 上,并发出 vlatch.lsf / vmatmul / vmatres op,每个 op 都标记其 MXU quadrant。第二个独立记录 MxuStat::SequenceInfo 是 bin-packer 的输出:两个 owning vector 加上 latch_latency 和 accumulated_latency 快照;一旦 greedy min-makespan 选择确定序列落在哪个物理 MXU 上,就写入每个 MXU 的 btree_map<int, SequenceInfo>(sequences_)。
本页是这两个结构的规范位置:字节精确的 MxuSequence 字段映射(来自 deleter 和 builder)、SequenceInfo 布局(来自 btree-insert 字段写入)、SetLatchIndices 中的 latch-index 分配、LatchLhs 中的 gain-matrix 划分,以及 set_mxu commit。填充这些列表的 op family,也就是 latch 0x8d..0x96、matmul 0x9b..0xa5、matres 0x152/0x153,以及它们的 LloInstruction 字段偏移,由 Matprep / IAR / Latch 和 MXU Slot 页面负责;这里仅引用,不重新推导。
对重新实现而言,契约是:
MxuSequence布局:五个固定偏移的自定义{ptr, count, cap}vector,sizeof 0x78,以及每个列表保存哪个 opcode family。- 逐指令的 "latch state" 模型:分配 pass 盖到每个成员
LloInstruction上的字段(latch mode、latch index、MSR、unit-id),而不是序列上的扁平标量。 SetLatchIndices:单调的 latch-index 分配,以及第一个 latch 的GainLatchModeHasOverrunChecksgate。LatchLhs:按 transpose 对 LHS 分组、ChunksPerTile × num_mxus ≥ ΣPackingFactor容量保护,以及在重建的 latch/matmul/matres op 上逐 quadrant 写入set_unit_id。MxuStat::SequenceInfo:bin-packer 的逐序列输出记录(两个 owning vector +latch_latency+accumulated_latency),以及 makespan cost 读取的 latency delta。
MxuSequence 大小 | 0x78(5 个列表 × 0x18)— default_delete<MxuSequence>::operator() 0x14504c00 |
| 列表元素 | {LloInstruction** @+0x00, int64 count @+0x08, int64 cap @+0x10}(count 是整数,不是 end-ptr) |
| 列表偏移 | +0x00 head,+0x18 latches,+0x30 matprep/MUBR aux,+0x48 matmuls,+0x60 matreses |
| Builder | CollectAndTransformSequencesInternal 0x14500800(逐列表 grow-realloc append,mxu_sequence_collector.cc) |
| Latch-index 分配 | MxuAssigner::SetLatchIndices 0x10f3b4c0 → WORD[op+0x42] |
| Gain-matrix 划分 | MxuAssigner::LatchLhs 0x10f3b5e0(const) |
SequenceInfo | MxuStat::SequenceInfo,0x40 字节,btree value sequences_ |
| Commit | AssignMxusForSequenceGroupInternal 0x10f77ca0 — sequences_[seq_key] = SequenceInfo |
| 逐指令状态 | latch_mode BYTE[+0x40],latch_index WORD[+0x42],MSR BYTE[+0x44],unit-id WORD[+0x0b] |
MxuSequence 记录
用途
MxuSequence 把一条 matmul 累加链的 LLO 指令分组,使 MXU-assignment pass 可以把这条链作为一个单元处理:加载权重的 latch、驱动阵列的 matmul 步骤,以及排空结果的 matres op。它是 bin-packer 遍历的容器,也是 MRB/MSR placement 消费的容器。它自身不携带调度标量,只包含五个列表和列表里的指令。
布局(sizeof 0x78)
deleter std::default_delete<MxuSequence>::operator()(0x14504c00)是主要布局证据:它按反向顺序释放五个 vector backing-store(seq[12]、seq[9]、seq[6]、seq[3]、seq[0],也就是 +0x60/+0x48/+0x30/+0x18/+0x00 的 ptr word),在释放前把每个列表的第二个 word(count,位于 ptr+0x08)清零,然后对整条记录执行 free(seq)。
function default_delete_MxuSequence(seq): // 0x14504c00, demangled symbol present
if seq == nullptr: return
if seq[12]: seq[13] = 0; free(seq[12]) // list @ +0x60 (matreses) : ptr @+0x60, count @+0x68
if seq[ 9]: seq[10] = 0; free(seq[ 9]) // list @ +0x48 (matmuls)
if seq[ 6]: seq[ 7] = 0; free(seq[ 6]) // list @ +0x30 (matprep / MUBR aux)
if seq[ 3]: seq[ 4] = 0; free(seq[ 3]) // list @ +0x18 (latches)
if seq[ 0]: seq[ 1] = 0; free(seq[ 0]) // list @ +0x00 (head / setup)
free(seq) // free(seq, 0x78)每个列表都是自定义的三 word vector,即 {ptr, count, cap},不是 libc++ 的 {begin, end, end_cap} 三元组:count word 是整数元素计数,不是 end-pointer。SetLatchIndices 通过循环 idx < seq[+0x20] 并索引 seq[+0x18][8*idx] 证明了这一点(这是一个作为循环上界使用的 count,而不是从 base pointer 减出来的值)。deleter 的“把每对的第二个 word 置零”模式(seq[1]、seq[4]、seq[7]、seq[10]、seq[13])与在释放 backing store 前清除 count 相一致;列表间隔为 0x18(ptr word 位于 seq[0]/seq[3]/seq[6]/seq[9]/seq[12]),五个列表合计 0x78。builder(mxu_sequence_collector.cc)用更新 {ptr@+0x00, count@+0x08, cap@+0x10} 的 grow-realloc 向这些列表追加元素,确认第三个 word 是 capacity。
| 偏移 | 列表 | 元素 opcode / 消费者 |
|---|---|---|
+0x00 | list0 — head / setup | 推断的序列头 setup |
+0x18 | latches | 0x8d..0x96 — SetLatchIndices count @+0x20 |
+0x30 | matprep / MUBR aux | 与每个 matmul 一起追加(builder line ~1190,"Adding vmatprep MUBR") |
+0x48 | matmuls (matmuls) | 0x9b..0xa5 — builder 追加 matmul op;LatchLhs ΣPackingFactor + seq->matmuls.size() CHECK,count @+0x50 |
+0x60 | matreses (matreses) | 0x152/0x153 — builder 在 kVectorMatres CHECK 后 emplace_back,count @+0x68 |
0x78 | sizeof | free(seq, 0x78) |
+0x18、+0x48、+0x60 这些身份是字节精确的:deleter 证明这几个偏移处有五个列表,独立消费者又用 opcode-checked access 索引它们。SetLatchIndices 读取 +0x18/+0x20 并断言 0x8d..0x96。在 CollectAndTransformSequencesInternal 中,matmul-family op((uint16)(op-0x9b) <= 0xa)被追加到 +0x48 列表(成对的 matprep/MUBR 值追加到 +0x30 列表),matres-family op((op & ~1) == 0x152,由 opcode == kVectorMatres gate)则被 emplace_back 到 +0x60 列表。随后 LatchLhs 把同一个 +0x48 列表作为 seq->matmuls 读取:其容量循环遍历 seq[+0x48](count @+0x50)上的 opcode 0x9b/0xa3,源字段名由 CHECK "...== seq->matmuls.size()" 锚定,该 CHECK 读取 *(seq+0x50)。+0x00(head/setup)和 +0x30(matprep/MUBR aux)的身份较弱:+0x30 是与 +0x48 同步增长的列表,并被记录为 "vmatprep MUBR"(HIGH);而 +0x00 会被填充,但其 opcode 成员没有逐 cell 隔离出来(MEDIUM)。
陷阱 — 每个序列的 latch state 在成员指令上,而不在序列上。 deleter 只释放五个 vector 和一条扁平的
0x78记录,MxuSequence上没有用于 latch mode、latch index、MSR 或 quadrant 的标量字段块。这些字段存在于每个成员LloInstruction上:latch_modeBYTE[+0x40],latch_index_in_sequenceWORD[+0x42],MSRBYTE[+0x44],unit-id packWORD[+0x0b]。把它们作为MxuSequence成员添加的重新实现,会偏离二进制的数据流:调度器是逐 op 通过列表读写它们的。完整字段图见 Matprep / IAR / Latch。
Builder
CollectAndTransformSequencesInternal(0x14500800,源码 mxu_sequence_collector.cc)是生产者。它遍历 region 的 LLO value,按 opcode 分类((uint16)(op - 0x9b) <= 0xa → matmul family 追加到最后一个序列的 +0x48 matmuls 列表,并把成对的 matprep/MUBR 值追加到 +0x30;(op & 0xfffe) == 0x152 且由 opcode == kVectorMatres gate → emplace_back 到 +0x60 matreses 列表;latch family → +0x18 列表),通过逐列表 grow-realloc 把它们填入 array<Span<unique_ptr<MxuSequence>>, 4> quadrant slot。它用 "Matres before matmul" FailedPrecondition 强制顺序。它检查的平衡不变量 ExpectedMatresesPerMatmuls(last_sequence->matmuls) >= total_matreses_count(mxu_sequence_collector.cc,读取 +0x48/+0x50 处的 last_sequence)就是 LatchLhs 稍后依赖的 matmul/matres 数量关系。
SetLatchIndices — Latch-Index 分配
用途
一旦某个序列的 latch op 按程序顺序位于 +0x18 列表中,每个 latch 都需要一个latch index in sequence,也就是它在链中的位置,写入 WORD[op+0x42]。下游的 bundle packer 把冲突的 latch index 作为 slot-legality 约束,因此该索引是把一个 op 绑定到一个 latch slot 的 commit。SetLatchIndices(0x10f3b4c0)负责分配它,但有一个特殊情况:第一个 latch 只有在其 GainLatchMode 携带 overrun check 时才会被索引。
入口点
MxuAssigner::VisitRegion 0x10f3a640
└─ MxuAssigner::SetLatchIndices 0x10f3b4c0 ── per-sequence latch ordering
├─ LloInstruction::latch_mode 0x1d4e7500 ── BYTE[op+0x40] (GainLatchMode)
├─ Target::GainLatchModeHasOverrunChecks vtbl+0x358 ── first-latch gate
└─ LloInstruction::set_latch_index_in_sequence 0x1d4e7960 ── WORD[op+0x42] = idx算法
function SetLatchIndices(span_ptr, span_count): // 0x10f3b4c0; arg = Span<unique_ptr<MxuSequence>>
if span_count == 0: return
for seq in span_ptr[0 .. span_count): // 8-byte stride, seq = *span_ptr
if seq[+0x20] == 0: continue // empty latch list — skip
for idx = 0 .. seq[+0x20) - 1: // latch count @ seq+0x20
op = seq[+0x18][idx] // latch list @ seq+0x18, 8-byte stride
check (uint16)(opcode(op) - 0x8d) < 0xa // 0x8d..0x96, else FATAL
// "LloOpcodeIsVectorLatch(opcode)" mxu_assigner.cc:420
target = op.region.module.target // [[op+0x10]+0x38]+0x10
glm = latch_mode(op) // BYTE[op+0x40]
has_overrun = target.vtbl[+0x358](glm) // GainLatchModeHasOverrunChecks
if idx == 0 and not has_overrun: break // first latch, no overrun ⇒ abandon this sequence
set_latch_index_in_sequence(op, idx) // WORD[op+0x42] = idx (check idx <= 65535)每个 latch 都获得一个等于其在 +0x18 列表中位置的单调索引。first-latch gate 是唯一分支:如果 idx == 0 且该代的 GainLatchModeHasOverrunChecks(glm) 为 false,循环会 break,不索引第一个 latch,并转到下一个序列。在 +0x358 override 恒为 FALSE 的四代上,第一个 latch 永远不会被索引;只有 Viperfish 有实际 handshake。store 本身会限制索引范围:set_latch_index_in_sequence(0x1d4e7960)重新检查 opcode family((uint16)(opcode - 0x8d) >= 0xa → FATAL LloOpcodeIsVectorLatch(opcode()) at llo_instruction.cc:3399),断言 index <= (65535)(llo_instruction.cc:3400),然后写入 *((uint16*)op + 33) = idx,也就是 WORD[op+0x42]。
怪癖 — index 0 可能会被刻意留空。 总是给每个 latch(包括第一个)盖索引的重新实现,会在四个非 Viperfish 代上过度约束 bundle packer;这些代的第一个 latch 会被有意留作未索引,因为它的 mode 不携带 overrun handshake。这个 gate 是逐代数据(
vtbl+0x358),不是编译期常量。完整的逐代GainLatchModeHasOverrunChecks真值表和 overrun-reservation cost 见 Latch Assignment & Overrun。
reader latch_index_in_sequence 是对称 accessor(同一个 WORD[op+0x42],同一个 opcode gate);latch_mode(0x1d4e7500)对 latch family 或 kVectorMatprepSubr 返回 BYTE[op+0x40],其他情况 FATAL "Unsupported opcode"。
LatchLhs — Gain-Matrix Latch 划分
用途
LatchLhs(0x10f3b5e0,一个 const 成员)是 latch / matmul / matres op 的生产者;这些 op 稍后会由 SetLatchIndices 索引,并由 MRB/MSR allocator 盖章。它接收一个 matmul 组的 LHS(stationary gain / weight operand),把其 packed column 划分到每个 MXU 的 latch grid 上,并在新 region 中重建 latch+matmul+matres 序列,同时给每个 op 标记其 MXU quadrant。它是 input/gain 侧,在 VisitRegion 中运行于 result-side AllocateMrb / Bounce 之前。
入口点
MxuAssigner::LatchLhs 0x10f3b5e0 (const)
├─ BuildXposeSequences 0x10f813a0 ── group LHS by transpose op
├─ MatmulDataFormatPackingFactor 0x1d629300 ── column-pack factor (table @0xb53c6bc)
├─ Target::ChunksPerTile 0x1d60f2c0 ── hwcfg[+0x198] / hwcfg[+0x1a0]
├─ LloRegionBuilder::VlatchLsf 0x1d573ec0 ── emit kVectorLatchLsf
├─ LloRegionBuilder::Vmatmul 0x1d575a60 ── emit matmul (K-tile loop)
├─ LloRegionBuilder::Vmatres 0x1d5761a0 ── emit matres
├─ LloValue::set_unit_id (inlined) 0x12698c00 ── WORD[op+0x0b] quadrant stamp
└─ ExpectedMatresesPerMatmul 0x145005e0 ── matmul/matres balance算法
function LatchLhs(target, lhs_span, sequences): // 0x10f3b5e0
xpose = BuildXposeSequences(lhs_span) // 0x10f813a0
// vec1 = ops with (opcode & 0xfffe) == 0xa6 → {0xa6 kVectorTranspose, 0xa7 kVectorTransposeBinary}
// vec2 = ops with opcode == 0x154 → {kVectorTransposeResult}
// xpose.size == 1 ⇒ single-xpose fast path; else multi/no-xpose path
for seq in sequences:
// --- capacity guard: packed columns must fit the per-MXU latch grid and tile-align ---
acc = 0
for op in seq.matmuls[+0x48 .. count@+0x50): // matmul-family ops
if op != 0x9b and op != 0xa3: abort // also requires matmul_data_format(op)-1 <= 1
acc += MatmulDataFormatPackingFactor(matmul_data_format(op))
num_mxus = target[+0x4ac] // target int[299]
check( ChunksPerTile() * num_mxus >= acc ) // else abort
check( acc % ChunksPerTile() == 0 ) // tile.size()*PackingFactor == ChunksPerTile()
// --- rebuild per quadrant, stamping the MXU-quadrant unit-id ---
for each matmul in seq.matmuls[+0x48 .. count@+0x50):
q = program_order & 3 // the MXU quadrant 0..3 (cmp 0x4 bound)
glm = byte_table_0xac0913e[matmul_op - 0x9b] // {0×8, 0xb, 0xb}: plain→0, packed→0xb
latch = VlatchLsf(builder, lhs_value, glm, 0) // emit kVectorLatchLsf (0x8d)
WORD[latch+0x0b] = (WORD[latch+0x0b] & 0xf8ff) | ((q << 8) + 0x400) // = set_unit_id(q)
for k = 0 .. MatmulDataFormatPackingFactor(fmt) - 1: // K-tile split
m = Vmatmul(builder, fmt, ...); WORD[m+0x0b] = set_unit_id(q)
r = Vmatres(builder, fmt, ...); WORD[r+0x0b] = set_unit_id(q)
check( ExpectedMatresesPerMatmul-balance ) // matres_index <= matreses.size() - pushes三个组成部分都是字节精确的:
BuildXposeSequences(0x10f813a0) 扫描 LHS span 并构建两个 vector:vec1包含(opcode & 0xfffe) == 0xa6的 op(transpose-prep pair0xa6/0xa7),vec2包含opcode == 0x154的 op(0x154= 340,即 transpose-result)。LatchLhs测试结果大小是否== 1,以选择 single-transpose fast path。- 容量保护遍历
+0x48matmuls 列表(count @+0x50),拒绝任何不是0x9b/0xa3的 op(或matmul_data_format - 1 > 1的 op),对其求和MatmulDataFormatPackingFactor,并要求ChunksPerTile() * target[+0x4ac] >= acc且acc % ChunksPerTile() == 0。MatmulDataFormatPackingFactor(0x1d629300)读取int32 table @0xb53c6bc = {1,2,4,4,4,4,8,8,4,4},索引为fmt - 1(带 bounds-check,FATAL"Unsupported MatmulDataFormat"atmatmul_data_format.cc:197)。ChunksPerTile(0x1d60f2c0)是target[119]->[+0x198] / target[119]->[+0x1a0](由 lane-count 派生的 tile granule)。 - quadrant stamp从
.rodatabyte table@0xac0913e = {0,0,0,0,0,0,0,0,0xb,0xb}读取glm(op0x9b..0xa2→ GainLatchMode 0 bf16;op0xa3/0xa4packed → 0xb =GLM_PACKED_BF16),通过VlatchLsf发出 latch,并对每个发出的 op(VlatchLsf、PackingFactor个Vmatmul、Vmatres)写入WORD[op+0x0b] = (WORD[op+0x0b] & 0xf8ff) | ((q & 3) << 8) + 0x400。
该 stamp 与 LloValue::set_unit_id(int)(0x12698c00)字节一致:
function set_unit_id(v, unit): // 0x12698c00
WORD[v+0x0b] = ((unit & 3) << 8) + (WORD[v+0x0b] & 0xf8ff) + 0x400; // bits 8-9 quadrant, bit 10 has-mxu
check unit <= 3 // "unit_id_ == unit_id" llo_value.h:408| 表 / callee | 地址 | 作用 |
|---|---|---|
BuildXposeSequences | 0x10f813a0 | 对 LHS 分组:vec1 {0xa6,0xa7},vec2 {0x154} |
MatmulDataFormatPackingFactor | 0x1d629300 | int32[fmt-1] @0xb53c6bc = {1,2,4,4,4,4,8,8,4,4} |
Target::ChunksPerTile | 0x1d60f2c0 | hwcfg[+0x198] / hwcfg[+0x1a0] |
num_mxus | Target+0x4ac | 每个 region 的 MXU 数量 |
| GLM byte table | 0xac0913e | (op-0x9b)→GLM:{0×8, 0xb, 0xb} |
LloRegionBuilder::VlatchLsf | 0x1d573ec0 | 发出 vlatch.lsf(LloValue*,GainLatchMode,int) |
LloRegionBuilder::Vmatmul | 0x1d575a60 | 发出 matmul(K-tile loop,每个 latch 有 PackingFactor×) |
LloRegionBuilder::Vmatres | 0x1d5761a0 | 发出 matres |
LloValue::set_unit_id(inlined) | 0x12698c00 | WORD[v+0x0b] quadrant pack |
怪癖 — matmul 循环运行
PackingFactor次,而不是一次。 Packed/nibble format(PackingFactor为 2、4 或 8)会为每个 latch 发出多个Vmatmulop,也就是把 packed contracting dimension 跨 systolic pass 拆分的 K-tiling。对 bf16(fmt 1)以外的所有 format,只为每个 latch 发出一个 matmul 的重新实现都会少算 systolic 步骤。下游的ExpectedMatresesPerMatmul平衡检查依赖这个计数。
MxuStat::SequenceInfo — Bin-Packer 输出记录
用途
如果说 MxuSequence 是 assignment pass 遍历的输入,那么 MxuStat::SequenceInfo 就是它产生的输出。bin-packer(AssignMxusForSequenceGroupInternal,0x10f77ca0)为每个物理 MXU 维护一个 MxuStat 结构(stride 0x28),其中 sequences_ 成员是一个 absl::btree_map<int, SequenceInfo>。对每个 MxuSequence,当 greedy min-makespan select 选择它落在哪个 MXU 后,pass 会把以 sequence index 为 key 的 SequenceInfo 插入该 MXU 的 sequences_ btree,记录该序列的两个 owning vector,以及一个 latch_latency 和 per-MXU accumulated_latency 快照。
布局(0x40 字节)
从 btree-insert 字段写入点恢复而来(btree_map_container<…map_params_impl<int, MxuStat::SequenceInfo>>::operator[]<int,0> 返回 value slot,随后在 0x10f77ca0 lines ~1064-1079 处写入 8 个 qword 字段)。value object 为 0x40 字节,布局为两个 owning vector 后跟两个 long,每个 vector 的前两个 word 会在 overwrite 时被释放:
struct SequenceInfo { // 0x40 = 64 bytes; btree value
void* vec1_begin; // +0x00 owning vector #1 begin (freed on overwrite)
long vec1_end; // +0x08 vector #1 end count
long vec1_cap; // +0x10 vector #1 capacity
void* vec2_begin; // +0x18 owning vector #2 begin (freed on overwrite)
long vec2_end; // +0x20 vector #2 end count
long vec2_cap; // +0x28 vector #2 capacity
long latch_latency; // +0x30 per-sequence latch latency (the `free`/new_val arg)
// CHECK "latch_latency == prev_it->second.latch_latency"
long accumulated_latency; // +0x38 per-MXU accumulated_latency snapshot at select time
};两个 vector 都是 owning 的(通过 operator new 分配,从 caller buffer 用 memcpy 填充,在 overwrite 时释放)。它们的元素类型是未验证为 int:+0x18 vector 的 __throw_length_error 命名为 vector<CycleTable::Instruction>(元素 stride 0x28),而 +0x00 vector 从一个 8 * count 大小的 buffer 复制。因此二者都不是普通的 vector<int>;哪个列表保存序列的 instruction set,哪个保存 result/cycle record,是推断出来的,而不是具名源码字段。两个尾随 long 是字节精确的:+0x30 是 cost function LatchLatencyChangeAfterAdding 通过 CHECK "latch_latency == prev_it->second.latch_latency"(mxu_latency_balancing.cc:236)与 predecessor 比较的值,+0x38 是一同写入的 per-MXU accumulated_latency 快照(*(stat_array_base))。
未验证 — 两个尾随
long不是 busy interval。 把SequenceInfo建模为{latch_latency, vec1, vec2, busy_start, busy_end},并把两个long视为 scheduled busy interval 很有诱惑力;但写入点不支持这种解释。value object 的两个尾随long是latch_latency/new-value arg 和 per-MXUaccumulated_latency快照,而不是 start/end pair,并且+0x00是 vector begin pointer(overwrite 时释放),不是latch_latency标量。cost function 通过 absl btree-node-internal 偏移(9*idx+9、9*idx+10qword)读取 predecessor 的latch_latency和 interval endpoint,这些是node偏移,不是 value-struct 偏移,并且尚未完全解析到两个尾随long。请把 cost-function 读取的精确含义视为未验证。
| 字段 | 偏移 | 类型 | 含义 |
|---|---|---|---|
vec1 | +0x00 / +0x08 / +0x10 | owning vector(8-byte elem) | 每序列列表 #1 |
vec2 | +0x18 / +0x20 / +0x28 | owning vector(CycleTable::Instruction,0x28 elem) | 每序列 cycle/result record |
latch_latency | +0x30 | long | 每序列 latch latency(由 CHECK 锚定) |
accumulated_latency | +0x38 | long | 每 MXU accumulated-latency 快照 |
set_mxu Commit
AssignMxusForSequenceGroupInternal(0x10f77ca0)持有一个 vector<InlinedVector<MxuAssignment, 4>>;每个 MxuStat(stride 0x28,即 5 * idx qword)携带它的 accumulated_latency 和 sequences_ btree。commit 是一次 greedy min-makespan select,随后进行 sequences_ insert:
function AssignMxusForSequenceGroupInternal(stats, sequences, cycle_table, ...): // 0x10f77ca0
for seq in sequences: // stats: vector<InlinedVector<MxuAssignment, 4>>
best = +INF; argmin = current
for i = 0 .. num_mxus - 1: // MxuStat stride 0x28 (5 qwords)
score = stats[i].accumulated_latency // *(stat_base)
+ stats[i].LatchLatencyChangeAfterAdding(seq_key, new_val, free) // 0x10f7f3e0
+ stats[i].free_extra // stat[5*i + 0]
if score < best: best = score; argmin = i // smaller-index tiebreak (>= keeps prior)
check( sequences_.find(seq_index) == sequences_.end() ) // mxu_latency_balancing.cc:256, not yet assigned
// operator[]<int,0> returns the value slot, then the 8-qword write:
stats[argmin].sequences_[seq_key] = SequenceInfo{ vec1, vec2, latch_latency, accumulated_latency }per-MXU cost delta 是 MxuStat::LatchLatencyChangeAfterAdding(0x10f7f3e0),它在 sequences_(btree)中执行 lower_bound 和 predecessor lookup,并返回 interval-extension delta。其算术是字节精确的,但两个 btree-node-internal 读取位于 node offset 9*idx+9 / 9*idx+10 qword(未直接解析到 value-struct 字段):
function LatchLatencyChangeAfterAdding(this, seq_key, new_val, free): // 0x10f7f3e0
a = btree_lower_bound_field(seq_key) // node qword [9*idx+9]
pred = predecessor(seq_key)
if pred.key == seq_key: // pred.latch_latency read
check( new_val == pred.latch_latency ) // "latch_latency == prev_it->second.latch_latency"
// mxu_latency_balancing.cc:236
new_val = 0
b = pred_field // node qword [9*idx+10]
c = max(0, new_val - b)
x = max(0, a - free)
y2 = max(0, a - b)
return c + x - y2 // the delta the makespan sums (return v21 + v22 - v23)makespan select 会在 MXU 间跟踪当前最小值,并在 tie 时保留较小索引的 argmin(0x10f77ca0 lines ~938-946 处的 v78 <= v83 / v78 >= v83 不对称)。cost 只读取 btree interval 字段和 per-MXU accumulated_latency;两个 owning vector 不被 cost path 读取。
注意 —
sequences_是按 sequence index keyed 的 btree,不是MxuSequence*map。 这里确认的 per-MxuStatassignment record 是btree_map<int, SequenceInfo>(CHECKsequences_.find(seq_index) == sequences_.end();value typeMxuStat::SequenceInfo在operator[]<int,0>的map_params_impl<int, MxuStat::SequenceInfo>实例化中被命名)。btree insert 和vector<InlinedVector<MxuAssignment, 4>>storage 由该反编译确定。此前断言的并行flat_hash_map<MxuSequence*, long>set-mxu commit 没有在此函数中找到,现在视为未验证。 在LatchLhs读取program_order & 3之前,为每个 matmul 确定程序顺序 quadrant 的上游路径尚未追踪到单个 op(未验证)。
Chain 如何流经 Pass
一个 bf16 LHS gain matrix latch 到 2-MXU region 时,会按以下顺序经过这些结构(VisitRegion,0x10f3a640):
1. CollectAndTransformSequencesInternal ── builds per-quadrant MxuSequences (the 5 lists)
2. LatchLhs ── partition LHS, emit vlatch.lsf/matmul/matres,
stamp WORD[op+0x0b] = unit_id (MXU quadrant)
3. SetLatchIndices ── walk seq[+0x18]; WORD[op+0x42] = program-order index
(first latch only if GainLatchModeHasOverrunChecks)
4. AllocateMrbEntriesAsFifo / Bounce ── result-FIFO address + MSR-A/B bank (output side)
5. AssignMxusForSequenceGroupInternal ── greedy min-makespan select → sequences_[key]=SequenceInfo
6. bundle packer ── reads WORD[op+0x42] latch index as a slot-legality input这些阶段完全由逐指令字段描述:unit_id(WORD[+0x0b],来自 LatchLhs)= gain matrix latch 到哪个 MXU quadrant;GainLatchMode(BYTE[+0x40])= 如何加载它(bf16 / packed);latch_index_in_sequence(WORD[+0x42],来自 SetLatchIndices)= 程序顺序;MSR(BYTE[+0x44],来自 Bounce)= 哪个 staging bank;MRB address = 结果落在哪里。这些都不在 MxuSequence 自身上,它纯粹是分组容器;SequenceInfo 也纯粹是逐序列 assignment 输出。
相关组件
| 名称 | 关系 |
|---|---|
MxuAssigner::SetLatchIndices 0x10f3b4c0 | 把 WORD[op+0x42] latch index 写到 +0x18 列表上 |
MxuAssigner::LatchLhs 0x10f3b5e0 | latch/matmul/matres op 和 unit_id stamp 的生产者 |
CollectAndTransformSequencesInternal 0x14500800 | 构建五个 MxuSequence 列表 |
AssignMxusForSequenceGroupInternal 0x10f77ca0 | greedy makespan select + SequenceInfo commit |
MxuStat::LatchLatencyChangeAfterAdding 0x10f7f3e0 | makespan 求和的 latency delta |
default_delete<MxuSequence>::operator() 0x14504c00 | 布局证据(五个列表,free(seq, 0x78)) |
交叉引用
- MXU 分配 Bin-Packer —
AssignMxusForSequenceGroup,消费这些记录并产生SequenceInfo输出的 greedy makespan 算法。 - Latch 分配与 Overrun — 深入说明
SetLatchIndices和逐代GainLatchModeHasOverrunChecksfirst-latch handshake。 - MRB Chain Allocator — 消费 matmul/matres 列表的 accumulation-chain reservation timeline。
- MRB FIFO / MSR Placement —
AllocateMrbEntriesAsFifo/BounceBetweenMsrs,在LatchLhs后盖上 MSR(BYTE[op+0x44])的输出侧。 - MXU Slot — 列表保存的 systolic op family(
vlatch/vmatmul/vmatres)以及逐代 bundle encoding。 - Matprep / IAR / Latch — latch-op builder 和完整
LloInstruction字段图(latch_mode+0x40、latch_index+0x42、MSR+0x44、unit-id+0x0b)。 - MXU Latency Overview — 为这些序列调度的 matmul/matprep occupancy 定价的逐代 reservation model。
- 调度概览 — 完整调度 pipeline 中的 Stage 2(MXU sequence assignment)。