Rolled / Strided / General 发射器
本页所有地址均适用于
libtpu-0.0.40-cp314wheel 中的libtpu.so(构建libtpu_lts_20260413_b_RC00,build-id md589edbbe81c5b328a958fe628a9f2207d)。该镜像未 strip;反混淆后的 C++ 符号名按原文引用。.textVMA 等于文件偏移(.text基址0xe63c000);.rodataVMA 等于文件偏移;.data.rel.ro带有0x200000的 VMA→文件 delta。其他版本会不同。
摘要
当 Mosaic TPU lowering pass 已经对一个 EnqueueDMAOp 做了 tiling、选定传输类型,并决定 tile grid 的形状之后,它必须发射实际的 SparseCore DMA / Stream op。这正是 lower_pass_base.cc 和 lower_memref_to_mlo.cc 中三个函数的职责:issueRolledTransfer 将剩余 tile grid 卷入一个 scf.for;issueStridedTransfer 是逐 tile 回调,把一个 DmaParameters 包转换成一个具体的 sc_tpu.* op;issueGeneralDma 组装跨 core / 多 stride 的 GeneralDma 形式。本页覆盖这三个发射器主体,以及 DmaSimpleStart / DmaSingleStridedStart / DmaStridedStream / DmaGeneralStart 的 ODS builder 槽位字段映射,也就是 create-arg → Properties-offset → ODS-attr → slot-bit 链。
已经熟悉 MLIR 的 OpBuilder / ODS create() / build() 模式的读者会认出其形状:每个发射器都是一次 *StartOp::create 调用,它的按值传递 bool、IntegerAttr、StringAttr 和 CoreTypeAttr 会写入一个 getOrAddProperties<> Properties blob,并且一个固定的 +0x40 delta 将每个原始 Properties 偏移映射到相对 op 的 getter 偏移。本页记录的两个意外点是:(1) issueRolledTransfer 不会每个 tile 发射一个 op,而是先运行维度合并优化器,在循环之前展平连续的 tile 维度,然后只把剩余的 leading dimension 卷入单个 scf.for,其 body 通过手动填充(调用 ForOp::create 时传入 NULL body builder);(2) 形式选择完全由一个整数驱动,即 DmaParameters.src_byte_strides.size()(0 / 1 / >1)与 TransferKind(kDma / kStream)的组合,一个 3×2 网格精确映射到六个 op,其中两个格子在 emitOpError 处 trap。
负责决定 Simple-vs-Strided-vs-General 并运行前置 dim-coalescing 的 selector 是 DmaParameters 选择器 的主题;这些 op 最终写入的 descriptor 字段布局以及 mem_id/core_id/dma_type 枚举见 Intra-Chip DMA 描述符;产生地址 operand 的 tile-index→flat-offset 代数见 Tile-Index 展开。本页是它们之间的发射器层。
对重实现而言,契约是:
- rolled SCF 循环 —
issueRolledTransfer的 coalesce-then-roll 结构:三个并行的逐维度ArrayRef、contiguity-merge predicate、带 NULL body builder + 手动 body 填充的单个scf.for、每次迭代的index × stride偏移,以及可整除性emitOpError。 - 逐 tile 回调 —
issueStridedTransfer的二维分派(TransferKind×src_byte_strides.size())、DmaParameters一致性 invariant、kStream gather/scatter striding gate、granularity divisor,以及绑定到每个*StartOp::create的 operand。 - general 路径 —
issueGeneralDma的 remote-vs-local target base、双边 sync CHECK、destination_id拓扑算术、多级 stride descriptor,以及StringAttrselector。 - ODS builder 槽位映射 —
DmaSimpleStart、DmaSingleStridedStart、DmaStridedStream和DmaGeneralStart的 create-arg →Properties-offset → ODS-attr → slot-bit 链,包括不变量+0x40raw↔getter delta。
| Rolled 发射器 | mlir::tpu::LowerPassBase::issueRolledTransfer @ 0x13516ca0 (0x1614 B) |
| Strided 发射器 | mlir::tpu::LowerMemrefToMlo::issueStridedTransfer @ 0x1350cb60 (0x37cd B) |
| General 发射器 | mlir::tpu::(anonymous namespace)::issueGeneralDma @ 0x1350b3a0 (0x16ff B) |
| 源文件 | lower_pass_base.cc (rolled), lower_memref_to_mlo.cc (strided/general) |
| 逐 tile 回调目标 | lowerEnqueueDma::$_0 policy-func 0x13516720 → issueStridedTransfer |
| 六个发射出的 op | DmaSimpleStart 0x6, DmaSingleStridedStart 0x7, GeneralDma 0x8, LinearStream 0x3b, StridedStream 0x3a, + 2 个 error cell |
| ODS raw↔getter delta | +0x40(raw Properties offset + 0x40 == op-relative getter offset) |
| 证据等级 | 重实现级 / 已按所有三个主体的 IDA decompile + ODS create/build 对逐字节确认 |
1. issueRolledTransfer — SCF 循环的 rolled 路径
目的
issueRolledTransfer 是多 tile descriptor 路径:当一个 tiled DMA 覆盖的 tile grid 无法表示为一次连续传输时,该函数发射 tile-grid 遍历。它不是递归,也不是完全展开的“每个 tile 一个 DmaSimpleStart”发射器。相反,它 (a) 将连续 tile 维度合并为一个维度,然后 (b) 把单个剩余 leading dimension 卷入一个 scf.for,手动填充 loop body,并通过 LowerPassBase virtual 发出逐 tile 传输。issueRetiledTransfer (0x13519480) 总是把多 tile 情况路由到这里(0x13519f3d / 0x1351a1ea 的两个 call 0x13516ca0 位置);unrolled-vs-rolled 选择位于该函数内部的 coalescing loop 中。
入口点
issueRetiledTransfer (0x13519480) ── tile-grid driver
└─ issueRolledTransfer (0x13516ca0, 0x1614 B) ── coalesce + roll
├─ $_0 (0x135182c0) ── per-axis tile-stride product table
├─ scf::ForOp::create (0x17866d60) ── the rolled loop (NULL body builder)
├─ $_1 (0x13518480) ── arith.constant n : i32
├─ vtable+0x20 [LowerPassBase] ── getConstantIntValue probe (coalescing)
└─ vtable+0x18 [LowerPassBase] ── per-tile sub-memref / offset generator算法
该函数接收三个并行的逐维度数组:bases(ArrayRef<Value>,来自 getTiledDynamicShape 的 tile-grid extent)、srcTileStrides(ArrayRef<long>)和 dstTileStrides(ArrayRef<long>),每个 tile-grid 维度一个条目。prologue 对它们的长度做 CHECK_EQ;主体执行 coalesce、roll 和 issue。
// mlir::tpu::LowerPassBase::issueRolledTransfer sub_13516CA0
function issueRolledTransfer(b, op, bases, srcTileStrides, dstTileStrides,
v1, v2, L1, L2, v3, v4, devIds, prio, kind,
perTileCallback):
// 0. three parallel arrays, one entry per tile-grid dim
CHECK_EQ(bases.size(), srcTileStrides.size()) // "tiled_dynamic_shape.size() == src_tile_strides.size()" @0xa166591
CHECK_EQ(bases.size(), dstTileStrides.size()) // "== dst_tile_strides.size()" @0xa16655b
// 1. per-axis suffix-product multipliers (row-major), one per side
srcMul = $_0(srcTileStrides) // sub_135182C0: factor*shape[k] imul, SmallVector<unsigned>
dstMul = $_0(dstTileStrides) // same, dst side
// 2. COALESCE contiguous striding dims (see §2 for the predicate)
coalesceStridingDims(bases, srcTileStrides, dstTileStrides) // backward scan @0x13517070..0x13517301
// 3. emit ONE scf.for over the residual leading dim
dim = residual_leading_dim
forOp = scf::ForOp::create(b, op.getLoc(),
lb = arith.constant 0 : i32, // $_1(0)
ub = bases[dim], // the tile-count Value
step = arith.constant 1 : i32, // $_1(1)
inits = {}, // empty ValueRange
body = NULL, // function_ref left null
false) // sub_17866D60 @0x13517477
b.setInsertionPointToStart(forOp.body[0]) // manual body population
// 4. per-iteration base offset, per side
for side in {src, dst}:
idx = inductionVar-derived index
off = arith.muli(idx, tileStride) // MulIOp<V,V> 0xfaaae00 / <V,ConstOp> 0x1351ab00
// 5. issue the per-tile transfer through the LowerPassBase virtual
base = this->vtable[+0x18](b, op.getLoc(), tileStrideConst, idx, accum) // per-tile sub-memref (HIGH)
b.restoreInsertionPoint(savedIP)
// 6. divisibility guard
if (L1 % perIterStride) != 0: // idiv @0x13517726
return op.emitOpError("Tile size (%d bytes) not divisible by %d bytes. …") // @0xa01cd05
return successQUIRK — 调用
scf::ForOp::create时传入的是 NULLfunction_refbody builder(在0x13517433清零的{callee=0, callable=0}16 字节结构)。这会使ForOp::create只构造循环外壳(一个携带 induction variable 的 body block)。随后会手动填充 body:OpBuilderinsertion point 被重定向到 for-op 的 region-0 block(在0x135174ac写入OpBuilder+0x10/+0x18),在那里发射每次迭代的 op,之后恢复 insertion point(SpecificNodeAccess::getNodePtr@0x1d8ccbc0,0x1351763b)。传入真实 body builder lambda 的重实现会发射相同 IR,但该 binary 采用手动填充路线。(Decompile @0x13516ca0line 419:mlir::scf::ForOp::create(v173, v118, v119);CONFIRMED。)
函数映射
| 函数 | 地址 | 角色 |
|---|---|---|
issueRolledTransfer | 0x13516ca0 | rolled 发射器 |
issueRolledTransfer::$_0 | 0x135182c0 | 逐 axis tile-stride suffix-product 表(SmallVector<unsigned>) |
issueRolledTransfer::$_1 | 0x13518480 | arith.constant n : i32 builder |
mlir::scf::ForOp::create | 0x17866d60 | rolled loop shell |
MulIOp<Value,Value>::createOrFold | 0xfaaae00 | 每次迭代的 index × stride(两边都是 Values) |
MulIOp<Value,ConstantOp>::createOrFold | 0x1351ab00 | 每次迭代的 index × stride(折叠常量) |
OpState::emitOpError | 0x1d8cefa0 | 可整除性诊断 |
LowerPassBase vtable +0x18 | (no symbol) | 逐 tile sub-memref / offset generator |
LowerPassBase vtable +0x20 | (no symbol) | getConstantIntValue 风格 probe(coalescing) |
NOTE — 逐 tile op-emission helper(vtable
+0x18)和 constant-int probe(vtable+0x20)是抽象LowerPassBasevirtual,没有独立符号;具体 passLowerMemrefToMlo是匿名命名空间类,没有发射出的ZTV表。调用参数以及周围的index/offset算术已按字节锁定;方法名称是从这些参数推断出来的,因此是 HIGH,而非 CONFIRMED。
2. 维度合并 predicate(rolled prologue)
目的
coalescing pass 是 rolled 形式高效的原因:在发射 scf.for 之前,连续或退化的 tile 维度会被展平为一次更大的传输(更少 descriptor、更少循环迭代),因此循环只覆盖无法展平的剩余维度。作为 selector 概念的 dim-coalescing 优化器由 DmaParameters 选择器 负责;这里记录在 issueRolledTransfer prologue 中运行的逐字节精确 merge predicate,因为它控制 rolled loop。
算法
一次反向相邻对扫描(0x13517150)从最后一个维度向第一个维度遍历。对每一对(outer d,inner d+1),它在每个 base 上通过 LowerPassBase vtable +0x20 查询 getConstantIntValue probe;该 probe 返回打包为 {bit32 = present | low32 = value} 的 optional<int64>,因此标记 0x100000001 表示“present AND value == 1”。
// coalescing scan in issueRolledTransfer @0x13517070..0x13517301
function coalesceStridingDims(bases, srcStride, dstStride):
for d = N-2 downto 0: // backward adjacent-pair scan
innerC = getConstantIntValue(bases[d+1]) // vtable+0x20 @0x13517176
outerC = getConstantIntValue(bases[d]) // vtable+0x20 @0x13517160
merge = false
// COND (i): degenerate inner tile-count == 1
if (innerC & 0x1ffffffff) == 0x100000001: // @0x13517186
merge = true
// COND (ii): row-major contiguity, per side
elif (outerC & 0x100000000) != 0: // outer base is a constant int @0x1351721a
if srcStride[d] == srcStride[d+1] * outerC // imul; cmp @0x13517243
&& dstStride[d] == dstStride[d+1] * outerC: // imul; cmp @0x1351726a
merge = true
if merge:
bases[d] = arith.muli(bases[d], bases[d+1]) // MulIOp 0xfaaae00 @0x135171e5
VLOG(1) << "Flattening striding dimension " << d // lower_pass_base.cc:123, @0xa1f260b
memmove(bases + d+1, bases + d+2, …) // 8-byte entries @0x135170b1
memmove(srcStride+ d+1, srcStride+ d+2, …) // 4-byte entries @0x135170ea
memmove(dstStride+ d+1, dstStride+ d+2, …) // 4-byte entries @0x1351711c
N -= 1 // dec all three counts一对维度会被折叠,当且仅当要么 inner 维度的 tile-count 是常量 1(COND i,总是可折叠,跳过 stride 检查),要么 outer base 是一个常量整数,且 outer stride 等于 inner stride 乘以 outer extent,并且在两侧都成立(COND ii,真正的 row-major 连续性)。如果两者都不成立,该对会保持不变,并成为剩余 loop/stride 维度。当 VLOG 路径无法折叠某个 stride 时,它会记录 "Attempted to combine non-constant stride: "(@0xa27e861)并跳过。
| Predicate 步骤 | 测试 (@VA) | 含义 |
|---|---|---|
| outer-const probe | getConstantIntValue(bases[d]) @ 0x13517160 | vtable+0x20 constant-int probe |
| inner-const probe | getConstantIntValue(bases[d+1]) @ 0x13517176 | 同上,inner dim |
| COND (i) | (rax & 0x1ffffffff) == 0x100000001 @ 0x13517186 | inner tile-count 是常量 1 |
| COND (ii) gate | test r13, 0x100000000 @ 0x1351721a | outer base 是常量整数 |
| COND (ii) src | srcStride[d] == srcStride[d+1] × outerC @ 0x13517243 | 源端 row-major 连续性 |
| COND (ii) dst | dstStride[d] == dstStride[d+1] × outerC @ 0x1351726a | 目标端 row-major 连续性 |
| MERGE | muli + VLOG + 3× memmove + dec @ 0x13517070 | 将 (d, d+1) 折叠为一个 dim |
GOTCHA — 两个 merge 条件是 binary 中不同的分支目标。COND (i)(inner count == 1)完全跳过 stride 检查。对 count-1 dim 来说,它是 COND (ii) 的保正确超集,而不是冗余特例。只在 stride-contiguity 测试(COND ii)上折叠的重实现,将无法折叠退化的 count-1 维度,并会发射浪费的 1-trip loop level。(两个不同 jump target 已按字节锁定;(i) 对 zero-stride inner dim 是否在逻辑上被 (ii) 涵盖尚未证明,binary 将它们保持为分离。)
3. issueStridedTransfer — 逐 tile 回调 {#3-issuestridedtransfer--the-per-tile-callback}
目的
issueStridedTransfer 是统一的逐 tile DMA-op 发射器,也是 tiled-DMA 分派通过 lowerEnqueueDma::$_0 policy-func 0x13516720 每个 tile 调用一次的 std::function 回调目标。它读取一个 DmaParameters 包,并精确发射一个具体的 sc_tpu.* op。它是一个二维 selector:轴 1 是 TransferKind 参数(kDma=0 / kStream=1 / 其他 → emitOpError),轴 2 是 DmaParameters.src_byte_strides.size()(0 / 1 / >1)。这个 3×2 网格映射到六个 SparseCore off-tile op。
入口点
lowerEnqueueDma::$_0 (policy-func 0x13516720) ── std::function callback target
└─ issueStridedTransfer (0x1350cb60, 0x37cd B) ── per-tile emitter
├─ DmaSimpleStartOp::create (0x145b9740, opcode 0x6)
├─ DmaSingleStridedStartOp::create (0x145bcd20, opcode 0x7)
├─ issueGeneralDma (0x1350b3a0, → GeneralDma 0x8) [§5]
├─ LinearStreamStartOp::create (0x145e3440, opcode 0x3b)
├─ StridedStreamStartOp::create (0x1460b8e0, opcode 0x3a)
├─ isGather (0x14afb1e0) ── stream gather/scatter classifier
└─ $_0 (0x13510340) ── per-side stride-level validator算法
// mlir::tpu::LowerMemrefToMlo::issueStridedTransfer sub_1350CB60
function issueStridedTransfer(b, op, src, dst, p /*DmaParameters&*/,
devIds, priority, kind, force):
CHECK_EQ(priority, 0) // "priority == 0" @0x9fc7181 (lower_memref_to_mlo.cc:1142)
srcSpace = GetMemorySpace(p.src) // 0x1459c7e0 (from memref @p+0x00)
dstSpace = GetMemorySpace(p.dst) // (from memref @p+0x08)
trace = op.getInherentAttr("sc.inside_trace_region") != null // @0x85fd7e8
n = p.src_byte_strides.size() // p+0x18 — the form selector
switch kind:
case kDma (0):
if n == 0 && !force: // contiguous
len = ceil_div(p.length, Target::GranuleBytes()) // 0x1d617f80
return DmaSimpleStartOp::create(b, loc, src, {0}, dst, {0}, len, sflagIdx, sflag) // 0x6
// Strided/General arm — consistency invariant first:
CHECK_EQ(p.src_byte_strides.size(), p.tgt_byte_strides.size()) // @0xa166501 (:843)
CHECK_EQ(p.src_byte_strides.size(), p.steps_per_stride.size() - 1) // @0x9e79ab6 (:844)
if n == 1:
return DmaSingleStridedStartOp::create(…, srcStride, dstStride, innerVecLen, elemsPerStride) // 0x7
else: // n > 1
return issueGeneralDma(b, locGen, target, op, src, dst, len, A, B, C, …) // GeneralDma 0x8
case kStream (1):
isG = isGather(op) // 0x14afb1e0
// gather/scatter striding gates (per side, via $_0 0x13510340 → vtable+0x20):
if isG && $_0(p.tgt_byte_strides) > 0: return op.emitOpError("Gather streams do not support destination striding. …") // @0xa06d0da
if isScatter && $_0(p.src_byte_strides) > 0: return op.emitOpError("Scatter streams do not support source striding. …") // @0xa06d17f
gran = TransferGranularityInBytes(target, dstSpace, …) // 0x14a89ea0 via target->[+0x948]
if n == 0: return LinearStreamStartOp::create(b, loc, false, dstIsHbm, trace, 0, …) // 0x3b
if n == 1: return StridedStreamStartOp::create(b, loc, false, dstIsHbm, trace, …, stride0, stride1, …) // 0x3a
else: return op.emitOpError("Streams support up to 1 level of striding. Got %d levels of …") // @0xa092a49
default:
return op.emitOpError("Unsupported transfer kind: %d") // @0x872e1dd形式选择网格
src_byte_strides.size() | kDma (0) | kStream (1) |
|---|---|---|
| 0(连续) | DmaSimpleStart 0x6 | LinearStream 0x3b |
| 1(一个 stride level) | DmaSingleStridedStart 0x7 | StridedStream 0x3a |
| >1(multi-stride) | issueGeneralDma → GeneralDma 0x8 | emitOpError(仅 ≤1 level) |
(其他 TransferKind) | emitOpError "Unsupported transfer kind: %d" | (同上) |
GOTCHA — kStream 最多支持一个 stride level。decompile 中有两个不同的 "Streams support up to 1 level of striding" 诊断:一个 keyed on source striding(
src_byte_strides.size() <= 1,lower_memref_to_mlo.cc:1189),另一个 keyed on steps per stride(:1193),外加单独的 gather/scatter gate。对收到的任意n >= 1都发射StridedStream的重实现,会错误编码任何流路径收到的 2+ level stride;只有 kDma 会把n > 1路由到GeneralDma形式。
DmaParameters 一致性 invariant
Strided/General 分支会 CHECK_EQ 包中的三个 size 字段(其 offset map 由 Tile-Index 展开 负责):|src_byte_strides| == |tgt_byte_strides| == |steps_per_stride| − 1。因此一个连续(0-stride)descriptor 具有 steps_per_stride.size() == 1:steps_per_stride vector 为 N 个 stride level 携带 N+1 个累计 step。
| 包字段 | 偏移 | 在 issueStridedTransfer 中的角色 |
|---|---|---|
src(memref Value) | +0x00 | → GetMemorySpace → src endpoint |
dst(memref Value) | +0x08 | → GetMemorySpace → dst endpoint |
src_byte_strides {data,size} | +0x10 / +0x18 | 形式 selector(+0x18) |
tgt_byte_strides {data,size} | +0x50 / +0x58 | CHECK_EQ vs +0x18 |
length/sflag Value | +0x90 | create len/sflag 参数 |
steps_per_stride {data,size} | +0x98 / +0xa0 | CHECK size == +0x18 + 1 |
NOTE — granularity divisor 因 kind 而异:kDma 使用带 ceil-div 惯用法的
Target::GranuleBytes()(0x1d617f80);kStream 使用通过target()->[+0x948]到达的xla_mlo_util::TransferGranularityInBytes(SparseCoreTarget const&, MemorySpace, bool)(0x14a89ea0),即逐 memory-space 的 SPMEM-stripe / DMA-word 单元。选定的 granularity 会变成一个arith.ConstantIndexOpdivisor,在构建 op 之前折叠进DivUIOp/idiv。
4. ODS builder 槽位字段映射(contiguous + single-stride 形式)
目的
每个 *StartOp::create 都是一个薄 wrapper,它按参数顺序原样转发到 build();后者添加 operand group(AttrSizedOperandSegments),并把 inherent attribute 写入 getOrAddProperties<…Properties>() blob。整个家族的不变量是 +0x40 delta:op getter 会在原始 Properties offset 上加常量 0x40(op-relative getter offset = raw + 0x40)。本节映射 contiguous(DmaSimpleStart 0x6)和 single-stride(DmaSingleStridedStart 0x7、DmaStridedStream 0x3a)形式的 create 参数;GeneralDma 映射在 §6。
DmaSimpleStart (0x6)
DmaSimpleStartOp::create(0x145b9740)是一个 wrapper,它提供 enable_trace=false、两个 null IntegerAttr 和 StringRef="";它转发到 build(0x1459ac00),后者添加 7 个 operand segment,并写入一个 0x40 字节的 Properties(TypeID @0x224e95c0)。7 个 create operand 参数是 (srcBase, srcIdx, dstBase, dstIdx, length, sflagIdx, sflag)。
| create 参数 / wrapper 默认值 | ODS getter(op-rel) | SparseCoreDma SimpleDma 槽位字段 |
|---|---|---|
enable_trace = false(默认) | getEnableTrace(+0x50) | TraceEn(slot +0x18 bit 41) |
srcMemIndex IntegerAttr = null | getSrcMemIndex(+0x58) | dma_sreg_source_offset |
dstMemIndex/opcode IntegerAttr = 0 | getDstMemIndex(+0x40) / getDstOpcode(+0x48) | dst_opcode(slot +0x18 >>39 &0x3) |
srcBase Value | getSrcBufferIndicesMutable | src_mem_{core,mem}_id(通过 GetMemorySpace) |
dstBase Value | getDstBufferIndicesMutable | dst_mem_{core,mem}_id(通过 GetMemorySpace) |
length Value | (operand seg) | dma_length(slot +0x18 >>42 &0x3f) |
sflagIdx, sflag | getDstSflagIndicesMutable | dest_sync_flags(slot +0x18 >>15 &0x3f) |
dst_opcode string-attr → 2-bit code 映射(write_4b / read_and_add / atomic_add)及其 memory-space gate 由 Intra-Chip DMA 描述符 §2 负责;GetDstOpcode<DmaSimpleStartOp> accessor(0x135aaa60)读取 this+0x48 处可选的 dst_opcode attribute。
DmaSingleStridedStart (0x7)
DmaSingleStridedStartOp::create(0x145bcd20)的反混淆签名是 (b, loc, V, VR, V, VR, V, VR, V, V, V, V, V),它是 DmaSimpleStart operand 集再加上 4 个尾随的 Value stride 参数,这些参数由 src_byte_strides[0]、tgt_byte_strides[0]、steps_per_stride span 和 inner-vector-length(通过 arith.ConstantIndexOp/IndexCastOp 物化)构建。按位置,这 4 个 stride 绑定到 SingleStridedDma {source_stride, destination_stride, inner_vector_length, elements_per_stride} 槽位字段(slot +0x10 >>41/35/—/29,以及 +0x18 >>10)。create arity(4 个 stride)和 SmallVector 读取已 CONFIRMED;精确的 1-to-1 stride-arg → slot-field 顺序按位置推定(HIGH,其 build body 未独立解码)。
DmaStridedStream (0x3a)
StridedStreamStartOp::create(0x1460b8e0)的反混淆签名是 (b, loc, b, b, b, V, VR, V, VR, V, V, V, IntegerAttr, V, VR),它是 LinearStream operand 集再加上 2 个尾随 stride Value(stride0、stride1)以及 IntegerAttr tile_local length-per-stride。前三个 bool 镜像 LinearStream 的 {upd, hbm4b/dstIsHbm, enable_trace}。这 2 个 stride 映射到 StridedStream {stride0, stride1} operand;create-arg 形状已 CONFIRMED,slot-bit join 是结构性推断(HIGH)。
NOTE —
LinearStream(0x3b)contiguous-stream 形式的完整 create→Properties→slot 映射(upd/hbm4b/enable_trace/opcode/tile_local_length_per_stride绑定)由 SparseCore stream slot-encoding 页面负责;在本页它是 §3 网格中的n==0/kStream 格。issueContiguousTransfer的 stream 调用是(false, dstIsHbm, TraceLocalDma, 0, …)⇒upd=false, hbm4b=dstIsHbm, enable_trace=TraceLocalDma, opcode=0。
5. issueGeneralDma — general-DMA 路径
目的
issueGeneralDma 是 GeneralDma(0x8)形式的组装器,也是唯一携带 >1 stride level 的 DMA 形式,以及跨 core / 跨 device 形式。issueStridedTransfer 将 kDma 的 n > 1 情况路由到这里。这个自由函数(匿名命名空间,lower_memref_to_mlo.cc)计算双边 sync、来自 device topology 的显式 destination_id,以及多级 stride descriptor,然后发射四个 DmaGeneralStartOp::create overload 中最丰富的一个。
入口点
issueStridedTransfer (0x1350cb60) ── kDma, src_byte_strides.size() > 1
└─ issueGeneralDma (0x1350b3a0, 0x16ff B) ── GeneralDma assembler
├─ lowering_util::GetRemoteMemBase (0x13d88660) ── remote target base
├─ AllocateAtOffsetOp::create (0x145a5aa0) ── local sflag memref
├─ TpuChipConfig::Megacore (0x20afca00) ── topology gate
├─ {Tensor,Sparse}CoresPerLogicalDevice (0x111f6020 / 0x135159c0)
├─ CoreIndexOp::create (0x145aba00) ── runtime core index
└─ DmaGeneralStartOp::create (0x145b1b80) ── GeneralDma 0x8 (28-arg overload)算法
// mlir::tpu::(anonymous namespace)::issueGeneralDma sub_1350B3A0
function issueGeneralDma(b, locGen, target, op, src, dst, len,
A, B, C /*ArrayRef<Value> stride lists*/,
isRemote, srcSpace, dstSpace, devIds, v1, v2, syncBool):
// 1. remote-vs-local target base (the SOURCE side of two-sided sync)
if isRemote && devIds.present:
base = GetRemoteMemBase(b, locGen, …) // 0x13d88660 → remote base
elif isRemote: // remote but no source semaphore
return MakeError("Source semaphore must be provided for a remote DMA.") // @0xa0c8904 (:915)
else:
base = AllocateAtOffsetOp::create(ChipIdOp, MemorySpaceAttr(sflag=5), MemRefType, ConstantIndex) // local sflag
// 2. target-semaphore core-type CHECK (the SET side)
ct = op.operand(3).getType().getMemorySpace().getCoreType() // getCoreType 0x14a9e320
if !ct.present:
return MakeError("Target semaphore is missing a core type in its memory space") // @0x86e65f5 (:948)
// 3. destination_id from topology
if Megacore(target) && target.coresGate >= 2: // [target+0x3b8]+0x7c >= 2
if srcSpace == vmem || dstSpace == vmem:
return MakeError("Targeting VMEM on MegaCore targets is ambiguous.") // @0x9ffb522 (:968)
divisor = (dstCoreType == TENSOR_CORE) ? SparseCoresPerLogicalDevice * chipMeta[+0x90]
: (dstCoreType == 1) ? SparseCoresPerLogicalDevice
: TensorCoresPerLogicalDevice
destId = arith.addi(arith.{rem,div}ui(index_cast(CoreIndexOp), ConstantIndex(divisor)),
ConstantIndex(offset)) // RemUI 0x1cb20800 / DivUI 0x1cb06d00 / AddI 0x1caf0b00
// 4. multi-level (>1) stride descriptor
shape = SmallVector<unsigned>(inline cap 6) // header 0x600000000
fill shape per stride-dim (cmp r15,N je ladder N=1..6; grow_pod for >=7)
nondefault_stride_dimensions = strideDimCount // ≤8, 3-bit
// 5. attribute selectors
dst_opcode = (srcSpace == smem || dstSpace == smem) ? "write_4b" : null // @0x879a767
enable_trace = (same smem condition) ? UnitAttr : null
sync_mode = syncBool ? "count_dones" : "count_words" // cmovne @0x8562648 / @0x8570908
dma_ordering = "relaxed" // @0x86ff2e0
// 6. emit
return DmaGeneralStartOp::create(b, loc, enable_trace, src, srcIdx, base, …,
A, B, C, ct1..ct4, dst_opcode, sync_mode, dma_ordering) // 0x145b1b80函数映射
| 函数 | 地址 | 角色 |
|---|---|---|
issueGeneralDma | 0x1350b3a0 | GeneralDma 组装器 |
lowering_util::GetRemoteMemBase | 0x13d88660 | 跨 device remote target base |
AllocateAtOffsetOp::create | 0x145a5aa0 | 本地 sflag memref |
MemorySpaceAttr::getCoreType | 0x14a9e320 | target-semaphore core type |
TpuChipConfig::Megacore | 0x20afca00 | megacore gate |
Target::SparseCoresPerLogicalDevice | 0x135159c0 | dest_id divisor(SC) |
Target::TensorCoresPerLogicalDevice | 0x111f6020 | dest_id divisor(TC) |
CoreIndexOp::create | 0x145aba00 | runtime core index |
DmaGeneralStartOp::create | 0x145b1b80 | emit(28-arg overload) |
QUIRK —
GeneralDma的 descriptor attribute 会以StringAttr发射,而不是 slot decode 显示的整数枚举:dst_opcode = "write_4b"、sync_mode = "count_dones"/"count_words"、dma_ordering = "relaxed"。codec 在 encode 时把这些符号名映射到 bit field。直接把整数 enum 写入 op 的重实现会跳过 binary 使用的 string 层;硬件看到的 descriptor 相同,但 MLIR op 携带的是 string。除非src或dst是smem,否则dst_opcode为null(若是则"write_4b"⇒WRITE_4B=1,4 字节 scalar write)。(全部四个 StringAttr selector 均在 decompile @0x1350b3a0lines 762/1025/1027/1033 中 CONFIRMED。)GOTCHA —
destination_id根据 device topology 计算,不是常量:dest_id = base + (core_index op divisor),其中 divisor 由目标 core type 选择TensorCoresPerLogicalDevice或SparseCoresPerLogicalDevice,且在 megacore 路径上op是RemUI,在非 megacore 路径上是idiv折叠常量。硬编码 routing id 的重实现会在任何非默认 chip geometry 上误路由。精确代数公式从 op 顺序重建(HIGH);按 core type 的分支选择已 CONFIRMED;[target+0x3b8]+0x7c(megacore core-count gate,119*8+124)和[target+0x948]+0x90(SparseCore multiplier,297*8+144)背后的 chip-metadata 字段名尚未解析为命名的TpuChipConfigaccessor。
6. DmaGeneralStart ODS 槽位映射 {#6-dmageneralstart-ods-slot-map}
DmaGeneralStartOp::create(0x145b1b80,28-arg overload)转发到 build(0x1459b2c0),后者添加 operand group(AttrSizedOperandSegments,segment-size array 位于 op+0x80),并把 8 个 inherent attribute 写入一个 0x88 字节的 Properties(operator new(0x88),TypeID @0x224e95b0)。与更简单形式相同的 +0x40 raw↔getter delta 仍然成立。build 的 attr-pointer 写入已做 data-flow trace;6 个 typed getter 已逐字节确认。
raw Properties | op-rel getter | getter | attr 名称 | 类型 |
|---|---|---|---|---|
+0x00 | +0x40 | (仅 getInherentAttr) | dma_ordering | StringAttr |
+0x08 | +0x48 | getDstMemCoreType | dst_mem_core_type | CoreTypeAttr |
+0x10 | +0x50 | getDstOpcode | dst_opcode | StringAttr |
+0x18 | +0x58 | getDstSyncFlagCoreType | dst_sync_flag_core_type | CoreTypeAttr |
+0x20 | +0x60 | getEnableTrace | enable_trace | UnitAttr |
+0x28 | +0x68 | getSrcMemCoreType | src_mem_core_type | CoreTypeAttr |
+0x30 | +0x70 | getSrcSyncFlagCoreType | src_sync_flag_core_type | CoreTypeAttr |
+0x38 | +0x78 | (仅 getInherentAttr) | sync_mode | StringAttr |
create-arg → GeneralDma slot-field join:
- src/dst data buffer →
{src,dst}_mem_{core,mem}_id(slot+0x18 >>26/28/34/36,通过 buffer memref 的MemorySpace)。 dst_opcode"write_4b"→DstOpcode(slot+0x18 >>39 &0x3=WRITE_4B=1)。enable_traceUnitAttr→TraceEn(slot+0x18bit 41)。dst_sync_flag_core_typeCoreTypeAttr→ 提供给dest_sync_flags_vs1的 sync-flag core type。destination_idValue(topologyAddIOp)→DestinationId(slot+0x10 >>29 &0x1f)+DestinationIdValid。- source semaphore →
SourceSyncFlagNumberWAIT(slot+0x10 >>41 &0x1f)+ valid bit。 - dst semaphore base →
DestSyncFlagsVs1SET(slot+0x10 >>35 &0x1f)+ valid bit。 - stride-dim count →
NondefaultStrideDimensions(slot+0x18 >>10 &0x7);三个ArrayRef<Value>→ 逐级 stride operand。
NOTE — 两个没有 typed getter 的
StringAttr槽位,即+0x00(dma_ordering)和+0x38(sync_mode),通过排除法以及调用方的StringAttr构造顺序配对到这些名称(6 个 typed getter 精确固定了其他槽位)。槽位 offset 已按字节锁定;这两个的 name↔offset 配对是一次推断 join(HIGH)。getInherentAttrname→offset bit-trace 可以确认它。
GeneralDma opcode 是 0x8,编码在 bundle bit 0xb5=181(gfc SparseCoreDmaEncoder::Encode 0x1eb5a3a0,oneof bound cmp rax,0xa,case 10)。
相关组件
| 组件 | 关系 |
|---|---|
issueRetiledTransfer (0x13519480) | tile-grid driver;总是将多 tile 情况路由到 issueRolledTransfer |
issueContiguousTransfer (0x1350a3e0) | 单次连续传输 peer(直接发射 DmaSimpleStart/LinearStream) |
lowerEnqueueDma::$_0 (0x13516720) | 每个 tile 调用 issueStridedTransfer 的 std::function policy-func |
getTiledDynamicShape (0x13516840) | 产生 rolled loop 迭代的 bases ArrayRef<Value> |
交叉引用
- DmaParameters 选择器 — Simple-vs-Strided-vs-General selector,以及决定这些发射器产出哪种形式的前置 dim-coalescing
- Intra-Chip DMA 描述符 — descriptor 字段布局,以及发射出的 op 最终写入的
mem_id/core_id/dma_type/dst_opcode枚举 - Tile-Index 展开 —
getTiledDynamicShape和DmaParameters包(src_byte_strides/tgt_byte_strides/steps_per_stride),其size()驱动形式选择 - Host↔Device DMA — host infeed/outfeed leg;
DMA_TYPE_CHIP_TO_HOST/DMA_TYPE_LOCAL_OR_HOST路径 - OCI Command DMA-ID — 与已发射 descriptor 的 begin/end trace point 配对的
trace_id_headerDMA-id - net_router 发射器流水线 — 发射这些 intra-chip transfer 的 collective router local leg;
issueGeneralDma的GetRemoteMemBase路径对应的 cross-chip remote-endpoint encoding