CANN pyasc:用 Python 写昇腾算子的 Hello World

文章目录
1. 前言
昇腾CANN(Compute Architecture for Neural Networks)是面向昇腾AI处理器的异构计算架构,覆盖从算子开发到推理部署的全栈能力。pyasc 是昇腾CANN开源社区推出的 Python 算子编程语言,让你用纯 Python 语法编写在昇腾 NPU(Ascend 910B/910C)上运行的高性能自定义算子。本文从一个 Add 算子的 Hello World 入手,完整走通 pyasc 的开发、编译、调试全流程,并深入拆解它的核心抽象与工程实践。
2. pyasc 的项目定位
为什么需要 Python 算子开发
在昇腾CANN生态中,传统算子开发走的是 Ascend C 路线——一种基于 C++ 标准的领域特定语言。Ascend C 提供多层级 API(高阶核函数接口、中阶并行计算接口、低阶指令级编程接口),性能极致、控制力强——但代价是学习曲线陡峭。
pyasc 要解决的问题很简单:把算子开发的入门门槛降下来。
pyasc 的定位是"Python 版本的 Ascend C":
- 接口一一对应:pyasc 的 Python API 与 Ascend C 类库接口保持一一映射,你在 Ascend C 中能用的核心抽象(GlobalTensor、LocalTensor、DataCopy、同步事件等),在 pyasc 中都有对应。
- Python 原生语法:用
@asc.jit装饰器标记核函数,用 Python 的控制流(for 循环、if 判断)组织计算逻辑。 - 一键集成 PyTorch:算子输入输出天然用
torch.Tensor,写好就能在 PyTorch 训练/推理链中直接调用。
与 Ascend C 的关系
这不是替代,是互补:
| 对比项 | Ascend C | pyasc |
|---|---|---|
| 编程语言 | C++(C++17 标准) | Python(CPython 3.9-3.12) |
| 编译流程 | CMake + 昇腾编译器 | JIT 编译(@asc.jit 装饰器) |
| 性能上限 | 极致(完全控制流水) | 接近 Ascend C(微管线差 ~5%) |
| 开发效率 | 慢,需编译-部署-验证循环 | 快,改完直接跑 |
| 适用人群 | 性能专家、硬件工程师 | Python 算法工程师、模型研究员 |
适用场景边界:
- 推荐用 pyasc:原型验证、快速自定义算子、小规模数据测试、教学实验。尤其是你的算子逻辑可以靠 Python 控制流表达时,pyasc 优势明显。
- 推荐用 Ascend C:生产级高性能算子、极致流水编排、与硬件特性深度绑定(如 L1 buffer 手动管理)。当你的算子在 pyasc 中 profiling 后发现 Cube 单元利用率低于 70%,就该考虑用 Ascend C 重写了。
3. Hello World 完整走通
坐稳了,我们从零开始走一遍。
3.1 环境准备
有两种方式:快速 pip 安装和源码编译安装。建议新手先用 pip 快速安装体验。
# 检查 NPU 设备是否在位
npu-smi info
# 确认 Python 版本(3.9-3.12)
python3 --version
# pip 安装 pyasc 最新稳定版
pip install pyasc
# 验证安装
pip3 list | grep -w "pyasc"
# 输出示例:pyasc x.x.x
如果你使用 Docker 环境(推荐用于生产),先拉取 CANN 官方镜像:
# 拉取预集成了 CANN 的 Docker 镜像
docker pull swr.cn-south-1.myhuaweicloud.com/ascendhub/cann:9.0.0-beta.2-910b-ubuntu22.04-py3.11
# 启动容器(注意映射 NPU 设备)
docker run --name pyasc-dev \
--ipc=host --net=host --privileged \
--device /dev/davinci0 \
--device /dev/davinci_manager \
--device /dev/devmm_svm \
--device /dev/hisi_hdc \
-v /usr/local/dcmi:/usr/local/dcmi \
-v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \
-v /usr/local/Ascend/driver/lib64/:/usr/local/Ascend/driver/lib64/ \
-v /usr/local/Ascend/driver/version.info:/usr/local/Ascend/driver/version.info \
-v /etc/ascend_install.info:/etc/ascend_install.info \
-v $(pwd):/workspace \
-it swr.cn-south-1.myhuaweicloud.com/ascendhub/cann:9.0.0-beta.2-910b-ubuntu22.04-py3.11 bash
注意:镜像的 tag 根据你实际的 CANN 版本调整。如果使用社区版 8.5.0.alpha001 及以上,对应的 pyasc v1.1.0/v1.1.1 都支持。
3.2 写第一个 Add 算子
以下代码基于 pyasc 仓库中的 python/tutorials/01_add/add.py 改写,手动插入同步流水实现最简单的向量加法:
# add_hello_world.py
import torch
import asc
import asc.runtime.config as config
import asc.lib.runtime as rt
USE_CORE_NUM = 8 # 用 8 个 NPU core 并行计算
BUFFER_NUM = 2 # 双缓冲乒乓
TILE_NUM = 8 # 每核分 8 个 Tile
@asc.jit
def vadd_kernel(x: asc.GlobalAddress, y: asc.GlobalAddress,
z: asc.GlobalAddress, block_length: int):
"""
向量加法核函数:z = x + y
参数:
x, y: 输入张量的 Global Memory 地址
z: 输出张量的 Global Memory 地址
block_length: 当前 core 需要处理的数据量
"""
offset = asc.get_block_idx() * block_length
# 在 Global Memory 上声明视图
x_gm = asc.GlobalTensor()
y_gm = asc.GlobalTensor()
z_gm = asc.GlobalTensor()
x_gm.set_global_buffer(x + offset, block_length)
y_gm.set_global_buffer(y + offset, block_length)
z_gm.set_global_buffer(z + offset, block_length)
tile_length = block_length // TILE_NUM // BUFFER_NUM
data_type = x.dtype
buffer_size = tile_length * BUFFER_NUM * data_type.sizeof()
# 在 Local Memory 上分配 buffer(VECIN / VECOUT 分别对应 Vector 单元的输入输出空间)
x_local = asc.LocalTensor(data_type, asc.TPosition.VECIN, 0, tile_length * BUFFER_NUM)
y_local = asc.LocalTensor(data_type, asc.TPosition.VECIN, buffer_size, tile_length * BUFFER_NUM)
z_local = asc.LocalTensor(data_type, asc.TPosition.VECOUT, buffer_size * 2, tile_length * BUFFER_NUM)
for i in range(TILE_NUM * BUFFER_NUM):
buf_id = i % BUFFER_NUM
# GM → LM:把数据加载到 Local Memory
asc.data_copy(x_local[buf_id * tile_length:], x_gm[i * tile_length:], tile_length)
asc.data_copy(y_local[buf_id * tile_length:], y_gm[i * tile_length:], tile_length)
# 等待数据搬入完成后再计算
asc.set_flag(asc.HardEvent.MTE2_V, buf_id)
asc.wait_flag(asc.HardEvent.MTE2_V, buf_id)
# 在 Vector 单元上执行加法
asc.add(z_local[buf_id * tile_length:],
x_local[buf_id * tile_length:],
y_local[buf_id * tile_length:],
tile_length)
# 等待计算完成后再搬出
asc.set_flag(asc.HardEvent.V_MTE3, buf_id)
asc.wait_flag(asc.HardEvent.V_MTE3, buf_id)
# LM → GM:把结果写回 Global Memory
asc.data_copy(z_gm[i * tile_length:], z_local[buf_id * tile_length:], tile_length)
# 等待搬出完成再进行下一轮
asc.set_flag(asc.HardEvent.MTE3_MTE2, buf_id)
asc.wait_flag(asc.HardEvent.MTE3_MTE2, buf_id)
def vadd_launch(x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
"""封装 launch 逻辑,接受 torch.Tensor 返回 torch.Tensor"""
z = torch.zeros_like(x)
total_length = z.numel()
block_length = total_length // USE_CORE_NUM
# 关键:通过 [USE_CORE_NUM, stream] 语法启动多核并行
vadd_kernel[USE_CORE_NUM, rt.current_stream()](x, y, z, block_length)
return z
def main():
# 配置后端:Model 为仿真模式,NPU 为上板模式
config.set_platform(config.Backend.Model, config.Platform.Ascend910B1)
size = 8 * 2048 # 16384 个 float32 元素
x = torch.rand(size, dtype=torch.float32)
y = torch.rand(size, dtype=torch.float32)
z = vadd_launch(x, y)
# 验证:与标准加法对比
assert torch.allclose(z, x + y, rtol=1e-5, atol=1e-5), "结果误差超限"
print("[PASS] Add Hello World 验证通过!")
print(f" x[0] = {x[0]:.6f}, y[0] = {y[0]:.6f}")
print(f" z[0] = {z[0]:.6f}, x+y = {(x[0]+y[0]):.6f}")
if __name__ == "__main__":
main()
3.3 编译与运行
pyasc 天然支持两种运行模式:
| 运行模式 | 参数值 | 说明 |
|---|---|---|
| 仿真模式 | Model |
不用真 NPU,走仿真器验证逻辑,适合开发调试 |
| NPU 上板 | NPU |
跑在真 NPU 上,需要驱动和 torch_npu |
# 仿真模式跑(推荐第一次跑)
python3 add_hello_world.py -r Model -v Ascend910B1
# 有真 NPU 时切到上板模式
python3 add_hello_world.py -r NPU -v Ascend910B1
输出应该类似:
[INFO] start process sample add.
[PASS] Add Hello World 验证通过!
x[0] = 0.123456, y[0] = 0.789012
z[0] = 0.912468, x+y = 0.912468
[INFO] Sample add run success.
踩坑提示:如果报
ModuleNotFoundError: No module named 'torch_npu',说明需要安装 PyTorch 和 torch_npu 插件。pyasc 仓库提供一键脚本:cd pyasc bash torch_npu_install.sh # 默认装 PyTorch 2.7.1 + torch_npu 7.3.0仿真模式不需要 torch_npu,直接
pip install torch(CPU 版即可)。
4. pyasc 算子开发的核心抽象
看懂 Hello World 后,我们来拆解 pyasc 中几个核心抽象。
4.1 Tensor 描述(GlobalTensor / LocalTensor)
pyasc 把 NPU 上的物理存储抽象为两种 Tensor:
- GlobalTensor:对应 Global Memory(DDR/HBM),容量大、访问速度慢(约 500 GB/s)。算子输入数据从 Host 侧搬到 Global Memory,输出数据也从这里读回。
- LocalTensor:对应 Local Memory(SRAM / L1 buffer),容量小(每核几十到几百 KB)、访问速度快(>5 TB/s)。算子的计算过程全部在 Local Memory 上完成。
# GlobalTensor 声明 —— 绑定到 Global Memory 中的一段连续地址
x_gm = asc.GlobalTensor()
x_gm.set_global_buffer(addr, length) # addr: 起始地址, length: 元素个数
# LocalTensor 声明 —— 在 Local Memory 的某个逻辑位置分配
x_local = asc.LocalTensor(
data_type, # 数据类型(如 asc.float32)
position, # 逻辑位置(VECIN / VECOUT / COBEOUT 等)
offset, # 字节偏移量
length # 元素个数
)
技术要点分析:LocalTensor 的 position 参数直接映射到 NPU 达芬奇架构的物理流水线。VECIN 对应 Vector 单元的输入缓冲区,VECOUT 对应输出缓冲区。放错位置会导致运行时崩溃——这是一个非常常见的陷阱,我们后面会专门讲。
4.2 BlockDim 配置(多核并行)
pyasc 通过 @asc.jit 装饰器下的 [N, stream] 语法实现多核并行:
# 用 8 个 AI Core 并行执行
vadd_kernel[8, rt.current_stream()](x, y, z, block_length)
NPU 上每个 AI Core 通过 asc.get_block_idx() 拿到自己的编号,然后各自处理数据的一分片。8 个 Core 并行工作,理论上是 8× 加速。
这里的 block_length 就是每个 Core 需要处理的元素数 = total_elements // block_dim。pyasc 不会自动做数据切分——你需要在核函数里自己算 offset 和 length。
4.3 Buffer 管理(双缓冲乒乓)
双缓冲(Double Buffering)是 NPU 算子调优的经典套路:用两个 buffer 交替,在一个 buffer 计算时另一个 buffer 预加载下一批数据,掩盖 GM→LM 的数据搬运延迟。
BUFFER_NUM = 2
for i in range(TILE_NUM * BUFFER_NUM):
buf_id = i % BUFFER_NUM # 0 → 1 → 0 → 1 → ...
# buf_id=0 时加载数据,buf_id=1 时在上一次加载的数据上计算
asc.data_copy(x_local[buf_id * tile_length:], x_gm[i * tile_length:], tile_length)
asc.data_copy(y_local[buf_id * tile_length:], y_gm[i * tile_length:], tile_length)
# ...
asc.add(z_local[buf_id * tile_length:], ...)
buf_id 在 0 和 1 之间交替。在第一次迭代的双缓冲启动后,计算和搬运就同时进行了。
4.4 同步语义的 Python API 映射
pyasc 通过 asc.set_flag 和 asc.wait_flag 实现跨流水线的硬件同步事件。对应 Ascend C 中的 SetFlag 和 WaitFlag:
# MTE2 → V:数据搬运完成,可以开始计算
asc.set_flag(asc.HardEvent.MTE2_V, buf_id)
asc.wait_flag(asc.HardEvent.MTE2_V, buf_id)
# V → MTE3:计算完成,可以搬出
asc.set_flag(asc.HardEvent.V_MTE3, buf_id)
asc.wait_flag(asc.HardEvent.V_MTE3, buf_id)
# MTE3 → MTE2:搬出完成,可以开始下一轮搬运
asc.set_flag(asc.HardEvent.MTE3_MTE2, buf_id)
asc.wait_flag(asc.HardEvent.MTE3_MTE2, buf_id)
pyasc 也提供了 Ascend C 框架封装的同步版本,见 02_add_framework 样例,框架会自动插入 pipeline 同步事件,代码更简洁但灵活性稍低。
5. Python 算子与 Ascend C 算子的性能对比
跳出 Hello World,我们来认真聊聊性能。
5.1 启动开销
# perf_compare.py —— pyasc vs Ascend C 启动开销对比
import time
import torch
import asc
import asc.runtime.config as config
import asc.lib.runtime as rt
USE_CORE_NUM = 8
@asc.jit
def nop_kernel(x: asc.GlobalAddress, n: int):
"""空内核 —— 只测试启动+调度开销"""
pass
def measure_pyasc_launch_overhead(num_runs=1000):
config.set_platform(config.Backend.Model, config.Platform.Ascend910B1)
x = torch.rand(1024, dtype=torch.float32)
# 预热:pyasc JIT 首次调用会触发编译
nop_kernel[USE_CORE_NUM, rt.current_stream()](x, 1024)
stream = rt.current_stream()
start = time.perf_counter()
for _ in range(num_runs):
nop_kernel[USE_CORE_NUM, stream](x, 1024)
end = time.perf_counter()
avg_ms = (end - start) / num_runs * 1000
print(f"[pyasc 空内核] {num_runs} 次平均启动延迟: {avg_ms:.3f} ms")
return avg_ms
if __name__ == "__main__":
measure_pyasc_launch_overhead()
Ascend C 的启动延迟通常在 5-20 微秒 量级(编译后静态二进制,走 Runtime 直调)。pyasc 因为有 JIT 路径和 Python 调用开销,首次调用后缓存代码,后续启动延迟约 50-200 微秒。这意味着:
- 小算子(数据处理量 < 1MB):pyasc 的启动开销占比明显,不适合高频小算子调用。
- 大算子(处理量 > 10MB):计算时间远大于启动开销,pyasc 的延迟影响可以忽略。
5.2 内存占用
pyasc 在 Python 侧需要保持 asc.GlobalAddress 对象和 asc.LocalTensor 的元信息(类型、形状、位置),每声明一个 Tensor 大约多占用几十个 Python 对象。如果你的算子只有 3-5 个 Tensor,这部分占用可以忽略。
5.3 适用算子复杂度边界
| 算子复杂度级别 | 示例 | pyasc 推荐度 | 原因 |
|---|---|---|---|
| 简单 Element-wise | Add, Mul, Relu | ⭐⭐⭐⭐⭐ | Python 控制流完全够用 |
| 规约操作 | ReduceSum, Softmax | ⭐⭐⭐⭐ | 注意流水编排即可 |
| 矩阵乘法 | MatMul, Conv | ⭐⭐⭐ | 对 Tile 和 Buffer 策略要求高 |
| 融合算子 | MatMul + Bias + Relu | ⭐⭐⭐ | 同步事件多,需小心处理 |
| 极致优化 | FlashAttention | ⭐⭐ | 建议用 Ascend C |
技术要点分析:当你的算子中 Cube 单元(矩阵乘)与 Vector 单元(激活/规约)交替执行时,pyasc 的流水同步可以正常处理。但如果你的算子需要手动管理 L1 A/B 缓冲区的地址对齐和 Bank Conflict,pyasc 的抽象层反而会成为障碍。此时切回 Ascend C 的底层 API 是唯一选择。
6. pyasc 的调试技巧
6.1 Python 侧断点
因为 pyasc 的核函数 @asc.jit 会被 JIT 编译成 NPU 指令,print() 和 pdb 在核函数内部不会执行。但核函数外面的 Python 代码(vadd_launch、main)完全可以打断点:
def vadd_launch(x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
z = torch.zeros_like(x)
total_length = z.numel()
block_length = total_length // USE_CORE_NUM
print(f"[debug] total_length={total_length}, block_length={block_length}")
print(f"[debug] x shape={x.shape}, y shape={y.shape}")
# 在这里打 pdb 断点完全支持
# import pdb; pdb.set_trace()
vadd_kernel[USE_CORE_NUM, rt.current_stream()](x, y, z, block_length)
return z
6.2 NPU 侧 printf 重定向
在 pyasc 中,你可以用 Ascend C 风格的写内存方式来"打印"NPU 侧的中间值——但不是真正的 printf。推荐的做法是:在核函数中把中间结果写到一块专门开的 Debug 缓冲区,跑完再读到 Host 侧分析。
@asc.jit
def debug_kernel(x: asc.GlobalAddress, debug_out: asc.GlobalAddress, n: int):
"""
debug_out:一块额外分配的 Global Memory,用来回传中间值
"""
offset = asc.get_block_idx() * (n // 8)
x_gm = asc.GlobalTensor()
x_gm.set_global_buffer(x + offset, n // 8)
dbg = asc.GlobalTensor()
dbg.set_global_buffer(debug_out + offset, n // 8)
tile_len = (n // 8) // 4
x_local = asc.LocalTensor(asc.float32, asc.TPosition.VECIN, 0, tile_len)
asc.data_copy(x_local, x_gm, tile_len)
asc.set_flag(asc.HardEvent.MTE2_V, 0)
asc.wait_flag(asc.HardEvent.MTE2_V, 0)
# 把中间值直接拷贝到 debug buffer
asc.data_copy(dbg, x_local, tile_len)
6.3 性能 Profiling
pyasc 支持通过 msprof 工具进行性能 Profiling。在算子运行前设置:
# 开启 msprof profiling
export MS_PROF_RUNMODE=1
export MS_PROF_CONFIG=msprof_config.json
# 运行算子
python3 add_hello_world.py -r NPU -v Ascend910B1
# 查看 profiling 结果
cat PROF_*/msprof_*.csv | head -20
msprof_config.json 示例:
{
"profiling_mode": "task_based",
"aic_metrics": "AiCoreMetrics",
"dvpp_metrics": "0"
}
7. 2 个关键陷阱
陷阱 1:误用 Ascend C 专有 API 导致运行时崩溃
这是 pyasc 新手踩得最多的坑。
症状:你在 pyasc 中直接调用 DataCopy(首字母大写)或 SetFlag 等 Ascend C 的 C++ 风格 API(因为看的是 Ascend C 文档写的代码),运行时直接 segfault 或报 “Symbol not found”。
原因:pyasc 的 API 全部是 Python 命名风格——asc.data_copy、asc.set_flag。Ascend C 中的 DataCopy() 是 C++ 函数,pyasc 内部走的是 Python 绑定的后端编译器路径,两套命名空间完全不兼容。
# ❌ 错误写法(Ascend C 风格)
DataCopy(x_local, x_gm, tile_length); # 运行时崩溃
# ✅ 正确写法(pyasc 风格)
asc.data_copy(x_local, x_gm, tile_length)
解决方案:写 pyasc 代码时,始终参考 pyasc 的 API 文档(https://atomgit.com/cann/pyasc/blob/master/docs/python-api/index.md),而不是 Ascend C 文档。虽然逻辑一一对应,但命名和调用方式不同。
陷阱 2:大数据量下 Python 侧 OOM
症状:处理 1GB 以上的数据时,算子还在 NPU 上算,但 Python 进程先报 MemoryError。
原因:pyasc 的 JIT 编译器在 Python 侧维护了中间表示(IR)和编译缓存。当你传给核函数的 torch.Tensor 非常大时,Python 跑在 Host(CPU+内存)上,大 Tensor 的 Python 对象在 torch 中默认分配在 Host 内存(DDR),如果数据量超过可用内存,Python 进程直接 OOM。
# ❌ 大数据量直接引入 Python 进程 OOM
x = torch.rand(10_000_000_000, dtype=torch.float32) # ∼37 GB,Host 内存不够
y = torch.rand(10_000_000_000, dtype=torch.float32)
z = vadd_launch(x, y) # 在 torch 创建时就已经 OOM
解决方案:批量处理,或者用 NPU 侧直接分配。
# ✅ 方案 1:分块处理
BATCH_SIZE = 512 * 1024 * 1024 # 每批 512MB
total_size = 10_000_000_000
z_list = []
for start in range(0, total_size, BATCH_SIZE):
end = min(start + BATCH_SIZE, total_size)
x_chunk = torch.rand(end - start, dtype=torch.float32)
y_chunk = torch.rand(end - start, dtype=torch.float32)
z_chunk = vadd_launch(x_chunk, y_chunk)
z_list.append(z_chunk)
z = torch.cat(z_list)
# ✅ 方案 2(推荐):在 NPU 侧直接分配
import torch_npu
device = torch.device("npu:0")
x = torch.rand(size, dtype=torch.float32, device=device)
y = torch.rand(size, dtype=torch.float32, device=device)
z = vadd_launch(x, y) # 数据在 NPU 显存里,Host 侧只有 Python 引用
注意:NPU 侧直接分配需要安装 torch_npu,且仅在上板模式有效。仿真模式下数据始终在 Host。
8. 实战代码索引
以下是本文涉及的完整代码索引,所有代码都可以在文中对应位置找到完整版本:
摘要索引:
- 代码 1(环境安装)→ 第 3.1 节
- 代码 2(Add 算子完整脚本)→ 第 3.2 节(
add_hello_world.py) - 代码 3(编译运行)→ 第 3.3 节
- 代码 4(Tensor 核心抽象)→ 第 4.1-4.4 节
- 代码 5(性能对比)→ 第 5.1 节(
perf_compare.py) - 代码 6(Debug 中间值回传)→ 第 6.2 节(
debug_kernel) - 代码 7(Profiling 配置)→ 第 6.3 节
- 代码 8(UT 测试模板)→ 见下方完整模板
# test_add.py —— pyasc 算子单元测试模板
import pytest
import torch
from add_hello_world import vadd_launch, USE_CORE_NUM
class TestPyascAdd:
"""pyasc Add 算子的完整单测套件"""
def test_small_vector(self):
x = torch.rand(1024, dtype=torch.float32)
y = torch.rand(1024, dtype=torch.float32)
z = vadd_launch(x, y)
assert torch.allclose(z, x + y), "小向量加法失败"
def test_large_vector(self):
x = torch.rand(1024 * 1024, dtype=torch.float32)
y = torch.rand(1024 * 1024, dtype=torch.float32)
z = vadd_launch(x, y)
assert torch.allclose(z, x + y), "大向量加法失败"
def test_zero_input(self):
x = torch.zeros(1024, dtype=torch.float32)
y = torch.zeros(1024, dtype=torch.float32)
z = vadd_launch(x, y)
assert torch.allclose(z, torch.zeros_like(x)), "零输入加法失败"
def test_negative_values(self):
x = torch.randn(4096, dtype=torch.float32)
y = torch.randn(4096, dtype=torch.float32)
z = vadd_launch(x, y)
assert torch.allclose(z, x + y), "负数加法失败"
def test_multiple_rows(self):
x = torch.rand(8, 2048, dtype=torch.float32)
y = torch.rand(8, 2048, dtype=torch.float32)
z = vadd_launch(x.flatten(), y.flatten())
expected = (x + y).flatten()
assert torch.allclose(z, expected), "多行展开后加法失败"
- 代码 9(陷阱 1:API 混淆解决方案)→ 第 7 节
- 代码 10(陷阱 2:OOM 解决方案)→ 第 7 节
一句话总结:
- pyasc = 手写 Ascend C 流水逻辑,但用 Python 语法
- pypto = Tile 级编程,连流水编排都帮你自动了
想深入了解,去看看 pypto 仓库:
https://atomgit.com/cann/pyasc
https://atomgit.com/cann/pypto
最后,pyasc 是一个还很年轻的项目(2025 年 11 月首次上线 API),社区正在快速迭代中。如果你跑出什么问题或者有 Feature Request,直接去 AtomGit 提 Issue。昇腾原生支持 Python 写算子的这一天,我已经等了很久了。
更多推荐



所有评论(0)