Skip to content

MxuOpHoldIssues 停顿递推

地址适用于 libtpu-0.0.40-cp314 wheel 中的 libtpu.so。其他版本会有所不同。

摘要

当两个 MXU 操作在同一个脉动阵列上背靠背发射时,第二个操作必须等到它需要的子单元空闲后才能开始,这是典型的 reservation-station 结构冒险。libtpu 用一个三函数模型为此定价,其顶层是 MxuLatencyTable::GetLatencyBetween(A, B):它计算 A 发射后 B 必须等待的周期数,方式是在 B 持有的 MXU 子单元上取 最大值,即 A 仍然占用每个子单元的时长。这是 MXU 内部版的 SchedModel WriteRes/ReadAdvance 预留,但粒度是具名 MXU 资源(kMatmulAccumulationPipekMsrAOverrunCheck0..3kFusedLoadReservedkLmrReserved),而不是抽象 proc-resource。

该递推是 max 归约,不是串行求和。两个触及不相交子单元的 MXU op 可以自由流水(停顿 0);两个存在争用的 op 在争用最严重的端口处共享。模型由三部分构成:MxuOpResourceReservations(A) 返回 A 的逐资源持有周期向量(array<int,N>,N=19 VF / 11 GL);MxuOpHoldIssues(B) 返回 B 持有的子单元集合(一个 flat_hash_set<MxuResource>,即“发射足迹”);GetLatencyBetween 迭代 B 的持有集合,读取 A 对每个资源的预留,并保留最大值。本页重构这三者,包括两个世代的逐字节精确归约循环和边界检查。

有两个门控决定何时查询这个矩阵。逐边结构门控位于 LatencyTableViperfish/Ghostlite::LatencyBetweenInternal:MXU 预留只精确用于 真实数据依赖的 MXU-MXU op 对;真实 RAW 依赖使用普通 op 延迟;非 MXU 对使用 XLU 冲突惩罚表。第二个是编译环境标志 MxuLatencyBalancingUseSequenceDependencies,它在延迟平衡路径和更简单排序路径之间切换 MXU 序列分配器(AssignMxusForSequenceGroup)。两者均在此记录。

对于重新实现,契约是:

  • 三函数拆分:MxuOpResourceReservations(持有周期向量)、MxuOpHoldIssues(持有资源集合)、GetLatencyBetween(对共享资源取最大)。
  • 闭式停顿递推:同 MXU guard、vlxmr→matmul 可用性 seed(以及 matmul→matres cost-map 旁路)、max 归约循环、逐世代资源数量边界(≤18 VF / ≤10 GL)。
  • 决定矩阵是否触发的逐边结构门控(IsTrueDependencyBetween + LloOpcodeUsesMxu)。
  • MxuLatencyBalancing 环境标志门控及其选择的分配器路径。
递推MxuLatencyTable::GetLatencyBetween(A,B) — VF @0x1c8ae320, GL @0x1c8b71e0
A 的持有周期向量MxuOpResourceReservations(A) — VF @0x1c8ad080, GL @0x1c8b6300 (array<int,N>)
B 的发射足迹MxuOpHoldIssues(B) — VF @0x1c8ad3a0, GL @0x1c8b6760 (flat_hash_set<MxuResource>)
资源边界resource < kNumMxuResources → VF ≤ 18(19 个资源),GL ≤ 10(11 个资源);否则 LogMessageFatal
归约k ∈ B.held_set 执行 stall = max(seed, A.reservation[k])cmovg
逐边门控LatencyTableViperfish::LatencyBetweenInternal @0x1c8a4ac0(GL @0x1c8b22e0
真实依赖测试IsTrueDependencyBetween @0x1c89fdc0FindOperandIndexFull ≥ 0 或两者 opcode 均为 0x14e
MXU-op 测试LloOpcodeUsesMxu @0x10a433e0 — op ∈ [0x8d,0xa5] ∪ [0xa8,0xab] ∪ [0x152,0x153]
环境标志门控MxuLatencyBalancingUseSequenceDependencies @0x1d6b9c80 — env +0xbe8, (~v & 0x101)==0

Issue→Stall 递推

目的

GetLatencyBetween(A, B) 返回调度器必须在两个发射到同一 MXU 的 MXU 操作之间插入的结构停顿。A 是较早(producer)op,B 是较晚 op。结果是 B 在复用 A 仍然持有的共享 MXU 子单元前需要等待的周期数。这不是 A 的结果延迟(那是 GetLatency,用于真实依赖边);它是共享脉动阵列端口产生的结构冒险。

入口点

text
LatencyTableViperfish::LatencyBetweenInternal  (@0x1c8a4ac0)   ── scheduler edge-latency
  └─ [MXU-MXU non-true-dep edge]
       MxuLatencyTable::GetLatencyBetween(A, B)  (@0x1c8ae320)  ── the recurrence
         ├─ MxuOpResourceReservations(A)  (@0x1c8ad080)         ── A's array<int,N> hold vector
         └─ MxuOpHoldIssues(B)            (@0x1c8ad3a0)          ── B's held-resource set
```text

### 算法

```c
function GetLatencyBetween(A, B):                  // VF @0x1c8ae320 / GL @0x1c8b71e0
    // same-MXU guard: unit_id from LloInstruction+0xb (bits 8-9 = mxu id, bit 0x400 = has-mxu)
    idA = (A[+0xb]&0x400) ? ((A[+0xb]>>8)&3 | 0x100000000) : 0
    idB = (B[+0xb]&0x400) ? ((B[+0xb]>>8)&3 | 0x100000000) : 0
    if !(idA.has_mxu && idB.has_mxu):
        if idA.has_mxu != idB.has_mxu: return 0     // one has no MXU → no structural conflict
    else if idA != idB:                             // distinct MXU instances → no conflict
        return 0

    seed = 0
    // availability seed: a matrix-load A (vlxmr/MSR, opcode 0xa9) feeding a matmul B
    if A.opcode == 0xa9 && (B.opcode - 0x9b) < 0xb:  seed = 1   // A=0xa9 (vlxmr), B in [0x9b,0xa5]
    else if A.opcode in [0x9b,0xa5] && B.opcode == 0x152:       // matmul A, matres B
        return MatmulDataFormat-keyed cost-map[A] (this+0x80)   // direct map lookup, not a seed
    // (every other pair: seed = 0, fall through to the max-reduction below)

    resA   = MxuOpResourceReservations(A)           // A's array<int,N> hold-cycle vector
    heldB  = MxuOpHoldIssues(B)                      // set of MxuResources B holds
    stall  = seed
    for k in heldB:                                  // iterate B's held-resource set
        CHECK(k < N)                                 // VF: k<=18 ; GL: k<=10 ; else LogMessageFatal
        if stall < resA[k]:                          // cmovg
            stall = resA[k]                          // MAX over shared resource
    return stall

已验证的 VF 归约循环(@0x1c8ae4c9):movzx edi,[rdx](下一个持有资源 k)、cmp rdi,0x12 ; ja FATALk ≤ 18)、mov eax,[rbp+rdi*4-0xa0]A 的预留数组在 rbp-0xa0)、cmp r12d,eax ; cmovg eax,r12dstall = max(stall, resA[k]))。GL 循环(@0x1c8b73a0)相同,但边界为 cmp rdi,0xak ≤ 10),数组在 rbp-0x6c。CHECK 字符串是 resource < to_underlying(MxuResource::kNumMxuResources)(VF mxu_latency_table_vf.cc:536,GL mxu_latency_table_gl.cc:431)。

QUIRK — 归约是 MAX,不是 SUM。朴素实现若把争用持有时长相加,会高估停顿:独立 MXU 子单元可以流水。模型只在单个争用最严重的子单元上串行化。这与更高层 ResourceVector::MaxResourceCycles 的普通 max 组对应,但粒度是 MXU 内部子资源。

MxuOpResourceReservations — A 的持有周期向量

MxuOpResourceReservations(A) @0x1c8ad080 对每个 MxuResource 返回 A 占用它多少周期(VF 为 array<int,19> / GL 为 array<int,11>)。它是逐 opcode dispatch:构造 modifier key,并从逐类 flat-hash map 中复制匹配的 array<int,N>

c
function MxuOpResourceReservations(A):              // VF @0x1c8ad080
    switch on A.opcode:
        [0x8d,0x96] matpush → MatpushModifier key → find in map(this+0x00) → copy array
        [0x9b,0xa5] matmul  → MatmulModifier  key → find in map(this+0x20) → copy array
        0xa9 (vlxmr/MSR)    → key from .rodata     → find in map(this+0x40)
        0xaa (vlxmr)        → key from .rodata     → find in map(this+0x40)
        0x152 (matres)      → chunk-index key      → find in map(this+0x60)
    // map miss → ThrowStdOutOfRange / LogMessageFatal (a valid MXU op always hits)
```text

`MatmulModifier` key 由指令字段逐字节精确构造:`{byte0 = matmul_data_format(), byte1 = (opcode==0xa5), byte2 = (done_with_gains_mode()!=2), byte3 = 0, word4 = matrix_staging_register()|0x100}`。`MatpushModifier` key 是 `{byte0 = GainLatchModeToMatmulDataFormat(latch_mode()), byte1 = LatchModeIsTranspose(latch_mode()), byte2 = 0, byte3 = LatchInstructionToMsr(instr)}`。这些 map 持有的逐格式预留值见 [MXU Latency: GF](mxu-latency-gf.md)。

### MxuOpHoldIssues — B 的发射足迹

`MxuOpHoldIssues(B) @0x1c8ad3a0` 返回 `B` 在发射时锁定的 MXU 子单元*集合*(一个 `flat_hash_set<MxuResource>`)。同样是逐 opcode dispatch,但 matpush 有一个额外细节:它按 `latch_index_in_sequence()`(0..3)分支,以选择逐序列步骤的持有集合。三个 opcode 范围及各自构造的持有集合:

```c
function MxuOpHoldIssues(B):                         // VF @0x1c8ad3a0
    if (B.opcode - 0x9b) <= 0xA:                     // matmul [0x9b,0xa5]
        seed_set = { from .rodata @0xb43b344 }       // kFusedLoadReserved-class seed
        if done_with_gains_mode(B) != 2:
            insert MsrReservedForLgmr(matrix_staging_register(B))    // kLmrReserved (14)
        if B.opcode == 0xa5 || LloInstructionIsIntegerMatmul(B):
            insert kMatmulAccumulationPipe (16)
        else:
            insert kLmrReserved (14)
        return set
    if (B.opcode - 0x8d) > 9:                         // vlxmr / matres (not matpush, not matmul)
        switch B.opcode:
            case 0xa9: seed = .rodata @0xb43b347, 2   // vlxmr
            case 0xaa: seed = .rodata @0xb43b345, 2
            case 0x152: seed = .rodata @0xb43b349, 1  // matres
            default:   LogMessageFatal("Unsupported MXU op")  // :508
        return flat_hash_set(seed ...)
    // else: matpush [0x8d,0x96]
    seed_set = { from .rodata @0xb43b200, 2 }         // SOO scratch seed
    if !MxuSeqPredicate(latch_mode(B)):               // vtable +0x358 gate
        return seed_set
    switch latch_index_in_sequence(B):                // 0..3
        case 0: insert (4*(msr!=0)) | 2               // kMsr{A,B}OverrunCheck0  (:447)
        case 1: insert (4*(msr!=0)) | 3               // ...Check1                (:454)
        case 2: insert (4*(msr!=0)) + 4               // ...Check2                (:461)
        case 3: insert (4*(msr!=0)) + 5               // ...Check3                (:468)
    return seed_set

GOTCHA — 注意哪个 opcode band 拥有哪个 seed。Matmul [0x9b,0xa5](v6-155)<=0xA)构造 @0xb43b344 seed,加上 done_with_gains/kMatmulAccumulationPipe/kLmrReserved 逻辑;matpush [0x8d,0x96] 是 fall-through else,它 seed @0xb43b200 并运行 latch_index_in_sequence OverrunCheck switch。两者都从连续 opcode 范围构造 held-set,因此很容易互换错。

held-set 元素是具名 MxuResource 枚举值(kMsrAOverrunCheck0..3 / kMsrBOverrunCheck0..3,transpose bit 通过 (4*(msr!=0)) 选择 A 与 B bank;kFusedLoadReservedkLmrReservedkMatmulAccumulationPipe),这由构造函数中的 CHECK 字符串确认(holds.insert(MxuResource::kMatmulAccumulationPipe).second :491holds.insert(MxuResource::kLmrReserved).second :488kMsrAOverrunCheck0..3 :447/454/461/468kFusedLoadReserved :483mxu_latency_table_vf.cc)。

GOTCHA — matpush held set 依赖序列步骤。同一个 matpush opcode 在序列步骤 0 持有 kMsr*OverrunCheck0,但在步骤 3 持有 ...Check3;transpose 标志通过 +4 翻转资源序号(A-bank vs B-bank,即 (4*(msr!=0)) 项)。把 matpush 当作持有固定资源集合来实现,会错误定价 latch 序列内部步骤的背靠背停顿。


Worked Recurrence

同一 MXU 上两个独立的 VF bf16 matmul AB,无真实数据依赖(例如两个 K-tile 累加到不同 MRB chunk;B 不消费 A 的输出):

text
ISSUE (footprints):
  A: matmul fmt1 → MxuOpResourceReservations(A) = {res1:15, res15:8, res17:7, res16:14}
                   (MatmulIssue 15cy, AccA 8, AccC 7, AccB 14)
  B: matmul fmt1 → MxuOpHoldIssues(B) held set = {res1, res15, res16, res17}

STALL (recurrence): GetLatencyBetween(A, B):
  same MXU [yes]
  seed = 0   (matmul-matmul pair: A is matmul-class, not 0xa9, so no availability seed)
  for k in {res1, res15, res16, res17}:  stall = max(stall, resA[k])
     = max(15, 8, 14, 7) = 15
  ⇒ B waits 15 cycles after A issues before re-using the MatmulIssue slot.

If B is instead a bf16 matpush (fmt1, narrow):
  resA(A=matpush) = {res0:2, MSR-A:1, MSR-B:1}
  heldB(B=matpush) ⊇ {res0, MSR-A, MSR-B}
  stall = max(2, 1, 1) = 2  ⇒ back-to-back bf16 matpushes pipeline at 2-cycle issue.

For int8/x8 (GLM_*_S8 → fmt6):
  matpush array {res0:8, MSR-A:7, MSR-B:6} → back-to-back stall 8
  matmul grp4 {res15:32, res17:31, res16:38} → stall 32  (4× bf16 = the x8 4-plane sequence)
```text

retire 半边是独立的:下游 op 若*真实*消费 matmul 结果,会等待完整的基础 TOTAL 延迟(bf16 ≈ 212、fp8 ≈ 204 周期,来自逐世代 Performance array),它通过真实依赖边路由,而不是结构停顿。因此发射率由预留停顿(2/8/15/32)门控,而结果可用性由 `GetLatency` 门控。

---

## 逐边结构门控

### 目的

`LatencyBetweenInternal` 是调度器的边延迟函数(vtable slot `+0x18`)。它为有序对 `(A, B)` 决定哪个成本模型定价该边:真实数据依赖 → 普通 op 延迟;MXU-MXU 结构冒险 → 预留矩阵;跨 lane unit 冲突 → XLU 冲突惩罚表。MXU 预留只在 MXU-MXU 非真实依赖场景中查询,这正是预留矩阵所建模的内容。

### 算法

```c
function LatencyBetweenInternal(A, B):              // VF @0x1c8a4ac0 / GL @0x1c8b22e0
    if (A.opcode - 233) < 4: return 0               // 0xe9..0xec pseudo class
    // (pop-and-then-push-same-FIFO consistency CHECK elided)
    if IsPseudo(A) || IsPseudo(B): return LatencyBetweenPseudo(A, B)

    base = GetLatency(A)                            // A's intrinsic op latency
    if vtable[+0x20](A, B):                         // IsTrueDependencyBetween @0x1c89fdc0
        return base                                  // B genuinely consumes A's result

    if LloOpcodeUsesMxu(A.opcode) && LloOpcodeUsesMxu(B.opcode):
        return MxuLatencyTable::GetLatencyBetween(this.mxu_table /*[this+0x1d8]*/, A, B)

    // else: XLU / permute conflict path
    if HasSetPermutePatternReservation(A, B): ... GetXluPathReservation + XluConflictPenaltyBetween
    ... final clamps (min floor, SetIar→indexed-load) ...
    return max(stall, GetResourceLatency(A, B))

IsTrueDependencyBetween(A, B) @0x1c89fdc0FindOperandIndexFull(B, A) >= 0(B 将 A 列为 operand)或 AB 的 opcode 均为 0x14e(自配对 pseudo)。LloOpcodeUsesMxu(op) @0x10a433e0op ∈ [0x8d, 0xa5] ∪ [0xa8, 0xab] ∪ [0x152, 0x153] 为真(编码为 (op-141) < 0x19 || (op-168) < 4 || (op-338) < 2)。MXU table 本身位于 LatencyTable+0x1d8*((_QWORD*)this+59))。XLU 冲突表位于 LatencyTable+0x18,为非 MXU permute/transpose-FIFO 冲突定价。

NOTE — 两个世代在结构上完全相同。GL LatencyBetweenInternal @0x1c8b22e0 尾调用 GL GetLatencyBetween @0x1c8b71e0,读取同一个 IsTrueDependencyBetween(vtable +0x20)和位于 this+0x1d8 的 MXU table。唯一的逐世代差异是归约内部的资源数量边界(19 VF vs 11 GL)。


MxuLatencyBalancing 环境标志门控

目的

第二个正交门控控制 MXU sequence-group 分配是否使用预留推导的 op 间停顿,还是使用更简单的排序。它是编译环境 knob,不是逐边测试:上面的逐边结构门控始终对 MXU-MXU 非真实依赖边触发;此标志只改变序列分配器如何把这些停顿组合成 MXU 分配。

算法

c
function MxuLatencyBalancingUseSequenceDependencies(env):   // @0x1d6b9c80
    p = env[+0xbe8]                                 // AutoProto* (TpuCompilationEnvironment+0xbe8)
    if !p: p = &AutoProto_globals_                  // default @0x223c8968
    v = AutoOr<bool>::FromProtoOrDie(*p)            // @0xf795300
    return (~v & 0x101) == 0                        // true iff v.bit8 (present) AND v.bit0 (value)
```text

唯一调用者是 `AssignMxusForSequenceGroup @0x10f753c0`。当标志为 ON(`@0x10f75410` 处 `al != 0`)时,该函数构造一个以 MRB 地址为 key 的 `linked_hash_map`,并使用 `CycleTable` 参数应用延迟平衡的 MXU-sequence 分配;当 OFF 时,它分支到更简单的排序路径。

> **NOTE —** accessor 及其 bit 编码(`bit8` present,`bit0` value)逐字节精确,默认 `AutoProto*` 是 `AutoProto_globals_ @0x223c8968`。嵌入的默认 proto 字节未解码,因此 v0.0.40 中 sequence-dependency balancing 默认是 ON 还是 OFF 为 **(LOW confidence)**;门控机制确定,默认值未钉牢。

---

## Function Map

| Function | Address | Role |
|---|---|---|
| `MxuLatencyTable::GetLatencyBetween` (VF) | `0x1c8ae320` | 对共享资源取最大值的停顿递推 |
| `MxuLatencyTable::GetLatencyBetween` (GL) | `0x1c8b71e0` | 相同,边界为 res ≤ 10 |
| `MxuOpResourceReservations` (VF / GL) | `0x1c8ad080` / `0x1c8b6300` | A 的 `array<int,N>` 持有周期向量 |
| `MxuOpHoldIssues` (VF / GL) | `0x1c8ad3a0` / `0x1c8b6760` | B 的 `flat_hash_set<MxuResource>` 发射足迹 |
| `LatencyTableViperfish::LatencyBetweenInternal` | `0x1c8a4ac0` | 逐边门控(vtable +0x18|
| `LatencyTableGhostlite::LatencyBetweenInternal` | `0x1c8b22e0` | GL 逐边门控 |
| `IsTrueDependencyBetween` | `0x1c89fdc0` | `FindOperandIndexFull ≥ 0` ∨ 两者 op 均为 `0x14e` |
| `LloOpcodeUsesMxu` | `0x10a433e0` | op ∈ `[0x8d,0xa5] ∪ [0xa8,0xab] ∪ [0x152,0x153]` |
| `MxuLatencyBalancingUseSequenceDependencies` | `0x1d6b9c80` | env `+0xbe8` AutoOr&lt;bool&gt; 门控 |
| `AssignMxusForSequenceGroup` | `0x10f753c0` | 环境标志的唯一消费者 |
| `flat_hash_set<MxuResource>` held-set ctor | `0x1c8ae0a0` | 构造 B 的 footprint set |

---

## 注意事项

- **资源数量分歧。** VF 携带 19 个 `MxuResource` 值,GL/GF 携带 11 个。CHECK 边界(`≤18` / `≤10`)是归约中唯一的行为差异;重新实现必须按 gen 设置 `A` 的预留数组大小,否则边界检查会触发 `LogMessageFatal`。
- **方向很重要。** `GetLatencyBetween(A, B)` 不对称:`A` 提供预留向量(它持有每个端口多久),`B` 提供 held set(它需要哪些端口)。交换参数会定价另一条不同的边。
- **Seed。** 预归约 seed 只有一种非零情况:matrix-load `A`(vlxmr/MSR,opcode `0xa9`)喂给 matmul `B`(`B.opcode ∈ [0x9b,0xa5]`)时 seed `stall = 1`(`@0x1c8ae320` 处 `v14`)。matmul `A` 对 matres `B`(`0x152`)会完全旁路归约,并返回一个以 `MatmulDataFormat` 为 key 的 cost-map 值(`this+0x80`)。其他所有 pair,包括 matmul-to-matmul 和 matpush-to-matpush,seed 都是 0,因此停顿纯粹是对 held set 的 max。
- **未钉牢的内容。** `MxuOpHoldIssues` SOO-scratch seed(`@0xb43b200`)的字面内容按 4 字节 stride 解码有歧义;起作用的 held set 来自 modifier-map 枚举,seed 的功能角色(scratch)已确认,但其字面字节为 **(LOW confidence)**。`MxuLatencyBalancing` 环境标志的默认值同样未钉牢(见上文)。

## 交叉引用

- [MXU 延迟概览](mxu-latency-overview.md) — 本页使用的 `MxuResource` 枚举和预留矩阵概念
- [MXU 延迟:GF](mxu-latency-gf.md) — `6acc60406` 逐格式 matmul/matpush 预留值和 conv 成本三元组
- [MXU 延迟:GL](mxu-latency-gl.md) — Ghostlite 预留矩阵(11 资源边界)
- [Performance 系列概览](performance-overview.md) — `GetLatency`(retire-half 基础延迟)和逐 gen Performance grid
- [MXU Slot](../isa/slot-mxu.md) — held resources 命名的物理 MXU 子单元