MXU 延迟概览
本页中的所有地址都适用于来自
libtpu-0.0.40-cp314wheel 的libtpu.so(buildlibtpu_lts_20260413_b_RC00,BuildID md589edbbe81c5b328a958fe628a9f2207d)。该二进制没有 strip —— 下方每个符号都是 demangled C++ 名称。Section map:.text/.rodataVMA == file offset;.data.rel.roVMA − 0x200000 == file offset。
摘要
MxuLatencyTable 是 per-generation cost model 的 MXU 半边。基础 Performance 模型(performance-overview 中的 per-opcode latency grid)给出每条 LLO instruction 的总 cycle latency;它无法表达的是 matrix-unit op 如何占用 MXU pipeline,也就是当 op in flight 时,每个内部 sub-unit(gain array、两个 matrix-staging register、matrix-result buffer read port)会被持有多少 cycles。两个 back-to-back matmul push 不是简单地把 latency 相加;它们会在共享的 MXU sub-resource 上排队,后续 push 会 stall 到它需要的 resource 释放为止。MxuLatencyTable 正是为这种 occupancy 精确定价的 reservation model。
熟悉的参照系是 LLVM TargetSchedModel / MCSchedModel:一条 instruction 会 reservation 一组 ProcResource 若干 cycles,而 list scheduler 只有在每个被 reservation 的 resource 都可用时才推进 issue cursor。MxuLatencyTable 是同一思想针对一个 functional unit 的专门化。每个 MXU op 会被归约为一个小的 modifier key(捕获其 data format、transpose flag 以及它触及的 matrix-staging register),该 key 索引一个 flat_hash_map,其 value 是固定长度的 array<int,N>,而 array[resource] 就是该 MxuResource 的 hold-cycle count。Viperfish 上 N 是 19(array<int,19>),Ghostlite / TPU7x 上 N 是 11(array<int,11>)—— resource set 是 per-generation 的,因此每代都有不同 subclass 和自己的 table。基础 scalar Performance::GetResourceUsage 对每个 resource 返回单一 cycle count;MxuLatencyTable::GetResourceUsage 会为 MXU op 覆盖它,返回 table cell。
本页记录每代共享的模型和索引方案:四个 op family 及其 modifier key、reservation-array 布局、find → array[resource] 读取、per-gen default fallback,以及 mxu-opholdissues-stall 中的 issue-stall logic 如何消费结果。实际的整数 reservation matrix 位于各代页面(JF/DF、PF、VF、GL、GF);modifier ordinal 和 format binding 位于 matmul-mode-modifiers。
重新实现的契约是:
- per-gen
MxuLatencyTableobject layout:每个 op family 一个flat_hash_map<Modifier, array<int,N>>,加上 per-resource default fallback。 - matmul / matpush (latch) / matres / vlxmr family 各自的 modifier-key construction:哪些 byte 编码 format、transpose 和 matrix-staging register。
GetResourceUsage(Instruction, Resource)lookup:按 opcode 选择 family → 构建 key →find→ bounds-checkresource < N→ 读取array[resource]。- JF/DF 没有
MxuLatencyTable—— 它们的 MXU occupancy 被折入 inline 15-field latency model。
| Class | xla::<gen>::MxuLatencyTable(每代一个:viperfish、ghostlite、TPU7x anon-ns) |
| VF ctor / table | xla::viperfish::MxuLatencyTable::MxuLatencyTable @0x1c8a52c0 · array<int,19> |
| VF lookup | xla::viperfish::MxuLatencyTable::GetResourceUsage @0x1c8ae5c0 |
| GL lookup | xla::ghostlite::MxuLatencyTable::GetResourceUsage @0x1c8b7560 · array<int,11> |
| TPU7x lookup | MxuLatencyTable::GetResourceUsage @0x1c8bdb20 · array<int,11> |
| Issue-stall consumer | MxuOpHoldIssues @0x1c8ad3a0, MxuOpResourceReservations @0x1c8ad080 |
| Resource count | MxuResource::kNumMxuResources = 19(VF)/ 11(GL, GF)—— CHECK-anchored |
| Where the table lives | pointer at LatencyTable this+0x1d8(= +472),来自 process-wide GetSharedMxuLatencyTable() singleton(仅 PF/VF/GL/GF) |
Generation 分拆
在成本模型服务的六个 TPU generation 中,只有四个携带 MxuLatencyTable。JF/DF latency class(LatencyTableJellyfish,v2/v3 subclass,object size 0x58)在其 inline 15-field latency table 内直接为 MXU occupancy 定价 —— 没有单独的 reservation object。Pufferfish(v4)、Viperfish(v5/v5e)、Ghostlite(v6e)和 TPU7x 各自获得一个 LatencyTable,其 +0x1d8(+472)槽持有指向 MxuLatencyTable 的 pointer;LatencyTableViperfish ctor @0x1c8a3f20 从 process-wide singleton GetSharedMxuLatencyTable() 设置该槽(a1 + 472 = mxu_latency_shared),因此 reservation map 只构建一次并共享,而不是每个 LatencyTable 重新构建。per-gen cost-model subclass selection 见 cycletable-family。
| Gen | TpuVer | MxuLatencyTable | Array width N | Lookup @addr | Source file |
|---|---|---|---|---|---|
| Jellyfish / Dragonfish | 0 / 1 | none —— inline 15-field model | —— | —— | —— |
| Pufferfish | 2 | yes | (PF page) | —— | mxu_latency_table_pf.cc |
| Viperfish | 3 | yes | 19 | @0x1c8ae5c0 | mxu_latency_table_vf.cc |
| Ghostlite | 4 | yes | 11 | @0x1c8b7560 | mxu_latency_table_gl.cc |
| TPU7x (6acc60406) | 5 | yes | 11 | @0x1c8bdb20 | 6acc60406/mxu_latency_table_gf.cc(own file;matmul opcodes 289/295/301/307) |
说明 —— array width 是 resource count,不是 row count。
MxuResource是 per-gen enum;kNumMxuResources在 Viperfish 上扩展到 19(XLU 最丰富的一代,xlu_count=3),在 Ghostlite/TPU7x 上收窄到 11。因此两代会对同一个物理 sub-unit 使用不同的 resource index,重新实现必须为每代保留一个 enum —— 不能共享。这由 bounds-check CHECK string(见下方)确认,它只在参与比较的 literal 上不同(0x13=19 vs0xB=11)。
Object Layout
目的
该 table 由 GetSharedMxuLatencyTable 通过 MxuLatencyTable constructor(viperfish @0x1c8a52c0,约 27 KB 的 straight-line try_emplace)在每个进程中 lazily 构建一次,填充多个 flat_hash_map,每个 MXU op family 一个。map 是 lookup 读取的唯一状态。
结构
每个 MxuLatencyTable 是一小组 Abseil flat hash map。lookup 通过 key type(传给 find 的 C++ template argument)区分 family,而不是通过 discriminator field,因此这些 map 位于固定 offset:
MxuLatencyTable (shared singleton, pointer stored at owning LatencyTable + 0x1d8)
this + 0x00 flat_hash_map<MatpushModifier, array<int,N>> ── latch / matprep ops
this + 0x20 flat_hash_map<MatmulModifier, array<int,N>> ── matmul ops
this + ... flat_hash_map<MatresModifier, array<int,N>> ── matrix-result-read ops
this + ... flat_hash_map<VlxmrModifier, array<int,N>> ── vector-latch-into-MRB ops
```text
matpush map 位于 `this+0x00`(`find<MatpushModifier>` 调用传入裸 `this`/`a2`);matmul map 位于 `this+0x20`(`find<MatmulModifier>(a2 + 32, ...)`)—— 两个 offset 都在 `GetResourceUsage` `@0x1c8ae5c0` 中直接读取。matres 和 vlxmr map 位于更靠后的 offset,由专用 helper `SetReservations<MatresModifier>` `@0x1c8acea0` 和 `SetReservations<VlxmrModifier>` `@0x1c8accc0` 填充。
### row 如何构建 —— `SetReservations`
每个 map entry 都以相同方式构建:通过 templated `SetReservations<Modifier>`(VF:matpush `@0x1c8abde0`,vlxmr `@0x1c8accc0`,matres `@0x1c8acea0`;GL:`@0x1c8b5d80`/`@0x1c8b5f60`/`@0x1c8b6140`)。它接收一个 `Modifier` key 和一个小的 `flat_hash_map<MxuResource,int>`(稀疏的“该 op 持有 resource k 共 c cycles”集合),把它扩展成密集的 `array<int,N>`,并插入:
```c
function SetReservations<Modifier>(key, resource_to_cycles_map): // VF @0x1c8abde0
array<int,N> res_vector = {0}; // zero-init all N slots
for (resource_index, cycles) in resource_to_cycles_map:
CHECK(resource_index < N); // "resource_index < to_underlying(
// MxuResource::kNumMxuResources)" — vf.cc:58
res_vector[resource_index] = cycles;
CHECK(target_map->try_emplace(key, res_vector).second); // vf.cc:71 — keys are uniquezero-init 意味着op 未命名的任何 resource 都会持有 0 cycles —— op 从不触及的 resource 不会施加 stall。vf.cc:58 的 CHECK(MakeCheckOpString(resource_index, 19, ...))把 index 硬性限制为 kNumMxuResources,vf.cc:71 的 CHECK 则保证没有 (Modifier) key 被插入两次。二者都在反编译的 SetReservations<MatpushModifier> 中按字节确认。
特性 —— 在
GetResourceUsage中,value array 通过 rawvmovupsload 从 hash bucket 读出,先把整个array<int,N>复制到 stack 再索引。matmul 读取是vmovups [rdx+8](8-byteMatmulModifierkey 位于 value 之前);matpush 读取是vmovups [rdx+4](4-byteMatpushModifierkey)。假设 key width 统一的重新实现,会在两个 family 中有一个从错误 offset 读取 array。
Modifier Keys
目的
modifier key 是把 MXU op 压缩为会改变其 pipeline occupancy 的少量属性。它是 row space 的索引;op 中不影响 occupancy 的所有内容都会被丢弃。
四个 family 及其 key
GetResourceUsage 按 instruction opcode 选择 family,然后用 helper function 构造 key。在 Viperfish 上,opcode→family mapping 和 key construction 如下(在 @0x1c8ae5c0 中按字节确认):
| Family | VF opcode(s) | Key type | Key construction | Map |
|---|---|---|---|---|
| matmul | 230(另有 212→fmt1,218→fmt2) | MatmulModifier | byte[0] = format(plain matmul case 为 6),byte[1..] = 0 | this+0x20 |
| matpush / matprep | 267 | MatpushModifier | byte[0]=GainLatchModeToMatmulDataFormat(latch_mode),byte[1..2]=LatchModeIsTranspose(latch_mode),byte[3]=LatchOpcodeToMsr(0x8F) | this+0x00 |
| matpush (xpose pass) | 271 | MatpushModifier | 同上,但 latch_mode ^= 0xB(XOR-flip 选择 transposed staging path) | this+0x00 |
| matpush (wide pass) | 277 | MatpushModifier | 同上,但 latch_mode |= 0x14(OR-set 选择 x8/wide bucket) | this+0x00 |
| matres | (matres opcode) | MatresModifier | result-chunk index | matres map |
| vlxmr | (vlxmr opcode) | VlxmrModifier | latch-into-MRB variant | vlxmr map |
matpush key 是有意思的那个:它由三个 helper 组装而来 —— GainLatchModeToMatmulDataFormat @0x1d629260(把 LLO GainLatchMode attribute 映射到 MatmulDataFormat code)、LatchModeIsTranspose @0x1d628ea0(transpose bit)以及 LatchOpcodeToMsr(0x8F) @0x1c8a1300(latch 目标 matrix-staging register)。latch mode 的 opcode-specific pre-transform(271 使用 ^0xB,277 使用 |0x14)会把同一个物理 latch 路由到 transposed 或 wide reservation bucket。完整的 MatmulMode/MatmulDataFormat/modifier ordinal table 位于 matmul-mode-modifiers。
陷阱 —— matmul opcode 不会跨 generation 保持稳定。Viperfish 用 opcode 230(
0xE6)匹配 matmul,并有 matmul-format case 212/218;Ghostlite 用 opcode 292(0x124)匹配 matmul family。opcode numbering 在 generation 之间发生了偏移,因此 family dispatch 是 per-gen 的。按 per-gen opcode list 绑定 family,不要用 hardcoded constant。
Lookup
算法
function MxuLatencyTable::GetResourceUsage(instr, resource, is_throughput): // VF @0x1c8ae5c0
// 1. Per-resource default fallback, taken before the table lookup.
if resource == 3: default_cycle = 15 // VF: latch/issue-slot seed
elif resource == 11: default_cycle = 0 // VF: matmul-issue slot seed
else: return InvalidArgument("Unsupported kind of resource") // vf.cc, status path
// 2. Select family + build key from the opcode.
switch (instr.opcode):
case 212: key = MatmulModifier{format=1}; map = this+0x20 // family = matmul
case 218: key = MatmulModifier{format=2}; map = this+0x20
case 230: key = MatmulModifier{format=6}; map = this+0x20
case 267: key = MatpushKey(latch_mode); map = this+0x00 // matpush
case 271: key = MatpushKey(latch_mode ^ 0xB); map = this+0x00
case 277: key = MatpushKey(latch_mode | 0x14); map = this+0x00
default: LogFatal("Unsupported opcode") // vf.cc:578
// 3. find + bounds-check + read.
entry = map.find(key)
if entry not found: throw out_of_range // raw_hash_map::at
array<int,N> res_vector = entry.value // vmovups copy, key-width-dependent offset
CHECK(resource < N) // VF: < 0x13 (19); GL: < 0xB (11)
return res_vector[resource] // the hold-cycle count for this resource
```text
default fallback(step 1)让 table sparse-by-default:对于 seed resource,lookup 会在查询任何 map 之前返回固定 cycle;对于 unsupported resource,它返回 error `Status`,而不是越界读取。step 3 中的 bounds-check(VF 上 `v9 >= 0x13` → `BUG()`)与 `SetReservations` 在写入侧强制的 `kNumMxuResources` guard 相同。
> **陷阱 ——** per-resource default key 是 **per-generation** 的,不共享。Viperfish 上 default 是 `resource==3 → 15` 和 `resource==11 → 0`(`@0x1c8ae5c0`,`a4==3`/`a4==11`)。Ghostlite 上是 `resource==4 → 3` 和 `resource==9 → 9`(`@0x1c8b7560`,`a4==4`/`a4==9`)。重新实现必须从 per-gen lookup 读取 default-resource key;GL/GF 自己页面上的 integer matrix 反映的是 11-resource indexing,不是 VF 19-resource indexing。
### 函数映射
| Function | Address | 角色 |
|---|---|---|
| `viperfish::MxuLatencyTable::MxuLatencyTable` | `0x1c8a52c0` | VF ctor —— 填充所有四个 family map(约 27 KB) |
| `viperfish::MxuLatencyTable::GetResourceUsage` | `0x1c8ae5c0` | VF lookup —— family dispatch + `find` + `array[resource]` |
| `ghostlite::MxuLatencyTable::MxuLatencyTable` | `0x1c8b2920` | GL ctor —— `array<int,11>` |
| `ghostlite::MxuLatencyTable::GetResourceUsage` | `0x1c8b7560` | GL lookup —— defaults `res4→3`,`res9→9` |
| TPU7x `MxuLatencyTable::GetResourceUsage` | `0x1c8bdb20` | TPU7x lookup —— `array<int,11>`;own `gf.cc`,CHECK `mxu_resource_idx < kNumMxuResources`(11)at gf.cc:415 |
| `viperfish::SetReservations<MatpushModifier>` | `0x1c8abde0` | densify `{resource→cycles}` → `array<int,19>`,`try_emplace` |
| `viperfish::SetReservations<VlxmrModifier>` | `0x1c8accc0` | vlxmr family row builder |
| `viperfish::SetReservations<MatresModifier>` | `0x1c8acea0` | matres family row builder |
| `viperfish::AddOverrunCheckReservations` | `0x1c8abfe0` | 插入四个 `kMsr{A,B}OverrunCheck0..3` slot(cycles `5/13/21/29`);`Msr` arg 选择 A-set vs B-set |
| `GainLatchModeToMatmulDataFormat` | `0x1d629260` | matpush key byte[0] —— `GainLatchMode` → format code |
| `LatchModeIsTranspose` | `0x1d628ea0` | matpush key byte[1..2] —— transpose flag |
| `LatchOpcodeToMsr` | `0x1c8a1300` | matpush key byte[3] —— staging-register selector |
| `MxuOpHoldIssues` | `0x1c8ad3a0` | issue-stall recurrence —— table 的 consumer |
| `MxuOpResourceReservations` | `0x1c8ad080` | 在一个 window 上累积 resource-reservation |
---
## Scheduler 如何消费 Reservation
该 table 由两个 method 读取,它们把 per-resource hold-cycles 转换为 issue cursor。`MxuOpResourceReservations` `@0x1c8ad080` 会对一个 instruction window 累积每个 op 施加的 per-`MxuResource` reservation(按 resource 调用 `GetResourceUsage`)。`MxuOpHoldIssues` `@0x1c8ad3a0` 随后计算 *next* MXU op 在可以 issue 前必须等待多少 cycles,给定已经 in flight 的 op 仍持有的 resource —— 这个 stall recurrence 详见 [`mxu-opholdissues-stall`](mxu-opholdissues-stall.md)。
分工类似 `MCSchedModel`:基础 `Performance` grid 提供 *latency*(结果何时 ready),而 `MxuLatencyTable` 提供 *occupancy*(unit 何时可接受下一个 op)。对于 back-to-back matmul stream,throughput 由持有时间最长的 resource 约束,而不是 per-op latency:只持有 staging register 一两个 cycle 的 low-precision push 可以接近 issue-rate 地流水化,同时 multi-hundred-cycle systolic latency 被隐藏在 array depth 内;而持有同一 register 数个 cycles 的 wide/high-precision push 会按比例 throttle 该 stream。上方的 overrun-check ladder(`5/13/21/29`)是这种增长 hold 在二进制中的具体表达;完整的 per-gen reservation integer 位于各 per-gen 页面。
> **说明 ——** `AddOverrunCheckReservations` `@0x1c8abfe0` 不是添加单个 cell —— 它会在 per-op `flat_hash_map<MxuResource,int>` 被 densify 之前,向其中插入**一组四个** overrun-check reservation。对于其 `Msr` argument,它会选择两个 bank 之一:`Msr==0` → `kMsrAOverrunCheck0..3`,`Msr==1` → `kMsrBOverrunCheck0..3`(任何其他值都是 vf.cc:95 上的 `CHECK`-fatal "Invalid MSR.")。两个 bank 携带相同的 cycle ladder `5 / 13 / 21 / 29`(`@0x1c8abfe0`、vf.cc:78–93 中按字节确认的 `try_emplace` literal)。这些 slot 建模 matpush overrun matrix-staging register 且 drain depth 增长时的 staircase stall;它们被折入 `SetReservations` densify 的同一个 map,因此 consumer 会把它们当作普通 array cell 读取。`kMsrAOverrunCheck0..3` 占据 `MxuResource` index 2–5,`kMsrBOverrunCheck0..3` 占据 6–9。
---
## 相关组件
| Name | Relationship |
|---|---|
| `mxu-latency-jf-df` | JF/DF 没有 `MxuLatencyTable` —— occupancy 在 inline 15-field model 中 |
| `mxu-latency-pf` / `-vf` / `-gl` / `-gf` | 由本模型索引的 per-gen integer reservation matrix |
| `matmul-mode-modifiers` | modifier ordinal、format code 以及 family→reservation binding |
| `resource-enum` | 更高层的 23-slot `Resource` vector —— 不同于 `MxuResource` |
| `mxu-opholdissues-stall` | 消费 reservation array 的 issue-stall recurrence |
---
## 交叉引用
- [MatmulMode 与 Modifiers](matmul-mode-modifiers.md) —— 16 个 `MatmulMode` ordinal,`MatpushModifier`/`MatmulModifier` array,`MatmulDataFormat` → reservation-group binding
- [MXU 延迟:JF / DF](mxu-latency-jf-df.md) —— 没有单独 reservation table 的 generation
- [MXU 延迟:PF](mxu-latency-pf.md) —— Pufferfish reservation matrix
- [MXU 延迟:VF](mxu-latency-vf.md) —— Viperfish `array<int,19>` integer matrix
- [MXU 延迟:GL (Ghostlite)](mxu-latency-gl.md) —— Ghostlite `array<int,11>` integer matrix
- [MXU 延迟:GF (6acc60406)](mxu-latency-gf.md) —— TPU7x `array<int,11>` integer matrix
- [MxuOpHoldIssues Stall Recurrence](mxu-opholdissues-stall.md) —— 把 reservation 转换为 issue stall 的 consumer
- [Resource Enum (23-slot)](resource-enum.md) —— 更高层的 `Resource` vector,不是 MXU-internal `MxuResource`
- [MXU Slot](../isa/slot-mxu.md) —— 本 table 定价其 op 的 LLO MXU instruction slot
- [Matprep / IAR / Latch](../isa/slot-matprep-iar-latch.md) —— matpush modifier family 背后的 matprep/latch op
- [Performance Overview](performance-overview.md) —— 本 table 扩展的基础 per-opcode latency grid
- [CycleTable Family](cycletable-family.md) —— per-gen cost-model subclass family;`LatencyTable` `+0x1d8` table slot