Skip to content

EUP 延迟概览

地址适用于 libtpu-0.0.40-cp314 wheel 中的 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 来自后者加 VectorEupReservationCycles accessor。它们是物理上不同的数组。
  • 读取路径。 LatencyBetween → 逐代 LatencyBetweenInternalGet<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 dispatcherLatencyTable::LatencyBetween @0x1c89f820 — vtable +0x18 处的 virtual LatencyBetweenInternal
v4+ read pathGet<Gen>Instruction(push)<Gen>Performance::GetLatency(Instr) = latencies[Instr]
GetLatencyVF @0x1c8cbc20,PF @0x1c8c3860,GL @0x1c8d36e0 — 相同的 bounds-checked latencies[idx]
Push→pop latencyJF/DF 4 (clamp);PF 7;VF 6;GL 13 (F32) / 14 (BF16);pop 以 latency 1 drain
JF/DF clampLatencyTableJellyfish::LatencyBetweenInternal @0x1c8a0d60from∈[0x128,0x13a] ∧ to==0x14e → field +0x1c
Issue reservationVectorEupReservationCycles(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)的结果。Npush→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;现代路径读取 逐指令数组

c
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 unmodified

GetLatency 在每个现代代上都是相同的四指令 lookup —— 先针对 Performance[+0x8] 处的元素数量做 bounds check,然后从 Performance[+0x0] 指针读取 latencies[instr]

c
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 组合:

QuantityBoundsSourcePFVFGL (F32/BF16)JF
push→pop data latency最小 bundle 数:push → its drainlatencies[Get<Gen>Instr(push)]7613 / 144 (clamp)
pop drain latencydrained value 携带的 latencylatencies[pop Instr]111
VectorEupReservationCycles最小 bundle 数:push → next pushTarget accessor (+0x480)2111
text
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)