Skip to content

SparseCore 硬件架构

本页中的每个地址、偏移和值,均从 libtpu-0.0.40-cp314 wheel(BuildID md5 89edbbe81c5b328a958fe628a9f2207d)内的 libtpu.so 逐字节读取而来。其他版本会有所不同。

摘要

本页是 SparseCore 后端所面向的硬件模型 — 在调度任何 SparseCore(SC)程序或放置任何片上缓冲区之前,重实现者必须重建的几何形状和内存布局。该模型由三项内容定义,而编译器会从同一个运行时对象读取这三项:停放在 Target+0x948SparseCoreTarget 子描述符。它固定了一颗芯片有多少 tile-execute core、每个 core 有多宽、片上 SRAM 各层级有多大,以及硬件保留区域位于何处。该结构的逐字节字段映射由 SparseCoreTarget (Target+0x948) 维护;本页将其综合为架构 — engine→core 布局、四层内存模型,以及把几何信息转为已放置缓冲区的 bump/stack tile 分配算法。

最接近的心智模型是带 shared memory 的 GPU SM,但方向相反。SM 将连续 tile 流入大型寄存器文件,而 SC 将间接(由索引驱动)的 embedding row 流入很小的每 tile SRAM(TILE_SPMEM),再用宽向量引擎(TEC)做归约。计算结构被分为三个 VLIW 子引擎 — SCS(标量控制)、TAC(tile-fetch DMA issuer,仅存在于 Viperfish/Ghostlite)和 TEC(宽向量)— 它们通过 sync flag 和共享 SRAM 协同。每芯片 TEC core 数(SparseCoreTiles)、每个 core 的向量宽度(SparseCoreLaneCount)以及各层容量不是代码常量:SparseCoreTarget::Init 从每 codename 的 TpuCoreParts(最终来自 gzip 压缩的 chip_parts.binarypb)读取它们。唯一的代码常量是字面量 HBM 4b-word = 4SCS group count = 2,以及 SPMEM stripe 粒度 = 32 bytes

本页组织为四个单元:SparseCoreTarget 映射(几何信息的事实来源和 accessor gate)、engine→core 布局(SCS/TAC/TEC 如何映射到物理 core 和 tile)、片上内存模型(四个地址空间、字大小,以及 SPMEM↔TILE_SPMEM 拆分),以及 tile 分配算法(带 scope stack 的每地址空间 bump allocator、tile sub-frame carve-out、embedding row sharding、minibatch-fit 不等式和 HBM spill)。每个单元都会以每代差异收尾,其中只有一个真正的代码分支。

对重实现而言,契约是:

  • 几何信息通过 SparseCoreTargetTarget+0x948)数据驱动。 重实现者必须在任何分配运行之前,从芯片的 core-parts 填充 SparseCoreTiles[0x948]+0x90)、SparseCoreLaneCount[0x948]+0x94)、stream_granule_size[0x948]+0xA4)以及每层 capacity/word block([0x948]+0x10..+0x54)。四层内存大小从这里读取,而不是硬编码。
  • 存在性 gate 是运行时拓扑测试。 每个 Target::SparseCore* accessor 都会 dispatch 虚函数 SupportsSparseCore(vtable slot +0x2600x1D48FD40),并在 TpuTopology[+0x98] 的 SC 数为零时 LOG(FATAL) 输出 "SparseCore is not supported by this target"。不存在编译期 class 测试。
  • 片上分配是带 scope stack 的 bump allocator — 绝不是 best-fit。 AllocationAssignmentPass 遍历 memref.alloca op,并按地址空间以 word 为单位 bump-reserve;tile-local 缓冲区会在 vector-lane-stripe 边界,从 SPMEM bump 指针中 carve 出一个专用 sub-frame,并在 pop 时回收。Deallocate 是 no-op。
  • allocator 中只有一个每代代码分支。 它是最后一个 TILE_SPMEM entry 中 circular-buffer 的硬件 bug 防护,针对 DeepseaVersion 3(Viperfish)和 4(Ghostlite)触发。其他所有内容 — 容量、word 宽度、tile 数、TAC 是否存在 — 都是数据驱动。
几何来源Target+0x948 处的 std::unique_ptr<SparseCoreTarget>;由 SparseCoreTarget::Init0x1D612B20)从 SPARSE_CORE TpuCoreParts 构建
存在性 gateTarget::SupportsSparseCore0x1D48FD40,vtable slot +0x260)— 读取 TpuTopology[+0x98] > 0
每芯片 tile 数SparseCoreTiles = [0x948]+0x90 = TpuCoreParts::SequencerCount(seq-type 5 = TEC)(v7x 6acc60406 = 16)
每 tile 向量宽度SparseCoreLaneCount = [0x948]+0x94 = SequencerParts(5).vector_isa().lane_count(v7x = 16)
引擎SCS(标量,32 B bundle) · TAC(tile-fetch DMA,64 B;仅 VF/GL) · TEC(向量,64 B;一个 TEC = 一个 tile)
片上 SRAM 层级HBM(GB) · SPMEM(MB,芯片范围) · TILE_SPMEM(KB,每 tile) · TIMEM(每 tile 指令);SMEM/SFLAG 按 scope
AllocatorAllocationAssignmentPass0x134D8240)— 每地址空间 bump + scope stack;Deallocate 被省略
SPMEM stripe32 bytes(SparseCoreSpmemStripeGranularityBytes = 0x1D499440optional<32>
分配对齐SpmemAlignment = (SparseCoreTiles × lane_count) / 4 words(0x13DC5500

SparseCoreTarget 映射(Target+0x948

目的

xla::jellyfish::Target 是 XLA TPU 后端在每个 codegen 和 cost 决策中查询的每代硬件描述符。它的 SparseCore 部分并非内联 — 它是在 Target+0x948 处单独分配的 std::unique_ptr<SparseCoreTarget>,由 SparseCoreTarget::Init0x1D612B20)构建一次,并由 Target::Init 安装。该子对象是 SC 几何信息的单一事实来源:tile 数、lane 宽度、各层 SRAM 容量与 word 宽度、embedding param-region 基址、保留区域窗口,以及每代 capability bit。编译器从不硬编码每代大小;它从这里读取。

注意 — 完整的逐字节字段映射(每个 +0xNN 偏移、类型和 Init store 位置)位于 SparseCoreTarget (Target+0x948)。本页只记录架构相关字段,以及 allocator 和引擎布局如何消费它们。对这里未复述的任何偏移,应以该页为准。

入口点

text
Target::Init                                       ── builds + installs the sub-object
  └─ SparseCoreTarget::Init (0x1D612B20)           ── populate geometry from SPARSE_CORE TpuCoreParts
       ├─ TpuCoreParts::SequencerCount(cp, 5=TEC)  ── SparseCoreTiles  → [0x948]+0x90
       ├─ SequencerParts(5).vector_isa().lane_count── SparseCoreLaneCount → [0x948]+0x94
       └─ MemoryParts(TILESPMEM/SPMEM/SMEM/SFLAG)  ── per-tier capacity + word-size block [0x948]+0x10..+0x54

Target::SparseCore*  (accessor surface)            ── each gates on vtable[+0x260] then loads [0x948]+field
  └─ Target::SupportsSparseCore (0x1D48FD40)       ── TpuTopology[+0x98] > 0  (the presence gate)
```text

### 算法

`SparseCoreTarget::Init`(`0x1D612B20`)— 几何块,已对照反编译逐字节确认(十进制偏移来自反编译器;十六进制在注释中):

```c
function SparseCoreTarget_Init(sc, core_type, topology, target, core_parts):  // 0x1D612B20
    sc[88]  = 4;                                  // +0x58 HBM 4b-word size (literal)
    sc[116] = 2;                                  // +0x74 SCS sequencer/group count (literal)
    sc[144] = TpuCoreParts.SequencerCount(core_parts, 5);   // +0x90 SparseCoreTiles (seq type 5 = tile-exec)
    vi      = TpuSequencerParts.vector_isa(core_parts.SequencerParts(5));
    if vi.has_vector_isa == 0: FATAL;             // tile-exec sequencer must carry a VectorIsa
    sc[148] = vi.lane_count;                       // +0x94 SparseCoreLaneCount
    sc[152] = vi.lane_count << 2;                  // +0x98 SC lane bytes
    if core_parts[488 /*+0x1E8 SparseCore submsg present*/]:
        sc[160] = core_parts[480];                 // +0xA0 tile_hbm_bandwidth_bytes_per_cycle (+0x1E0)
        sc[164] = core_parts[484];                 // +0xA4 stream_granule_size (+0x1E4)
    // per-tier capacity/word block (+0x10..+0x54) populated from MemoryParts(type) above;
    // barrier sync-flag base/count (+0x1D0/+0x1D4) from a SpecialPurposeSyncFlags sub-object.

特殊点 — 有两套 TpuSequencerType 编号;Init 使用 codec-template 那套。 几何信息以 codec-template enum {SCS=3, TAC=4, TEC=5} 中的 TpuSequencerType 值 5 = TEC 为键。这正是 TpuCoreParts::SequencerCount/SequencerParts0x20B2AA20/0x20B2AA60)实际采用的索引空间 — 两者都会对 6 槽 presence mask 做 bittest64,并在 index >= 6ud1 trap,因此有效范围是 0..5,slot 5 是 tile-execute 池。Init 读取 SequencerCount(5)/SequencerParts(5) 来确定 tile 和 lane 大小,而 chip_parts proto 将该 16-instance 池标记为 tile-execute geometry。TpuSequencerTypeToString 渲染的 C++ TpuSequencerType enum 使用这同一套 codec-template 编号(off_22010DE0[3]="SparseCoreSequencer"[5]="…TileExecuteCoreSequencer")。另一套编号是 protobuf enum TpuSequencerTypeProto,它保留 INVALID=0,因此把同样的引擎编号为 {SCS=4, TAC=5, TEC=6} — 比 C++ enum 大一。chip_parts proto 将字段存为 TpuSequencerTypeProto,但 TpuSequencerParts::FromProto0x20b30700)会先通过 TpuSequencerTypeFromProto(减一),之后才索引 core-parts,因此内存中的 TpuCoreParts 以 codec-template {3,4,5} enum 为键。重实现者必须用 {3,4,5}(即 Init 使用的那套)索引 TpuCoreParts;喂入原始 proto {4,5,6} ordinal 会读错池。完整 off-by-one 对齐见 getSequencerType

Accessor Surface 和存在性 Gate

每个 Target::SparseCore* accessor 都遵循同一种形状:dispatch +0x260 虚拟 gate,若为 false 则 LOG(FATAL),然后从 [0x948]+field 加载一个标量。反编译器将 Target+0x948 渲染为 *((_QWORD*)this + 297)297 × 8 = 0x948)。SparseCoreTiles0xFAAFA40)的代表性函数体:

c
function Target_SparseCoreTiles(this):              // 0xFAAFA40
    if !vtable[+0x260](this):                        // SupportsSparseCore
        LOG(FATAL) << "SparseCore is not supported by this target";  // target.h:1704
    return *(u32*)(this[297] + 0x90);                // [0x948] + 0x90  (v7x = 16)
```text

| Accessor | Address | Reads | v7x value |
|---|---|---|---|
| `SparseCoreTiles` | `0xFAAFA40` | `[0x948]+0x90` | 16 |
| `SparseCoreLaneCount` | `0xF7906E0` | `[0x948]+0x94` | 16 |
| `SparseCoreHbm4bWordSizeBytes` | `0x1320C220` | `[0x948]+0x58` | 4 |
| `SparseCoreStreamGranuleSizeBytes` | `0x13886EE0` | `[0x948]+0xA4` | 4 |
| `GetSparseCoreBarrierSyncFlagCount` | `0x10972FA0` | `[0x948]+0x1D4` ||
| `SupportsSparseCore` | `0x1D48FD40` | `TpuTopology[+0x98] > 0` | true |
| `SparseCoresPerLogicalDevice` | `0x135159C0` | `CoresPerChip / LogicalDevicesPerChip` | 2 |

> **易错点 —** gate 是*运行时拓扑*测试,而不是 class 测试。`SupportsSparseCore` 读取 `*(u32*)(TpuTopology* /*Target+0x3B8*/ + 0x98) > 0`,而不是 `[0x948]` 指针。重实现必须在任何 SC accessor 可达之前设置拓扑描述符的 SparseCore-count 字段;否则整个 accessor surface 都会以 `target.h:1704` FATAL trap。在 BarnaCore 世代(Jellyfish/Dragonfish/Pufferfish)以及仅 TC 的 lite die 上,该 count 为零,因此 `Target::Init` 永远不会构建子对象。

---

## Engine → Core 布局

### 目的

SC 计算结构由三个 VLIW 子引擎组成,每个都是拥有自身 bundle stream 和 codec 的独立机器。架构相关事实在于它们如何映射到*物理 core 和 tile*:一个 TEC sequencer 就是一个 tile,tile 数是 `SparseCoreTiles`,而 SCS/TAC/TEC 名册按世代变化。

### 布局

```text
                     ┌──────────────────────── SparseCore (one of SparseCoresPerLogicalDevice) ─────────────┐
                     │                                                                                      │
  codec-tmpl type 3SCS  (1 control sequencer, 32 B bundle)                                             │
  (SPARSE_CORE_SEQ)  │   ├─ program counter, address arithmetic, circular buffers                          │
                     │   ├─ chip-register reads (GTC, tile id, sparse-core id, DMA credits)                 │
                     │   └─ atomic + sync-flag slot (coordinates tiles ↔ tiles, SC ↔ TC)                    │
                     │                                                                                      │
  codec-tmpl type 4TAC  (tile-access core, 64 B bundle; VF/GL ONLY — absent on 6acc60406)              │
  (SPARSE_CORE_TILE_ │   └─ stream slot issues tile-fetch DMA  HBM[base + idx·stride] → TILE_SPMEM          │
   ACCESS_CORE_SEQ)  │      (no FPU, no vector ALU, no vector load/store — pure address + DMA)              │
                     │                                                                                      │
  codec-tmpl type 5  │  TEC  ×SparseCoreTiles  (tile-execute cores, 64 B bundle — one TEC = one tile)       │
  (SPARSE_CORE_TILE_ │   ├─ 3 vector ALU slots + vector load/store + vector-extended + vector-result        │
   EXECUTE_CORE_SEQ) │   ├─ vector_isa.lane_count lanes (= SparseCoreLaneCount, read at SequencerParts(5))  │
                     │   └─ on 6acc60406: TEC stream slot also issues tile-fetch DMA (TAC role absorbed)    │
                     └──────────────────────────────────────────────────────────────────────────────────── ┘

芯片携带 SparseCoreTiles 个 TEC core;每个 TEC 拥有一个 TILE_SPMEM 窗口(向量 ALU 在其上计算的每 tile working set)以及一个 TIMEM(每 tile instruction memory)。单个 64-byte TEC bundle 可以在一个周期内发出三个 vector ALU op、一个 vector load、一个 vector store、一个 vector-extended op(scan/sort/uniquify)、一个 vector-result pop、两个 scalar ALU slot、immediate、DMA 和一个 stream slot — 这就是为什么该引擎是整个 SparseCore 中最宽的计算表面。

每代名册

引擎名册在不同 silicon 上不是常量。判别依据是每 codename 的 codec family;是否存在 SparseCore<Engine>CodecBase class 是二进制中的直接读数。

GenCodenameFamily nsSCSTACTECTile-fetch issuerSCs/TCSCs/chip
v5pViperfishvxc.vfcYYYTAC stream48
v6eGhostlitegxc.glcYYYTAC stream48
v7x6acc60406gxc.gfcYYTEC stream(无 TAC)44

特殊点 — 6acc60406(gfc)世代将 SCS+TAC+TEC 三引擎流水线折叠为 SCS+TEC。gfc namespace 中有零个 SparseCoreTac* symbol;TEC 通过自身 stream slot(IndirectStream / IndirectVregStream / LinearStream / StridedStream)吸收地址生成 + DMA 发出职责,而 SCS 计算 gather 地址,TEC 通过 tile_wait_scs_smem 读取它们。对 tile 分配而言,这改变的是谁消费 tile,而不是如何分配 tile — bump allocator 从来不知道 TAC。absorbed-role 细节见 TAC Engine,每引擎 bundle surface 见 SCS (Scalar) Engine / TEC (Vector) Engine

注意事项

Jellyfish/Dragonfish/Pufferfish 世代没有 SparseCore(它们拥有的是已退役的 BarnaCore embedding accelerator)。在这些 target 上,存在性 gate 返回 false,本文描述的整个 SC 架构都不存在。每芯片 SC 数是 SparseCoreCountPerTensorCore × tensor_cores_per_chipSparseCoreCountPerTensorCore0x1C6CB760)计算 sparse_core_count_per_chip / LogicalDevicesPerChip,并同时断言 sparse_core_count_per_chip >= tensor_core_count_per_chip(lowering_util.cc:4488)和 … % … == 0(line 4489)— 也就是整数 4:1 SC:TC 比率。


片上内存模型

目的

SC 拥有四个地址空间层级,作用域递减、速度递增。编译器以 word 为单位寻址每个 SC SRAM 层级,其中 word 宽度按层级、按世代变化,来自 SparseCoreTarget capacity/word block。架构关键事实是 TILE_SPMEM 和 SPMEM 共享同一块物理 SRAM — TILE_SPMEM 是从 SPMEM 中 carve 出的 stripe,而不是独立 bank。

四个层级

TierScopeSize classLLVM addrspaceCapacity fieldWord-size fieldUse
HBM芯片范围(与 TC 共享)GB203Target::HbmSizeBytes[0x948]+0x38 4b-word countEmbedding table、gradient buffer、spill
SPMEM芯片上所有 SC coreMB202[0x948]+0x2C[0x948]+0x4C跨 SC 通信、大缓冲区、tile backing store
TILE_SPMEM每 tile(一个 TEC)KB201[0x948]+0x28[0x948]+0x48向量 ALU 计算的 local working set
TIMEM每 tile 指令内存small214[0x948]+0x10[0x948]+0x50Tile-local kernel code

SC 还暴露按 scope 的 SMEM(标量内存,addrspace 0;TEC scope 中每 tile 的 smem_tile 219,每 SCS 的 smem_scs 224)和 SFLAG(sync-flag 池,addrspace 204;每 tile 的 sflag_tile 217,每 SCS 的 sflag_scs 223)。mlir::sparse_core::MemorySpace enum(1-based)通过 .rodata 0xAF36CE8 处的 22-entry table 映射到 LLVM address space,由 MemorySpaceToAddressSpace0x14B78780)解码;scope(SCS vs TEC/tile)决定逻辑 spmem/timem 请求最终落入哪个物理 addrspace。

SPMEM ↔ TILE_SPMEM 拆分

text
SPMEM (chip-wide SC SRAM, capacity [0x948]+0x2C)

├─ divided evenly into SparseCoreTiles regions:  total_spmem_bytes % num_tiles == 0
│                                                 num_tiles <= sequencer_count_
│                                                 num_tiles % num_groups == 0

└─ one tile's TILE_SPMEM window  = a stripe of SPMEM, aligned to a full vector-lane stripe
       private tile base        = SPMEM bump offset / SparseCoreTiles   (per-tile partition; 0x134DD340)
       stripe granularity        = 32 bytes   (SparseCoreSpmemStripeGranularityBytes, 0x1D499440)
       allocation alignment       = SpmemAlignment = (SparseCoreTiles × lane_count) / 4 words  (0x13DC5500)
```text

`SpmemAlignment`(`0x13DC5500`)返回 `*(u32*)(Target[0x948] + 0x90) × lane_count / 4` — 即 `+0x90` 的 tile 字段乘以向量 lane count,再除以 4。其意图是把每个 SPMEM/TILE_SPMEM 分配向上取整到完整 vector-lane stripe,使 TEC 的 vector load/store 总是命中对齐地址;匹配的 alignment-in-bits 检查会拒绝 `numElements × elementBitWidth` 不是 `SpmemAlignmentInBits` 倍数的 tile store。

> **注意 —** 逐字节反编译(`0x13DC5500`)计算 `*(u32*)(SparseCoreTarget[+0x90]) × lane_count / 4`,其中 `+0x90` 字段是 tile count(`SequencerCount(5)` 槽位),`lane_count` 通过 `SparseCoreTarget` vtable slot `+0xD0` 的虚函数获取。重实现者应从*实际两个几何字段*(`+0x90` 和向量 lane count)计算分子,而不是使用硬编码常量。(反编译会让整个 accessor 经过 `SupportsSparseCore()` gate — `target.h:1704` FATAL — 并在 lane-count optional 缺失时返回 1。)

每 tile sync-flag 预算在 `Init` 中推导为 `tec_sflag_capacity_bytes / sflag_bytes_per_word`(逐字节确认:`[a5+0x18] / [a5+0x40]`,其中 `[a5+0x18]` 是 `tec_sflag_parts.word_count × bytes_per_word`,`[a5+0x40]` 是 `scs_sflag_parts.bytes_per_word`);四个编译器保留 sync-flag watermarks 是该商值 `− {1,2,3,4}`(存于 `[a5]+0x1EC/0x1F0/0x1F4/0x1FC`),它们从用户池中保留 TAC↔TEC(或在 `6acc60406` 上为 SCS↔TEC)握手 flag。跨引擎地址空间目录见 [Memory Hierarchy](../targets/memory-hierarchy.md)。

### 注意事项

Embedding table **不会**放在 SC SRAM 中。它们位于 HBM,并在每次 lookup 时流入 TILE_SPMEM。因此“table allocation”是 HBM 放置(由运行时 BestFit allocator + memory-space assignment 处理,不属于 SC 范围)加上下文记录的 logical-replica row sharding。片上层级只保存瞬时的每窗口 working set。

---

## Tile 分配算法

### 目的

几何信息已知后,SC-MLO 编译器必须把每个中间缓冲区放入四个片上层级。它**不**使用运行时 HBM/VMEM best-fit allocator。SC 片上 allocator 是**带 scope stack 的每地址空间 bump allocator**,完全实现于 `AllocationAssignmentPass` MLIR pass。没有 free list、没有 coalescing、没有 splitting;`Deallocate` 是 no-op(`TileOverlayAllocationPass::ElideDeallocs` 会逐字面移除每个 `memref.dealloc`)。

### 入口点

```text
AllocationAssignmentPass::runOnOperation (0x134D8240)   ── SC tile/SMEM/SFLAG bump-allocate driver
  ├─ reset allocation_stack_ to frame 0 (root)
  ├─ pre-reserve HW-reserved regions in addrspaces {20 timem-SCS, 14 sflag-tile, 21 timem-other}
  ├─ param_table_size_ = GetParamTableSize(module)       ── 0x13DABDA0 (sc.param_table_size attr)
  ├─ IterateOverFunctions (twice): global+SCS, then per-func allocations
  │    └─ WalkOpsForAllocations → Allocate(memref, core, is_circular_buffer)  ── 0x134DB1E0
  │         ├─ GetMemRefSize (0x134DC1C0)                ── byte→word + alignment round-up
  │         └─ MemoryUsage::Reserve (0x134D9700)          ── the bump primitive
  ├─ for each sc_tpu.tile_task: PushToStackForTileAllocations (0x134DD340) → allocate → PopStack
  └─ emit sc.alloc_high_water_mark + sc.execute_alloc_high_water_mark

GlobalAllocationAssignmentPass::DoAllocations (0x1351BF80)  ── capacity check + llvm_tpu.spill_ranges
TileOverlayAllocationPass::runOnOperation (0x136025E0)      ── TIMEM overlay bump alloc
PrepareHbmSpillPass::runOnOperation (0x135F3B60)            ── per-SC HBM spill stack

算法 — bump 原语

MemoryUsage::Reserve0x134D9700)是核心,已对照反编译逐字节确认:它在 absl::flat_hash_map<int /*addrspace*/, long /*words*/> 中查找当前地址空间的 bump 指针,将其作为新分配的 base 返回,并按请求大小推进它。

c
function MemoryUsage_Reserve(usage, memory_space, size):   // 0x134D9700
    addrspace = MemorySpaceToAddressSpace(memory_space)    // enum → LLVM addrspace (0x14B78780)
    slot      = usage_map.find_or_insert(addrspace)         // flat_hash_map<int,long>
    base      = slot.value                                  // current bump pointer (words)
    slot.value = base + size                                // bump
    return base                                             // base offset of the new allocation
```text

### 算法 — `Allocate` 和 circular-buffer(VF/GL)防护

```c
function Allocate(memref, opt_core, is_circular_buffer):    // 0x134DB1E0
    space = GetMemorySpace(memref.type)
    // remap (space, core_scope) → effective addrspace; core arrives as 0x100000000|core:
    //   0x100000000 = SCS scope, 0x100000002 = TEC/tile scope
    //   timem + SCS → 20 ; timem + TEC → 14 ; spmem + SCS → 21 ; spmem + TEC → 16
    //   reject smem/sflag outside the remap with FATAL:
    //     "memory_space != MemorySpace::smem && memory_space != MemorySpace::sflag"
    size  = GetMemRefSize(memref)                            // 0x134DC1C0 (words, alignment round-up)
    base  = top_frame.Reserve(eff_space, size)              // bump
    last  = base + size - 1
    hi    = GetUserAllocatableWordOffsets(target, eff_space) // 0x13DABC00 — per-space user window upper bound
    if last > hi:
        error("current allocation offset upper bound ({last} words) exceeds the legitimate "
              "user allocatable offset upper bound ({hi} words) in memory space {space} ...")
    // THE ONLY PER-GEN CODE BRANCH (byte-exact gate at 0x134DB1E0:195):
    //   guard fires iff (DeepseaVersion − 3) <= 1, i.e. version in {3,4} = Viperfish OR Ghostlite
    if (uint)(target[+0x398 /*DeepseaVersion*/] - 3) <= 1 && is_circular_buffer && last > hi - 8:
        emitOpError("Attempting to allocate circular buffer into last entry of TileSpmem. "
                    "This will result in an out-of-bounds tile-local stream on VFC due to a HW bug. ...")
    return base

GetMemRefSize0x134DC1C0)计算 byte_size = numElements × elementBitWidth / 8,检查字节大小是对应层级 word size 的倍数(否则 "memref is not padded correctly"),再除以 word 得到 word 数,并向上取整到 WordAlignmentInBytes / WordSizeInBytes

算法 — scope stack 和 tile sub-frame

allocator 保存 allocation_stack_,它是由 40-byte frame 组成的 std::vector<MemoryUsage>PushToStack0x134DA8C0)追加一个复制父级 bump-pointer map 的 frame,因此 nested scope 会在父级 high-water mark 之上分配;PopStack0x134DAB00)丢弃顶层 frame(nested allocation 由栈纪律回收),并断言 allocation_stack_.size() >= 2 — frame 0 是持久 root。

PushToStackForTileAllocations0x134DD340)push 一个 tile sub-frame,并从 SPMEM bump 指针 carve TILE_SPMEM,已逐字节确认:

c
function PushToStackForTileAllocations(is_shared):          // 0x134DD340
    PushToStack()
    top.tile_spmem_snapshot = top[addrspace 0x10]           // snapshot spmem-TEC bump ptr
    top.tile_sflag_snapshot = top[addrspace 0x0E]           // snapshot sflag-tile bump ptr
    top.tile_unused         = 0
    spmem_base  = top[addrspace 3]                          // current SPMEM bump pointer
    spmem_align = SparseCoreTarget[+0x90] * lane_count / 4   // = SpmemAlignment (words)
    CHECK(spmem_base % spmem_align == 0)
        // "prev_frame[kSpmemAddressSpace] % LlvmTpuDialect::SpmemAlignment(target_) == 0"  (alloc pass:737)
    if is_shared:  tile_base = top[addrspace 2]             // shared TILE_SPMEM window
    else:          tile_base = top[addrspace 3] / SparseCoreTarget[+0x90]
                                                            // private tile window = SPMEM offset / SparseCoreTiles
                                                            // (the SPMEM bump ptr divided by the tile count — partitions SPMEM into per-tile windows)
    top[addrspace 2] = tile_base                            // set the tile bump pointer (MemoryUsage::operator[](…,2))
```text

> **特殊点 —** “tile fetch”和“tile evict”不是 RAM 操作 — 它们是 bump-and-pop。获取下一个 tile 是 bump-allocate 到下一个空闲 TILE_SPMEM 窗口;evict 是 `PopStack`,它回退 bump 指针,使 tile N+1 复用 tile N 释放的 offset。没有单独的 tile-cache SRAM;TILE_SPMEM *就是* cache,eviction 隐含在 stack discipline 中。把它建模为带显式 eviction 的独立 cache 会导致 working set 大小错误。

### Embedding Row Sharding

由于 table 位于 HBM,唯一的“per-SC”放置是 row sharding。`GetLogicalReplicaInfo`(`0x13CA1AE0`)在两个不变量下求解 `{logical_replica_count, feature_dim_split_factor, sample_dim_split_factor}`,两者都由 CHECK 字符串逐字节确认:

```text
logical_replica_count % physical_sparse_cores == 0          (logical_replica_util, line ~147)
RoundUpToPowerOf2(logical_replica_count) == logical_replica_count   (line ~186)

每张 table 被拆成 logical_replica_count 个 row-shard;row r 落入 shard r mod logical_replica_count;shard 以 round-robin 映射到 physical_sparse_cores。由于该 count 是 2 的幂,并且能整除 SC 数(GetNumSparseCores0x13C9EBA0 = num_partitions × num_replicas × SCs_per_TC),每个 SC 的 row 数是均匀的。

Minibatch Fit

lookup window 的可变大小 ID 必须适配 TILE_SPMEM。CalculateVariableSizeWords0x13CA3F20)计算 fit 不等式,其错误字符串已逐字节确认:

c
// byte-exact at 0x13CA3F20: a3 = max_nz_per_row, a4 = logical_replicas
max_nz_per_row_partitioned = max(ceil(a3 / a4), SparseCoreLaneCount)   // clamp floor = [0x948]+0x94, the lane count (v7x = 16) — NOT sparse_cores_per_chip
variable_size_words        = num_variable_size_allocations × max_nz_per_row_partitioned  // num_variable_size_allocations = virtual method (*(estimator+0x38))()
require variable_size_words <= tile_spmem_words
    // "Variable size allocations (%d * %d = %d words) do not fit in TileSpmem (%d words)."
```text

如果 batch 的 ID 超过该限制,mini-batching 会把 sample batch 拆成每个都能适配的窗口。`num_variable_size_allocations` 是 pipeline buffer 数(double-buffer ⇒ 2);六个 `allocation_estimator` 子类各自为自己的 pipeline-stage buffer 定大小。

### HBM Spill

当片上 working set 超过 SPMEM/TILE_SPMEM 时,`PrepareHbmSpillPass`(`0x135F3B60`)会供应一个每 SC 的 HBM-backed spill stack,大小为 `FLAGS_xla_sc_hbm_spill_stack` 个 4-byte word(addrspace 4/HBM 中的 1-D i32 memref),另加一个 11-word SMEM scratch pad,由 `sc_tpu.hbm_spill_stack_capture` 捕获。`DoAllocations`(`0x1351BF80`)发出 `llvm_tpu.spill_ranges`(`{smemStart/Limit, tilespmemStart/Limit}`),标记哪些 offset 驻留、哪些 spilled。spill 关闭时(`flag == 0`),过大的分配是硬编译错误(`"TileSpmem high-water mark exceeds memory capacity"`)。

### 函数映射

| Function | Address | Role |
|---|---|---|
| `AllocationAssignmentPass::runOnOperation` | `0x134D8240` | bump-allocate driver(保留 HW 区域、遍历 allocas、遍历 tile task) |
| `…::Allocate(MemRefType, opt<Core>, bool)` | `0x134DB1E0` | 分配一个 memref → base word offset;VFC 防护 |
| `…::GetMemRefSize(MemRefType)` | `0x134DC1C0` | byte→word + padding/alignment round-up |
| `…::MemoryUsage::Reserve(MemorySpace, long)` | `0x134D9700` | bump 原语(flat_hash_map) |
| `…::PushToStack` / `PopStack` | `0x134DA8C0` / `0x134DAB00` | scope-frame push(复制父级指针)/ pop(`size >= 2`) |
| `…::PushToStackForTileAllocations(bool)` | `0x134DD340` | tile sub-frame;在 stripe 边界从 SPMEM carve TILE_SPMEM |
| `GlobalAllocationAssignmentPass::DoAllocations` | `0x1351BF80` | capacity check + `llvm_tpu.spill_ranges` emit |
| `TileOverlayAllocationPass::runOnOperation` | `0x136025E0` | TIMEM overlay bump alloc;elide deallocs |
| `PrepareHbmSpillPass::runOnOperation` | `0x135F3B60` | 每 SC HBM spill stack |
| `lowering_util::GetUserAllocatableWordOffsets` | `0x13DABC00` | 每空间 user-window 上界 |
| `xla_mlo_util::{WordSizeInBytes,WordAlignmentInBytes,CapacityInBytes}` | `0x14A89D00` / `0x14A89E20` / `0x14A89EE0` | 每层 word 宽度 / 对齐 / 容量 |
| `mlir::sparse_core::MemorySpaceToAddressSpace` | `0x14B78780` | enum → LLVM addrspace(table `0xAF36CE8`) |
| `LlvmTpuDialect::SpmemAlignment` | `0x13DC5500` | `(tiles × lane_count) / 4` words |
| `logical_replica_util::GetLogicalReplicaInfo` | `0x13CA1AE0` | row-shard / replica-count solver(pow-2,整除 #SC) |
| `logical_replica_util::GetNumSparseCores` | `0x13C9EBA0` | mesh 上总 SC 数 |
| `lowering_util::SparseCoreCountPerTensorCore` | `0x1C6CB760` | `SCs_per_chip / TCs_per_chip`(4:1|
| `VariableWindowAllocationEstimator::CalculateVariableSizeWords` | `0x13CA3F20` | TILE_SPMEM minibatch-fit 不等式 |

### 每代差异

| Aspect | Viperfish (VF) | Ghostlite (GL) | `6acc60406` (GF) |
|---|---|---|---|
| Allocator algorithm | bump + stack | bump + stack | bump + stack(相同) |
| SCs per TC / per chip | 4 / 8 | 4 / 8 | 4 / 4 |
| Tile-fetch issuer | TAC stream | TAC stream | TEC stream(无 TAC) |
| Access/Execute scope split | TAC + TEC | TAC + TEC | SCS + TEC(`tile_wait_scs_smem`) |
| TILE_SPMEM / SPMEM word + capacity | chip_parts(`[0x948]+0x48/+0x28`,`+0x4C/+0x2C`) | chip_parts | chip_parts |
| SPMEM stripe granularity | 32 B | 32 B | 32 B |
| `SpmemAlignment` (words) | `tiles·lane/4` | `tiles·lane/4` | `tiles·lane/4` |
| Circular-buffer-in-last-entry | **HW bug — guarded** | **HW bug — guarded** | OK |
| HBM spill | yes(flag) | yes(flag) | yes(flag) |

> **易错点 —** 整个 allocator 中*唯一*真正的每代代码分支是最后一个 TILE_SPMEM entry 中 circular-buffer 的防护。它的 gate(逐字节,`0x134DB1E0:195`)是 `(uint)(Target[+0x398 DeepseaVersion] − 3) <= 1`,也就是对 DeepseaVersion **3(Viperfish)和 4(Ghostlite)**触发 — *不只是* Viperfish — 同时还要求 `is_circular_buffer` 且 `last > hi − 8`。诊断文本提到 “VFC”(最早表征该 HW bug 的芯片),但代码路径同时对 VF 和 GL 应用防护;只有 `6acc60406`(version 5)豁免。其他每代差异 — 容量、word 宽度、tile 数、TAC 是否存在、每芯片 SC 数 — 都通过 `SparseCoreTarget`/`TpuCoreParts` 数据驱动,没有代码分支。重实现者应把 allocator 视为 generation-agnostic,并把所有每代变化推入几何描述符。

---

## 相关组件

| Name | Relationship |
|---|---|
| `SparseCoreTarget::Init`(`0x1D612B20`) | 填充本页架构所读取的 geometry struct |
| `Target::SupportsSparseCore`(`0x1D48FD40`) | 每个 accessor 和 allocator dispatch 都会经过的存在性 gate |
| `AllocationAssignmentPass`(`0x134D8240`) | 将几何信息转成已放置缓冲区的 bump/stack allocator |
| `SparseCoreHierarchicalSpmdPartitioner` | 将 SC program I/O pad 到 logical-replica shard 边界(SPMD plumbing) |

## 交叉引用

- [SparseCore 概览](overview.md) — Part IX 的导航入口;引擎名称、每代存在性和数据路径。
- [SparseCoreTarget (`Target+0x948`)](../targets/sparsecore-target-descriptor.md) — 逐字节字段映射、24 个虚拟 accessor,以及本页所综合的每 codename MXU 表。
- [SC 后端流水线](sc-backend-pipeline.md) — 运行 `AllocationAssignmentPass`(以及 MEGACORE barrier)的 SC-MLO pass pipeline。
- [SC Core 选择](sc-core-selection.md) — computation 如何分配给物理 SparseCore。
- [getSequencerType](getsequencertype.md) — SCS/TAC/TEC 引擎选择函数和 sequencer-type enum
- [Region → Sequencer Outliner](region-to-sequencer-outliner.md) — 将 SC computation 切分为每引擎 bundle stream。
- [SCS(标量)引擎](scs-engine.md) · [TAC 引擎](tac-engine.md) · [TEC(向量)引擎](tec-engine.md) — 三个子引擎 bundle surface。
- [GetSparseCoreConfig](getsparsecoreconfig.md) — 后端与该几何信息一同读取的 offload op-type 配置。
- [Memory Hierarchy](../targets/memory-hierarchy.md) — SC 层级所嵌入的跨引擎地址空间目录。
- [每 Codename 常量表](../targets/per-codename-hw-constants.md) — 从 `chip_parts` 获取的每代 memory/core-count 表,几何信息据此解码。
- **Binary:** `extracted/libtpu-0.0.40-cp314-cp314-manylinux_2_31_x86_64/libtpu/libtpu.so`(build-id `89edbbe81c5b328a958fe628a9f2207d`)
- **Index entry:** Part IX — SparseCore & BarnaCore / SparseCore engines — [返回索引](../index.md)