Skip to content

WindowDescription 字节成本

本页上的每个偏移、值和地址都从 libtpu-0.0.40-cp314 wheel 中的 libtpu.so 逐字节读取(BuildID md5 89edbbe81c5b328a958fe628a9f2207d;build libtpu_lts_20260413_b_RC00)。该二进制文件被 strip,下面的每个符号都是解修饰后的 C++ 名称。其他版本会有差异。所有地址都是虚拟地址;对该二进制而言,.text VMA == 文件偏移(0xe63c000),.rodata VMA == 文件偏移(0x84a0000)。.data.rel.ro VMA − 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-statereduce-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 指针 +0x20ChunkBytes +0x2d8,以及 element_type +0x320
  • 两个 builder:from-LloValueMakeWindowDescription(chunk-count window)和成本模型的 MakeWindowDescription(来自 Window proto),以及它们强制执行的 rank-equality CHECK。
  • 字节原语 Size = ElementSize · align_up(Product(span), granule),以及把它除小为传输字节的 operand-shape 修正(ElementPackingFactorCompact2ndMinorRatio)。
  • 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。
Structxla::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+0x00byte"is-default"/valid flag;ctors 设为 0
name string+0x08std::string(libc++ SSO,0x18 B)debug/name string;__init_copy_ctor_external @0xfaa9de5
string-present+0x18byteflag:name string 已填充
operand pointer+0x20const 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+0x28inlined_vector<LloValue*,8>每轴 window-iteration index 变量
window sizes+0x70inlined_vector<long,6>window 的每轴 chunk count = ChunkCountsWithTmp(Shape)
layout order+0xa8inlined_vector<long,6>Shape 的 minor-to-major 物理维度顺序
second array+0xe0heap {ptr, size, cap}(long)heap-only long vector;由 copy ctor memcpy;fragment loop 将其作为第二数组迭代
strides+0xf8inlined_vector<long,6>每轴 stride;Resize(N, init=1)。数组 Size/fragment loop 在其上取乘积
base dilation+0x130inlined_vector<long,6>每轴 base/window dilation;Resize(N, init=1)
dilation+0x168inlined_vector<long,6>每轴 dilation;Resize(N, init=0)。Fragment-run gate(必须为 0)
ElementalStrideInfo+0x1a0sub-structelemental stride descriptor;由 LLO window-iteration emitter 读取,而不是 byte/cycle 路径
ESI-present+0x260byteflag:ElementalStrideInfo 已构造
padding-low+0x268inlined_vector<long,6>每轴 padding-low;Resize(N, init=0)。Fragment-run gate(必须为 0)
padding-high+0x2a0inlined_vector<long,6>每轴 padding-high / aux
ChunkBytes+0x2d8long= Target::ChunkBytes()= topology[+0x1a8]·4
index callback+0x310std::function<…>(16 B)每轴 index-computation callback(+0x310 fn-ptr,+0x318 policy)
element_type+0x320PrimitiveType(dword)= Shape::element_type()
config flags+0x324word=1, byte=0默认配置(1/0
config scalars+0x3283×long(1,0,1transfer-granularity 默认值
offset LloValues+0x350inlined_vector<LloValue*,?>offset index 变量(ComputeWindowWithIndicesOnly

NOTE — 反编译器以十进制渲染这些偏移。copy ctor 的 vmovups [rbx+0x78]/[rbx+0x88] 对(qword index this+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 路径,而不是 inline vmovups 复制。重新实现必须先读取 metadata word,绝不能假定数据位于 inline。

函数图

函数地址角色
windowing_util::WindowDescription::WindowDescription(const&)0xfaa9da0copy ctor — 字节精确布局见证者
windowing_util::MakeWindowDescription(Target, LloValue*, Shape)0x1c86c1c0from-LloValue builder(chunk-count window)
cost_model_util::…::MakeWindowDescription(CycleTable, Window, Shape)0x138456c0from-Window builder(成本模型路径)
inlined_vector<long,6>::Assign0xf913da0Storage 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 数组,并将其他每个维度数组设为默认:

c
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-LloValue builder 读取的同一个 BYTE[a4+312]):当它是 3 时,dimensions vector inline 在 Shape 内;当它是 5 时,Shape 是 boxed array,rank 来源要通过 *(Shape+8)+8 追踪。(不匹配的 tag 会因 shape.h:843CHECK(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

算法

c
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                                    // v37

ElementPackingFactor 对 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+0xf8 strides 数组上取乘积,不是+0x70 window-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 读取带宽。

算法

c
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 尾部的 vdivsdWindowCycles 将它从 RecordMemXferCyclesImpl 收到的 bytes_per_cycle(收到的 xmm0)直接作为其 bytes 参数转发给核心,所以在非 F16 路径上,核心的 div 就是 bytes_per_cycle,而 contiguous_chunk_count · ratio / div 项正是 chunks · ratio / bytes_per_cyclebytes_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.0xmmword_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

c
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% 内比较。

函数图

函数地址角色
RecordMemXferCyclesImpl0x13844e80链条 — Size → corrections → latency + bandwidth → ResourceVector
RecordInputMemXferCycles0x13845580input wrapper — R9/R10 lanes
RecordOutputMemXferCycles0x13845860output wrapper — R11/R12 lanes
GetBytesPerCycle0x1454dd00每 op bytes-per-cycle 预算
Target::ChunkGranules0x1d61a440Size granule = (ChunkCellCount·4)/vtable[+0x5c0]
ComputeDmaLevels0x1c86a9e0向 fragment early-out 供给 DMA-level vector

每维迭代成本

window 的每轴数组让成本成为按维度的成本。这里取两个不同的每轴乘积:

  • Byte volumewindowing_util::Size+0xf8 strides 数组取乘积。每个轴贡献其 stride(chunk-sweep count);更宽或 stride 更长的轴会乘大字节数,整个乘积再向上舍入到一个 transfer granule。这是随 window 空间范围缩放的项。
  • Fragment countWindowCyclesGenericTargetAgnostic 对 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 完全忽略 +0x168 dilation 和 +0x268 padding 数组;它们只通过 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:

c
// 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