WindowDescription 字节成本
本页上的每个偏移、值和地址都从
libtpu-0.0.40-cp314wheel 中的libtpu.so逐字节读取(BuildID md589edbbe81c5b328a958fe628a9f2207d;buildlibtpu_lts_20260413_b_RC00)。该二进制文件未被 strip,下面的每个符号都是解修饰后的 C++ 名称。其他版本会有差异。所有地址都是虚拟地址;对该二进制而言,.textVMA == 文件偏移(0xe63c000),.rodataVMA == 文件偏移(0x84a0000)。.data.rel.roVMA −0x200000== 文件偏移。
摘要
xla::jellyfish::windowing_util::WindowDescription 是成本模型的窗口对象:它把 convolution 或 pooling window 的每轴维度,包括 chunk 计数、stride、dilation 和 padding,打包起来,使 memory-transfer pricer 可以把窗口转换为字节数,再把字节数转换为 HBM/VMEM 带宽 cycle。重新实现者应把它理解为 XLA Window proto 在成本模型中的对应物,但被压平成一个堆对象,其中六个 absl::inlined_vector<long,6> 维度数组各自为每个逻辑轴保存一个 long。每个 TensorCore operand DMA、每个 conv kernel transfer、每个 reduce-window tile,都是通过从 operand Shape 加 op 的 Window 构造 WindowDescription,用 windowing_util::Size 计算传输字节数,再把 bytes / bytes_per_cycle(由 DMA-fragmentation ratio 修正)沉积到 ResourceVector 的带宽 lane 中来定价。
熟悉的参照框架是 LLVM 的 TargetTransformInfo::getMemoryOpCost:一次 load/store 的成本是访问大小除以单元的 bytes-per-cycle,并对 misalignment 或 scatter 施加惩罚。WindowCycles 是同一个想法,只是专用于带 stride 的多维 DMA。"access size" 是 ElementSize · align_up(Product(window_strides), ChunkGranules),再除以 element-packing factor(bf16 为 1:1;int8/PRED 更紧密打包)和 2nd-minor-tile compaction ratio。"scatter penalty" 是 DMA-fragment ratio:传输被分解为连续 descriptor run,run 计数在 {1, 2-3, 4-7, 8-31, >31} 中选择 {1.0, 1.6/1.3, 1.1, 1.05, 1.0} 中的一个 multiplier,>31 桶回退到 1.0(无惩罚),单 DMA-level 传输也是如此。启动延迟(DefaultHbmInitLatency)是 InitialDmaLatencyInNs · TC_GHz,一次性沉积到独立的 latency lane。
本页端到端记录字节成本模型:WindowDescription 字段布局(从 copy constructor 和两个 builder 逐字节恢复)、windowing_util::Size 字节原语、WindowCyclesGenericTargetAgnostic 的 fragment-aware byte→cycle 核心、WindowCycles 带宽沉积及其较新代(Target+0x398 >= 5)双方向混合,以及把 latency 和 bandwidth 路由到 resource vector 的 RecordMemXferCyclesImpl 接线。此成本路径乘入的 matmul/matpush throughput 整数(VfCycleTable CT 值)在 vf-cycletable 中框定;消耗它们的 conv-deposit 算术在 convolution-cost-state 和 reduce-window-pooling-cost 中。
对重新实现而言,契约是:
WindowDescription对象布局:六个inlined_vector<long,6>维度数组(window-sizes+0x70、strides+0xf8、base-dilation+0x130、dilation+0x168、padding-low+0x268、padding-high+0x2a0)、operand-shape 指针+0x20、ChunkBytes+0x2d8,以及element_type+0x320。- 两个 builder:from-
LloValue的MakeWindowDescription(chunk-count window)和成本模型的MakeWindowDescription(来自Windowproto),以及它们强制执行的 rank-equality CHECK。 - 字节原语
Size = ElementSize · align_up(Product(span), granule),以及把它除小为传输字节的 operand-shape 修正(ElementPackingFactor、Compact2ndMinorRatio)。 - byte→cycle 核心:DMA-fragment count(连续 stride run 上的乘积)、其
{1.0, 1.6, 1.3, 1.1, 1.05}ratio 表、F16-class element divisor,以及transferred_bytes / bytes_per_cycle。 RecordMemXferCyclesImpl路由:DefaultHbmInitLatency→ R9/R11 latency lane,WindowCycles→ R10/R12 bandwidth lane。
| Struct | xla::jellyfish::windowing_util::WindowDescription — 堆对象,字段延伸到 +0x350 |
| Copy ctor(布局来源) | WindowDescription::WindowDescription(const&) @0xfaa9da0 |
| Builder(来自 LloValue) | windowing_util::MakeWindowDescription(Target, LloValue*, Shape) @0x1c86c1c0 |
| Builder(来自 Window) | cost_model_util::…::MakeWindowDescription(CycleTable, Window, Shape) @0x138456c0 |
| 字节原语 | windowing_util::Size(Span<long>, MemUnit, long granule) @0x1c86f320 |
| Byte→cycle 核心 | fusion_util::WindowCyclesGenericTargetAgnostic @0x14552180 |
| 带宽沉积 | fusion_util::WindowCycles @0x14552660 |
| 启动延迟 | fusion_util::DefaultHbmInitLatency @0x14552ca0 |
| 消费者 | cost_model_util::…::RecordMemXferCyclesImpl @0x13844e80 |
| 维度数组元素类型 | absl::inlined_vector<long,6>(inline ≤6 longs,否则 heap) |
WindowDescription 对象
目的
WindowDescription 是成本模型对一次窗口化访问的冻结快照:它包含足够的 operand Shape 和 op 的 Window 信息,用于计算传输字节数并统计传输会碎片化成多少 DMA descriptor。它为每个被定价 operand 新建,由 byte/cycle 路径读取一次,然后丢弃。它不同于 SparseCore 的 windowing_util::WindowDescription(位于 0x132113xx 附近的另一个更小类型);这里只记录成本模型类型,即 copy ctor @0xfaa9da0 构造的那个。
结构
下面的布局从三个必须一致的独立来源逐字节恢复:copy ctor @0xfaa9da0(以固定偏移 vmovups/InitFrom 复制每个字段)、from-LloValue builder @0x1c86c1c0(写入每个字段),以及 byte/cycle reader @0x14552180/@0x13844e80(读取它们)。每个维度数组都是 absl::inlined_vector<long,6>:第一个 qword 是 metadata word(bit 0 = is-heap-allocated;否则为 size << 1);如果 inline,最多六个 long 从 +0x8 开始;如果 heap,+0x8 是数据指针,+0x10 是 capacity。该 Storage 布局由 inlined_vector<long,6>::Assign @0xf913da0 确认。
| 字段 | Offset | 类型 | 含义(确认来源) |
|---|---|---|---|
| valid flag | +0x00 | byte | "is-default"/valid flag;ctors 设为 0 |
| name string | +0x08 | std::string(libc++ SSO,0x18 B) | debug/name string;__init_copy_ctor_external @0xfaa9de5 |
| string-present | +0x18 | byte | flag:name string 已填充 |
| operand pointer | +0x20 | const void*(LloValue* / element-type-bearing ptr) | 从 builder 的 LloValue* 参数写入(成本模型路径上为 null);byte/cycle reader 将其视为带 element type 的指针,按 WORD[ptr+0xb] >> 2 & 0x1f 读取类型(并用 WORD[ptr+0xb] & 0x7c == 0x10 控制 F16 divisor) |
| index LloValues | +0x28 | inlined_vector<LloValue*,8> | 每轴 window-iteration index 变量 |
| window sizes | +0x70 | inlined_vector<long,6> | window 的每轴 chunk count = ChunkCountsWithTmp(Shape) |
| layout order | +0xa8 | inlined_vector<long,6> | Shape 的 minor-to-major 物理维度顺序 |
| second array | +0xe0 | heap {ptr, size, cap}(long) | heap-only long vector;由 copy ctor memcpy;fragment loop 将其作为第二数组迭代 |
| strides | +0xf8 | inlined_vector<long,6> | 每轴 stride;Resize(N, init=1)。数组 Size/fragment loop 在其上取乘积 |
| base dilation | +0x130 | inlined_vector<long,6> | 每轴 base/window dilation;Resize(N, init=1) |
| dilation | +0x168 | inlined_vector<long,6> | 每轴 dilation;Resize(N, init=0)。Fragment-run gate(必须为 0) |
| ElementalStrideInfo | +0x1a0 | sub-struct | elemental stride descriptor;由 LLO window-iteration emitter 读取,而不是 byte/cycle 路径 |
| ESI-present | +0x260 | byte | flag:ElementalStrideInfo 已构造 |
| padding-low | +0x268 | inlined_vector<long,6> | 每轴 padding-low;Resize(N, init=0)。Fragment-run gate(必须为 0) |
| padding-high | +0x2a0 | inlined_vector<long,6> | 每轴 padding-high / aux |
| ChunkBytes | +0x2d8 | long | = Target::ChunkBytes()(= topology[+0x1a8]·4) |
| index callback | +0x310 | std::function<…>(16 B) | 每轴 index-computation callback(+0x310 fn-ptr,+0x318 policy) |
| element_type | +0x320 | PrimitiveType(dword) | = Shape::element_type() |
| config flags | +0x324 | word=1, byte=0 | 默认配置(1/0) |
| config scalars | +0x328 | 3×long(1,0,1) | transfer-granularity 默认值 |
| offset LloValues | +0x350 | inlined_vector<LloValue*,?> | offset index 变量(ComputeWindowWithIndicesOnly) |
NOTE — 反编译器以十进制渲染这些偏移。copy ctor 的
vmovups [rbx+0x78]/[rbx+0x88]对(qword indexthis+14)是 metadata-word+0x70处 window-sizes vector 的inline payload;同样,+0x100/+0x110是 metadata+0xf8后面的 strides payload。当 metadata word 的 bit 0 被置位时,数组已溢出到 heap,ctor 会走Storage<long,6>::InitFrom路径,而不是 inlinevmovups复制。重新实现必须先读取 metadata word,绝不能假定数据位于 inline。
函数图
| 函数 | 地址 | 角色 |
|---|---|---|
windowing_util::WindowDescription::WindowDescription(const&) | 0xfaa9da0 | copy ctor — 字节精确布局见证者 |
windowing_util::MakeWindowDescription(Target, LloValue*, Shape) | 0x1c86c1c0 | from-LloValue builder(chunk-count window) |
cost_model_util::…::MakeWindowDescription(CycleTable, Window, Shape) | 0x138456c0 | from-Window builder(成本模型路径) |
inlined_vector<long,6>::Assign | 0xf913da0 | Storage metadata 布局见证者 |
Target::ChunkBytes | — | +0x2d8 来源(topology[+0x1a8]·4) |
Target::ChunkCountsWithTmp | — | +0x70 window-sizes 来源 |
两个 Builder
目的
WindowDescription 在定价时从不默认构造;它由两个 MakeWindowDescription 重载之一构建,取决于调用方是否有 LloValue(LLO lowering 路径)或 Window proto(成本模型路径)。二者产生相同布局;区别只在每轴维度数组的来源。
来自 LloValue — @0x1c86c1c0
from-LloValue builder 用 chunk count 填充 window-sizes 数组,并将其他每个维度数组设为默认:
function MakeWindowDescription(Target, LloValue* shape_val, Shape): // @0x1c86c1c0
wd.window_sizes = Target.ChunkCountsWithTmp(Shape) // +0x70 = per-axis chunk count
wd.strides = Resize(rank, init = 1) // +0xf8 = all-ones
wd.base_dilation = Resize(rank, init = 1) // +0x130 = all-ones
wd.dilation = Resize(rank, init = 0) // +0x168 = all-zero
wd.padding_low = Resize(rank, init = 0) // +0x268 = all-zero
wd.layout_order = Assign(Shape.layout.minor_to_major) // +0xa8
wd.element_type = Shape.element_type() // +0x320 (DWORD this+200)
wd.chunk_bytes = Target.ChunkBytes() // +0x2d8 (qword this+91)
```text
全一 strides 与全零 dilation/padding 是“稠密、未碎片化”的默认值:以这种方式构建的 window 正好产生一个 DMA descriptor(fragment count 1,ratio 1.0),除非后续 pass 覆写 strides。
### 来自 Window proto — `@0x138456c0`
成本模型 builder 首先委托 from-`LloValue` builder 写入默认值,然后用 `Window` 的 dimension arrays 覆写 window-sizes 和 strides 数组:
```c
function MakeWindowDescription(CycleTable, Window, Shape): // @0x138456c0
MakeWindowDescription(Target, /*shape_val=*/null, Shape) // @0x1c86c1c0 — defaults
CHECK(window.base_bounds.size() == shape.dimensions().size()) // cost_model_util.cc:287
CHECK(window.window_bounds.size() == shape.dimensions().size()) // cost_model_util.cc:288
tag = BYTE[Shape + 0x138] // shape kind ∈ {3=array, 5=tuple/boxed}
dim_vec = (tag == 3) ? &Shape.dimensions : *(Shape + 8) + 8 // shape rank source
rank = dim_vec.size // shape.dimensions().size()
wd.window_sizes = Assign(window.window_bounds, rank) // +0x70 (Storage this+14)
wd.strides = Assign(window.base_bounds, rank) // +0xf8 (Storage this+31)GOTCHA — 两个
Assign和两个 CHECK 使用的 rank 都从Shape读取,并由Shape+0x138处的 shape-kind tag byte 门控(即 from-LloValuebuilder 读取的同一个BYTE[a4+312]):当它是3时,dimensions vector inline 在Shape内;当它是5时,Shape是 boxed array,rank 来源要通过*(Shape+8)+8追踪。(不匹配的 tag 会因shape.h:843的CHECK(buffer != nullptr)abort。)硬编码 inline read 的重新实现会在 boxed shape 上解引用垃圾。两个 CHECK 字符串("window.base_bounds.size() == shape.dimensions().size()"、"window.window_bounds.size() == shape.dimensions().size()")是 rank-equality guard,也就是 window 必须对每个 shape dimension 正好有一个 entry。
字节原语 — windowing_util::Size
目的
Size 是最低层:它把每轴计数 span(window strides)转换为字节数,并向上舍入到传输 granule。它是成本模型的 align_up(elements, granule) · element_size。
算法
function Size(span, MemUnit memunit, long granule): // @0x1c86f320
cnt = xla::Product(span) // @0x20cf5200 — Π span[i]; empty span → 1
rounded = ceil_div(cnt, granule) // q = cnt/granule; if cnt > q·granule: q += 1
bytes = ElementSizeBytes(memunit) · granule · rounded
= ElementSizeBytes · align_up(cnt, granule)
return MemUnit{ byte_count = bytes, mem_tag = memunit }
```text
`Product` 作用于每轴 stride 数组(不是 window-size 数组):每个 stride entry 是该轴扫过的 granule-sized chunk 数,它们的乘积是传输的总 chunk 数。ceil-div-then-multiply 就是 `align_up`:未填满整数个 granule 的传输按下一个完整 granule 计费。element-size 因子(`granule · rounded` 以 granule 为单位;乘以 `ElementSizeBytes`)转换为字节。结果是一个 `{byte_count, mem_tag}` 对,使调用方知道字节属于哪个 memory space(HBM vs VMEM)。
granule 来自 `Target::ChunkGranules` `@0x1d61a440` = `(topology.ChunkCellCount · 4) / vtable[+0x5c0]()`,即 chunk-cell count 缩放到字节后,再除以每代的 element-per-chunk factor。
---
## 从字节到传输字节 — operand 修正
`Size` 给出原始 window 字节数;随后 `RecordMemXferCyclesImpl` `@0x13844e80` 在交给 cycle 核心之前,会将其除以两个 operand-shape 修正:
```c
function bytes_to_transfer_bytes(raw_bytes, Target, shape): // inside @0x13844e80
granule = Target.ChunkGranules(MemUnit) // @0x1d61a440
raw_bytes = windowing_util::Size(wd.strides, MemUnit, granule) // r14 = byte_count
pred_as_1bit = TransferSizeUtil.ShouldPackPREDAsSingleBit(shape)
packing_factor = TransferSizeUtil.ElementPackingFactor(elem_type, pred_as_1bit) // bf16=1
compaction = Target.Compact2ndMinorRatio(shape) // 2nd-minor tiling
divisor = compaction · packing_factor // v99
transfer_bytes = raw_bytes / divisor // v37ElementPackingFactor 对 bf16/fp32 为 1(每个逻辑元素对应一个 byte-stream 元素),对 sub-byte packed format(int8/int4,以及 ShouldPackPREDAsSingleBit 成立时按 single bits 打包的 PRED)更大。Compact2ndMinorRatio 计入布局应用的 2nd-minor-dimension tiling compaction。除以二者得到实际跨总线的字节数。
QUIRK — 成本模型按 strides 而不是 window sizes 对字节数计费。
Size在+0xf8strides 数组上取乘积,不是在+0x70window-sizes 数组上取乘积。window-sizes 数组驱动下面的 fragment count 和 LLO iteration;strides 数组驱动 byte volume。对 window-sizes 取乘积的重新实现会在任何非 unit-stride window 上得到错误字节数。
Byte→Cycle 核心 — WindowCyclesGenericTargetAgnostic
目的
此函数把字节数转换为经过 fragment 修正的 cycle 值:它统计 window 会分解成多少个连续 DMA descriptor run,为该 fragment count 查找 efficiency multiplier,应用 element-type divisor,并返回 chunk_count · ratio / divisor + residual。它是 "target-agnostic",因为它把字节数作为 double 参数接收,而不是从 Target 读取带宽。
算法
function WindowCyclesGenericTargetAgnostic(MemUnit, wd, count_descriptors, bytes): // @0x14552180
// 1. Element-type divisor. The element-type-bearing pointer is at wd+0x20.
elem_ptr = *(wd + 0x20)
if elem_ptr != null and (WORD[elem_ptr + 0xb] & 0x7c) == 0x10: // F16-class element type
div = 2003.0 // @0xa2df1b0 — fixed F16 unpack penalty
else:
div = (bytes != 0) ? bytes : 1.0 // @0xa2df230 — default, no extra penalty
ComputeDmaLevels(&dma_levels, wd) // @0x1c86a9e0 — populates the descriptor levels
// 2. DMA-fragment count: product over contiguous stride runs.
frag = 1
for i in reverse(rank): // loop @0x14552250
if wd.strides[i] == wd.window_sizes[i] // stride matches window span
and wd.dilation[i] == 0 // wd+0x168
and wd.padding_low[i] == 0: // wd+0x268
frag *= wd.strides[i] // run continues — contiguous
else:
frag *= wd.strides[i]; break // run broken — descriptor boundary
// 3. Fragment-efficiency ratio by descriptor count.
if dma_levels <= 1: ratio = 1.0 // single descriptor, no penalty
elif frag > 31: ratio = 1.0 // (very large — falls through)
elif frag <= 3: ratio = table[frag >= 2] // @0xa2d71a0 = {1.6, 1.3}
elif frag <= 7: ratio = 1.1 // @0xa2df4c8
else: ratio = 1.05 // @0xa2df2a8 (frag in 8..31)
// 4. Return (NOT yet divided by bytes_per_cycle — that is WindowCycles' job).
// div is `bytes` itself on the non-F16 path, so the ratio term is normalised by
// the byte count; the trailing addend is a carried-in count term (UNVERIFIED label).
return contiguous_chunk_count · ratio / div + addend
```text
fragment count 是成本模型的 scatter 估计:stride 等于 span、无 dilation、无 padding 的 window 是一个连续 run(`frag` 小,ratio 接近 1.0);跨过间隙、dilate 或 pad 的 window 会碎片化为许多 descriptor,ratio 随之升高。此函数内的 VLOG-6 trace 命名了这些中间值:`"stride_levels.size"`、`"contiguous_chunk_count"`、`"access multiplier"`,确认 `frag` 是 contiguous-chunk count,`ratio` 是 access multiplier。
> **GOTCHA —** fragment ratio 由两个近义但*不同*的计数键控。早退条件 `dma_levels <= 1`(来自 `ComputeDmaLevels`)返回 ratio 1.0;表查找由 `frag`(contiguous-chunk product)键控。它们通常一致,但只有在有多个 DMA level 时才会查询表。把 `{1.6, 1.3, 1.1, 1.05}` 表挂到错误计数器上的重新实现会错误定价每个 multi-level transfer。`@0xa2d71a0` 处的 `{1.6, 1.3}` 对由 `frag >= 2` 选择(以该 boolean 索引的 2 元素表),所以 2-fragment transfer 取 `1.3`,而 1-fragment-but-multi-level transfer 取 `1.6`。
### element-type divisor
F16-class 测试会 mask `wd+0x20` 指针的 element-type bits(`WORD[ptr+0xb] & 0x7c == 0x10`),命中时把 divisor 替换为固定 `2003.0`(`@0xa2df1b0`),而不是传入的 `bytes_per_cycle`。在其他每种(非 F16)类型上,divisor 保持调用方传入的 `bytes_per_cycle` 参数(`vblendvpd` 在非零时保留 `xmm0`,仅当 `bytes_per_cycle == 0` 时回退到 `@0xa2df230` 的 `1.0`)。所以 F16 是唯一一种 access cost *不*按 per-cycle byte budget 归一化,而按常数 `2003.0` 归一化的 element type。未遍历 F16-class 单元格之外的完整 `PrimitiveType → divisor` 枚举;只有 `2003.0` 情形被锚定(LOW confidence:没有其他类型走特殊单元格)。
---
## 带宽沉积 — WindowCycles
### 目的
`WindowCycles` 是公共入口:它加入启动延迟,调用 target-agnostic 核心,除以 `bytes_per_cycle`,并在 `Target` generation 字段 `+0x398` 为 `>= 5` 时,按 TC 频率混合两个传输方向(HBM 与 VMEM)。其返回值是沉积到 R10/R12 lane 的 bandwidth-cycle 值。
### 算法
```c
function WindowCycles(MemUnit, wd, Target, bytes_per_cycle, a, b, c): // @0x14552660
if MemUnit == Target.MemUnitFromKiB(0): return 0.0 // sentinel — no transfer
// startup-latency term
if c (7th arg) != -1:
init = Target.TensorCoreFrequencyInMegaHertz() / 1000.0 · c // freq-scaled startup
else:
init = DefaultHbmInitLatency(MemUnit, wd, Target) // @0x14552ca0
count_desc = (Target.vtable[+0x590]() == 1) // per-gen count-descriptors predicate
base = WindowCyclesGenericTargetAgnostic(MemUnit, wd, count_desc, bytes_per_cycle) + init
if Target[+0x398] >= 5: // v5p+ (a4[230] >= 5)
// two-direction TC-freq blend: read the {HBM,VMEM} TC-freq pair, divide it by
// 1000.0 (the xmmword pair @0xa2ce650), multiply the per-direction byte rates by it,
// take the per-direction MAX, then add the bandwidth + init terms
return max(dir0_cycles, dir1_cycles) + (hbm_term + init)
else: // < v5p
return max(dir0_cycles, dir1_cycles) + (hbm_term + init) // simple add path核心除法是 WindowCyclesGenericTargetAgnostic 尾部的 vdivsd:WindowCycles 将它从 RecordMemXferCyclesImpl 收到的 bytes_per_cycle(收到的 xmm0)直接作为其 bytes 参数转发给核心,所以在非 F16 路径上,核心的 div 就是 bytes_per_cycle,而 contiguous_chunk_count · ratio / div 项正是 chunks · ratio / bytes_per_cycle。bytes_per_cycle 本身由 GetBytesPerCycle 计算,即 HbmFullChipBytesPerSecond / (TC_freq_MHz · 1e6) / CoresPerChip,也就是每核、每 cycle 的字节预算,并且已与 memory-bandwidth-latency-model 中的相同几何交叉核对。
在较新代路径(Target+0x398 >= 5)上,此函数为两个传输方向定价并取较慢者:它把 byte volume 分成 {HBM, VMEM} 百分比对,读取 {HBM, VMEM} TC-frequency 对并将其除以 1000.0(xmmword_A2CE650 MHz→GHz 对),用该频率乘以每方向 byte rate,再取 per-direction 最大值。VLOG-6 trace 明确命名这些方向:"hbm_percentage"、"vmem_percentage"、"hbm_bytes"、"vmem_bytes"、"hbmbw"、"vmembw"、"hbmlatency"、"vmemlatency"、"next_gen",确认较新代模型是双 lane(HBM vs VMEM)max,而不是单次传输。
启动延迟 — DefaultHbmInitLatency
function DefaultHbmInitLatency(MemUnit, wd, Target): // @0x14552ca0
elem = wd.element_type via wd+0x20 // reads operand shape element type
init_ns = Target.vtable[+0x20].InitialDmaLatencyInNs(MemUnit, window_bytes)
return init_ns · (TC_freq_MHz / 1000.0) // ns · GHz = cycles
```text
这是启动一次 DMA 的固定成本,无论大小如何,每次传输支付一次。`InitialDmaLatencyInNs · TC_GHz` 将纳秒转换为 cycle:`InitialDmaLatencyInNs` 虚调用返回每代、每 element type、每 byte count 的纳秒数,`TC_freq_MHz / 1000.0` 是二进制应用的 GHz 转换(`@0xa2e0430 = 1000.0`)。具体 latency 完全取决于目标代的 `InitialDmaLatencyInNs` 表,此处没有遍历。
---
## 消费者 — RecordMemXferCyclesImpl
### 目的
`RecordMemXferCyclesImpl` `@0x13844e80` 是整条链运行的位置,也是两个 cycle 值落入 [`ResourceVector`](resource-enum.md) 的位置。它对每个被定价 operand(input 或 output)调用一次;latency 进入一个 resource lane,bandwidth 进入另一个。
### 算法
```c
function RecordMemXferCyclesImpl(label, latency_res, bw_res, hlo, wd, CycleTable, rv, do_check, fn):
if not is_priced_memory_space(wd.shape): return // element-type gate (bittest)
granule = Target.ChunkGranules(MemUnit) // @0x1d61a440
raw_bytes = windowing_util::Size(wd.strides, MemUnit, granule)
transfer_bytes = raw_bytes / (Compact2ndMinorRatio · ElementPackingFactor)
if rv[latency_res] == 0.0: // latency paid once per lane
latency = DefaultHbmInitLatency(transfer_bytes, MemUnit, wd, Target)
rv.Acc(latency_res, latency) // R9 (input) / R11 (output)
bpc = GetBytesPerCycle(hlo, Target) // @0x1454dd00
bandwidth = WindowCycles(transfer_bytes, MemUnit, wd, Target, bpc, 0, 0, -1)
rv.Acc(bw_res, bandwidth) // R10 (input) / R12 (output)latency 沉积由 rv[latency_res] == 0.0 保护:DMA startup 只在某个 lane 第一次被触及时计费,因此同一 lane 中的一系列 transfer 支付一次 startup,而不是 N 次。bandwidth 沉积始终累加。RecordInputMemXferCycles @0x13845580 传入 (R9, R10);RecordOutputMemXferCycles @0x13845860 传入 (R11, R12),即 memory-bandwidth-latency-model 的 input/output 切分。VLOG-1 trace 命名了这些沉积:"window_size.bytes"(原始 Size 输出)、"packing_factor"(divisor)、"window_xfer_size.bytes"(传输字节)、"window_xfer_latency_cycles"(R9/R11)、"window_xfer_bandwidth_cycles"(R10/R12)。还有一个一致性自检("…are inconsistent. estimated_cycles_for_bytes:… error threshold: 0.01"),将沉积的 cycles 与 bytes·rate 在 1% 内比较。
函数图
| 函数 | 地址 | 角色 |
|---|---|---|
RecordMemXferCyclesImpl | 0x13844e80 | 链条 — Size → corrections → latency + bandwidth → ResourceVector |
RecordInputMemXferCycles | 0x13845580 | input wrapper — R9/R10 lanes |
RecordOutputMemXferCycles | 0x13845860 | output wrapper — R11/R12 lanes |
GetBytesPerCycle | 0x1454dd00 | 每 op bytes-per-cycle 预算 |
Target::ChunkGranules | 0x1d61a440 | Size granule = (ChunkCellCount·4)/vtable[+0x5c0] |
ComputeDmaLevels | 0x1c86a9e0 | 向 fragment early-out 供给 DMA-level vector |
每维迭代成本
window 的每轴数组让成本成为按维度的成本。这里取两个不同的每轴乘积:
- Byte volume —
windowing_util::Size对+0xf8strides 数组取乘积。每个轴贡献其 stride(chunk-sweep count);更宽或 stride 更长的轴会乘大字节数,整个乘积再向上舍入到一个 transfer granule。这是随 window 空间范围缩放的项。 - Fragment count —
WindowCyclesGenericTargetAgnostic对 strides 数组的一个前缀取乘积:它从 minor 到 major 遍历轴,只要每个轴连续(stride == window_size && dilation == 0 && padding_low == 0)就继续相乘,并在第一个 dilated、padded 或 strided past its span 的轴处停止。停止点是 DMA descriptor boundary;得到的乘积就是键控 efficiency ratio 的 fragment count。
重新实现者应把一个 window 轴的成本理解为:它总是乘入 byte volume(因此 transfer 更大),并且只在第一个非连续轴之前乘入 fragment count(因此 dilated/padded window 会碎片化并损失带宽效率)。一个没有 dilation/padding 的稠密 3×3 stride-1 conv window 保持为一个连续 fragment(ratio 1.0);同一个 window 若 dilation=2,则会在 dilated 轴处打断 run,提高 fragment count 和 ratio。这是成本模型对“分散 DMA 比连续 DMA 更慢”的编码。
QUIRK — dilation 和 padding 在此模型中从不改变 byte volume,因为
Size完全忽略+0x168dilation 和+0x268padding 数组;它们只通过 fragment-count gate 进入。按 dilation factor 缩放 byte count 的重新实现会重复计数:dilation 消耗的是带宽效率(更高 ratio),不是带宽体积。
它如何馈入 Conv 和 Reduce-Window
字节成本路径为 windowed op 的 operand DMA 半边定价。计算半边,即 convolution 的 matmul/matpush pushes 或 pooling op 的 XLU reductions,会单独定价并加入同一个 ResourceVector。桥梁是 VfCycleTable 吞吐量整数 thru(CT),也就是 matmul 或 matpush push 的每格式 MXU reservation cycle:
// RecordConvKernelCycles (see convolution-cost-state) deposits, per the conv kernel:
R[0] Matpush = matpush_count · thru(CT_matpush) // bf16 = 2, int8 = 8 (VF array[0])
R[1] Matmul = op_count · thru(CT0) · 0.5 / derate // bf16 = 8, int8 = 32 (VF array[15])
R[2] Xlu = thru(CT 0x1c) · chunks_per_tile · rem // VfPerf(0x11f, res 0xe) + 1
// and the operand window DMA, priced here:
R[9/10] input MemXfer = DefaultHbmInitLatency + WindowCycles(input window)
R[11/12] output MemXfer = DefaultHbmInitLatency + WindowCycles(output window)
```text
最终 op 成本是这些 lane 上的 bundle-MAX:带宽受限的 conv 由 R10/R12(本页定价的 `WindowCycles` 沉积)门控;MXU 受限的 conv 由 R0/R1([`vf-cycletable`](vf-cycletable.md) 的 matmul/matpush 吞吐量)门控。Reduce-window/pooling 复用相同的 operand-window 定价,并以 XLU reduction CT(`0x16`,`0x1b`)替代 matmul 沉积,见 [`reduce-window-pooling-cost`](reduce-window-pooling-cost.md)。这就是两个页面都桥接到同一个 `WindowDescription` 的原因:它是 conv DMA 与 pooling tile transfer 共享的单一字节模型。
---
## 相关组件
| 名称 | 关系 |
|---|---|
| `vf-cycletable` | conv compute 沉积乘入的 `thru(CT)` matmul/matpush 吞吐量整数 |
| `convolution-cost-state` | `RecordConvKernelCycles` — 为 operand DMA + conv compute 消耗此字节模型 |
| `reduce-window-pooling-cost` | 为 pooling tiles 复用此 operand-window 定价 |
| `memory-bandwidth-latency-model` | R9/R10/R11/R12 lane 路由和 `bytes_per_cycle` 几何 |
| `mxu-latency-overview` | `thru(CT)` 整数索引的 `MxuLatencyTable` reservation 数组 |
| `resource-enum` | `WindowCycles`/`DefaultHbmInitLatency` 沉积进入的 `ResourceVector` lane |
---
## 交叉引用
- [VfCycleTable](vf-cycletable.md) — 32 项 CT 吞吐量表,其 matmul/matpush 整数为 conv compute 半边定价
- [ConvolutionCostState](convolution-cost-state.md) — `RecordConvKernelCycles`:conv R0/R1/R2 沉积加上此 operand window DMA
- [Reduce-Window / Pooling Cost](reduce-window-pooling-cost.md) — pooling 用 XLU reduction CT 复用此 window 字节模型
- [Memory Bandwidth / Latency Model](memory-bandwidth-latency-model.md) — R9/R10/R11/R12 lane 切分和 `bytes_per_cycle = HBM B/s / (TC_MHz·1e6) / cores`
- [Local DMA Bandwidth](local-dma-bandwidth.md) — 同一个 MemXfer pricer 的 VMEM/local transfer 侧
- [MXU Latency Overview](mxu-latency-overview.md) — `VfCycleTable` CT 读取的 `MxuLatencyTable::GetResourceUsage` 数组
- [Resource Enum (23-slot)](resource-enum.md) — latency 和 bandwidth 沉积累加进入的 `ResourceVector`
- [CycleTable Family](cycletable-family.md) — 拥有 `VfCycleTable`/`GlcCycleTable`/`GfcCycleTable` 的每代成本模型类家族
- [MXU Slot](../isa/slot-mxu.md) — conv compute 沉积定价的 LLO matmul/matpush ops
- [Matprep / IAR / Latch](../isa/slot-matprep-iar-latch.md) — matpush throughput CT 背后的 latch ops