Skip to content

TpuTopology 结构体 (Target+0x3b8)

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

摘要

tpu::TpuTopology 是芯片几何和设备 mesh 描述符,每个 xla::jellyfish::Target 都在 Target+0x3b8 持有它。它是回答“一个 TPU 芯片有多大,以及芯片如何排列成 slice”的唯一对象:lane/sublane MXU-tile 几何、每核心类型的核心计数、X/Y/Z 芯片 torus 范围,以及 host/chip mesh 乘积。XLA-for-TPU 后端在每个 tiling、成本模型和内存空间决策中读取它;C-API 运行时通过一组平行的 TpuTopology_* wrapper 读取同样的字段。

该结构体只构造一次,在 0x20acee60 的构造函数中(约 309 行反编译代码),由一个 shared_ptr<TpuChipParts> 加两个 TpuDimensions 三元组(host bounds 和 chips-per-host)构成。构造函数将两个维度三元组相乘得到组合芯片边界,向 chip_parts 查询每核心类型计数,然后遍历 chip_parts->CoreParts(TENSOR_CORE)->SequencerParts(...)->vector_isa() 来填充 +0x198..+0x1b0 处的几何块。如果该 VectorIsa 链缺失,构造函数回退到硬编码的 128 lanes × 8 sublanes,因此 128×8 是任何世代的内建默认值。对象精确为 0x3c8 字节(每个构造点都有 operator new(0x3C8u)),并以自身位于 +0x3b8..+0x3c8 的 HalLocations vector 结束(这是数字上的巧合:TpuTopology+0x3b8 是该对象自己的 HalLocations 计数字,与命名此结构体的 Target+0x3b8 指针无关)。

Target::Init (0x1d60fc20) 安装该指针:*((_QWORD *)target + 119) = topologyTpuTopology* 放在 Target+0x3b8,而 *((_QWORD *)target + 297) = sparsecore 将 SparseCore 子描述符放在 Target+0x948。相邻的 Target+0x3c0 槽位(*((_QWORD *)target + 120)不是第二个 topology:Target::Init 在那里存储传入的 unique_ptr<CpuTopology>,析构函数通过 TargetMachineOptions 成员释放它,所以它是 host-CPU topology,与芯片几何无关。本页其余内容是逐字节精确的字段布局、读取每个字段的访问器,以及构造函数的几何填充链,全部直接读取自反编译的构造函数和访问器函数体。

对于重新实现,契约是:

  • 字段布局 +0x00..+0x3c8:标量 mesh 维度、每核心类型计数、+0x198..+0x1b0 MXU-tile 几何块,以及 location-vector 尾部。
  • 几何填充链:lane/sublane/granules 如何从 chip_parts VectorIsa 派生,以及 128×8 回退。
  • 访问器表面:哪些 Target::TpuTopology_* 方法解引用哪个偏移,从而让重新实现暴露同样的标量契约。
结构体tpu::TpuTopology
构造函数0x20acee60(约 309 行),_ZN3tpu11TpuTopologyC1...
析构函数0xe6b0080
sizeof0x3c8 字节(精确值;每个构造点都有 operator new(0x3C8u),例如 TpuTopologySerdes::Construct 0x20805ee0TpuTopology::Subslice 0x20ad20a0
持有者xla::jellyfish::Target+0x3b8(唯一的 TpuTopology*Target+0x3c0 持有 CpuTopology,不是 topology)
安装于Target::Init 0x1d60fc20*((_QWORD*)target+119) = topology
几何块+0x198 lane,+0x1a0 sublane,+0x1a8 lane·sublane,+0x1b0 chunk-granules
几何来源chip_parts→CoreParts(TENSOR_CORE)→SequencerParts→vector_isa();回退 128×8

字段布局

下表是完整的 tpu::TpuTopology 布局。每个偏移都从构造函数存储点逐字节读取(反编译中的十进制偏移在此转换为十六进制),或来自匹配的访问器函数体。类型:i32/i64 标量,u8 布尔值,ptr 指针 / shared_ptr 控制字,vec/loc libc++ inline-vector 或 location-array(begin/end/cap 三元组或 count+pointer 对)。

字段偏移类型含义
platform_type+0x00i32TpuPlatformType(ctor *(_DWORD*)a1 = a2
chip_parts (ctrl)+0x08ptrshared_ptr<const TpuChipParts> 控制字;几何 + Version 通过它读取
chip_parts (refcnt)+0x10ptrshared_ptr<const TpuChipParts> 引用计数块
chip_config (ctrl)+0x18ptrshared_ptr<const TpuChipConfig>;Megacore / logical-device 门控
chip_config (refcnt)+0x20ptrshared_ptr<const TpuChipConfig> 引用计数块
flags+0x28i64long flags 实参(ctor *(_QWORD*)(a1+40) = a7
chips_per_host.x/.y/.z+0x30/+0x34/+0x38i32×3每 host 的 chip-mesh 维度(第一个 TpuDimensions 实参的 ctor vmovups;构造函数自己的 assert 称其为 chips_per_host_bounds()
chips_per_host.w+0x40i32第 4 个 chip-mesh 维度
host_bounds.x/.y/.z+0x44/+0x48/+0x4ci32×3host-mesh 维度(第二个 TpuDimensions 实参的 ctor vmovups;构造函数自己的 assert 称其为 host_bounds()
host_bounds.w / using_tensornode+0x54i32/u8第 4 个 host 维度;低字节由 UsingTensorNode 读取([+0x54]
ChipBounds_X+0x58i32chips_per_host.x · host_bounds.x(ctor v22*v24
ChipBounds_Y+0x5ci32chips_per_host.y · host_bounds.y
ChipBounds_Z+0x60i32chips_per_host.z · host_bounds.z
wrap.x / wrap.y+0x64/+0x68u8×2torus-wrap 字节,ctor 初始化为 0
HostCount+0x6ci32∏ host-bound 维度(ctor v29*v24*v25
chips_product+0x70i32∏ 组合 chip-bound 维度 = ChipBounds_X·Y·Z(ctor v31 = v30*v26*v27);所有每核心类型计数的 chips_product 乘数
ChipsPerHost+0x74i32∏ chips-per-host 维度(ctor v28*v22*v23
total_cores+0x78i32chips_product · CoreCount()(所有核心类型)
TENSOR_CORE count/chip+0x7ci32CoreCount(chip_parts, 0)CoresPerChip(t) 的基址
TENSOR_CORE · chips+0x80i64[+0x7c] · chips_product(8 字节存储)
SPARSE_CORE count/chip+0x88i32CoreCount(chip_parts, 1)CoresPerChip(1)
SPARSE_CORE · chips+0x8ci32[+0x88] · chips_product
TENSOR_CORE · chips (dup)+0x90i32TC·chips 乘积的重复副本
BARNA_CORE count/chip+0x94i32CoreCount(chip_parts, 2)CoresPerChip(2)
core[2] · chips+0x98i32CoreCount(...,2) · chips_productSupportsSparseCore 测试 >0(见注)
(TC+SC) · chips+0x9ci32TC·chips 与 SC·chips 乘积之和
wrap_proto_lo16 / wrap_proto_b16+0xa0/+0xa2u16/u8TpuWrapProto/TpuWrapTag 实参的低位(ctor *(_WORD*)(_RBX+160)=a14*(_BYTE*)(+162)=BYTE2(a14));+0xa0 & 0x101 供给 Topology wrap ctor,与 +0x28 处的 flags long 不同
twisted_bool+0xa3u8尾部 bool ctor 实参(a15);在下面选择 TwistedTorusTopologynew 0x138)或 Topologynew 0x58
topology_kind+0xa4i32TwistedTorus-vs-Topology 有效性选择器
topology+0xa8ptrslice_builder::Topology*new 0x58)或 TwistedTorusTopology*new 0x138
HostLocations+0xb0locMakeHostLocations (0x20acf5c0)
ChipLocations+0xc8locMakeChipLocations (0x20acf800)
CoreLocations (primary)+0xe0ptrMakeCoreLocationscores() 基址,元素 stride 0x38
CoreLocations (megacore)+0xf8loc第二个 MakeCoreLocations;当 TpuChipConfig::Megacorelogical_devices (0x20ad38c0) 返回 [+0xf8],否则返回 primary [+0xe0]+stride
SharedMemoryLocations+0x110locMakeSharedMemoryLocations (0x20ad02c0)
MemoryLocations+0x128locMakeMemoryLocations (0x20ad08e0)
StandardFactoryInfo+0x140blob可选 0x14 字节块;ctor 初始化为 0
subslice dims+0x158/+0x15c/+0x160 + +0x16c/+0x170/+0x174i32×6subslice chip-bound / extent 字段;GetFullSliceDeviceCount 正好相乘这六个字段
subslice_valid+0x184u8subslice-valid 标志(GetFullSliceDeviceCount cmpb
has_subslice+0x190u8ctor movb $0/$1;门控 subslice 路径
lane_count+0x198i64VectorIsa lane 计数;Target::LaneCount = [0x3b8]->[0x198]
sublane_count+0x1a0i64VectorIsa sublane 计数;Target::SublaneCount = [0x3b8]->[0x1a0]
lane·sublane+0x1a8i64MXU-tile 元素计数(ctor imul);供给 ChunkSizeBytes
chunk_granules+0x1b0i64派生值(见 几何填充);version<2 ? computed : 32
HalLocations+0x1b8locMakeHalLocations (0x20ad0de0),由 popcount(granules)<2 门控
ChipViewLocations+0x2b8/+0x2c0vec计数 [+0x2b8] / heap ptr [+0x2c0],元素 stride 0x20(dtor <9 门控再 ×32);由 MakeChipViewLocations (0x20ad1080,从 +0x2c8 写入) 构建
HalLocations tail+0x3b8/+0x3c0vec对象自己的 HalLocations:计数 [+0x3b8] / heap ptr [+0x3c0](dtor cmp $6,[0x3b8] 然后 free([+0x3c0]));0x3c8 字节对象的最后 16 字节

陷阱 — 数值偏移 0x3b8 在两个对象中出现,但含义互不相关。在 xla::jellyfish::Target 中,+0x3b8tpu::TpuTopology* 成员。在 tpu::TpuTopology 自身(一个 0x3c8 字节对象)内部,+0x3b8 是对象自己的 HalLocations 计数字(最后一个 vector 的尾部)。本页中每个 [0x3b8]->[X] 都表示“解引用 TargetTpuTopology*,然后读取字段 X”。混淆这两者的重新实现会读到垃圾。

Target::CoresPerChip(t) (0x1d615b40) 返回 [0x3b8]->[0x7c + 12·t],即 t=0 (TENSOR_CORE) 时 +0x7ct=1 (SPARSE_CORE) 时 +0x88t=2 (BARNA_CORE) 时 +0x94 的每芯片计数,并且对 t≥3 执行 BUG()。相邻的 +0x80..+0x9c 是对应的 ·chips 乘积。Target::SupportsSparseCore (0x1d48fd40) 读取 [0x3b8]->[0x98] > 0,但构造函数存储的是 [+0x98] = CoreCount(chip_parts, 2) · chips_product,即 type-index-2(BARNA 槽)乘积,而不是 SPARSE_CORE 乘积。

注意 — 运行时 TpuCoreType 枚举中 index 2 是否为 SparseCore 槽(区别于 proto BARNA_CORE 顺序)尚未独立确认:+0x98 处的字面存储是 CoreCount(chip_parts, 2) · chips,因此该字段上的 SparseCore-vs-Barna 标签为中等置信度。


几何填充 {#geometry-population}

+0x198..+0x1b0 处的 lane/sublane/granule 块在构造函数接近末尾处填充(反编译第 230–268 行)。路径是:先检查 TENSOR_CORE·chips 计数非零,然后获取 TensorCore 的 CoreParts → SequencerParts(0) → vector_isa(),检查 +0x18 处的 VectorIsa has_vector_isa 字节,并复制 vector_isa[0](lane)和 vector_isa[+0x04](sublane)。如果链上任何一环缺失,构造函数使用硬编码的 128 × 8 回退。

算法

c
function PopulateGeometry(this, chip_parts):           // ctor 0x20acee60, lines 230-268
    if this->[0x80] /*TENSOR_CORE · chips*/ == 0:       // line 230
        goto fallback
    parts = chip_parts->CoreParts(0 /*TENSOR_CORE*/)    // line 233, sub_20b1e840
    if parts == NULL: goto fallback
    seq = parts->SequencerParts(0 /*TC_SEQ*/)           // line 236, sub_20b2aa60
    if seq == NULL: goto fallback
    vi = seq->vector_isa()                              // line 237, = seq+0x1c (sub_20b31840)
    if vi->[0x18] /*has_vector_isa*/ != 1: goto fallback
    if vi->[0x18] == 0: BUG()                           // line 240-241, FATAL double-check
    lane    = (i64)(i32)vi->[0x00]                       // line 242
    sublane = (i64)(i32)vi->[0x04]                       // line 244
    goto store

fallback:                                                // lines 248-251 (LABEL_40)
    lane    = 128                                        // movq $0x80
    sublane = 8

store:
    this->[0x198] = lane                                 // line 243/249
    this->[0x1a0] = sublane                              // line 253
    this->[0x1a8] = lane * sublane                       // line 254-255  (MXU-tile elems)

    // chunk_granules (tc_max_packing_factor):
    numer   = 4 * (lane * sublane)                       // line 256, bytes
    divisor = 4 * lane                                   // line 258
    cpc     = chip_parts->[0xc8]                          // line 259
    if cpc > divisor: divisor = cpc                       // line 259-260  (MAX, not min)
    q       = numer / divisor                             // line 261-264
    this->[0x1b0] = (chip_parts->[0] < 2 /*version<2*/) ? q : 32   // lines 265-268
    CHECK(this->[0x1b0] > 0 && IsPowerOfTwo(this->[0x1b0]))         // lines 269-274
```text

> **注意 —** `chunk_granules` 的除数是 `max(4·lane, chip_parts[+0xc8])`,即 `4·lane` 与 chip-parts 字段中*较大*者;被除数是 `4·(lane·sublane)`。反编译代码(`if (*(_QWORD*)(v59+200) > v60) v60 = *(_QWORD*)(v59+200)`)是在 `lane·4` 上取 `max`,不是在 `sublane·4` 上取 `min`。结果会被强制检查为正的 2 的幂(`IsPowerOfTwo(result.tc_max_packing_factor)`,source line 129 处 FATAL);对任何报告 `chip_parts.version >= 2` 的世代,存储值就是 `0x20`(32)。
>
> **注意 —** 回退会写入 `lane=0x80, sublane=8`,因此即便某个 `TpuTopology` 由缺少已填充 VectorIsa 的 chip-parts blob 构建,它仍呈现 128×8 几何。对于此 wheel 中嵌入的 v7 (`6acc60406`) chip-parts,VectorIsa 存在并且同样报告 `lane=128, sublane=8`,所以在本构建中填充路径与回退路径一致。`lane·sublane = 1024` 乘积以及 `chunk_granules = 32` 随之得到。

### 来源链

```text
TpuTopology ctor (0x20acee60)
  └─ chip_parts->CoreParts(TENSOR_CORE)          0x20b1e840
       └─ TpuCoreParts::SequencerParts(TC_SEQ)   0x20b2aa60
            └─ TpuSequencerParts::vector_isa()    0x20b31840  (= this+0x1c)
                 ├─ [+0x00] lane_count    → TpuTopology+0x198
                 ├─ [+0x04] sublane_count → TpuTopology+0x1a0
                 └─ [+0x18] has_vector_isa (gate; FATAL if 0 after the outer test)

访问器

有两个平行表面读取这些字段:xla::jellyfish::Target 方法(供编译器使用)会先解引用 Target+0x3b8,而 TpuTopology_* C-API wrapper(供运行时使用)直接接收 TpuTopology*。两者都逐字节读取;偏移一致。

Target 访问器(读取 [0x3b8]->[X]

*((_QWORD*)target + 119)target + 0x3b8,即 TpuTopology*。每个访问器都会解引用它并读取字段 X。

访问器VA读取返回
Target::LaneCount0x1d60f400[0x3b8]->[0x198]i64 lane 计数
Target::SublaneCount0x1d60f300[0x3b8]->[0x1a0]i64 sublane 计数
Target::ChunksPerTile0x1d60f2c0[0x198] / [0x1a0]lane/sublane(128/8 时为 16)
Target::TileBytes0x1d615bc04 · [0x198] · [0x198]lane²·4 字节(lane 128 时为 65,536)
Target::ChunkSizeBytes0x1d6171004 · (i32)[0x1a8]lane·sublane·4 字节(1024 时为 4096)
Target::ChunkGranules0x1d61a440(4·[0x1a8]) / vtable->GranuleBytes()每 granule 的 tile chunk 数
Target::LaneCountLog20x1d615be0bsr (i32)[0x198]log2(lane) = 128 时为 7
Target::SublaneCountLog20x1d615c40bsr (i32)[0x1a0]log2(sublane) = 8 时为 3
Target::CoresPerChip(t)0x1d615b40[0x3b8]->[0x7c + 12·t]每 coretype 计数;若 t≥3BUG()
Target::SupportsSparseCore0x1d48fd40[0x3b8]->[0x98] > 0bool(+0x98 = CoreCount(chip_parts,2)·chips;SparseCore-vs-Barna 标签为中等置信度)
Target::HbmCountPerChip0x1d616080chip_parts->SharedMemoryCount([0x3b8]+8, 0)HBM stack 数;若 [0x3b8] 为空则 FATAL

注意 — ChunkSizeBytes[0x1a8] 作为 32 位值读取(4 * *(_DWORD*)(... + 424)),但该字段以 64 位 lane·sublane 乘积存储。对任何现实几何,该乘积都能放入 32 位,因此无害;但重新实现必须全宽存储该乘积(imul 是 64 位),即便某个消费者会将它变窄。相反,TileBytes 将 lane 字段作为完整 64 位 _QWORD 读取并求平方。

TpuTopology C-API wrapper(直接读取 [X]

这些 wrapper 将 TpuTopology* 作为实参,因此偏移是原始结构体偏移(没有 +0x3b8 间接层)。

WrapperVA读取含义
TpuTopology_ChipBounds_X0xeabc040[+0x58]组合 chip-torus X 范围
TpuTopology_ChipBounds_Y0xeabc060[+0x5c]chip-torus Y 范围
TpuTopology_ChipBounds_Z0xeabc080[+0x60]chip-torus Z 范围
TpuTopology_HostCount0xeabc000[+0x6c]∏ host-bound 维度
TpuTopology_ChipsPerHost0xeabc020[+0x74]∏ chips-per-host 维度
TpuTopology_Version0xeabc2a0**(i32**)[+0x08]chip_parts.version < 4 ? version+1 : 0
TpuTopology::UsingTensorNode0x20ad7700[+0x54] (u8)tensornode-vs-full-chip blob 选择器
TpuTopology::cores(t)0x20ad3880base [+0xe0] + 0x38·[+0x84+12·t]核心类型 t 的 location span;若 t≥3BUG()

怪异点 — TpuTopology_Version 不读取已存储的 version 字段。它加载 chip_parts blob 的第一个 i32**(i32**)(this+8)),并在 version<4 时返回 version+1,否则返回 0。因此 C-API 的 “version” 是经过 clamp 的 chip_parts.version + 1,与 Target 保存在 Target+0x398 的内部 tpu_version 编号不同。通过这个 wrapper 报告原始内部 version 的重新实现会差一,并且会静默地把任何 ≥4 的值清零。


每 Codename 几何

这些几何字段对每个世代都是确定的,因为该 wheel 嵌入了全部九个 <name>_chip_parts.binarypb blob 且包含数据(每个都是 .data.rel.ro 中的 name→data→length→md5 TOC 条目,blob 字节在 .rodata 0xBDF2BA0..0xBDF38C0;例如 jellyfish_chip_parts.binarypb 为 435 B,位于 0xBDF3700,并以 field-1 version=1 开头)。每个 blob 都携带自己的绝对 lane/sublane VectorIsa;解码值全部报告 128×8,这也正是构造函数 128×8 回退会产生的结果,因此在本构建中填充路径和回退一致。内部 TpuVersion 从 0 开始并按时间排序(kJellyfish=0kDragonfish=1kPufferfish=2kViperfish=3kGhostlite=4k6acc60406=5);外部 “TPU vN” 轴是另一回事,见 version→codename 矩阵

从该 wheel 可恢复的一个确定的每 codename MXU 差异根本不是 TpuTopology 字段,而是每 codename Target 子类中的 C++ 字面量:基类 Target::MxuContractingSize (0x1d490060) 返回 128,而 GhostliteTarget::MxuContractingSize (0x1d497840) 和 MxuNoncontractingSize (0x1d497860) 返回 256。因此 systolic MXU 在 Jellyfish 到 Viperfish 类上是 128×128,而在 Ghostlite 和 6acc60406 类(外部 TPU v6e / TPU7x)上是 256×256。这个 256 是 systolic depth,区别于 VectorIsa 报告的 128-lane 宽度。

几何常量字段 / 来源Jellyfish…Viperfish (v0–v3)Ghostlite / 6acc60406 (v4–v5)
lane_count[0x3b8]+0x198128(回退 / chip-parts)1286acc60406 chip-parts)
sublane_count[0x3b8]+0x1a08(所有世代)86acc60406 chip-parts)
lane·sublane[0x3b8]+0x1a810241024
chunk_granules[0x3b8]+0x1b0computed(version<2)/ 32(version≥232(version ≥ 2 → 0x20)
ChunksPerTile[0x198]/[0x1a0]1616
TileBytes4·lane²65,53665,536
ChunkSizeBytes4·lane·sublane40964096
MXU contracting / noncontracting*Target::Mxu*Size (CODE)128 / 128256 / 256(Ghostlite override)
TENSOR_CORE / chip[0x3b8]+0x7cgen-dep(v0–v3 std 上为 2)1(die)/ 2(full chip)
SPARSE_CORE / chip[0x3b8]+0x88gen-dep(v0–v2 上为 BarnaCore 引擎;v3 起为 SparseCore)2(die)/ 4(full chip)
BARNA_CORE / chip[0x3b8]+0x94gen-dep(v0/v1 为 2,v2 为 4;v3 起为 0)06acc60406 chip-parts 中没有)

注意 — +0x7c/+0x88/+0x94 处的每芯片计数反映运行时选择的 chip-parts blob,即半 die tensornode blob 或完整双 die chip blob,并由 UsingTensorNode[+0x54])门控。对于 6acc60406,tensornode blob 报告 TC=1、SC=2、HBM=1,而 full-chip blob 将每项翻倍(TC=2、SC=4、HBM=2)。TpuTopology 单元格不是固定的每 codename 常量;它们跟随所选 blob。


SparseCore 几何

TpuTopology 跟踪 SparseCore 计数(每 chip +0x88,·chips 为 +0x8c),但 SparseCore 几何 位于 Target+0x948 处的单独子描述符中,由 Target::Init 安装(*((_QWORD*)target + 297) = sparsecore)并由 SparseCoreTarget::Init (0x1d612b20) 构建。其访问器解引用 *((_QWORD*)target + 297) = target + 0x948,并由 vtable[+0x260] 处的 SupportsSparseCore vtable 谓词守卫;如果目标没有 SparseCore,它会 FATAL("SparseCore is not supported by this target")。

访问器VA读取6acc60406
Target::SparseCoreLaneCount0xf7906e0[0x948]->[0x94]16
Target::SparseCoreTiles0xfaafa40[0x948]->[0x90]16 TEC/SC
Target::SparseCoreHbm4bWordSizeBytes0x1320c220[0x948]->[0x58]4
Target::SparseCoreStreamGranuleSizeBytes0x13886ee0[0x948]->[0xa4]4

完整的 SparseCoreTarget 字段映射是单独对象,记录在 SparseCore target 描述符 页面;这里只走查了上面四个字段。


尚未解析

  • v0–v4 的绝对 lane/sublane。 此 wheel 嵌入了全部九个 <name>_chip_parts.binarypb blob(从 jellyfish 到 6acc60406,外加 pufferfish_lite/viperfish_lite/6acc60406_tensornode 变体),因此每个世代的 VectorIsa 都可直接解码,而不是从构造函数回退推断。解码出的 VectorIsa.sublane_count 在本构建的每个世代都是 8;proto 从 jellyfish 到 6acc60406 都统一携带 sublane_count = 8(见 每 Codename 硬件常量)。当 VectorIsa 链缺失时构造函数会提供的 128×8 回退,与每个嵌入式 blob 报告的结果一致。
  • +0x158..+0x190 subslice 字段语义。 GetFullSliceDeviceCount+0x158/+0x15c/+0x160/+0x16c/+0x170/+0x174 相乘并由 +0x184/+0x190 门控,但哪个轴是 subslice base、哪个轴是 extent 尚未逐一钉牢。布局表中标记为中等置信度。
  • location 元素结构体。 已恢复 +0xb0..+0x2c8 vector 的基址偏移和 stride(Core stride 0x38、ChipView 0x20、Hal 0x30),但尚未解码每个元素的 TpuCoreLocation / TpuChipLocation 字段打包。
  • +0x98 SparseCore-vs-Barna 标签。 构造函数在那里存储 CoreCount(chip_parts, 2) · chipsSupportsSparseCore 读取它;运行时 TpuCoreType index-2 → SparseCore-or-Barna 映射尚未单独确认。

交叉引用