Skip to content

Pufferfish 51 字节 Bundle

地址适用于 libtpu-0.0.40-cp314 wheel 中的 libtpu.so。其他版本会有所不同。

摘要

Pufferfish TensorCore VLIW bundle 是一个 51 字节(408 位) 的发射字——它是 41 字节 Jellyfish bundle 的 TPU v4 后继者,也是 Jellyfish 直接打包编码器与 V6+ GXC codec 之间的架构桥梁。最好把它理解为相对 Jellyfish 的增量:相同的 kNeverExecute 空槽约定、相同的谓词语义、相同的共享操作数池,但有四项新的 datapath 能力(第二个 MXU、更宽的 VALU lane、一等常量内存加载槽、第二条结果 drain lane)以及一种根本不同的编码机制。

Jellyfish 通过对 53 字节 scratch struct 的 qword 执行 shl/and/or 算术来构建 bundle,然后剥离 12 字节 header;Pufferfish 则没有 scratch struct,也没有 header stripEncoderPfTensorCore::EncodeBundleInternal0x1e8c5c40)是一个薄包装器:它向 codec 请求字节大小(51),operator new 一个 51 字节缓冲区并用 memset 清零,然后把缓冲区交给 codec 的 Encode 方法。codec——asic_sw::deepsea::pxc::isa::TensorCoreCodecBase<TensorCoreBundle, …, Predication>Encode @ 0x1d224300)——逐槽遍历 bundle proto,并对每个存在的槽调用一个逐槽 TensorCore{Slot}Encoder::Encode,该编码器通过一次共享位打包原语 BitCopy(void* dst, int dst_bit, const void* src, int src_bit, int nbits)0x1fa0a900)把每个字段写入缓冲区。字段的绝对 bundle 位就是其 BitCopy 调用中的字面量 dst_bit 参数——没有要反推的移位算术,也没有要减掉的 header 偏移。输出字节 N 直接就是缓冲区字节 N。

本页以绝对位精度记录完整的 51 字节槽映射、以 proto+0x10 为键的十二槽 has-bit 分派、逐槽 BitCopy 字段算术、三个内存槽都针对同一个共享寻址子消息和操作数池内联的五个共享 Load/Store/Cmem 操作数子编码器、kNeverExecute=31 预填充,以及逐字节精确的 Jellyfish→Pufferfish 差异。对重新实现而言,契约是:

  • 直接 BitCopy 模型:一个 51 字节缓冲区,memset 为零,通过 BitCopy(buf, abs_bit, &field, 0, width) 逐字段写入;输出字节 N == 缓冲区字节 N,没有 12 字节剥离,也没有 scratch struct
  • 十二槽 has-bit 分派TensorCoreBundle proto +0x10 处的 12 位槽 has-mask,proto+0x18..+0x70 处的逐槽子消息指针,以及 codec 在位清除时通过 test [proto+0x10],bit / cmove default 替换为 _globals_ 默认值。
  • 每个槽的绝对位基址 + 宽度:低专用区域(bits 17..240)、共享操作数池(241..353),以及顶部的两个标量槽(354..407)。
  • 五个共享内存操作数子字段:base-address(2b)、offset(2b)、stride(3b)、3 项 Y-register selector SlotMap(5b @241/246/251)和 6 项 immediate SlotMap(16b @256/272/288/304/320/338),它们在 proto 构建时由 VectorYVEncode / ScalarYEncode / SetImmOrDie 绑定。
  • kNeverExecute=31 预填充,由 FillDefaultBundle0x1d222ee0)盖印到每个槽的谓词字段,以及 JF→PF 的四轴差异(第 2 个 MXU、宽 VALU0、cmem_load、第 2 个 result)。
编码包装器EncoderPfTensorCore::EncodeBundleInternal(TensorCoreBundle const&) @ 0x1e8c5c40
Codec 引擎TensorCoreCodecBase<…, Predication>::Encode @ 0x1d224300(12 槽分派)
位原语BitCopy(void*, int dst_bit, void const*, int src_bit, int nbits) @ 0x1fa0a900dst_bit == 绝对 bundle 位
线缆宽度51 字节 / 408 位EncoderPfTensorCore::BundleSizeBytes @ 0x1d227740 返回 0x33
位编号LSB-first — 位 0 是字节 0 的 LSB;BitCopydst_bit 是 LSB-first 绝对索引(匹配 Bundle Model
缓冲区模型operator new(51) + memset(buf,0,51)输出字节 N == 缓冲区字节 N(无 header strip)
槽 has-maskTensorCoreBundle proto +0x10 处的 12 位字;每槽一位
可分派槽12(scalar ×2、vector-ALU ×2、vstore、vload、cmem_load、vextended/MXU ×2、vresult ×2、misc)
共享操作数池3× 5 位 Y-register selector @241/246/251 + 6× 16 位 immediate @256/272/288/304/320/338
空槽标记谓词 0x1f(31 = kNeverExecute),由 FillDefaultBundle @ 0x1d222ee0 盖印
MC 交叉检查TPUMCCodeEmitter::encodeInstruction @ 0x13c74885 加载 0x198 = 408 bits = 51 bytes
TpuVersion2 = Pufferfish = TPU v4(第二个宽度来源:PufferfishCodecMetadata::BundleSizeBytes @ 0x1ecf7ac0 返回 51
置信度CONFIRMED(以字节为锚),除非某行另有说明

直接 BitCopy 模型和绝对位法则

Jellyfish 把 bundle 编码为 scratch struct 上的读-改-写算术,然后从中复制出 41 字节窗口。Pufferfish 两者都不做。EncodeBundleInternal 是 codec 外围的五步包装器,反汇编就是证据:

c
// EncoderPfTensorCore::EncodeBundleInternal(TensorCoreBundle const&)  @ 0x1e8c5c40
codec = this->codec;                       // r13 = encoder; codec ptr from this
size  = codec->vtable[0x30]();             // call [rax+0x30] -> 51 (BundleSizeBytes)
buf   = operator new(size);                // _Znwm(51)        @ 0x1e8c5c6d
memset(buf, 0, size);                      // zero the whole buffer @ 0x1e8c5c7d
codec->vtable[0x18](buf, bundle, size);    // call [rax+0x18] -> TensorCoreCodecBase::Encode
result.data = buf; result.size = 51;       // StatusOr<vector<uint8>> of size 51

没有 +0x0C 前移,也没有重叠的 vmovups copy-out——codec 写入的缓冲区就是线缆 bundle。每个字段都直接落在其绝对 bundle 位,因为 codec 就在那里写入:

c
// TensorCore{Slot}Encoder::Encode field write (every slot, every field)
BitCopy(/*dst=*/buf, /*dst_bit=*/ABS_BIT, /*src=*/&proto_field, /*src_bit=*/0, /*nbits=*/WIDTH);

BitCopy0x1fa0a900,mangled _Z7BitCopyPviPKvii)在 rdi 中接收目标缓冲区,在 esi 中接收绝对目标位,在 rdx 中接收源字段指针,在 ecx 中接收源起始位(新字段总是 0),在 r8d 中接收宽度。位索引是 LSB-first:位 0 是缓冲区字节 0 的最低有效位,位 8 是字节 1 的 LSB,依此类推——与编码路径中到处使用的约定相同(见 Bundle Model §bit-numbering)。不存在 MSB-first 顺序,也没有任何需要取反的东西。

注意 — 这是让整个 bundle 可解码的单一事实。要恢复任意字段的位置,反汇编其逐槽编码器,读取每次 call 0x1fa0a900 前面的 mov esi,0xNN(绝对位)和 mov r8d,0xNN(宽度)立即数。下面的槽映射就是从十二个逐槽编码器中的每一对这样的值收割而来。编码侧是权威来源;解码侧 TensorCore{Slot}Decoder::Decode 函数读取相同位,作为反向确认(见 Decode-Side: JF / PF)。

408 位宽度由独立代码路径交叉确认:LLVM MC 层的 TPUMCCodeEmitter::encodeInstruction0x13c74885 加载 mov r15d,0x1980x198 = 408)——这是与 proto codec 分离的发射器,逐位一致。51 字节按绝对位划分为七个 qword:

text
 qword0 = abs   0 .. 63    output bytes 0x00..0x07
 qword1 = abs  64 .. 127   output bytes 0x08..0x0F
 qword2 = abs 128 .. 191   output bytes 0x10..0x17
 qword3 = abs 192 .. 255   output bytes 0x18..0x1F
 qword4 = abs 256 .. 319   output bytes 0x20..0x27
 qword5 = abs 320 .. 383   output bytes 0x28..0x2F
 qword6 = abs 384 .. 407   output bytes 0x30..0x32   (partial, 3 bytes)

字段顶部正好到达 bit 407——scalar_0 谓词位于 abs 403,宽度 5 位——因此顶端的全部 408 位都已覆盖。


十二槽 Has-Bit 分派

编译器侧 TensorCoreBundle proto 在 proto+0x10 的字中携带 12 位槽 has-mask,并在 proto+0x18..+0x70 为每个槽携带一个带类型子消息指针。codec Encode0x1d224300)依次测试每个 has-bit;位被置位时编码存在的子消息,位清除时替换为该槽的 _globals_ 默认实例(通过到默认指针的 cmove)。分派顺序和 bit→slot 映射由 test BYTE/DWORD PTR [r12+0x10],<bit>; cmove rax,<default> 阶梯逐字节精确给出:

c
// TensorCoreCodecBase<...>::Encode dispatch  @ 0x1d224300 (decompiled)
mask = *(uint16_t*)(proto + 0x10);                  // 12-bit slot has-mask @ proto+0x10
for each slot in dispatch order:                    // scalar_0 (0x1) .. misc (0x800)
    sub = (mask & slot.bit) ? *(proto + slot.proto_off)  // present submessage ptr
                            : slot.globals_default;       // _globals_ default
    if (sub == nullptr) sub = slot.globals_default;  // null-ptr also falls back to default
    slot.encoder->Encode(sub, buf);                  // per-slot BitCopy writes

十二个 test [proto+0x10],N 立即数以严格递增顺序出现——0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x100, 0x200, 0x400, 0x800——确认这是没有空洞的密集 12 位 mask。每个槽的编码器写入一个互不相交的专用位区域,因此十二个槽可以在一个 bundle 中共存;操作数池冲突(寄存器/立即数操作数超过 3 个 Y-reg / 6 个 imm 槽容量)由 SlotMap 打包器在 proto 构建时更早解决(见五个共享操作数子编码器)。

怪癖 — scalar_1 的编码取决于 scalar_0 的 opcode,而不纯粹取决于自己的 has-bit。 分派确实按预期测试 scalar_1 has-bit(0x2),但 codec 把整个 scalar_1 编码门控在 (scalar0.opcode_field − 17) >= 3 之后(codec 读取子消息 dword +0x50 处的 scalar_0 opcode oneof,并在它落入 {17,18,19} 时跳过 scalar_1)。这三个 scalar_0 opcode 是会自己消耗 scalar_1 位窗口的宽形式;同时发出第二个 scalar op 会覆盖位。其他每个槽都纯粹由自己的 has-bit 门控。重新实现者必须复现这个 scalar_0→scalar_1 interlock,而不能把两个 SPU 槽视为完全独立。

Has-bitproto+_globals_ default @槽(角色)逐槽编码器 @绝对位(区域)
0x001+0x180x223fd378scalar_0 (SPU)0x1ed16dc0381..407 (27b)
0x002+0x200x223fef38scalar_1 (SPU)0x1ed2a0e0354..380 (27b)
0x004+0x280x22400f90vector_alu_0(VPU,宽 lane0x1ed45060198..240 (43b)
0x008+0x300x224036b8vector_alu_1(VPU,窄 lane)0x1ed68d80167..197 (31b)
0x010+0x380x22411eb0vector_store(mem-store)0x1ee3b440142..166 (25b)
0x020+0x400x22410b88vector_load(mem-load)0x1ee287e0119..140 (22b)
0x040+0x480x223fb410cmem_load(const-mem load,新增)0x1ecf89a0103..118 (16b)
0x080+0x500x22407210vector_extended_0 / MXU00x1edb090083..102 (20b)
0x100+0x580x2240cf50vector_extended_1 / MXU1(−20 twin)0x1ee0806063..82 (20b)
0x200+0x600x22410fd8vector_result_0(EUP-pop lane 0)0x1ee2b1c052..62 (11b)
0x400+0x680x22411428vector_result_1(EUP-pop lane 1)0x1ee2d18041..51 (11b)
0x800+0x700x223fbce8misc(mask / rotate / imm-set)0x1ed0350017..40 (24b)

怪癖 — proto 指针顺序和线上位顺序相反。 has-bit / proto+ 顺序从 scalar_0 到 misc(bit 0x1 到 0x800),但线上位布局反过来:misc 位于 bundle 底部(abs 17),scalar_0 位于最顶部(abs 381..407)。如果重新实现按 has-bit 顺序从低到高把槽放入缓冲区,就会反转整个 bundle。has-bit 是 proto 占用索引;绝对位只来自 BitCopy dst_bit,从不来自槽序号。


三个线上区域

408 位 bundle 不是十二个连续槽字段。它由三个区域组成:

text
 abs   0 ..  16   header / reserved   (not written by any per-slot encoder; buffer stays 0)
 abs  17 .. 240   LOW dedicated region: one disjoint sub-range per non-scalar slot
 abs 241 .. 353   SHARED operand pool: 3 Y-reg selectors + 6 immediate words
 abs 354 .. 407   the two scalar slots (SPU), packed at the TOP

低专用区域按从低到高的顺序不相交地打包十个非标量槽:

text
 abs  17 ..  40 (24b)  misc
 abs  41 ..  51 (11b)  vector_result_1
 abs  52 ..  62 (11b)  vector_result_0
 abs  63 ..  82 (20b)  vector_extended_1 / MXU1
 abs  83 .. 102 (20b)  vector_extended_0 / MXU0
 abs 103 .. 118 (16b)  cmem_load                (NEW in Pufferfish)
 abs 119 .. 140 (22b)  vector_load
 abs 142 .. 166 (25b)  vector_store
 abs 167 .. 197 (31b)  vector_alu_1             (narrow lane)
 abs 198 .. 240 (43b)  vector_alu_0             (WIDE lane, +12b)

因为每个专用子范围都互不相交,所有十二个槽都可以填充在单个 bundle 中。真正相交的不是共享操作数池:三个内存槽和两个 VALU lane 都从同一组 3 个 Y-register selector(abs 241/246/251)和 6 个 immediate word(abs 256/272/288/304/320/338)中提取其寄存器和立即数操作数。这个共享才是限制共发射性的因素——一个 bundle 在所有存在槽合计最多只能命名三个不同的 Y-register 和六个不同的 immediate。

注意 — 有三个小位间隙从未被十二个逐槽编码器中的任何一个写入:前导 17 位(abs 0..16)、bit 141(vector_load 和 vector_store 之间)以及 bits 336..337(immediate-pool 区域内部,imm slot 4 和 imm slot 5 之间)。缓冲区被 memset 为零,因此它们以零发出。abs 0..16 是否携带 bundle-level sequencer/loop prefix(如 SparseCore 序列器 bundle 那样)或纯粹是对齐,尚未隔离——十二个编码器中没有任何 BitCopy 以它为目标。标为 LOW;见尚未钉住的内容


逐槽字段算术

每个逐槽编码器都用 BitCopy(buf, abs_bit, &proto_field, 0, width) 写入字段。下面的位置是直接从每个编码器中 call 0x1fa0a900 站点收割的 esi/r8d 立即数;它们是绝对 bundle 位,没有应用任何变换。

标量槽(abs 354..407)— 0x1ed16dc0(scalar_0),0x1ed2a0e0(scalar_1)

两个标量槽布局相同,scalar_0 正好位于 scalar_1 之上 27 位。Pufferfish 用大约五十个逐 opcode oneof 子消息(TensorCoreScalar0_ScalarMove_ScalarIntAdd_ScalarDmaSimple_ScalarHalt、…)替代 Jellyfish 的单个 ScalarInstruction proto;opcode 字段从其中两个 oneof 分支写出,但总是落在相同基址,标量的 Y/immediate 操作数通过 ScalarYEncode 绑定进共享 immediate 池,不是内联写入标量区域。

字段scalar_0 absscalar_1 abs宽度
operand(Scalar Y / operand)38135411
X reg3863596
opcode3973706
predicate4033765

scalar_0 收割结果字面为:BitCopy(buf,0x193,…,5)(pred @403)、BitCopy(buf,0x18d,…,6)(opcode @397)、BitCopy(buf,0x182,…,6)(X @386)、BitCopy(buf,0x17d,…,11)(operand @381),外加四个 16 位 immediate 写入共享池(abs 288/304/320/338)。

Vector-ALU lane — 0x1ed45060(lane 0,宽),0x1ed68d80(lane 1,窄)

Lane 0 是宽 lane(43 位,abs 198..240);lane 1 是窄 lane(31 位,abs 167..197)。两者都携带一个 6 位 opcode,匹配 56 值 VectorAluOpcode 枚举(见 VPU Slot)。

字段lane0(宽)abslane1(窄)abs宽度
(lane0 第一个操作数)1985
dest2031675
(仅宽 lane 字段区域)208..21912
Vx2201775
y2251725
x21825
opcode2301876
predicate2361935

两个 lane 还会把其寄存器操作数写入共享 Y-register selector(abs 241/246/251),并把 immediate 写入共享池。

怪癖 — 宽 VALU0 lane 有一个 12 位字段区域(abs 208..219),VALU1 没有,而且没有主 opcode 分支写入它。 12 位范围已 CONFIRMED(它正好是 43 位 VALU0 区域与 31 位 VALU1 区域的宽度差),但在已收割的 VALU0 BitCopy 调用中,没有任何 mov esi,0x.. 以 bits 208..219 为目标——它们由逐 opcode oneof 分支(宽专用 op,TensorCoreVectorAlu0_*)写入,而不是公共路径。其角色——第二个向量源、宽 immediate selector 或 vmask/select 操作数——由 lane 宽度差推断,标为 LOW。重新实现者必须保留这 12 位,但应把其含义视为未解决。

Vector-Extended / MXU 槽 — 0x1edb0900(MXU0),0x1ee08060(MXU1)

Pufferfish 把 Jellyfish 的单个 VectorExtended 槽加倍为两个独立 MXU 控制槽。MXU1(abs 63..82)是 MXU0(abs 83..102)逐位相同、偏移正好 −20 位的 twin

字段MXU0 absMXU1 abs宽度
sub-op83633
mode / mxu-num89692
opcode91717
predicate98785

MXU0 收割结果为 BitCopy(buf,0x62,…,5)(pred @98)、BitCopy(buf,0x5b,…,7)(opcode @91)、BitCopy(buf,0x59,…,2)(mode @89)。7 位 opcode 字段选择 matmul / PushGains / transpose 名册;对 matmul,该字段加宽到 9 位(abs 89..97),因此 @89..90 的低两位携带物理 MXU 编号(0..3)。完整 opcode 名册和 −20 twin 记录在 MXU Slot 页面和 Decode-Side: JF / PF 页面中。

Vector-Result 槽 — 0x1ee2b1c0(result_0),0x1ee2d180(result_1)

两个 EUP-result drain lane,每个 11 位。result_0(abs 52..62)正好位于 result_1(abs 41..51)之上 11 位。

字段result_0 absresult_1 abs宽度
which-destination52412
(mode block)54432
valid54431
result-format56452
predicate58475

which-destination 字段把延迟的 transcendental/EUP 结果路由到从共享寄存器引用中选出的目标 VREG(V0_DEST / V1_DEST / VLD_DEST 路由);见 ResultFifoEUP / Transcendental Slot

cmem_load 槽(abs 103..118)— 0x1ecf89a0

一等常量内存加载槽,Pufferfish 新增。其寻址操作数从 proto+0x48 处的寻址子消息读取(与 vector_load 使用同一结构),并用共享内存操作数形状写入。

字段abs bit宽度
sublane-mask1033
base-address1062
offset1082
stride1103
has-bit1131
predicate1145

收割结果为 BitCopy(buf,0x72,…,5)(pred @114)、BitCopy(buf,0x71,…,1)(has @113)、BitCopy(buf,0x6e,…,3)(stride @110)、BitCopy(buf,0x6c,…,2)(offset @108)、BitCopy(buf,0x6a,…,2)(base @106)、BitCopy(buf,0x67,…,3)(sublane-mask @103)。字段名称逐字节匹配 cmem_load Slot 页面。索引/目标寄存器来自共享 Y-register selector(abs 241/246/251),最多四个 16 位 immediate(abs 256/272/288/304)来自共享池。寻址模式和常量内存路径的完整覆盖在 cmem_load Slot 页面中。

vector_load(abs 119..140)— 0x1ee287e0,vector_store(abs 142..166)— 0x1ee3b440,misc(abs 17..40)— 0x1ed03500

vector_load 携带 predicate @136(5b)、addr-mode/oneof discriminator @134(2b)、dest @129(5b)、stride @126(3b)、offset @122(2b)以及 base @134(2b,依模式而定);它有四个寻址模式 oneof 分支(Vmem / Shuffled / Indexed-Iar0 / Indexed-Iar1),复用相同字段基址。vector_store 携带 abs 162/157/152 处的源寄存器字段(各 5b)、abs 149/142 处的 stride/feature 字段(各 3b)、base @145(2b)和 offset @147(2b)。misc 携带 abs 22/25/28 处的三个 3 位字段、@31 的 5 位 sub-op,以及 @36 的 predication(5b)。三者都从共享 Y-register selector 拉取其寄存器操作数,并从共享池拉取 immediate。四种 vector_load 寻址模式的逐模式字段角色差异尚未按分支分离(基址已 CONFIRMED;逐模式语义为 HIGH);见 Memory-LoadMemory-Store


五个共享操作数子编码器

在 Jellyfish 中,内存操作数子字段由五个独立的 EncodeVector{SublaneMask,BaseAddress,Offset,Shuffle,Stride}Encoding 辅助函数写入。Pufferfish 没有独立辅助函数——vector_loadvector_storecmem_load 内联写入同样五种子字段形状,全部从一个公共寻址子消息(proto+0x48,供 vector_load / cmem_load 使用)读取,并写入同一个共享操作数池。五个共享子字段:

子字段宽度绝对位来源 / 角色
base-address2vload @134 · vstore @145 · cmem @106BaseAddressEncoding(ZERO / vs0 / vs1 / vs2)
offset2vload @122 · vstore @147 · cmem @108OffsetEncoding 模式选择器
stride3vload @126 · vstore @142/149 · cmem @110/103stride / feature-length 模式
index/mask register5shared @241 / 246 / 2513 项 Y-register selector SlotMap
index/offset immediate16shared @256 / 272 / 288 / 304 / 320 / 3386 项 immediate SlotMap

操作数来源——给定内存操作数使用三个 Y-register 槽或六个 immediate 槽中的哪一个——不是由编码器决定的。它在更早的 proto 构建层由一组 xla::pufferfish::proto_utils 模板函数绑定,这些函数接收一个 SlotMap<unsigned long, 3>&(三个 Y-register selector)和一个 SlotMap<ImmValue, 6>&(六个 immediate):

c
// xla::pufferfish::proto_utils — operand-source binding (proto-build layer)
VectorYVEncode<Slot>(VregnoOrImm, SlotMap<ulong,3>&, SlotMap<ImmValue,6>&, Slot*);  // vector Y reg-or-imm
VectorYSEncode<Slot>(SregnoOrImm, SlotMap<ulong,3>&, SlotMap<ImmValue,6>&, Slot*);  // vector Y sreg-or-imm
ScalarYEncode<Slot>(SregnoOrImm,  Slot*, SlotMap<ImmValue,6>&);                     // scalar Y immediate
SetImmOrDie<...>(int slot, ImmValue, Slot*);                                        // writes one imm slot
VisitImmediateSlots<0..5>(TensorCoreBundle*, optional<uint>);                       // the 6 imm-slot visitors

打包器把每个存在槽的寄存器/立即数操作数分配到一个空闲 SlotMap 项;如果请求的操作数多于可用槽(整个 bundle 中超过 3 个不同 Y-register,或超过 6 个不同 immediate),绑定会在 proto 构建时被拒绝。这是 Pufferfish 对 Jellyfish 逐 lane FindFreeSlot 搜索的对应物——这里是在一个共享池上做 SlotMap 分配,而不是逐 lane 子消息搜索。

陷阱 — 共享池才是真正的共发射约束,不是专用区域。 若重新实现把每个槽都视为完全独立(因为专用区域互不相交),就会允许例如两个内存槽和两个 VALU lane 各自索要第四个不同 Y-register,并静默覆盖彼此的 selector。专用区域可以自由共存;操作数必须合计装入 3 个 Y-register selector 和 6 个 immediate word。SlotMap 分配器负责强制这一点,并且它在 Encode 之前运行。


kNeverExecute 预填充

不存在的槽必须是定义好的 no-op,而不是垃圾。EncodeBundleInternal 把整个缓冲区清零,而槽默认机制提供 no-op 值:FillDefaultBundle0x1d222ee0)默认构造每个槽的子消息,设置全部十二个 has-bit,并把谓词值 0x1f(31 = kNeverExecute)盖印到每个槽的谓词字段,另外还有一个默认 Scalar0_ScalarHalt op。反汇编直接显示该盖印——mov DWORD PTR [rax+0x1c],0x1fmov DWORD PTR [rax+0x20],0x1f,对每个槽重复一次,写入 proto 谓词字段(取决于槽类型,在子消息偏移 +0x1c 或 +0x20)。

当槽的 has-bit 清除时,codec 会替换为该槽的 _globals_ 默认实例(通过分派阶梯中的 cmove),其谓词字段就是 FillDefaultBundle 盖印的 31。逐槽编码器随后把谓词 31 写入该槽的谓词字段。存在槽的编码器则写入真实的 5 位 Predication 值:0..14 表示 predicate-register 引用,15 表示 kAlwaysExecute,+16 表示取反形式,31 表示 kNeverExecute

陷阱 — 空是 predicate-31,而不是全零,尽管缓冲区初始为零。 缓冲区被 memset 为 0,但 predicate 0 是一个有效 predicate-register 引用,不是“skip”。只填活跃槽并把其余保留为清零默认值的重新实现,会把每个空槽变成由 predicate register 0 门控的活 op。空槽值是显式的 0x1f 盖印,与 Jellyfish 的空槽编码相同。见 NOP / Unused-Slot Canonical EncodingPredicate Slot


Jellyfish → Pufferfish 差异

Pufferfish 把 bundle 从 41 字节增长到 51 字节(+10 字节 / +80 位),并改变了编码器机制。四项 datapath 增加和一项机制变化全部逐字节精确:

变化JellyfishPufferfish位成本
编码器机制scratch struct + shl/and/or + 12-byte strip51-byte buffer + BitCopy absolute-bit, no strip
第 2 个 MXU一个 VectorExtended 槽MXU0(abs 83..102)+ MXU1(abs 63..82),−20 twin+20b
宽 VALU lane两个近似对称的 VALU laneVALU0 宽(43b)+ VALU1 窄(31b)+12b
cmem_load 槽一等 const-mem load(abs 103..118)+16b
第 2 个 result 槽一个 VectorResultresult_0(abs 52..62)+ result_1(abs 41..51)+11b
操作数子编码器5 个独立辅助函数5 个内联 BitCopy 写入一个共享池(更大的池)

机制变化更为深层。Jellyfish 的编码器是单体且与特定个性绑定的;Pufferfish 是一个泛型 TensorCoreCodecBase 模板,以十二对 {Slot}Decoder/{Slot}Encoder 加上一个 Predication 策略为参数。相同的 codec 模板配合不同槽列表(四个 VALU lane、两个 load、vector-scalar + DMA 槽)就是下一代 GXC codec——Pufferfish 是贯穿到 V6+ 的 codec 设计在 v4 的起点。

怪癖 — 第二个 MXU 由 opcode 字段选择,而不是由 bundle 槽选择,尽管存在两个 MXU Pufferfish 在 bundle 中有两个 MXU 控制槽(MXU0/MXU1,即 −20 twin),并且有四个物理 MXU array。两者正交:matmul op 的 9 位 opcode 字段在其低两位中携带物理 MXU 编号(0..3)(对 MXU0 为 abs 89..90),所以 bundle 槽选择控制 lane,而 opcode 选择物理 array。重新实现者不能把“哪个 MXU 槽”与“四个 MXU 中的哪一个”混为一谈。


HBM / DMA 成帧

51 字节宽度是发射宽度。存储在 HBM 中时,每个 bundle 由以 (TpuVersion, TpuSequencerType) 为键的共享 codec-metadata 表成帧,而不是由硬编码 Pufferfish 常量成帧。EncoderPfTensorCore 上的成帧辅助函数全部委托给该表:

辅助函数@ addr结果 / 委托
BundleSizeBytes()0x1d2277400x33 = 51(线上发射宽度)
BundleSizeBytesForDma()0x1d227760读取版本 [encoder+0x8] → tail-jumps codec_metadata::BundleSizeBytesForHbm(ver, seqtype) @ 0x1ecf71a0
HasCheckByteForDma()0x1d227780codec_metadata::HasCheckByteForHbm @ 0x1ecf71c0
BundleCheckByte()0x1d2277a0codec_metadata::BundleCheckByte @ 0x1ecf71e0
BundleCheckByteMask()0x1d2277c0codec_metadata::BundleCheckByteMask @ 0x1ecf7200
DmaEncodingBytesRequired(n)0x1d2277e0n + BundleSizeBytesForHbm()(逐 bundle 步长)
MinimumBundlesRequiredToEncodeToDma()0x1d2279200xa = 10
EncodeProgramForHbmInternal0x1e8c5ce0薄包装器 → codec vtable [+0x20],逐 bundle 循环遍历 TensorCoreProgram.bundles

注意 — codec-metadata 表位于 platforms_deepsea::jellyfish::isa::codec_metadata,并在各代之间共享——Pufferfish(TpuVersion = 2)是 (TpuVersion, TpuSequencerType) 查找的一行,因此它的 HBM 步长和 check-byte 不是编译进 EncoderPfTensorCore 的。精确的逐 bundle 成帧字节(check byte 和任何 pad)由程序级 EncodeProgramForHbmInternal 写入,本页没有单独钉住。


尚未钉住的内容

  • VALU0 12 位字段区域(abs 208..219)。 范围 CONFIRMED,角色 LOW——由 TensorCoreVectorAlu0_* 逐 opcode oneof 分支写入,而不是公共路径;第二个向量源、宽 immediate selector 或 vmask 是推断的候选集合。
  • header / reserved 位(abs 0..16、bit 141、abs 336..337)。 十二个逐槽编码器均未写入;以零发出。abs 0..16 是 bundle-level sequencer prefix 还是纯对齐为 LOW。
  • 逐 opcode oneof 子消息名册。 Pufferfish 用大约五十个带类型 oneof 子消息替代每个 scalar/VALU/MXU opcode 字段;opcode-field 位基址+宽度已 CONFIRMED,但 enum-value → oneof 映射是一个单独(大型)交付物,本页未枚举。
  • 四个 vector_load 寻址模式分支。 共享字段基址已 CONFIRMED;逐模式角色拆分(Vmem / Shuffled / Indexed-Iar0 / Indexed-Iar1,由 2 位 oneof @134 区分)为 HIGH。

交叉引用

  • Jellyfish 41B Bundle — 本页相对其做差异说明的 v3 前身:scratch-struct / 12-byte-strip 编码器、slot_mask@proto+0x10 分派,以及 Pufferfish 加倍或加宽的单 MXU / VALU / result 槽。
  • Bundle Model — 每代 bundle 宽度(41 / 51 / 64)以及本页为 Pufferfish 实例化的槽分类。
  • cmem_load Slot — 新的 v4 常量内存加载槽(abs 103..118)及其完整寻址子消息。
  • MXU Slot — vector-extended/MXU opcode 名册、−20 twin,以及 7 位 opcode @91/71 所选择的 matmul / PushGains / transpose 家族。
  • MXU Latency: Pufferfish — 消费此处解码的 MXU 槽字段的成本模型。
  • Decode-Side: JF / PF — 读取同样这些位的反向 TensorCore{Slot}Decoder::Decode 路径,是每个槽映射基址的独立确认。
  • Record Format — 239-bit MC APInt;为什么 proto codec 路径不同于交叉确认 408 位宽度的 MC 层。
  • MC-EmitterTPUMCCodeEmitter::encodeInstruction 以及独立固定 bundle 宽度的 0x198 = 408-bit 常量。