Merged
feat(dsl): compile class-organized kernels #1292
yuantaoAdd on Sep 17
feat(dsl): compile class-organized kernels #1292
Merged
Pull Request has been successfully merged by CANN-robot.
(Thank you for yuantao's contribution.)Sep 17 Associated issue: [Requirement|需求建议]: CATLASS DSL 支持以类实例组织 Kernel
atomgit-bot
Sep 17 Comments:
Sep 17 Comments:
变更摘要
本 PR 支持将 CATLASS DSL kernel 组织为类实例来编译:编译期配置(tile size、stage 数、dtype 等)作为实例属性与 kernel 及其编译入口放在一起,使 tla.compile 可以直接接收声明了 @tla.kernel 方法的类实例。kernel 方法的第一个 self 参数被识别为编译期 receiver,不占运行时 ABI 槽位,self.attr 读取在 body 编排(staging)时折叠为常量;tla.compile 按「@tla.jit __call__ 单行转发指向的方法 → 名为 kernel 的方法 → 类中唯一的 @tla.kernel 方法」的优先级选取入口,并同步调整启动签名以对齐编译计划。
主要改动
- 类实例编译入口:在
compiler.py中新增_resolve_kernel_target、_instance_kernel_target、_pure_forward_target等逻辑,CompileCallable支持接收声明了@tla.kernel方法的类实例,将实例作为首个样本拼入type_args,并对无 kernel 方法、入口歧义、转发参数数量不符等情况给出可操作的报错信息。 - 编译期 receiver 识别与常量折叠:在
execution_lowering.py中新增_method_receiver_name(依据__qualname__判定类成员,避免把普通函数中恰好名为self的参数误判为 receiver),LoweredTlaIR新增receiver_name字段;receiver 被加入constexpr_names且不生成运行时 ABI 叶子,使self.attr(含嵌套普通属性对象)在编排时折叠为常量。 - 启动签名对齐:在
execution.py中新增_launch_signature,编译时从 kernel 签名中剔除 receiver 参数,保证启动参数与编译后的计划一致。 - 装饰器顺序诊断:在
dsl.py中新增_reject_descriptor_wrapped_dsl_function,并由core.py在装饰期调用,当@tla.kernel/@tla.jit包裹staticmethod/classmethod描述符时立即提示正确的装饰器顺序。 - 测试与文档:新增
tests/test_kernel_class_organization.py(16 个用例,覆盖入口选择、转发校验、receiver 无 ABI 槽位、self.attr常量折叠、装饰器顺序诊断、按配置区分的缓存 key 等),并同步更新了相关测试桩与中英文 API 参考文档。


Incorrect?
atomgit-bot
Sep 17 Comments:
Sep 17 Comments:
Sep 17 Added label: cann-cla/yes
252 messages are collapsed here。 See more
26 days ago Added label: approved
CheaterAbec
26 days ago Comments:
26 days ago Comments:
/lgtm


26 days ago Added label: lgtm
26 days ago Merged pull request
CANN-robot
26 days ago Comments:
26 days ago Comments:
Pull Request 已合并或已关闭。
If you want to solve this problem, you can click here to do it in the FAQs.


描述
让
tla.compile接受「以类实例组织的 kernel」:编译期配置、kernel 与编译入口封装在同一个类里,实例交给tla.compile时,实例作为编译期接收者参与编排,self.attr折叠为常量。class VectorAdd: def __init__(self, vector_ele: int) -> None: self.vector_ele = vector_ele @tla.jit def __call__(self, lhs, rhs, output): self.kernel(lhs, rhs, output) @tla.kernel def kernel(self, lhs, rhs, output): ... # self.vector_ele 作为编译期常量 opts = {"options": "--npu-arch 3510"} # 1. 编译实例:靠 @tla.jit __call__ 的一行纯转发指出要编译的 kernel a = tla.compile(VectorAdd(128), lhs, rhs, output, **opts) # 2. 编译从实例读到的绑定方法:实例随属性读取一并作为 receiver b = tla.compile(VectorAdd(128).kernel, lhs, rhs, output, **opts) # 3. 换一份配置:编译期常量不同,得到新的产物 d = tla.compile(VectorAdd(64), lhs, rhs, output, **opts) a(lhs, rhs, output, block_num=1)写法 1 与 2 编译出同一份产物,
cache_key相同;启动签名都不含接收者:tla.compile(实例, ...)@tla.jit __call__一行转发指向的 kernel 方法lhs, rhs, outputtla.compile(实例.kernel, ...)lhs, rhs, output换一份编译期配置(写法 3)得到新的
cache_key:以新增样例为例,n_ele=64为74035fa27c9e26e8,n_ele=384为fb2175bc67b0adb9。改动涉及的文件:
catlass/base_dsl/compiler.py_resolve_kernel_target、@tla.jit __call__纯转发校验_check_jit_call_is_pure_forwardingcatlass/dsl.pyTlaJitFunction增加receiver字段与__get__,从实例取值时绑定实例catlass/base_dsl/runtime/jit_arg_adapters.pyreceiver_argument_name:按位置与名字判定首位接收者catlass/base_dsl/jit_executor.pyExecutionArgs.filter_runtime_signature复用同一套静态参数规则,让启动签名与编译计划对齐catlass/execution_lowering.pyself.attr折叠为常量编译入口的确定规则:
@tla.jit __call____call__一行转发指向的方法,方法名不限实例.kernel@tla.jit __call__@tla.jit __call__;不做「按方法名kernel」等回落@tla.kernel函数、也不是声明了它的类实例接收者规则:
self时视为编译期接收者:不占运行时 ABI 槽位,self.attr在 kernel 编排时折叠为常量receiver_argument_name以「无宿主」(owning_func=None)调用判定规则,而cls需要classmethod宿主才成立,因此这条路径实际只把self认作接收者;首位cls不会成为接收者实例.kernel)已经带上 receiver,不要再把实例作为实参传一次:tla.compile会在拼接前检测到重复传入并明确报错对既有行为的影响:首位
self不再进入运行时 ABI,仓库内两处按旧语义书写的代码随之调整。examples/end_to_end/structured_arguments/static_tensor_interfaces.pycopy_from_self改名copy_from_tensor,首位参数改名input_tensortests/test_execution_args.pyself不再出现在启动签名;cls在没有 classmethod 归属时仍是运行时参数检视意见处理(CheaterAbec)
@tla.jit __call__是否一行纯转发,与 Host JIT 全路径无关_check_jit_call_is_pure_forwardingkernel」「类内唯一@tla.kernel方法」两条回落,代码里没有@tla.jit __call__直接报错,不做回落receiver_argument_name的cls分支是死路径(判定时固定传owning_func=None)self实例.kernel已带 receiver,再显式传实例会重复传入tla.compile在拼接前对重复 receiver 抛错,并加用例test_bound_method_rejects_a_duplicated_receiver@tla.kernel作用于staticmethod/classmethod会在装饰期报错dsl.py只新增receiver字段与__get__,测试也无对应用例),描述已删除该条关联的Issue
https://gitcode.com/cann/catlass/issues/559
原因
issue #559 要求 CATLASS DSL 支持以类实例组织 Kernel,让分块大小、stage 数、dtype 这类编译期配置与 kernel 一起封装,而不是散在 host 侧的调用点。
改动前存在两个障碍:
TlaUnsupportedAbiError: tla.compile expects a @tla.kernel function, or an instance of a class that declares one.tla.compile只接受@tla.kernel的返回值self被算作运行时参数有意不支持的写法:host 侧没有对应的 pass,因此
@tla.jit __call__只能是到 kernel 的纯转发——单条语句、按序传全部参数、不用关键字、不计算实参。其他写法都会报错,这是本次变更的边界;实例若没有@tla.jit __call__,也直接报错,不做「按方法名或唯一性猜入口」的回落。测试
单元测试(
python/tla_dsl,Python 3.11 venv,python -m pytest tests/):tests/frontend/test_kernel_class_organization.py,27 个用例:实例与绑定方法两种写法产物等价(cache_key相同、启动签名一致)、入口选择(转发指向的方法名不限)、转发形态校验与报错文案、无@tla.jit __call__的实例被拒、接收者不占 ABI 槽位、self.attr(含嵌套普通对象属性)折叠为常量、首位self在类外也同样是接收者、运行时取值误当接收者被拒、按配置区分的 cache keytests/frontend/test_kernel_class_organization.py+tests/test_execution_args.py共83 passed2471 passed, 7 skipped;另有13 failed全部落在tests/test_tla_lower_to_hivm.py,是本机TlaCompile二进制未随分支重建(缺tla-vector-region-simdpass)所致,与本改动无关ruff check --select E4,E7,E9,F --line-length 120与format通过;新增测试文件里_BareNameCallKernel.__call__的裸名调用是刻意用例,已按仓内既有做法在pyproject.toml补per-file-ignores端到端(Ascend 950,
--npu-arch 3510):类版本 vector-add 编译并启动,结果逐元素精确一致,n_ele=64与n_ele=384均passed=True,cache_key分别为74035fa27c9e26e8、fb2175bc67b0adb9。内存 / 性能 / 确定性检测不涉及。新增样例:
examples/end_to_end/kernel_in_class/vector_add_with_static_length.py,类版本 vector-add,编译期配置、kernel 与编译入口封装在同一个类里。文档更新
docs/zh/kernel_development/core_concepts/compile_and_launch.mddocs/zh/api/host_api_reference.mdtla.compile参数与约束同步docs/en/api/host_api_reference.mdexamples/end_to_end/kernel_in_class/README.md类型标签
Description
Let
tla.compileaccept a kernel organized as a class instance: compile-time configuration, the kernel and its compile entry live in one class, and the instance joins compilation as a compile-time receiver, soself.attrfolds into constants.class VectorAdd: def __init__(self, vector_ele: int) -> None: self.vector_ele = vector_ele @tla.jit def __call__(self, lhs, rhs, output): self.kernel(lhs, rhs, output) @tla.kernel def kernel(self, lhs, rhs, output): ... # self.vector_ele as a compile-time constant opts = {"options": "--npu-arch 3510"} # 1. the instance: the one-line @tla.jit __call__ forward names the kernel a = tla.compile(VectorAdd(128), lhs, rhs, output, **opts) # 2. the bound method read off an instance: the instance rides along as the receiver b = tla.compile(VectorAdd(128).kernel, lhs, rhs, output, **opts) # 3. another configuration: different constants, a different binary d = tla.compile(VectorAdd(64), lhs, rhs, output, **opts) a(lhs, rhs, output, block_num=1)Forms 1 and 2 compile to the same artifact and share a
cache_key; neither launch signature carries the receiver:tla.compile(instance, ...)@tla.jit __call__forwards tolhs, rhs, outputtla.compile(instance.kernel, ...)lhs, rhs, outputAnother compile-time configuration (form 3) gets a new
cache_key: in the new sample,n_ele=64gives74035fa27c9e26e8andn_ele=384givesfb2175bc67b0adb9.Files touched:
catlass/base_dsl/compiler.py_resolve_kernel_target, the@tla.jit __call__pure-forwarding check_check_jit_call_is_pure_forwardingcatlass/dsl.pyTlaJitFunctiongains areceiverfield and__get__, binding the instance when read off itcatlass/base_dsl/runtime/jit_arg_adapters.pyreceiver_argument_name: a leading receiver is decided by position and namecatlass/base_dsl/jit_executor.pyExecutionArgs.filter_runtime_signaturereuses the same static-parameter rule so the launch signature lines up with the compiled plancatlass/execution_lowering.pyself.attrfolds into constantsHow the entry point is picked:
@tla.jit __call__instance.kernel)@tla.jit __call__@tla.jit __call__; no "method namedkernel" fallback@tla.kernelfunction nor an instance of a class declaring oneReceiver rule:
selfis a compile-time receiver: it takes no runtime ABI slot, andself.attrfolds into the kernel as a constant while the body is stagedreceiver_argument_namecalls the rule with no owner (owning_func=None), andclsonly qualifies under aclassmethodowner, so on this path onlyselfis ever taken as the receiver; a leadingclsis notinstance.kernel) already carries the receiver; passing the instance again is a duplicate thattla.compilerejects with a clear messageImpact on existing behaviour: a leading
selfno longer reaches the runtime ABI, so two places in the tree that relied on the old reading are adjusted.examples/end_to_end/structured_arguments/static_tensor_interfaces.pycopy_from_selfbecomescopy_from_tensor, its leading parameter becomesinput_tensortests/test_execution_args.pyselfleaves the launch signature;clsstays a runtime parameter when no classmethod owns itReview follow-up (CheaterAbec)
@tla.jit __call__is a one-line forward, unrelated to the full Host JIT path_check_jit_call_is_pure_forwardingkernel", "the class's only@tla.kernelmethod") that the code does not have@tla.jit __call__is rejected outright, with no fallbackreceiver_argument_name'sclsbranch is dead (the rule is called withowning_func=None)selfinstance.kernelalready carries the receiver, so passing the instance again would duplicate ittla.compilerejects the duplicate receiver before prepending, with a dedicated test@tla.kerneloverstaticmethod/classmethodis diagnosed at decoration timedsl.pyonly adds thereceiverfield and__get__, and no test covers it), so the claim is removedRelated Issue
https://gitcode.com/cann/catlass/issues/559
Reason
Issue #559 asks CATLASS DSL to support organizing a kernel as a class instance, so that compile-time configuration such as tile size, stage count and dtype travels with the kernel instead of being spread across host call sites.
Two things blocked it:
TlaUnsupportedAbiError: tla.compile expects a @tla.kernel function, or an instance of a class that declares one.when passing an instancetla.compileonly accepted the object returned by@tla.kernelselfwas counted as a runtime parameterDeliberately unsupported: there is no host pass for it, so a
@tla.jit __call__has to be a plain forward to the kernel — one statement, every parameter in order, no keywords, no computed arguments. Any other shape is rejected; that is the boundary of this change. An instance whose class has no@tla.jit __call__is rejected too, with no "guess the entry from the method name or uniqueness" fallback.Test
Unit tests (
python/tla_dsl, Python 3.11 venv,python -m pytest tests/):tests/frontend/test_kernel_class_organization.py, 27 cases: the instance and bound-method forms agreeing (samecache_key, same launch signature), entry selection (the forwarded method's name is free), forward-shape validation and its messages, an instance without@tla.jit __call__being rejected, the receiver taking no ABI slot,self.attr(including a nested plain attribute object) folding to constants, a leadingselfbeing a receiver outside a class too, a runtime receiver being rejected, per-configuration cache keystests/frontend/test_kernel_class_organization.py+tests/test_execution_args.py,83 passed2471 passed, 7 skipped; the13 failedall sit intests/test_tla_lower_to_hivm.pyand come from a locally builtTlaCompilethat predates the branch (missing thetla-vector-region-simdpass), unrelated to this changeruff check --select E4,E7,E9,F --line-length 120andformatpass under the CI setup; the deliberate bare-name call in the new test file is covered by aper-file-ignoresentry, following the repo's existing practiceEnd-to-end (Ascend 950,
--npu-arch 3510): the class-organized vector add compiles and launches with element-wise exact results;n_ele=64andn_ele=384both reportpassed=True, withcache_key74035fa27c9e26e8andfb2175bc67b0adb9. Memory, performance and determinism checks are not applicable.New sample:
examples/end_to_end/kernel_in_class/vector_add_with_static_length.py, the class version of the vector add with compile-time configuration, kernel and compile entry in one class.Documentation
docs/zh/kernel_development/core_concepts/compile_and_launch.mddocs/zh/api/host_api_reference.mdtla.compilearguments and constraintsdocs/en/api/host_api_reference.mdexamples/end_to_end/kernel_in_class/README.mdType