Skip to content

Stream Gather/Scatter

本页中的每个地址、字段偏移、opcode 值和 enum 值,均读取自 libtpu-0.0.40-cp314 wheel 中的 libtpu.so(build-id 89edbbe81c5b328a958fe628a9f2207d;build libtpu_lts_20260413_b_RC00)。.text VA 等于文件偏移 0xe63c000.rodata 位于 0x84a0000 -- 二者均为恒等映射。除非另有标记,地址均来自 gfc(6acc60406 / TPU7x)实例;glc(Ghostlite, v6e)和 vfc(Viperfish, v5)同族实例在不同地址上承载相同 schema。其他版本会有所不同。

摘要

SparseCore Stream 引擎是间接 DMA 数据路径:硬件将 embedding id 列表转换为 HBM gather(table -> tile)或 scatter(tile -> table)事务流,并可选地在目标端执行原子读-改-加。它是一个 VLIW slot -- Stream sub-bundle -- 由单个 proto 消息 SparseCoreStream 编码而来,该消息在 SCS、TAC(仅 vfc/glc)和 TEC bundle 中以相同形式出现。这个 slot 正是 概览 中 host-table -> HBM -> SC gather 路径实际执行的部分:当概览说“TAC/TEC stream slot 发出 tile-fetch DMA”时,本页描述的就是该 slot 的位布局、编译器为它构建的描述符,以及硬件执行的逐元素地址运算。

SparseCoreStream 是一个共享 header 加上四路 oneof寻址形式LinearStream(连续)、StridedStream(一个 stride 维度)、IndirectStream(embedding gather/scatter -- id 列表驱动地址)和 IndirectVregStream(id 列表从 vector register 而非内存流入;见 IndirectVregStream)。四者共享完全相同的 stream-control 字段尾部 -- sync flag、tile-local stride、filter、length-type、StreamOpcode 累加模式、OffTileMemoryType 池选择器,以及两个标量操作数。只有开头的操作数组不同。oneof 判别符会成为 6-bit slot opcode:LinearStream=0x3bStridedStream=0x3aIndirectStream=0x39IndirectVregStream=0x38

重新实现表面分为三层,本文将每层作为独立单元记录:(1)slot 编码 -- SparseCoreStream proto、其共享 header 和公共控制尾部、每种形式的 opcode,以及 IndirectStream 形式的位精确 encode/decode 映射;(2)间接 DMA 描述符 -- MLIR sc_tpu.indirect_stream_start op,及其四个操作数组 {Src, Dst, Offset, Sflag}Indicestpu.enqueue_indirect_dma 如何 lower 到它,以及逐元素地址公式 addr_i = table_base + offset_list[i] * indirect_list_stride;(3)粒度模型 -- IndirectListType(word vs row offset)、OffTileMemoryType(HBM vs HBM_4B)、IndirectLengthType(fixed vs variable window)和 TileLocalStride(32 B...2 KB)如何设定读/写粒度。

对重新实现而言,契约如下:

  • 一个 proto,三个 bundle slot。 SparseCoreStreamSparseCoreStreamEncoder::Encode(SCS bundle,32 B)、SparseCoreTecStreamEncoder::Encode(TEC bundle,64 B)和 SparseCoreTacStreamEncoder::Encode(TAC bundle,64 B -- 仅 vfc/glc)编码。三者都接收 SparseCoreStream const&。各引擎之间字段语义相同;只有 slot 在 bundle 内的 bit 位置不同。
  • oneof 就是 opcode。 选择形式 #8...#11 会选择 6-bit opcode 0x3b/0x3a/0x39/0x38。除形式判别符外没有单独的 opcode 字段;encoder 在 bundle bit 181 写入 opcode。
  • StreamOpcode 是累加模式,与形式正交。 GATHER/SCATTER 移动行;*_INTEGER_ADD/*_FLOAT_ADD 变体在目标端执行原子读-改-加。对 HBM 的 SCATTER_FLOAT_ADD(6)是 TensorCore MXU 无法执行的 embedding-gradient 累加原语。
  • 描述符就是 MLIR op 的操作数组。 id 列表是 OffsetIndices 组;连续的 Linear/Strided 形式会去掉它。逐元素 row-stride 乘法是硬件 Stream 引擎内建的 -- lowering 中没有逐元素 imul;编译器只分配操作数组并设置 indirect_list_stride 属性。
Proto messageSparseCoreStream -- 共享 header(#1...#7)+ 4-form oneof#8...#11
SCS encoderSparseCoreStreamEncoder::Encode @ 0x1eb9b4c0(gfc;32 B bundle)
TEC encoderSparseCoreTecStreamEncoder::Encode @ 0x1ebe33e0(gfc;64 B bundle)
TAC encoderSparseCoreTacStreamEncoder::Encode @ 0x1e8ee040(vfc;另 glc @ 0x1ea338e0;gfc 中不存在)
Form opcodes(6-bit @ bit 181)Linear 0x3b · Strided 0x3a · Indirect 0x39 · IndirectVreg 0x38
Accumulate modeStreamOpcode(3-bit)-- GATHER=0...SCATTER_FLOAT_ADD=6
Off-tile poolOffTileMemoryType(3-bit)-- SPMEM=0, TILE_SPMEM_N=1, HBM=2, HBM_4B=3
Descriptor opsc_tpu.indirect_stream_start / .indirect_stream_add_startAtLeastNOperands<6>
Per-element addressaddr_i = table_base + offset_list[i] * indirect_list_stride(HW;row-stride 缩放内建)
Per-gen presenceSCS+TEC Stream 存在于全部 3 个 SC 世代;TAC Stream 仅 vfc/glc(gfc 无 TAC)
ConfidenceCONFIRMED(encode-offset / decode-shift / opcode-value 已与 decompile 双重核对),除非行内另有说明

SparseCoreStream Proto 和四种形式

目的

SparseCoreStream 是每个 Stream-engine slot 编码所依据的单一消息。它的职责是描述一个地址生成 stream -- 读或写什么、base 在哪里、逐元素地址如何计算,以及如何发出完成信号 -- 与承载它的子引擎无关。四种 oneof 形式是共享同一个控制面的四个地址生成器;embedding gather/scatter 正是 IndirectStream 形式。

消息布局

共享 header(#1...#7)和四形式 oneof#8...#11):

text
message SparseCoreStream {                          // the Stream sub-bundle slot
  // ---- shared header (all forms) ----
  #1  normal_predication            : SparsecoreNormalPredication  (3-bit)
  #2  normal_predication_inversion  : bool
  #3  rotate_predication            : SparsecoreRotatePredication  (4-bit, 16-entry ring)
  #4  is_rotate_predication         : bool
  #5  latency                       : uint32        // scheduling-only, not emitted
  #6  resource_usage                : map<string,uint32>   // scheduling-only
  #7  bit_width                     : uint32        // scheduling-only
  // ---- oneof addressing form ----
  #8  linear_stream                 : LinearStream         // opcode 0x3b
  #9  strided_stream                : StridedStream        // opcode 0x3a
  #10 indirect_stream               : IndirectStream       // opcode 0x39  ← gather/scatter
  #11 indirect_vreg_stream          : IndirectVregStream   // opcode 0x38
}
```text

字段 `#5...#7`(`latency`、`resource_usage`、`bit_width`)是调度元数据;encoder 不会将它们发射到 bundle 中(它们驱动 SC 指令调度器,而不是硬件 slot)。四种形式只在*开头*的操作数上不同;它们都以如下相同的控制尾部结束。

### 公共控制尾部

每种形式都以一块相同的 stream-control 字段结尾。这里以 `IndirectStream` 字段名展示(由于开头操作数不同,各形式的编号也不同):

| Field | Type / width | Role |
|---|---|---|
| `sync_flag_count_type` | `SyncFlagCountType` (1) | 完成计数单位:WORD_4B=0 vs DESCRIPTOR=1 |
| `set_done_bit` | bool | 完成时拉起 stream-done flag |
| `tile_local_stride` | `TileLocalStride` (3) | 目标 tile 写入粒度(32 B...2 KB / NO_STRIDE) |
| `post_update_circular_buffer` | bool | 每个元素后推进 tile-local circular buffer |
| `indirect_list_type` | `IndirectListType` (1) | offset 解释:WORD_OFFSET=0 vs ROW_OFFSET=1 |
| `indirect_list_stride` | uint32 (6-bit slot) | **embedding row stride** -- 逐元素乘数 |
| `indirect_filter_en` | bool | 启用 id 过滤 |
| `indirect_filter_mode` | `IndirectFilterMode` (1) | SKIP=0 vs COMPACT=1 |
| `indirect_length_type` | `IndirectLengthType` (1) | FIXED=0 vs VARIABLE=1 lookup length |
| `indirect_offset_source` | `IndirectOffsetSource` (1) | offset/size 来自 SREG=0 vs CBREG window=1 |
| `post_update_indirect_offset_circular_buffer` | bool | 每个元素后推进 CBREG offset |
| `trace_en` | bool | 启用硬件 trace |
| `indirect_mask` | uint32 (4-bit) | lane/id mask |
| `stream_opcode` | `StreamOpcode` (3) | 累加模式(GATHER...SCATTER_FLOAT_ADD) |
| `gather_scatter_add_is_b16` | bool | bf16 vs f32 add-accumulate dtype |
| `tile_local_memory_type` | `TileLocalMemoryType` (1) | SMEM=0 vs TILE_SPMEM=1 目标 |
| `tile_local_stream_type` | `TileLocalStreamType` (1) | LINEAR=0 vs CIRCULAR_BUFFER=1 目标布局 |
| `off_tile_memory_type` | `OffTileMemoryType` (3) | off-tile 池(HBM table / SPMEM / TILE_SPMEM_N) |
| `s0_x` / `s1_x` | uint32 (5-bit SREG each) | 标量操作数 0/1 X(offset / size source register) |
| `s0_y` / `s1_y` | `ScalarY` (6-bit each) | 标量操作数 0/1 Y(operand-source encoding) |

各形式开头的操作数:

| Form | Opcode | Leading operands (before the common tail) |
|---|---|---|
| `LinearStream` | `0x3b` | `off_tile_start_offset[_valid]` -- 单个线性 base |
| `StridedStream` | `0x3a` | `stride_size[_valid]`, `stride_length_and_offset[_valid]` -- 一个 stride 维度 |
| `IndirectStream` | `0x39` | `indirect_offset[_valid]`(5-bit), `indirect_size_and_hbm4b_offset[_valid]`(5-bit) |
| `IndirectVregStream` | `0x38` | `indirect_offsets`, `indirect_access_lengths`(VREG-sourced), `off_tile_start_offset[_valid]` |

> **NOTE --** `Linear`/`Strided` 形式并不是 gather/scatter 路径本身的一部分;它们是连续和 strided 地址生成器,为 embedding lookup 周围的非索引传输 -- 批量 feed/drain -- 共享同一控制面(filter、length-type、sync flag)。只有 `IndirectStream`(及其 VREG 变体)会消费 id 列表。由于尾部共享,重新实现者可以从同一个 struct 编码四者:形式判别符选择开头操作数;其后的所有字段相同。

### 函数映射

| Function | Address (gfc) | Role |
|---|---|---|
| `SparseCoreStreamEncoder::Encode` | `0x1eb9b4c0` | 将 `SparseCoreStream` 编码到 SCS 32 B bundle slot |
| `SparseCoreStreamDecoder::Decode` | `0x1eb96be0` | 将 SCS Stream slot 解码回 `SparseCoreStream` |
| `SparseCoreTecStreamEncoder::Encode` | `0x1ebe33e0` | 编码到 TEC 64 B bundle slot |
| `SparseCoreTecStreamDecoder::Decode` | `0x1ebdd440` | 解码 TEC Stream slot |
| `SparseCoreTacStreamEncoder::Encode` | `0x1e8ee040`(vfc) | 编码到 TAC 64 B bundle slot(仅 vfc/glc) |
| `BitCopy` (generic bit-field packer) | `0x1fa0a900` | little-endian field packer `(dst, dst_off, src, src_off, nbits)` |

---

## Stream-Engine Enums

### 目的

Stream slot 携带十一个小 enum,以及 6-bit `ScalarY` operand-source code。它们的数值由 `.rodata` 中的 proto descriptor 固定,并且是 wire format 的一部分 -- 重新实现者必须精确复现。完整集合在有重叠的 [Vector Opcode Enum](vector-opcode-enum.md) 和 [Scalar Opcode Enum](scalar-opcode-enum.md) 页面中编目;Stream 专属项列在这里。

### 值

```text
StreamOpcode (3-bit) — the accumulate mode:
  GATHER=0  GATHER_INTEGER_ADD=1  GATHER_FLOAT_ADD=2  RESERVED_0=3
  SCATTER=4 SCATTER_INTEGER_ADD=5 SCATTER_FLOAT_ADD=6 RESERVED_1=7

OffTileMemoryType (3-bit) — the off-tile pool:
  SPMEM=0  TILE_SPMEM_N=1  HBM=2  HBM_4B=3  RESERVED_0..3=4..7

IndirectOffsetSource (1-bit):  SREG=0  CBREG=1
IndirectListType     (1-bit):  WORD_OFFSET=0  ROW_OFFSET=1
IndirectFilterMode   (1-bit):  SKIP=0  COMPACT=1
IndirectLengthType   (1-bit):  FIXED=0  VARIABLE=1
SyncFlagCountType    (1-bit):  WORD_4B=0  DESCRIPTOR=1
TileLocalMemoryType  (1-bit):  SMEM=0  TILE_SPMEM=1
TileLocalStreamType  (1-bit):  LINEAR=0  CIRCULAR_BUFFER=1
TileLocalStride      (3-bit):  32B=0 64B=1 128B=2 256B=3 512B=4 1024B=5 2048B=6 NO_STRIDE=7

SparsecoreNormalPredication (3-bit):  PREG0_IS_1=0 .. PREG6_IS_1=6, ALWAYS=7
SparsecoreRotatePredication (4-bit):  PREG0_IS_1=0 .. PREG15_IS_1=15   (16-entry rotating ring)
ScalarY (6-bit, ~110 values): SREG_0..N + immediate slots (IMM0..3)
                              + hardwired constants (ONE/TWO/FOUR/EIGHT/PI/E/HEX_*…)

QUIRK -- StreamOpcode 与 slot opcode 正交。 6-bit slot opcode(0x3b/0x3a/0x39/0x38)选择寻址形式;3-bit StreamOpcode 选择在目标端做什么。单个 IndirectStream(slot opcode 0x39)可以是普通 GATHER(StreamOpcode=0)、gradient SCATTER_FLOAT_ADDStreamOpcode=6),也可以是 GATHER_FLOAT_ADD -- 它们都是同一种形式。将两个 opcode 字段混淆的重新实现者会错误解码每个 scatter-add。随后 gather_scatter_add_is_b16 bit 会将 add accumulate 收窄为 bf16 vs f32。


Stream Slot 位编码 -- IndirectStream 形式

目的

这是 gather/scatter slot 的位精确映射,从 codec 两侧给出:encode 侧(encoder 通过 BitCopy 写入的 bundle-relative bit offset)和 decode 侧(...Field::GetConcatenatedValue() accessor 读取的 decoded-struct word + shift)。二者描述同一个物理 slot;两侧共同确认字段宽度。

Encode 侧(bundle-relative)

SparseCoreStreamEncoder::Encode @ 0x1eb9b4c0 写入 32-byte(256-bit)SCS bundle。Bit offset 是传给 BitCopyesi immediate:

text
Shared header (emitted first):
  normal_predication            @ bit 187, width 3
  normal_predication_inversion  @ bit 190, width 1
  rotate_predication            @ bit 187, width 4   (overlaps normal when is_rotate)
  is_rotate_predication         @ bit 191, width 1
Per-form opcode (oneof discriminator selects branch):
  stream-form opcode            @ bit 181, width 6
      LinearStream=0x3b  StridedStream=0x3a  IndirectStream=0x39  IndirectVregStream=0x38
Form-leading operands (IndirectStream):
  indirect_offset_valid                  @ bit 110, width 1
  indirect_offset                        @ bit 105, width 5   (5-bit SREG)
  indirect_size_and_hbm4b_offset_valid   @ bit 104, width 1
  indirect_size_and_hbm4b_offset         @ bit  99, width 5
Common control tail (selected fields):
  off_tile_memory_type      @ bit 111, width 3
  sync_flag_count_type      @ bit 127, width 1
  set_done_bit              @ bit 128, width 1
  post_update_circular_buffer @ bit 131, width 1
  indirect_list_type        @ bit 132, width 1
  indirect_list_stride      @ bit 133, width 4   (slot-narrowed from proto uint32)
  tile_local_stride         @ bit 137, width 3
  indirect_filter_en        @ bit 140, width 1
  indirect_filter_mode      @ bit 141, width 1
  indirect_length_type      @ bit 142, width 1
  s0_x                      @ bit 143, width 6
  s0_y                      @ bit 149, width 5   (encode narrows the 6-bit ScalarY to 5)
  indirect_offset_source    @ bit 155, width 1
  post_update_indirect_offset_cb @ bit 156, width 1
  trace/mask region         @ bit 157, width 3   then bit 160, width 1 / 161, width 1 / 162, width 6
  tile_local_memory_type    @ bit 168, width 1
  tile_local_stream_type    @ bit 169, width 1
  s1_y                      @ bit 170, width 6
  s1_x                      @ bit 176, width 5
```text

> **NOTE -- TEC slot 是同一组字段,但 base 不同。** `SparseCoreTecStreamEncoder::Encode` @ `0x1ebe33e0` 从同一个 `SparseCoreStream` struct 编码到 64-byte(512-bit)TEC bundle;形式 opcode `0x3b/0x3a/0x39/0x38` 不变。唯一差异是 `IndirectVregStream` 的 `indirect_offsets` 操作数,TEC bundle 将其放在高位区域 bit 322(width 6)。TAC slot(vfc/glc)同样是在 TAC bundle base 上的同一组字段。

### Decode 侧(decoded-struct relative)

`IndirectStream` 的 `...Field::GetConcatenatedValue()` accessor 是权威字段标签。struct 以 64-bit word 读取;`+0x10` 是 QWORD index 2,`+0x18` 是 QWORD index 3。下表每一行都已通过读取 accessor body 确认:

| Field | Struct word | Shift | Width | Accessor addr (gfc) |
|---|---|---|---|---|
| `IndirectOffsetSource` | `+0x18` | 0 | 1 | `0x1eb9b320` |
| `PostUpdateIndirectOffsetCircularBuffer` | `+0x18` | 3 | 1 | -- |
| `TraceEn` | `+0x18` | 4 | 1 | -- |
| `IndirectMask` | `+0x18` | 5 | 4 | -- |
| `StreamOpcode` | `+0x18` | 9 | 3 | `0x1eb9b3a0` |
| `GatherScatterAddIsB16` | `+0x18` | 12 | 1 | `0x1eb9b3c0` |
| `TileLocalMemoryType` | `+0x18` | 13 | 1 | -- |
| `TileLocalStreamType` | `+0x18` | 14 | 1 | -- |
| `S1Y` | `+0x18` | 15 | 6 | -- |
| `S1X` | `+0x18` | 21 | 5 | -- |
| `SyncFlagCountType` | `+0x18` | 27 | 1 | -- |
| `SetDoneBit` | `+0x18` | 28 | 1 | -- |
| `TileLocalStride` | `+0x18` | 29 | 3 | `0x1eb9b240` |
| `PostUpdateCircularBuffer` | `+0x18` | 32 | 1 | -- |
| `IndirectListType` | `+0x18` | 33 | 1 | `0x1eb9b280` |
| `IndirectListStride` | `+0x18` | 34 | 6 | `0x1eb9b2a0` |
| `IndirectFilterEn` | `+0x18` | 40 | 1 | -- |
| `IndirectFilterMode` | `+0x18` | 41 | 1 | `0x1eb9b2e0` |
| `S0Y` | `+0x18` | 42 | 6 | -- |
| `IndirectSizeAndHbm4bOffset` | `+0x10` | 35 | 5 | -- |
| `IndirectSizeAndHbm4bOffsetValid` | `+0x10` | 40 | 1 | -- |
| `IndirectOffset` | `+0x10` | 41 | 5 | `0x1eb9b1a0` |
| `IndirectOffsetValid` | `+0x10` | 46 | 1 | `0x1eb9b180` |
| `OffTileMemoryType` | `+0x10` | 47 | 3 | `0x1eb9b420` |
| `IndirectLengthType` | `+0x10` | 63 | 1 | `0x1eb9b300` |
| `S0X` | `+0x1e` (u16) | 0 | 5 | `0x1eb9b440` |

形式 opcode 本身是位于 `+0x18` bit 53 的 6-bit 字段(decode word 的连续部分)。opcode matcher `SparseCoreStreamIndirectStreamOpcode::Matches` @ `0x1eb9aaa0` 读取 `(*((QWORD*)this + 3) & 0x7E0000000000000) == 0x720000000000000`,也就是 bits 53-58 == `0x39`。

> **GOTCHA -- `indirect_list_stride` 在 decoded struct 中是 6 bits,但在 SCS encode slot 中是 4 bits。** decode accessor 在 `+0x18>>34` 处以 `& 0x3F`(6-bit)mask;SCS `BitCopy` 只在 bundle bit 133 写入 4 bits。proto 字段是 `uint32`。观察到的最窄物理宽度是 SCS bundle 中的 4 bits。重新实现者必须在 encode 时将 stride 视为范围受限,而不能假设完整的 proto `uint32` 会到达硬件。每一代的确切物理计数器宽度尚未钉牢(LOW -- 见 Limits)。

---

## 间接 DMA 描述符

### 目的

描述符定义了 embedding-id 列表如何变成 HBM gather 地址。它不是编译器构建的独立内存 struct;它是 MLIR `sc_tpu.indirect_stream_start` op 的操作数结构,该 op 会 lower 到上面的 Stream slot 字段。框架 op `tpu.enqueue_indirect_dma`(TensorCore 侧)lower 到它,tiled-memref index expansion 展平操作数组,而在硬件 issue 时,Stream 引擎逐元素遍历 offset list。

### SC 侧 Op 及其操作数组

```text
sc_tpu.indirect_stream_start / sc_tpu.indirect_stream_add_start
  Traits: ZeroResults, AtLeastNOperands<6>, AttrSizedOperandSegments
  Operand groups (MutableOperandRange getters):
    getSrcIndicesMutable()    @ 0x145cd9a0  — HBM table-base memref + indices (the table)
    getDstIndicesMutable()    @ 0x145cda60  — TILE_SPMEM destination tile memref + indices
    getOffsetIndicesMutable() @ 0x145cdc40  — the EMBEDDING-ID / offset list memref
    getSflagIndicesMutable()  @ 0x145cdb40  — completion sync-flag operand(s)
  Attributes (getters):
    getIndirectListType()         @ 0x145cf400   — WORD_OFFSET vs ROW_OFFSET
    getHbm4b()                    @ 0x145cf360   — 4-byte-granule HBM addressing
    getTileLocalLengthPerStride() @ 0x145cf3a0   — destination granule per stride
    getUpd()                      @ 0x145cf340   — post-update (advance circular buffer)
    getEnableTrace()              @ 0x145cf380   — hardware trace enable

连续的同族 LinearStreamStartOp / StridedStreamStartOp 没有 OffsetIndices 组 -- 它们不携带逐元素 id 列表。_add_start 变体选择一个累加 StreamOpcode*_FLOAT_ADD / *_INTEGER_ADD 模式)。

Lowering 和 Index Expansion

c
// tpu.enqueue_indirect_dma (TC side; AtLeastNOperands<4>, getAdd() = atomic-add mode)
//   lowered by mlir::tpu::LowerMemrefToMlo::lowerEnqueueIndirectDma
//   shapes verified by getVerifiedIndirectDmaShapes
//   target SC partition picked by getRemoteDeviceAndSparseCoreIds
//   -> sc_tpu.indirect_stream_start

function expandSCStreamStart<IndirectStreamStartOp>(op):   // 0x134ead00
    // ExpandTiledMemRefsPass flattens every tiled-layout memref index
    for group in {Src, Dst, Offset, Sflag}Indices:
        expanded = expandTiledIndices(group, tiles, loc, builder)  // 0x134e1f20
                                            // tiled memref index -> flat word offset
        group.assign(expanded)                                     // MutableOperandRange::assign
```text

`tpu.wait_indirect_dma`(`NOperands<3>`)是完成侧框架 op。`ExpandTiledMemRefsPass::addPattern<IndirectStreamStartOp>` 注册该 expansion(已在 decompile 中确认)。`_add_start`、`IndirectVectorStreamStartOp` 和 `StridedStreamStartOp` 的 expansion 是 `expandSCStreamStart<...>` 的同族实例化。

> **NOTE -- 描述符携带的是 index,而不是 address;乘法由硬件执行。** lowering 只重新分配操作数组并设置 `indirect_list_stride` 属性。没有发射逐元素 `imul`。row-stride 缩放(`offset_list[i] * indirect_list_stride`)是 Stream 引擎逐元素地址生成器内建的。这一点可信度为 HIGH(由字段名 + lowering 中缺少逐元素乘法推断),但并非从硬件寄存器描述中位确认。

---

## Gather/Scatter 粒度模型

### 目的

三个独立旋钮决定每个间接元素触及多少数据以及落在哪里。重新实现者用它们来匹配 embedding table 的 row width、HBM 对齐方式,以及 variable-length-lookup 行为。

### 逐元素地址公式

```c
// HW per-element gather/scatter, driven by the slot fields:
for i in 0 .. num_indices:
    if indirect_filter_en and filtered(offset_list[i]):
        continue                                 // SKIP, or COMPACT out the slot
    if indirect_list_type == ROW_OFFSET:
        addr_i = table_base + offset_list[i] * indirect_list_stride   // offset is a ROW index
    else: // WORD_OFFSET
        addr_i = table_base + offset_list[i]                          // offset is a byte/word offset
    row = read_or_write(addr_i, granule = tile_local_stride)
    if stream_opcode in {GATHER_*ADD, SCATTER_*ADD}:
        atomic_add(addr_i, row, dtype = gather_scatter_add_is_b16 ? bf16 : f32)
    if indirect_offset_source == CBREG and post_update_indirect_offset_cb:
        advance_cbreg_offset_mod_size()          // sliding embedding-table window

三个粒度旋钮

KnobFieldValuesWhat it sizes
Offset interpretationIndirectListTypeWORD_OFFSET=0 / ROW_OFFSET=1offset_list[i] 是 byte/word offset,还是由 indirect_list_stride 缩放的 row index
HBM addressing granuleOffTileMemoryTypeHBM=2 / HBM_4B=3HBM_4B 选择 4-byte-granule HBM addressing(indirect_size_and_hbm4b_offset 操作数携带 sub-word offset)
Destination write granuleTileLocalStride32 B...2 KB / NO_STRIDE进入目标 tile 的逐元素 write stride
Lookup lengthIndirectLengthTypeFIXED=0 / VARIABLE=1FIXED:每个 lookup 拉取相同的 row count;VARIABLE:逐行可变长度(variable-window lookup)

row stride indirect_list_stride(embedding row width)是在 ROW_OFFSET 下的逐元素乘数。在 WORD_OFFSET 下,offset 已经是 word/byte offset,stride 不会对其缩放。

QUIRK -- indirect_size_and_hbm4b_offset 是共享操作数。 同一个 5-bit IndirectStream 操作数在普通 HBM 情况下携带 lookup size,在 HBM_4B 情况下携带 sub-word offset。解释方式由 OffTileMemoryType==HBM_4B 门控。字段名同时包含两个角色,正是因为硬件复用了它。重新实现者在打包该操作数时必须基于 OffTileMemoryType 分支。

Offset Source 和 Sliding Window

IndirectOffsetSource 选择逐元素 offset/size 的来源:

  • SREG (0): offset/size 从 s0_x/s1_x(5-bit SREG selector)命名的 scalar register 读取,并配合 s0_y/s1_y(6-bit ScalarY)operand-source code。它是静态、per-stream 的。
  • CBREG (1): offset/size 来自 circular-buffer register window;post_update_indirect_offset_circular_buffer 在每个元素后推进 CBREG offset,并对 window size 取模。这就是 sliding embedding-table window -- id 列表通过 CBREG ring 消费,而不是通过平坦 SREG。有关 circular-buffer register 布局,见 CBREG

Filtering 和 Bounds

indirect_filter_en + indirect_filter_mode 控制哪些 id 会触发:SKIP 丢弃被过滤的 id(留下空洞),COMPACT 移除它(压缩输出)。filter value 由 SCS op sc_tpu.set_indirect_filter_valueSetIndirectFilterValueOp,通过 SparseCoreScalarAlu_SetIndirectFilterValue scalar op lower)在带外设置。id-range bounds check 由 AddStreamBoundChecksPass::runOnOperation @ 0x134c3fa0(其 AddStreamBufferChecks 有 Linear/Strided/Indirect overload)和 IndirectStreamBoundsCheckPass 插入;越界 id 会触发“does not match number of TPU embedding rows”诊断。


Scatter-Add Slot(Embedding-Gradient Accumulation)

目的

反向传播需要将一个 gradient slice 加到 embedding row 中。存在两条路径:(a)累加到 CBREG-windowed TILE_SPMEM 区域并使用 TEC vector-store op;或(b)发出 Stream SCATTER_FLOAT_ADD,直接进入 HBM embedding row。路径(b)就是 概览 称为关键原语的 in-HBM atomic FP-add。

TEC Vector-Store Add Slot

TileSpmemStoreCircularBufferPostUpdateAddF32(gfc)-- TILE_SPMEM circular-buffer post-update add store。decoded-struct word +0x30(DWORD index 12 / QWORD index 6),已通过读取 accessor body 确认:

FieldShiftWidthAccessor addr (gfc)Meaning
Mask850x1eccac40lane predicate / vmask
Stride1340x1eccac20逐元素 address stride
Offset1730x1eccac00window 内 offset
BaseAddress2030x1eccabc0显式 base(CBREG base 的替代)
Cbreg2340x1eccabe0哪个 CBREG(-> 16 CBREGs)
Source2760x1eccaba0被 scatter-added 的源 VREG

该 op 按 accumulate dtype 存在 -- Add{Bf16,F32,S16,S32} -- 覆盖整个 store-form 家族(plain、CircularBufferCircularBufferPostUpdateIndexed,...)。slot +0x30Cbreg 的 4-bit 宽度(16 CBREGs)与 vfc(Viperfish)布局匹配,从而确认了 gfc(6acc60406)。完整 store-slot 家族见 VectorStore Slot,gather-load 对应项见 VectorLoad Slot

Gradient Flow

text
TEC vector-loads gathered rows (TileSpmemLoadCircularBuffer[PostUpdate])
  -> applies optimizer math (stochastic round-to-bf16 / FP8 pack on gfc TEC)
  -> EITHER (a) TileSpmemStoreCircularBufferPostUpdateAdd{dt}   (CBREG-windowed TILE_SPMEM)
     OR     (b) Stream SCATTER_FLOAT_ADD into HBM               (StreamOpcode=6, OffTileMemoryType=HBM)
```text

路径(b)是直接进入 HBM 的 atomic FP-add(`StreamOpcode=6`,`gather_scatter_add_is_b16` 选择 accumulate dtype)-- 也就是 TensorCore 的 systolic array 无法执行的操作。

---

## SCS / TAC / TEC 引擎握手

### 目的

Stream slot 存在于所有三个 engine bundle 中,但某个给定的 gather/scatter 由一个引擎*发出*。具体是哪一个取决于世代,而引擎之间通过发布到 SMEM 的参数和跨引擎同步原语进行协调。这是 [架构](architecture.md) 页面端到端描述的 pipeline 的 slot-level 视图。

### Forward Lookup

```text
1. SCS computes per-lookup addressing params (table base, offset-list base, row
   stride, window CBREG triple) and publishes them to SMEM.
2. ACCESS engine issues the indirect gather Stream op:
     VF/GL:    TAC bundle Stream slot  (SparseCoreTacStreamEncoder @ 0x1e8ee040 vfc)
     gfc:      TEC bundle Stream slot  (no TAC; SparseCoreTecStreamEncoder @ 0x1ebe33e0)
               gated by TileWaitScsSmemOp  (TEC waits for SCS's SMEM value)
   The gather reads offset_list[i] (SREG or CBREG window),
   computes HBM[table_base + offset*stride], DMAs the row -> TILE_SPMEM,
   and (CBREG case) post-updates the CBREG offset mod size.
3. EXECUTE engine (TEC) vector-loads from TILE_SPMEM, reduces (sum / weighted /
   max via the VectorExtended scan/segscan slots), emits the result tile.
4. Result tile DMA'd HBM->VMEM for MXU consumption; SC raises an SFLAG;
   the TC's tpu.sem_wait advances.

Sync Primitives

text
Cross-sub-engine (xla::tpu::sparse_core::collective::OffloadFactory):
  SyncScsWithTec(builder, value, CoreKind)        @ 0x133e9260
  SyncTecWithScs(builder, v1, v2, CoreKind)       @ 0x133e8fe0
  CoreKind {kScs, kTec, kTac(VF/GL)}
gfc TAC-replacement:
  TileWaitScsSmemOp / sc_tpu.tile_wait_scs_smem   (TEC waits until SCS writes a
    designated SMEM value, then proceeds with fetch+compute — collapses the
    SCS<->TAC<->TEC three-way sync to a single SCS<->TEC boundary)
Cross-CORE (SC<->TC):
  SFLAG memory (sflag / sflag_scs / sflag_tile), tpu.sem_wait / sem_signal /
  fetch_and_add_sync; double-buffered (AllocateSflag(..., 2)).
Tile-task program model:
  sc_tpu.tile_task (Access+Execute regions) launched via launch_tile_task /
  prefetch_tile_task / tile_task_wait.
```text

> **NOTE -- 在 gfc(6acc60406)上,gather 从 TAC 迁移到 TEC。** 由于 TAC 被移除(gfc namespace 中 `SparseCoreTacStream*` 函数为零,而 vfc 中有 68 个 / glc 中有 71 个),access engine 的角色 -- 发出 indirect gather Stream op -- 折叠进 TEC,并通过 `TileWaitScsSmemOp` 同步,而不是三方 SCS/TAC/TEC 握手。Stream slot 的*编码*不变;只有发出引擎和同步拓扑不同。给定 Stream op 由哪个引擎承载,由 `LowerMemrefToMlo::getSequencerType` 决定;确切的 per-shape 决策未进行 bit-trace(见 [getSequencerType](getsequencertype.md))。

---

## 各世代存在性

| Mechanism | VF (vfc, Viperfish v5) | GL (glc, Ghostlite v6e) | GF (gfc, 6acc60406 / TPU7x) |
|---|:---:|:---:|:---:|
| SCS-bundle Stream slot (`SparseCoreStream`) | yes | yes | yes |
| TAC-bundle Stream slot (`TacStream`) | yes | yes | **-- (no TAC)** |
| TEC-bundle Stream slot (`TecStream`) | yes | yes | yes |
| `LinearStream` (opcode `0x3b`) | yes | yes | yes |
| `StridedStream` (opcode `0x3a`) | yes | yes | yes |
| `IndirectStream` (opcode `0x39`) gather/scatter | yes | yes | yes |
| `IndirectVregStream` (opcode `0x38`) | yes | yes | yes |
| `StreamOpcode` 8-value set (GATHER...SCATTER) | yes | yes | yes |
| `IndirectOffsetSource` SREG/CBREG | yes | yes | yes |
| `OffTileMemoryType` {SPMEM, TILE_SPMEM, HBM, HBM_4B} | yes | yes | yes |
| Indirect gather issued from ACCESS engine | TAC | TAC | TEC |
| Scatter-add CB store `...PostUpdateAdd{dt}` | int/float | 4 dtypes | 4 dtypes |

`TacStream` 函数数量是判别项:**gfc = 0**,**vfc = 68**,**glc = 71** 个已 decompile 的 `SparseCoreTacStream*` 函数。在 `jxc`(Jellyfish)或 `pxc`(Pufferfish)namespace 下没有出现 `SparseCoreStream` schema -- 这些世代没有 SparseCore。

---

## 限制和未解决项

| Item | Status |
|---|---|
| `SparseCoreStream` proto + 4-form `oneof`, all field numbers/names/types | 从 descriptor 解码;3 份副本(vfc/glc/gfc) |
| All Stream enums with numeric values | proto-descriptor 已确认 |
| Per-form opcodes `0x3b/0x3a/0x39/0x38` @ bit 181 | 来自 SCS + TEC encoder 的位精确结果 |
| `IndirectStream` encode offsets + decode shifts | 两侧一致;已读取 accessor body |
| Scatter-add slot `{Mask,Stride,Offset,BaseAddress,Cbreg,Source}` @ `+0x30` | 已读取 accessor body |
| Indirect-DMA descriptor operand groups + attribute getters | op getter 已在 decompile 中定位 |
| Per-element address formula (`table_base + offset*stride`, HW multiply) | 由字段名推断;lowering 中无逐元素 `imul` |
| Which engine issues a given Stream op per gen | 已定位 `getSequencerType`,未 bit-trace |
| Physical HW width of `indirect_list_stride` / SREG operands | proto `uint32`;slot 在 SCS 中编码 4-bit,decode 为 6-bit;HW counter width 未知 |
| `IndirectVregStream` VREG-read micro-op datapath | 字段位置已解码;VREG source 未 trace |
| `ROW_OFFSET` vs logical-replica row sharding (per-shard vs global row) | 未解决 |
| Absolute bit base of the SCS Stream slot within the 256-bit bundle | opcode @ bit 181 已知;完整 SCS slot-base partition 未交叉核对 |

---

## 交叉引用

- [SparseCore 概览](overview.md) -- 该 slot 执行的 host-table -> HBM -> SC gather 数据路径;关键的 in-HBM atomic add。
- [SparseCore 架构](architecture.md) -- 引擎角色和完整深度的 embedding datapath。
- [IndirectVregStream](indirect-vreg-stream.md) -- `0x38` 形式:id 列表从 vector register 而非内存列表流入。
- [VectorLoad Slot](vectorload-slot.md) -- 消费 streamed rows 的 TILE_SPMEM gather-load 对应项。
- [VectorStore Slot](vectorstore-slot.md) -- scatter-add `...PostUpdateAdd{dt}` 所属的 store-slot 家族。
- [CBREG Circular-Buffer Register](cbreg.md) -- CBREG offset source 推进的 sliding-window register。
- [getSequencerType](getsequencertype.md) -- 选择发出引擎的 SCS/TAC/TEC selection。
- [Vector Opcode Enum](vector-opcode-enum.md) / [Scalar Opcode Enum](scalar-opcode-enum.md) -- 周边 opcode roster。
- [GetSparseCoreConfig](getsparsecoreconfig.md) -- offload op-type 配置来源。
- [SparseCore vs Neuron MatmultSparse](sparsecore-vs-neuron-matmultsparse.md) -- indirect-gather 原语的跨厂商比较。
- **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 pointers & DMA -- [返回索引](../index.md)