Performance:PF
地址适用于来自 libtpu-0.0.40-cp314 wheel 的 libtpu.so。其他版本会不同。
摘要
PufferfishPerformance 是堆网格 Performance 对象中的第一代,也就是 Pufferfish (v4) 引入、并由 Viperfish/Ghostlite/6acc60406 继承的架构。较早的 Jellyfish/Dragonfish 家族把成本存放在一个带偏移 LUT 的内联 POD 中,而 Pufferfish 在堆上分配两个对象:一个扁平的逐指令延迟数组(336 个 int32,按 Performance::Instruction 索引)和一个二维 Instruction × Resource 占用网格(336 行 × 20 宽的 std::vector<int>)。GetLatency(instr) 读取该数组;GetResourceUsage(instr, res) 读取 grid[instr][res],也就是该指令占用微流水线端口 res 的周期数。这是 libtpu 中类似 LLVM SchedMachineModel 的结构,它不是从 .td 文件读取,而是通过解码填充它的构造函数(PufferfishPerformanceC1 @0x1c8be080)恢复出来的。
两个事实使 Pufferfish 成为该家族的枢纽。第一,它的读取路径,即外层边界、24 字节行步幅、内层边界、row.data[res],与 Viperfish/Ghostlite/6acc60406 的 GetResourceUsage 逐字节相同;一份说明(performance-overview)覆盖全部四代网格,而本页只给出 PF 专有的宽度、单元格和 Ghostlite 之前的列布局。第二,Pufferfish 是最后一代带有 BarnaCore embedding 引擎的版本:LatencyTablePufferfish 为一个 variant<PufferfishPerformance, PufferfishBarnaCorePerformance> 定价,因此一条指令会按其 variant tag 分派到两个不同网格之一。TensorCore 网格(variant 0)是本页转储的内容;BarnaCore 网格(variant 1,PufferfishBarnaCorePerformance)是另一个 0x30 字节对象,通过同一个 ResourceUsageFromInstruction visitor 到达。
PF 网格有 20 个资源列,是网格家族中最窄的(从 PF → VF → GL → GF 的演进为 20 → 28 → 31 → 31)。本页记录按占用者 LLO 类命名的 20 列布局、延迟数组的取值分布、EUP push/pop 占用,以及它相对所有后续代唯一的结构差异:Pufferfish 没有直接的 Xlu 网格列,也没有单独的 MxuLatencyTable;它的 matmul/transpose 占用由网格的 matmul 吞吐列(res 9)加一个单独的 XluConflictPenaltyTable 定价。这个差异的完整说明见 mxu-latency-pf。
对重新实现而言,契约是:
- 双对象布局:
int32 latency[336]堆数组 + 一个 336 行、每行 20 宽std::vector<int>的网格,包含精确的new大小和字段偏移。 GetResourceUsage(instr, res)/GetLatency(instr)读取路径及其两个边界检查。- 20 列
Performance::Resource布局,按向各列写入的 LLO 指令类命名,尤其是 res 9(matmul 吞吐)和 res 6(matrix-result/Xlu 写入)。 - LLO opcode 如何通过
GetPufferfishInstruction到达网格行,以及 MXU band 如何把单个 matmul/matprep opcode 扇出到多个 ordinal。 - BarnaCore variant:同一个
LatencyTablePufferfish定价的第二个网格(PufferfishBarnaCorePerformance)。
| 类 | xla::pufferfish::PufferfishPerformance(非多态值类型) |
| 构造函数 | PufferfishPerformanceC1 @0x1c8be080(对象 0x30 B;约 2376 行构造函数) |
| 延迟数组 | new 0x540 = 1344 B = 336 个 int32,memset(_, 0xff, 1344),随后每个槽位都被覆盖 |
| 网格 | new 0x1f80 = 8064 B = 336 行 × 24 B(std::vector<int>),每行 new 0x50 = 20 个 int32 |
| 读取路径 | GetResourceUsage @0x1c8c3880 · GetLatency @0x1c8c3860 · GetResources @0x1c8c3840 |
| 资源数量 | 20(kResources @0xb43cd94,{0..19} 的 20 字节排列) |
| 已填充单元格 | 180 行中的 265 个网格单元格;全部 336 个延迟槽位都被写入 |
| 分类器 | GetPufferfishInstruction @0x1c8a1fe0(LloValue → Instruction,带 variant tag) |
| Xlu 定价 | res 6(kVectorMatres)+ 通过 XposeXLUReservationLatency @0x1c8a13e0 使用的 XluConflictPenaltyTable,没有直接的 Xlu 网格列 |
| 单例 | GetSharedPufferfishPerformance::pf_shared @0x22579a10 |
对象布局
目的
PufferfishPerformance 回答调度器的两个逐指令问题:流水线深度(GetLatency,用于抬高真依赖边)和逐端口占用(GetResourceUsage,累加到逐 bundle 吞吐压力中)。它把两者都存放在堆对象中,使同一类形状能够扩展到后续代更宽的资源集合。
结构
构造函数 @0x1c8be080 分配并填充两个堆区域。对象本身很小,即 LatencyTablePufferfish 中的 operator new(0x30),只保存数组/网格指针及其计数:
struct PufferfishPerformance { // object 0x30 B; built by PufferfishPerformanceC1 @0x1c8be080
int32* latency; // +0x00 ; new 0x540 = 336 int32, memset 0xff
u64 latency_size; // +0x08 ; = 336
u64 latency_cap; // +0x10 ; = 336
vector<int>* grid; // +0x18 ; new 0x1f80 = 336 × 24-B vector<int>
u64 grid_outer_count; // +0x20 ; = 336 (the GetResourceUsage outer bound)
u64 grid_outer_cap; // +0x28 ; = 336
// each row is a 24-B std::vector<int>: { int* data (new 0x50 = 20 int32, zero-init), size=20, cap=20 }
};
```text
行分配循环在构造函数中是逐字节精确的:`for (i = 16; i != 8080; i += 24)` 为每行分配 `operator new(0x50)`,将其清零(用 `vmovups` 把 `ymm0` 写过 80 字节),并设置 `[row+8] = [row-8+24] = 20`(`std::vector<int>` 的 size 和 capacity)。延迟数组先被 `memset` 为 `0xff`(255),然后**每个**槽位都被覆盖;这不同于 Ghostlite/`6acc60406`,后者的未定价行会保留 `0xff` 默认值。因此重新实现必须写入全部 336 个 PF 延迟槽位;没有任何槽位回退到哨兵值。
> **注意 —** 这里延迟数组的 `memset` 值是 `0xff`(255),由构造函数中的 `memset(v4, 255, 1344)` 确认;这不同于扁平 Jellyfish 家族,后者的 POD 被 memset 为 `0x7fffffff` INT_MAX 哨兵。在 PF 中,因为全部 336 个槽位都被写入,哨兵永远不会到达消费者。
### 示例单元格(按字节锚定)
构造函数通过 `[latency + 4*instr]` 写入延迟,并通过 `[grid + 24*instr]` 再 `[row.data + 4*res]` 写入网格单元格。三个单元格把布局固定到原始字节:
```text
latency[0x67] = 7 ; store [latency + 0x19C], 0x19C = 0x67*4 (rsqrt EUP push)
latency[0x7A] = 83 ; store [latency + 0x1E8], 0x1E8 = 0x7A*4 (matmul band base, format A)
grid[0x67][2] = 1 ; row 0x9A8 = 0x67*24, [row.data + 8] = res 2 (EUP-prep stage A)
grid[0x67][3] = 2 ; row 0x9A8, [row.data + 12] = res 3 (EUP-prep stage B)
grid[0x7A][9] = 8 ; row 0xB70 = 0x7A*24, [row.data + 36] = res 9 (matmul throughput)读取路径
算法
GetResourceUsage @0x1c8c3880 是读取网格单元格的唯一访问器。它接收 Instruction ordinal 和 Resource 列,对两者做边界检查,用 lea 计算 24 字节行步幅,然后读取单元格:
function PufferfishPerformance_GetResourceUsage(perf, instr, res): // @0x1c8c3880
if perf.grid_outer_count <= instr: // [perf+0x20] = 336 ; outer bound
BUG() // trap (ud1 / BUG)
row = perf.grid // [perf+0x18]
v4 = 3 * instr // row = grid + instr*24 (3*instr then *8)
if row[v4*8 + 8].size <= res: // [row+8] = inner bound = 20
BUG()
return *(int*)(row[v4*8].data + 4*res) // grid[instr][res]
```text
```c
function PufferfishPerformance_GetLatency(perf, instr): // @0x1c8c3860
if perf.latency_size <= instr: // [perf+0x8] = 336
BUG()
return *(int*)(perf.latency + 4*instr) // latency[instr]两个边界检查在越界时都会 trap(BUG()),而不是返回默认值;不存在安全的越界读取。GetResources @0x1c8c3840 返回 kResources 遍历顺序 @0xb43cd94(20 字节,{0..19} 的一个排列),也就是迭代某一行以求和逐端口占用时使用的顺序。
陷阱 — 外层索引是逐代的
Performance::Instruction,不是原始 LLO opcode。GetPufferfishInstruction@0x1c8a1fe0把一个LloValue分类成Instructionordinal(跳转表@0xb43927c,索引 = opcode − 2),而 MXU band 会通过二级 latch-mode WORD 表把单个 matmul/matprep opcode 扇出到约 0x60 个 ordinal。直接用 LLO opcode 索引网格的重新实现会误读每个 MXU 行。
variant 分派:TensorCore 与 BarnaCore
LatencyTablePufferfish 不持有单个 Performance;它持有两个单例,即 pf_shared(PufferfishPerformance,[table+0x1d0])和 pf_bc_shared(PufferfishBarnaCorePerformance,[table+0x1d8]),并通过一个 variant<PufferfishPerformance::Instruction, PufferfishBarnaCorePerformance::Instruction> 为指令定价。ResourceUsageFromInstruction 分派该 variant:
// variant index 0 — TensorCore op (@0x1c8a3180)
return PufferfishPerformance::GetResourceUsage(table.pf_shared, instr, res);
// variant index 1 — BarnaCore op (@0x1c8a31a0)
if (instr.tag == 2):
return PufferfishBarnaCorePerformance::GetResourceUsage(table.pf_bc_shared, instr, 0);
else:
return 0;
```text
因此一次 PF 成本查询会按 variant tag 路由到两个网格之一。本页记录 variant 0(TensorCore 网格)。BarnaCore 网格(`PufferfishBarnaCorePerformance::GetResourceUsage` `@0x1c8c4800`,构造函数约在 `@0x1c8c38c0`)是一个独立的 0x30 字节对象,有自己的延迟数组和网格;它是最后一代 embedding 引擎成本模型,此处不转储。
> **特性 —** Pufferfish 是**唯一**一个从同一个 `LatencyTable` 为两个不同核心定价的网格代。Viperfish 及之后每代只有一个 `Performance`。如果重新实现把 PF 建模为一个网格,会静默错误计算 13 个由 `GetPufferfishInstruction` 标记为 high-16 = 1 的 BarnaCore(variant-1)opcode。
---
## 20 个资源列
### 目的
网格的内层轴是 `PufferfishPerformance::Resource` 枚举,也就是 intra-op 微流水线保留端口(EUP-prep、matmul/matprep 吞吐、cross-lane/transpose 结果、sync 等)。二进制中该枚举没有 `ToString`,因此这些列按功能命名:读取哪些 LLO 指令类向各列写入周期(外层索引是 opcode),并以具名访问器 `XposeXLUReservationLatency` 为锚。这些名称在*含义*上足以用于重新实现(每列保留哪个物理端口),但不是字面符号字符串。
> **注意 —** 这个 20 列 `Performance::Resource` 枚举不同于 23 槽逐 bundle 的 `ResourceVector`([`resource-enum`](resource-enum.md)),是一个**不同且更低层**的枚举。网格为 intra-op 微流水线阶段占用定价;`ResourceVector` 是更高层成本模型写入的逐 bundle 功能单元累加器。`kResources` `@0xb43cd94` 给出的是网格的列遍历顺序,不是 `ResourceVector` 槽位顺序。
### 列
`kResources` `@0xb43cd94` 以填充顺序列出 20 列:`13 11 08 00 05 02 03 12 0a 06 09 0e 04 01 0b 10 07 0c 0d 0f`(`{0..19}` 各出现一次)。按主导占用者 LLO 类为各列命名(单元格数量、取值集合、占用者):
| 列 | 单元格 | 取值 | 占用者 LLO band(分类器命名) | 物理端口(功能) |
|---|---|---|---|---|
| r0 | 1 | 5 | `kDma`(instr 0x32) | DMA 地址/发射端口 A |
| r1 | 1 | 5 | `kDma`(instr 0x32) | DMA 地址/发射端口 B |
| r2 | 6 | 1 | EUP push 0x67..0x6c(rsqrt/pow2/log2/tanh/recip/erf) | EUP transcendental-prep stage A |
| r3 | 6 | 2 | 同一 EUP push band | EUP transcendental-prep stage B |
| r4 | 2 | 7, 9 | `kVectorSetRngSeed`(0x6d)、`kVectorPrng`(0x6f) | RNG seed / PRNG 设置端口 |
| r5 | 1 | 1 | `kVectorEupResult`(0x76 = EUP pop) | EUP-result-pop drain 端口 |
| r6 | 1 | 8 | **`kVectorMatres`(0x77)** | **matrix-result(Xlu)写入端口** |
| r7 | 1 | 8 | `kVectorXlaneResult`/permute/transpose-result(0x78) | cross-lane / transpose 结果端口 |
| r8 | 1 | 2 | `kVectorCmemResult`(0x79) | Cmem-result 端口 |
| r9 | 96 | 8, 16 | MXU matmul band(0x7a..0xd9;latch-mode ords) | **MXU matmul 吞吐端口**(×96) |
| r10 | 20 | 7 | MXU matprep band(0xdc..0xef) | MXU matprep 吞吐端口 |
| r11 | 54 | 1, 8, 16 | matprep;`SetSegmentPattern`(0xf3);transpose-binary(0xfc);permute(0x104);rotate(0x106) | matprep / transpose-binary 结果 A |
| r12 | 34 | 1, 8, 16 | 同一 transpose/permute/rotate band | transpose / permute 结果 B |
| r13 | 4 | 47, 55 | `kVectorPermute`(0x104)、`kVectorRotate`(0x106) | permute/rotate 扩展结果阶段 |
| r14 | 10 | 18 | `kVector{Add,Max,Min,..}ReduceF32`(0x108..0x111) | reduce-result stage A |
| r15 | 10 | 57 | 同一 reduce band | reduce-result stage B |
| r16 | 11 | 1 | `kVectorSyncFlag{Set,Add}*` / `kVectorWait*`(0x113..) | sync-flag / wait 端口 |
| r17 | 2 | 4 | `kVectorCmemStore`(0x135..0x136) | Cmem-store 端口 |
| r18 | 3 | 4 | `kVectorSetIar{Lane,Sublane,Raw}`(0x14b..0x14d) | SetIar(index-addr-reg)端口 |
| r19 | 1 | 2 | `kVectorCmemLoad`(0x14f) | Cmem-load 端口 |
逐列单元格数量(总和 265):`r0:1 r1:1 r2:6 r3:6 r4:2 r5:1 r6:1 r7:1 r8:1 r9:96 r10:20 r11:54 r12:34 r13:4 r14:10 r15:10 r16:11 r17:2 r18:3 r19:1`。
### matmul 吞吐列(res 9)和 matrix-result 列(res 6)
Res 9 是单个 matmul 吞吐端口,共 96 个单元格,取值集合 `{8, 16}`,覆盖整个 MXU matmul band(`Instruction` 0x7a..0xd9)。在 PF 上,全部逐格式 matmul 吞吐都折叠到这一列中;后续代把它展开到多阶段 matprep 组(VF res 3 + r4..r7,GL/GF 为更宽的 EUP-AndPop FIFO)。单元格值(窄格式为 8,宽格式为 16)是该格式 matmul 施加的逐发射吞吐占用。
Res 6 是 matrix-result 写入(`kVectorMatres` 0x77,值 8),即 PF 中类似 conv `R[2]` 的 Xlu 项。它是写入列,但 PF **不会**像 VF/GL/GF 读取其 Xlu 列那样,通过 `GetXluPathReservation` 访问器读取它。相反,conv/transpose Xlu 保留由单独的 `XluConflictPenaltyTable` 定价,这是下文和 [`mxu-latency-pf`](mxu-latency-pf.md) 详细说明的结构差异。
> **特性 —** MXU matmul band ordinal 0x7a..0xd9 在 `GetPufferfishInstruction` 中**没有**跳转表分支;它们只能通过二级 latch-mode WORD 表到达,该表把单个 matmul/matprep opcode 扇出到整个 band。网格行存在且带有单元格,但行身份是 `(matmul-opcode, GainLatchMode)` 对,而不是直接分类的 opcode。重新实现必须复现 latch-mode 扇出,而不是只实现直接分支分类器。
---
## 延迟数组
延迟数组和 EUP push/pop 边都直接来自构造函数写入。336 项数组有 14 个不同取值:
| 延迟 | 数量 | 含义 |
|---:|---:|---|
| 1 | 145 | 便宜的 vector / scalar ops |
| 83 | 48 | MXU matmul 基础延迟,format A |
| 101 | 48 | MXU matmul 基础延迟,format B |
| 2 | 30 | sync / wait |
| 7 | 26 | EUP push + matprep |
| 126 | 16 | transpose-binary |
| 79 | 10 | reduce |
| 5 | 4 | (中等成本) |
| 4 | 2 | (中等成本) |
| 69 | 2 | permute |
| 77 | 2 | rotate |
| 3 | 1 | (单个 op) |
| 30 | 1 | (单个中等成本 op) |
| 53 | 1 | Cmem-load |
两个 MXU 基础延迟 83 和 101 是两种 matmul 数据格式的流水线深度(format-A 与 format-B 路径);它们位于 matmul band 头部(`latency[0x7A]=83`,字节确认)。全部六个已分类的 F32 EUP 函数(rsqrt = pow2 = log2 = tanh = recip = pushErf,`Instruction` 0x67..0x6c)的 EUP push 延迟统一为 7;EUP pop(`Instruction` 0x76)为 1。
### EUP push 网格占用
Pufferfish 的一个独特点是,它的 EUP push 除了 7 周期延迟之外还会保留**网格**端口:每个 EUP push 行写入 `grid[push][2] = 1` 和 `grid[push][3] = 2`,也就是两个 EUP-prep 微流水线端口(stage A 和 stage B)。相比之下,Viperfish 只通过延迟数组为其 EUP push(延迟 6)定价,不保留网格单元格。PF EUP 单元也是半速率:`PufferfishTarget::VectorEupReservationCycles` `@0x1d494cc0` 返回 2(VF 返回 1)。
| 代 | EUP push `Instruction` | Push 延迟 | Pop `Instruction` | Pop 延迟 | Push 网格占用 | `VectorEupReservationCycles` |
|---|---|---:|---|---:|---|---:|
| PF | 0x67..0x6c | 7 | 0x76 | 1 | r2:1, r3:2 | 2(半速率) |
| VF | 0xcc..0xd2 | 6 | 0x168 | 1 | (无) | 1 |
> **注意 —** 在 PF 上,push→pop 依赖边权重是 `GetLatency(push) = 7`,并且**原样**返回;它*不会*按 `VectorEupReservationCycles` 缩放。保留(PF = 2,即半速率 EUP 发射速率)是约束 push→push 间距的正交轴,不是 push→pop 窗口;两者组合方式是取 `max`,绝不是相乘(见 [EUP Latency Overview](eup-latency-overview.md) 和 [EUP Per-Gen Integers](eup-per-gen-integers.md))。在 VF 上,该边为 6 且保留为 1。把 7 周期延迟乘以 2 周期保留的重新实现会高估每个 PF transcendental;把 PF 上的 EUP 建模为全速率(保留 1)的重新实现会低估背靠背链。
---
## Pufferfish 的 Xlu 差异
每个后续网格代都会通过 `GetXluPathReservation` 访问器读取一个显式的 Xlu/matrix-result 列(VF res 0x0e,GL res 0x0f,GF res 0x10)。**Pufferfish 不这样做。** 它的 `LatencyTablePufferfish` 构造函数 `@0x1c8a1960` 通过 10 次 `SetXluConflictPenaltyBetween` 调用构建一个 `XluConflictPenaltyTable`(位于表的 `+0x18` 内部对象),并通过 `XposeXLUReservationLatency` `@0x1c8a13e0` 为 conv/transpose Xlu 保留定价:
```c
function XposeXLUReservationLatency(table, mode, earlier_type, later_type, a, b): // @0x1c8a13e0
CHECK(IsTranspose(earlier_type)) // latency_table_pf.cc:62
v = (b - a) + XluConflictPenaltyBetween(&table.penalty, // +0x18 inner table
earlier_type, later_type_lo, later_type_hi)
if v < -5: v = -6
return v + 7XluConflictPenaltyTable 是一个三轴整数表,按 [XluInstrType][lo<6][hi<3] 索引,步幅为 72*type + 12*lo + 4*hi + 8(XluConflictPenaltyBetween @0x1c8a0180);存储值为 penalty + 1。IsTranspose(type) 是 (type - 2) < 3,即 XluInstrType ordinal {2,3,4} 是 transpose ops。PF 构造函数安装的 10 个 penalty pair(例如 (0→2)=56、(2→5)=96、(2→0)=86,每个都为第二个 hi 平面重复)是 transpose-result 生产者与后续 Xlu 消费者之间的冲突周期。
这是逐代结构差异:PF 通过冲突矩阵为 Xlu 定价,VF/GL/GF 通过网格列定价。它也是区分 matmul 占用模型的同一个架构分界:Pufferfish 没有单独的 MxuLatencyTable 保留对象,而是把 MXU 占用折叠进网格(res 9)+ 这个 penalty table。
Opcode 如何到达行:GetPufferfishInstruction
GetPufferfishInstruction @0x1c8a1fe0(匿名命名空间)把 LloValue* 映射到 Performance::Instruction ordinal。它是一个跳转表 @0xb43927c(索引 = LloOpcode − 2,边界 0x1ca),其分支为 mov eax, IMM32,低 16 位是 Instruction ordinal,高 16 位是 variant tag(0 = TensorCore,1 = BarnaCore)。约 195 个已定价 opcode 分支中的大部分解析到 variant-0 ordinal;恰好 13 个分支把 high-16 variant tag 设为 1(BarnaCore,即 opcode 0x1ac-0x1ad、0x1af-0x1b5、0x1b8、0x1c9/0x1ca、0x1cc;例如 kBarnaCoreVectorLoad 0x1c9 返回 0x10084 → ins 0x84,var1)。其余 9 个分支(例如 0x1ae)是 LogMessageFatal “unsupported for Pufferfish” 桩。MXU band 是例外:matmul/matprep 分支读取一个二级 latch-mode WORD 表,把单个 matmul/matprep opcode 扩展到网格定价的约 0x60 个 ordinal band。
| 阶段 | 机制 | 锚点 |
|---|---|---|
| LloValue → opcode | 读取 WORD[value] | 内联 |
opcode → Instruction + variant | jt @0xb43927c,idx = op − 2 | GetPufferfishInstruction @0x1c8a1fe0 |
| MXU opcode → band ordinal | 二级 latch-mode WORD 表(按 GainLatchMode) | 分类器中的 matmul 分支 |
Instruction → 延迟 / 单元格 | latency[instr] / grid[instr][res] | @0x1c8c3860 / @0x1c8c3880 |
注意 — 通过 shared-epilogue jump 到达的 band/result-pop 行(而非直接
mov-IMM 分支)是按占用者 LLO 类功能命名的,不是逐 ordinal 跟随跳转链命名的(这些行的置信度为 MEDIUM)。band 身份(MXU matmul/matprep)、其基础延迟(83/101)和吞吐单元格是逐字节精确的;band 内部逐 ordinal 的(opcode, latch_mode)映射是一种机制,此处不枚举。