ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

PyPTO 动态形状内核调试指南:pypto.loop 符号边界、symbolic indexing 与 pypto.view 维度匹配实战

2026/9/19 4:14:24 拓冰建站 浏览量
PyPTO 动态形状内核调试指南:pypto.loop 符号边界、symbolic indexing 与 pypto.view 维度匹配实战 PyPTO 动态形状内核调试指南pypto.loop 符号边界、symbolic indexing 与 pypto.view 维度匹配实战【免费下载链接】pypto-gymPyPTO-Gym 是基于 PyPTO 编程框架构建的算子与模型样例仓库项目地址: https://gitcode.com/cann/pypto-gym本文是 PyPTO-Gym 仓库中pypto-general-debug技能体系下动态形状Dynamic Shapes专题的完整技术指南源自 cannbot-skills/ops/pypto-general-debug/references/dynamic-shapes.md并由仓库内 GDRChunked Gated Delta Rule生产内核源码与配套文档交叉印证而成。读完本文你将掌握在pypto.Tensor([pypto.DYNAMIC, ...])动态维度下如何规避Not concrete value、F21004、F00002等编译期错误正确使用pypto.loop、pypto.view、pypto.set_vec_tile_shapes并能处理pypto.is_loop_begin/pypto.is_loop_end跨函数边界失效的问题。说明本文是 Agent 在 GDR 内核开发过程中沉淀的实战模式总结所有错误码与示例均来自真实开发记录仓库中对应的生产实现可作为可运行的参考样板。背景PyPTO 的动态形状语义PyPTO 允许在pypto.frontend.jit内核签名中声明动态维度x: pypto.Tensor([pypto.DYNAMIC, pypto.DYNAMIC], pypto.DT_FP32)此时x.shape解包出的B, T x.shape是符号标量SymbolicScalar它们的取值只有在运行时才确定。PyPTO 的编译器需要在编译期对很多结构tile shape、loop bounds、view 中除 offsets 之外的形状参数求值因此凡是编译期必须具体的地方传入符号值都会直接报错。仓库中的生产内核大量使用了这一模式例如 chunked_gated_delta_rule_impl.py 中同时声明了动态维度和静态维度pypto.frontend.jit(**jit_kwargs) def kernel( query: pypto.Tensor([DYNAMIC_T, nqk, d], pypto.DT_FP32), key: pypto.Tensor([DYNAMIC_T, nqk, d], pypto.DT_FP32), value: pypto.Tensor([DYNAMIC_T, nv, d], pypto.DT_FP32), states: pypto.Tensor([DYNAMIC_B, nv, d, d], pypto.DT_FP32), mask: pypto.Tensor([l, l], pypto.DT_FP32), # l 为模块级常量 ... ): _, nqk, d query.shape b states.shape[0] l, l mask.shape其中DYNAMIC_T pypto.DYNAMIC是动态序列长度nqk/nv/d/l全部是编译期已知的具体整数由工厂函数参数传入。这正是下文所有正确姿势的底层逻辑能静态化的就静态化必须动态的只让符号参与允许符号的 API如pypto.view的 offsets 与 valid_shape。Issue 1set_vec_tile_shapes要求具体数值症状当使用符号维度时编译器抛出ValueError: Not concrete value根因pypto.set_vec_tile_shapes用于设置向量算子的 tile 尺寸编译器需要在编译期据此完成 tile 规划与资源分配无法接受符号值。错误写法与正确写法对比# WRONG pypto.frontend.jit() def kernel(x: pypto.Tensor([pypto.DYNAMIC, pypto.DYNAMIC], pypto.DT_FP32)): B, T x.shape pypto.set_vec_tile_shapes(B, T, 32, 32) # FAILS - B, T are symbolic # CORRECT - use concrete constants TILE_M, TILE_N 32, 32 pypto.frontend.jit() def kernel(x: pypto.Tensor([pypto.DYNAMIC, pypto.DYNAMIC], pypto.DT_FP32)): pypto.set_vec_tile_shapes(TILE_M, TILE_N, 32, 32) # WORKS仓库佐证在 chunked_gated_delta_rule_impl.py 中l2norm_scaled函数内部直接使用字面量def l2norm_scaled(query, key, eps1e-6, d128): pypto.set_vec_tile_shapes(128, 128) ...pre_attn、inverse_pto、cal_value_and_key_cumdecay等辅助函数也统一以pypto.set_vec_tile_shapes(128, 128)开头且所有调用点传入的都是模块级常量或工厂参数。即使在动态批大小/动态序列的循环内部tile shape 也始终保持静态——这是 GDR 内核能编译通过的前提之一。实战建议将 tile 尺寸提升为模块级常量如TILE_M, TILE_N 32, 32或工厂函数参数需要按计算阶段切换 tile 时在循环体内用字面量调用GDR 内核在 S 循环内多次pypto.set_vec_tile_shapes(16, 16, 128, 128)、pypto.set_vec_tile_shapes(128, 128, 128)切换见 chunked_gated_delta_rule_impl.py。Issue 2pypto.loop不允许符号边界症状ValueError: Invalid value type Errcode: F21004! op [MUL]tile shape not set根因pypto.loop要求 start/stop/step 全部是具体整数。凡是来自 tensor shape 的符号表达式如B * H、T // bt都会导致编译器无法在编译期确定循环边界。# WRONG - B and H are symbolic from tensor shape for session in pypto.loop(range(B * H), namesessions): for i in pypto.loop(range(nt), namechunks): # nt T // bt is symbolic注意这里的range(...)包裹同样无济于事——问题不在range而在B * H与nt本身是符号量。解决方案将循环边界作为具体整数参数传入内核。调用侧负责在运行时计算这些值此时它们是真实的 Python int。# Kernel signature: pass B, H, nt as concrete parameters pypto.frontend.jit(runtime_options{run_mode: pypto.RunMode.NPU}) def kernel( q_in: pypto.Tensor([], pypto.DT_FP32), ... B: int, H: int, nt: int, # Concrete loop bounds ): for session in pypto.loop(0, B * H, 1, namesessions): for c in pypto.loop(0, nt, 1, namechunks): ... # Caller: pass concrete values kernel(q, k, ..., B, H, nt)要点一旦循环边界具体化循环体内由索引派生的量如b session // H、h session % H仍然是符号标量但它们是合法的——可以安全用于pypto.view的 offsets详见 Issue 3/4。仓库佐证GDR 内核把b、nv、l等循环上界全部做成工厂函数参数调用侧用运行时真实值填充循环内部再通过索引运算派生出符号偏移量for b_idx in pypto.loop(b, nameLOOP_B_TND, idx_nameb_idx): s act_seq_len[b_idx 1] - act_seq_len[b_idx] # 符号量来自动态张量 b_ofs act_seq_len[b_idx] for nv_idx in pypto.loop(nv, nameLOOP_Nv_TND, idx_namenv_idx, parallelFalse): ... for s_idx in pypto.loop(0, s, l, nameLOOP_S_TND, idx_names_idx, unroll_list[16, 1]): bs_ofs b_ofs s_idx actual_l (s - s_idx).min(l) # 符号量用于 valid_shape见 chunked_gated_delta_rule_impl.py。这里b、nv、l是具体整数来自工厂参数s、b_ofs、bs_ofs、actual_l是符号标量——两者各司其职互不越界。Issue 3tensor 直接索引不允许符号下标症状TypeError: Cannot convert symbols to int当代码中出现tensor[symbolic_index]时触发。PyPTO 的编译器把 Python 风格的方括号索引视作需要在编译期确定的取片无法接受符号下标。解决方案改用pypto.view承载符号偏移。# WRONG result tensor[idx, :, :] # idx is symbolic - FAILS # CORRECT - use view with offsets view pypto.view(tensor, [1, T, K], [idx, 0, 0]) result pypto.matmul(...) # operate on the view directlypypto.view是 PyPTO 中专门为动态形状设计的取片原语它的shape必须是具体整数静态切出的窗口大小而offsets允许SymbolicScalar决定窗口落在哪个位置这就把编译期确定形状、运行期确定位置的两类需求干净地分开了。仓库佐证GDR 内核中所有动态位置取片都通过pypto.view完成例如 chunked_gated_delta_rule_impl.pyquery_view pypto.view(query, [l, 1, d], [bs_ofs, nqk_idx, 0], valid_shape[actual_l, 1, d]) beta_view pypto.view(beta, [l, 1], [bs_ofs, nv_idx], valid_shape[actual_l, 1])bs_ofs、nqk_idx、nv_idx都是符号量而窗口形状[l, 1, d]中的l、d是编译期常量valid_shape则用符号量actual_l表达末尾 chunk 实际有效长度。Issue 4pypto.view的 shape 与 offsets 维度不匹配症状RuntimeError: Errcode: F21004! Their size actually are 4 and 2, func GetViewValidShape根因pypto.view的shape与offsets的元素个数必须相等。当传入的 shape 维度数与 offsets 维度数不一致时例如 shape 是 2 维、offsets 是 4 个元素编译器在校验 view 合法形状时直接报错。错误与正确写法# WRONG - shape has 2 dims, offsets has 4 elements pypto.view(tensor, [K, V], [b, h, 0, 0]) # CORRECT - use matching dimensions, then reshape pypto.view(tensor, [1, 1, K, V], [b, h, 0, 0]).reshape([K, V])铁律len(shape) len(offsets)恒成立。处理办法是用1把 shape 的维度补齐到与 offsets 一致再通过.reshape()收敛到目标形状。常见模式速查按 4D/3D/5D 场景归类# Tensor [B, T, H, K], view [bt, K] at [b, t0, h, 0] view pypto.view(tensor, [1, bt, 1, K], [b, t0, h, 0]).reshape([bt, K]) # Tensor [B, H, K, V], view [K, V] at [b, h, 0, 0] view pypto.view(tensor, [1, 1, K, V], [b, h, 0, 0]).reshape([K, V]) # Tensor [B, T, H], view [bt] at [b, t0, h] view pypto.view(tensor, [1, bt, 1], [b, t0, h]).reshape([bt]) # Tensor [B, H, nt, bt, bt], view [bt, bt] at [b, h, c, 0, 0] view pypto.view(tensor, [1, 1, 1, bt, bt], [b, h, c, 0, 0]).reshape([bt, bt])配套参考文档pypto-view.md 提供了完整的pypto.view权威参考包括签名pypto.view(input, shape, offsets, valid_shapeNone)其中shape必须是具体整数列表offsets允许Union[int, pypto.SymbolicScalar]valid_shape允许符号标量Golden Rulelen(shape) len(offsets)是强制约束常见错误速查表MistakeErrorFixShape dims ≠ offset dimsTheir size actually are X and YPad with 1sUsing[bt, K]with 4 offsetsDimension mismatchUse[1, bt, 1, K]Forgetting reshape after viewWrong shape in computationAdd.reshape([bt, K])配套 API写回时使用pypto.assemble(s.reshape([1, 1, K, V]), [b, h, 0, 0], output)且 assemble 同样要求 view 维度匹配5D view 与 4D vec tile shapes 的组合还可能触发Run pass failed详见同目录下的 matmul.md。Issue 5pypto.is_loop_begin/pypto.is_loop_end跨函数边界失效症状编译期报ValueError: Not concrete value错误码F00002且没有行号指示。典型场景pypto.frontend.jit入口调用了一个辅助函数如_op_kernel_impl该辅助函数体内含有pypto.is_loop_begin(idx)或pypto.is_loop_end通常用于累加器的首/末次迭代初始化。根因PyPTO 解析器无法跨函数边界解析pypto.is_loop_begin/pypto.is_loop_end。默认的Layer I 辅助函数被 Layer Jpypto.frontend.jit调用的函数拆分不足以让编译器识别这些循环控制原语于是它们变成了无法求值的符号量。两种解法# WRONG — compile-time F00002 with no source line def _my_kernel_impl(x, y): for idx in pypto.loop(N): if pypto.is_loop_begin(idx): ... pypto.frontend.jit(...) def my_kernel_npu(x, y): _my_kernel_impl(x, y) # OPTION 1 (PREFERRED) — inline body into JIT entry pypto.frontend.jit(...) def my_kernel_npu(x, y): for idx in pypto.loop(N): if pypto.is_loop_begin(idx): ... # OPTION 2 — decorate helper with pypto.frontend.function (tensor args only) pypto.frontend.function def _my_kernel_impl(x, y): for idx in pypto.loop(N): if pypto.is_loop_begin(idx): ... pypto.frontend.jit(...) def my_kernel_npu(x, y): _my_kernel_impl(x, y)Option 1推荐把循环体直接内联进 JIT 入口函数Option 2给辅助函数加pypto.frontend.function装饰器注意只支持 tensor 参数非 tensor 参数目前不支持。重要澄清避免误修SymbolicScalar出现在pypto.view的valid_shape参数中并不是本错误的成因——生产内核正是这样使用的例如actual_l (s - s_idx).min(l); pypto.view(..., valid_shape[actual_l, ...])。如果看到F00002不要试图去修valid_shape元凶是辅助函数提取。仓库佐证GDR 非对齐版本在 JIT 入口内部直接使用pypto.is_loop_end(s_idx)做分支未提取到辅助函数中见 chunked_gated_delta_rule_impl.pyif pypto.is_loop_end(s_idx): pad_q pypto.fillpad(query_view_2d, constant, 0.0) ... chunk_attn_out_reshaped chunk_attn_out.reshape( [l, 1, d], valid_shape[actual_l, 1, d]) pypto.assemble(chunk_attn_out_reshaped, [bs_ofs, nv_idx, 0], core_attn_out) else: ... core_attn_out[bs_ofs:bs_ofs l, nv_idx] chunk_attn_outis_loop_end分支用于处理序列长度不能被 chunk 整除时的尾部补齐与写回最后一个 chunk 先fillpad填充到满 chunk 参与计算再用valid_shapeassemble只把有效部分写回输出。文件头注释也记录了负面经验cumsum for gate_cum… fails to compile in unalignedis_loop_endbranching context印证了该分支的编译期约束。综合实战GDR 内核中的动态形状完整落地以上五个 Issue 的解法在仓库的 chunked_gated_delta_rule_impl.py 中被系统性组合使用是本文档最完整的生产级参考实现动态维度声明query/key/value用pypto.Tensor([DYNAMIC_T, nqk, d], pypto.DT_FP32)states用[DYNAMIC_B, nv, d, d]第 347-361 行具体循环边界b、nv、l全部来自工厂函数参数第 317-333 行供pypto.loop(b, ...)、pypto.loop(nv, ...)、pypto.loop(0, s, l, ...)使用第 370、374、379 行符号偏移 静态形状的 viewpypto.view(query, [l, 1, d], [bs_ofs, nqk_idx, 0], valid_shape[actual_l, 1, d])第 503-512 行静态 tile shape所有pypto.set_vec_tile_shapes(...)/pypto.set_cube_tile_shapes(...)调用点均为字面量如第 376、395、514、548 行循环控制原语内联pypto.is_loop_end(s_idx)直接写在 JIT 入口循环体内第 523 行。配套的精度验证测试 test_chunked_gated_delta_rule.py 覆盖了 8 个用例包括对齐/非对齐T128、T130、T512、T64、T32等与混合Nv4/8的 GQA 场景从aligned_gqa到aligned_single_chunk_L32是对本文所有模式可运行性的直接验证。调试路由从哪里找到这份指南在 PyPTO-Gym 的调试技能体系中这份文档被挂接为pypto-general-debug技能的叶子参考文件路由入口DEBUG_GUIDEBOOK.md 的§9.2小节将Dynamic shapes,pypto.loop, symbolic indexing, view dimension matching映射到本文档并在Quick map: situation → leaf file表中把Working with dynamic shapes /pypto.loop路由到 dynamic-shapes.md技能说明SKILL.md 定义了该技能的调试协议——遇到 tile-shape 类失败先通过 DEBUG_GUIDEBOOK 路由到对应叶子文档再逐条核对pypto调用点。速查五种错误的症状、根因与修复症状错误信息根因修复ValueError: Not concrete valuetile shapeset_vec_tile_shapes收到符号值用模块级常量或函数参数传具体整数Invalid value type / F21004 / op [MUL]tile shape not setpypto.loop边界含符号表达式循环边界改为内核的具体 int 参数TypeError: Cannot convert symbols to int用符号下标直接索引 tensor改用pypto.view 符号 offsetsF21004 / Their size actually are 4 and 2, func GetViewValidShapeshape与offsets维度数不等用1补齐 shape 维度len(shape) len(offsets)再 reshapeF00002 / Not concrete value无行号is_loop_begin/is_loop_end在辅助函数体内内联进 JIT 入口或辅助函数加pypto.frontend.function仅 tensor 参数总结PyPTO 动态形状编程的核心纪律可以浓缩为一句话编译期求值的东西必须具体运行期变化的东西必须走符号通道——tile shape、loop bounds、view 的 shape 属于前者view 的 offsets、valid_shape、循环索引派生的位置量属于后者。遵循pypto.loop具体边界、pypto.view维度对齐、is_loop_begin/is_loop_end留在 JIT 入口这三条规则配合仓库中 GDR 内核这一完整样板绝大多数动态形状编译错误都能一次定位、按图索骥解决。【免费下载链接】pypto-gymPyPTO-Gym 是基于 PyPTO 编程框架构建的算子与模型样例仓库项目地址: https://gitcode.com/cann/pypto-gym创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考