Skip to content

MXU 延迟概览

本页中的所有地址都适用于来自 libtpu-0.0.40-cp314 wheel 的 libtpu.so(build libtpu_lts_20260413_b_RC00,BuildID md5 89edbbe81c5b328a958fe628a9f2207d)。该二进制没有 strip —— 下方每个符号都是 demangled C++ 名称。Section map:.text/.rodata VMA == file offset;.data.rel.ro VMA − 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/DFPFVFGLGF);modifier ordinal 和 format binding 位于 matmul-mode-modifiers

重新实现的契约是:

  • per-gen MxuLatencyTable object 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-check resource < N → 读取 array[resource]
  • JF/DF 没有 MxuLatencyTable —— 它们的 MXU occupancy 被折入 inline 15-field latency model。
Classxla::<gen>::MxuLatencyTable(每代一个:viperfishghostlite、TPU7x anon-ns)
VF ctor / tablexla::viperfish::MxuLatencyTable::MxuLatencyTable @0x1c8a52c0 · array<int,19>
VF lookupxla::viperfish::MxuLatencyTable::GetResourceUsage @0x1c8ae5c0
GL lookupxla::ghostlite::MxuLatencyTable::GetResourceUsage @0x1c8b7560 · array<int,11>
TPU7x lookupMxuLatencyTable::GetResourceUsage @0x1c8bdb20 · array<int,11>
Issue-stall consumerMxuOpHoldIssues @0x1c8ad3a0, MxuOpResourceReservations @0x1c8ad080
Resource countMxuResource::kNumMxuResources = 19(VF)/ 11(GL, GF)—— CHECK-anchored
Where the table livespointer 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

GenTpuVerMxuLatencyTableArray width NLookup @addrSource file
Jellyfish / Dragonfish0 / 1none —— inline 15-field model——————
Pufferfish2yes(PF page)——mxu_latency_table_pf.cc
Viperfish3yes19@0x1c8ae5c0mxu_latency_table_vf.cc
Ghostlite4yes11@0x1c8b7560mxu_latency_table_gl.cc
TPU7x (6acc60406)5yes11@0x1c8bdb206acc60406/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 vs 0xB=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:

text
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 unique

zero-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 通过 raw vmovups load 从 hash bucket 读出,先把整个 array<int,N> 复制到 stack 再索引。matmul 读取是 vmovups [rdx+8](8-byte MatmulModifier key 位于 value 之前);matpush 读取是 vmovups [rdx+4](4-byte MatpushModifier key)。假设 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 中按字节确认):

FamilyVF opcode(s)Key typeKey constructionMap
matmul230(另有 212→fmt1,218→fmt2)MatmulModifierbyte[0] = format(plain matmul case 为 6),byte[1..] = 0this+0x20
matpush / matprep267MatpushModifierbyte[0]=GainLatchModeToMatmulDataFormat(latch_mode),byte[1..2]=LatchModeIsTranspose(latch_mode),byte[3]=LatchOpcodeToMsr(0x8F)this+0x00
matpush (xpose pass)271MatpushModifier同上,但 latch_mode ^= 0xB(XOR-flip 选择 transposed staging path)this+0x00
matpush (wide pass)277MatpushModifier同上,但 latch_mode |= 0x14(OR-set 选择 x8/wide bucket)this+0x00
matres(matres opcode)MatresModifierresult-chunk indexmatres map
vlxmr(vlxmr opcode)VlxmrModifierlatch-into-MRB variantvlxmr 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

算法

c
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==315` 和 `resource==110`(`@0x1c8ae5c0`,`a4==3`/`a4==11`)。Ghostlite 上是 `resource==43` 和 `resource==99`(`@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:7893 中按字节确认的 `try_emplace` literal)。这些 slot 建模 matpush overrun matrix-staging register 且 drain depth 增长时的 staircase stall;它们被折入 `SetReservations` densify 的同一个 map,因此 consumer 会把它们当作普通 array cell 读取。`kMsrAOverrunCheck0..3` 占据 `MxuResource` index 25,`kMsrBOverrunCheck0..3` 占据 69

---

## 相关组件

| 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