--- slug: ascend-npu-image-build displayName: Ascend NPU image → SWR → ModelArts version: 0.1.0 summary: 构建昇腾 NPU 训练/推理镜像并进行 ModelArts 适配:从官方基础镜像分层、ma-user 用户适配、aarch64、离线内置资产、SWR 推送,以及镜像构建和验证的策略。 license: MIT author: xuopoj homepage: https://github.com/xuopoj/ascend-delivery name: ascend-npu-image-build description: 制作一个能在ModelArts上使用的昇腾训练/推理镜像主要分两个部分,一个是昇腾镜像,另外基于昇腾镜像还需要做一些ModelArts的适配,比如ma-user用户,ipykernel以支持jupyter notebook调试。在构建过程中,需要注意一些点,比如在训练场景可以选择基于官方(包括昇腾官方或者三方框架官方)镜像仅做简单的ma-user适配,或者从头创建(cann,训练框架,ModelArts适配);对于推理场景,需要关注是传统的推理(针对小模型,使用om 昇腾推理),还是使用常用的LLM推理框架比如vLLM 或者 SGLang,以及推理1.0或者推理2.0场景。 --- # 昇腾 NPU 镜像:构建、交付、运行 昇腾镜像通常无法在构建它的地方完整验证。设备驱动在宿主机上,所以在一台没有 NPU 的 机器上 `import torch_npu` 失败是**设计如此**。但"有没有 NPU"是要探测的事实,不是可以 假设的前提 —— 构建机有 NPU 就能做真实验证,没有就只能验证脱离设备可验证的部分, 并把其余结论明确标注为未验证。 同样要探测而不是假设的还有:**构建机能不能上网**(NPU 主机通常不能),以及 **构建机与目标机的架构是否一致**。这三件事决定了后面每一步怎么做。 所以:**先探测环境,再让用户选择路线,然后在官方基础镜像上分层、改动越少越好。** ## 步骤 0:先探测构建机 动手写任何东西之前先跑这一段,它决定了后面所有步骤的形态: ```bash uname -m command -v npu-smi >/dev/null && npu-smi info | head -5 || echo "no npu-smi" ls /dev/davinci* 2>/dev/null || echo "no davinci devices" curl -sS -m 5 -o /dev/null -w '%{http_code}\n' https://quay.io/v2/ || echo "no egress" docker version --format '{{.Server.Arch}}' ``` | 探测结果 | 意味着 | |---|---| | 外网通 | **构建必须在这里做。** 能拉基础镜像、能装 pip 包 | | 外网不通 | 这台机器构建不了。换一台能上网的机器构建,再走 `references/offline.md` 的 tarball 路线送过去 | | 有 `npu-smi` 且列出健康芯片 | **可以做真实的 NPU 验证** —— 这是最好的情况,用上它 | | 无 NPU | 只能做脱离设备的验证。`references/verify-image.md`,并如实说明哪些没验证 | | 构建机架构 == 目标机架构 | 原生构建,`--platform` 可省 | | 构建机架构 != 目标机架构 | **必须显式 `--platform linux/arm64`**,且需要 binfmt/QEMU,会很慢 | **外网是构建的前提,不是目标机的前提。** 需要外网的是构建机;NPU 主机一般没有外网, 镜像通过 SWR 或 tarball 送过去 —— 见 `references/swr-and-modelarts.md`。 ## 步骤 1:问清楚四件事 这四个问题的答案会改变 Dockerfile 本身,不只是文档。不要替用户猜: 1. **目标机架构** —— 一般是 arm(昇腾是 aarch64),但要问。结合步骤 0 的构建机架构, 决定是否显式指定 `--platform`。 2. **训练还是推理**,以及芯片型号(910B / 310P / A2 / A3)。用目标机的 `npu-smi info` 确认 —— "Atlas 800" 是机箱型号而不是芯片。 3. **构建路线** —— 从头构建,还是基于可靠的三方昇腾镜像只做 ModelArts 适配。 两条路都要认真展开讲清楚代价,再让用户选,见下面的"两条构建路线"。 4. **推理场景下,是推理 1.0 还是 2.0** —— 这决定要不要做 ma-user 适配,见规则 4。 ## 两条构建路线 两条都是正当的选择。把代价讲清楚,让用户选,不要替他们决定。 | | 基于三方/官方镜像做 ModelArts 适配 | 从头构建 | |---|---|---| | 起点 | `quay.io/ascend/llamafactory`、`vllm-ascend`、`mindie` 等 | `quay.io/ascend/cann`,或 CANN 安装包 | | 你要做的 | ma-user 适配、`ipykernel`、少量缺失的包 | CANN → 训练/推理框架 → ModelArts 适配,全套 | | 三件套 | **继承,不要动** —— 已在真实硅片上测过 | **必须自己钉** —— 没有可继承的,依据文档化的配套关系 | | 代价 | 受限于上游的版本组合 | 版本自由,但 torch/torch_npu/CANN 的配套关系要自己负责 | | 什么时候选 | 绝大多数情况 | 上游没有合适的版本/芯片,或制度要求自有来源 | 选哪条,`references/bases.md` 都要读 —— 它讲怎么确认一个 tag 真实存在, 以及各仓库的 tag 格式。 ## 五条规则 1. **从已发布的昇腾基础镜像开始**,除非用户明确选了从头构建。不要编造 registry tag —— 见 `references/bases.md`,构建前先确认 tag 存在。 2. **不要重新钉基础镜像已经带的 torch / torch_npu / CANN。** 它们是耦合的 (`torch_npu 2.7.1` ↔ CANN 8.3.RC1、`numpy<2`),而且基础镜像里这三件套是在 真实硅片上测过的。先检查,再只补缺失的部分(`references/verify-base.md`)。 *例外:* 从头构建时没有三件套可继承,那就必须自己钉 —— 有意识地钉, 依据是有文档的配套关系,并在硬件验证之前标注为未验证。 3. **架构要和目标机对上。** 昇腾是 aarch64。构建机架构与目标不一致时必须显式 `--platform linux/arm64`,在 CI 里尤其不能省 —— x86 runner 会悄无声息地产出一个 没用的镜像。 4. **`ma-user`、uid 1000、gid 100 —— 但只在需要的场景。** 训练与推理 1.0:需要,这是 ModelArts 的契约。 推理 2.0:以 root 运行,不做 ma-user 适配。 需要时只在不存在时创建,而且**绝不**加 `|| true` —— uid 冲突必须让构建大声失败, 而不是之后以挂载卷上的 permission denied 的形式冒出来。 5. **不要写碰设备的 HEALTHCHECK。** 在没有 NPU 的机器上它永远 unhealthy、只在硬件上 才绿,因此在那种机器上无法冒烟测试。就绪与否,靠人在机器上跑真实命令来判断。 ## 步骤 1. **探测构建机**(步骤 0),**问清楚四件事**(步骤 1)。 2. **选基础镜像并确认它能解析。** `references/bases.md`。如果用户已经有一个能用的 基础镜像,就用他们的。 3. **在添加任何东西之前,先检查基础镜像带了什么** —— `references/verify-base.md`。 你以为存在的包常常并不存在,而在隔离网机器上,缺的包在运行时是补不回来的。 ModelArts 适配要装的 `ipykernel`(用于 Jupyter notebook 调试)也在这里确认。 4. **写 Dockerfile。** 模板以及每一行背后的理由:`references/dockerfile.md`。 目标是离线环境的话,还要读 `references/offline.md`,把每一份资产都内置进去。 5. **构建之后读日志 —— 不要相信退出码。** `docker build` 可能在失败的情况下仍然 exit 0。grep `^ERROR|did not complete successfully`,并确认镜像确实存在。 6. **验证。** 构建机有 NPU 就做真实验证;没有就只验证脱离设备可验证的部分, 并明确说出哪些结论未经测试:`references/verify-image.md`。 7. **交付:** `references/swr-and-modelarts.md` —— 直接推送,或者用 `docker save` → 传输 → `docker load` 应对隔离网站点。 8. **训练产物转成推理要的格式:** `references/artifacts.md` —— 传统模型走 onnx → om,大模型存 safetensors、LoRA 先合并权重。 ## 速查 | 现象 | 原因 | |---|---| | `libascend_hal.so: cannot open shared object file` | 没有设备/驱动。在无 NPU 的机器上属正常;在 NPU 机器上出现则是驱动挂载缺失 | | `Failed to load the backend extension: torch_npu` | 同一根因,经由 `import torch` 暴露出来 | | 导入正常,但看不到设备 | 只挂了 `/dev/davinci*`,没挂 manager/driver | | pip 报 `from versions: none` | 被拦截/403 的 index,**不是**版本号钉错了 | | 挂载卷上 permission denied | uid/gid 不是 1000:100(推理 1.0 与训练场景) | | 本地能跑,机器上失败 | 按错误的架构构建的 | ## 危险信号 —— 停 - 写一个你没有确认过存在的 registry tag - 钉一个"大概兼容"的 torch/torch_npu 版本 - `useradd ... || true` - 在推理 2.0 镜像里做 ma-user 适配(2.0 以 root 运行,不需要) - 导入 `torch_npu` 或调用 `.npu()` 的 HEALTHCHECK - `ENV PIP_INDEX_URL=...`(会污染离线运行时;请用构建期 ARG) - 假设构建机没有 NPU 就不做验证 —— 先探测,有就用 - 在没有任何东西跑过 NPU 的情况下说"镜像能用"