 读懂调度产物)
编译器图像处理编程语言高性能计算【免费下载链接】Halidea language for fast, portable>项目地址https://gitcode.com/gh_mirrors/ha/Halide点击查看免费下载Func::print_loop_nest()是 Halide 开发者在写调度schedule时最常用的验证工具它以伪代码形式打印一条已调度管线最终生成的循环结构用于确认调度是否实现了你的意图。本文以 Halide 官方调度指南第 12 章.claude/skills/scheduling/references/guide/12-reading-a-loop-nest.md为核心结合仓库源码src/PrintLoopNest.cpp深入讲解输出记号的五种行形状、缩进语义、单个 Func 的循环生成规则以及更新阶段update stage以兄弟嵌套打印的原理读完你就能熟练读懂并核对任意 Halide 调度的循环结构。什么是 print_loop_nest()在 Halide 中程序被拆分为**算法algorithm与调度schedule**两部分算法定义每个值是什么调度决定每个值何时、何地计算与存储。调度不改变计算结果只改变循环的顺序、嵌套与类型。问题在于一个包含split、reorder、compute_at、vectorize等指令的调度最终会组合出什么样的循环结构Func::print_loop_nest()正是回答这个问题的最直接方式。它把调度后的管线输出为一段可读的伪代码是调度指南.claude/skills/scheduling/references/guide/README.md第三部分调度指令所有章节的共同基准每一章展示的循环嵌套就是print_loop_nest()打印出来的样子。从源码看这个功能是一条完整但简化了的编译链路。在src/PrintLoopNest.cpp的print_loop_nest(const vectorFunction output_funcs)中Halide 先对整张 Func 图做深拷贝、把输出 Func 固定为compute_root().store_root()、锁定所有 LoopLevel然后依次执行调度函数schedule_functions、边界推断bounds_inference、滑窗sliding_window、分配边界推断allocation_bounds_inference等 lowering 前段管线最后用一个PrintLoopNest访问器把 IR 语句转成伪代码字符串返回。换言之它打印的是真正会执行的循环结构与 lowering 后段之前的形态一致而不是对调度的猜测。五种行形状输出记号print_loop_nest()的输出由五种行形状构成通过两个空格的缩进分层produce f: # 一个区域计算并写入Func f consume f: # 一个区域读取 f 已存储的值 for var: # 对某一维的循环 f(...) ... # 叶子计算 f 的一个点 store f: # f 的存储作用域仅当与计算作用域不同时打印对应的源码实现见src/PrintLoopNest.cppPrintLoopNest继承IRVisitor重写visit(const ProducerConsumer*)打印produce/consume重写visit(const For*)打印循环重写visit(const Provide*)打印叶子赋值行重写visit(const Realize*)在存储与计算层级不同时额外打印store。缩进即包含关系这是整套记号的核心语义produce f包含计算 f 的循环结束于与之匹配的consume或退格缩进所有读取 f 的内容都位于consume f之下for包含其循环体叶子f(...) ...位于最内层是真正发生计算的位置。consume与store只有在调度把多个 Func 组合起来、或将存储与计算拆分开时才会出现因此它们会随compute_at、store_at等指令见 Placement: compute_root 与 compute_at 和 Storage Levels在后续章节登场。一个未经调度的 Func 只需要produce、for和叶子三类即可完整表达。输出里被省略的两类信息print_loop_nest还会打印两类不影响结构的信息本指南的示例一律将其省略因为它们既不影响循环结构也不可手工复现精确的循环变量名Halide 的变量名携带内部split/rfactor谱系信息外加一个全局计数器如x.split.1.outer$1。源码中simplify_var_name/simplify_func_name专门负责裁掉$n唯一性后缀和函数名、阶段号只保留用户可读的部分——这从侧面印证了名称本身对理解结构没有意义。常量循环边界如for x in [0, 7]。这类边界来自边界推断Bounds Inference属于运行时才确定的信息。PrintLoopNest的visit(const For*)实现里确实有打印min/max的逻辑src/PrintLoopNest.cpp但只有在两者都是常量时才输出in [min, max]否则留空。这两类信息都不改变结构但它们带来一个实用的推论交换两个纯串行serial循环的reorder在输出中不留任何痕迹。因为本指南忽略变量名与常量边界两个普通for交换后结构完全相同。只有当循环携带了类型vectorize/parallel/unroll之后reorder才会以可见的方式体现详见 Reshaping Loops 与 Loop Types。循环省略结构不完全由调度决定调度结构并非总是完全由调度本身决定最主要的例外是循环省略loop elision当一个循环的迭代范围塌缩为单点时它会被从输出中删除。是否发生省略取决于边界推断作用于真实索引数学的结果而不是调度指令本身详见 Placement: compute_root 与 compute_at。因此调度隐含的循环是图中显示的这些但其中哪些会塌缩为单点是一个边界推断层面的问题在涉及之处会单独标注。例如一个范围恰好为 1 的纯串行循环不会打印for行——而 GPU 循环除外1 次迭代的 GPU 循环会保留见 Loop Types。单个 Func 的循环维度列表与行主序在不做任何调度的情况下一个 Func 的循环直接来自它的定义。这正是输出中produce块内部的内容也是任何独立构建的 Func 的形态。每个阶段stage都携带一个有序的维度列表最内层在前。对于纯定义pure definition维度列表初始化为参数Var的顺序因此第一个参数就是最内层维度。阶段打印时为每个维度输出一个for循环最外层在前即列表的逆序叶子在中间。所以f(x, y, c) ...的维度列表是[x, y, c]打印结果为produce f: for c: for y: for x: f(...) ...这就是行主序row-major第一个维度变化最快c是最外层最慢x是最内层最快。reorder改变顺序split/fuse/tile改变循环数量见 Reshaping Loops。split把一个维度变成内层运行0..factor-1加外层两个循环fuse相反tile等价于两次split加一次reorder都会直接体现在打印的for行数与嵌套上。更新阶段打印为兄弟嵌套一个 Func 的所有阶段都打印在同一个produce f块内表现为背靠背的兄弟循环嵌套而不是独立的 produce/consume 块——阶段之间没有consume。读者看到的是 Func 更新完成后的最终值因此它的consume如果有包裹的是整块内容。以编程模型一章The Programming Model中的直方图为例Var x(x); ImageParam in(type_ofint(), 1, in); Func hist(hist); hist(x) 0; RDom r(0, 256, r); hist(clamp(in(r), 0, 255)) 1; hist.print_loop_nest();打印结果produce hist: for x: # stage 0: initialize hist(x) 0 hist(...) ... for r: # stage 1: the scatter update hist(...) ...两个阶段初始化与累加散射同处一个produce hist块内第二个阶段的for r直接缩进在第一个阶段的叶子之后说明它们顺序执行、共享同一个存储缓冲。这种散射更新正是 Halide 处理直方图、邻域累加等归约计算的典型形态。每个阶段的维度列表如何构成每个阶段都有自己的维度列表由**该阶段自己的左侧LHS**决定左侧的自由Var加上它使用的那一个RDom的RVar。RVar排在最内层按RDom声明顺序排列r.x最内层自由纯Var在它们外层。若某个纯维度的 LHS 槽位被RVar或一般表达式占据则该阶段不为其打印循环。几个关键示例f(x, y) in(x r.x, y r.y)使用 2-DRDom的更新阶段给出for y: for x: for r.y: for r.x:——自由变量y、x在外归约变量r.y、r.x在内纯阶段f(x, y) 0只给出for y: for x:像直方图这样没有自由 LHS 变量的散射其阶段只有归约循环for r:。理解这一点对判断更新阶段的计算代价与内存访问模式至关重要归约维度总在最内层意味着每个输出点都要遍历整个归约域。源码视角调用链与实现细节print_loop_nest()的完整调用链在仓库中清晰可循公共 APIFunc::print_loop_nest()定义于src/Func.cpp声明于src/Func.h直接委托给pipeline().print_loop_nest()Pipeline 层Pipeline::print_loop_nest()在src/Pipeline.cpp中调用内部实现Halide::Internal::print_loop_nest(contents-outputs)并输出到调试流内部实现std::string print_loop_nest(const vectorFunction output_funcs)位于src/PrintLoopNest.cpp其头文件src/PrintLoopNest.h注释明确指出其职责是emit simple pseudocode that shows the structure of the loop nest specified by this pipelines schedulePython 绑定pybind11绑定同样暴露了该方法见python_bindings/halide/src/halide_/PyFunc.cpp因此 C 指南中的示例在 Python 中等价可用f.print_loop_nest()作为halide.Func的方法。实现细节上值得注意两点其一PrintLoopNest打印循环时会带上for_type即serial、parallel、vectorized、unrolled、gpu_block等类型标记以及 GPU 的device_api后缀src/PrintLoopNest.cpp这正是调度指南所说的类型标记属于结构的一部分其二整个打印过程在 lowering 前段完成、且将目标平台的所有设备特性全部启用src/PrintLoopNest.cpp所以它展示的是调度决定的结构本身不掺杂具体代码生成细节。实战何时用、怎么用写完调度立即核对在print_loop_nest()前后各调一次对比调度前后循环数量、嵌套顺序与类型标记的变化这是确认compute_at/store_at/reorder是否落在预期循环层级上的最可靠手段。调试并行/向量化问题检查parallel、vectorized、unrolled标记是否出现在预期维度上注意vectorize(v, n)会先生成split并给内层打上vectorized标记而parallel(v, n)给外层打标记见 Loop Types。评估 GPU 调度print_loop_nest原样展示 GPU 循环带Default_GPU等设备后缀可用于检查 block/thread 嵌套是否符合 GPU 合法性要求。与相关工具配合更精细的验证可结合HL_DEBUG_CODEGEN1查看 lowering 各阶段 IR以及.stmt文件见 Reading the .stmt File确认矢量化形态。小结print_loop_nest()用五种行形状与缩进把一条管线的循环结构变成可读、可讨论、可验证的伪代码。掌握它的记号就掌握了 Halide 调度的透视镜你能看出维度顺序行主序、阶段组织兄弟嵌套、归约循环位置最内层也能识别哪些信息被省略变量名、常量边界、哪些循环会被消掉单点省略。它是调度指南后续所有指令章节Reshaping Loops、Loop Types、Placement 等共同依赖的解读基础——先学会读循环嵌套再谈如何塑造它。赞分享编译器图像处理编程语言高性能计算【免费下载链接】Halidea language for fast, portable>项目地址https://gitcode.com/gh_mirrors/ha/Halide点击查看免费下载相关推荐使用 MDM 命令恢复被删除的 fleetd agentFleet 系统管理员实战指南使用 MDM 命令恢复被删除的 fleetd agentFleet 系统管理员实战指南 本指南以 Fleet 开源仓库中的系统管理员系列文章《Sysadmin编译器图像处理编程语言高性能计算BrewUI Configuration界面完整指南5分钟快速读懂Homebrew环境配置BrewUI Configuration界面完整指南5分钟快速读懂Homebrew环境配置 BrewUI 是 Homebrew 官方的 macOS 图形界面编译器图像处理编程语言高性能计算把微信公众号变成RSS一份可私有化的完整自托管笔记把微信公众号变成RSS一份可私有化的完整自托管笔记 早上翻再看列表我发现有 23 个微信公众号超过十天没打开过。我不想在微信里天天打卡希望这些号的编译器图像处理编程语言高性能计算上一篇ComfyUI性能优化终极指南让AI绘图速度提升3倍的10个技巧下一篇Koodo Reader智能推荐基于阅读历史的书籍推荐创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考