请添加图片描述

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_flagasc.wait_flag 实现跨流水线的硬件同步事件。对应 Ascend C 中的 SetFlagWaitFlag

# 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_launchmain)完全可以打断点:

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_copyasc.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 写算子的这一天,我已经等了很久了。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐