☰
深度解析 Halide Anderson2021 自动调度器的留一法训练权重
2026/10/8 1:32:27 网站建设 项目流程

导读

本文以 src/autoschedulers/anderson2021/weights/README.md 为核心,系统讲解 Halide 中基于机器学习的 Anderson2021 自动调度器(autoscheduler)所使用的预训练权重文件:它们如何以"留一法(hold-one-out)"方式训练、为何按不同应用分别存储、.weights二进制文件的内在格式与版本校验机制,以及在 V100 之外的其他 GPU 上使用这些权重时需要留意的前提条件。读完本文,你将能够正确理解、加载、转换并评估这套预训练模型在你自己 Halide 管道上的适用边界。

一、背景:Anderson2021 自动调度器与成本模型

Halide 是一门面向快速、可移植的数据并行计算的语言。用户用高层算法描述图像处理/计算管道(pipeline),再由编译器自动推导出调度(schedule)。在 Halide 的自动调度器家族中,anderson2021(位于 src/autoschedulers/anderson2021)采用基于机器学习的成本模型来指导粗到细的 beam search:它先对候选循环嵌套(loop nest)提取特征,再由神经网络成本模型打分,从而在大规模搜索空间中挑选最优调度。

这套机制的核心文件包括:

  • AutoSchedule.cpp:自动调度器主体,实现optimal_schedule/optimal_schedule_pass的 beam search 主循环;
  • CostModel.h 与 DefaultCostModel.cpp:成本模型接口与默认实现;
  • Featurization.h:定义管道特征(PipelineFeatures)与调度特征(ScheduleFeatures);
  • Weights.h / Weights.cpp:神经网络权重结构的定义、读写与文件格式;
  • weights 目录:本文主角,存放训练好的模型权重文件。

成本模型本质上是一个小型神经网络,其参数正是weights目录下那些.weights文件。因此,权重文件的质量与适用范围直接决定了自动调度器给出的调度是否高效。

二、留一法(Hold-One-Out)训练策略

README 明确说明了权重目录的训练方式:

The weights in this directory were trained in a hold-one-out fashion. Each app was trained on a set of random pipelines and all the other apps, but not itself.

也就是说,这里存放的不是一份通用权重,而是按应用(app)分别训练的多份权重。每一份权重都遵循"留一法"交叉验证原则:

  • 训练某个应用的权重时,训练集由两部分组成:随机生成的管道(random pipelines)与除该应用之外的所有其他应用;
  • 该应用自身绝不参与训练,从而保证后续在该应用上评测时,模型面对的是完全未见过的数据。

README 给出的例子最直观:

For example, bilateral_grid.weights was trained on the random pipelines and all apps except bilateral grid.

即bilateral_grid.weights的训练集 = 随机管道 + 除 bilateral grid 以外的全部应用,bilateral grid 自身被排除在训练集之外。

这一策略的设计意图可以从源码层面得到印证。在 AutoSchedule.cpp 中实现了random_dropout函数,用于在 beam search 中随机丢弃状态——它正是为"随机探索搜索树、生成训练数据"服务的(源码注释:"Used for randomly exploring the search tree for autotuning and to generate training data.")。结合 generate_data.sh 与 retrain_cost_model.cpp 可见,训练数据正是由随机管道与既有应用管道共同构成:随机管道保证特征的广度,其他应用保证特征的多样性,留一法保证评测的公正性——这正是一套标准的"跨应用泛化 + 独立验证"评估框架。

权重目录中都有哪些应用?

从仓库实际内容看,weights 目录 中存放了与 Halide 官方应用测试集一一对应的权重文件:

权重文件对应应用
bgu.weightsBGU(background subtraction 类管道)
bilateral_grid.weightsbilateral grid 双边网格
camera_pipe.weights相机图像处理管线
conv_layer.weights卷积层
cuda_mat_mul.weightsCUDA 矩阵乘法
depthwise_separable_conv.weights深度可分离卷积
gpu.weightsGPU 综合管道
harris.weightsHarris 角点检测
hist.weights直方图
iir_blur.weightsIIR 递归模糊
interpolate.weights插值
lens_blur.weights镜头散景模糊
local_laplacian.weights局部拉普拉斯
max_filter.weights最大值滤波
nl_means.weights非局部均值去噪
stencil_chain.weights模板(stencil)链
unsharp.weights反锐化掩膜

此外,目录外还共存一份baseline.weights(位于 src/autoschedulers/anderson2021/baseline.weights),用于对照实验。当你的管道与某个应用高度相似时,可直接选用对应的权重;当需要更通用的场景时,baseline.weights或随机管道训练的通用权重是起点。

三、.weights文件的二进制格式

要理解这些权重文件如何被自动调度器消费,需要阅读 Weights.cpp 中的格式注释。源码在文件开头明确给出了.weights文件的磁盘布局:

uint32 signature 始终为 0x68776631 ('hwf1') uint32 PipelineFeatures::version uint32 ScheduleFeatures::version uint32 buffer-count uint32 dimension-count uint32 x (dimension-count) 各维度长度 extent float32 x (element-count) 按行优先存储的权重数据 (所有数值均为小端序 little-endian)

对应地,Weights.h 中的Weights结构体包含六个权重缓冲(buffer),它们恰好对应成本模型神经网络的六组参数:

成员含义维度来源
head1_filter/head1_bias网络第一个分支头(head1)的卷积权重与偏置head1_channels、head1_w、head1_h
head2_filter/head2_bias网络第二个分支头(head2)的卷积权重与偏置head2_channels、head2_w
conv1_filter/conv1_bias汇合后的主干第一层卷积权重与偏置conv1_channels、head1_channels + head2_channels

各维度的具体数值定义在 NetworkSize.h 中,加载时 Weights.cpp 会对每一段数据进行严格校验:

  1. 前 4 字节必须是签名0x68776631,即 ASCII 字符串'hwf1'(Halide Weights Format 1),否则load直接返回false;
  2. 接下来两个 uint32 是PipelineFeatures::version()与ScheduleFeatures::version()的版本号;
  3. 然后是buffer-count,必须恰为 6,与结构体中的六个缓冲一一对应;
  4. 每个缓冲先写维度个数与各维度 extent,读取时必须与结构体声明的维度完全一致(dimension_count必须等于buf.dimensions(),每个extent必须等于buf.extent(d));
  5. 最后按行优先顺序读取float32权重数据。

load_onelambda 中任何一处不匹配(i.fail()或尺寸不符)都会导致整个加载失败,从而保证损坏的、被截断的或来自不同网络结构的权重文件不会静默产生错误结果。这也是为什么"版本号 + 签名 + 尺寸三重校验"是理解这些.weights文件的关键。

两个版本号为何重要?

Weights结构体的头两个字段就是特征版本号(Weights.h):

uint32_t pipeline_features_version = PipelineFeatures::version(); uint32_t schedule_features_version = ScheduleFeatures::version();

这两个版本号的意义在于:成本模型输入的特征向量由 Featurization.h 中定义的PipelineFeatures与ScheduleFeatures计算得到,而特征集合一旦在代码中增删或调整,其version()就会变化。如果权重文件的特征版本号与当前编译的 Halide 不一致,模型对特征向量的解释就会错位,因此load会记录版本号,由上层代码判断是否匹配。从源码结构可以推断:这是为了让训练好的权重与代码库中的特征定义保持同步,避免"老权重喂新特征"造成的维度错配。若遇到版本不匹配,就需要用 retrain_cost_model.cpp 重新训练。

四、训练平台约束:V100 与 GPU 迁移注意事项

README 末尾明确给出了唯一的硬件前提:

These weights were trained on a V100 and may not perform the same on other GPUs.

这短短一句话包含两层事实:

  1. 训练平台:这批权重是在 NVIDIA V100 GPU 上完成训练的(注意训练平台与推理平台并非同一概念——训练时的硬件环境会影响模型学习到的调度偏好分布);
  2. 适用边界:在其他 GPU 上使用这些权重,性能表现可能不一致。

为什么会有这种差异?从 GPULoopInfo.cpp / GPULoopInfo.h 与 GPUMemInfo.h 的源码结构可以看到,自动调度器的特征工程中包含了大量与 GPU 硬件相关的信息(如内存层级、共享内存、线程块组织、访存模式等)。不同代际、不同厂商的 GPU 在共享内存大小、寄存器数量、缓存行为、带宽延迟比例上差异显著,V100 上最优的 tile 尺寸、unroll 因子、向量化宽度到了其他 GPU 上未必最优。因此:

  • 如果你的目标设备恰好是 V100 或与 V100 架构行为相近的 GPU,直接使用本目录权重即可;
  • 如果是其他 GPU(如 A100、H100、消费级 RTX 或非 NVIDIA 平台),建议把本目录权重作为起点/基线,并用 retrain_cost_model.cpp 结合你目标硬件上的采样数据微调权重,或与baseline.weights做对比评测。

五、权重的加载、转换与预测实操

5.1 加载机制

Weights结构体提供两种加载途径(Weights.cpp):

  • load_from_file/save_to_file:读写新版单一.weights文件(即本目录中的格式,含签名与版本号);
  • load_from_dir/save_to_dir:读写旧版目录格式——六个原始二进制.data文件:
head1_conv1_weight.data head1_conv1_bias.data head2_conv1_weight.data head2_conv1_bias.data trunk_conv1_weight.data trunk_conv1_bias.data

注意旧格式(load_from_dir)不记录版本号,源码中 Weights.cpp 会直接"假设它们与当前特征版本一致"("Old style data doesn't record the versions, so just assume they are current")——这意味着旧格式数据遇到特征版本演进时天然存在隐患,这也正是新格式引入签名与版本号的原因。

5.2 旧格式转换工具

仓库中专门提供了一个转换工具 weightsdir_to_weightsfile.cpp,用于把旧目录格式统一转换为新.weights文件格式:

Usage: weights_dir weights_file.weights

它内部依次调用Weights::load_from_dir(argv[1])与Weights::save_to_file(argv[2])。源码注释说明了它的定位:"Utility to convert from the old dir-of-raw-data into a new .weights file. Should live only long enough for downstream users to convert existing data files to the new format."——即它只服务于存量数据的格式迁移。该工具的构建由 CMakeLists.txt 与 Makefile 管理。

5.3 用权重做推理预测

scripts/predict_all.sh 展示了如何用已有权重在样本集上做推理预测:

Usage: $0 halide_build_dir samples_dir weights_file predictions_file include_filenames limit parallelism

实际调用的是retrain_cost_model可执行程序,并设置NUM_EPOCHS=1、学习率 0.001——即只跑一个 epoch 的前向预测,将预测结果写入predictions_file。相关配套脚本还有 average_times.sh(汇总多次运行的平均耗时)与 utils.sh(路径处理等公共函数)。而训练数据集的生成则依赖 generate_data.sh。

六、何时需要重新训练

结合 README 的留一法说明与源码结构,以下场景应当考虑重新训练而非直接套用:

  1. 目标硬件与 V100 差异大:README 已明确提示"may not perform the same on other GPUs";
  2. 特征版本不匹配:Weights::load读取的PipelineFeatures::version()/ScheduleFeatures::version()与当前代码库不一致时(版本号定义见 Featurization.h),应使用 retrain_cost_model.cpp 重新训练;
  3. 管道类型显著偏离训练分布:例如你的应用与目录中 17 个应用均无相似结构,留一法训练出的权重在极端陌生的特征空间上外推能力有限;
  4. 追求更强的调度质量:baseline.weights与各应用权重的存在本身说明"按应用定制权重"能带来更好结果,有条件的团队可在目标硬件上做留一法训练流程复现。

七、结语:这些权重如何指导你的使用决策

归纳 README 与源码,这套权重的核心事实链是:按应用分离 + 留一法训练 + V100 平台限定 + 带签名与版本号的二进制格式。实际使用时的决策顺序可以简化为:

  1. 判断目标设备是否为 V100 或近似架构——是则直接用,否则做好性能可能下降的心理预期并准备重新训练;
  2. 在 weights 目录 中选择与你的管道最接近的应用权重(或使用baseline.weights);
  3. 若持有旧版六个.data文件,用 weightsdir_to_weightsfile.cpp 转换为新格式;
  4. 用 scripts/predict_all.sh 在验证集上评测权重质量,必要时用 retrain_cost_model.cpp 在目标硬件上重新训练。

通过上述流程,你既能快速上手现成的预训练权重,也清楚它们在什么条件下会失效、如何针对自己的硬件与应用做定制化迭代。

  • 编译器
  • 图像处理
  • 编程语言
  • 高性能计算

【免费下载链接】Halide

a language for fast, portable>项目地址:https://gitcode.com/gh_mirrors/ha/Halide

点击查看免费下载

相关推荐

上一篇:Move 语言常量(Constants)权威指南:声明语法、可见性控制与编译期求值 —— 基于 aptos-core 仓库 Move Book 文档深度解析
下一篇:MobileViT-v2 模型对比分析:与其他轻量级视觉模型的差异

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询