IndirectVregStream — 由 VREG 驱动的 Gather 循环
本页中的每个函数地址、操作数索引、结构体偏移、移位/掩码以及 opcode 值,都读取自
libtpu-0.0.40-cp314wheel 中的libtpu.so(build-id89edbbe81c5b328a958fe628a9f2207d;buildlibtpu_lts_20260413_b_RC00)。.textVA 等于文件偏移0xe63c000,.rodata位于0x84a0000,两者都是恒等映射。除非另有标注,地址来自 gfc(6acc60406)实例;glc(Ghostlite)和 vfc(Viperfish)同级实现以不同地址承载相同 schema。其他版本会有差异。
摘要
IndirectVregStream 是 SparseCore Stream 的一种形式,它不是从内存 id-list 读取逐元素 offset 流,而是从向量寄存器读取。它是 SparseCoreStream 的第四个 oneof 形式(slot opcode 0x38),除了开头四个 proto 字段之外,结构上与 IndirectStream(0x39)完全相同:两个 SREG 操作数(indirect_offset、indirect_size_and_hbm4b_offset)被替换为一个 VREG 选择器(indirect_offsets)、一个 lane 掩码(valid_offset_mask)和一个显式 base(off_tile_start_offset)。从 proto 字段 #5 往后,也就是整个 stream-control 尾部,都与 IndirectStream 逐字节相同。
本页负责说明 VREG 读取循环和逐索引 DMA 发起:offset 流如何来自向量寄存器和 lane 掩码,SC 侧 MLIR op 如何把 VREG 操作数作为普通 SSA Value(而不是 memref index group)携带,索引展开如何只展开三个 index group(并且明显不展开 offset group),以及 LLVM lowering 如何根据 (hbm4b, src-space, dst-space) 三元组选择九个逐路由 DMA-issue intrinsic 之一。slot 位编码和真正的 indirect-DMA 描述符位于 Stream Gather/Scatter,本页只链接过去而不重新推导。将此形式固定到 TEC 的 engine-assignment 策略位于 getSequencerType。
重新实现契约:
- offset 来源是寄存器,而不是内存列表。
IndirectStream从两个 5-bit SREG selector 读取 offset/size;IndirectVregStream从 6-bit VREG selector(indirect_offsets)读取 offset 流,并用另一个独立的 6-bit VREG selector 读取逐元素访问长度(indirect_access_lengths),由valid_offset_mask门控。id 流位于 TEC 向量寄存器文件中,这正是该形式仅限 TEC 的原因。 - 循环体相同;只有索引供给不同。 逐元素地址算术、row-stride 乘法、accumulate
StreamOpcode、filter、sliding-window post-update,以及所有公共控制尾部字段,都与IndirectStream拥有相同的 proto 布局。重新实现者应从一个 struct 编码两种形式;形式判别器只决定每个 lane 的 offset 如何获取。 - SC op 携带三个 index group,而不是四个。
IndirectVectorStreamStartOp通过expandTiledIndices展开{Src, Dst, Sflag}Indices;没有OffsetIndicesgroup(offset 是 VREG 操作数),也没有getIndirectListType属性。这是相对于IndirectStreamStartOp的结构性差异,已在索引展开和 LLVM lowering 主体中确认。 - 逐索引发起是 9 路 intrinsic dispatch。 LLVM lowering 构建九个候选 emitter lambda,并通过匹配运行时
(hbm4b-flag, src-memory-space, dst-memory-space)三元组来选择。未匹配的三元组会发出"IndirectVectorStreamStartOp doesn't support transfer from source memory space … to destination memory space …"。
| Proto 形式 | IndirectVregStream — SparseCoreStream oneof 成员,slot opcode 0x38 |
| 同级形式 | IndirectStream(0x39);逐字节共享 proto 字段 #5…#22 |
| VREG offset selector | indirect_offsets — 6-bit VREG selector,slot +0x28 >> 27 & 0x3F |
| VREG length selector | indirect_access_lengths — 6-bit VREG selector,slot +0x30 >> 2 & 0x3F |
| Lane 门控 | valid_offset_mask — 本轮哪些 VREG lane 携带有效 offset |
| Gather base | off_tile_start_offset[_valid] — 显式 base(替代 SREG indirect_offset) |
| SC 侧 op | sc_tpu.indirect_vector_stream_start / .indirect_vector_stream_add_start |
| Op traits | ZeroResults, AtLeastNOperands<6>, AttrSizedOperandSegments |
| Index groups | {Src, Dst, Sflag}Indices — 没有 OffsetIndices |
| Index expansion | expandSCStreamStart<IndirectVectorStreamStartOp> @ 0x134eb880(Add @ 0x134ebd20) |
| Builder | lowering_util::InitiateIndirectVectorStreamOperation @ 0x13d870e0 / 0x13d866c0 |
| 逐索引发起 | IndirectVectorStreamStartOpLowering::rewriteSparseCoreStreamOpToLLVM @ 0x13560ce0 |
| Engine | 仅 TEC(VREG 操作数需要 TEC 向量寄存器文件) |
| 置信度 | CONFIRMED(由反编译/accessor/symbol 锚定),除非行内另有说明 |
此形式的位置
SparseCoreStream 是一个共享 header 加上四路 oneof 寻址形式。完整 message、其共享控制尾部以及各形式的 opcode 记录在 Stream Gather/Scatter。四种形式及其 offset 来源如下:
| 形式 | Opcode | 逐元素 offset 来源 |
|---|---|---|
LinearStream | 0x3b | 单个 linear base,没有逐元素 offset |
StridedStream | 0x3a | 一个 stride 维度,没有逐元素 offset |
IndirectStream | 0x39 | 内存 offset id-list(两个 5-bit SREG 操作数) |
IndirectVregStream | 0x38 | 向量寄存器 offset 流(一个 6-bit VREG selector + lane mask) |
IndirectStream 和 IndirectVregStream 是两种间接(gather/scatter)形式:二者都会逐元素遍历 offset 流,并为每个元素发起一次 off-tile transfer。它们只在 offset 流从哪里读取 上不同。IndirectStream 通过 SREG 寻址的内存列表往返读取 id;IndirectVregStream 直接从 TEC 向量寄存器消耗它们。本页说明第二种情况。
NOTE — 这是 variable-length / minibatch inner-loop gather。 当 embedding-id 流是在寄存器中产生时,VREG 形式是自然选择,例如 TEC 对一个 minibatch 做 scan/uniquify 的输出,并且必须在不先物化成内存 id-list 的情况下按 lane 并行 gather。
valid_offset_mask门控哪些 lane 触发,因此 ragged minibatch 可以直接映射到固定宽度 VREG sweep。这与 per-physical-core minibatch 模型相关(见 Embedding Minibatching);它是唯一 offset 操作数来自寄存器文件的 Stream 形式,这也正是它不能位于 SCS 或 TAC bundle 中的原因。(用例推断:HIGH。)
Proto 差异 — 字段 #1…#4
IndirectVregStream 和 IndirectStream 完全共享 proto 字段 #5…#22(sync_flag_core_type, sync_flag_count_type, set_done_bit, tile_local_stride, post_update_circular_buffer, indirect_list_type, indirect_list_stride, indirect_filter_en, trace_en, stream_opcode, tile_local_memory_type, tile_local_stream_type, off_tile_memory_type, gather_scatter_add_is_b16, s0_x, s0_y, s1_x, s1_y)。这些字段及其含义记录在公共控制尾部。两种形式只在开头四个字段不同:
IndirectStream (#1…#4) — offset stream from MEMORY (an SREG-addressed id-list):
#1 indirect_offset_valid : bool
#2 indirect_offset : uint32 (5-bit SREG) ← base/list address
#3 indirect_size_and_hbm4b_offset_valid : bool
#4 indirect_size_and_hbm4b_offset : uint32 (5-bit SREG) ← lookup size / HBM_4B suboffset
IndirectVregStream (#1…#4) — offset stream from a VECTOR REGISTER:
#1 indirect_offsets : uint32 ← a VREG selector (the offset stream)
#2 valid_offset_mask : uint32 ← which VREG lanes carry a valid offset
#3 off_tile_start_offset_valid : bool
#4 off_tile_start_offset : uint32 ← explicit gather BASE (like LinearStream)
```text
因此替换关系很精确:两个用于*命名保存列表地址和 size 的寄存器*的 SREG 操作数,被替换为 (a) 一个用于*命名保存 offset 流本身的寄存器*的 VREG selector,(b) 一个 lane mask,以及 (c) 一个替代 SREG 来源 base 的显式 base offset。硬件随后逐 lane 读取 `indirect_offsets[lane]`,而不是遍历内存 id-list。
> **GOTCHA — base 从 SREG 移到 immediate 风格字段。** 在 `IndirectStream` 中,gather base 是两个 SREG 操作数之一;在 `IndirectVregStream` 中,base 是 `off_tile_start_offset`,与 `LinearStream` 使用相同字段名。把 `IndirectStream` 描述符移植到 VREG 形式的重新实现者,必须把 base 从 SREG 操作数移入 `off_tile_start_offset`,并把 id 流从内存移入 VREG。`IndirectStream` 打包在 `indirect_size_and_hbm4b_offset` 中的 size/length,在 slot 中变成独立的逐 lane `indirect_access_lengths` VREG selector(见下文)。
---
## VREG 读取循环 — Slot Selector
在 bundle 层面,两个 VREG 操作数是 6-bit *selector*,即命名硬件从哪个向量寄存器读取 offset 流和逐 lane 访问长度的小索引。它们由 gfc TEC-stream `…Field::GetConcatenatedValue()` accessor 解码,直接读取如下:
```c
// asic_sw::deepsea::gxc::gfc::isa::
// SparseCoreTecStreamIndirectVregStreamIndirectOffsetsField::GetConcatenatedValue @0x1ebe30a0
return (*((u64*)this + 5) >> 27) & 0x3F; // struct +0x28, shift 27, width 6
// …IndirectAccessLengthsField::GetConcatenatedValue @0x1ebe30c0
return (*((u32*)this + 12) >> 2) & 0x3F; // struct +0x30, shift 2, width 6| Slot field | Struct word | Shift | Width | Accessor (gfc) | 作用 |
|---|---|---|---|---|---|
IndirectOffsets | +0x28 | 27 | 6 | 0x1ebe30a0 | VREG selector,offset 流 |
IndirectAccessLengths | +0x30 | 2 | 6 | 0x1ebe30c0 | VREG selector,逐 lane 访问长度 |
OffTileStartOffset | +0x10 | 41 | 5 | 0x1ebe3100 | gather base |
OffTileStartOffsetValid | +0x10 | 46 | 1 | 0x1ebe30e0 | base-present bit |
OffTileMemoryType | +0x10 | 47 | 3 | 0x1ebe3340 | HBM / HBM_4B / SPMEM pool |
StreamOpcode | +0x18 | 9 | 3 | 0x1ebe32c0 | accumulate mode(共享尾部) |
TileLocalStride | +0x18 | 29 | 3 | 0x1ebe3160 | dst write granule(共享尾部) |
两个 VREG selector 是读取侧的全部差异。其他所有内容(StreamOpcode、TileLocalStride、OffTileMemoryType、sync flag、filter、list stride)都是共享的 IndirectStream decode map,见 Stream Gather/Scatter § Decode Side;只有开头操作数字 +0x28/+0x30 携带 VREG selector,而不是 SREG 操作数。
NOTE — 6-bit selector 命名 VREG,而不是 lane 数。
indirect_offsets和indirect_access_lengths是 6-bit 寄存器 selector;它们索引 TEC 向量寄存器文件(≤64 个可寻址 selector)。被 sweep 的 lane 数,也就是每轮 id 数,是 VREG 宽度,属于芯片几何,而不是 slot 字段。掩码valid_offset_mask随后门控这些 lane 中哪些在本轮携带有效 offset。6-bit selector 背后的物理 VREG-file 大小尚未枚举(LOW,见 Limits)。
Encode 侧
gfc TEC-stream encoder 写入 opcode 0x38,并把两个 VREG selector 操作数放到 bundle 的高位区域:
SparseCoreTecStreamEncoder::Encode @0x1ebe33e0 (oneof switch on *(u32*)(stream+88))
case 8 → opcode 0x3b (59) LinearStream
case 9 → opcode 0x3a (58) StridedStream
case 10 → opcode 0x39 (57) IndirectStream
case 0xB → opcode 0x38 (56) IndirectVregStream ← case-11 branch
opcode @ bundle bit 181, width 6 = 0x38 (56) BitCopy(a3,181,…,6)
vreg sel #1 (msg member +6) @ bundle bit 283, width 6 BitCopy(a3,283,…,6)
vreg sel #2 (msg member +7) @ bundle bit 322, width 6 BitCopy(a3,322,…,6)
```text
(TEC bundle 为 64 B / 512 bits;两个 VREG selector 宽度都是 6,并落在高位区域。member +6 先序列化,member +7 后序列化。)
> **GOTCHA — TEC encoder 的 `oneof` 上界是 `0xb`;SCS encoder 的上界是 `0xa`。** `SparseCoreStreamEncoder::Encode`(SCS,`@0x1eb9b4c0`)在 `cmp rax, 0xa` 处限制其 `oneof` switch(最大 case 10),它只有三种形式,**没有** `IndirectVreg` 分支。TEC encoder 的上界是 `cmp rax, 0xb`(case 11)。这个额外 case 是 `IndirectVregStream` 仅限 TEC 的全部结构性原因:SCS 和 TAC stream encoder 无法到达第四种形式。任何 generation 中都没有 `SparseCoreStreamIndirectVregStream*` 和 `SparseCoreTacStreamIndirectVregStream*` field accessor;该形式的 field accessor 只存在于 `SparseCoreTecStream` 前缀下(vfc `0x1e936240`、glc `0x1ea7be20`、gfc `0x1ebe30a0`)。形式清单见 [Stream Gather/Scatter](stream-gather-scatter.md)。
---
## SC 侧 op 及其操作数组
VREG gather 由 MLIR op 对 `sc_tpu.indirect_vector_stream_start`(gather)和 `sc_tpu.indirect_vector_stream_add_start`(带 accumulate 的 gather/scatter)表达。该 op 携带与 `IndirectStreamStartOp` 相同的 traits:`ZeroResults`、`AtLeastNOperands<6>`、`AttrSizedOperandSegments`,但操作数和属性集合不同:
```text
sc_tpu.indirect_vector_stream_start / .indirect_vector_stream_add_start
Traits: ZeroResults, AtLeastNOperands<6>, AttrSizedOperandSegments
Memref index groups (MutableOperandRange getters):
getSrcIndicesMutable() @ 0x145d7860 — off-tile (HBM table) source memref + indices
getDstIndicesMutable() @ 0x145d7920 — TILE_SPMEM destination memref + indices
getSflagIndicesMutable() @ 0x145d7a00 — completion sync-flag operand(s)
(NO getOffsetIndicesMutable — the offsets are a VREG operand, not a memref index group)
Attributes (getters):
getHbm4b() @ 0x145d9120 — 4-byte-granule HBM addressing
getTileLocalLengthPerStride() @ 0x145d9160 — destination granule per stride
getUpd() @ 0x145d9100 — post-update (advance circular buffer)
getEnableTrace() @ 0x145d9140 — hardware trace enable
(NO getIndirectListType — the VREG form omits it)
Operand-segment table: getODSOperandIndexAndLength @ 0x145d7720 (op)
@ 0x145d7620 (adaptor base)相对于 IndirectStreamStartOp 的结构差异是两个省略项,二者都由反编译中相应 symbol 的缺失确认:没有 getOffsetIndicesMutable(没有逐元素 id-list memref),也没有 getIndirectListType 属性(内存 id-list 需要的 word-vs-row 解释)。_add_start 变体选择 accumulate StreamOpcode(*_FLOAT_ADD / *_INTEGER_ADD)。
AttrSizedOperandSegments 意味着操作数列表是多个可变长 segment 的拼接,其大小位于每个 op 的数组中;getODSOperandIndexAndLength(i) 是该数组上的标准 MLIR prefix-sum(@0x145d7620 对 this[16..16+i] 求和,对于 i ≥ 4 使用 vpaddd 向量化,并返回 (start << 0) | (len << 32))。VREG offset/length/mask 操作数和 memref group 都位于这个分段列表中;上面的 getter 命名这些 slice。
Index Expansion — 三个 Group,没有 Offset Group
在 LLVM lowering 之前,ExpandTiledMemRefsPass 会把每个 tiled-layout memref index 展开为 flat word offset。对于 IndirectVectorStreamStartOp,这是 expandSCStreamStart<IndirectVectorStreamStartOp>(@0x134eb880;…AddStartOp 实例为 @0x134ebd20)。反编译主体 clone 该 op,然后将恰好三个 index group 送入 expandTiledIndices:
// expandSCStreamStart<IndirectVectorStreamStartOp> @0x134eb880 (reconstructed)
started = cloneWithSubstOperands<IndirectVectorStreamStartOp>(op, ...);
// group 1 — Src (ODS operand index 1, adaptor segment 2)
if (layoutIsTiled(getMemRefType(operand[1]))) {
range = getSrcIndicesMutable();
seg = adaptor.getODSOperandIndexAndLength(2);
flat = expandTiledIndices(seg, getTiles(layout), builder); // 0x134e1f20
range.assign(ValueRange(flat)); // MutableOperandRange::assign
}
// group 2 — Dst (ODS operand index 3, adaptor segment 4)
if (layoutIsTiled(getMemRefType(operand[3]))) {
getDstIndicesMutable().assign(expandTiledIndices(adaptor.getODSOperandIndexAndLength(4), ...));
}
// group 3 — Sflag (ODS operand index 6, adaptor segment 7)
if (layoutIsTiled(getMemRefType(operand[6]))) {
getSflagIndicesMutable().assign(expandTiledIndices(adaptor.getODSOperandIndexAndLength(7), ...));
}
rewriter.replaceOp(op, started);
return success();
```text
三个 group,三次 `expandTiledIndices` + `MutableOperandRange::assign` 调用,并且**没有 offset list 的第四次 pass**。对比 `expandSCStreamStart<IndirectStreamStartOp>`(`@0x134ead00`),它会处理 `{Src, Dst, Offset, Sflag}Indices`(四个 group)。缺失的第四次 pass 是 VREG 形式在索引展开层面的签名:逐元素 offset 从不进入 memref index 机制,因为它们是向量寄存器操作数。`ExpandTiledMemRefsPass::addPattern<IndirectVectorStreamStartOp>`(`@0x134fc680`)注册该展开。
> **NOTE — 操作数索引 `1 / 3 / 6` 是 memref group;VREG 操作数位于 scalar segment。** 展开只触碰 *memref* segment(Src/Dst/Sflag,ODS operands 1/3/6)。VREG offset/length/mask 操作数是其他 segment(5/8/9,见下面的 LLVM lowering)中的普通 SSA `Value`,不会进行 tiled-memref-expanded;它们原样通过。重新实现者不得把 VREG 操作数送入 tiled-index expander。
---
## 逐索引 DMA 发起 — `rewriteSparseCoreStreamOpToLLVM`
最终 lowering `IndirectVectorStreamStartOpLowering::rewriteSparseCoreStreamOpToLLVM`(`@0x13560ce0`)把 op 转换成实际的 SparseCore stream-start intrinsic。这里会把 VREG 操作数作为 value 读取,并选择逐索引发起 intrinsic。恢复出的流程如下:
```c
// rewriteSparseCoreStreamOpToLLVM @0x13560ce0 (reconstructed)
// 1. Dereference the VREG operands as scalar SSA Values (NOT memref index groups):
v_off = adaptor.getODSOperandIndexAndLength(5); offVal = ValueRange(...).dereference_iterator(0);
v_len = adaptor.getODSOperandIndexAndLength(8); lenVal = ValueRange(...).dereference_iterator(0);
v_mask = adaptor.getODSOperandIndexAndLength(9); maskVal = ValueRange(...).dereference_iterator(0);
hbm4b = op.getHbm4b();
// 2. Read src/dst element types + shapes from the Src (operand 1) and Dst (operand 3) memrefs
srcShape = getShape(getMemRefType(operand[1])); srcElem = getElementType(...);
dstShape = getShape(getMemRefType(operand[3])); dstElem = getElementType(...);
// 3. HBM_4B address adjustment (when hbm4b set): scale the Src/Dst offset operands
// via getHbm4bOffset(...) + adjustOffsetForHbm4b(...) — adaptor operands 2 (src) / 4 (dst)
if (hbm4b) { srcOff = adjustOffsetForHbm4b(getHbm4bOffset(operand[2], srcElem)); ... }
// 4. Build NINE candidate issue lambdas (#3…#11), one per (hbm4b, srcSpace, dstSpace) route.
// Each lambda closes over {offVal, lenVal, maskVal, base, sync, ...} and emits the
// sc-stream-start intrinsic for that specific memory-space route.
cand[0..8] = { lambda#3, …, lambda#11 }; // each: {flag, srcSpace, dstSpace} + emitter
// 5. Select the lambda whose (flag,srcSpace,dstSpace) triple matches the runtime triple:
for k in 0..8:
if cand[k].flag == hbm4b && cand[k].src == srcSpace && cand[k].dst == dstSpace:
// optional: if a filter value operand (operand 0) is present, emit
// sc_tpu.set_indirect_filter_value first
if (operand[0] present) builder.create<SetIndirectFilterValueOp>(filterVal);
cand[k].emit(); // ISSUE the per-route stream-start
return success();
// 6. No match → diagnostic:
return op.emitError(
"IndirectVectorStreamStartOp doesn't support transfer from source memory space: %d"
" to destination memory space: %d", srcSpace, dstSpace);九个候选 emitter 是 SparseCore stream-start 的逐路由特化。dispatch key 是 (hbm4b-flag, src-memory-space, dst-memory-space) 三元组,也就是 GetTransferKind 上游用来把它分类为 Stream 而非 DMA 的同一对 (srcSpace, dstSpace)。因此,getSequencerType lowering 中做出的 kStream 决定,在这里转化为具体的逐路由 intrinsic。不支持的 pair 会落入 emitError,其消息通过反编译中看到的 LLVM SmallVector<u32> 插值构建。
| Lowering 元素 | 证据 (gfc) |
|---|---|
VREG offset 操作数位于 adaptor segment 5,并作为 Value 解引用 | getODSOperandIndexAndLength(5) + dereference_iterator(0) |
| VREG length 操作数位于 adaptor segment 8 | getODSOperandIndexAndLength(8) + dereference_iterator(0) |
| VREG mask 操作数位于 adaptor segment 9 | getODSOperandIndexAndLength(9) + dereference_iterator(0) |
getHbm4b() 门控 HBM_4B offset scaling | getHbm4b @ 0x145d9120;getHbm4bOffset / adjustOffsetForHbm4b 调用 |
| HBM_4B offset 操作数位于 adaptor segments 2 (src) / 4 (dst) | hbm4b 分支中的 getODSOperandIndexAndLength(2) / (4) |
对 emitter lambda 的 9 路 (hbm4b, src, dst) 三元组 dispatch | 九个 operator new(0x50/0x58) emitter closure + 对 (flag, *(+1), *(+2)) 的 match chain |
operand 0 存在时可选 SetIndirectFilterValueOp | 匹配分支上的 OpBuilder::create<SetIndirectFilterValueOp> |
| 不支持路由的 diagnostic | "IndirectVectorStreamStartOp doesn't support transfer from source memory space: … to destination memory space: …" |
| Lane 并行循环体 / 硬件逐 lane 地址算术 | 不是软件循环,而是作为一个 intrinsic 发起;HW 遍历 VREG |
GOTCHA — 没有软件逐元素循环;“循环”是硬件 Stream engine。 lowering 不会 对 offset lane 发出
scf.for和逐 laneimul/DMA。它发出单个 stream-start intrinsic,把 VREG selector、length selector、lane mask 和 base 交给硬件 Stream engine,然后硬件自行遍历 lane,并应用IndirectStream中记录的同一逐元素 row-stride 乘法(addr_i = base + offsets[lane] * indirect_list_stride)。编译器的工作是操作数分配和路由选择,而不是发出循环。建模 lowering 的重新实现者必须为每个 stream-start 发出一个 intrinsic,而不是展开的循环。
Builder — InitiateIndirectVectorStreamOperation
从 target 和一组 ValueRange 操作数构造 op 的高层 builder 是 lowering_util::InitiateIndirectVectorStreamOperation(@0x13d870e0;另一个同级 overload 在 @0x13d866c0)。恢复出的主体把 VREG 操作数作为 ValueRange 传递,并在构建 op 之前校验 transfer geometry:
// InitiateIndirectVectorStreamOperation(Target&, OpBuilder, LocationGenerator,
// Value, ValueRange, ValueRange, …, StreamOptions) @0x13d870e0 (reconstructed)
srcSpace = GetMemorySpace(srcMemref);
srcGran = xla_mlo_util::TransferGranularityInBytes(target.chip_parts, srcSpace, ...);
dstSpace = GetMemorySpace(dstMemref);
dstGran = xla_mlo_util::TransferGranularityInBytes(target.chip_parts, dstSpace, ...);
// transfer byte-count must be a multiple of each address space's granularity
if (bytes % srcGran) return InvalidArgument(
"Bytes transferred by IndirectStreamOperation must be a multiple of "
"src address space %d granularity %d. Got %d", srcSpace, srcGran, bytes);
if (bytes % dstGran) return InvalidArgument( /* … dest address space … */ );
if (!target.SupportsSparseCore()) LOG(FATAL) << "SparseCore is not supported by this target";
minGran = target.chip_parts->sc_min_granularity; // chip_parts +164
if (bytes % minGran) return InvalidArgument(
"Bytes transferred by IndirectStreamOperation must be a multiple of %d. Got %d", minGran, bytes);
// then build sc_tpu.indirect_vector_stream_start with the VREG operands as a ValueRange
…
```text
有两点值得保留:(1) builder 校验每个 stream 的 byte count 都必须是**两个**端点 transfer granularity 以及芯片 SparseCore minimum granularity 的倍数,这是约束单次 stream pass 的 granularity 模型;(2) 它的 diagnostic string 使用 `"IndirectStreamOperation"`(通用名称),与 SREG 形式共享。该 builder 是 VREG 形式复用的公共入口,由携带 offset 的操作数参数化。
> **NOTE — 这里的 `SupportsSparseCore()` 是硬 `LOG(FATAL)`,不同于 `SupportsScVar`。** 此 builder 断言 target 整体支持 SparseCore(`target.h:1757`);它不是 [`GetTransferKind`](getsequencertype.md#layer-1-gettransferkind--stream-vs-dma) 用来门控 kStream 路由的 `SupportsScVar` capability bit。重新实现者必须区分这两个 target predicate:`SupportsSparseCore()` 是 build-time invariant(对每个 SC generation 都为 true),`SupportsScVar` 是每 generation capability,在该 wheel 中为 0。
---
## 各 Generation 的存在性
| 机制 | VF (vfc, Viperfish) | GL (glc, Ghostlite) | GF (gfc, 6acc60406) |
|---|:---:|:---:|:---:|
| `IndirectVregStream` 形式(opcode `0x38`) | yes | yes | yes |
| 位于 **TEC** Stream slot | yes | yes | yes |
| 位于 SCS Stream slot | **no** | **no** | **no** |
| 位于 TAC Stream slot | **no** | **no** | —(无 TAC) |
| `IndirectOffsets` field accessor | `0x1e936240` | `0x1ea7be20` | `0x1ebe30a0` |
| TEC IndirectVreg field-accessor 数量 | 21 | 22 | 26 |
| SCS / TAC IndirectVreg field-accessor 数量 | 0 | 0 | 0 |
| `sc_tpu.indirect_vector_stream_start` op + 3 个 index group | yes | yes | yes |
判别依据是 field-accessor namespace 前缀:每个 `IndirectVregStream` field accessor 都位于 `SparseCoreTecStream…` 下;任何 generation 中,在 `SparseCoreStream…`(SCS)或 `SparseCoreTacStream…`(TAC)下都**没有**。每代 TEC accessor 数量(21/22/26)的增长,是因为更多共享尾部字段获得了显式 accessor,而不是因为形式改变。`jxc`/`pxc` namespace 下没有 `SparseCoreStream` schema(这些 generation 没有 SparseCore)。
---
## 限制和待解决项
| 项目 | 状态 |
|---|---|
| Proto 差异 `#1…#4`(VREG offsets + mask + base 替代 2 个 SREG 操作数) | 从 descriptor 解码;共享 `#5…#22` 与 `IndirectStream` 相同 |
| `IndirectOffsets` / `IndirectAccessLengths` slot selector(6-bit,`+0x28>>27` / `+0x30>>2`) | 已读取 accessor body(`0x1ebe30a0` / `0x1ebe30c0`) |
| Opcode `0x38` @ bundle bit 181;两个 width-6 VREG selector @ bits 283 / 322 | TEC encoder `@0x1ebe33e0` 的 `case 0xB`:`BitCopy(…,181,…,6)`、`(…,283,…,6)`、`(…,322,…,6)` |
| 仅 TEC(SCS bound `0xa`,无 SCS/TAC field accessor) | encoder bound + 零 accessor 计数 |
| Op:3 个 index group,无 `OffsetIndices`,无 `getIndirectListType` | 已定位 getter;offset/list-type getter 缺失 |
| Index expansion 运行 3 个 group(Src/Dst/Sflag),没有第 4 个 | `expandSCStreamStart<IndirectVectorStreamStartOp>` `@0x134eb880` 主体 |
| VREG 操作数位于 adaptor segments 5/8/9,并作为 `Value` 解引用 | `rewriteSparseCoreStreamOpToLLVM` `@0x13560ce0` 主体 |
| 9 路 `(hbm4b, src, dst)` 逐路由 intrinsic dispatch + diagnostic | 九个 emitter closure + match chain;`emitError` string |
| Builder 校验 transfer byte-count 与 src/dst/min granularity | `InitiateIndirectVectorStreamOperation` `@0x13d870e0` 主体 |
| 逐 lane HW 地址算术(`base + offsets[lane] * list_stride`) | lowering 中无软件循环;HW intrinsic |
| Minibatch / variable-length 用例 | 由 `valid_offset_mask` + register-sourced ids 推断 |
| 6-bit selector 背后的物理 VREG-file 大小 | selector width 已知;file size 是 chip_parts geometry,尚未枚举 |
| 9 个 emitter lambda 到具体 `(src,dst)` memory-space pair 的映射 | dispatch 结构已确认;每个 lambda 的 space triple 未穷尽解码 |
| 512-bit bundle 中 TEC Stream slot 的绝对 bit base | 已知 opcode @ 181 / `indirect_offsets` @ 322;完整 slot-base partition 未映射 |
---
## 交叉引用
- [Stream Gather/Scatter](stream-gather-scatter.md) — `SparseCoreStream` proto、共享控制尾部、slot 位编码,以及此形式镜像的 `IndirectStream` indirect-DMA 描述符;逐元素地址公式。
- [getSequencerType](getsequencertype.md) — 将此形式固定到 TEC 并提供逐索引 dispatch 所用 `(src,dst)` 三元组的 SCS/TAC/TEC engine-assignment 策略(`GetTransferKind` kStream-vs-kDma)。
- [VectorLoad Slot](vectorload-slot.md) — 消耗 streamed rows 的 TILE_SPMEM gather-load 对应项。
- [SparseCore Architecture](architecture.md) — engine 角色和完整 embedding datapath。
- [SparseCore Overview](overview.md) — 此形式从寄存器执行的 host-table → HBM → SC gather 路径。
- [Embedding Minibatching](embedding-minibatching.md) — register-sourced id stream 服务的 per-physical-core minibatch 模型。
- [CBREG Circular-Buffer Register](cbreg.md) — 共享尾部 offset source 可推进的 sliding-window register。
- **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)