成本模型概览
地址适用于
libtpu-0.0.40-cp314wheel 中的libtpu.so。其他版本会有所不同。
摘要
libtpu.so 中的 TPU 编译器会按照按代划分的成本模型,为每条 LLO 指令、每个 VLIW bundle、以及每个融合 HLO 区域定价。该模型完全由数据表驱动,发布构建中没有学习式预测器(nm -CD 只找到 LearnedCostModelClientOptions proto 方法,从未找到 Predict/Train 实现;参见 学习式成本模型客户端)。该模型根植于三个彼此独立的类族,每个类族都通过不同的注册表机制按 tpu::TpuVersion 选择,并在底层由一个按代的 Performance 对象统一,该对象保存实际的周期整数。
熟悉 LLVM 的读者可以先把它映射到两个熟悉的组件,再注意二者的差异。Performance 延迟数组类似于 LLVM MCSchedModel 的 WriteLatency 表,即每个 opcode 桶一个周期计数。CycleTable 类似于按目标划分的 TargetSchedModel 查询表面,它把约 462 个 opcode 的 LLO 宇宙折叠成约 33 个“桶”并回答吞吐量查询。差异在于 XLA 的成本模型运行在 HLO 层级(它供给 LatencyHidingScheduler 和融合成本分析),并且与第二套完全独立的 LLVM TPUWriteLatencyTable 并行运行,后者为 LLO 之后的 MIR 定价。这两层使用不同的周期常量,且从不共享数字;本页只记录 XLA HLO 层级模型。
三个类族,三个分发器。LatencyTable 类族回答依赖延迟(写后读停顿),并通过按版本索引的 absl::InlinedVector 注册表选择。CycleTable 类族回答吞吐量(发射速率周期),并通过以 Target 为键的 util_registration::FunctionRegistry 哈希表选择。Performance 类族保存原始的按 (opcode, resource) 划分的周期数据,并分成两种架构:v2/v3 使用多态的 platforms_deepsea::jellyfish::isa::Performance{Jf,Df},v4+ 使用非多态的 xla::<gen>::<Gen>Performance 值类型。本页末尾把三者关联到一张按代类表,并解释代际合并模式(六个版本对应五个类)。
对重新实现而言,契约是:
- 两套注册表机制:
LatencyTable使用按版本索引的InlinedVector,CycleTable使用以Target为键的FunctionRegistry;包括 SOO 编码、边界检查和静态初始化填充。 LatencyTable::Create(TpuVersion)工厂分发,以及它调用的按代 factory→ctor→vtable 链。Performance::CreateTensorCore的DeviceIdentifiers分发,它在一个LatencyTable类背后区分 v2 (Jf) 与 v3 (Df)。- 按代类表(五个
LatencyTable+ 五个CycleTable+Performance类族)以及生成该表的硅架构接缝合并模式。 - 将 bundle 周期转换为挂钟秒数的 TC 时钟频率连线。
| LatencyTable 工厂 | xla::jellyfish::LatencyTable::Create(TpuVersion) @ 0x1c89fba0 |
| LatencyTable 注册表 | (anon)::registry @ 0x225799f8 — 按版本索引的 absl::InlinedVector<AnyInvocable,8> |
| CycleTable 工厂 | xla::jellyfish::CycleTable::Create(Target const&) @ 0x1c89cc00 |
| CycleTable 注册表 | GetCycleTableRegistry()::r @ 0x225799e8 — util_registration::FunctionRegistry |
| Performance (v2/v3) 工厂 | platforms_deepsea::jellyfish::isa::Performance::CreateTensorCore @ 0x1d4927e0 |
| 代际 | TpuVersion 0..5 → jellyfish / dragonfish / pufferfish / viperfish / ghostlite / 6acc60406 |
| TC 时钟访问器 | Target::TensorCoreFrequencyInMegaHertz @ 0x1d615b60(读取 Target+0x90c) |
| 成本模型层级 | HLO 层级(LHS + 融合成本);独立于 LLVM TPUWriteLatencyTable |
三类族架构
成本模型并不是一个对象,而是三个类族,每个类族都按代划分并连接在一起,使一次查询(GetLatencyBetween、GetCyclesForThroughput)能抵达正确的按代周期整数。
TpuVersion (0..5, from Target+0x398)
│
├─ LatencyTable::Create(version) ── dependency latency (RAW stalls)
│ registry @0x225799f8 (InlinedVector, version-indexed)
│ └─ {Jellyfish, Pufferfish, Viperfish, Ghostlite, Gf/Gfc} subclass
│
├─ CycleTable::Create(Target) ── throughput (issue-rate cycles)
│ FunctionRegistry @0x225799e8 (FlatHashMap, Target-keyed)
│ └─ {JfCycleTable, PfCycleTable, VfCycleTable, GlcCycleTable, GfcCycleTable}
│
└─ Performance (the raw cycle integers)
├─ v2/v3: isa::Performance{Jf,Df} (polymorphic, CreateTensorCore)
└─ v4+ : xla::<gen>::<Gen>Performance (value-types in the CycleTable ctor)
```text
> **注意 —** `LatencyTable` 和 `CycleTable` 都查询*同一个*按代 `Performance` 对象;它们只是在查找方法上不同(`GetLatency`/`GetLatencyBetween` 对 `GetCyclesForThroughput`)。吞吐量周期和依赖延迟是同一张周期表的两个视图;参见 [Performance 类族概览](performance-overview.md) 和 [CycleTable 类族](cycletable-family.md)。
两套注册表机制刻意保持*不同*,这种不对称是该成本模型最令人意外的结构事实。`LatencyTable` 使用原始 `InlinedVector`,由 `TpuVersion` 序号直接索引,O(1)、无哈希、无互斥锁。`CycleTable` 使用通用的 `util_registration::FunctionRegistry<TpuVersion, ...>`,即一个带互斥锁保护的 `FlatHashMap`,其工厂接受完整的 `Target const&` 而不是裸版本。把二者改用同一种机制的重新实现仍能在功能上正确,但不会匹配二进制的分发成本或致命错误消息路径。
---
## LatencyTable 工厂 — `LatencyTable::Create`
### 用途
`LatencyTable::Create(TpuVersion)` 返回按代对象,用于为调度器的 `GetLatencyBetween` 查询定价写后读依赖延迟。它是**基于按版本索引向量的注册表分发**,不是硬编码的 `switch`。
### 入口点
```text
LatencyTable::Create(TpuVersion) @0x1c89fba0
└─ registry[version].invoker(entry, version) ── the AnyInvocable LocalInvoker
└─ new <size> + LatencyTable<Gen>::ctor(version)算法
function LatencyTable_Create(version): // sub_1C89FBA0
if VLOG(2) enabled: // cmp esi,0x2 + VLogSite @0x2236e060
log("Creating latency table for deepsea version: ", version)
if registry == null: // (anon)::registry @0x225799f8
LogFatal("registry != nullptr") // latency_table.cc:120
if (s64)version < 0:
LogFatal("index >= 0") // latency_table.cc:122
reg0 = *registry // SOO header word
size = reg0 >> 1 // InlinedVector size = header>>1
if size <= version:
LogFatal("index < registry->size()") // latency_table.cc:123
buf = (reg0 & 1) ? *(registry+0x10) : registry+0x10 // inline vs heap buffer
entry = buf + version * 0x20 // element stride 0x20
factory = *(entry + 0x18) // AnyInvocable invoker fn ptr
if factory == null:
LogFatal("(*registry)[index]") // latency_table.cc:124
return factory(entry, version) // call rax @0x1c89fc0f
```text
该分发在反编译中是逐字节精确的:`absl::InlinedVector` 头字为 `2*size | inline_bit`,因此 `size = header >> 1`,`inline_bit = header & 1`。每个元素为 `0x20` 字节;`AnyInvocable` 的 manager 位于 `+0x10`,invoker 位于 `+0x18`。`Create` 以 `rdi = entry`(`AnyInvocable` 的存储区)和 `esi = version` 调用 invoker。
> **陷阱 —** `Create` 顶部的 `cmp esi, 0x2` **不是**分发分支,而是 `VLOG(2)` 详细级别门控(`VLogSite` @ `0x2236e060`)。如果重新实现者按字面阅读反汇编,会把它误认为“version >= 2 → 某条路径”。它只控制是否发出跟踪行。真正的边界是注册表上的 `version < size`。
### 注册表填充 — `LatencyTable::Register`
注册表在静态初始化期间由 `LatencyTable::Register(version, AnyInvocable)` @ `0x1c89fac0` 填充。它把 `InlinedVector` 调整到 `version + 1`(第一次调用时惰性分配 `operator new(272, 16)`),然后把 `AnyInvocable` 的 manager 存到槽位 `version*0x20 + 0x10`,把 invoker 存到 `+0x18`。这些存储偏移与 `Create` 读回的偏移完全相同,确认注册表是**由 `TpuVersion` 序号直接索引**。
六次 `Register` 调用来自五个翻译单元初始化器:
| TpuVer | 代号 / 代际 | 静态初始化 TU | 工厂 invoker | ctor(new 大小) | 子类 |
|---|---|---|---|---|---|
| 0 | jellyfish / v2 | `latency_table_jf.cc` @ `0x21353860` | jf `$_0` @ `0x1c8a1280` | `LatencyTableJellyfish` @ `0x1c8a0c20` (0x58) | `xla::jellyfish::LatencyTableJellyfish` |
| 1 | dragonfish / v3 | `latency_table_jf.cc` @ `0x21353860` | jf `$_1` @ `0x1c8a12c0` | `LatencyTableJellyfish` @ `0x1c8a0c20` (0x58) | `xla::jellyfish::LatencyTableJellyfish` |
| 2 | pufferfish / v4 | `latency_table_pf.cc` @ `0x213538d0` | pf `$_0` @ `0x1c8a31c0` | `LatencyTablePufferfish` @ `0x1c8a1960` (0x1e0) | `xla::pufferfish::LatencyTablePufferfish` |
| 3 | viperfish / v5p | `latency_table_vf.cc` @ `0x21353920` | vf `$_0` @ `0x1c8a5280` | `LatencyTableViperfish` @ `0x1c8a3f20` (0x1e0) | `xla::viperfish::LatencyTableViperfish` |
| 4 | ghostlite / v6e | `latency_table_gl.cc` @ `0x21353970` | gl `$_0` @ `0x1c8b28e0` | `LatencyTableGhostlite` @ `0x1c8b0c00` (0x1e0) | `xla::ghostlite::LatencyTableGhostlite` |
| 5 | 6acc60406 / v7 | `latency_table_gf.cc` @ `0x213539c0` | gf `$_0` @ `0x1c8bb180` | (anon-ns gf ctor) @ `0x1c8b9520` (0x1e0) | (Gf/Gfc anon-ns) |
五个子类服务六个版本:`LatencyTableJellyfish` 被注册两次(槽 0 和槽 1),区别只在于它转发给 ctor 的 `TpuVersion`。Jellyfish 对象很小,只有 `0x58` 字节,因为 v2/v3 延迟模型是 15 个内联字段,没有堆上的 `MxuLatencyTable`。PF/VF/GL/GF 对象为 `0x1e0`(480 字节):它们携带一个堆表层,包括 `+0x1d8` 的 `MxuLatencyTable`(VF/GL)以及约 `30` 项的 XLU 冲突惩罚表。
> **注意 —** v7 (`Gf/Gfc`) 的 `LatencyTable` 子类是 `latency_table_gf.cc` 中的**匿名命名空间**类型,没有具名 typeinfo 符号,因此无法从符号表恢复其精确 C++ 类名(对*名称*为 MEDIUM 置信度;其 vtable @ `0x21c20930`(offset-to-top 位于 `0x21c20930`,typeinfo 位于 `0x21c20938`,首个 slot/vptr 位于 `0x21c20940`,ctor 安装 `*obj = 0x21c20940`)、ctor @ `0x1c8b9520`、以及 `GhostlitePerformance` 委托都是 CERTAIN)。它的 `VectorRawHazardCycles` 返回常量 `7`,并且其 `LatencyBetweenInternal` @ `0x1c8bab40` 复用 `GhostlitePerformance::GetResourceUsage`;v6e/v7 延迟后端在不同包装类背后共享。
### 函数映射
| 函数 | 地址 | 角色 |
|---|---|---|
| `LatencyTable::Create` | `0x1c89fba0` | 按版本索引的注册表分发 → 按代工厂 |
| `LatencyTable::Register` | `0x1c89fac0` | 调整到 `version+1` + 在槽位 `version*0x20` 存储 `{manager,invoker}` |
| `Storage::Resize<DefaultValueAdapter>` | `0x1c8a07c0` | `Register` 中的 `InlinedVector` 增长 |
| `LatencyTable` 基类 ctor | `0x1c89f800` | 空主体,设置基类 vtable,转发版本 |
---
## CycleTable 工厂 — `CycleTable::Create`
### 用途
`CycleTable::Create(Target const&)` 返回按代对象,用于通过 `GetCyclesForThroughput(Instruction)` 为发射速率吞吐量周期定价。它通过**以 `Target` 为键的 `FunctionRegistry`** 分发,这是一种不同于 `LatencyTable` `InlinedVector` 的机制。
### 算法
```c
function CycleTable_Create(target): // sub_1C89CC00
once: GetCycleTableRegistry()::r = new(0x28) // __cxa_guard lazy-init @0x225799e8
version = *(int*)(target + 0x398) // TpuVersion at Target+0x398
fn = FunctionRegistry::Get(registry, version) // sub_1C89CD20: Mutex + FlatHashMap::find
if fn.missing: // [result+0x10] == 1
LogFatal("No cycle table registered for platform: ", version) // cycle_table.cc:960
return fn(target) // call the registered std::function该注册表是 util_registration::FunctionRegistry<TpuVersion, unique_ptr<CycleTable>(Target const&)>,即一个受 Mutex 保护的 FlatHashMap。它的 Get(@ 0x1c89cd20)获取共享锁并探测版本键;未命中是致命错误。它存储的工厂是一个接受完整 Target const&(而非裸版本)的 std::function,因此按代 CycleTable ctor 可以直接读取芯片参数。
_GLOBAL__sub_I_cycle_table.cc @ 0x21353460 执行六次 Register 插入。五个不同的 CycleTable 子类(都在 xla::jellyfish 命名空间中)以及每个子类构建的 Performance 如下:
| TpuVer | 代际 | CycleTable 子类 | new 大小 | 构建的 Performance | vtable |
|---|---|---|---|---|---|
| 0 (v2) | jellyfish | JfCycleTable | 0x18 | CreateTensorCore → PerformanceJf | 0x21c1ffb8 |
| 1 (v3) | dragonfish | JfCycleTable | 0x18 | CreateTensorCore → PerformanceDf | 0x21c1ffb8 |
| 2 (v4) | pufferfish | PfCycleTable | 0x30 | PufferfishPerformance | 0x21c20048 |
| 3 (v5p) | viperfish | VfCycleTable | 0x20 | ViperfishPerformance | 0x21c200c8 |
| 4 (v6e) | ghostlite | GlcCycleTable | 0x30 | GhostlitePerformance | 0x21c20148 |
| 5 (v7) | 6acc60406 | GfcCycleTable | 0x30 | GhostlitePerformance(共享) | 0x21c201c8 |
JfCycleTable 服务 v2 和 v3(两次注册,一个类)。GlcCycleTable 与 GfcCycleTable 是不同包装类,但都构造 GhostlitePerformance;v6e/v7 吞吐量后端共享,这与 LatencyTable 侧相呼应。
Performance 类族 — CreateTensorCore
用途
Performance 对象保存原始周期整数:一个按指令的延迟数组,以及一个二维的按 (Instruction, Resource) 划分的周期成本数组。这里有两种架构。
对 v2/v3,Performance::CreateTensorCore(DeviceIdentifiers) @ 0x1d4927e0 通过比较设备标识符在两个多态子类之间选择:
function CreateTensorCore(ids): // sub_1D4927E0
if ids == kJellyfishIdentifiers: // @0xbdf3c0c, byte compare
return new(0xE00) PerformanceJf(ids) // vtable 0x21cc74b8
elif ids == kDragonfishIdentifiers: // @0xbdf3c18
return new(0xE00) PerformanceDf(ids) // vtable 0x21cc7468
else:
LogFatal("Don't know how to create performance for ...") // performance.cc:27
```text
v2 与 v3 的成本差异就在这里。一个 `JfCycleTable` 类和一个 `LatencyTableJellyfish` 类同时服务二者,但底层网格是 `PerformanceJf` (v2) 或 `PerformanceDf` (v3)。`PerformanceDf` 与 `PerformanceJf` 字节级相同,除偏移 `+0x28` 处的单个补丁外(以 DWORD 对存储 `66` 和 `13`);这一个差异就是该构建中 Jellyfish 与 Dragonfish 之间的全部成本差异,并且它存在于依赖延迟路径,而非吞吐量路径。
对 v4+,`Performance` 是非多态的 `xla::<gen>::<Gen>Performance` 值类型(`PufferfishPerformance`、`ViperfishPerformance`、`GhostlitePerformance`),在按代 `CycleTable` ctor 内构造。按代网格元数据见 [Performance 类族概览](performance-overview.md)。
---
## 按代成本模型类表
权威的按代表把执行单元计数(从按代 `VectorIsa` 读取,`Target+0x4a8`=iar,`+0x4ac`=mxu,`+0x4b0`=xlu)与三个类族合并在一起:
| TpuVer | 代号 | 代际 | mxu | xlu | iar | LatencyTable | CycleTable | Performance | LT 大小 | LT vtable |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | jellyfish | v2 | 1 | 1 | 2 | `LatencyTableJellyfish` | `JfCycleTable` | `PerformanceJf` (isa) | 0x58 | `0x21c202d0` |
| 1 | dragonfish | v3 | 2 | 1 | 2 | `LatencyTableJellyfish` | `JfCycleTable` | `PerformanceDf` (isa) | 0x58 | `0x21c202d0` |
| 2 | pufferfish | v4 | 4 | 2 | 2 | `LatencyTablePufferfish` | `PfCycleTable` | `PufferfishPerformance` | 0x1e0 | `0x21c20320` |
| 3 | viperfish | v5p | 4 | 3 | 2 | `LatencyTableViperfish` | `VfCycleTable` | `ViperfishPerformance` | 0x1e0 | `0x21c203f0` |
| 4 | ghostlite | v6e | 2 | 2 | 2 | `LatencyTableGhostlite` | `GlcCycleTable` | `GhostlitePerformance` | 0x1e0 | `0x21c20698` |
| 5 | 6acc60406 | v7 | 2 | 2 | 2 | (Gf/Gfc anon-ns) | `GfcCycleTable` | `GhostlitePerformance` (shr) | 0x1e0 | `0x21c20930` |
> **特性 —** 成本模型把六个 `TpuVersion` 折叠到**五个**位于硅架构接缝处的类边界。v2/v3 共享 Jellyfish 延迟类 + `JfCycleTable` + `isa::Performance` 类族(只通过 `DeviceIdentifiers` → Jf/Df 分裂)。v4 和 v5p 各自获得专用三类组。v6e/v7 在不同包装类背后共享 `GhostlitePerformance`。这就是为什么 v6e/v7 的 `VectorIsa` 字节级相同(mxu=2、xlu=2、iar=2),且 GL/GF 超越函数估计相同(142 / 151):它们是两个包装类背后的同一后端。按六个版本建键的重新实现会分配两个对象,而二进制分配一个共享后端;功能上可以,但不会复现二进制依赖的共享指针身份。
---
## 时钟频率 — 周期到挂钟时间
成本模型产生周期计数;调度器需要秒数。转换读取一个按代字段。`Target::TensorCoreFrequencyInMegaHertz` @ `0x1d615b60` 返回 `*(uint32*)(Target + 0x90c)`,该字段在 `Target::Init` 中由 `TpuCorePartsProto.frequency_mhz` 字段填充。随后 `NodeCost`(`CostModelLatencyEstimator::NodeCost` @ `0x10ffaba0`)计算:
```c
time_s = cycles * trip_count / (TC_freq_MHz * 1e6) // 1e6 const @0xa2e0208CostModelLatencyEstimator::CyclesPerMicrosecond @ 0x10ffb3c0 不是常量,而是直接委托给 TensorCoreFrequencyInMegaHertz(target)(读取估算器在 +0x28 处的 Target 指针),因此 kCyclesPerMicrosecond == TC_freq_MHz。TensorCoreFrequencyInMegaHertz(0x1d615b60)是单条指令的 mov 0x90c(%rdi),%eax 字段读取;该值从加载到 Target+0x90c 的 chip_parts blob 中按当前 TpuVersion 选择。该构建内嵌全部九个代号的 chip_parts blob(参见 chip_parts.binarypb 解码),所以按代 TC 时钟是可恢复的,不是推断的:v6e = 1750 MHz,v7x = 1900 MHz(已通过 chip_parts 的 Core[TC].frequency_mhz 字段 CONFIRMED,二者都以 32 位 .rodata 常量存在:v7 chip_parts 块中的 0x76c = 1900,v6e 的 0x6d6 = 1750)。连线(Target+0x90c → /freq/1e6)与代际无关。
相关组件
| 组件 | 关系 |
|---|---|
| Performance 类族概览 | 两个工厂都会选择的原始按 (opcode, resource) 划分的周期网格 |
| CycleTable 类族 | 由 CycleTable::Create 分发的吞吐量表子类 |
| Resource 枚举(23 槽) | Acc/MaxResourceCycles 归约的 ResourceVector 槽 |
| MXU 延迟概览 | v4+ LatencyTable 对象内部的堆 MxuLatencyTable 层 |
| ResourceType 分类 | 调度器单独的 47-ID 资源模型(并发性,而非周期权重) |
交叉引用
- Resource 枚举(23 槽) — 成本模型供给的
ResourceVector槽模型和MaxResourceCycles归约 - Performance 类族概览 — 两个工厂共享的周期整数后端
- CycleTable 类族 —
CycleTable子类分发和Instruction桶枚举 - JfCycleTable — v2/v3 吞吐量表以及
0x19FFC0821桶掩码 - 按 Opcode 划分的周期常量 — 按代周期主表
- MXU 延迟概览 — 通过 v4+
LatencyTable抵达的MxuLatencyTable层 - 按代计数汇总 — 融入类表的 mxu/xlu/iar
VectorIsa计数 - 学习式成本模型客户端 — 为什么发布模型由数据表驱动,而不是 ML
- MXU 槽 — 成本模型归入 matmul/matpush/matres 的 LLO MXU opcode
- 调度器概览 —
GetLatencyBetween的消费者LatencyHidingScheduler