《Ascend C 编译能力优化》
## 1. 背景
### 1.1 业务背景
PyTorch 原生 `torch.utils.cpp_extension` 模块为多种原生源语言提供了统一的编译扩展能力:开发者声明 `Extension`、注册 `BuildExtension`,即可自动获得 ninja 增量编译(lazy)、多核并行编译(parallel)、ABI 探测、依赖追踪等能力。
昇腾 NPU 适配工程共有 9 个示例工程,其中 4 个涉及 Ascend C 内核源码(`.asc` 文件),必须使用昇腾专用编译器 `bisheng` 编译。由于原生 `cpp_extension.py` 没有 `.asc` 和 `bisheng` 的支持插槽,这 4 个工程被迫自定义 `build_ext`,绕过原生构建链路,采用单命令 bisheng 一体编译+链接。
### 1.2 问题触发
在相关适配工程的评审中,有开发者反馈:现有构建方式在 Python 侧单独注入,没有纳入原生 cmake→ninja 的 lazy / parallel 构建体系,也无法把 Ascend 内核二进制产物直接和 Python 扩展链接到同一个动态库中,希望获得与 PyTorch 原生编译一致的体验。
该反馈包含三个递进的技术判断,本质指向同一个根因:**原生 `BuildExtension` 不认识 `.asc` 源和 `bisheng` 编译器**。
### 1.3 名词术语
| 术语 | 定义 |
|------|------|
| `BuildExtension` | PyTorch 原生类(`torch.utils.cpp_extension.BuildExtension`),继承 setuptools `build_ext`,是原生扩展的构建驱动 |
| `_write_ninja_file` | 原生函数,负责生成 `build.ninja` 文件,内含 compile/link 规则和依赖追踪配置 |
| `_is_cuda_file` / `_is_sycl_file` | 原生函数,按文件后缀识别对应源语言,路由到对应编译规则 |
| `bisheng` | 昇腾专用编译器,支持 `-x asc` 编译 Ascend C 源码、`--npu-arch` 指定芯片架构 |
| `.asc` | Ascend C 内核源文件后缀,编译产物为 AI Core 二进制(aibin) |
| `aibin` | AI Core 二进制产物,由 bisheng 编译 `.asc` 时附带生成,需合入最终 `.so` |
| lazy build | 增量编译,只重编变更的源文件,依赖 `-MMD -MF $out.d + deps=gcc` |
| parallel build | 并行编译,每个源文件一条 ninja build 规则,ninja 按 CPU 核数并行 |
| `NpuExtension` | NPU 适配层提供的 `CppExtension` 封装,自动注入 NPU 头文件/库路径 |
| `--npu-arch` | bisheng 的芯片架构参数,按芯片型号自动映射(支持 910 系列/950 系列芯片) |
---
## 2. 现状分析
### 2.1 原生 PyTorch 的编译能力架构
原生 `BuildExtension` 的 lazy/parallel 能力通过以下机制实现:
```
setup(ext_modules=[ext], cmdclass={"build_ext": BuildExtension})
│
▼
BuildExtension.build_extensions()
├─ ① _check_abi() → 自动探测 C++ ABI
├─ ② src_extensions += [.cu/.sycl] → 注册合法源后缀
├─ ③ compiler.compile = ninja 版 → 替换编译方法(关键!)
└─ ④ super().build_extensions() → 走 distutils 默认流程
└─ compiler.compile(...)
└─ unix_wrap_ninja_compile()
├─ _write_ninja_file() → 生成 build.ninja
│ ├─ rule compile: $cxx -MMD -MF $out.d ... -c $in -o $out
│ │ depfile = $out.d / deps = gcc ← lazy
│ ├─ rule cuda_compile: $nvcc ... -c $in -o $out
│ ├─ rule sycl_compile: $sycl ... -c $in -o $out
│ ├─ rule link: $cxx $in $ldflags -o $out
│ └─ build x.o: compile x.cpp ← parallel(每源一条)
└─ subprocess.run(["ninja"])
```
**机制要点**(具体实现细节可参考 PyTorch 官方源码,此处仅描述原理):
- lazy 能力依赖 `depfile` + `deps = gcc`:编译器输出 `.d` 依赖文件,ninja 据此实现只重编变更源文件。
- parallel 能力依赖「每源一条 build 规则」:ninja 按依赖图并行调度。
- 编译器的替换点在于 `compiler.compile` 与内部 `_write_ninja_file` 生成的 ninja 规则。
**语言识别机制**:原生通过**文件后缀**识别源语言,通过 `_write_ninja_file` 里**硬编码的编译器变量**(`$cxx`/`$nvcc`/`$sycl`)和**硬编码的编译规则**(`compile`/`cuda_compile`/`sycl_compile`)路由编译。当前仅支持 `.cu`/`.cuh`/`.sycl` 等原生源后缀,没有 `.asc`。
### 2.2 昇腾适配工程当前实现
4 个 Ascend C 工程的当前实现对比:
| 工程 | 继承基类 | cmdclass 写法 | 芯片架构 | 编译方式 |
|------|---------|--------------|---------|---------|
| `cpp_extension` | torch `BuildExtension` | `.with_options(use_ninja=True)` | 硬编码 | ninja |
| `acl_dnn` | 自定义 `AscendBuildExtension` | 手写 `cmdclass` | 硬编码 | 单命令 bisheng |
| `kernel_ascend` | 自定义 `AscendBuildExtension` | 手写 `cmdclass` | 硬编码 | 单命令 bisheng |
| `attention` | 自定义 `AscendBuildExtension` | 手写 `cmdclass` | 硬编码 | 单命令 bisheng |
> 注:上表为示意(工程名已泛化),当前实现的核心问题在于:4 个工程中除首个外均绕过了原生构建链路,且芯片架构为硬编码参数,无法自动适配不同型号。
**自定义 `build_ext` 的实现方式**:
```python
class AscendBuildExtension(build_ext):
def build_extension(self, ext):
# 单命令 bisheng 编译+链接,无 ninja、无增量、无并行
compile_cmd = [...]
self.spawn(compile_cmd)
```
**现状问题总结**:
1. 不享受原生 ninja 的 lazy/parallel 能力,全量编译耗时随工程规模线性增长;
2. aibin 产物无显式依赖声明,无法进入构建依赖图、无法追踪;
3. 芯片架构硬编码,换型号需改代码;
4. 重复造轮子:每个工程各写一套 `compile_cmd`。
---
## 3. 目标与方案设计
### 3.1 设计目标
| 编号 | 目标 | 说明 |
|------|------|------|
| US-1 | 原生支持 `.asc` 源 | `Extension(language="asc")` + `BuildExtension` 直接编译 |
| US-2 | 支持 lazy 增量编译 | 修改单个 `.asc` 只重编该文件 |
| US-3 | 支持 parallel 并行编译 | 多个 `.asc` 并行编译 |
| US-4 | aibin 显式产物声明 | 进入构建依赖图,可追踪 |
| US-5 | 芯片架构自动探测 | 免手写 `--npu-arch` |
| US-6 | 适配工程统一迁移 | 4 个工程全部走原生链路 |
### 3.2 总体思路
在 PyTorch 原生编译链路中补齐 `.asc` 与 `bisheng` 的支持插槽,让 Ascend C 工程与原生工程享受一致的构建体验:
```
Extension(language='asc') + BuildExtension
│
▼
_write_ninja_file()
├─ src_extensions += ['.asc'] ← FR-1 源注册
├─ config += ['bisheng = ...'] ← FR-2 编译器变量
├─ rule asc_compile: $bisheng -x asc ← FR-3 编译规则
│ --npu-arch=$npu_arch -MMD -MF ...
├─ rule asc_devlink: $bisheng ... ← FR-5 设备链接规则
└─ per-source build rules ← lazy + parallel(复用 ninja 机制)
▲
└─ get_npu_arch() 自动探测 ← FR-4
```
---
## 4. 实现方案
### 4.1 原生层改动总览
| 功能需求 | 目标 | 位置 |
|---------|------|------|
| FR-1 | `.asc` 源注册 | `cpp_extension.py`(原生层) |
| FR-2 | `bisheng` 编译器变量 | `cpp_extension.py` |
| FR-3 | `asc_compile` 编译规则 | `cpp_extension.py` |
| FR-4 | `--npu-arch` 架构自动探测 | NPU 适配层 |
| FR-5 | `asc_devlink` 设备链接规则 | `cpp_extension.py` |
| FR-6 | `AscendExtension` 工厂函数 | NPU 适配层 |
### 4.2 原生层改动
#### FR-1:`.asc` 源注册
```python
# 在 BuildExtension 的源后缀注册处新增
if with_asc: # 检测到 .asc 源时
self.compiler.src_extensions += ['.cu', '.cuh', '.sycl', '.asc']
```
#### FR-2:`bisheng` 编译器变量
参照 `_write_ninja_file` 中原生编译器变量的写法:
```python
# 在 _write_ninja_file 的 config 块中新增
if with_asc or asc_dlink_post_cflags:
if "PYTORCH_BISHENG" in os.environ:
bisheng = os.getenv("PYTORCH_BISHENG")
else:
bisheng = _find_bisheng() # 从 PATH 查找 bisheng
config.append(f'bisheng = {bisheng}')
```
#### FR-3:`asc_compile` 编译规则
参照原生编译规则的定义方式:
```python
if with_asc:
asc_compile_rule = ['rule asc_compile']
asc_compile_rule.append(
' command = $bisheng -x asc --npu-arch=$npu_arch '
'-MMD -MF $out.d $asc_cflags -c $in -o $out $asc_post_cflags'
)
asc_compile_rule.append(' depfile = $out.d')
asc_compile_rule.append(' deps = gcc')
```
#### FR-4:`--npu-arch` 架构探测
参照原生编译器探测的模式,在 NPU 适配层提供:
```python
def get_npu_arch() -> str:
result = subprocess.run(["npu-smi", "info"], capture_output=True, text=True)
# 解析芯片型号,映射到对应架构参数
if '950' in chip_name:
return 'dav-3510'
elif '910' in chip_name:
return 'dav-2201'
```
#### FR-5:aibin 显式产物声明(设备链接)
参照原生设备链接规则:
```python
if asc_dlink_post_cflags:
asc_devlink_out = os.path.join(os.path.dirname(objects[0]), 'asc_dlink.o')
asc_devlink_rule = ['rule asc_devlink']
asc_devlink_rule.append(
' command = $bisheng --npu-arch=$npu_arch $in -o $out $asc_dlink_post_cflags'
)
asc_devlink = [f'build {asc_devlink_out}: asc_devlink {" ".join(objects)}']
objects += [asc_devlink_out]
```
#### FR-6:`AscendExtension` 工厂函数
参照原生扩展工厂函数,放在 NPU 适配层:
```python
def AscendExtension(name, sources, extra_compile_args=None, ...):
# 注入 NPU 头文件/库路径 + language='asc'
return Extension(
name=name,
sources=sources,
language='asc',
extra_compile_args=extra_compile_args or {},
...
)
```
### 4.3 NPU 适配层改动
| 改动项 | 位置 | 说明 |
|--------|------|------|
| `AscendExtension` 工厂函数 | NPU 适配层 utils/cpp_extension.py | 类比 `NpuExtension`,注入 NPU 路径 + `language='asc'` |
| `get_npu_arch()` | NPU 适配层 utils/cpp_extension.py | `npu-smi info` 探测芯片架构 |
| 注册 `.asc` 到 `src_extensions` | NPU 适配层(`_LazyBuildExt` 或 monkey-patch) | 让 `BuildExtension` 认识 `.asc` |
### 4.4 昇腾适配工程层改动
4 个工程的 setup.py 统一改为:
```python
from torch_npu.utils.cpp_extension import AscendExtension
from torch.utils.cpp_extension import BuildExtension
ext = AscendExtension(
name="xxx.custom_ops_lib",
sources=source_files, # glob("csrc/*.asc")
)
setup(
ext_modules=[ext],
cmdclass={"build_ext": BuildExtension.with_options(use_ninja=True)},
)
```
删除内容:
- 自定义 `AscendBuildExtension` 类
- 覆写的 `build_extension()` 方法
- 手写的 `compile_cmd` 拼接
- 硬编码的 `--npu-arch` 参数
### 4.5 实现优先级与里程碑
| 阶段 | 目标 | 涉及功能 | 交付物 |
|------|------|---------|--------|
| **阶段一** | 原生支持 `.asc` + `bisheng` | FR-1/2/3/6 | 原生 `cpp_extension.py` 补 `asc_compile` 规则 + `bisheng` 变量;NPU 适配层提供 `AscendExtension` |
| **阶段二** | 适配工程 4 工程迁移 | 全部 | 4 个 setup.py 改用 `AscendExtension + BuildExtension`;test.sh 全 PASS |
| **阶段三** | aibin 显式化 + 架构探测 | FR-4/5 | `asc_devlink` 规则合入原生;`get_npu_arch()` 合入 NPU 适配层 |
---
## 5. 验收标准
### 5.1 功能验收
| 编号 | 验收项 | 验收方法 | 对应需求 |
|------|--------|---------|---------|
| AC-1 | `Extension(language="asc")` + `BuildExtension` 可编译 `.asc` 源 | 新建最小工程编译成功 | US-1, FR-1/2/3/6 |
| AC-2 | 修改单个 `.asc` 后只重编该文件 | 触摸一个 `.asc`,重跑 build,ninja 日志显示只编 1 个 | US-2, FR-3 |
| AC-3 | 多个 `.asc` 并行编译 | ninja 日志显示多线程编译 | US-3, FR-3 |
| AC-4 | aibin 出现在构建依赖图中 | `ninja -t deps` 可查到 aibin 依赖 | US-4, FR-5 |
| AC-5 | 芯片架构自动探测 | 不写 `--npu-arch`,构建成功且芯片正确 | US-5, FR-4 |
| AC-6 | 4 个工程迁移后 test.sh 全 PASS | `bash test.sh` 全 PASS | US-6 |
| AC-7 | 原 C++ 工程编译不受影响 | base/full/pybind 等工程编译行为不变 | NFR-1 |
### 5.2 非功能验收
| 编号 | 验收项 |
|------|--------|
| NAC-1 | 适配工程中不再有自定义 `AscendBuildExtension` 类 |
| NAC-2 | 适配工程中不再有手写 `compile_cmd` 拼接 |
| NAC-3 | 原有 C++ 工程编译路径无回归 |
---
## 6. 风险与约束
| 编号 | 风险/约束 | 影响 | 缓解措施 |
|------|----------|------|---------|
| R-1 | bisheng 对 depfile(`-MMD -MF`)的兼容性待验证 | FR-3 增量编译能力依赖此兼容性 | 阶段一优先验证 bisheng 的 depfile 支持 |
| R-2 | bisheng 的 device link 模式待验证 | FR-5 aibin 显式化依赖此能力 | 阶段三前与编译工具链维护方确认 |
| R-3 | 原生上游接受度 | 改动能否合入 PyTorch 主干 | 以 guard 方式实现,非 Ascend 用户不受影响 |
| R-4 | bisheng 不在 PATH | 构建失败 | 支持通过 `PYTORCH_BISHENG` 环境变量指定 bisheng 路径 |
| R-5 | 新芯片型号未覆盖 | `get_npu_arch()` 报错 | 报错信息引导用户手动指定 `--npu-arch` |
---
## 7. 依赖关系
```
bisheng 支持 -MMD -MF(depfile)
│
▼
FR-3 asc_compile 规则
│
├── FR-1 _is_asc_file ──┐
├── FR-2 bisheng 变量 ──┤
│ ├── FR-6 AscendExtension ──→ 适配工程迁移
│ │
└── bisheng 支持 dlink ──┤
│
FR-5 asc_devlink
│
FR-4 get_npu_arch
```
**关键前置依赖**:bisheng 对 `-MMD -MF` 和 device link 的支持需优先与编译工具链维护方确认。
---
## 8. 附录
### 附录 A:原生扩展编译规则(参照模板)
以下规则结构为新增 `asc_compile` / `asc_devlink` 规则的参照:
```python
# compile 规则(C++ 源)
compile_rule = ['rule compile']
compile_rule.append(' command = $cxx -MMD -MF $out.d $cflags -c $in -o $out $post_cflags')
compile_rule.append(' depfile = $out.d')
compile_rule.append(' deps = gcc')
# 每源一条 build 规则(parallel 的关键)
for source_file, object_file in zip(sources, objects):
build.append(f'build {object_file}: {rule} {source_file}')
# 设备链接规则(aibin 显式化的参照)
if dlink_post_cflags:
dlink_out = os.path.join(os.path.dirname(objects[0]), 'dlink.o')
dlink_rule = ['rule dlink']
dlink_rule.append(' command = $compiler $in -o $out $dlink_post_cflags')
dlink = [f'build {dlink_out}: dlink {" ".join(objects)}']
objects += [dlink_out]
```
### 附录 B:当前 4 个 Ascend C 工程的编译命令(待替换)
```python
# 4 个工程统一使用此命令(单命令 bisheng,无 ninja)
compile_cmd = [
"bisheng", "-x", "asc", "--npu-arch=dav-2201",
"-shared", "-fPIC", "-std=c++17",
f"-D_GLIBCXX_USE_CXX11_ABI={abi_value}",
"-ltorch_npu", "-ltorch", "-lc10",
*ext.sources, "-o", ext_fullpath,
]
self.spawn(compile_cmd)
```
### 附录 C:需求背景说明
评审反馈的核心诉求归纳如下:
| 诉求 | 实际含义 |
|---------|---------|
| 构建方式未纳入原生标准链路 | 绕过原生标准构建体系 |
| 未享受 lazy / parallel 能力 | 单命令 bisheng,无 ninja 增量/并行 |
| 无法将 aibin 链接到同一动态库 | aibin 靠 bisheng 隐式兜底,非显式构建产物 |
鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。
更多推荐

所有评论(0)