getSequencerType — SCS / TAC / TEC 引擎选择
本页中的每个函数地址、枚举值、属性字符串字节模式和路由常量,均读取自
libtpu-0.0.40-cp314wheel 中的libtpu.so(build-id89edbbe81c5b328a958fe628a9f2207d;buildlibtpu_lts_20260413_b_RC00)——来源包括具名函数的反编译 C++、demangle 后的符号表、嵌入的 proto 描述符字符串,以及off_22010DE0处的.rodata跳转表。其他版本会有所不同。
摘要
SparseCore 后端会把每个 lowering 后的 op 精确放置到三个 SparseCore 子引擎之一:SCS(标量控制)、TAC(tile-access / DMA)或 TEC(向量计算)。二进制文件将这种放置记录为每个函数一个名为 sc.sequencer 的字符串属性。这里不存在一个整体的“选择器”来推理某个 op 并返回引擎;相反,决策被拆成三层,本页从头到尾记录这些层:
- 一个策略分类器
GetTransferKind(@0x1351b140),它根据源/目标 memory-space 对以及一个 SparseCore 能力位,决定一次 off-tile 数据移动是 Stream(间接 gather/scatter)还是 DMA(连续块)传输。 - 一个region outliner(
LowerSequencerFunctionsPass/OutlineSequencerFunction),它给每个按引擎 outline 出来的func.func标上sc.sequencer = "scs"/"access"/"execute"。 - 一个很简单的属性访问器
LowerMemrefToMlo::getSequencerType(@0x13507760),它把该字符串读回,使后续 pass 选择匹配的每引擎 bundle codec(SparseCoreScsCodecBase/…TacCodecBase/…TecCodecBase)。
支撑这一切的是 tpu::TpuSequencerType C++ 枚举及其 TpuSequencerTypeToString 跳转表,也就是用于确定每引擎资源数组大小的运行时编号。该 C++ 枚举给 SparseCore 引擎的编号是 {SCS=3, TAC=4, TEC=5},与 codec 模板参数编号完全相同。存在差一的对应方,但它是 protobuf 枚举 TpuSequencerTypeProto:该枚举插入了一个 INVALID=0 槽,因此编号为 {SCS=4, TAC=5, TEC=6};TpuSequencerTypeFromProto switch 会减一,从 proto 跨到 C++ 枚举。本页统一解释这三者。
重新实现契约:
- op 级引擎标签是字符串,不是数字。 每个 op 的引擎归属是其所在 outline 函数上的
sc.sequencerStringAttr,三个已按字节确认的值只能是"scs"/"access"/"execute"。重新实现者必须按字符串路由;两个数值型TpuSequencerType枚举都不会出现在 op 本身。 getSequencerType是访问器,不是决策。LowerMemrefToMlo::getSequencerType(Operation&)返回optional<StringRef>:它读取sc.sequencer(先查 inherent attr,再回退到 dictionary attr)并返回其值,或返回nullopt。决策已在上游由GetTransferKind+ outlining 完成。- Stream-vs-DMA 由 memory spaces + 一个能力位决定。
GetTransferKind会规范化每个mlir::sparse_core::MemorySpace,按源空间跳转表分派,并且只有当目标空间位于 HBM/SPMEM/TILE_SPMEM gather 集合(bitmask0x210018/0x210004)且 target 报告 SparseCore-variable 能力(vtable[+0xa0],在此 wheel 中为 0)时才设置 kStream;否则为 kDma,非法 pair 会给出InvalidArgument诊断。 - 有两套编号,相差一,但分界在 proto-vs-C++,不是 runtime-vs-codec。 C++
tpu::TpuSequencerType枚举(TpuSequencerTypeToString渲染的枚举,且也是 codecEncoderBase非类型模板参数使用的枚举)编号为{TC=0, BARNA=1, BARNA_ADDR=2, SCS=3, TAC=4, TEC=5}——索引 0 没有INVALID槽,SCS 是3,与 codec 模板相同。差一的对应方是 protobuf 枚举TpuSequencerTypeProto,它确实以INVALID=0开始,因此编号为{TC=1, BARNA=2, BARNA_ADDR=3, SCS=4, TAC=5, TEC=6, SCv0=7/8}。TpuSequencerTypeFromProto(@0x20b36300) 是执行减一的桥(proto4→ C++3,等等)。gfc(6acc60406) 家族只携带 codec 参数 3 和 5,其SparseCoreTacCodecBase完全不存在。
| 它是什么 | SparseCore op→engine 分配机制(SCS / TAC / TEC) |
| op 级标签 | sc.sequencer StringAttr(12 字符名称);值为 "scs" / "access" / "execute" |
| 访问器 | LowerMemrefToMlo::getSequencerType @0x13507760 → optional<StringRef> |
| 策略分类器 | xla::tpu::sparse_core::GetTransferKind @0x1351b140(kStream vs kDma) |
| Outliner | LowerSequencerFunctionsPass::runOnOperation @0x13532120;OutlineSequencerFunction |
| 枚举 | tpu::TpuSequencerType;TpuSequencerTypeToString @0x20b362e0 over off_22010DE0 |
| C++ 枚举(ToString + codec) | TC=0 · BARNA=1 · BARNA_ADDR=2 · SCS=3 · TAC=4 · TEC=5(无 INVALID 槽) |
| Codec 模板枚举 | SCS=3 · TAC=4 · TEC=5(EncoderBase<…, TpuSequencerType=N>)——与 C++ 枚举相同 |
| Proto 枚举(差一对应方) | TpuSequencerTypeProto: INVALID=0 · TC=1 · BARNA=2 · BARNA_ADDR=3 · SCS=4 · TAC=5 · TEC=6 · SCv0=7/8 |
6acc60406 (gfc) | 无 TAC:gfc::…SparseCoreTacCodecBase = 0 个文件;只有 codec 参数 3 与 5 |
| 置信度 | CONFIRMED(由函数字节 / 符号 / 字符串表锚定),除非某行另有说明 |
引擎角色本身见 SparseCore 概览 与 架构;outliner 产物见 Region → Sequencer Outliner。
三层机制概览
一个 off-tile memory op(tpu.enqueue_dma、tpu.enqueue_indirect_dma)在进入 sequencer 专属 bundle 前会经过三层决策:
tpu.enqueue_dma / tpu.enqueue_indirect_dma (TC-framework op, has memref operands)
│
▼ LowerMemrefToMlo::lowerEnqueueDma @0x135105a0
LowerMemrefToMlo::lowerEnqueueIndirectDma @0x13511da0
│
▼ getTransferKind<EnqueueDMAOp> @0x135114a0 → GetTransferKind @0x1351b140
│ (srcMemSpace, dstMemSpace, local/remote bits, capability)
│
┌───┴────────────────────────┐
kStream ([result+8]=1) kDma ([result+8]=0 / InvalidArgument)
gather/scatter slot SparseCoreDma bulk slot
│ │
▼ ▼
emitted into a TileTask region (Access vs Execute)
│
▼ LowerSequencerFunctionsPass / OutlineSequencerFunction
stamps the outlined func.func with:
sc.sequencer = "scs" (control sequencer → SCS)
sc.sequencer = "access" (tile-fetch / gather → TAC) [VF/GL only]
sc.sequencer = "execute" (vector compute → TEC)
│
▼ later passes:
LowerMemrefToMlo::getSequencerType(op) @0x13507760 → reads sc.sequencer
→ select per-engine codec: SparseCoreScsCodecBase / …Tac… / …Tec…
```text
第 1 层(`GetTransferKind`)回答“这是 gather/scatter 还是 bulk copy?”。第 2 层(outliner)回答“这个 op 属于哪个引擎的程序?”,并把答案写成字符串。第 3 层(`getSequencerType`)是驱动 codec 选择的读回。三个数值型 `TpuSequencerType` 值只存在于*资源定尺*层(每引擎 bundle-limit 表),从不出现在 op 层。
> **注意 — 并不存在一个根据 op opcode 返回 SCS/TAC/TEC 的 `getSequencerType`。** 如果重新实现者期待一个 `switch(op.kind)` 选择器,是找不到的。op→engine 映射已经完全物化为*被 outline 出来的函数*上的 `sc.sequencer` 字符串;op 从其被 outline 进入的函数继承引擎(由下面的 `ParentFuncHasCoreSequencerTypeAttribute` trait 强制)。`getSequencerType` 只会重新读取该字符串。
---
## 第 3 层:`getSequencerType` 访问器
具名函数是三层里最简单的一层,也是本页标题所指的函数。反编译结果(`@0x13507760`)显示,它是一个字符串属性 getter,返回 17 字节的 `optional<StringRef>`(8 字节 data ptr、8 字节 length、1 字节 present flag):
```c
// mlir::tpu::LowerMemrefToMlo::getSequencerType(this=result, op) @0x13507760
// result layout: [0]=StringRef.data, [8]=StringRef.size, [16]=present
optional<StringRef> getSequencerType(Operation& op) {
// 1. fast path: inherent attr if the op's registered-info bit is set
// (*((u32*)op + 11) >= 0x1000000) AND getInherentAttr succeeds
Attribute a = op.getInherentAttr("sc.sequencer", /*len=*/12);
// 2. fallback: dictionary attr lookup on the op's attr dict (op + 56)
if (!a) a = op.getDiscardableAttrDictionary().get("sc.sequencer", 12);
// 3. must be a StringAttr (TypeID check against StringAttr::id)
if (a && typeid(a) == StringAttr::id) {
result = { StringAttr::getValue(a), /*present=*/1 }; // "scs"/"access"/"execute"
} else {
result = { /*present=*/0 }; // nullopt
}
return result;
}需要保留两个结构事实:
- 属性名称是 12 字符字符串
"sc.sequencer"(该字面量和长度12被烘进反编译体中的getInherentAttr(a2, "sc.sequencer", 12)调用)。相同的 name+length pair 也出现在HasCoreSequencerTypeAttribute和HasExecuteSequencerTypeAttribute(见下文)中,确认三者读取的是同一属性。 - 两步 inherent→dictionary 查找镜像了 MLIR 在固有属性(在 op 定义上声明)与可丢弃字典属性之间的拆分。访问器接受任一来源,因此 outliner 可以用对 op kind 方便的路径附加
sc.sequencer。
| 属性 | 值 |
|---|---|
| Function VA | 0x13507760 |
| 属性名 / 长度 | "sc.sequencer" / 12 |
| 返回类型 | optional<StringRef>(data、size、+16 处 present-byte) |
| 查找顺序 | inherent attr,然后 discardable dictionary attr |
| 类型保护 | StringAttr TypeID(TypeIDResolver<StringAttr>::id) |
| 返回值 | "scs" / "access" / "execute" |
属性值 — 按字节确认
三个合法的 sc.sequencer 值不只是表里的字符串;其中两个会被专门的谓词函数匹配,其反编译字节比较钉住了精确拼写和长度。ScDialect::HasCoreSequencerTypeAttribute (@0x14599ec0) 和 ScDialect::HasExecuteSequencerTypeAttribute (@0x1459a020) 都复用同一个 sc.sequencer(len-12)访问器,然后把 StringAttr 值与长度及 packed byte literal 比较:
// HasCoreSequencerTypeAttribute @0x14599ec0 — value == "scs"
if (len == 3)
return ( (*(u16*)v ^ 0x6373) | (*(u8*)(v+2) ^ 0x73) ) == 0; // 's','c' | 's'
// 0x6373 LE = bytes {0x73='s', 0x63='c'}; v[2]=0x73='s' → "scs"
// HasExecuteSequencerTypeAttribute @0x1459a020 — value == "execute"
if (len == 7)
return ( (*(u32*)v ^ 0x63657865) | (*(u32*)(v+3) ^ 0x65747563) ) == 0;
// 0x63657865 LE = {'e','x','e','c'}; (v+3) 0x65747563 LE = {'c','u','t','e'}
// overlapping at offset 3 → "exec"+"cute" = "execute"
```text
解码 little-endian mask:
| `sc.sequencer` 值 | 引擎 | 长度 | Byte-literal 证据 | 谓词 |
|---|---|:---:|---|---|
| `"scs"` | **SCS**(标量控制) | 3 | `0x6373`="sc", `0x73`="s" | `HasCoreSequencerTypeAttribute` `@0x14599ec0` |
| `"execute"` | **TEC**(向量计算) | 7 | `0x63657865`="exec", `0x65747563`="cute" | `HasExecuteSequencerTypeAttribute` `@0x1459a020` |
| `"access"` | **TAC**(tile-access / DMA) | 6 | —(无专用 `Has*` 谓词) | — |
> **陷阱 — `"access"` 没有专用谓词。** SCS 与 TEC 各有一个 `Has…SequencerTypeAttribute` 测试,因为 SC-MLO pipeline 会在 SCS↔TEC 边界上运行;第三个值 `"access"`(TAC)在此二进制文件中没有对应的 `Has*` 函数。这与 6acc60406 (`gfc`) 家族已完全删除 TAC 一致:在最新代上,本应落入 `"access"` 函数的工作会折叠进 `"execute"` 函数(见 [TAC 引擎](tac-engine.md) 和 [Region → Sequencer Outliner](region-to-sequencer-outliner.md))。只建模 SCS/TEC pair 的重新实现者可以生成正确的 6acc60406 代码;只有 Viperfish/Ghostlite 需要 `"access"` 值。
### 父函数 trait
`sc.sequencer` 属性位于*被 outline 出来的函数*上,而不是单个 op 上。要求该属性存在的 op 会带有 `OpTrait::ParentFuncHasCoreSequencerTypeAttribute` trait(已在 `TileTaskWaitOp` `@0x14689880` 验证;同一 trait 附着于 TileTask family)。共享检查是 `ParentHasSequencerTypeAttribute` (`@0x1353e980`):
```c
// ParentHasSequencerTypeAttribute @0x1353e980
// walk parent ops until the enclosing LLVMFuncOp, then test BOTH predicates
for (op = start; ; op = op->getBlock()->getParentOp()) {
if (!op) return false;
if (typeid(*op) == LLVMFuncOp::id) break; // reached the outlined func
}
h_core = HasCoreSequencerTypeAttribute(func); // "scs"?
h_exec = HasExecuteSequencerTypeAttribute(func); // "execute"?
// require BOTH predicate calls to have evaluated (present bit 0x100 set on each),
// then return their OR of the low (match) bits
return (h_core & 0x100) && (h_exec & 0x100) ? (h_core | h_exec) & 1 : false;因此该 trait 会沿 op 树上爬到外围 LLVM::LLVMFuncOp,并断言该函数已标记为 "scs" 或 "execute"。这是二进制文件中对每个 TileTask op 都在 verify 时已知其引擎的函数内运行的强制;引擎归属是函数作用域属性,不是 per-op 字段。
第 1 层:GetTransferKind — Stream vs DMA
在 op 能被 outline 进某个引擎前,lowering 必须决定它是 Stream(间接 gather/scatter,embedding datapath)还是 DMA(连续块移动)。这就是 xla::tpu::sparse_core::GetTransferKind (@0x1351b140),它通过类型化 wrapper getTransferKind<EnqueueDMAOp> (@0x135114a0) 和 getTransferKind<WaitDMA2Op> (@0x135145e0),从 LowerMemrefToMlo::lowerEnqueueDma (@0x135105a0) 以及 lowerEnqueueIndirectDma (@0x13511da0) 到达。
其签名(demangled)为:GetTransferKind(const jellyfish::Target&, mlir::sparse_core::MemorySpace src, MemorySpace dst, bool, bool, bool, bool),返回 FailureOr<TransferKind>。反编译主体:
// GetTransferKind @0x1351b140 (args: target a2; src a3; dst a4;
// a5=src-local, a6=dst-local, a7=capability-allowed-flag, a8=strict-ordering)
// 1. normalize spmem: a space encoded as 1 maps to (16 if a7 else 21)
if (src == 1) src = 5*(a7 ^ 1) + 16; // → 16 (cap) or 21 (no cap)
if (dst == 1) dst = 5*(a7 ^ 1) + 16;
// 2. kStream only when BOTH endpoints are local (a6 & a5 == 1)
if ((a6 & a5) == 1) {
switch (src) { // jump table on the source memory space
case 2: /* HBM */ ... if (dst<=0x15 && bittest(0x210018, dst)) ok; // 2162712
else if (dst==6) ok only if target.vtable[+0xa0]() // SupportsScVar
case 3: /* HBM_4B*/ ... if (dst<=0x15 && bittest(0x210004, dst)) ok; // 2162692
case 4: case 21: ... if (dst==2) ok; // → HBM
case 6: ... ok only if a7 && dst==2 && target.vtable[+0xa0]()
case 16: /* SPMEM */ ... if (a7 && ((dst-2)&~2)==0) ok; // dst in {2,4}
default: goto kDma;
}
result.kind = kStream; result.present = 1; // [this+8]=1; [this]=1
return;
}
// 3. otherwise kDma — but only for a recognized legal contiguous pair;
// an unrecognized pair builds an InvalidArgument status
kDma:
if (legal_dma_pair(src, dst, a6)) { result.kind = kDma; [this]=1; return; }
// diagnostic (transfer_emitter.cc:196):
return InvalidArgument(
"SparseCore does not support transfers with %s ordering from %s %v to %s %v "
"issued %sfrom TEC.", ordering, srcLocality, src, dstLocality, dst, fromTec);
```text
这些路由常量对重新实现很重要:
| 机制 | 值 | 含义 |
|---|---|---|
| spmem 规范化 | `src/dst==1 → 5*(¬cap)+16` | encoded `1` → 16(cap)或 21(no cap) |
| both-local gate | `(dst_local & src_local) == 1` | kStream 要求两个端点均为 local |
| HBM dst-set bitmask | `0x210018` (2162712) over `dst` | 来自 HBM 源的可 gather 目标 |
| HBM_4B dst-set bitmask | `0x210004` (2162692) over `dst` | 来自 HBM_4B 的可 gather 目标 |
| 能力槽 | `target` vtable `+0xa0` | `SupportsScVar` 谓词 |
| kStream 结果 | `[result+0]=1, [result+8]=1` | FailureOr success + `kind=kStream` |
| kDma 结果 | `[result+0]=1, [result+8]=0` | FailureOr success + `kind=kDma` |
| illegal-pair diag | `transfer_emitter.cc:196` `InvalidArgument` | "SparseCore does not support transfers…" |
> **注意 — 该能力位是 `SupportsScVar` 谓词,并且在此 wheel 中为 0。** 对 `target.vtable[+0xa0]()` 设门的 case(`src==6` 以及 HBM 的 `dst==6` 子分支)会调用 `SparseCoreTarget` 上的虚方法;交叉引用把此槽解析为 `SupportsScVar`(Ghostlite `0x1d499340`,Viperfish `0x1d49c7e0`),在这里发布的每一代上都返回 0。因此这些由能力设门的 Stream 路径在此 build 中被*编译掉*,会落入 kDma 路径。面向这些芯片的重新实现者必须把 `SupportsScVar` 视为 false。
>
> **陷阱 — 诊断文本说 "from TEC",确认 kDma/Stream 拆分以 TEC 为中心。** `transfer_emitter.cc:196` 消息("…issued %sfrom TEC")和 `MemorySpace` 操作数表明分类器是在推理*从 TEC 向量引擎发出的*传输。这直接对应 [IndirectVregStream](indirect-vreg-stream.md) 是一种仅 TEC 使用的 Stream 形式:gather/scatter datapath 锚定在 TEC 上,而 `GetTransferKind` 是决定 off-tile 移动使用该 datapath(kStream)还是普通 bulk descriptor(kDma)的 gate。
完整 memory-space 枚举以及按 (core,mem) 解析 DMA-destination 的细节不在本文范围内;Stream-slot descriptor 见 [Stream Gather/Scatter](stream-gather-scatter.md),这些 lowering 在 pass 顺序中的位置见 [SC 后端 Pipeline](sc-backend-pipeline.md)。
---
## 第 2 层:Region Outlining — `sc.sequencer` 写入处
`GetTransferKind` 的 kStream/kDma 结果会结合 op 的数据依赖,决定某个 op 被发射到哪个 TileTask region 中。随后 `LowerSequencerFunctionsPass::runOnOperation` (`@0x13532120`) 把每个 region outline 成独立的 `LLVM::LLVMFuncOp`,并给它打上 `sc.sequencer` 字符串。反编译后的 pass 主体很大(它还通过 `GetParameterTable` `@0x13534ec0` 构建每引擎参数表,并通过 `LoadPointersFromHbm` `@0x13536c40` 加载 HBM 指针);引擎打标步骤是附加 StringAttr 的 `OutlineSequencerFunction` 回调。
outliner 产出的映射:
| TileTask region | `sc.sequencer` | 引擎 | 承载内容 | 存在于 |
|---|---|---|---|---|
| Control / sequencer | `"scs"` | **SCS** | program counter、addressing、sync-flag/atomic issue、SCS Stream/DMA slots | vfc · glc · gfc |
| Access | `"access"` | **TAC** | tile-fetch DMA issue、gather-stream issue、address staging | vfc · glc only |
| Execute | `"execute"` | **TEC** | vector reductions、pack/unpack、TEC Stream slot(incl. IndirectVreg) | vfc · glc · gfc |
> **注意 — MLIR tile-task pipeline 只发射 `"scs"` 与 `"execute"`;`"access"` 是 codec/proto 路径上的引擎。** 上面的 `"access"` 行是 *`TpuSequencerType=4` TAC codec* 服务的引擎,并且在 Viperfish/Ghostlite 上真实存在。但 outlining 回调(`0x136066e0`)的反编译显示它只引用 `"execute"` 值字符串(`@0x8681624`,7 chars)和 `sc.sequencer`:它在**每一代**上都无条件把 tile body 标为 `"execute"`,从不写入 `"access"`(不存在 `HasAccessSequencerTypeAttribute` 谓词,也没有任何 lowering 链中的 length-6 `"access"` compare)。TAC 引擎通过 legacy `ProgramWrapper.tac` proto 字段和独立的 `SparseCoreTacCodecBase` 到达,而不是通过 MLIR tile-task outliner。因此在 VF/GL 上,*MLIR* 路径同样通过 TEC Stream slot 把 gather/scatter 折叠进 `"execute"`(TEC)函数——这与 6acc60406 的形态相同,后者还完全没有 TAC codec。完整的字节级说明见 [TEC 引擎](tec-engine.md),支撑这一点的 TEC-exclusive Stream 形式见 [IndirectVregStream](indirect-vreg-stream.md)。
对于给定 lowering 后 op,选择 Access region 还是 Execute region 的精确 per-op 规则没有在本分析中逐字节追踪(`GetTransferKind` 结果加上 op 的 tile-data 依赖会输入其中)。这里标为 LOW,并由 [Region → Sequencer Outliner](region-to-sequencer-outliner.md) 负责。
---
## `TpuSequencerType` 枚举及其跳转表
字符串机制底下是数值型 `tpu::TpuSequencerType` 枚举,用于确定每引擎资源表(例如 bundle limit)的大小与索引。`TpuSequencerTypeToString` (`@0x20b362e0`) 会把它渲染成文本;该函数是一个纯跳转表:
```c
// tpu::TpuSequencerTypeToString(unsigned a1) @0x20b362e0
__int64 TpuSequencerTypeToString(unsigned a1) {
return (__int64) *(&off_22010DE0 + a1); // indexed array of C-string pointers
}off_22010DE0 表直接索引到 .rodata 中确认的字符串表字面量(通过数组的 R_X86_64_RELATIVE relocation 解析)。其顺序确定了 C++ 运行时编号,并且第一个条目是 TensorCore sequencer,不是 INVALID 占位符:
| C++ 枚举值(表索引) | off_22010DE0[i] 字符串字面量 | 简称 | Bundle |
|---|---|---|---|
| 0 | "TensorCoreSequencer" | TC | — |
| 1 | "BarnaCoreSequencer" | Barna | — |
| 2 | "BarnaCoreAddressHandler" | Barna-AH | — |
| 3 | "SparseCoreSequencer" | SCS | 32 B |
| 4 | "SparseCoreTileAccessCoreSequencer" | TAC | 64 B |
| 5 | "SparseCoreTileExecuteCoreSequencer" | TEC | 64 B |
off_22010DE0 数组正好有这六个 SparseCore/TensorCore/BarnaCore 条目;索引 6 已属于相邻的无关字符串数组("IMEM")。因此 C++ 枚举没有 INVALID 槽,也没有 SCv0 条目;SCv0 只存在于 protobuf 枚举(见下文)。解析 relocation:索引 0 → 0x85b767c "TensorCoreSequencer",索引 3 → 0x85b76b3 "SparseCoreSequencer",索引 5 → 0x85b7690 "SparseCoreTileExecuteCoreSequencer"。
差一之处:C++/codec 枚举 vs protobuf 枚举
codec 层与 TpuSequencerTypeToString 层共享同一个 C++ 枚举。每引擎 codec 以 TpuSequencerType 作为非类型模板参数进行模板化:EncoderBase<…SparseCore{Scs,Tac,Tec}CodecBase…, TpuSequencerType=N>;这里的值为 {SCS=3, TAC=4, TEC=5},与上面的 off_22010DE0 索引完全匹配(nm 显示 demangled 模板字面量 (TpuSequencerType)3 ×32 与 (TpuSequencerType)4 ×16;codec-metadata BundleSizeBytes(TpuVersion, TpuSequencerType) @0x1ecf7180 消费同一个 C++ 枚举)。codec 与 runtime 之间没有差一,两者都是 {SCS=3, TAC=4, TEC=5}。
差一发生在第三套编号上:protobuf 枚举 TpuSequencerTypeProto(.rodata 中的描述符 TpuSequencerTypeProto,完整 TPU_SEQUENCER_TYPE_* 字面量)。它确实以 INVALID=0 开头,并编号为 {INVALID=0, TC=1, BARNA=2, BARNA_ADDR=3, SCS=4, TAC=5, TEC=6, SCv0=7, SCv0-AH=8}。proto→C++ 桥是 tpu::TpuSequencerTypeFromProto (@0x20b36300);其 switch 映射 proto 1→0, 2→1, 3→2, 4→3, 5→4, 6→5(SparseCore 块上就是字面减一),并且以 Invalid sequencer type 拒绝 proto 7/8(SCv0),确认 SCv0 完全没有 C++ 枚举值:
| 引擎 | sc.sequencer 字符串 | C++ 枚举(ToString + codec) | Proto 枚举 |
|---|---|---|---|
| SCS | "scs" | 3 | 4 |
| TAC | "access" | 4(仅 vfc/glc) | 5 |
| TEC | "execute" | 5 | 6 |
陷阱 — +1 发生在 proto 边界,而不是 codec 边界。 对同一引擎而言,protobuf
TpuSequencerTypeProto值比 C++TpuSequencerType(以及 codec 模板)值大一,因为只有 proto 枚举保留了INVALID=0。TpuSequencerTypeFromProto@0x20b36300是受认可的转换点(减一)。把 proto ordinal 直接送入 codec 模板选择器的重新实现会选错引擎;把 C++ 枚举直接传过去则是正确的。op 级分配不使用任何数字,而是使用sc.sequencer字符串。
Codec-base 存在性确认 gfc 缺失 TAC
按 family namespace 统计反编译出的 SparseCore{Scs,Tac,Tec}CodecBase 实例化文件,直接确认了差一表所暗示的 TAC 移除(gfc 只携带 codec 参数 3 与 5,从不携带 4):
| Family ns | 代际 | SparseCoreScsCodecBase 文件 | SparseCoreTacCodecBase 文件 | SparseCoreTecCodecBase 文件 |
|---|---|---|---|---|
vfc | Viperfish | 13 | 13 | 13 |
glc | Ghostlite | 30 | 30 | 30 |
gfc | 6acc60406 | 31 | 0 | 32 |
6acc60406 (gfc) namespace 中 SparseCoreTacCodecBase 文件为零,而 Viperfish/Ghostlite 分别为 13/30;SCS 与 TEC codec base 则存在。gfc 中没有以 TpuSequencerType=4 参数化的 codec,因此运行时在 6acc60406 上永远无法选择 TAC 引擎,"access" sequencer 值在那里不可达——这正是第 2 层记录的折叠。
SCv0 — 仅枚举保留
两个尾部 proto 值(TPU_SEQUENCER_TYPE_SPARSE_CORE_V0_SEQUENCER = 7,…_V0_ADDRESS_HANDLER = 8)命名的是旧的单体 SparseCore 前身。它们在此 build 中仅作为 TpuSequencerTypeProto 上的 proto-descriptor 字面量保留:它们不在 off_22010DE0 C++ TpuSequencerTypeToString 表中(该表在 TEC,即索引 5 处停止),并且 TpuSequencerTypeFromProto @0x20b36300 会以 Invalid sequencer type 拒绝它们,因此它们完全没有 C++ TpuSequencerType 值。没有任何 SCv0 codec、encoder、decoder 或 sc.sequencer 值(如 "scs0" 等)随此 build 发布。引擎选择机制永远不会产生 SCv0 标签;getSequencerType 只返回三个 live 值。完整的 SCv0 废弃说明见 SparseCore 概览。
重新实现清单
要复现 SparseCore 引擎选择:
- 把
sc.sequencer建模为函数作用域 StringAttr,合法值恰好为"scs"/"access"/"execute"三个。在 outlining 期间附加它;不要逐 op 附加。通过父函数 trait 在 TileTask op 上强制它存在。 - 把
getSequencerType实现为纯访问器:先查 inherent-attr,回退到 dictionary-attr,进行 StringAttr 类型保护,返回optional<StringRef>。它不做任何决策。 - 把
GetTransferKind实现为 kStream/kDma gate,包括精确的 memory-space 规范化(1 → 5*(¬cap)+16)、both-local gate、每个源空间对应的0x210018/0x210004目标 bitmask、SupportsScVar能力调用(在这些芯片上为 false),以及非法 pair 的InvalidArgumentfallback。 - 按 region outline,并在 TAC 不存在时把 "access" 折叠到 "execute"(6acc60406 /
gfc家族)。是否存在"access"函数应由 target 是否携带SparseCoreTacCodecBase设门。 - 分离 proto 与 C++ 编号:C++
TpuSequencerType{SCS=3,TAC=4,TEC=5}同时用于资源定尺表(TpuSequencerTypeToString、codec-metadata)和 codec 选择(模板参数),因此这两者之间无需转换。唯一的 +1 在 protobuf 边界:TpuSequencerTypeProto{SCS=4,TAC=5,TEC=6}在索引任何以 C++ 枚举为 key 的表前,必须经过TpuSequencerTypeFromProto(减一)。
置信度摘要
| 论断 | 证据 |
|---|---|
getSequencerType 是返回 optional<StringRef> 的属性访问器 | 反编译 @0x13507760:inherent→dictionary sc.sequencer 查找,StringAttr guard |
属性名是 "sc.sequencer"(12 字符) | 三个 reader 函数中的 getInherentAttr(…, 12) 字面量 |
"scs" → SCS,"execute" → TEC,"access" → TAC | HasCore… @0x14599ec0 / HasExecute… @0x1459a020 中的 byte-literal compare;"access" 是第三个值(无谓词) |
| 引擎标签是函数作用域;op 通过 parent-func trait 继承 | ParentHasSequencerTypeAttribute @0x1353e980 上爬到 LLVMFuncOp;trait 已在 TileTaskWaitOp @0x14689880 验证 |
GetTransferKind 根据 memory-space pair + capability 选择 kStream vs kDma | 反编译 @0x1351b140:both-local gate、bitmask 0x210018/0x210004、vtable[+0xa0]、transfer_emitter.cc:196 diag |
SupportsScVar 能力在这些代际上为 0(能力设门的 Stream 路径被编译掉) | vtable[+0xa0] 解析为 SupportsScVar(GL 0x1d499340 / VF 0x1d49c7e0),=0 |
Feeder:lowerEnqueueDma @0x135105a0、lowerEnqueueIndirectDma @0x13511da0、getTransferKind<…> @0x135114a0/@0x135145e0 | demangled 反编译符号存在 |
Outliner 按 region 写入 sc.sequencer | LowerSequencerFunctionsPass::runOnOperation @0x13532120 + OutlineSequencerFunction |
6acc60406 (gfc) 把 "access" 折叠到 "execute"(无 TAC) | gfc::…SparseCoreTacCodecBase = 0 个文件 vs 13/30;无 HasAccessSequencerTypeAttribute |
TpuSequencerTypeToString 是 over off_22010DE0 的跳转表 | 反编译 @0x20b362e0:*(&off_22010DE0 + a1) |
C++ 枚举顺序 via off_22010DE0 = {TC=0, BARNA=1, BARNA_ADDR=2, SCS=3, TAC=4, TEC=5};无 INVALID 槽 | R_X86_64_RELATIVE relocation 解析 idx0→"TensorCoreSequencer"、idx3→"SparseCoreSequencer"、idx5→"SparseCoreTileExecuteCoreSequencer";idx6→"IMEM"(相邻表) |
| Codec 模板枚举 {SCS=3,TAC=4,TEC=5} == C++ 枚举(没有差一) | nm (TpuSequencerType)3×32 / )4×16;codec-metadata BundleSizeBytes @0x1ecf7180 以同一 C++ 枚举为 key |
Proto 枚举 {INVALID=0…SCS=4,TAC=5,TEC=6,SCv0=7/8};相对 C++ 枚举 +1,并由 TpuSequencerTypeFromProto 桥接 | TpuSequencerTypeProto descriptor literals;FromProto @0x20b36300 switch 映射 proto 4→3,5→4,6→5,拒绝 7/8 |
| per-op Access-vs-Execute region 规则;runtime→codec 转换点 | 本分析未逐字节追踪 |
交叉引用
- SparseCore 概览 — 三类引擎、每代存在性以及 SCv0 仅枚举保留的故事。
- 架构 — 引擎角色,以及 engine split 服务的 embedding datapath。
- SCS(标量)引擎 —
"scs"控制 sequencer。 - TAC 引擎 —
"access"tile-fetch 引擎,以及它在 6acc60406 (gfc) 家族上的移除。 - TEC(向量)引擎 —
"execute"向量计算引擎。 - Region → Sequencer Outliner — 把计算划分为每引擎函数并写入
sc.sequencer的 pass。 - IndirectVregStream — 仅 TEC 使用的 Stream 形式,其存在把 kStream datapath 锚定在 TEC 上。
- Stream Gather/Scatter — kStream 路径到达的 indirect-DMA descriptor。
- SC 后端 Pipeline —
GetTransferKind、outlining 与getSequencerType在 SparseCore pass 顺序中的位置。 - Binary:
extracted/libtpu-0.0.40-cp314-cp314-manylinux_2_31_x86_64/libtpu/libtpu.so(build-id89edbbe81c5b328a958fe628a9f2207d) - 索引条目: Part IX — SparseCore & BarnaCore / SparseCore engines — 返回索引