EUP 延迟概览
地址适用于
libtpu-0.0.40-cp314wheel 中的 libtpu.so。其他版本会有所不同。
摘要
EUP —— libtpu 的超越函数近似器、VPU 的“XLU” —— 是一条很深的 FIFO 缓冲流水线,push 在一个 bundle 中进入,pop 在若干 bundle 后 drain。因为 TensorCore bundle 就是 issue packet,且没有 runtime hazard interlock,编译器必须精确知道 push 与其可 drain 结果之间相隔多少个 bundle,并且必须把 kVectorEupResultValue pop 放在不早于该距离的位置。这个间隔就是 EUP push→pop data latency,也是 bundle scheduler 提升 push→pop dependency edge 的单一数字。本页是该数字的成本模型框架:它存放在哪里、每代如何读取、如何与正交的 EUP issue reservation 组合,以及重实现者必须复现什么才能正确调度一个超越函数。
延迟模型是 ISA 侧 push/pop 编码的成本侧对偶。EUP / Transcendental Slot 页面负责说明超越函数 如何 编码(Alu3 slot-3 push、function selector、0x14e pop、fused-AndPop-vs-split duality);本页负责说明 pop 何时 可以调度。其机制是 dependency graph 读取的逐代成本表:LatencyTable::LatencyBetween(push, pop) 调用逐代的 LatencyBetweenInternal,后者在 v4+ 代上把 LLO push opcode 分类为逐代 Performance::Instruction ordinal,并从 heap int32 array 中读取 Performance::GetLatency(Instruction) = latencies[Instruction]。旧的 Jellyfish/Dragonfish 路径没有该 array;它会把 edge clamp 到固定的 Performance[+0x30] field。无论哪种方式,返回值都是 push 到其 pop 的最小 bundle gap —— 也就是交错的 VALU correction arithmetic 必须填充的软件流水深度。
重实现者最容易把它和 VectorEupReservationCycles 混淆,后者是 EUP 单元的 issue rate(push 后该单元保持 reserved 的 bundle 数,约束 back-to-back push)。二者从不同位置读取、约束不同间隔,并且 不 相乘:latency 是 push→pop deadline,reservation 是 push→push throughput,scheduler 以 max(latency-deadline, resource-availability) 组合它们。Pufferfish 是唯一二者发散的一代 —— 7-cycle latency 与 half-rate (2-cycle) reservation —— 它正好会击穿朴素的 latency × reservation 模型。逐代 latency integer、correction polynomial、Payne-Hanek reduction 和 lane-width fan-out 各自有独立页面;本页解释它们都接入的 edge。
对于重实现,契约是:
- 双数组模型。 每个 v4+
Performance对象持有一个平坦的latencies[Instruction]heap array(data-latency edge)以及 一个resourceUsage[Instruction][Resource]2D grid(issue reservation)。GetLatency读取前者;reservation 来自后者加VectorEupReservationCyclesaccessor。它们是物理上不同的数组。 - 读取路径。
LatencyBetween→ 逐代LatencyBetweenInternal→Get<Gen>Instruction(push)→GetLatency(Instruction),其中 EUP push 不 在 transpose/MXU floor 集合中,因此原始值不经修改地通过。 - 逐代 latency integer(PF 7,VF 6,GL 13 F32 / 14 BF16,JF/DF 4-clamp)以及跨函数统一、按 datatype 拆分的结构 —— 完整归属 EUP Per-Gen Integers。
- reservation 正交性。
VectorEupReservationCycles(PF 2,VF/GL/JF 1)约束 push→push 间隔,而不是 push→pop 窗口;组合是 max,不是 product。
| Edge dispatcher | LatencyTable::LatencyBetween @0x1c89f820 — vtable +0x18 处的 virtual LatencyBetweenInternal |
| v4+ read path | Get<Gen>Instruction(push) → <Gen>Performance::GetLatency(Instr) = latencies[Instr] |
GetLatency | VF @0x1c8cbc20,PF @0x1c8c3860,GL @0x1c8d36e0 — 相同的 bounds-checked latencies[idx] |
| Push→pop latency | JF/DF 4 (clamp);PF 7;VF 6;GL 13 (F32) / 14 (BF16);pop 以 latency 1 drain |
| JF/DF clamp | LatencyTableJellyfish::LatencyBetweenInternal @0x1c8a0d60 — from∈[0x128,0x13a] ∧ to==0x14e → field +0x1c |
| Issue reservation | VectorEupReservationCycles(Target vtable +0x480):JF/VF/GL = 1,PF = 2(half-rate) |
| 组合 | max(push_bundle + latency, prev_push + reservation) — latency ⊥ reservation,绝不相乘 |
| 软件流水深度 | latency(PF 7),独立于 reservation |
Push→Pop 延迟 Edge
目的
超越函数是两条指令组成的序列:一个 reserve EUP 的 push,以及一个在 N 个 bundle 后取回结果的 pop。scheduler 不能把 pop 放在 push_bundle + N 之前,否则会读取仍在飞行中(尚未 drain)的结果。N 是 push→pop data latency —— EUP pipeline depth —— 也是从 push 到 pop 的 dependency-graph edge weight。中间的 N 个 bundle 是 late decomposer 交错 VALU correction arithmetic(Newton steps、range reconstruction)或无关工作的空间,正如 CPU 后端隐藏长延迟 divide。因为 bundle 是 issue packet,这个 latency 不是硬件会为之 stall 的 runtime hazard;它是成本模型编码的 compile-time scheduling constraint。
两种机制
edge 有两种计算方式,在 Jellyfish/Pufferfish 硅片边界处分开。旧路径 clamp;现代路径读取 逐指令数组。
function LatencyBetweenInternal_jellyfish(from, to): // @0x1c8a0d60 (JF/DF)
edge = 1
// ... RPU / IndexedStore / matres floors elided ...
is_eup_push = (from.opcode - 0x128) <= 0x12 // from ∈ [0x128, 0x13a]
if (is_eup_push && to.opcode == 0x14e) // 0x14e = kVectorEupResultValue (pop)
|| LloOpcodeIsPseudoEupInstruction(from.opcode): // or a fused AndPop op
if edge <= this[+0x1c]: // clamp UP to the EUP-latency field
edge = this[+0x1c] // = Performance[+0x30] = 4
return edge
```text
clamp field `this+0x1c` 由 `LatencyTableJellyfish` ctor 从 `Performance[+0x30] = 4` 拷贝而来 —— flat-POD 代没有逐函数超越函数 latency,只有一个常量。(`Performance[+0x30]` 位于 [Performance Family Overview](performance-overview.md) 的 JF/DF inline-POD model;在 Jellyfish 上它是 `.rodata` block-copied cell 之一,不是 scalar store。)
v4+ 代改为走逐指令 heap array。Pufferfish、Viperfish 和 Ghostlite 的结构一致:
```c
function LatencyBetweenInternal_modern(from, to): // PF @0x1c8a2aa0 / VF @0x1c8a4ac0 / GL @0x1c8b22e0
instr = Get<Gen>Instruction(from) // LLO opcode → per-gen Instruction ordinal
base = <Gen>Performance::GetLatency(instr) // latencies[instr] from the heap array
// a transpose-latch HALVING (base/2) applies ONLY to the matmul-latch region
// (GL/VF opcodes 0x8d..0x96 / 0x73..0x7c) — the EUP push (0x128..0x13a) is excluded
// XLU / MXU max-combines only RAISE base on a conflict — the EUP push has none
return base // EUP push: passes through unmodifiedGetLatency 在每个现代代上都是相同的四指令 lookup —— 先针对 Performance[+0x8] 处的元素数量做 bounds check,然后从 Performance[+0x0] 指针读取 latencies[instr]:
function <Gen>Performance::GetLatency(perf, instr): // VF @0x1c8cbc20, PF @0x1c8c3860, GL @0x1c8d36e0
if perf.latency_count <= instr: // [perf+0x8]
trap() // ud2 / BUG
return perf.latency_ptr[instr] // *(int32*)([perf+0x0] + instr*4)
```text
> **GOTCHA —** EUP push 是携带 latency 的指令;pop 是 1-cycle drain。push→pop dependency edge 以 `LatencyBetweenInternal(first = push, second = pop)` 计算,逐代路径会对 **第一个** 操作数 —— push —— 求 `GetLatency`。因此 edge weight 等于 *push* latency(6/7/13/14)。`0x14e` pop 自身的 `latencies[pop] = 1` 不是 push→pop gap;它是 *drained value* 对下游消费指令携带的 latency。重实现者如果为 push→pop edge 读取 `GetLatency(pop)`,会得到 1,并把 pop 调度到 push 后一个 bundle —— 读取垃圾。
### 逐代 Latency Values
在给定 generation 上,push value 对每个已分类 EUP function 都一致 —— `tanh = pow2 = recip = log2 = rsqrt = sigshft = sinq = cosq = erf` —— 因此 EUP 单元具有按 datatype 划分的一份 transcendental latency,而不是逐函数表。Ghostlite 是唯一将 F32 与 BF16 拆开的 generation(它保留 16-bit BF16 通道,因此 BF16 transcendental 多一个 cycle);Viperfish 和 Pufferfish 只分类 F32 push,并把 BF16 EUP widen 到 F32 push,因此它们只有一个 latency。
| Gen | TpuVersion | EUP push latency | Pop latency | Mechanism | Byte anchor |
|---|---|---|---|---|---|
| Jellyfish | v2 | 4 | (clamp) | `Performance[+0x30]` = 4,clamped in `LatencyBetweenInternal` | clamp field `+0x1c` |
| Dragonfish | v3 | 4 | (clamp) | inherits JF `+0x30` = 4 | (= JF) |
| Pufferfish | v4 | 7 | 1 | `PufferfishPerformance` `latencies[]` | ctor `[ptr+0x19c..0x1b0]=7`,`[+0x1d8]=1` |
| Viperfish | v5p | 6 | 1 | `ViperfishPerformance` `latencies[]` | ctor `[ptr+0x330..0x348]=6`,`[+0x5a0]=1` |
| Ghostlite | v6e | 13 (F32) / 14 (BF16) | 1 | `GhostlitePerformance` `latencies[]` | ctor `[ptr+0x418..0x43c]=0xd`,`[+0x440..0x460]=0xe`,`[+0x710]=1` |
上面的 byte anchor 已逐 cell 对照构造函数确认:Pufferfish 的 `PufferfishPerformance::PufferfishPerformance @0x1c8be080` 存储 `latencies[0x67..0x6c] = 7`(六个 EUP-push ordinal)和 `latencies[0x76] = 1`(pop ordinal);Ghostlite 的 ctor 存储 `latencies[0x106..0x10f] = 13`(十个 F32 push)、`latencies[0x110..0x118] = 14`(九个 BF16 push)、`latencies[0x1c4] = 1`;Viperfish 的 ctor 存储 `latencies[0xcc..0xd2] = 6` 和 `latencies[0x168] = 1`。将 LLO push opcode 转为 array index 的逐代 opcode→Instruction classifier,以及完整九函数 block,归属 [EUP Per-Gen Integers](eup-per-gen-integers.md)。
> **QUIRK —** Pufferfish 的 latency table 是一个 `std::variant<PufferfishPerformance, PufferfishBarnaCorePerformance>`,由 2-arm `__fmatrix` visitor 分派(`LatencyFromInstruction` `@0x21c203d0`);`GetPufferfishInstruction` 返回值的高 16 bits 选择 variant(`shr r14d,0x10` 然后 `call [fmatrix + idx*8]`)。**每个** EUP opcode 都发出 variant 0(TensorCore),因此 EUP edge 是 TensorCore array = 7 —— 不是 BarnaCore variant,后者为旧 embedding engine 持有自己的更低 latency EUP block(= 6,在 ordinal `0x77..0x7c`,已在 `PufferfishBarnaCorePerformance` ctor `@0x1c8c38c0` 中按字节确认)。Pufferfish 同时包含二者,是因为 BarnaCore 只在 Pufferfish 后才退役;TensorCore transcendental 永远不会到达 6-cycle BarnaCore block。
### 函数映射
| Function | Address | Role |
|---|---|---|
| `LatencyTable::LatencyBetween` | `0x1c89f820` | edge dispatcher;调用 virtual `LatencyBetweenInternal`,只对 `0x82`/`0x84` 做 floor |
| `LatencyTableJellyfish::LatencyBetweenInternal` | `0x1c8a0d60` | JF/DF EUP clamp 到 field `+0x1c`(= `Performance[+0x30]` = 4) |
| `LatencyTablePufferfish::LatencyBetweenInternal` | `0x1c8a2aa0` | PF path;`__fmatrix` variant dispatch(EUP = variant 0) |
| `LatencyTableViperfish::LatencyBetweenInternal` | `0x1c8a4ac0` | VF path;`GetViperfishInstruction` → `GetLatency` |
| `LatencyTableGhostlite::LatencyBetweenInternal` | `0x1c8b22e0` | GL path;`GetGhostliteInstruction` → `GetLatency(this+0x1d0)` |
| `<Gen>Performance::GetLatency` | `0x1c8c3860` (PF), `0x1c8cbc20` (VF), `0x1c8d36e0` (GL) | bounds-checked `latencies[Instruction]` |
| `PufferfishPerformance` ctor | `0x1c8be080` | 填充 EUP=7(TensorCore variant 0),pop=1 |
| `ViperfishPerformance` ctor | `0x1c8c4840` | 填充 EUP=6,pop=1 |
| `GhostlitePerformance` ctor | `0x1c8cbc80` | 填充 F32 EUP=13,BF16=14,pop=1 |
---
## Latency vs Reservation:为什么它们不相乘
### 目的
push→pop 对由两个独立的量约束,它们从两个不同数组读取。混淆二者是错误调度超越函数最常见的方式,因此成本模型让它们物理分离:**data latency**(push→pop deadline)位于 `latencies[Instruction]`;**issue reservation**(push→push throughput)来自 `VectorEupReservationCycles` 和逐指令 `resourceUsage` grid。把它们相乘的重实现者会高估 Pufferfish 上每个 EUP-bound schedule。
### Edge 不经修改地通过
`LatencyBetween` 不会按任何 reservation field 缩放 edge。它调用逐代 `LatencyBetweenInternal`,可选地加入 uniform-random jitter(由 `[this+0x10]` 处 flag gate 的 noise model),然后只 special-case **matres/transpose opcode** —— `0x82` (matres) 将低于 3 的 edge clamp 到 2,`0x84` (transpose) 采用 MXU `AutoProto` floor —— 其他情况直接返回 internal value。EUP push 既不是 `0x82` 也不是 `0x84`,因此它的 edge 原样通过:
```c
function LatencyTable::LatencyBetween(this, from, to): // @0x1c89f820
edge = this->LatencyBetweenInternal(from, to) // virtual, vtable +0x18
if noise_model_enabled([this+0x10]):
edge += UniformRandom(0, 101) // optional scheduling jitter
if from.opcode == 0x84 && to.opcode == 0x84: // transpose-only MXU floor
edge = max(edge, MxuProtoFloor(from))
else if from.opcode == 0x82 && (to.opcode - 0x82) <= 2 && edge < 3:
edge = 2 // matres-only floor
return edge // EUP push: returned unchanged
// NO multiply by VectorEupReservationCycles anywhere on this path组合方式
reservation 是 EUP 单元的 issue occupancy —— push 后该单元保持 reserved 的 bundle 数,由 bundle packer 的逐指令 resource model(resourceUsage grid 加 SlotTracker)应用,而不是由 latency edge 应用。两个约束按 max 组合:
| Quantity | Bounds | Source | PF | VF | GL (F32/BF16) | JF |
|---|---|---|---|---|---|---|
| push→pop data latency | 最小 bundle 数:push → its drain | latencies[Get<Gen>Instr(push)] | 7 | 6 | 13 / 14 | 4 (clamp) |
| pop drain latency | drained value 携带的 latency | latencies[pop Instr] | 1 | 1 | 1 | — |
VectorEupReservationCycles | 最小 bundle 数:push → next push | Target accessor (+0x480) | 2 | 1 | 1 | 1 |
bundle(pop) >= bundle(push) + latency // deadline (e.g. push+7 on PF)
bundle(push_i+1) >= bundle(push_i) + VectorEupReservationCycles // throughput (e.g. +2 on PF)
// → effective gap = max(...), not product
```text
> **QUIRK —** Pufferfish 的 half-rate EUP(reservation = 2)**不会** 让 7-cycle push→pop window 翻倍。它降低的是 *issue rate*:每 2 个 bundle 才能有一个新 push 进入 EUP。late decomposer 必须填充的 VALU-correction 软件流水深度是 **latency**(7),独立于 reservation。对于 Pufferfish 上一串 `N` 个独立超越函数,EUP-bound schedule 长度是 `≈ 2·(N−1) + 7` 个 bundle(push 之间的 throughput-bound spacing 加最后一个 7-cycle drain),不是 `7·2`。在 Viperfish 和 Ghostlite 上,reservation = 1,back-to-back push 时两个约束重合,只有 latency tail(6 / 13 / 14)重要。
### `VectorEupReservationCycles` Accessor
`VectorEupReservationCycles` 是 `Target` 上 vtable slot `+0x480` 处的 pure-virtual。四个具体 generation 各返回一个常量 —— 通过直接读取 accessor body 确认:
| Gen | Accessor | Returns |
|---|---|---|
| Jellyfish | `JellyfishTarget::VectorEupReservationCycles` `@0x1d490660` | 1 |
| Pufferfish | `PufferfishTarget::VectorEupReservationCycles` `@0x1d494cc0` | 2 |
| Viperfish | `ViperfishTarget::VectorEupReservationCycles` `@0x1d49b060` | 1 |
| Ghostlite | `GhostliteTarget::VectorEupReservationCycles` `@0x1d497ee0` | 1 |
> **NOTE —** `+0x480` vtable slot 与约 160 个无关 vtable 冲突(LLVM TTI cost functions、proto facades),因此消耗 reservation 的单个 bundle-resource call site 未能隔离到一条指令(HIGH:它是 resource-model 调用方,而不是 latency edge)。正交性本身 —— latency edge 可证明 *没有* 按 reservation 缩放 —— 已从 `LatencyBetween`/`LatencyBetweenInternal` 反汇编按字节确认。逐代 `Performance::Resource` 列中哪一列是 EUP unit,以及其 `resourceUsage[push][EUP]` cell 是否等于 `VectorEupReservationCycles`,仅结构性读取;EUP resource row 未从 ctor 的 template-fill 中隔离出来(LOW),因此 reservation→grid binding 是模型中唯一未验证的链接。
---
## Result FIFO
### 目的
latency edge 约束单个 pop 可以放在哪里;result FIFO 约束同时可以有多少个 pushed-but-not-yet-popped 结果在飞行中。二者共同决定 scheduler 能打开的最大软件流水宽度。
### 模型
EUP result FIFO **不是** 固定的 compile-time depth。hardware-state simulator proto 将其保存为 `repeated EupResultFifoEntry eup_result_fifo_entries` field —— 一个 in-flight pushed results 的 runtime snapshot list,每个 entry 是逐通道 result vector —— 因此 depth 是 schedule 已放置的内容,而不是 libtpu literal。在 schedule time,in-flight push 数量由 latency edge 加 FIFO push/pop ordering(`BaseFifoTracker<LloValue*>::FindBlockingPushesAndPops` `@0x14442f60`,它为 FIFO-ordering edge 调用 `LatencyTable::LatencyBetween`)约束;在 v5+ bundle 层面,则由 `HasEupRestrictions` 强制每个 push 和其 pop 位于不同 bundle。silicon FIFO 的物理 depth 是芯片参数,不能从这个 binary 中恢复。
> **NOTE —** push 和 pop 是 FIFO-ordered:`LloInstructionsPopAndThenPushSameFifo @0x1d4f3c80` 通过 head-check 确认 pop drain 匹配的 push。在 v5+ 代上,`HasEupRestrictions` 为 TRUE(Viperfish,Ghostlite),因此 push 和 pop **不能 co-issue** —— late decomposer 必须将它们放在不同 bundle 中,并用 VALU correction 或无关工作填充二者之间的 latency gap。在 Jellyfish 和 Pufferfish 上 restriction 为 FALSE,因此当 schedule 允许时,匹配的 push+pop 可以重新融合为单个 `AndPop` bundle;见 [EUP / Transcendental Slot](../isa/slot-eup-transcendental.md#fused-vs-split-the-andpop-duality)。
### 函数映射
| Function | Address | Role |
|---|---|---|
| `BaseFifoTracker<LloValue*>::FindBlockingPushesAndPops` | `0x14442f60` | FIFO push/pop ordering edges(调用 `LatencyBetween`) |
| `LloInstructionsPopAndThenPushSameFifo` | `0x1d4f3c80` | FIFO-ordering head-check,将 pop 配对到其 push |
| `EupResultFifoEntry` (proto ctor) | `0x0e7a6cc0` | runtime `repeated`-message FIFO list(不是固定 depth) |
---
## 成本模型如何消费该 Edge
EUP latency 供两个消费者使用。**dependency-graph scheduler** 将 `LatencyBetween(push, pop)` 用作 true-dependency edge weight,把 pop 放在不早于 `push_bundle + latency` 的位置,并为 correction arithmetic 精确打开那么多个 bundle。**bundle-cost transcendental estimate**(高层成本模型归因给 `sin`/`cos`/`tanh`/etc. 的逐 op 成本)按同一个 latency 定尺寸:一代的 transcendental estimate 随其 EUP push latency 缩放,因此成本模型和 scheduler 对超越函数打开的软件流水深度保持一致。重实现者为该 estimate 需要的两个 numeric —— 逐代 latency integer 和 correction-window contents —— 归属 per-gen 和 coefficient 页面。
latency value 是 correction window 要填充的深度,而 correction window 是超越函数路径上每个 polynomial 所在的位置。在带硬件 EUP 的 generation 上,`tanh` raw push 后直接取结果;`recip`/`rsqrt` 可能在 push 周围包一层 Newton refinement;`sin`/`cos` 先运行 Payne-Hanek range reduction 生成可 push 的 argument;在缺少硬件 push 的 generation 或 datatype 上,`exp`/`ln` 会 fallback 到完整 VALU polynomial。它们各自占用 `N`-bundle latency window 的一部分,逐代 latency 就是该窗口拥有的预算。相关 numeric:
- 逐代 push→pop integer、逐 opcode → `Performance::Instruction` classifier,以及完整九函数 block —— [EUP Per-Gen Integers](eup-per-gen-integers.md)。
- 填充 latency window 的 Newton/rational/`*NoEupF32` correction polynomial —— [EUP Correction Coefficients](eup-correction-coeffs.md)。
- 位于 `sin`/`cos` push 之前的 trig argument reduction —— [EUP Payne-Hanek Range Reduction](eup-paynehanek.md)。
- 决定 1:N EUP fan-out 的 packed-sub-lane unpack —— [EUP Lane-Width Unpack](eup-lane-width-unpack.md)。
---
## 相关组件
| Name | Relationship |
|---|---|
| `Performance` family | 持有 `latencies[]`(此 edge)和 `resourceUsage[]` grid(reservation)的逐代对象 |
| `LatencyTable` | 为 push→pop edge 读取 `GetLatency` 的逐代 dispatcher |
| `Target` accessors | `VectorEupReservationCycles`(正交 issue rate),`HasEupRestrictions`(强制 push/pop 分离) |
| Bundle packer / `SlotTracker` | 将 reservation 应用为 EUP-resource occupancy,不同于 latency edge |
## 交叉引用
- [EUP / Transcendental Slot](../isa/slot-eup-transcendental.md) — ISA 侧 push/pop 编码、fused-vs-split duality,以及从编码视角看的 latency table
- [EUP Per-Gen Integers](eup-per-gen-integers.md) — 逐代 push→pop latency integer、opcode→Instruction classifier,以及完整九函数 block
- [EUP Correction Coefficients](eup-correction-coeffs.md) — 填充 latency window 的 Newton/rational/`*NoEupF32` polynomial
- [EUP Payne-Hanek Range Reduction](eup-paynehanek.md) — 位于 `sin`/`cos` push 之前的 1/(2π) trig reduction
- [EUP Lane-Width Unpack](eup-lane-width-unpack.md) — 将 push 数量相乘的 packed-sub-lane 1:N fan-out
- [Performance Family Overview](performance-overview.md) — `Performance` object layout、`GetLatency`/`GetResourceUsage` read paths,以及 JF/DF inline-POD vs heap-grid split
- [VPU Slot](../isa/slot-vpu.md) — EUP push 被限制到的 VALU slot family(slot 3)