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::EncodeBundleInternal(0x1e86c7c0)通过在 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 bits(JellyfishCodecMetadata::BundleSizeBytes @ 0x1ecf7460 返回 41;缓冲区由 EncodeBundleInternal 中的 operator new(0x29) 硬固定) |
| 内部 scratch | 53 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 = 31(0xB834CFC),位于每个槽的 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)字节:
// 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)占据 bitS及以上。Jellyfish 直接打包编码器通过其shl/orshift 常量使用这一约定;编码路径中没有任何 MSB-first / big-endian 排列。由(value & mask) << S写入 byte offsetB处 struct qword 的字段因此落在绝对束位B*8 + S − 96(因为0x0C * 8 = 96)。当B == 0x0C时,−96抵消,绝对位就等于 shift 本身,这就是 qword-0 预填充常量按原样读作其束位位置的原因。
41 字节按绝对位划分成五个 qword:
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 值。
// 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]); // 0x1e86da20bit 到槽的映射来自反编译的 if ((mask & N) != 0) 梯形结构,按字节精确;每个 writer 都在列出的 proto+ 偏移读取其子消息指针(空指针回退到逐类型 _globals_ default instance)。同一个 proto+0x10 word 也被 ProtoUtils::GetPopulatedSlots(0x1e875be0)用相同的 bit 测试读取,它是规范的束占用 bitfield。
slot_mask 位 | proto+ | 槽(角色) | 逐槽 writer | Struct word |
|---|---|---|---|---|
0x0001 | +0x18 | scalar_0(SPU lane0 / 定序器 lane) | EncodeScalarInstruction(lane=0) @ 0x1e862060 | 0x2D(qword4) |
0x0002 | +0x20 | scalar_1(SPU lane1) | EncodeScalarInstruction(lane=1) @ 0x1e862060 | 0x2D(qword4) |
0x0008 | +0x30 | vector_alu_0(VPU lane0) | EncodeVectorAluInstruction(lane=0) @ 0x1e864f00 | 0x1D(qword2) |
0x0010 | +0x38 | vector_alu_1(VPU lane1) | EncodeVectorAluInstruction(lane=1) @ 0x1e864f00 | 0x16 window |
0x0020 | +0x40 | vector_store(mem-store) | EncodeVectorStoreInstruction @ 0x1e868c40 | 0x14 + 0x16 |
0x0040 | +0x48 | vector_load(mem-load) | EncodeVectorLoadInstruction @ 0x1e867340 | 0x0C + 0x1F |
0x0080 | +0x50 | vector_extended(MXU / EUP) | EncodeVectorExtendedInstruction @ 0x1e869f00 | 0x0C + 0x16 |
0x0100 | +0x58 | vector_result(matres / EUP-pop) | EncodeVectorResultInstruction @ 0x1e865ae0 | 0x0C |
0x0200 | +0x60 | misc(mask / rotate / imm-set) | EncodeMiscInstruction @ 0x1e86be80 | 0x0C |
0x0400..0x8000 | +0x68..+0x7C | immediate 槽 imm0..imm5(每个 16 位) | ValidateImmediate @ 0x1e86da20 | imm 区域 0x1F/0x27 |
(0x0004) | — | TTU 操作数 | EncodeTtuOperands @ 0x1e863280 | 0x2D + 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 在 word0x1D,lane1 在0x16处的 56 位窗口),而不是同一个 word 中的相邻字段。
五个物理 Word 区域
41 字节束不是九个连续槽字段。若干槽共享一个 64 位 word,并通过占据其不相交位子范围共存;另一些则散布在多 word 操作数窗口中。五个区域:
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 operandsqword0 中四个槽的打包是最重要的结构事实: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 操作数都会伸入 struct0x16处的同一个 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 → abs | lane1 shift → abs | 宽度 |
|---|---|---|---|---|
| predicate | EncodePredication & 0x1F | <<53 → 317 | <<26 → 290 | 5 |
| opcode | [instr+0x50] & 0x3F | <<47 → 311 | <<20 → 284 | 6 |
| X reg | ScalarBinaryOperands+0x20 & 0x1F | <<31 → 295 | <<4 → 268 | 5 |
| Y reg | +0x1C & 0x1F | <<42 → 306 | <<15 → 279 | 5 |
| ScalarY | +0x18 & 0x3F | <<36 → 300 | <<9 → 273 | 6 |
opcode 跳转表覆盖 ScalarOpcode 0..0x37;立即数 Y 常量通过 EncodeImmediateValueForScalarYEncoding(0x1e85f3a0)路由到立即数区域。定序器 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 << 36、Vx << 25(clear 0xC1FFFFFF)、y << 10(clear 0xFFFF83FF)。
| 字段 | proto 源 | Lane0(word 0x1D)abs | Lane1(window 0x16)abs | 宽度 |
|---|---|---|---|---|
| Vx | [instr+0x58] & 0x1F | <<0 → 136 | <<25 → 105 | 5 |
| opcode | [instr+0x50] & 0x3F | <<5 → 141 | <<30 → 110 | 6 |
| predicate | & 0x1F | <<11 → 147 | <<36 → 116 | 5 |
| y | [instr+0x60] & 0x1F | (溢出到 0x16) | <<10 → 90 | 5 |
| dest | [instr+0x60] & 0x1F | <<41/<<51(溢出) | <<41 → 121 | 5 |
opcode 为 6 位(VectorAluOpcode 0..62)。Opcode 0x18(LANE_ID)和 EUP 范围 0x30..0x34 走专用分支;IsEupOpcode(0x1e875900)在束提交前门控是否需要 EUP/XLU 保留。
注意 — lane-0 dest 字段由共享
y/dest分支(struct0x16shl 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 | 宽度 |
|---|---|---|---|
| predicate | EncodePredication & 0x1F | 35 | 5 |
| opcode | (6 位 VectorExtendedOpcode) | 29..34 | 6 |
| mxu-id | [instr+0x64] & 3 | 27..28 | 2 |
| has-bit | | 0x20000000 | 29 | 1 |
| operands | [instr+0x6C] | <<46/<<15 到 0x16 窗口;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 <<22 | 22 | 5 |
| result-format | [instr+0x40] & 3 <<20 | 20 | 2 |
| result-mode | [instr+0x44] & 3 <<18 | 18 | 2 |
| dest-Vreg | [instr+0x48] & 0x1F | 取决于 mode:<<10 / <<41 / <<51 | 5 |
目标寄存器的位位置由 which_destination 子形式(值从哪个 EUP/result 目标流出)选择。结果有效路由在 ResultFifo 页面中介绍。
Vector-Load(struct 0x0C + Vs 端口 0x1F)— EncodeVectorLoadInstruction @ 0x1e867340
| 字段 | proto 源 | abs bit | 宽度 |
|---|---|---|---|
| predicate | & 0x1F <<58 | 58 | 5 |
| opcode(addr-mode) | [instr+0x50] & 3 <<56 | 56 | 2 |
| has-bit | [instr+0x60]!=0 → | (1<<40) | 40 | 1 |
| destVreg | [instr+0x64] & 0x1F <<51 | 51 | 5 |
| stride | [instr+0x54] & 7 <<48 | 48 | 3 |
| offset | [instr+0x58] & 3 <<46 | 46 | 2 |
| base | [instr+0x5C] & 3 <<44 | 44 | 2 |
| vs2 / vs1 | [target+0x88] & 0x1F <<10, [target+0x84] & 0x1F <<5 | word 0x1F | 每个 5 |
abs 56 处的 2 位 opcode 选择 {VmemLoad, VmemLoadShuffled, VmemLoadIndexedIar0, VmemLoadIndexedIar1}。Vs base/index 端口和 sublane-mask 子字段通过 EncodeVectorSublaneMaskEncoding(0x1e867840)路由。这些位置补上了一个长期缺口: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 <<13 | 13 | 5 |
| operand | [instr+0x40] <<8 | 8 | — |
| sub-op | [instr+0x18]/[+0x1C] <<5(按 MiscOperandEncoding) | 5 | — |
kNeverExecute 预填充
在编码任何槽之前,EncodeBundleInternal 用两个 ymm store 将槽区域清零,然后将谓词值 kNeverExecute = 31(0xB834CFC)戳入每个槽的 5 位谓词字段。相关 prologue(反编译,lines 98-109):
// 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<<11 | VALU lane0 @147 |
[struct+0x1A] = 0x1F0 | VALU 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> 折叠进槽。
// 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(EmitVectorBinop、EmitVectorMatmul、EmitScalarLoad 等)会填充 proto 字段(opcode @+0x50、operands @+0x58/+0x60、predication),而 FinalizeCurrentBundle(0x140afc60)提交该束,供 EncodeBundleInternal 序列化。标量路径使用 FindFreeScalarSlot(0x140b2f40)→ FindFreeSlot<ScalarInstruction, ScalarOpcode>(0x140c0180),其合法性谓词强制 branch/call 只能在 slot-0 中的规则(见 定序器槽)。
注意 — BarnaCore address-handler personality 使用此折叠的静态类比:
MakeInstruction<Slot...>(0xfa961c0..0xfa96680)=EmptyInstruction(0x1416aa60),然后对编译期可变 tuple 的每个元素执行一次InsertOperation(instr, slot)(0x1416a240),每个InsertOperation返回一个Status(槽冲突时为FailedPrecondition)。16 个模板实例化在编译期枚举合法槽组合,而 TensorCore 路径在运行时搜索 lane。address-handler 束是单独的 23 字节帧,不是这个 41 字节束。
HBM Chunk 布局
41 字节宽度是发射宽度。存储在 HBM 中时,每个束占用稍大的槽位,并且束按 128 字节 DMA chunk 分组。JellyfishCodecMetadata::BundleSizeBytesForHbm(0x1ecf74c0)对 TensorCore 返回 42,比 41 字节发射束多一个字节。EncoderJf::GetBundleByteOffset(0x1e86db80)计算束在 chunk 内的偏移:
// 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 B | BundleSizeBytes @ 0x1ecf7460 |
| HBM 存储束 | 42 B | BundleSizeBytesForHbm @ 0x1ecf74c0 |
| chunk 内步长 | 43 B(BundleSizeBytes()+2) | GetBundleByteOffset @ 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 byte0x55以及任何 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 槽字段的成本模型。