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 strip。EncoderPfTensorCore::EncodeBundleInternal(0x1e8c5c40)是一个薄包装器:它向 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 分派:
TensorCoreBundleproto+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预填充,由FillDefaultBundle(0x1d222ee0)盖印到每个槽的谓词字段,以及 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) @ 0x1fa0a900 — dst_bit == 绝对 bundle 位 |
| 线缆宽度 | 51 字节 / 408 位(EncoderPfTensorCore::BundleSizeBytes @ 0x1d227740 返回 0x33) |
| 位编号 | LSB-first — 位 0 是字节 0 的 LSB;BitCopy 的 dst_bit 是 LSB-first 绝对索引(匹配 Bundle Model) |
| 缓冲区模型 | operator new(51) + memset(buf,0,51);输出字节 N == 缓冲区字节 N(无 header strip) |
| 槽 has-mask | TensorCoreBundle 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 |
| TpuVersion | 2 = Pufferfish = TPU v4(第二个宽度来源:PufferfishCodecMetadata::BundleSizeBytes @ 0x1ecf7ac0 返回 51) |
| 置信度 | CONFIRMED(以字节为锚),除非某行另有说明 |
直接 BitCopy 模型和绝对位法则
Jellyfish 把 bundle 编码为 scratch struct 上的读-改-写算术,然后从中复制出 41 字节窗口。Pufferfish 两者都不做。EncodeBundleInternal 是 codec 外围的五步包装器,反汇编就是证据:
// 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 就在那里写入:
// 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);BitCopy(0x1fa0a900,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::encodeInstruction 在 0x13c74885 加载 mov r15d,0x198(0x198 = 408)——这是与 proto codec 分离的发射器,逐位一致。51 字节按绝对位划分为七个 qword:
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 Encode(0x1d224300)依次测试每个 has-bit;位被置位时编码存在的子消息,位清除时替换为该槽的 _globals_ 默认实例(通过到默认指针的 cmove)。分派顺序和 bit→slot 映射由 test BYTE/DWORD PTR [r12+0x10],<bit>; cmove rax,<default> 阶梯逐字节精确给出:
// 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-bit | proto+ | _globals_ default @ | 槽(角色) | 逐槽编码器 @ | 绝对位(区域) |
|---|---|---|---|---|---|
0x001 | +0x18 | 0x223fd378 | scalar_0 (SPU) | 0x1ed16dc0 | 381..407 (27b) |
0x002 | +0x20 | 0x223fef38 | scalar_1 (SPU) | 0x1ed2a0e0 | 354..380 (27b) |
0x004 | +0x28 | 0x22400f90 | vector_alu_0(VPU,宽 lane) | 0x1ed45060 | 198..240 (43b) |
0x008 | +0x30 | 0x224036b8 | vector_alu_1(VPU,窄 lane) | 0x1ed68d80 | 167..197 (31b) |
0x010 | +0x38 | 0x22411eb0 | vector_store(mem-store) | 0x1ee3b440 | 142..166 (25b) |
0x020 | +0x40 | 0x22410b88 | vector_load(mem-load) | 0x1ee287e0 | 119..140 (22b) |
0x040 | +0x48 | 0x223fb410 | cmem_load(const-mem load,新增) | 0x1ecf89a0 | 103..118 (16b) |
0x080 | +0x50 | 0x22407210 | vector_extended_0 / MXU0 | 0x1edb0900 | 83..102 (20b) |
0x100 | +0x58 | 0x2240cf50 | vector_extended_1 / MXU1(−20 twin) | 0x1ee08060 | 63..82 (20b) |
0x200 | +0x60 | 0x22410fd8 | vector_result_0(EUP-pop lane 0) | 0x1ee2b1c0 | 52..62 (11b) |
0x400 | +0x68 | 0x22411428 | vector_result_1(EUP-pop lane 1) | 0x1ee2d180 | 41..51 (11b) |
0x800 | +0x70 | 0x223fbce8 | misc(mask / rotate / imm-set) | 0x1ed03500 | 17..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 不是十二个连续槽字段。它由三个区域组成:
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低专用区域按从低到高的顺序不相交地打包十个非标量槽:
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 abs | scalar_1 abs | 宽度 |
|---|---|---|---|
| operand(Scalar Y / operand) | 381 | 354 | 11 |
| X reg | 386 | 359 | 6 |
| opcode | 397 | 370 | 6 |
| predicate | 403 | 376 | 5 |
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(宽)abs | lane1(窄)abs | 宽度 |
|---|---|---|---|
| (lane0 第一个操作数) | 198 | — | 5 |
| dest | 203 | 167 | 5 |
| (仅宽 lane 字段区域) | 208..219 | — | 12 |
| Vx | 220 | 177 | 5 |
| y | 225 | 172 | 5 |
| x2 | — | 182 | 5 |
| opcode | 230 | 187 | 6 |
| predicate | 236 | 193 | 5 |
两个 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 abs | MXU1 abs | 宽度 |
|---|---|---|---|
| sub-op | 83 | 63 | 3 |
| mode / mxu-num | 89 | 69 | 2 |
| opcode | 91 | 71 | 7 |
| predicate | 98 | 78 | 5 |
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 abs | result_1 abs | 宽度 |
|---|---|---|---|
| which-destination | 52 | 41 | 2 |
| (mode block) | 54 | 43 | 2 |
| valid | 54 | 43 | 1 |
| result-format | 56 | 45 | 2 |
| predicate | 58 | 47 | 5 |
which-destination 字段把延迟的 transcendental/EUP 结果路由到从共享寄存器引用中选出的目标 VREG(V0_DEST / V1_DEST / VLD_DEST 路由);见 ResultFifo 和 EUP / Transcendental Slot。
cmem_load 槽(abs 103..118)— 0x1ecf89a0
一等常量内存加载槽,Pufferfish 新增。其寻址操作数从 proto+0x48 处的寻址子消息读取(与 vector_load 使用同一结构),并用共享内存操作数形状写入。
| 字段 | abs bit | 宽度 |
|---|---|---|
| sublane-mask | 103 | 3 |
| base-address | 106 | 2 |
| offset | 108 | 2 |
| stride | 110 | 3 |
| has-bit | 113 | 1 |
| predicate | 114 | 5 |
收割结果为 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-Load 和 Memory-Store。
五个共享操作数子编码器
在 Jellyfish 中,内存操作数子字段由五个独立的 EncodeVector{SublaneMask,BaseAddress,Offset,Shuffle,Stride}Encoding 辅助函数写入。Pufferfish 没有独立辅助函数——vector_load、vector_store 和 cmem_load 内联写入同样五种子字段形状,全部从一个公共寻址子消息(proto+0x48,供 vector_load / cmem_load 使用)读取,并写入同一个共享操作数池。五个共享子字段:
| 子字段 | 宽度 | 绝对位 | 来源 / 角色 |
|---|---|---|---|
| base-address | 2 | vload @134 · vstore @145 · cmem @106 | BaseAddressEncoding(ZERO / vs0 / vs1 / vs2) |
| offset | 2 | vload @122 · vstore @147 · cmem @108 | OffsetEncoding 模式选择器 |
| stride | 3 | vload @126 · vstore @142/149 · cmem @110/103 | stride / feature-length 模式 |
| index/mask register | 5 | shared @241 / 246 / 251 | 3 项 Y-register selector SlotMap |
| index/offset immediate | 16 | shared @256 / 272 / 288 / 304 / 320 / 338 | 6 项 immediate SlotMap |
操作数来源——给定内存操作数使用三个 Y-register 槽或六个 immediate 槽中的哪一个——不是由编码器决定的。它在更早的 proto 构建层由一组 xla::pufferfish::proto_utils 模板函数绑定,这些函数接收一个 SlotMap<unsigned long, 3>&(三个 Y-register selector)和一个 SlotMap<ImmValue, 6>&(六个 immediate):
// 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 值:FillDefaultBundle(0x1d222ee0)默认构造每个槽的子消息,设置全部十二个 has-bit,并把谓词值 0x1f(31 = kNeverExecute)盖印到每个槽的谓词字段,另外还有一个默认 Scalar0_ScalarHalt op。反汇编直接显示该盖印——mov DWORD PTR [rax+0x1c],0x1f 和 mov 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 Encoding 和 Predicate Slot。
Jellyfish → Pufferfish 差异
Pufferfish 把 bundle 从 41 字节增长到 51 字节(+10 字节 / +80 位),并改变了编码器机制。四项 datapath 增加和一项机制变化全部逐字节精确:
| 变化 | Jellyfish | Pufferfish | 位成本 |
|---|---|---|---|
| 编码器机制 | scratch struct + shl/and/or + 12-byte strip | 51-byte buffer + BitCopy absolute-bit, no strip | — |
| 第 2 个 MXU | 一个 VectorExtended 槽 | MXU0(abs 83..102)+ MXU1(abs 63..82),−20 twin | +20b |
| 宽 VALU lane | 两个近似对称的 VALU lane | VALU0 宽(43b)+ VALU1 窄(31b) | +12b |
| cmem_load 槽 | 无 | 一等 const-mem load(abs 103..118) | +16b |
| 第 2 个 result 槽 | 一个 VectorResult | result_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() | 0x1d227740 | 0x33 = 51(线上发射宽度) |
BundleSizeBytesForDma() | 0x1d227760 | 读取版本 [encoder+0x8] → tail-jumps codec_metadata::BundleSizeBytesForHbm(ver, seqtype) @ 0x1ecf71a0 |
HasCheckByteForDma() | 0x1d227780 | → codec_metadata::HasCheckByteForHbm @ 0x1ecf71c0 |
BundleCheckByte() | 0x1d2277a0 | → codec_metadata::BundleCheckByte @ 0x1ecf71e0 |
BundleCheckByteMask() | 0x1d2277c0 | → codec_metadata::BundleCheckByteMask @ 0x1ecf7200 |
DmaEncodingBytesRequired(n) | 0x1d2277e0 | n + BundleSizeBytesForHbm()(逐 bundle 步长) |
MinimumBundlesRequiredToEncodeToDma() | 0x1d227920 | 0xa = 10 |
EncodeProgramForHbmInternal | 0x1e8c5ce0 | 薄包装器 → 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-Emitter —
TPUMCCodeEmitter::encodeInstruction以及独立固定 bundle 宽度的0x198= 408-bit 常量。