Skip to content

Jellyfish 41 字节束

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

摘要

Jellyfish TensorCore VLIW 束是一个 **41 字节(328 位)**发射字。它是 libtpu 仍会编码的最老 TensorCore 束,Pufferfish、Viperfish、Ghostlite 以及 gen-5 6acc60406 编解码器的逐代束页面最适合当作相对于它的差异来阅读。与 V5+ 代际不同,后者构建全零 MC 记录并完全通过 BitCopy 汇编到共享 span 中(见 记录格式);Jellyfish 是直接打包编码器:EncoderJf::EncodeBundleInternal0x1e86c7c0)通过在 53 字节内部 scratch struct 的 qword 上执行 read-modify-write shl/and/or 算术来构建束,然后剥离前 12 字节并拷出 41 字节尾部。没有逐指令记录,也没有 BitCopy;每个字段的位置都是其槽编码器中的字面 shift 常量。

三个性质使该束仅凭二进制就可解码。第一,12 字节剥离定律:编码器在 53 字节 struct 中工作,成功时将 struct[0x0C .. 0x34] 复制为线格式束,因此 output byte N == internal-struct byte (0x0C + N),任意字段的绝对束位是 struct_byte*8 + shift − 96。第二,槽掩码分派Bundle proto 在 proto+0x10 携带 32 位 slot_mask,每个槽一位,EncodeBundleInternal 测试每一位来调用匹配的逐槽 Encode* writer。第三,kNeverExecute 预填充:在任何槽被填充之前,编码器将谓词值 31 戳入每个槽的谓词字段,因此缺失槽是定义明确的 no-op,而不是垃圾。

本页以绝对位精度记录完整槽位图、EncodeBundleInternal 打包顺序、逐槽字段算术、空槽预填充,以及把单个 LLO op 折叠进束槽的 MakeInstruction / FindFreeSlot VLIW 构造模型。对于重新实现,契约是:

  • 53 字节 scratch / 41 字节线格式关系,以及将任意逐槽 shift 转换为绝对束位的 output_byte N == struct_byte 0x0C+N 定律。
  • slot-mask → 逐槽 writer 分派:九个可分派槽、六个 16 位立即数槽和 TTU 路径,全都以 proto+0x10 为键。
  • 束的五个物理 word 区域,以及哪些槽通过划分一个 64 位 word 的不相交部分而共存。
  • kNeverExecute=31 预填充约定,以及每个槽携带的 5 位谓词字段。
  • FindFreeSlot 折叠:发射器如何将 LLO op 分配到空闲且合法的槽,并标记 slot_mask 位。
代际TpuVersion 0 = kJellyfish;外部显示名 TPU v2;内部代号 jellyfish(二进制中不存在 Trillium 字符串)— 见 代号矩阵
编码入口EncoderJf::EncodeBundleInternal(Bundle const&, bool) @ 0x1e86c7c0
编码器工厂tpu::internal::CreateEncoderJfDf @ 0x1e835b80(同时构建 JF 和 DF 编码器)
线格式宽度41 bytes / 328 bitsJellyfishCodecMetadata::BundleSizeBytes @ 0x1ecf7460 返回 41;缓冲区由 EncodeBundleInternal 中的 operator new(0x29) 硬固定)
内部 scratch53 bytes;线格式 = struct[0x0C..0x34]output_byte N == struct_byte 0x0C+N
槽掩码Bundle proto +0x10 处的 32 位 word;每槽一位
可分派槽9(scalar ×2、vector-ALU ×2、vstore、vload、vextended/MXU、vresult、misc)+ 6 imm + TTU
空槽标记HardwareBundleBits::kNeverExecute = 310xB834CFC),位于每个槽的 5 位谓词字段
HBM 存储宽度42 bytes(BundleSizeBytesForHbm @ 0x1ecf74c0);每 128 字节 DMA chunk 含 3 个束
共享者Dragonfish(EncoderDf 继承此 EncodeBundleInternal)— 见 Dragonfish 束

12 字节剥离和绝对位定律

EncodeBundleInternal 并不在 41 字节缓冲区中构建束。它在更大的内部 scratch struct(编码器的工作对象,通过 r15/this 寻址)中构建,将每个槽写入 struct 相对字节偏移,然后在成功路径中从 struct 拷贝出一个 41 字节窗口作为线格式束。

成功路径就是证明。在所有存在的槽都编码后,编码器将 struct 指针前移 0x0C 并复制 41(0x29)字节:

c
// EncoderJf::EncodeBundleInternal success path  @ 0x1e86c7c0 (lines 849-857)
buf = operator new(0x29);                 // 41-byte heap buffer
src = (char*)struct + 12;                 // strip the 12-byte header
// vmovups ymm0=[src]      ; ymm1=[src+9]   -> [buf]/[buf+9]   (overlapping 32+9 = 41 B)
memcpy(buf, src, 41);
result.data = buf;  result.size = 0x29;   // StatusOr<vector<uint8>> of size 41

两个 vmovups 分别复制字节 [src .. src+32)[src+9 .. src+41);合在一起覆盖全部 41 字节(9 + 32 = 41)。因此线格式束正好是 struct[0x0C .. 0x34]

注意 — 位编号约定。 本页上的每个绝对位位置都是 LSB-first,与 束模型 一致:bit 0 是输出 byte 0 的最低有效位,左移 S 的字段((value & mask) << S)占据 bit S 及以上。Jellyfish 直接打包编码器通过其 shl/or shift 常量使用这一约定;编码路径中没有任何 MSB-first / big-endian 排列。由 (value & mask) << S 写入 byte offset B 处 struct qword 的字段因此落在绝对束位 B*8 + S − 96(因为 0x0C * 8 = 96)。当 B == 0x0C 时,−96 抵消,绝对位就等于 shift 本身,这就是 qword-0 预填充常量按原样读作其束位位置的原因。

41 字节按绝对位划分成五个 qword:

text
 qword0 = abs   0 .. 63    (struct 0x0C..0x13)   output bytes 0x00..0x07
 qword1 = abs  64 .. 127   (struct 0x14..0x1B)   output bytes 0x08..0x0F
 qword2 = abs 128 .. 191   (struct 0x1C..0x23)   output bytes 0x10..0x17
 qword3 = abs 192 .. 255   (struct 0x24..0x2B)   output bytes 0x18..0x1F
 qword4 = abs 256 .. 327   (struct 0x2C..0x34)   output bytes 0x20..0x28 (partial, 9 B)

槽掩码分派

编译器侧 Bundle 是一个 proto,在 proto+0x10 带有 32 位 slot_mask,并为每个槽带有一个类型化子消息指针。EncodeBundleInternal 将掩码读入寄存器一次,测试每一位,并在位被置位时用子消息指针调用该槽的逐槽 writer。未置位的位让槽保持其预填充的 kNeverExecute 值。

c
// EncodeBundleInternal slot dispatch  @ 0x1e86c855.. (decompiled, lines 110-270)
mask = bundle->slot_mask;                 // *(uint32_t*)(proto + 0x10)
if (mask & 0x001) EncodeScalarInstruction(scalar0 = proto+0x18, /*lane=*/0);  // vtable +120
if (mask & 0x002) EncodeScalarInstruction(scalar1 = proto+0x20, /*lane=*/1);  // vtable +120
if (mask & 0x008) EncodeVectorAluInstruction(valu0 = proto+0x30, /*lane=*/0); // 0x1e864f00
if (mask & 0x010) EncodeVectorAluInstruction(valu1 = proto+0x38, /*lane=*/1); // 0x1e864f00
if (mask & 0x020) EncodeVectorStoreInstruction(vstore = proto+0x40);          // 0x1e868c40
if (mask & 0x040) EncodeVectorLoadInstruction(vload  = proto+0x48);           // 0x1e867340
if (mask & 0x080) EncodeVectorExtendedInstruction(vext = proto+0x50);         // vtable +136
if (mask & 0x100) EncodeVectorResultInstruction(vresult = proto+0x58);        // vtable +128
if (mask & 0x200) EncodeMiscInstruction(misc = proto+0x60);                   // vtable +144
// then the six 16-bit immediate slots, validated < 0x10000:
for bit in {0x8000,0x4000,0x2000,0x1000,0x800,0x400}:
    if (mask & bit) ValidateImmediate(proto[+0x7C..+0x68]);                   // 0x1e86da20

bit 到槽的映射来自反编译的 if ((mask & N) != 0) 梯形结构,按字节精确;每个 writer 都在列出的 proto+ 偏移读取其子消息指针(空指针回退到逐类型 _globals_ default instance)。同一个 proto+0x10 word 也被 ProtoUtils::GetPopulatedSlots0x1e875be0)用相同的 bit 测试读取,它是规范的束占用 bitfield。

slot_maskproto+槽(角色)逐槽 writerStruct word
0x0001+0x18scalar_0(SPU lane0 / 定序器 lane)EncodeScalarInstruction(lane=0) @ 0x1e8620600x2D(qword4)
0x0002+0x20scalar_1(SPU lane1)EncodeScalarInstruction(lane=1) @ 0x1e8620600x2D(qword4)
0x0008+0x30vector_alu_0(VPU lane0)EncodeVectorAluInstruction(lane=0) @ 0x1e864f000x1D(qword2)
0x0010+0x38vector_alu_1(VPU lane1)EncodeVectorAluInstruction(lane=1) @ 0x1e864f000x16 window
0x0020+0x40vector_store(mem-store)EncodeVectorStoreInstruction @ 0x1e868c400x14 + 0x16
0x0040+0x48vector_load(mem-load)EncodeVectorLoadInstruction @ 0x1e8673400x0C + 0x1F
0x0080+0x50vector_extended(MXU / EUP)EncodeVectorExtendedInstruction @ 0x1e869f000x0C + 0x16
0x0100+0x58vector_result(matres / EUP-pop)EncodeVectorResultInstruction @ 0x1e865ae00x0C
0x0200+0x60misc(mask / rotate / imm-set)EncodeMiscInstruction @ 0x1e86be800x0C
0x0400..0x8000+0x68..+0x7Cimmediate 槽 imm0..imm5(每个 16 位)ValidateImmediate @ 0x1e86da20imm 区域 0x1F/0x27
(0x0004)TTU 操作数EncodeTtuOperands @ 0x1e8632800x2D + 0x2B(借用 scalar0)

怪异点 — 标量槽是 lane-aware 的,两个 lane 位于同一 qword 的不同位基址。 EncodeScalarInstruction 接收 lane 实参(0 或 1),并将 scalar_0 写入 qword4 的位(opcode @abs311),将 scalar_1 写入位(opcode @abs284)。若重新实现假设“槽 0 是低位、槽 1 是高位”,就会颠倒两个标量 lane。相同的 lane 拆分也适用于两个 vector-ALU lane,它们位于两个不同 struct word 中(lane0 在 word 0x1D,lane1 在 0x16 处的 56 位窗口),而不是同一个 word 中的相邻字段。


五个物理 Word 区域

41 字节束不是九个连续槽字段。若干槽共享一个 64 位 word,并通过占据其不相交位子范围共存;另一些则散布在多 word 操作数窗口中。五个区域:

text
 struct 0x0C  -> qword0 (abs 0..63)    Misc | VectorResult | VectorExtended(MXU) | VectorLoad
                                        (four slots packed disjointly into one qword)
 struct 0x14  -> qword1 low (abs 64..)  VectorStore source/control
 struct 0x16  -> 56-bit window (abs 90..127)  VALU lane1 + VStore/VExtended operand cross-fields
 struct 0x1D  -> abs 136..167          VALU lane0
 struct 0x1F/0x27 -> abs ~152..223     immediate-value bytes
 struct 0x2D  -> qword4 (abs 264..327) scalar_0 / scalar_1 (SPU) + TTU operands

qword0 中四个槽的打包是最重要的结构事实:Misc(abs 5..17)、VectorResult(abs 18..26)、VectorExtended/MXU(abs 27..39)和 VectorLoad(abs 40..62)各自拥有不相交子范围,因此一个束可以同时携带全部四个槽。struct 0x16 处的 56 位窗口是真正的跨 word 字段:编码器将其组装为 dword[0x16] | (word[0x1A] << 32) | (byte[0x1C] << 48),它容纳 VALU lane1 以及 VectorStore 和 VectorExtended 的操作数溢出部分;这三者受束打包器相互约束,并非彼此独立。

陷阱 — qword0 共存是真实的,但 0x16 窗口是共享的。 Misc / VResult / VExtended / VLoad 确实可以共存(qword0 中位范围不相交),但 VALU-lane1、VectorStore 和 VectorExtended 操作数都会伸入 struct 0x16 处的同一个 56 位窗口。把每个槽都当作完全独立的打包器,会允许一个 VALU-lane1 op 和一个宽 VectorStore 同时占用窗口位并静默破坏彼此。FindFreeSlot 中的槽合法性谓词正是用于防止这种情况。


逐槽字段算术

每个逐槽 writer 都通过 (field & mask) << shift 将字段 OR 到一个 struct qword 中。下面的 shift 是相对于 struct word 的;加上 struct_byte*8 − 96 即可得到绝对束位(对于 qword0 / struct 0x0C,shift 就是绝对位)。每个位置都直接读取自反编译编码器中的 shl/and/or 三元组。

Scalar(struct 0x2D,qword4)— EncodeScalarInstruction @ 0x1e862060

lane-0(定序器)lane 位于高位,lane-1 位于低位。反编译结果(lines 240-253):lane1 走 (... & 0x3F) << 20 / pred << 26 分支(clear-masks 0x...FC0FFFFF / 0x...83FFFFFF);lane0 走 << 47 / pred << 53 分支(clear-masks 0xFFE07F... / 0xFC1F...)。

字段proto 源lane0 shift → abslane1 shift → abs宽度
predicateEncodePredication & 0x1F<<53 → 317<<26 → 2905
opcode[instr+0x50] & 0x3F<<47 → 311<<20 → 2846
X regScalarBinaryOperands+0x20 & 0x1F<<31 → 295<<4 → 2685
Y reg+0x1C & 0x1F<<42 → 306<<15 → 2795
ScalarY+0x18 & 0x3F<<36 → 300<<9 → 2736

opcode 跳转表覆盖 ScalarOpcode 0..0x37;立即数 Y 常量通过 EncodeImmediateValueForScalarYEncoding0x1e85f3a0)路由到立即数区域。定序器 lane(lane0)opcode 在 abs 311;lane-1 opcode 在 abs 284。

Vector-ALU lane — EncodeVectorAluInstruction @ 0x1e864f00

Lane0 打包到 struct 0x1D 处的 16 位 word;lane1 打包到 struct 0x16 处的 56 位窗口。反编译结果(lines 80-128):lane0 pred << 11(clear & 0x7FF)和 opcode << 5(clear 0xF81F);lane1 pred << 36Vx << 25(clear 0xC1FFFFFF)、y << 10(clear 0xFFFF83FF)。

字段proto 源Lane0(word 0x1D)absLane1(window 0x16)abs宽度
Vx[instr+0x58] & 0x1F<<0 → 136<<25 → 1055
opcode[instr+0x50] & 0x3F<<5 → 141<<30 → 1106
predicate& 0x1F<<11 → 147<<36 → 1165
y[instr+0x60] & 0x1F(溢出到 0x16<<10 → 905
dest[instr+0x60] & 0x1F<<41/<<51(溢出)<<41 → 1215

opcode 为 6 位(VectorAluOpcode 0..62)。Opcode 0x18(LANE_ID)和 EUP 范围 0x30..0x34 走专用分支;IsEupOpcode0x1e875900)在束提交前门控是否需要 EUP/XLU 保留。

注意 — lane-0 dest 字段由共享 y/dest 分支(struct 0x16 shl 0x29/shl 0x33)写入,而不是由专用具名字段写入。lane-1 窗口完全隔离(y@90, Vx@105, opcode@110, pred@116, dest@121);解码侧独立佐证了 lane-1 布局 — 见 解码侧:JF / PF

Vector-Extended / MXU(struct 0x0C)— EncodeVectorExtendedInstruction @ 0x1e869f00

反编译结果(lines 49-76):pred << 35(clear 0xFFFFFF07FFFFFFFF)、mxu_id << 27(clear 0xFFFFFFFFE7FFFFFF)、has-bit | 0x20000000(bit 29),以及由 0xFFFFFFF81FFFFFFF 清除的 opcode 字段(bits 29..34)。

字段proto 源abs bit宽度
predicateEncodePredication & 0x1F355
opcode(6 位 VectorExtendedOpcode29..346
mxu-id[instr+0x64] & 327..282
has-bit| 0x20000000291
operands[instr+0x6C]<<46/<<150x16 窗口;byte 0x14 <<11

这里的 opcode clear-mask 0xFFFFFFF81FFFFFFF 与解码器用于提取 abs 29..34 处 6 位 opcode 的掩码相同,因此编码和解码侧逐位一致。opcode 空间是 {matmul 0..6, latch/PushGains 7..12, transpose/RPU 13..34};完整名册和两级跳转表解码见 MXU 槽解码侧:JF / PF

Vector-Result / matres(struct 0x0C)— EncodeVectorResultInstruction @ 0x1e865ae0

字段proto 源abs bit宽度
predicate& 0x1F <<22225
result-format[instr+0x40] & 3 <<20202
result-mode[instr+0x44] & 3 <<18182
dest-Vreg[instr+0x48] & 0x1F取决于 mode:<<10 / <<41 / <<515

目标寄存器的位位置由 which_destination 子形式(值从哪个 EUP/result 目标流出)选择。结果有效路由在 ResultFifo 页面中介绍。

Vector-Load(struct 0x0C + Vs 端口 0x1F)— EncodeVectorLoadInstruction @ 0x1e867340

字段proto 源abs bit宽度
predicate& 0x1F <<58585
opcode(addr-mode)[instr+0x50] & 3 <<56562
has-bit[instr+0x60]!=0| (1<<40)401
destVreg[instr+0x64] & 0x1F <<51515
stride[instr+0x54] & 7 <<48483
offset[instr+0x58] & 3 <<46462
base[instr+0x5C] & 3 <<44442
vs2 / vs1[target+0x88] & 0x1F <<10, [target+0x84] & 0x1F <<5word 0x1F每个 5

abs 56 处的 2 位 opcode 选择 {VmemLoad, VmemLoadShuffled, VmemLoadIndexedIar0, VmemLoadIndexedIar1}。Vs base/index 端口和 sublane-mask 子字段通过 EncodeVectorSublaneMaskEncoding0x1e867840)路由。这些位置补上了一个长期缺口:VectorLoad/Store 字段布局在这里由 proto-path 编码器按字节精确写出,而不是隐藏在 InstBits 行中。见 内存加载

Vector-Store(struct 0x14 + 0x16)— EncodeVectorStoreInstruction @ 0x1e868c40

将源 vreg(11 位打包)写入 word 0x14 的 abs 75,将 predication 写入 0x16 跨 word 的 abs 85,设置 presence bit or [struct+0x13], 0x80,并将 base/offset 写入 word 0x2D。sublane/stride 子字段通过 <<8/<<6/<<4 落入 word 0x14。source(abs 75)、predication(abs 85)和 presence bit 已确认;sublane/stride 子字段已定位,但尚未各自固定到绝对宽度(HIGH)。见 内存存储

Misc(struct 0x0C)— EncodeMiscInstruction @ 0x1e86be80

字段proto 源abs bit宽度
predication& 0x1F <<13135
operand[instr+0x40] <<88
sub-op[instr+0x18]/[+0x1C] <<5(按 MiscOperandEncoding5

kNeverExecute 预填充

在编码任何槽之前,EncodeBundleInternal 用两个 ymm store 将槽区域清零,然后将谓词值 kNeverExecute = 310xB834CFC)戳入每个槽的 5 位谓词字段。相关 prologue(反编译,lines 98-109):

c
// EncodeBundleInternal prologue  @ 0x1e86c7e0 (decompiled)
vmovups [struct+0x20] = 0;  vmovups [struct+0x14] = 0;     // clear the slot region
nx = kNeverExecute & 0x1F;                                  // = 31
*(uint64*)(struct+0x2D) = (nx << 53) | (nx << 26);          // scalar_0 @317, scalar_1 @290
*(uint16*)(struct+0x1D) = nx << 11;                         // VALU lane0 @147
*(uint32*)(struct+0x16) = nx << 5;                          // VALU lane1 opcode-region init
*(uint16*)(struct+0x1A) = 0x1F0;                            // = nx << 4  -> VALU lane1 pred @116
*(uint8 *)(struct+0x1C) = 0;                                // window high byte clear
*(uint64*)(struct+0x0C) = (0x400000800000000 * nx)          // bits 35 (MXU pred) + 58 (VLoad pred)
| (nx << 22)                        // VResult pred @22
| (nx << 13);                       // Misc predication @13

常量 0x400000800000000 正好有两个置位 bit,即 bit 35 和 bit 58,因此乘以 31 会在 MXU predicate(abs 35)和 VectorLoad predicate(abs 58)处放置 kNeverExecute。struct 0x2D store 将它放在两个 scalar predicate(abs 317、abs 290),struct 0x1D/0x1A store 将它放在两个 VALU-lane predicate(abs 147、abs 116)。由于 struct byte 0x0C 是 bit 96,qword-0 shift 会直接读作其绝对位位置(35、58、22、13),这是 12 字节剥离定律最清楚的展示。

预填充 store将 predicate(abs bit)设为 31
[struct+0x2D] = (31<<53)|(31<<26)scalar_0 @317;scalar_1 @290
[struct+0x1D] = 31<<11VALU lane0 @147
[struct+0x1A] = 0x1F0VALU lane1 @116
[struct+0x0C] = (31·0x400000800000000)|(31<<22)|(31<<13)VLoad @58;MXU @35;VResult @22;Misc @13

存在槽的 writer 会用真实 5 位 EncodePredication 值(0..14 谓词寄存器、15 kAlwaysExecute、+16 取反、31 kNeverExecute)覆盖其 predicate;常量 kPredicateRegisterCount=15 / kAlwaysExecute=15 / kNeverExecute=31 位于 0xB834CF4..0xB834CFC。缺失槽保持 predicate 31,硬件会跳过它。见 NOP / 未用槽规范编码谓词槽

陷阱 — 空槽不是全零。 预填充是一个非零戳记(31 复制到若干谓词字段)。若重新实现对束执行 memset 为零,然后只填充活动槽,就会让非活动槽保持 predicate 0,而这是有效的谓词寄存器引用,会把空槽变成活跃的垃圾 op。


VLIW 打包模型 {#the-vliw-packing-model}

字节编码器之上是决定 LLO op 占用哪个槽的模型。Bundle proto 是 VLIW 打包记录:slot_mask 位于 proto+0x10,逐槽子消息指针位于 proto+0x18..+0x60,六个 16 位立即数字段位于 proto+0x68..+0x7C。LLO op 由 JellyfishEmitter::FindFreeSlot<Inst, Opcode> 折叠进槽。

c
// JellyfishEmitter::FindFreeVectorAluSlot(opcode)  @ 0x140b0f00
//   -> FindFreeSlot<VectorAluInstruction, VectorAluOpcode>  @ 0x140c09e0 (ecx = 3 candidate slots)
// 5 std::function callbacks drive the fold:
function FindFreeSlot(opcode, candidate_count, {legal, free, get_or_create, mark_used, err}):
    for slot in candidate_slots(candidate_count):     // 2 VALU lanes for the ALU case
        if !legal(opcode, slot):       continue       // (i)  op allowed in this BundleSlot?
        if !free(bundle, slot):        continue       // (ii) slot's has-bit clear in the Bundle?
        sub = get_or_create(bundle, slot)             // (iii) Arena::DefaultConstruct the submessage
        mark_used(bundle, slot)                        // (iv)  set the slot_mask bit @proto+0x10
        return sub                                     //       first free + legal slot
    return error(err(opcode))                          // (v)   "no free VALU slot" diagnostic

这五个 callback 作为 std::function thunk(0x140c3680/.../0x140c38a0)传入。一旦 FindFreeSlot 返回子消息指针,逐 op Emit* populator(EmitVectorBinopEmitVectorMatmulEmitScalarLoad 等)会填充 proto 字段(opcode @+0x50、operands @+0x58/+0x60、predication),而 FinalizeCurrentBundle0x140afc60)提交该束,供 EncodeBundleInternal 序列化。标量路径使用 FindFreeScalarSlot0x140b2f40)→ FindFreeSlot<ScalarInstruction, ScalarOpcode>0x140c0180),其合法性谓词强制 branch/call 只能在 slot-0 中的规则(见 定序器槽)。

注意 — BarnaCore address-handler personality 使用此折叠的静态类比:MakeInstruction<Slot...>0xfa961c0..0xfa96680)= EmptyInstruction0x1416aa60),然后对编译期可变 tuple 的每个元素执行一次 InsertOperation(instr, slot)0x1416a240),每个 InsertOperation 返回一个 Status(槽冲突时为 FailedPrecondition)。16 个模板实例化在编译期枚举合法槽组合,而 TensorCore 路径在运行时搜索 lane。address-handler 束是单独的 23 字节帧,不是这个 41 字节束。


HBM Chunk 布局

41 字节宽度是发射宽度。存储在 HBM 中时,每个束占用稍大的槽位,并且束按 128 字节 DMA chunk 分组。JellyfishCodecMetadata::BundleSizeBytesForHbm0x1ecf74c0)对 TensorCore 返回 42,比 41 字节发射束多一个字节。EncoderJf::GetBundleByteOffset0x1e86db80)计算束在 chunk 内的偏移:

c
// EncoderJf::GetBundleByteOffset(int n)  @ 0x1e86db80 (decompiled)
if (n < 0 || n >= bundles_per_chunk)
    return error("Requested bundle number N exceeds limit of "
                 "bundles-per-chunk (...)");        // encoder_jf.cc:3202
offset = (n / 3) * 128                              // 3 bundles per 128-byte chunk
       + (n % 3) * (BundleSizeBytes() + 2);         // 41 + 2 = 43-byte in-chunk stride
数量来源
发射束41 BBundleSizeBytes @ 0x1ecf7460
HBM 存储束42 BBundleSizeBytesForHbm @ 0x1ecf74c0
chunk 内步长43 B(BundleSizeBytes()+2GetBundleByteOffset @ 0x1e86db80
每 chunk 束数3(n/3)*128 分组
Chunk 粒度128 B(n/3)*128

陷阱 — chunk 内步长是 BundleSizeBytes()+2 = 43不是 HBM 束宽度 42。两者相差一个字节:BundleSizeBytesForHbm 报告存储束为 42(比 41 字节发射宽度多一个 check/pad 字节),而 GetBundleByteOffset 在 chunk 中的连续束之间前进 43。这个额外字节与一个 check byte 加一个 pad byte,或一个 2 字节 trailer 相符;确切的逐束 framing 字节(check byte 0x55 以及任何 pad)由程序级 EncodeProgramForHbmInternal 写入,本页不固定。


交叉引用

  • 束模型 — 本页为 Jellyfish 实例化的逐代束宽度和槽分类。
  • MXU 槽 — vector-extended 槽的 matmul/matpush/latch 字段和逐代 MXU twin。
  • 记录格式 — 239 位 MC APInt;为什么 Jellyfish 路径是 proto-direct 并绕过它。
  • MC 发射器getBinaryCodeForInstr 以及 Jellyfish 不会将束字节通过其路由的 MC pipeline。
  • Dragonfish 束 — 继承这个完全相同的 EncodeBundleInternal 和 41 字节布局的 gen-1 编解码器。
  • MXU 延迟:JF / DF — 消费这里解码的 MXU 槽字段的成本模型。