ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Halide 编程模型详解:Var、Func、RDom 与流水线图——调度手册 Part I 核心词汇与对象

Halide 编程模型详解:Var、Func、RDom 与流水线图——调度手册 Part I 核心词汇与对象 编译器图像处理编程语言高性能计算【免费下载链接】Halidea language for fast, portable>项目地址https://gitcode.com/gh_mirrors/ha/Halide点击查看免费下载导读本文讲解 Halide 调度体系中最基础、也是首先需要建立的「编程模型」调度scheduling操作的对象到底有哪几类它们各自的状态与行为以及它们如何通过生产者producer边连成一张有向无环的流水线图pipeline graph。读完本文你将掌握Var、Func、Expr、ImageParam、RDom/RVar这五个核心词汇的确切含义理解 Stage初始纯定义 更新定义与纯/非纯函数的划分并知道输出output与边界分析bounds analysis在这张图上扮演的特殊角色——这些正是整个调度手册.claude/skills/scheduling/references/guide/后续所有章节使用的公共词汇。调度操作的对象五个核心词汇调度scheduling只作用于少量几类对象。本手册第 2 章02-the-programming-model.md先给它们命名、说明它们组成的图手册其余部分默认沿用这套词汇。Var维度与循环变量的名字Var是一个维度的名字本质上就是一条循环变量loop variable。它唯一的身份就是名字本身不携带任何额外状态——两个同名的Var会被视为同一个变量。在源码中这一点体现得非常直接src/Var.h中的Var类只有一个Expr e成员一个Int(32)的内部变量表达式构造时仅记录名字same_as()的实现就是字符串比较return name() other.name();Var.h。Var的典型用途有两个作为纯定义pure definition的左值参数例如f(x, y) ...中的x、y作为调度调用里的循环句柄例如f.parallel(x)、f.vectorize(x, 8)中指定要操作的维度。Var还支持一类特殊的隐式变量源码里预声明了_0到_9共十个src/Var.h末尾的ImplicitVar0…ImplicitVar9以及占位符_。当函数调用参数个数少于其维度数时Halide 会自动注入隐式变量补齐维度这为f(_)、img.in().gpu_threads(_0, _1)这类简写提供了语法基础。Func函数定义的操作句柄Func是「一个函数定义」的句柄handle它是被调度的那个对象。理解Func的关键在于复制语义复制一个Func只是产生指向同一个底层函数的另一个句柄无论通过哪个句柄做调度作用的都是同一个函数。从源码结构看Func内部持有Internal::Function见 Func.h而Internal::Function是对FunctionPtr引用计数的共享指针的封装因此拷贝Func并不会拷贝函数本体。一个Func的状态包括状态含义name用于打印输出也用于在计算顺序中打破平局breaking ties in computation orderstages有序列表一个初始纯定义加上零个或多个更新定义见下文「Stage 与更新定义」producers它从哪些其他Func读取数据即其定义右侧引用的上游Func集合compute level / store level它在哪儿被计算compute、存储store放在哪儿默认值是 inline内联其中 compute level 与 store level 在源码中以LoopLevel表达src/Schedule.h中LoopLevel::inlined()表示内联、LoopLevel::root()表示根层级相当于compute_root/store_rootLoopLevel(f, x)则把计算/存储锚定到某个函数的某个循环上对应compute_at/store_at。Expr定义右侧的值表达式Expr是定义右侧RHS的值表达式。对于循环嵌套loop nest的构建而言Expr中唯一真正重要的是它读取了哪些Func——正是这一点把生产者/消费者图producer/consumer graph连接起来。普通算术、常量以及castT(...)这类表达式不会出现在循环嵌套中它们最终被折叠进叶子行f(...) ...内部。也就是说Expr的复杂计算是在赋值那一行内部完成的不单独产生循环结构。ImageParam输入缓冲叶子ImageParam代表一个输入缓冲例如管线的输入图像。它是叶子leaf永远不会被计算永远不会出现在循环嵌套中读取某个ImageParam的Func只是少了一个需要构建的生产者——因为缓冲已经存在了。src/ImageParam.h中的注释确认了这一点ImageParam 是「a leaf in the pipeline」管线中的叶子它经set()绑定具体Buffer后即可通过operator()生成读取表达式。它甚至可以直接隐式转换为Funcoperator Func()并支持img.in()包装以便在调度中为其建立中间缓冲例如把输入加载到 GPU shared memory。RDom / RVar归约域与归约变量RDom/RVar是归约域reduction domain与归约变量reduction variable。声明RDom r(min, extent, …)会生成一个或多个RVarr.x、r.y依此类推供**更新定义update definition**使用。RVar命名循环的方式与Var相同但有两个关键区别它的迭代是**有序ordered**的相邻迭代之间可以携带依赖例如累加和running sum。因此RVar只能出现在更新定义里不能用于纯定义。src/RDom.h展示了RDom的完整构造方式RDom r(0, 10)单维归约域min0extent10RDom r(0, 4, 0, 5)多维归约域一次声明多条归约轴RDom r(input)/RDom r(buffer)直接遍历某个ImageParam或Buffer的全部像素维度与参数一致r.where(predicate)为归约域添加谓词只迭代谓词为真的子集可用于非矩形域或形状依赖自由变量的域。RVar可以直接当作Expr使用单维RDom也可以直接转换为RVar或Expr。归约变量的取值满足min()≤ 值 min() extent()。Stage 与更新定义一个Func可以拥有更新定义update definitions写在首个定义之后的赋值就地修改该Func的值。手册第 2 章给出的标准例子Func hist(hist); hist(x) 0; // stage 0: the pure (initial) definition RDom r(0, N, r); hist(in(r)) 1; // stage 1: an update definition这就是一个直方图统计的骨架先用纯定义把所有位置清零stage 0再用更新定义对每个输入像素in(r)做累加stage 1。有序的 stage 列表「初始定义 更新定义」构成一个有序的stages列表s0纯定义、s1依此类推。语义上有两条硬规则每个 stage 完成之后下一个 stage 才开始它们之间串行衔接所有 stage 写入同一份存储。从源码结构看Internal::Function持有一个初始Definitionis_init() true和一组更新Definitiondefine_update()见 Definition.h 与 Function.h每个Definition各带自己的StageSchedule。这就是「每个 stage 被单独调度」的内部基础。由于 stage 属于算法部分——它们描述了这个Func是什么理解它们不需要任何调度知识——所以每个 stage 仍要各自调度通过f.update(n)定位。例如f.update(0)代表第一个更新定义参见后续章节 Reshaping Loops以及src/Func.h中Stage::name()返回形如f.update(2)的字符串。更新定义的约束一条更新定义最多引用一个RDom。如果需要多条归约轴就来自单个多维RDom例如RDom r(0, 4, 0, 5); // r.x ∈ [0,4)r.y ∈ [0,5)同一条更新定义内使用RDom.h还给出了归约的多种经典用法可帮助理解更新定义的表达能力递归自引用构建纯函数在 Halide 中不能递归但更新定义可以——例如前 20 个斐波那契数Func f; Var x; RDom r(2, 18); f(x) 1; f(r) f(r-1) f(r-2);散射scatter与纯定义不同更新定义的左侧可以是一般表达式因此可把数据写到任意位置ImageParam input(UInt(8), 2); Func histogram; Var x; RDom r(input); // 遍历输入全部像素 histogram(x) 0; histogram(input(r.x, r.y)) histogram(input(r.x, r.y)) 1;混合纯维度与归约变量可以把某个维度保持为纯维度以获得更灵活的调度。例如二维求和表先水平累加再垂直累加若sum_x(r.x, y)保留纯维度y就可以用sum_x.update().parallel(y)并行化每一行的内部累加注意sum_x.parallel(y)只并行化初始化step不会作用于更新 step——调度更新 step 必须经由f.update()返回的句柄。当归约变量与纯维度混合时归约域默认最外层遍历必要时可用sum_x.compute_at(sum_y, y)这样的compute_at调度来改变遍历方向与局部性详见手册第 14 章 Placement: compute_root and compute_at。纯 vs 非纯Pure vs Non-pure一个Func是纯pure的当且仅当它只有初始定义、没有更新定义一旦加上一个或多个更新定义它就变成非纯non-pure。这一划分在手册 Part III调度指令部分中反复起作用。两者的本质区别决定了编译器能做什么纯 Func 可以被代入substitute到调用者中作为一个表达式。因为纯定义本身就是一个表达式编译器可以把它直接展开进消费它的循环体而不必单独分配存储。非纯 Func 不能这样代入——归约不是表达式reduction isnt an expression所以它必须被构建到存储storage中。「这一个事实驱动了后续的若干规则」非纯函数天然不能内联必然占据存储、成为循环嵌套中真实存在的一环这也与src/Inline.cpp/InlineReductions.cpp等内部 pass 的分工对应——内联化只对可作为表达式展开的纯函数成立归约则经由单独的归约内联路径处理。流水线图The Pipeline GraphFunc之间通过生产者producer边连接构成一张有向无环图DAG这就是流水线pipeline。其中有一个Func是输出output即对其调用print_loop_nest()或realize的那个Func。从输出出发、沿生产者边可达的所有Func都属于这条流水线除此之外的一切都与本流水线无关。这张图有两个值得尽早锁定的性质输出是特殊的它总是在最顶层被计算恰好一次并且永远不会被内联掉。因此输出必然以真实的循环与存储形式存在。强制一个 Func 非内联会使它对于边界分析bounds analysis变得不透明opaque。设想一个仿射辅助函数helper(x) m*x b被用作索引f(helper(x))。内联时它编译得很好但一旦强制其非内联边界推断bounds inference可能失败报错形如calls ... in an unbounded way in dimension ...因为编译器无法再「看穿」非内联函数内部的索引关系。所以索引辅助函数通常最好保持内联。相关机制可进一步阅读手册第 4 章 Bounds Inference以及src/BoundsInference.cpp与src/PrintLoopNest.cppprint_loop_nest的实现入口。小结从词汇到后续章节本篇建立的词汇表是整本调度手册的公共地基词汇一句话定位Var只携带身份的循环/维度名字Func被调度的函数句柄包含名字、stages、producers、compute/store levelExpr右侧表达式唯一影响循环嵌套的是它读取了哪些 FuncImageParam叶子输入缓冲不出现在循环嵌套中RDom/RVar有序、可携带迭代依赖的归约域与归约变量只出现在更新定义中Stage纯定义 有序更新定义各自独立调度f.update(n)纯/非纯纯可被表达式代入非纯必须落存储流水线图生产者边构成 DAG输出可及者才属于本管线后续阅读建议均在 guide 目录 下03-realizing-a-pipeline.md管线何时真正执行JIT vs AOT04-bounds-inference.md缓冲区与循环边界如何由输出请求推导12-reading-a-loop-nest.md 与 13-defaults-and-inlining.md如何读循环嵌套、内联默认值与三种计算层级16-reshaping-loops.mdsplit/fuse/reorder/tile如何重塑循环以及f.update(n)的具体用法。源码层面Var、Func、RDom、ImageParam的完整定义分别位于 src/Var.h、src/Func.h、src/RDom.h、src/ImageParam.h调度相关的LoopLevel/计算存储层级定义位于 src/Schedule.h均可作为本文各结论的直接佐证。赞分享编译器图像处理编程语言高性能计算【免费下载链接】Halidea language for fast, portable>项目地址https://gitcode.com/gh_mirrors/ha/Halide点击查看免费下载相关推荐3D-Speaker开发者进阶指南自定义模型架构与训练流程开发3D Speaker开发者进阶指南自定义模型架构与训练流程开发 3D Speaker是一个专注于单模态和多模态说话人验证、识别及说话人分割的开源项目为开发者人工智能语音深度学习Horovod核心API与编程模型详解Horovod核心API与编程模型详解 本文深入解析Horovod分布式深度学习框架的核心API与编程模型重点剖析 hvd.init 初始化机制、分布式优化器深度学习机器学习分布式训练VIC水文模型完整配置手册3大核心模块详解与实战应用掌握VIC模型配置是水文科研工作的关键基础。本文提供完整的VIC模型配置手册从基础概念到高级应用帮助水文研究者快速上手并避免常见配置陷阱。 VIC模型数据分析上一篇构建高效数字人对话系统OpenAvatarChat模块化架构深度解析下一篇OpenCore Legacy Patcher3 步免费把老 Mac 升级最新 macOS创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表