mcpField
H
helion-jagged-and-autotuning

helion-jagged-and-autotuning

github.com/d-laub开发042 次安装2 周前更新
代码仓库

关于 helion-jagged-and-autotuning

此 Helion 技能为使用 Helion 编写 GPU kernel 的开发者提供 ragged 张量及自动调优配置管理指导。内容涵盖隐式掩码迭代、调优结果缓存,以及按形状和 GPU 选择配置的 AOT 启发式。

主要功能

  1. 01使用 jagged_tile 迭代变长维度
  2. 02配置自动调优缓存
  3. 03保存并复用调优配置
  4. 04通过 AOT 启发式选择配置
  5. 05跨机器共享远程缓存
  6. 06导出 Triton 代码

适用场景

  1. 01编写 ragged 张量计算 kernel
  2. 02在部署时避免重复调优
  3. 03为多种形状和 GPU 选择配置
  4. 04在团队或 CI 环境复用调优结果

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/d-laub/dlaub-togo --skill helion-jagged-and-autotuning

安装后在 Claude Code 中输入/helion-jagged-and-autotuning确认已加载。

详细介绍

Helion:不规则张量与自动调优/配置管理

概述

Helion 中有两个很有价值、但容易出错或被完全忽略的领域:

  1. 不规则/锯齿张量——hl.jagged_tile() 可迭代长度可变的内层维度,并采用隐式掩码,因此无需手动构建掩码。
  2. 自动调优与配置管理——自动调优很慢;Helion 提供了一套分层系统(磁盘缓存 → 已保存的配置 → AOT 启发式),可调优一次并复用结果,结果以 GPU 架构和输入形状为键。

最容易被忽略的功能是:AOT 启发式(helion.experimental.aot_kernel

  • python -m helion.experimental.aot_runner)可在运行时按形状选择配置,开销为零,并自动回退到兼容的计算能力。如果请求中提到“多种 GPU”“多种形状”或“不想在部署时重新调优”,应使用 AOT,而不只是缓存。

第 1 部分 — 不规则/锯齿张量

数据布局

锯齿数据采用前缀打包方式存储:一个包含所有拼接行的扁平 x_data 缓冲区,以及一个长度为 num_rows + 1 的 x_offsets 张量,其中第 i 行为 x_data[x_offsets[i] : x_offsets[i+1]]。每行长度 = ends - starts。

hl.jagged_tile(parent) — 核心 API

hl.jagged_tile(parent) 是 hl.tile() 的锯齿版本。parent 是一个来自外层 tile 上下文的 N 维每个 lane 的结束位置张量。它会降级为一个密集的 hl.tile(parent.amax()) 循环,但会自动屏蔽 tile_k.index >= parent[lane] 的索引——你可以直接编写不规则循环。

import torch
import helion
import helion.language as hl


@helion.kernel()
def jagged_sum(x_data: torch.Tensor, x_offsets: torch.Tensor) -> torch.Tensor:
    b = x_offsets.size(0) - 1
    out = torch.zeros([b], dtype=x_data.dtype, device=x_data.device)

    for tile_b in hl.tile(b):
        starts = x_offsets[tile_b]
        ends = x_offsets[tile_b.index + 1]   # note: tile_b.index + 1 for the upper offset
        lengths = ends - starts

        acc = hl.zeros([tile_b], dtype=x_data.dtype)
        for tile_k in hl.jagged_tile(lengths):       # implicit masking, no manual mask
            idx = starts[:, None] + tile_k.index[None, :]
            acc = acc + x_data[idx].sum(dim=1)
        out[tile_b] = acc
    return out
jagged_tile 的规则与注意事项
  • parent 必须是秩 ≥ 1(不能是标量);每个轴都必须来自外层 tile 上下文。常见用法是一维“每行长度”。
  • 它不能作为内核最外层循环——必须有父 tile。
  • 锯齿子 tile 必须使用其父轴进行索引:x[tile_b, tile_k] 有效,但仅使用 x[tile_k] 无效。
  • 对多级不规则迭代(例如可变行数 × 可变特征数),可嵌套使用 jagged_tile。参见 jagged_mean.py。
  • 当所有 lane 的内层边界一致时,使用普通的 hl.tile()(而非锯齿版本)。
  • 手动实现方式(有需要时):使用 for tile_k in hl.tile(lengths.amax()),并将 extra_mask=tile_k.index[None, :] < lengths[:, None] 传给 hl.load。
参考示例(Helion 仓库中的 examples/)
文件模式
jagged_sum.py基本的逐行归约
jagged_mean.py嵌套 jagged_tile(行 × 特征)
jagged_softmax.py对不规则维度进行在线/Welford 归约
jagged_layer_norm.py逐行归一化
jagged_dense_add.py不规则张量与密集张量相加,手动使用 extra_mask
jagged_dense_bmm.py、jagged_hstu_attn.py不规则矩阵乘法/注意力

第 2 部分 — 自动调优与配置管理

选择合适的机制
digraph config_mgmt {
    "Need a tuned config?" [shape=diamond];
    "One shape, one GPU?" [shape=diamond];
    "Fixed known shapes (a handful)?" [shape=diamond];
    "Many shapes / many GPUs / deploy without re-tuning?" [shape=diamond];
    "Share across machines/CI?" [shape=diamond];

    "Save config(); @kernel(config=...)" [shape=box];
    "@kernel(configs=[...]) + key=" [shape=box];
    "AOT heuristic (aot_kernel + aot_runner)" [shape=box];
    "Remote cache backend" [shape=box];
    "Default: on-disk autotune cache" [shape=box];

    "Need a tuned config?" -> "One shape, one GPU?";
    "One shape, one GPU?" -> "Save config(); @kernel(config=...)" [label="yes"];
    "One shape, one GPU?" -> "Fixed known shapes (a handful)?" [label="no"];
    "Fixed known shapes (a handful)?" -> "@kernel(configs=[...]) + key=" [label="yes"];
    "Fixed known shapes (a handful)?" -> "Many shapes / many GPUs / deploy without re-tuning?" [label="no"];
    "Many shapes / many GPUs / deploy without re-tuning?" -> "AOT heuristic (aot_kernel + aot_runner)" [label="yes"];
    "Many shapes / many GPUs / deploy without re-tuning?" -> "Share across machines/CI?" [label="no"];
    "Share across machines/CI?" -> "Remote cache backend" [label="yes"];
    "Share across machines/CI?" -> "Default: on-disk autotune cache" [label="no"];
}
调优一次、保存并重新加载

Kernel.autotune(args) 会返回最佳 helion.Config。Config 可序列化为 JSON。

config = my_kernel.autotune(example_inputs)   # returns helion.Config
config.save("configs/my_kernel.json")          # atomic write

best = helion.Config.load("configs/my_kernel.json")
@helion.kernel(config=best)        # one config, applied to ALL shapes/dtypes/devices
def my_kernel(x, y): ...

Config 的其他方法:.to_json() / .from_json(str)、.from_dict(d)、 .minimize(config_spec)(保存前删除默认值键)。

针对多个有代表性的形状进行调优,并按形状保存:

for tag, args in datasets.items():
    my_kernel.autotune(args).save(f"configs/my_kernel_{tag}.json")
磁盘上的自动调优缓存(自动按架构 + 形状作为键)

默认情况下,首次调用会自动调优,并根据硬件、特化信息(形状/数据类型/步幅)、CUDA/ROCm 运行时、后端和内核源码哈希作为键缓存结果——因此,H100 和 B200,或两种不同形状,会分别保存条目,并在后续运行时自动选用。无需编写代码。

环境变量作用
HELION_CACHE_DIR缓存根目录(默认:torch 缓存目录 /helion)
HELION_AUTOTUNE_CACHE缓存类:LocalAutotuneCache(默认)、StrictLocalAutotuneCache(还会以 Helion/PyTorch/Triton 版本作为键)、RemoteAutotuneCache、AOTAutotuneCache
HELION_FORCE_AUTOTUNE=1忽略缓存配置,重新调优,并将结果写回
HELION_SKIP_CACHE=1跳过缓存读取和写入
HELION_ASSERT_CACHE_HIT=1如果没有缓存配置则失败(CI 防护)
HELION_AUTOTUNE_EFFORTnone | quick | full(默认)——调优深度
HELION_AUTOTUNE_BUDGET_SECONDS调优的实际耗时上限
部署少量已知配置(configs= + key=)
@helion.kernel(
    configs=[helion.Config.load("small.json"), helion.Config.load("large.json")],
    key=lambda x, y: helion.next_power_of_2(x.numel()),   # re-benchmark bucket
    static_shapes=False,
)
def my_kernel(x, y): ...

Helion 会在首次遇到每个特化键时,对列出的配置进行轻量级基准测试,并选择最快的配置。key= 控制重新选择的时机(在形状特化之外)。

使用先前缓存/最佳配置作为调优起点

helion.from_cache() 会返回一个 autotuner_fn,从先前的最佳配置开始搜索,而不是从头开始:

@helion.kernel(autotuner_fn=helion.from_cache(max_configs=5))
def my_kernel(x, y): ...
AOT 启发式——跨 GPU 实现零成本的按形状选择(核心特性)

对于“部署一个无需运行时调优、可在多种 GPU 上处理多种形状的内核”:离线对内核进行代表性形状扫描,逐一调优,然后提炼出一个决策树启发式,在运行时以微秒级速度选择配置。

  1. 使用 helion.experimental.aot_kernel 而不是 helion.kernel 进行装饰:

    import helion.experimental
    
    @helion.experimental.aot_kernel()   # extras: batched=..., key=fn, collect_fn, measure_fn
    def vector_add(x, y): ...
    
  2. 在目标 GPU 上,针对能够覆盖形状扫描的基准测试运行 AOT 工作流(速度较慢——每种不同形状都要进行完整自动调优,每种约需 5–15 分钟):

    python -m helion.experimental.aot_runner -- python my_benchmark.py
    
  3. 它会在内核源码旁边生成 _helion_aot_<kernel>_<device>_sm<NN>.py(例如 _helion_aot_vector_add_cuda_sm90.py)。将其提交。

  4. 运行时查找顺序:$HELION_HEURISTIC_DIR → 与当前计算能力匹配的文件 → 较旧的兼容计算能力(例如,在 sm120 上,会依次尝试 sm120 → sm100 → sm90)。一个启发式文件可以适用于多代 GPU。未找到文件 → 使用默认配置并发出一次性警告。

要支持新 GPU:在该硬件上运行 aot_runner,并将新生成的 _helion_aot_*_sm<NN>.py 与现有文件一同提交。请参阅 pretuned_kernels/ 中的完整示例(vector_add、softmax、layer_norm、rms_norm、cross_entropy、rope),以及 docs/deployment_autotuning.md 中的完整工作流。

在多台机器之间共享配置(远程缓存)

实现 helion.autotuner.remote_cache.RemoteCacheBackend(get/put,可选 list 用于热启动),然后:

export HELION_REMOTE_CACHE_BACKEND=mypkg.cache.MyBackend   # required to load it
export HELION_AUTOTUNE_CACHE=RemoteAutotuneCache           # also write winners to remote

读穿/写穿;本地磁盘仍是事实来源,远程服务故障时会平稳降级。可用于团队、CI 和多节点热启动。

控制哪些维度进行特化(影响缓存条目)
API位置作用
static_shapes=True(默认)装饰器根据精确形状/步幅进行特化;性能最佳,每种形状对应一个条目
static_shapes=False装饰器将维度划分为 {0, 1, ≥2};一个内核可处理多种大小
hl.specialize(dim)内核内部始终将该维度设为编译时常量,适用于每次调用
torch._dynamo.mark_static(t, dims)调用前仅对指定张量的维度进行特化
key=lambda ...装饰器自定义额外分组,用于重新选择

使用 static_shapes=False 时,如果张量元素数量通常超过 2**31,请固定 index_dtype=torch.int64,以避免增加额外的特化边界。

高级:手动路由/在服务时移除 Helion
bound = my_kernel.bind(example_inputs)            # BoundKernel, tied to input types
run_small = bound.compile_config(small_cfg)        # callable for a specific config
src = bound.to_triton_code(small_cfg)              # export raw Triton source

常见错误

错误修正
为不规则循环手动构建掩码使用 hl.jagged_tile(lengths) —— 掩码会自动处理
对不规则子块使用 x[tile_k]使用父块进行索引:x[tile_b, tile_k]
将 hl.jagged_tile 用作最外层循环它需要一个外围父块
每次部署或遇到新形状时都重新调优使用 AOT 启发式(aot_kernel + aot_runner)
以为没有自动调优命令行工具有:python -m helion.experimental.aot_runner
对形状各异的场景使用 @kernel(config=...)一个配置无法适用于所有形状;请使用 configs=/key= 或 AOT
为同一 GPU/形状在每台机器上重新调优远程缓存后端可为团队预热调优结果
手动编辑 AOT 文件以适配新 GPU在该 GPU 上重新运行 aot_runner;它会生成 sm<NN> 文件

评论

支持 Markdown