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 构建会有所不同。

摘要

EUP,即 Extended Unary Pipeline,是 libtpu 对超越函数单元的称呼,等价于 VPU 页面中的 “XLU”。它是 TensorCore 的硬件近似器,用于 softmax/attention/activation 工作负载实际需要的九个向量函数:tanhpow2 (2^x)、log2reciprocalrsqrtshifted-sigmoidsinqcosqerf。它不是在同一个 bundle 中产出结果的 ALU opcode。它是一个很深的、带 FIFO 缓冲的流水线,由某个 bundle 中的 push 驱动,并在一个或多个 bundle 之后由 pop 排出;在每个 v5+ 代上,push 都被限制在 VALU slot 3。中间的 bundle 是编译器用 VALU 校正算术隐藏 EUP 延迟的地方,这与 CPU 后端为长延迟除法使用的软件流水化技巧相同,只是在这里暴露到了指令编码中,因为 TPU bundle 就是发射包,而调度器没有运行时 hazard interlock 可以依赖。

该槽有两个部分,重新实现者必须分别复现。编码部分:push 是一个 TensorCoreVectorAlu3 op,其 8-bit VALU-opcode 字段选择 EUP-push 族(值 0),其 5-bit function selector 选择超越函数;pop 是一个 VectorResult 槽 op (PopEupResult),并指定一个目标 vreg。数值/时序部分:哪些函数由硬件直接计算,哪些由编译器发出为 VALU 多项式 fallback;三角函数在 push 前需要的 Payne-Hanek 2/π 范围缩减;用于细化或替代原始 EUP 结果的 Newton 和 rational-minimax correction 系数;以及每一代的 push→pop 延迟,FIFO 强加该延迟,而校正窗口必须填满它。三代通过逐指令堆数组路由 push→pop 延迟(Pufferfish = 7,Viperfish = 6,Ghostlite = F32 为 13 / BF16 为 14);旧的 Jellyfish/Dragonfish 路径将其钳制为固定的 4。

一个反直觉的事实奠定了后文:在支持 EUP 的一代上,降低超越函数的 V*Decomposed builder 会发出裸 push + 裸 pop,除此之外没有任何内容,也就是直接采用硬件超越函数。本页中的每个多项式系数都存在于另外两个地方之一:在某一代或某个 datatype 缺少硬件 EUP 时使用的 *NoEupF32 软件 fallback,以及少数精度敏感函数仍然围绕 push 包裹的共享 Newton/范围缩减 helper(recip/rsqrt refinement、tanh rational、sin/cos Payne-Hanek)。本页的组织方式旨在清楚保持这一分界。

对于重新实现,契约是:

  • push 编码。 选择 EUP-push 族的 Alu3 VALU-opcode 字段(glc/gfc 上 8-bit,vxc 上 7-bit)、5-bit function selector 及其完整 F32/BF16 值映射、source-vreg 字段,以及它们在 bundle 中的精确 bit 偏移。
  • pop 编码。 VectorResult PopEupResult op 及其 destination-vreg 字段,以及硬编码进 pop builder 的 kVectorEupResultValue (0x14e) LLO opcode。
  • 融合与拆分的二重性。 MLIR tpu_*_macro 降低到融合的 kVector*AndPop pseudo-op (0x13b..0x14d),以及 LloLateDecomposerHasEupRestrictions 门控下将其重写为裸 push (0x128..0x13a) + 延迟 pop (0x14e) + 交错 VALU 校正。
  • lane-width / unpack 模型。 SupportsBf16AluInstructions 作为 16-vs-32-bit lane sub-element selector,以及递归 UnpackOperand halving,它决定当 packed sub-lane vector 必须在逐片 EUP op 前暂存时的 1:N AluEp fan-out。
  • Payne-Hanek 2/π 表以及三角函数 argument reduction 中的 VcomposeF32(π/2) 重建。
  • 校正多项式系数tanh rational、recip/rsqrt Newton、*NoEupF32 fallback)以及每条路径消费哪些系数。
  • 逐代 push→pop 延迟及其与 VectorEupReservationCycles 发射速率的正交性;EupResultFifoEntry 运行时 list FIFO 模型。
Push 槽VALU slot 3 (TensorCoreVectorAlu3);每代一个 EUP-push helper,没有 Alu0/1/2 EUP
Push LLO opcode0x128..0x13a(裸)— 9 个函数 × {F32, BF16};0x131 push-form erf
Pop LLO opcode0x14e kVectorEupResultValue(硬编码在 CreateVectorEupResult @ 0x1d4d9820 中)
融合 pseudo-op0x13b..0x14d kVector*AndPop(MLIR 发出的形式,由 late decomposer 拆分)
Function selector5-bit 字段;gfc/glc @ bit 183,vxc @ bit 186;F32 + BF16 值映射(已验证)
Pop 结果VectorResult 槽,PopEupResult opcode(gfc 上 result-opcode 5),dest vreg @ bit 11 (gfc)
Push→pop 延迟JF/DF 4(钳制);PF 7;VF 6;GL 13 (F32) / 14 (BF16);pop 以 latency 1 排出
发射速率VectorEupReservationCycles:JF/VF/GL 1,PF 2(半速率 EUP)— 与 latency 正交
Result FIFOEupResultFifoEntry — 运行时 repeated-message list,不是固定的编译期深度

Push:VALU Slot 3,Function Selector

目的

超越函数通过 VALU-slot-3 push 进入 EUP。每一代恰好有一个 EUP-push encoder helper,并且它只存在于 Alu3 op 集合中;不存在 Alu0/Alu1/Alu2 helper,这从编码层面说明 XLU/EUP 是单发射的,并且源只来自 slot 3。push helper 写入三个字段:VALU-opcode(选择 EUP-push )、5-bit function selector(选择哪个超越函数)以及 source vreg。

编码

存在两种布局,每种 opcode 宽度一种。在 6acc60406(gfc,TPU7x TC bundle;外部名称 TPU7x)和 Ghostlite (glc) 上,VALU opcode 是 bit 194 处的 8 bits;在 Viperfish (vxc) 上,它是 bit 197 处的 7 bits。function selector 始终是 5 bits,在 gfc/glc 上位于 bit 183,在 vxc 上位于 bit 186。source vreg 是 6 bits。每个字段都由通用 BitCopy(dst, dst_bit, src, src_bit, nbits) packer (@0x1fa0a900) 写入。本页所有 bit 位置都是 LSB-first:bit 0 是 byte 0 的最低有效 bit(byte = dst_bit >> 3,bit-in-byte = dst_bit & 7),与 Bundle ModelVPU Slot 全文使用的 BitCopy 约定一致;packer 从按 LSB 编号的 dst_bit 开始向上写入 nbits

c
function EncodeTensorCoreVectorAlu3F32Tanh(bundle, alu_proto):   // gfc @0x1f96ae40
    BitCopy(bundle, 194, &0,  0, 8)    // VALU opcode = 0  (the EUP-push family)
    BitCopy(bundle, 183, &19, 0, 5)    // function selector = 0x13 (Tanh, F32)
    if alu_proto.opcode == 211:        // present-bit gate (sub-message attached)
        if (alu_proto.submsg.flags & 1) == 0: return
    BitCopy(bundle, 188, &src_vreg, 0, 6)   // 6-bit source vreg
```text

push helper 按函数区分:`EncodeTensorCoreVectorAlu3F32Tanh` 硬编码 selector `0x13`,`...F32Reciprocal` 硬编码 `0x15`,依此类推。Viperfish 还提供一个*泛型* `EncodeTensorCoreVectorAlu3EupPush` (`@0x1ef6e400`),它写入 selector `0x16`(函数在别处携带的 generic-push slot),以及命名的逐函数 vxc helper。

```c
function EncodeTensorCoreVectorAlu3EupPush(bundle, alu_proto):    // vxc @0x1ef6e400
    BitCopy(bundle, 197, &0,  0, 7)    // VALU opcode = 0  (7-bit on Viperfish)
    BitCopy(bundle, 186, &22, 0, 5)    // generic-push selector = 0x16
    ... present gate ...
    BitCopy(bundle, 191, &src_vreg, 0, 6)

Function Selector 映射

5-bit function selector(gfc/glc @ bit 183,vxc @ bit 186)对每个函数以及每种 datatype取一个不同的值。下列每个值都直接读取自逐函数 Encode...Alu3<Op> helper 在其 BitCopy(_, 183, _, _, 5) 前写入的 mov immediate。F32 和 BF16 两列是不同 selector,不是 datatype bit;硬件从同一个 5-bit 字段解码函数和宽度。

函数F32 selectorBF16 selectorLLO push opcode (F32 / BF16)
Erf0x0e (14)0x0f (15)0x130 / 0x13a
ReciprocalSqrt (rsqrt)0x10 (16)0x0c (12)0x12c / 0x136
PowTwo (2^x)0x11 (17)0x19 (25)0x129 / 0x133
LogTwo (log2)0x12 (18)0x1a (26)0x12b / 0x135
Tanh0x13 (19)0x1b (27)0x128 / 0x132
ShiftedSigmoid0x14 (20)0x1c (28)0x12d / 0x137
Reciprocal0x15 (21)0x1d (29)0x12a / 0x134
Sinq (sin)0x17 (23)0x1e (30)0x12e / 0x138
Cosq (cos)0x18 (24)0x1f (31)0x12f / 0x139
(generic push, vxc)0x16 (22)
Erf, push-form0x131(仅 F32)

怪异点 — selector 0x16 在命名的 gfc 映射上是 rsqrt (0x15) 与 sin (0x17) 之间的一个空洞,但在 Viperfish 上它是 generic EUP push。驱动单一硬件解码表的重新实现者必须将 0x16 视为“函数在带外携带”,而不是第十个超越函数。LLO opcode 0x131 kVectorPushErf 是 erf 的 F32-only push-form,没有 BF16 对应项(BF16 erf 使用 0x13a)。

函数映射

函数地址作用
gfc::EncodeTensorCoreVectorAlu3F32Tanh0x1f96ae40gfc Tanh push(sel 0x13 @183,op@194 w8,src@188)
gfc::EncodeTensorCoreVectorAlu3F32Reciprocal0x1f96afc0gfc Reciprocal push(sel 0x15
gfc::EncodeTensorCoreVectorAlu3F32ReciprocalSqrt0x1f96ac00gfc Rsqrt push(sel 0x10
gfc::EncodeTensorCoreVectorAlu3F32Erf0x1f96b200gfc Erf push(sel 0x0e
gfc::EncodeTensorCoreVectorAlu3Bf16Reciprocal0x1f96b680gfc BF16 Reciprocal push(sel 0x1d
gfc::EncodeTensorCoreVectorAlu3Bf16Tanh0x1f96b500gfc BF16 Tanh push(sel 0x1b
vxc::EncodeTensorCoreVectorAlu3EupPush0x1ef6e400vxc generic EUP push(sel 0x16 @186,op@197 w7,src@191)
BitCopy0x1fa0a900通用 bit 粒度 packer (_Z7BitCopyPviPKvii)

注意事项

push 的 VALU-opcode 字段在所有三个 v5+ gen 上都是 0,这意味着 EUP-push 族不消耗二元 ALU op 所用的密集 VectorAluOpcode 空间中的 opcode 点;函数区分符完全在 5-bit selector 中。push 偏移位于 slot-3 VALU 窗口内(gfc VALU0 opcode @ bit 293,约 33-bit/slot stride → VALU3 @ 约 bit 194),与 VPU Slot 的四槽 VALU 布局一致。push 由 FindFreeEupSlot<gen>gfc/glc @ 0x142def40)放入 slot 3,它调用 FindFreeVectorSlot<gen>(..., 3, ...) 并设置 EUP-occupancy bit Bundle[+0x10] |= 0x20,受 VregReadPort hazard set 约束。


Pop:VectorResult 槽

目的

EUP 结果在 push 之后一个或多个 bundle 中从 VectorResult 槽排出;这是同一个用于排出 MXU matmul 结果 (PopMxuResult) 的槽。一个 result-opcode 值选择是哪一种:在 6acc60406 (gfc) 上,TensorCoreVectorResult0Encoder::Encode switch (@0x1fa01820) 将 5 → PopEupResult6 → TransposeResult7 → PopMxuResultcase 7 分支是唯一写入 bit 323 处 8-bit accum-mode/format 的分支。pop 只指定一个 destination vreg;它不携带函数(函数在 push 时决定,并随 FIFO entry 传播)。

编码

PopEupResult 通过通用 VectorResult 尾部写入 result-type discriminator 中的 result-tag 和 6-bit destination vreg。在 6acc60406 (gfc) 上,encoder 首先在 bit 20 写入 2-bit 顶层 result-tag(每个 result sub-message 都通用,值 = proto field +0x1c),然后在 case 5 (EUP) 分支中,于 bit 17 写入 3-bit EUP sub-tag literal 0,最后在 bit 11 写入 6-bit dest vreg。在 Viperfish/Ghostlite 上,discriminator 是 bit 24 处的 4 bits,dest vreg 是 bit 14 处的 6 bits。

c
function PopEupResult_gfc(bundle, result_proto):     // gfc, result-opcode 5 (case 5 @0x1fa01820)
    BitCopy(bundle, 20, &result_proto.tag, 0, 2)  // common 2-bit result-tag (proto +0x1c)
    BitCopy(bundle, 17, &0, 0, 3)                 // EUP sub-tag literal 0 (case-5 arm)
    BitCopy(bundle, 11, &dest_vreg, 0, 6)         // common dest-vreg tail
    // (contrast case 7 / PopMxuResult, which writes tag literal 0x2 @ bit 18
    //  and the 8-bit accum-mode/format @ bit 323)
```text

在 LLO 层级,pop 是 `kVectorEupResultValue`,opcode `0x14e`。builder `CreateVectorEupResult` (`@0x1d4d9820`) 将其硬编码;pop 没有宽度或函数变体:

```c
function CreateVectorEupResult(eup_push, region):    // @0x1d4d9820
    assert (eup_push.opcode - 0x128) < 0x13          // push must be in [0x128, 0x13a]
                                                     // ("LloOpcodeIsVectorEup(eup->opcode())")
    return LloInstruction::New(0x14e, {eup_push}, /*n_operands=*/1)

函数映射

函数地址作用
CreateVectorEupResult0x1d4d9820构造 0x14e pop;断言 push ∈ [0x128,0x13a]
gfc::TensorCoreVectorResult0Encoder::Encode0x1fa01820result slot switch:op5=PopEup,op6=Transpose,op7=PopMxu
vxc::TensorCoreVectorResult0Encoder::Encode0x1f018f40vxc result slot(disc @bit24 w4,dest @bit14 w6)
glc::TensorCoreVectorResult0Encoder::Encode0x1f3bc160glc result slot;增加 PopAddMxu01Result

注意事项

逐 gen 的 VectorResult 槽携带不同的 sub-message:vxc 增加 PopCcrfResult(scalar/CRF pop),glc 增加 PopAddMxu01Result(K>128 matmul 路径的 fused matres+accumulate)。EUP pop 在三代上都是同一个 PopEupResult op。result slot 还携带自己的 predication 字段(TensorCoreVectorResult1PredicationField::GetConcatenatedValue @ 0x1fa02520),因此 EUP pop 可以独立于 push 进行 predication。


Fused vs Split:AndPop 二重性

目的

MLIR 不发出裸 push + 裸 pop。超越函数(例如 math::TanhOp)的 UnaryFloatVector lowering 发出一个融合tpu_*_macro IR op,它降低到单个融合 LLO pseudo-op kVector{fn}{F32,Bf16}AndPop(opcode 0x13b..0x14d)。这个融合 op 稍后会被 pass 拆分,或者在能够共置 push 和 pop 的一代上保持融合。二重性在 LLO opcode 空间中是显式的:0x12a kVectorReciprocalF32(裸 push)、0x13d kVectorReciprocalF32AndPop(融合)、0x14e kVectorEupResultValue(独立 pop)。

算法

LloLateDecomposer (@0x1269cb20) 遍历融合 pseudo-EUP op,并将每个重写为裸 push + 延迟 pop,调用 DecomposeEupInstruction (@0x126a0340),后者根据 pseudo-opcode 分派到九个 V*Decomposed builder 之一。融合 op 的分类器是精确的:

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

`0x7fdff` mask 精确排除 19 个 AndPop opcode 中的一个:bit 9,即 opcode `0x144 kVectorPushErfAndPop`,因此 19 个中的 18 个被视为 pseudo-EUP。每个 `V*Decomposed` builder 都是**裸 push + 裸 pop**,从 `PrimitiveType` 参数选择 F32 或 BF16 push opcode:

```c
function VtanhDecomposed(builder, prim_type, value):  // @0x1d555040
    push_opcode = (prim_type == 0xb) ? 0x128 : 0x132  // 0xb = BF16 → F32 push 0x128;
                                                      // else BF16 push 0x132
    push = CreateVectorEup(push_opcode, value, region)   // @0x1d4d78a0
    AppendInstruction(region, push)
    pop  = CreateVectorEupResult(push, region)           // hardcodes 0x14e
    AppendInstruction(region, pop)
    // NO inline correction polynomial, NO refinement helper

注 — V*Decomposed builder 不携带 inline correction。VrsqrtDecomposedVtanhDecomposed 发出裸 push + 裸 pop,除此之外没有任何内容;二者既不会为 Newton refinement 发出第二对 push+pop,也不会在 push 与 pop 之间交错校正步骤。VfastTwoSum (@0x1d5550a0) 是一个独立的 Dekker two-sum helper(三个 op,无常量),只是物理上在 .text 中紧邻 VtanhDecomposed;它并未被后者调用。带系数的校正数学存在于 *NoEupF32 fallback 和共享 Newton/rational helper 中,而不是 V*Decomposed builder 内部。

拆分在 v5+ 代上是强制性的,因为 Viperfish (@0x1c458620) 和 Ghostlite (@0x1c458d80) 上 HasEupRestrictions 为 TRUE:EUP push 及其 pop 不能 co-issue,所以 decomposer 必须将它们发到不同 bundle,并用 VALU correction(或无关工作)填补间隙。在 Jellyfish (@0x1c457b80) 和 Pufferfish (@0x1c4580c0) 上,该限制为 FALSE,因此融合形式可以保留;反向 simplifier SimplifyTanhAndPop (@0x1d593c60)、SimplifyReciprocalAndPop (@0x1d595d40)、SimplifySinqAndPop (@0x1d596de0)、SimplifyCosqAndPop (@0x1d597680) 会在调度允许共置时重新融合匹配的 push+pop。

LLO Opcode 分类

opcode 空间由三个连续区段外加 pop 组成:

区段范围含义
裸 push0x128..0x13a9 个函数 × {F32, BF16};CreateVectorEup 发出其中之一
Push-form erf0x131erf 的 F32-only push 变体
融合 AndPop0x13b..0x14dMLIR 发出的 fused push+pop;0x144 (PushErfAndPop) 从 pseudo-EUP 中排除
延迟 pop0x14ekVectorEupResultValueCreateVectorEupResult 将其硬编码

函数映射

函数地址作用
DecomposeEupInstruction0x126a0340分派到 9 个 V*Decomposed builder
LloLateDecomposer0x1269cb20拆分 fused AndPop → bare push + deferred pop
LloOpcodeIsPseudoEupInstruction0x1d60c880分类 fused AndPop(bitmask 0x7fdff
CreateVectorEup0x1d4d78a0发出裸硬件 EUP push(1 个 operand)
VtanhDecomposed / VrsqrtDecomposed0x1d555040 / 0x1d557b60裸 push+pop builder
DecomposeEupOperationsForBarnacore0x1269c5c0BarnaCore EUP split(独立 result-drain 路径)

Lane Width 和 AluEp Unpack Fan-Out

目的

在超越函数可以 push 之前,其 operand 必须适配 EUP lane width。在 ALU lane 宽于元素的一代上,packed sub-lane vector(32-bit lane 中的两个 BF16,或 sub-byte f8/s8)必须被unpack 到 lane-width piece,每个 piece 分别 push,然后再 repack 结果。这就是 AluEpOpLowering 1:N 路径;它是否触发以及 N 是多少,由 lane-width 模型决定。

算法

selector 是 SupportsBf16AluInstructions(Target vtable slot +0x780):FALSE → lane sub-element width 是 32 bits(一个 F32 lane),TRUE → 16 bits(原生 BF16 lane)。Viperfish 返回 FALSE (@0x1d49c0e0);Ghostlite 返回 TRUE (@0x1d498ce0);base Target 是一个 LogFatal pure-virtual,因此每个具体 gen 都会 override。IsDynamicallyLegal (@0x135ddd20) 决定 1:1(保留为 EUP-macro 路径)还是 1:N(unpack):

c
function IsDynamicallyLegal(op, target, operand_idx):   // @0x135ddd20
    forced = ForceBF16ALUOperationsToUnpack(op, ty)     // force-1:1 flag
    if !isVectorType(ty):            return LEGAL        // scalar → 1:1
    if !IsPackedVectorType(ty):      return LEGAL        // not packed → 1:1
    fmt = GetVpackFormat(ty)                             // 0=no-pack,1=bf16,0xb=f16,0x7=sub-byte
    if fmt == 0:                     return LEGAL        // not packable → 1:1
    if !target.SupportsBf16AluInstructions():            // *0x780 false (VF) → 1:N
        return ILLEGAL
    return (ty.element.bitwidth == 16) ? LEGAL : ILLEGAL // bf16-16 → 1:1, else (f8/s8) → 1:N
```text

当 op 被标记为 ILLEGAL 时,`UnpackOperand<UnpackFOp>` (`@0x1360fac0`) 递归地将 packed element 对半拆到 lane width,并返回一个 `std::deque<Value>`,其长度就是 1:N fan-out 的 N:

```c
function UnpackOperand(loc, packBits, operand, vecTy, target, override):  // @0x1360fac0
    lane_bw = target.SupportsBf16AluInstructions() ? 16 : 32   // *0x780; override via optional<int>
    deque = [operand]
    while lane_bw > bitwidth(current_element):                 // outer loop @0x1360fb70
        sub_ty = GetUnpackResultElementType(current, ...)      // @0x1360ff20: 16→BF16, 32→F32,
                                                              //   else getIntegerType(2*src_bw)
        count  = bitwidth(result_elem) / bitwidth(sub_elem)    // pieces this step (typ. 2)
        tuple  = UnpackFOp::create(...)                        // one unpack → a tuple
        for i in 0..count:  deque.push(ExtractTupleElementOp(tuple, i))
    return deque    // length == N

随后 AluEp body 在一个 unpack 和一个 PackResults<PackFOp> 之间恰好发出 N 个逐片 ComputeOp::create 调用(每个都有自己的 EUP push+pop)。

示例 — packed bf16 rsqrt,Viperfish vs Ghostlite

vector<…×bf16>(16-bit packed sub-lane)上的 sc.rsqrt。两代都会同时注册 1:1 UnaryFloatVector 和 1:N AluEp pattern;IsDynamicallyLegal 会选择:

text
VIPERFISH (SupportsBf16AluInstructions = false):
  fmt(bf16) = 1 (!=0), SupportsBf16Alu = false → ILLEGAL → AluEp 1:N fires.
  UnpackOperand: lane_bw = 32 (F32) > 16 (bf16) → one halving step:
    result_bw = 32, sub_bw = 16 → count = 2 pieces; loop exits (32 == 32).
  ⇒ N ≈ 2 × (shape / lane-tiling); each F32 piece → its own 0x12c rsqrt push + 0x14e pop.

GHOSTLITE (SupportsBf16AluInstructions = true, elem bw == 16):
  fmt != 0, SupportsBf16Alu = true, elem == 16 → LEGAL → NO unpack; the 1:1 path fires:
    tpu_rsqrt_macro → fused 0x149 kVectorRsqrtBf16AndPop, split into a 0x136 BF16 push +
    0x14e pop. ONE push/pop, the BF16 ALU runs the per-lane op natively.
⇒ The newer gen (GL) processes packed bf16 at half the push count of VF, because the native
   BF16 lane lets it skip the unpack to F32 entirely.
```text

### 函数映射

| 函数 | 地址 | 作用 |
|---|---|---|
| `UnpackOperand<UnpackFOp>` | `0x1360fac0` | 递归 sub-element halving;返回 N-deque |
| `GetUnpackResultElementType` | `0x1360ff20` | 按 lane width 得到下一个更窄的 sub-element type |
| `IsDynamicallyLegal` | `0x135ddd20` | 1:1 vs 1:N selector(4-arm truth table) |
| `Target::SupportsBf16AluInstructions` | `0x1d498ce0` (GL) / `0x1d49c0e0` (VF) | lane sub-element width(vtable +0x780) |
| `GetVpackFormat` | `0x13dad800` | pack-format enum(0=none,1=bf16,0xb=f16,0x7=sub-byte) |

### 注意事项

`SupportsSparseCore`(vtable `+0x260`,`@0x1d48fd40`)是 AluEp *entry guard*(如果 target 没有 SparseCore,lowering 会退出),不是 lane-width 输入;这很容易和 `+0x780` 混淆,因为二者都门控同一个 lowering。`UnpackOperand` 尊重的 `optional<int>` lane-width override(`bt $0x20`/`cmovb` 路径)允许 caller 为 `f8`/sub-byte staging 强制非默认 lane width;哪些 caller 传入它未在此枚举。完整 vpack-format 模型见 [Pack/Unpack Precision](pack-unpack-precision.md)。

---

## Payne-Hanek 范围缩减(三角函数)

### 目的

`sin`/`cos` 不能直接 push 原始值:很大的 argument |x| ≫ 1 如果在 float 中按 π/2 取模,会丢失所有重要小数位。`PayneHanekRangeReduction` (`@0x1d5819c0`) 执行经典的精确小数缩减:它将 x 乘以 1/(2π) 的高精度定点展开,保留由 x 指数选定的窗口化小数,并重建缩减后的 argument r = (x mod π/2) 和象限索引 k。该函数是三角路径的整数部分,与 `sin`/`cos` EUP push 配对。

### 1/(2π) 表

六个连续的 32-bit word 构成 **1/(2π)** 的定点展开(象限计数形式:乘以 1/(2π) 得到完整圈数),通过 `LloModule::VectorU32Constant` (`@0x1d506400`) 按 MSB-first 加载:

| word | u32 | 作用 |
|---|---|---|
| w0 | `0x28be60db` | 1/(2π) 的 bits[191..160],MSB limb(= floor(2³²/(2π)) = 683565275) |
| w1 | `0x9391054a` | bits[159..128] |
| w2 | `0x7f09d5f4` | bits[127..96] |
| w3 | `0x7d4d3770` | bits[95..64] |
| w4 | `0x36d8a566` | bits[63..32] |
| w5 | `0x4f10e410` | bits[31..0],LSB limb |

小数重建 Σ wᵢ·2^(−32(i+1)) = 0.15915494309189535 = 1/(2π),精确到 fp64。窗口化乘法(`VshllU64High` @ `0x1d583ac0` + `VmulU64`)提取由 argument 指数选定的 product bit window,因此相关小数位对任意大的 |x| 都能保留下来。

重建使用这些额外的 `VectorU32Constant` immediate:

| u32 | value | 作用 |
|---|---|---|
| `0x00000020` | 32 | 窗口化乘法的 per-word shift width |
| `0x00000fff` | 4095 | 12-bit window / round mask |
| `0x0000001e` | 30 | fractional-product extraction 的 window-bit count |
| `0xffffffe2` | −30 | binary-point exponent offset(送入 `SubS32` → `CreateVectorBinop 0x121`) |
| `0x20000000` | 2^29 | rounding / half-ULP bias |
| `0x00c90fdb` | — | π/2 的 24-bit significand(float π/2 = `0x3fc90fdb`)→ `VcomposeF32` |

缩减后的 argument 是 r = `VcomposeF32(0x00c90fdb, ...)` 搭配 −30 binary-point shift,也就是 (x mod π/2) 与 π/2 mantissa 重新组合。象限索引 k = floor(x/(π/2)) mod 4 选择 sin/cos 的符号以及 sin↔cos 交换。

### 注意事项

k-mod-4 → {+sin, +cos, −sin, −cos} 的 sign/swap truth table 由该函数的 16 个 `SimplifySelect` 分支计算,此处不复现。尽管约定名称是“2/π reduction”,该表是 **1/(2π),不是 2/π**:leading word 是 floor(2³²/(2π)),重建和精确为 1/(2π)。成本模型视角见 [Payne-Hanek Range Reduction](../cost/eup-paynehanek.md)。

---

## 校正系数

### 目的

在支持 EUP 的一代上,大多数超越函数直接 push 原始值,该值就是答案。三个地方持有多项式系数:少数函数为提升精度而围绕原始 push 包裹的 **Newton refinement**(`recip`、`rsqrt`);用作 no-EUP 软件路径的 **rational minimax** 近似(`tanh`、`atan2`);以及当某个 gen/datatype 缺少硬件 EUP 时,以纯 VALU 完整实现超越函数的 **`*NoEupF32` 软件 fallback**(`exp`、`expm1`、`ln`、`ln1p`、`log2`)。所有常量都是 fp32 `.rodata` literal(VMA == file offset)。

### Newton-Raphson Refinement

```c
function EmitRecpNrIteration(x, y):       // @0x1d5a9ec0 — recip Newton step
    return y * (2 - x*y)                  // one constant: 1.0 = 0x3f800000 @0x84a2444

function EmitRsqrtNrIteration(x, y):      // @0x1d5a9e20 — rsqrt Newton step
    return y * (1.5 - 0.5*x*y*y)          // 0.5 = 0x3f000000 @0x84a27e8;
                                          // 1.5 = 0x3fc00000 @0x84a2680

Tanh Rational (tanh(x) ≈ x·P(x²)/Q(x²))

EmitTanhPolyApproximation (@0x1d5a9f40) 是 no-EUP 软件 tanh,也是成本模型用于估算规模的 rational。SSA 结构经由 VdivF32 追踪如下:将 x clamp 到 [−9, +9];small-|x| guard(|x| < 4e-4 → 返回 x);x² = x·x;numerator = x·P(x²),其中 P 是以 x² 为变量的 7 系数 Horner(seed c0 + 6 个 FMA c1..c6,然后 ×x);denominator = Q(x²),一个 4 系数 Horner(seed d0 + 3 个 FMA d1..d3);VdivF32(NUM, DEN)Vselect(small ? x : quotient);最终 clamp 到 [−1, +1]。

numerator P 有 7 个系数(c0..c6):seed VimmF32(c0) 加上六个 VmulAddF32 FMA 步骤(c1..c6)。VdivF32 operand trace 消除了两个 Horner accumulator 的歧义:与 clamped x 相乘(形成奇函数 x·P)的那个是 numerator;裸的偶函数 Horner 是 denominator。

Numerator P(x²) — 7 个系数,Horner 按 x² 从低到高,然后 ×x:

角色u32f32.rodata off
c00xa59f25c0-2.7607683663038313e-160x84a2654
c10x2a61337e2.0001879384549948e-130x84a2428
c20xaebd37ff-8.604671836165423e-110x84a2ef4
c30x335c00415.122297253024044e-080x84a27d8
c40x3779434a1.4857223504805006e-050x84a24f0
c50x3a270ded0.00063726195367053150x84a242c
c60x3ba059dc0.0048935245722532270x84a24a0

Denominator Q(x²) — 4 个系数,Horner 按 x² 从低到高:

角色u32f32.rodata off
d00x35a0d3d81.1982583600911312e-060x84a2e8c
d10x38f895d60.000118534706416539850x84a2658
d20x3b14aa050.00226843473501503470x84a2564
d30x3ba059dd0.00489352503791451450x84a27dc

边界:clamp lo −9.0 (0xc1100000 @ 0x84a283c),clamp hi +9.0 (0x41100000 @ 0x84a2a84),small thresh 4e-4 (0x39d1b717 @ 0x84a2758),saturate −1.0 (0xbf800000 @ 0x84a26cc),+1.0 (0x3f800000 @ 0x84a2444)。

注 — c6 (0x3ba059dc) 和 d3 (0x3ba059dd) 只在 LSB 上不同。leading numerator 和 denominator 系数刻意接近相等,使得当 |x| → 9(saturation limit)时 x·P/Q → ±1;这是该 rational 的高阶渐近线。它们是经字节确认的不同常量,不是转录错误。

No-EUP 软件 Fallback

当某个 gen/datatype 没有硬件 EUP 时,*NoEupF32 族以纯 VALU 实现超越函数。这些是 exp/log 的系数来源(它们没有硬件 EUP push;只有 pow2/log2 有,而 exp/ln 通过缩放由它们构建)。

函数地址方法关键常量
VexpNoEupF320x1d533820exp(x)=2^(x·log2e),Cody-Waitelog2e 1.4426950 (0x3fb8aa3b);ln2-hi −0.693359375 (0xbf318000),ln2-lo 2.1219e-4 (0x395e8083);clamp [−87.337, +88.723];5-term Taylor {0.49999988, 0.16666518, 0.041669652, 0.0083689447, 0.0013744964}
Vexpm1NoEupF320x1d533ec0expm1,自有 5-coeff Taylorclamp lo −17.32868 (0xc18aa123);5-term {1.428607e-6, 0.0013910766, 0.008363097, 0.041666709, 0.16666578}
Vlog2NoEup0x1d556a60mantissa-extract + 9-coeff Hornermask 0x7fffff / 0x3f800000;sqrt2 1.4142135 (0x3fb504f3);9 个系数 c0..c8,以 1.44269502 结尾
VlnNoEupF320x1d534740ln = log2 · ln2ln2 = 0.6931472 (0x3f317218)
Vln1pNoEupF320x1d534a20log1p,8-coeff Hornerln2-hi 0.693145752 (0x3f317200),ln2-lo 1.428607e-6 (0x35bfbe8e);8 个系数
VtanhNoEupF320x1d535b2032-byte thunk → EmitTanhPolyApproximation(上文 rational)
EmitAtan2Approximation0x1d5aa1c0r=min/max ratio;7-term odd Hornerquadrant consts π/2 1.5707964,π 3.1415927,π/4 0.78539819,3π/4 2.3561945

使用 EUP 的 exp/ln 变体会在硬件 pow2/log2 push 上应用一个小的 fp32 correction:VexpEup (@0x1d556080) 先乘以 log2e,然后使用融合的 0x146 kVectorPow2Bf16AndPopVln1pEup (@0x1d557340) 使用 log2 EUP 加上 4.4273e-4 correction (0x39e81ecb)。

注意事项

“no-EUP fallback vs bare push”的精确 gen×datatype 门控由逐 gen SupportsXxxInstruction Target accessor 拥有,此处不重新推导;fallback 绑定到具体 gen 的信息存在于这些 accessor 中。完整系数目录见 EUP Correction Coefficients


Push→Pop 延迟和 Result FIFO

目的

EUP push→pop 对由从两个不同数组读取的两个独立量约束:data latency(从 push 到其排出所需的最少 bundle 数)和 issue reservation(从一个 push 到下一个 push 的最少 bundle 数)。data latency 是 VALU correction 窗口必须隐藏的深度;reservation 是 EUP 单元的吞吐量。将二者相乘的重新实现者会得到错误的调度。

Latency Edge

两种机制设置 push→pop data-latency edge。旧的 Jellyfish/Dragonfish 路径会钳制它:LatencyTableJellyfish::LatencyBetweenInternal (@0x1c8a0d60) 在第一个 opcode 是裸 push(∈ [0x128, 0x13a])且第二个是 0x14e pop 时,将 latency 钳制到 this+0x1c,ctor 从 Performance[+0x30] = 4 复制该值。较新的 gen 改为通过逐指令堆数组路由:

c
function LatencyBetweenInternal_newer(from, to):   // PF/VF/GL
    inst = Get<Gen>Instruction(from)               // LLO-opcode → per-gen Instruction enum
    base = <Gen>Performance::GetLatency(inst)       // latencies[inst]: int32 heap array
                                                    // ptr@Perf[0], count@Perf[+0x8]
    // max-combine with XLU/MXU floors — the EUP push is NOT in the floored set —
    // so `base` passes through unmodified.
    return base
```text

`GetLatency` 在每个较新 gen 上都是相同的 4 条指令 lookup(VF `@0x1c8cbc20`,PF-TC `@0x1c8c3860`,GL `@0x1c8d36e0`):`cmp [rdi+8],idx; jbe ud2; mov rcx,[rdi]; mov eax,[rcx+idx*4]; ret`。逐 gen `Performance` ctor 逐元素填充数组;EUP push 值就是 edge weight。

| gen | EUP push latency | pop latency | 机制 | byte anchor |
|---|---|---|---|---|
| Jellyfish | 4 | (clamp) | `Performance[+0x30]` = 4 clamp | `PerformanceJf` ctor `@0x1d4930c0` |
| Dragonfish | 4(继承 Jf) | (clamp) | `PerformanceDf` 保持 `+0x30` = 4 | `@0x1d493060` |
| Pufferfish | 7 | 1 | `PufferfishPerformance` array | `[rax+0x19c]=7` (idx 0x67) |
| Viperfish | 6 | 1 | `ViperfishPerformance` array | `[rax+0x330..0x348]=6` (idx 0xcc..0xd2) |
| Ghostlite | 13 (F32) / 14 (BF16) | 1 | `GhostlitePerformance` array | `[rax+0x418..0x460]=0xd/0xe`;pop `[rax+0x710]=1` |

push 值在每个 gen 上对所有已分类 EUP 函数都是**统一的**:`tanh = pow2 = recip = log2 = rsqrt = sigshft = erf`,因此 EUP 单元对每种 datatype 有单一的超越函数 latency。Ghostlite 是唯一将 F32 (13) 与 BF16 (14) 分开的 gen:它有原生 BF16 ALU(`SupportsBf16AluInstructions` TRUE),所以其 classifier 将 F32 push(Instr 0x106..0x10f13)和 BF16 push(Instr 0x110..0x11814)映射到不同 latency slot;BF16 超越函数多花一个 cycle。Viperfish 和 Pufferfish 只分类 F32 push(late decomposer 在这些 gen 上将 BF16 EUP widen 到 F32 push),因此它们只有单一 EUP latency。

> **怪异点 —** Pufferfish 的 latency table 是一个 `std::variant<PufferfishPerformance, PufferfishBarnaCorePerformance>`,由 2-arm `__fmatrix` visitor (`@0x21c203d0`) 分派;`GetPufferfishInstruction` 返回值的高 16 bits 选择 variant。**每个** EUP opcode 都发出 variant 0 (TensorCore),因此 EUP edge 是 TensorCore array = 7,不是 BarnaCore variant(后者在独立的 134-entry array 中有自己的 6-cycle EUP block)。Pufferfish 同时带有二者,是因为 BarnaCore 只在 Pufferfish 之后 retire。

### Latency 与 Reservation 的正交性

`LatencyTable::LatencyBetween` (`@0x1c89f820`) 调用逐 gen 的 `LatencyBetweenInternal`,可选地添加 random jitter,只对 matres/transpose opcode(`0x82`/`0x84`)用 MXU floor 特判,并对 EUP push 返回**未改变**的 edge。该路径上任何地方都没有乘以任何 reservation 字段。`VectorEupReservationCycles`(Target vtable `+0x480`:JF/VF/GL = 1,PF = 2)是*正交*的 issue-occupancy,即 EUP resource 在 push 后保持 reserved 的 bundle 数,由逐指令 resource model(`GetResourceUsage` matrix + `SlotTracker`)应用,而不是由 latency edge 应用。

|| 约束 | 来源 | PF | VF | GL (F32/BF16) | JF |
|---|---|---|---|---|---|---|
| push→pop data latency | push → drain 的最少 bundle 数 | `latencies[Get<Gen>Instr(push)]` | 7 | 6 | 13 / 14 | 4 (clamp) |
| pop latency | drained value 携带的 latency | `latencies[pop Instr]` | 1 | 1 | 1 ||
| `VectorEupReservationCycles` | push → next push 的最少 bundle 数 | Target accessor (`+0x480`) | 2 | 1 | 1 | 1 |

组合方式是 `max(latency-deadline, resource-availability)`,不是乘积:pop 最早放在 `push_bundle + latency`;连续 push 的间隔不少于 `reservation` 个 bundle。decomposer 必须填充的 VALU-correction software-pipeline 深度 = **latency**(PF 7),独立于 reservation。Pufferfish 的半速率 EUP(reservation 2**不会**7-cycle push→pop 窗口翻倍;它是将发射速率减半。对于 PF 上 N 个独立超越函数的链,EUP-bound schedule length ≈ 2·(N−1) + 7 bundles,而不是 7·2

### Result FIFO

EUP result FIFO **不是固定的编译期深度**。HW-state simulator proto 将它保存为 `repeated EupResultFifoEntry eup_result_fifo_entries = 3`(descriptor bytes `18 05 20 03 28 0b`),其中 `message EupResultFifoEntry { repeated Lane lanes = 1; }`;这是 in-flight pushed result 的运行时快照 list,每个 entry 都是逐 lane result vector。in-flight push 数量在*调度*时由 latency edge + `BaseFifoTracker<LloValue*>` push/pop ordering(`FindBlockingPushesAndPops` @ `0x14442f60`,它为 FIFO-ordering edge 调用 `LatencyTable::LatencyBetween`)约束,并在 v5+ bundle 层由 `HasEupRestrictions` 强制 push/pop 分到不同 bundle。silicon FIFO 的物理深度是芯片参数,不是 libtpu literal;无法从这个 binary 中恢复。

### 函数映射

| 函数 | 地址 | 作用 |
|---|---|---|
| `LatencyTable::LatencyBetween` | `0x1c89f820` | dispatcher;原样返回 EUP edge |
| `LatencyTableJellyfish::LatencyBetweenInternal` | `0x1c8a0d60` | JF/DF EUP clamp 到 `Performance[+0x30]`=4 |
| `GhostlitePerformance` ctor | `0x1c8cbc80` | 填充 F32 EUP=13,BF16=14,pop=1 |
| `ViperfishPerformance` ctor | `0x1c8c4840` | 填充 EUP=6,pop=1 |
| `PufferfishPerformance` ctor | `0x1c8be080` | 填充 EUP=7(TensorCore variant 0|
| `<Gen>Performance::GetLatency` | `0x1c8cbc20` (VF) etc. | `latencies[Instruction]` heap lookup |
| `EupResultFifoEntry` (proto) | `0x0e7a6cc0` (ctor) | 运行时 `repeated`-message FIFO list |
| `BaseFifoTracker<LloValue*>::FindBlockingPushesAndPops` | `0x14442f60` | FIFO push/pop ordering edge |

### 注意事项

Pufferfish `kVectorSigShftF32` (0x12d) Instruction ordinal 落到 classifier default(其他六个 F32 EUP push 全部为 7),因此 PF 上 shifted-sigmoid edge 遵循统一 EUP latency 模式 7,尽管此处未钉住该具体 edge。31 个 `GhostlitePerformance` resource 中哪一个是 EUP 单元(以及其 `GetResourceUsage` cycle count 是否等于 `VectorEupReservationCycles`)未从 ctor 的 template fill 中单独分离。成本模型集成见 [EUP Latency Overview](../cost/eup-latency-overview.md)。

---

## 交叉引用

- [VPU 槽](slot-vpu.md) — VALU slot 族;EUP push 仅限 `Alu3`(slot-3 restriction,op@197/194 sel@186/183 src@191/188
- [Viperfish 64-bit Bundle](bundle-vf-64b.md) — 完整 64-byte bundle 上下文中的 Alu3 EUP push
- [Ghostlite Bundle](bundle-gl.md) — glc bundle 布局及其 8-bit VALU opcode
- [6acc60406 Bundle](bundle-gf.md) — gfc result-slot map(`5`=PopEup,`6`=Transpose,`7`=PopMxu)和完整 64-byte slot 布局
- [Pack/Unpack Precision](pack-unpack-precision.md) — `IsDynamicallyLegal` 和 AluEp unpack 背后的 vpack-format 模型
- [EUP Latency Overview](../cost/eup-latency-overview.md) — push→pop latency edge 和 reservation 的成本模型视角
- [EUP Correction Coefficients](../cost/eup-correction-coeffs.md) — no-EUP fallback 与 Newton/rational refinement 的完整系数目录
- [Payne-Hanek Range Reduction](../cost/eup-paynehanek.md) — 三角函数 1/(2π) reduction table 和 quadrant reconstruction