资讯动态

PyPTO 条件分支编程实战:嵌套循环分支、动静结合条件与循环边界处理

发布时间:2026/9/19 5:33:56 来源:尧图企业网站定制
PyPTO 条件分支编程实战嵌套循环分支、动静结合条件与循环边界处理【免费下载链接】pyptoPyPTO发音: pai p-t-oParallel Tensor/Tile Operation编程范式。项目地址: https://gitcode.com/cann/pypto本篇技术指南基于 CANN/PyPTO 开源仓库中的examples/02_intermediate/controlflow/condition示例系统讲解如何在 PyPTO JIT 内核Kernel中编写条件分支逻辑。你会掌握四种典型场景的写法多级循环内的if/else分支、动态轴Dynamic Axis搭配编译期静态条件、动态轴搭配运行时动态条件以及基于is_loop_begin/is_loop_end的循环边界处理并能结合仓库源码理解条件在编译与图构建层面的实现原理。背景与目标在真实算子开发中同一份内核常常需要根据不同的条件走不同的计算路径——例如对不同分块执行加法或减法、在循环首尾迭代执行特殊计算、或依据某个编译期开关选择是否叠加偏移量。PyPTO 采用“宿主语言描述计算图”的编程范式在pypto.loop构建的符号循环体中可以直接使用 Python 的if/else结构描述分支PyPTO 前端会将其转换为控制流如cond分支与跳转语句进入计算图。本示例覆盖的四种场景嵌套循环中的条件语句Conditional Statements in Nested Loops在多层pypto.loop内使用if/else构建分支。动态轴 静态条件Dynamic Axis and Static Condition用编译期可确定的布尔标志控制循环体行为。动态轴 动态条件Dynamic Axis and Dynamic Condition用运行期的循环索引比较控制分支。动态轴 循环边界条件Dynamic Axis and Loop Boundary Condition用is_loop_begin/is_loop_end处理首尾迭代的边界逻辑。示例代码结构本示例位于 examples/02_intermediate/controlflow/condition/condition.py是一个集成式脚本包含 4 个可独立运行的测试用例用例 ID--list输出测试函数覆盖场景nested_loops_with_conditions::test_nested_loops_with_conditionstest_nested_loops_with_conditions嵌套循环中的条件语句dyn_axis_static_cond::test_add_scalar_loop_dyn_axis_static_condtest_add_scalar_loop_dyn_axis_static_cond动态轴 静态编译期条件dyn_axis_dyn_cond::test_add_scalar_loop_dynamic_axis_dynamic_condtest_add_scalar_loop_dynamic_axis_dynamic_cond动态轴 动态运行期条件dyn_axis_dyn_loop_cond::test_add_scalar_loop_dynamic_axis_dynamic_loop_condtest_add_scalar_loop_dynamic_axis_dynamic_loop_cond动态轴 循环边界条件所有用例均遵循同一套验证模式在 NPUnpu模式上运行内核用torch构造 golden 参考结果再通过numpy.testing.assert_allclose断言内核输出与 golden 在容差范围内一致在sim仿真模式下则跳过数值断言仅验证流程可执行。用例外层统一使用pypto.options(pass_options{enable_slice: True})装饰开启切片slice相关 pass。环境准备与运行方法环境准备# 配置 CANN 环境变量 # 安装完成后需配置环境变量请根据 set_env.sh 的实际路径执行下述命令。 # 上述环境变量配置仅对当前终端窗口生效可按需写入环境变量配置文件如 .bashrc。 # 默认路径安装以 root 用户为例非 root 用户请将 /usr/local 替换为 ${HOME} source /usr/local/Ascend/ascend-toolkit/set_env.sh # 设置设备 ID export TILE_FWK_DEVICE_ID0从脚本源码看TILE_FWK_DEVICE_ID由get_device_id()读取并强转int校验若环境变量未设置或不是整数脚本会打印提示Please set the environment variable TILE_FWK_DEVICE_ID before running并直接返回因此该变量是 NPU 模式下运行的必需项见 condition.py。执行脚本# 运行全部条件分支示例 python3 condition.py # 列出所有可用测试用例 python3 condition.py --list # 运行指定测试用例 python3 condition.py nested_loops_with_conditions::test_nested_loops_with_conditions脚本支持三个命令行参数由argparse解析见 condition.py位置参数example_id可选指定要运行的用例 ID缺省时运行全部用例。传入不存在的 ID 会打印ERROR: Invalid example ID并列出合法 ID 后退出。--list仅打印全部用例的 ID、名称与描述不执行内核。--run_mode {npu,sim}运行模式默认npu。sim为仿真模式。脚本还会在模块加载早期通过_peek_run_mode_from_argv预读该参数以便模块级的pypto.frontend.jit(runtime_options{run_mode: ...})装饰器提前拿到正确的运行模式NPU 或 SIM对应 runtime.py 中的pypto.RunMode枚举。场景一嵌套循环中的条件语句第一个用例演示在两层pypto.loop中根据外层循环索引i选择不同的计算路径pypto.frontend.jit(runtime_options{run_mode: global_run_mode}) def nested_loops_with_conditions_kernel( a: pypto.Tensor([pypto.DYNAMIC, pypto.DYNAMIC], pypto.DT_FP32), b: pypto.Tensor([pypto.DYNAMIC, pypto.DYNAMIC], pypto.DT_FP32), y: pypto.Tensor([pypto.DYNAMIC, pypto.DYNAMIC], pypto.DT_FP32), ): pypto.set_vec_tile_shapes(2, 8) for i in pypto.loop(2): for j in pypto.loop(2): a_view a[i:i 1, j:j 1] b_view b[i:i 1, j:j 1] if i 0: y[i:i 1, j:j 1] a_view b_view else: y[i:i 1, j:j 1] a_view - b_view关键点解析张量a、b、y的维度声明为[pypto.DYNAMIC, pypto.DYNAMIC]即形状在编译期未知、运行期确定DYNAMIC定义于 enum.py。pypto.loop(2)的边界 2 是编译期常量因此这是一个静态循环嵌套。pypto.set_vec_tile_shapes(2, 8)设置向量计算各维度的 tile 形状见 python/pypto/_controller.py这是 PyPTO 中控制向量化分块大小的常用手段。条件if i 0中i是符号循环索引PyPTO 前端会把该分支编译为图中的条件跳转而非在 Python 宿主侧展开。golden 验证为第 0 行执行a[0] b[0]第 1 行执行a[1] - b[1]与内核语义完全对应。场景二动态轴 静态编译期条件该用例的核心是公共内核函数add_core它接收一个普通的 Python 布尔参数add1_flag该参数在编译期即可确定用来控制循环体内部是否追加 val的偏移def add_core(input0: pypto.Tensor, input1: pypto.Tensor, output: pypto.Tensor, val: int, add1_flag: bool False): tensor_shape input0.shape pypto.set_vec_tile_shapes(1, 4, 1, 64) b tensor_shape[0] tile_b 1 b_loop b // tile_b for idx in pypto.loop(b_loop): b_offset idx * tile_b b_offset_end (idx 1) * tile_b t0_sub input0[b_offset:b_offset_end, ...] t1_sub input1[b_offset:b_offset_end, ...] t3_sub t0_sub t1_sub if add1_flag: output[b_offset:b_offset_end, ...] t3_sub val else: output[b_offset:b_offset_end, ...] t3_sub该内核被两个不同的 JIT 包装函数复用用于对比“静态形状”与“动态轴”两种声明方式的差异pypto.frontend.jit(runtime_options{run_mode: global_run_mode}) def add_scalar_loop_dyn_axis_static_cond_kernel_static( input0: pypto.Tensor(), input1: pypto.Tensor(), output: pypto.Tensor(), val: int, flag: bool ): add_core(input0, input1, output, val, flag) pypto.frontend.jit(runtime_options{run_mode: global_run_mode}) def add_scalar_loop_dyn_axis_static_cond_kernel_dynamic( input0: pypto.Tensor([pypto.DYNAMIC, ...], pypto.DT_FP32), input1: pypto.Tensor([pypto.DYNAMIC, ...], pypto.DT_FP32), output: pypto.Tensor([pypto.DYNAMIC, ...], pypto.DT_FP32), val: int, flag: bool, ): add_core(input0, input1, output, val, flag)两种内核行为完全一致区别在于输入张量的形状声明方式pypto.Tensor()未显式标注动态轴而pypto.Tensor([pypto.DYNAMIC, ...])显式声明首维为动态、其余维度沿用张量实际形状。测试中分别以flagFalse和flagTrue调用验证编译期开关add1_flag能正确切换两条计算路径flagFalseoutput input0 input1golden 为torch.add(input_data0, input_data1)flagTrueoutput input0 input1 valval1golden 为torch.add(input_data0, input_data1) val。测试输入形状为(32, 32, 1, 256)tile_b 1意味着循环以 1 为粒度切分首维set_vec_tile_shapes(1, 4, 1, 64)规定了剩余维度上的 tile 形状。这里的关键认知是flag是一个普通 Python 布尔值在构建计算图时其取值已经确定因此分支在编译期即可被前端识别并生成对应的图结构——这就是“静态条件”。场景三动态轴 动态运行期条件与场景二不同本用例的条件来自循环索引idx与常量 2 的运行期比较pypto.frontend.jit(runtime_options{run_mode: global_run_mode}) def add_scalar_loop_dyn_axis_dyn_cond_kernel( input0: pypto.Tensor([pypto.DYNAMIC, ...], pypto.DT_FP32), input1: pypto.Tensor([pypto.DYNAMIC, ...], pypto.DT_FP32), output: pypto.Tensor([pypto.DYNAMIC, ...], pypto.DT_FP32), val: int, ): pypto.set_vec_tile_shapes(1, 4, 1, 64) b input0.shape[0] tile_b 1 b_loop b // tile_b for idx in pypto.loop(b_loop): b_offset idx * tile_b b_offset_end (idx 1) * tile_b t0_sub input0[b_offset:b_offset_end, ...] t1_sub input1[b_offset:b_offset_end, ...] t3_sub t0_sub t1_sub if idx 2: output[b_offset:b_offset_end, ...] t3_sub val else: output[b_offset:b_offset_end, ...] t3_sub这里的循环上界b_loop b // tile_b依赖运行期才知道的张量形状input0.shape[0]因此循环次数是动态的而if idx 2的判断对象idx是符号循环索引其取值只有在运行期才能确定。PyPTO 前端会将这类分支编译为运行期条件跳转——这正是“动态条件”与场景二“静态条件”的本质区别。golden 验证逻辑与之严格对应golden[0:2, ...]需要额外 val其余部分保持a b。场景四动态轴 循环边界条件最后一个用例引入 PyPTO 专为循环边界设计的两个符号函数is_loop_begin与is_loop_end在循环的首、尾迭代执行特殊计算pypto.frontend.jit(runtime_options{run_mode: global_run_mode}) def add_scalar_loop_dyn_axis_dyn_loop_cond_kernel( input0: pypto.Tensor([pypto.DYNAMIC, ...], pypto.DT_FP32), input1: pypto.Tensor([pypto.DYNAMIC, ...], pypto.DT_FP32), output: pypto.Tensor([pypto.DYNAMIC, ...], pypto.DT_FP32), val: int, ): pypto.set_vec_tile_shapes(1, 4, 1, 64) b input0.shape[0] tile_b 1 b_loop b // tile_b for idx in pypto.loop(b_loop): b_offset idx * tile_b b_offset_end (idx 1) * tile_b t0_sub input0[b_offset:b_offset_end, ...] t1_sub input1[b_offset:b_offset_end, ...] t3_sub t0_sub t1_sub if pypto.is_loop_begin(idx): output[b_offset:b_offset_end, ...] t3_sub val elif pypto.is_loop_end(idx): output[b_offset:b_offset_end, ...] t3_sub val 1 else: output[b_offset:b_offset_end, ...] t3_subgolden 验证明确了边界语义golden[0:1, ...]首个 tile加valgolden[31:32, ...]末个 tile加val 1中间部分保持a b。is_loop_begin/is_loop_end的底层实现这两个 API 定义于 python/pypto/_controller.py签名均为(scalar: SymbolicScalar) - SymbolicScalar返回一个“当前迭代是否为循环起点/终点”的符号布尔表达式。其前置检查会校验入参是否为循环索引若传入对象不携带_loop_begin/_loop_end属性即不是pypto.loop产生的索引会抛出FeError(ValueError(not loop index))。在 PIL前端图构建层的实现位于 python/pypto/pil/ops.py可以看到它们如何从当前循环上下文推导出等价条件impl(pypto.is_loop_begin) def is_loop_begin_impl(ctx: BuildContext, scalar: SymbolicScalar): start, _, _ ctx.loop_stack[-1] assert isinstance(start, (SymbolicScalar, int)), is_loop_begin() must be called in a pypto.loop return scalar start impl(pypto.is_loop_end) def is_loop_end_impl(ctx: BuildContext, scalar: SymbolicScalar): _, end, step ctx.loop_stack[-1] assert isinstance(end, (SymbolicScalar, int)), is_loop_end() must be called in a pypto.loop assert isinstance(step, (SymbolicScalar, int)), is_loop_end() must be called in a pypto.loop return scalar step end即is_loop_begin(idx)等价于idx startstart默认 0is_loop_end(idx)等价于idx step end考虑到step不为 1 的情形采用而非以保证边界判断的健壮性。同时二者都要求调用点必须处于pypto.loop上下文内读取ctx.loop_stack否则断言失败——这也解释了为何该 API 只能用于循环体中。关键 API 速查与原理pypto.loopPyPTO 的核心循环原语以生成器方式在构建计算图时产出符号循环索引。支持两种调用形式定义见 python/pypto/_controller.pypypto.loop(stop)等价于range(stop)从 0 迭代到stop不含。pypto.loop(start, stop, step1)显式指定起点、终点与步长。常用关键字参数包括name循环名默认自动编号loop_{N}、idx_name循环索引名、unroll_list展开因子列表默认[1]、submit_before_loop循环前是否插入 barrier、parallel是否并行化。同一目录下的 loop 示例 对循环展开与调度有更完整的演示。pypto.cond与if/else在前端图中if分支最终通过pypto.cond见 python/pypto/_controller.py承载条件表达式并在 PIL 层转换为条件跳转见 python/pypto/pil/ops.py。注意文档示例中pypto.cond多用于标量分支场景如if pypto.cond(k 0): ...在条件示例脚本中则直接使用 Python 原生if写法二者最终都会进入控制流图。条件判断应放在循环体内、且基于循环索引或编译期常量才能被正确编译为设备端分支。pypto.set_vec_tile_shapes设置向量计算的 tile 形状接受若干整数或可具体化的符号标量最终通过pypto_impl.SetScope({vec_tile_shapes: concrete_shapes})写入当前作用域python/pypto/_controller.py。示例中三种动态轴用例统一使用(1, 4, 1, 64)首个维度batch 维以tile_b 1逐块切分。RunMode与TILE_FWK_DEVICE_ID运行模式由 python/pypto/runtime.py 中的pypto.RunMode枚举控制NPU 0、SIM 1。NPU 模式下脚本会通过torch.npu.set_device(device_id)指定设备并执行数值断言SIM 模式仅验证图构建与执行流程。设备号来源即环境变量TILE_FWK_DEVICE_ID。注意事项条件分支会影响编译器的代码生成路径复杂嵌套条件可能增加编译时间。从实现角度看每个分支都会在图构建层生成额外的控制流结构条件表达式与跳转语句分支越复杂、嵌套越深前端与后端 pass 需要处理的图节点越多。静态条件与动态条件的选择取决于条件值是否在编译期已知若条件来自普通 Python 变量/函数参数如add1_flag编译期即可确定分支在图构建阶段被直接选择代码生成路径更简单高效若条件来自符号循环索引或张量形状等运行期值如idx 2则必须编译为运行期分支设备端在执行时逐次判断。边界 API 的使用约束is_loop_begin/is_loop_end必须接收pypto.loop生成的索引并在循环体内调用否则会抛出异常其语义与循环的start、stop、step参数绑定首迭代idx start末迭代idx step end。形状声明一致性pypto.Tensor()与pypto.Tensor([pypto.DYNAMIC, ...])声明方式在行为上等价动态轴均以运行期形状为准但显式DYNAMIC声明更利于表达意图也便于编译器预知哪些维度需要动态处理。延伸阅读示例完整源码examples/02_intermediate/controlflow/condition/condition.py控制流示例总览examples/02_intermediate/controlflow/README_en.md循环与展开专题examples/02_intermediate/controlflow/loop/README_en.md动态形状与内核输入专题examples/02_intermediate/controlflow/others/README_en.md核心 API 实现python/pypto/_controller.py、python/pypto/pil/ops.py【免费下载链接】pyptoPyPTO发音: pai p-t-oParallel Tensor/Tile Operation编程范式。项目地址: https://gitcode.com/cann/pypto创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价