Skip to content

EUP 通道宽度与解包数量

本页中的所有地址均适用于 libtpu-0.0.40-cp314 wheel 中的 libtpu.so(build-id 89edbbe81c5b328a958fe628a9f2207d,781,691,048 字节,未 strip)。.text.rodata 的 VMA 等于其文件偏移;.data.rel.ro 的 VMA 减去 0x200000 等于其文件偏移。其他 libtpu 构建会有所不同。

摘要

SparseCore 上的超越函数不是一份成本,而是同一份成本的 N 份拷贝,其中 N解包扇出:一个打包操作数向量在每个片段能被推入 Extended Unary Pipeline (EUP) 之前,会拆成多少个通道宽度的片段。本页是 EUP 数据路径的成本模型部分。EUP / Transcendental Slot 页面记录一次 push 如何 编码;本页记录给定操作数需要 多少次 push,以及 push→pop 延迟 如何 通过软件流水拆分隐藏,而不是内联计费。这两个量都在 bundle scheduler 运行之前,由两个 MLIR 层面的机制决定,重实现者的成本模型必须复现它们。

第一个机制是 通道宽度 / 解包数量模型。SparseCore lowering 会为每个逐元素浮点 op 注册两个竞争 pattern:一个 1:1 的 UnaryFloatVector pattern,发出单个融合 EUP macro;以及一个 1:N 的 AluEp pattern,将打包操作数解包成通道宽度子元素,对每个片段运行一个 compute op,然后重新打包。IsDynamicallyLegal (@0x135ddd20) 决定哪一个触发;当它触发 1:N 路径时,UnpackOperand<UnpackFOp> (@0x1360fac0) 递归地将打包元素二分到通道子元素宽度,并返回一个 std::deque<Value>,其 长度就是 N。通道宽度本身是一个单一的逐代 boolean:SupportsBf16AluInstructions(Target vtable +0x780)—— FALSE 表示 32-bit (F32) 通道,TRUE 表示原生 16-bit (BF16) 通道。成本后果很具体,也有些反直觉:拥有原生 BF16 ALU 的一代(Ghostlite)处理打包 bf16 超越函数时,push 数量是没有它的一代(Viperfish)的一半,因为它不必解包到 F32。解包数量就是将每片段超越函数成本计费 N 次的乘数。

第二个机制是 bare-push / tpu_eup_pop 软件流水拆分。MLIR 将超越函数发出为一个 融合kVector{fn}{F32,Bf16}AndPop 伪 op(opcode 0x13b..0x14d)。在 v5+ 代上,EUP push 和它的 pop 不能共同发射(HasEupRestrictions 在 Viperfish 和 Ghostlite 上为 TRUE),因此 LloLateDecomposer 会把融合 op 重写成一个 裸硬件 pushCreateVectorEup0x128..0x13a)和一个位于单独 bundle 中的 延迟 popCreateVectorEupResult,硬编码 0x14e kVectorEupResultValue)。对成本模型的含义是,超越函数的延迟 不是 单个 bundle 的 stall,而是一个 push→pop 窗口,调度器用无关 VALU 工作填充它;EUP 吞吐由正交的发射保留速率(VectorEupReservationCycles)控制,而不是由延迟控制。把延迟乘以 reservation,或把 push 和 pop 作为一个不可分割 op 计费的成本模型,都会得到错误调度。

对于重实现,成本模型的契约是:

  • 通道子元素宽度。 SupportsBf16AluInstructions(vtable +0x780)作为 16-vs-32-bit 选择器、它的逐代取值,以及允许调用方强制非默认宽度的 optional<int> override。
  • 1:1-vs-1:N 决策。 四分支 IsDynamicallyLegal 真值表,它选择 EUP-macro 路径(成本 1)或 AluEp 解包路径(成本 N)。
  • 解包数量 N UnpackOperand 的递归二分 —— 每步片段数 result_bw / sub_bw、在通道宽度处退出循环,以及向量形状如何折入其中。
  • bare-push / deferred-pop 拆分。 V*Decomposed builder 发出裸 push + 裸 pop,且 没有 内联修正;该拆分由 HasEupRestrictions 强制;延迟隐藏工作是调度器交错的无关 VALU 指令,而不是 EUP op 的一部分。
  • 延迟 / reservation 正交性。 VectorEupReservationCycles(push→push 间隔)是与 push→pop 延迟分离的量,后者是 correction window 要填充的窗口。
通道宽度选择器Target::SupportsBf16AluInstructions(vtable +0x780);VF 0x1d49c0e0 → FALSE (32-bit),GL 0x1d498ce0 → TRUE (16-bit)
1:1-vs-1:N 门控IsDynamicallyLegal (@0x135ddd20) — LEGAL ⇒ 1:1 macro(成本 1),ILLEGAL ⇒ AluEp 解包(成本 N
解包数量引擎UnpackOperand<UnpackFOp> (@0x1360fac0) — 递归二分;deque 长度 = N
子元素类型GetUnpackResultElementType (@0x1360ff20) — 16→BF16,32→F32,sub-8-bit → int(2·src_bw)
拆分触发器HasEupRestrictions — VF 0x1c458620,GL 0x1c458d80 = TRUE;JF 0x1c457b80,PF 0x1c4580c0 = FALSE
DecomposerLloLateDecomposer (@0x1269cb20) → DecomposeEupInstruction (@0x126a0340) → 跨 19 个 case 的 10 个 V*Decomposed builder
裸 push / 延迟 popCreateVectorEup (@0x1d4d78a0, 0x128..0x13a) + CreateVectorEupResult (@0x1d4d9820, hardcoded 0x14e)
发射 reservationVectorEupReservationCycles(vtable +0x480):JF/VF/GL = 1,PF = 2(半速率 EUP)

通道子元素宽度

目的

通道宽度是解包模型二分下降的下限:比通道更窄的片段不会继续暂存;更宽的片段必须拆分。它是将抽象的“解包一个打包操作数”转化为具体数量的单一逐代输入。重实现者的成本模型每个 target 读取一次,并在所有计算 AluEp 扇出的地方使用它。

算法

宽度来自一次虚调用。Target::SupportsBf16AluInstructions 是 vtable slot +0x780(十进制 1920,每个消费者中出现的 immediate)。两个 v5+ override 的反编译很简单且结论明确:

c
function ViperfishTarget::SupportsBf16AluInstructions():    // 0x1d49c0e0
    return 0                                                // FALSE → 32-bit (F32) lane

function GhostliteTarget::SupportsBf16AluInstructions():    // 0x1d498ce0
    return 1                                                // TRUE → 16-bit (BF16) lane
```text

基础 `Target::SupportsBf16AluInstructions` (`@0x1d61f580`) 是一个 `LogFatal` 纯虚 sentinel —— 每个具体代 **必须** override,因此重实现者不能让它保持未设置。这个 boolean 在两个解包消费者中通过相同算术转换为宽度:`width = 16 * (supports ^ 1) + 16`,即 TRUE16FALSE32

```c
// from GetUnpackResultElementType @0x1360ff20 and UnpackOperand @0x1360fac0:
lane_bw = 16 * (target.SupportsBf16AluInstructions() ^ 1) + 16   // *0x780 → {16, 32}
if (override & 0x100000000)   // optional<int> engaged-bit
    lane_bw = override        // caller-forced width (f8 / sub-byte staging)

NOTE — lowering 传入的 Target const& 是基础 xla::jellyfish::Target(逐代的 ViperfishTarget / GhostliteTarget),不是 派生的 …SparseCoreTarget。gen-target vtable 上的 +0x780 slot 是 SupportsBf16AluInstructions;在 SparseCoreTarget vtable 上,同一偏移是另一个方法(NumVsSlots),属于不同的类布局,这里不会查询。重实现者如果在错误对象上分派 +0x780,会读到错误字段。

函数映射

FunctionAddressRole
ViperfishTarget::SupportsBf16AluInstructions0x1d49c0e0返回 FALSE → 32-bit (F32) 通道
GhostliteTarget::SupportsBf16AluInstructions0x1d498ce0返回 TRUE → 16-bit (BF16) 通道
Target::SupportsBf16AluInstructions (base)0x1d61f580LogFatal pure-virtual;必须被 override

注意事项

optional<int> override(bt $0x20 / cmovb engaged-bit 测试,在 UnpackOperandGetUnpackResultElementType 中均已按字节确认)允许调用方为 f8 / sub-byte staging 强制非默认通道宽度,而不是使用从 SupportsBf16AluInstructions 推导出的 16/32。默认路径(无 override)已按字节确认;哪些 AluEp 调用方传入非默认 override 未枚举,因此 override 驱动的扇出变体是 LOW 置信度。完整的、与这些宽度交互的 VpackFormat 模型见 Pack/Unpack Precision


1:1-vs-1:N 决策

目的

在任何解包数量存在之前,lowering 会先决定是否要解包。一个超越函数源 op(例如 sparse_core::RsqrtOp)同时注册了 1:1 的 UnaryFloatVector pattern(一个融合 EUP macro,成本 1)和 1:N 的 AluEp pattern(解包 → N 个 compute ops → 重新打包,成本 N)。IsDynamicallyLegal 是逐 op 的动态合法性谓词,用来仲裁:如果 op 是 LEGAL,它保持 macro(1:1);如果是 ILLEGAL,转换会通过 AluEp 解包(1:N)运行。成本模型必须精确复现这个谓词,因为它决定一个超越函数计费一次还是计费 N 次。

算法

IsDynamicallyLegal (@0x135ddd20) 是一个四分支真值表,反编译中已按字节确认:

c
function IsDynamicallyLegal(op, target, operand_idx):   // 0x135ddd20
    forced = ForceBF16ALUOperationsToUnpack(op, ty)     // @0x135dd6e0 — force-UNPACK (1:N) flag
    if ty.typeID != mlir::VectorType::id:  return LEGAL  // scalar operand → 1:1
    if !IsPackedVectorType(ty, op):        return LEGAL  // vector but not packed → 1:1
    fmt = GetVpackFormat(ty)                             // @0x13dad800: 0=no-pack,1=bf16,0xb=f16,0x7=sub-byte
    if fmt == 0 || forced:   return (fmt == 0)           // not packable → 1:1; forced+packable → !fmt = ILLEGAL (1:N)
    if !target.SupportsBf16AluInstructions():            // *0x780 FALSE (VF) → 1:N
        return ILLEGAL
    return (ty.element.bitwidth == 16) ? LEGAL : ILLEGAL // native bf16-16 → 1:1, else (f8/s8) → 1:N
```text

对成本重要的四个分支如下:

| operand type | `IsPackedVectorType` | `GetVpackFormat` | `SupportsBf16Alu` | elem bw | result | EUP cost |
|---|---|---|---|---|---|---|
| 不是 vector / 未打包 |/ false |||| LEGAL | 1 (macro) |
| 打包,`fmt == 0` | true | 0 (no-pack) ||| LEGAL | 1 (macro) |
| 打包,`fmt != 0` | true | != 0 | FALSE (VF) | any | ILLEGAL | `N` (unpack) |
| 打包,`fmt != 0` | true | != 0 | TRUE (GL) | 16 (bf16) | LEGAL | 1 (macro) |
| 打包,`fmt != 0` | true | != 0 | TRUE (GL) | != 16 (f8/s8) | ILLEGAL | `N` (unpack) |

> **QUIRK —** 最便宜路径是 *最新* 代在其 *原生* datatype 上运行。打包 `bf16` op 只有在该代拥有 BF16 ALU **** 元素正好是 16-bit 时才是 LEGAL(成本 1)。Viperfish 缺少 BF16 ALU,会把同一个 op 标记为 ILLEGAL,并支付 `N` 路解包到 F32 的成本。假设“打包总是解包”的重实现者会把 Ghostlite 高估 `N` 倍;假设“超越函数总是 1:1”的重实现者会把 Viperfish 低估同样倍数。
>
> **GOTCHA —** `ForceBF16ALUOperationsToUnpack` (`@0x135dd6e0`) 强制走 *ILLEGAL / 1:N 解包* 路径,而不是 1:1 macro —— 名称是字面意思。当 op 带有 `sc.emit_vectorized_alu_operation_in_f32_precision` BoolAttr **true** 且其元素 `isBF16` 时,该函数返回 1,反编译中的 `(fmt == 0) | forced` 短路(`if ((VpackFormat == 0) | v4) return !VpackFormat`)返回 `!fmt`。对打包 bf16 op,`fmt == 1`,所以 `!fmt == 0 == ILLEGAL` —— op 被强制解包到 F32 并以 F32 精度运行 vectorized ALU。因此该 flag 只会影响本来会将 bf16 op 标记为 LEGAL 的一代(Ghostlite,原生 BF16 ALU):它把该路径 override 成 1:N F32 解包。

### 函数映射

| Function | Address | Role |
|---|---|---|
| `IsDynamicallyLegal` | `0x135ddd20` | 1:1-vs-1:N 选择器(4 分支真值表) |
| `ForceBF16ALUOperationsToUnpack` | `0x135dd6e0` | force-1:N flag;在 `f32-precision` attr 上驱动打包 bf16 op 变为 ILLEGAL(解包到 F32) |
| `IsPackedVectorType` | `0x13611720` | 子元素打包 vector 测试 |
| `GetVpackFormat` | `0x13dad800` | pack-format enum (0=none,1=bf16,0xb=f16,0x7=sub-byte) |

### 注意事项

两个 pattern family 会为 *同一个* 源 op 注册(`sc::RsqrtOp` 在 `0x1357e540` 有 1:1 `UnaryFloatVector`,在 `0x135e1c80` 有 1:N `AluEp`);`IsDynamicallyLegal` 是决定哪一个适用的动态合法性谓词,conversion framework 会通过 AluEp pattern 重写 ILLEGAL op。两个共同注册 pattern 之间的 `PatternBenefit` 顺序是结构性读取而非按字节解码,因此“动态合法性是 *唯一* 仲裁者(没有 benefit tie)”这一说法是 **HIGH**,不是 CERTAIN。

---

## 解包数量 `N`

### 目的

这是成本乘数。当 `IsDynamicallyLegal` 将 op 标记为 ILLEGAL 时,AluEp body 调用 `UnpackOperand<UnpackFOp>` 将打包操作数拆成通道宽度片段,精确地为每个片段发出一个 compute op,然后重新打包。片段数量 —— 返回的 `std::deque<Value>` 长度 —— 就是 `N`,AluEp body 的逐片段 `ComputeOp::create` 循环运行 `N` 次。重实现者的逐元素 / 超越函数成本是 `N × (per-piece op cost)`。

### 算法

`UnpackOperand<UnpackFOp>` (`@0x1360fac0`) 用宽操作数初始化一个 deque,然后重复二分,直到每个片段适合通道宽度:

```c
function UnpackOperand(loc, packBits, operand, vecTy, builder, target, override):  // 0x1360fac0
    lane_bw = 16 * (target.SupportsBf16AluInstructions() ^ 1) + 16   // *0x780 → {16, 32}
    if (override & 0x100000000):  lane_bw = override                 // engaged optional<int>
    elem = vecTy.getElementType()
    deque = [operand]                                                // seed with the wide value
    do:                                                              // outer loop @0x1360fb70
        sub_ty = GetUnpackResultElementType(elem, builder, target, override)  // @0x1360ff20
        sub_vec = GetVectorType(sub_ty, target, ...)                 // CHECK: status OK
        count = bitwidth(elem) / bitwidth(sub_ty)                    // pieces this step (idiv)
        tuple = UnpackFOp::create(...)                               // one unpack → a tuple
        for i in 0 .. count-1:                                       // inner loop @0x1360fb80
            deque.push( ExtractTupleElementOp::create(tuple, i) )    // one piece per tuple slot
        elem = sub_ty                                                // narrow for next iteration
    while (lane_bw > bitwidth(elem))                                 // exit when piece fits lane
    return deque                                                     // length == N

每步子元素类型由 GetUnpackResultElementType (@0x1360ff20) 给出:

c
function GetUnpackResultElementType(elem, builder, target, override):  // 0x1360ff20
    lane_bw = 16 * (target.SupportsBf16AluInstructions() ^ 1) + 16      // *0x780
    if (override & 0x100000000):  lane_bw = override
    src_bw = elem.getIntOrFloatBitWidth()
    out_bw = (src_bw >= 8) ? lane_bw : 2 * src_bw      // ≥8-bit jumps to lane; sub-8-bit doubles
    if elem is a FloatType:
        if out_bw == 16:  return builder.getBF16Type()
        if out_bw == 32:  return builder.getF32Type()
    return builder.getIntegerType(out_bw)
```text

因此,一个 `bf16` (16-bit) 元素在 32-bit 通道上会一步解包(`count = 32/16 = 2` 个片段,然后 `lane_bw == elem_bw` 退出);sub-8-bit 元素每步将宽度翻倍(`out_bw = 2·src_bw`),直到达到通道宽度,每步将片段数乘以 2。总 `N` 是所有二分步骤中 `result_bw / sub_bw` 的乘积,再折入向量形状的通道 tiling。

> **NOTE —** 每步片段数 `result_bw / sub_bw` 是字节精确的(`0x1360fbfc` 处的 `idiv`)。多元素向量的 ** `N` 会折入 `getShape` 乘积除以通道 tiling 容量(与逐片段成本使用的 `LaneCount = 128` sub-lane geometry 相同);该形状折入因子是结构性应用而非重新 dump —— 因此每步数量是 CERTAIN,而给定 tensor shape 的绝对 `N` 是从通道 tiling geometry **INFERRED** 出来的。

### AluEp Body 计费 `N` 个 Compute Ops

代表性 AluEp body,`AluEp<math::ExpOp>` (`@0x135df200`),让成本变得具体:

```text
AluEp<math::ExpOp>::matchAndRewrite:                 // 0x135df200
  if !target.SupportsSparseCore():  bail              // *0x260 — ENTRY GUARD, not a width input
  if IsDynamicallyLegal(op, target):
        ExpOp::create(...)                            // 1:1 — a single fresh op (cost 1)
        replaceOp; return
  deque = UnpackOperand<UnpackFOp>(...)               // N narrow pieces  @0x135df55b
  for piece in deque:                                 // walk the deque   @0x135df8f0
        results.push( ExpOp::create(piece) )          // ONE compute op per piece (N total)
  PackResults<PackFOp>(results)                       // repack the N results @0x135df7ba
  replaceOp

compute-op 数量等于 deque 长度 N。对超越函数来说,这是 N 个独立的 EUP push/pop 对(每个片段都有自己的 push);对普通 arithmetic op 来说,这是 N 个 VALU ops。这就是逐片段超越函数 / 逐元素成本要计入的 N

函数映射

FunctionAddressRole
UnpackOperand<UnpackFOp>0x1360fac0递归二分;返回 N-deque
GetUnpackResultElementType0x1360ff20按通道宽度给出下一层更窄子元素类型
AluEp<math::ExpOp> (representative)0x135df200一次 unpack 和一次 pack 之间有 NComputeOp::create
PackResults<PackFOp>0x13610940N 个结果重新组装为宽输出
Target::SupportsSparseCore0x1d48fd40AluEp entry guard(vtable +0x260),不是 宽度输入

注意事项

SupportsSparseCore(vtable +0x260topology[+0x3b8][+0x98] > 0)是 AluEp matchAndRewrite entry guard —— 如果 target 没有 SparseCore,lowering 会 bail —— 因为它和 +0x780 宽度选择器 gate 同一段 lowering,所以很容易混淆。它 不是 解包数量的输入;成本模型只能从 +0x780 读取宽度。解包 helper 有 float / signed-int / unsigned-int 版本(UnpackOperand<UnpackFOp/SIOp/UIOp>);这里只有 UnpackFOp(float)路径被解码,int 路径基于结构对称性假设遵循相同二分。


Bare-Push / Deferred-Pop 拆分

目的

解包数量确定一个超越函数需要 多少次 EUP push;拆分确定 每次 push 的延迟如何支付。MLIR 将超越函数发出为单个融合 kVector{fn}{F32,Bf16}AndPop 伪 op。在 v5+ 代上,push 和 pop 不能位于同一个 bundle,因此 late decomposer 会把融合 op 拆成裸硬件 push 和放在一个或多个 bundle 之后的延迟 pop。成本模型的后果是:超越函数的延迟是一个调度器用无关工作填充的 窗口,不是 stall;EUP 吞吐是正交的 push→push reservation,而不是延迟。这是长延迟 divide 的经典软件流水形式;由于 TPU bundle 是 issue packet 且没有 runtime hazard interlock,它显式暴露在指令流中。

算法

融合伪 op 占据 opcode 0x13b..0x14d,并由 LloOpcodeIsPseudoEupInstruction (@0x1d60c880) 识别:

c
function LloOpcodeIsPseudoEupInstruction(op):        // 0x1d60c880
    return (op - 0x13b) < 0x13            // op in [0x13b, 0x14d] (19 AndPop ops)
        && (0x7fdff >> (op - 0x13b)) & 1  // bitmask clears bit 9 → excludes 0x144 PushErfAndPop
```text

`0x7fdff` mask(二进制 `0111 1111 1101 1111 1111`,bit 9 清零)正好排除 19 个中的一个 —— `0x144 kVectorPushErfAndPop` —— 剩余 18 个融合 op 被视为 pseudo-EUP。`LloLateDecomposer` (`@0x1269cb20`) 遍历这些 op 并调用 `DecomposeEupInstruction` (`@0x126a0340`);后者是一个基于 pseudo-opcode 的 `switch`(19 个 case,`0x13b`..`0x14d`),分派到十个 `V*Decomposed` builder 之一 —— `Vpow2`、`Vrecp`、`Vlog2`、`Vrsqrt`、`Vsigshft`、`Vsinq`、`Vcosq`、`Vtanh`、`VpushErf`、`Verf` —— 每个都由 opcode 选择,并接收 `PrimitiveType` `11`(BF16-source F32 形式)或 `16`(BF16 形式)。每个 builder 都是 **裸 push + 裸 pop**,从 `PrimitiveType` 参数选择 F32 或 BF16 push opcode:

```c
function VtanhDecomposed(builder, prim_type, value):  // 0x1d555040 (23 lines, byte-exact)
    push_opcode = (prim_type == 0xb) ? 0x128 : 0x132  // 0xb = BF16 → F32 push 0x128; else BF16 push 0x132
    push = CreateVectorEup(push_opcode, value, region)   // @0x1d4d78a0 — bare push, 1 operand
    AppendInstruction(region, push)
    pop  = CreateVectorEupResult(push, region)           // @0x1d4d9820 — deferred pop, hardcodes 0x14e
    AppendInstruction(region, pop)
    // NO inline correction polynomial, NO refinement, NO second push/pop pair

VrsqrtDecomposed (@0x1d557b60) 的结构按字节相同(push_opcode = (prim_type == 0xb) ? 0x12c : 0x136),也是 23 行,也是裸 push + 裸 pop。CreateVectorEup 断言 push opcode 是 vector-EUP opcode((opcode - 0x128) < 0x13,即包含 0x128..0x13aLloOpcodeIsVectorEup 检查),并断言操作数 ProducesVreg(其 opcode_produced_register_type[] 项是 EUP class 4);CreateVectorEupResult 断言同一个 push 范围并发出 New(0x14e, {push}, 1) —— pop 不携带函数或宽度,只携带 push handle。

GOTCHA — V*Decomposed builder(0x1d5550400x1d557b60)只有裸 push + 裸 pop —— 每个 23 行,没有 CreateVectorBinop,没有 VfastTwoSum,也没有第二个 push/pop 对。因此 push→pop 窗口中的延迟隐藏工作是 调度器交错的无关 VALU 指令,不是 decomposer 发出的 correction arithmetic。超越函数自身的修正数学(如果存在)位于 *NoEupF32 软件 fallback 和共享 Newton/rational helper 中 —— 见 EUP Correction CoefficientsVfastTwoSum helper (@0x1d5550a0) 是一个独立的 Dekker two-sum,只是在 .text 中物理上邻近 VtanhDecomposed;它不是该拆分的一部分。

该拆分在 v5+ 上是 强制 的,因为 HasEupRestrictions 在 Viperfish (@0x1c458620) 和 Ghostlite (@0x1c458d80) 上为 TRUE:push 和 pop 不能 co-issue,因此 decomposer 必须把它们放在不同 bundle 中。在 Jellyfish (@0x1c457b80) 和 Pufferfish (@0x1c4580c0) 上 restriction 为 FALSE,因此融合 AndPop 形式可以保留 —— 反向的 Simplify{Tanh,Reciprocal,Sinq,Cosq}AndPop simplifier(0x1d593c60..)会在 schedule 允许共址时重新融合匹配的 push+pop。

LLO Opcode Bands(成本相关)

BandRangeCost role
裸 push0x128..0x13a一个 EUP push(N 路扇出的单位);CreateVectorEup 发出一个
融合 AndPop0x13b..0x14dMLIR 发出;late decomposer 在 v5+ 上拆分它(0x144 PushErfAndPop 从 pseudo-EUP 中排除)
延迟 pop0x14ekVectorEupResultValue;在一个或多个 bundle 后 drain push(CreateVectorEupResult 硬编码它)

函数映射

FunctionAddressRole
LloLateDecomposer0x1269cb20将融合 AndPop 拆成裸 push + 延迟 pop
DecomposeEupInstruction0x126a0340switch 分派(19 个 case)到 10 个 V*Decomposed builder
LloOpcodeIsPseudoEupInstruction0x1d60c880分类融合 AndPop(范围 [0x13b,0x14d],mask 0x7fdff
VtanhDecomposed / VrsqrtDecomposed0x1d555040 / 0x1d557b60裸 push + 裸 pop builder(各 23 行)
CreateVectorEup0x1d4d78a0裸 push(0x128..0x13a,1 个操作数,断言 ProducesVreg
CreateVectorEupResult0x1d4d9820延迟 pop,硬编码 0x14e,断言 push ∈ 0x128..0x13a
HasEupRestrictions (VF/GL/JF/PF)0x1c458620/0x1c458d80/0x1c457b80/0x1c4580c0v5+ separate-bundle 约束(TRUE/TRUE/FALSE/FALSE)
DecomposeEupOperationsForBarnacore0x1269c5c0BarnaCore EUP 拆分(独立 result-drain 路径)

注意事项

DecomposeEupOperationsForBarnacore (@0x1269c5c0) 也调用 DecomposeEupInstruction,但通过 BarnaCore EupResultRead address-handler drain EUP 结果,而不是 TensorCore VectorResult pop —— 其 push→pop 距离是否不同未拆分确认(HIGH)。pseudo-opcode 0x144 kVectorPushErfAndPop0x7fdff mask 从 pseudo-EUP 集合中排除,不会由 late decomposer 拆分;它的处理未追踪,超出这里的成本模型范围。


延迟与 Reservation 正交性

目的

成本模型用两个独立的量约束一个 EUP push,它们从两个不同机制读取;重实现者如果混淆它们,会得到错误调度。push→pop 延迟 是延迟 pop 位于其后的窗口深度 —— 调度器为隐藏它而交错的工作。发射 reservation 是 EUP 单元在 push 后保持 busy 的 bundle 数,约束 back-to-back push。它们按 max 组合,而不是相乘。

算法

VectorEupReservationCycles(Target vtable +0x480)是 issue-occupancy —— 从一个 push 到下一个 push 的最小 bundle 数:

c
function JellyfishTarget::VectorEupReservationCycles():  return 1   // 0x1d490660
function PufferfishTarget::VectorEupReservationCycles():  return 2  // 0x1d494cc0 — half-rate EUP
function ViperfishTarget::VectorEupReservationCycles():  return 1   // 0x1d49b060
function GhostliteTarget::VectorEupReservationCycles():  return 1   // 0x1d497ee0
```text

这个 reservation 由逐指令 resource model(`GetResourceUsage` matrix + slot tracker)应用,**不是** 由 push→pop latency edge 应用。latency edge —— 从 push 到其 drain 的最小 bundle 数 —— 是单独的 dependency-graph 权重;二者正交。

| quantity | meaning | source | JF | PF | VF | GL |
|---|---|---|---|---|---|---|
| push→pop latency | 最小 bundle 数:push → drain(correction window) | dependency-graph edge weight | (clamp) | (heap array) | (heap array) | (heap array) |
| `VectorEupReservationCycles` | 最小 bundle 数:push → next push | Target accessor (`+0x480`) | 1 | 2 | 1 | 1 |

> **GOTCHA —** Pufferfish 的 reservation 为 2(半速率 EUP),并 **不会** 让 push→pop 延迟翻倍。延迟是窗口深度;reservation 是发射速率。对 Pufferfish 上一串 `M` 个独立超越函数,EUP-bound schedule 长度大致是 `2·(M−1) + latency` 个 bundle —— reservation 将连续 push 间隔为两个 bundle,最终 pop 在最后一次 push 后 `latency` 个 bundle drain。把二者相乘(`latency × reservation`)的成本模型会严重高估该链。

绝对逐代 push→pop 延迟整数记录在 [EUP Latency Overview](eup-latency-overview.md) 和 [EUP / Transcendental Slot](../isa/slot-eup-transcendental.md) 页面(PF 7,VF 6,GL 13/14,JF clamp 4);它们从逐指令 heap array 读取,不来自单个 immediate。本页确定的是组合 *形状* —— `max(latency-deadline, reservation-availability)` —— 以及解包数量 `N` 会乘以 push/pop 对的 *数量*,但不乘以每对的延迟。

### 注意事项

最小 push→pop bundle gap(latency-edge weight)是结构性确认的 —— decomposer 构建 region,scheduler 将 pop 放在 deferred work 之后 —— 但逐代 latency integer 不是拆分路径中的单个 `mov` immediate;它来自 EUP result-FIFO depth 加 dependency-graph latency model。因此每代 numeric latency 在 latency-overview / slot 页面中从 `<Gen>Performance` array 记录,并在这里引用,而不是重新推导。延迟和 reservation 的正交性,以及 `N` 倍 push-count 乘数,是 CERTAIN;绝对 push→pop 距离是 **HIGH**

---

## 示例 — 打包 `bf16` rsqrt,Viperfish 对比 Ghostlite

一个 `vector<…×bf16>`(16-bit 打包 sub-lane)上的 `sc.rsqrt`。两代都同时注册 1:1 `UnaryFloatVector` 和 1:N `AluEp` pattern;`IsDynamicallyLegal` 作选择,解包数量随之确定:

```text
VIPERFISH (SupportsBf16AluInstructions = FALSE32-bit lane):
  IsDynamicallyLegal: fmt(bf16)=1 (!=0), SupportsBf16Alu=FALSE → ILLEGAL → AluEp 1:N fires.
  UnpackOperand: lane_bw = 32 > 16 (bf16) → one halving step:
      count = bitwidth(result=32) / bitwidth(sub=16) = 2 pieces; loop exits (32 == 32).
  ⇒ N ≈ 2 × (shape / lane-tiling). Each F32 piece → its own bare 0x12c rsqrt push + 0x14e pop.
  COST: N × (one EUP push/pop). The push count is DOUBLED by the unpack to F32.

GHOSTLITE (SupportsBf16AluInstructions = TRUE16-bit lane, elem bw == 16):
  IsDynamicallyLegal: fmt!=0, SupportsBf16Alu=TRUE, elem==16 → LEGAL → NO unpack, 1:1 macro fires.
  tpu_rsqrt_macro → fused 0x149 kVectorRsqrtBf16AndPop; LloLateDecomposer (HasEupRestrictions=TRUE)
  splits it into a bare 0x136 BF16 rsqrt push + 0x14e pop. ONE push/pop; the BF16 ALU runs the
  per-lane op natively.
  COST: 1 × (one EUP push/pop). No unpack.

⇒ The newer generation (GL) processes the packed bf16 rsqrt at HALF the EUP push count of VF,
  because the native BF16 lane lets it skip the unpack to F32 entirely. The unpack count N is the
  cost-model multiplier; SupportsBf16AluInstructions is the single boolean that sets it.

交叉引用

  • EUP / Transcendental Slot — push/pop 编码(VALU slot 3、5-bit function selector、0x14e pop);本页是 成本(解包数量 + 拆分)部分
  • EUP Latency Overview — 本页视为与 reservation 正交的逐代 push→pop 延迟整数
  • EUP Correction Coefficients — 超越函数 correction math 的实际位置(*NoEupF32 fallback、Newton/rational helper),因为 V*Decomposed 拆分不携带这些内容
  • Pack/Unpack PrecisionVpackFormat 模型,以及 AluEp UnpackFOp/PackFOp lower 到的 kVectorUnpack/kVectorPack LLO ops
  • VPU (Vector-ALU) Slot — VALU slot family;EUP push 只能在 Alu3,交错的 correction work 运行在这些 VALU slots 上