55 个独立仓库,每个仓库独立迭代——CANN 8.0 里的 ops-transformer 是哪个 commit?hccl 是 v2.1.3 还是 v2.2.0?runtime 和 driver 的版本是否兼容?manifest 仓库用一份 XML 格式的清单文件回答了所有这些问题。它是 CANN 发行版的「物料清单」——定义一次发行包含哪些仓库、每个仓库用哪个 commit、仓库之间的依赖关系。

Manifest 文件结构

每个 CANN 发行版有一个 manifest XML 文件:

<!-- manifest/releases/CANN_8.0.0.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<manifest>
    <remote name="atomgit" fetch="https://atomgit.com/cann/" />

    <!-- 核心算子库 -->
    <project name="opbase"           path="src/opbase"           revision="e3f2a1b" />
    <project name="ops-math"        path="src/ops-math"        revision="7c8d9e0" />
    <project name="ops-nn"          path="src/ops-nn"          revision="a1b2c3d" />
    <project name="ops-transformer"  path="src/ops-transformer"  revision="f4e5d6c" />
    <project name="ops-cv"          path="src/ops-cv"          revision="b7a8c9d" />
    <project name="ops-blas"        path="src/ops-blas"        revision="1a2b3c4" />
    <project name="ops-fft"         path="src/ops-fft"         revision="d5e6f7a" />
    <project name="ops-rand"        path="src/ops-rand"        revision="8b9c0d1" />
    <project name="ops-tensor"      path="src/ops-tensor"      revision="e2f3a4b" />

    <!-- 编译与运行时 -->
    <project name="ge"              path="src/ge"              revision="5c6d7e8" />
    <project name="runtime"          path="src/runtime"          revision="f9a0b1c" />
    <project name="driver"          path="src/driver"          revision="2d3e4f5" />
    <project name="metadef"         path="src/metadef"         revision="6a7b8c9" />

    <!-- 加速库 -->
    <project name="catlass"         path="src/catlass"         revision="0d1e2f3" />
    <project name="ascend-transformer-boost" path="src/atb"    revision="a4b5c6d" />
    <project name="graph-autofusion" path="src/graph-autofusion" revision="e7f8a9b" />
    <project name="asnumpy"         path="src/asnumpy"         revision="0c1d2e3" />
    <project name="torchtitan-npu"  path="src/torchtitan-npu"  revision="4f5a6b7" />

    <!-- 通信库 -->
    <project name="hccl"            path="src/hccl"            revision="8c9d0e1" />
    <project name="hcomm"           path="src/hcomm"           revision="f2a3b4c" />
    <project name="hixl"            path="src/hixl"            revision="5d6e7f8" />
    <project name="ascend-boost-comm" path="src/ascend-boost-comm" revision="9a0b1c2" />
    <project name="shmem"           path="src/shmem"           revision="3d4e5f6" />
</manifest>

每个 <project> 元素的核心三个字段:

  • name:AtomGit 仓库名
  • path:在源码树中的相对路径
  • revision:Git commit hash(短格式,7 位)

有了这份 manifest,可以一键拉取整个 CANN 发行版的全部源码:

# 用 repo 工具拉取 CANN 8.0.0 完整源码
repo init -u https://atomgit.com/cann/manifest \
    -b refs/tags/CANN_8.0.0 -m releases/CANN_8.0.0.xml
repo sync -j16

# 拉取后的目录结构:
# src/
# ├── opbase/          → opbase@e3f2a1b
# ├── ops-math/        → ops-math@7c8d9e0
# ├── ops-nn/          → ops-nn@a1b2c3d
# ├── ops-transformer/ → ops-transformer@f4e5d6c
# ├── ge/              → ge@5c6d7e8
# ├── runtime/         → runtime@f9a0b1c
# ├── driver/          → driver@2d5e4f5
# ...(共 55 个仓库,精确到 commit)

repo sync 自动执行 git clone + git checkout 到 manifest 指定的 commit——不需要手动挨个 clone 55 个仓库。

Manifest 的版本管理

CANN 有两个 release 轨道:stable 和 RC(Release Candidate)。Manifest 用 Git 分支区分:

manifest 仓库分支结构
├── main(开发分支,跟着各仓库的 HEAD 走)
├── releases/8.0.x(8.0 stable 线)
│   ├── CANN_8.0.0.xml  → 各仓库 commit 指向 8.0.0 发布时的
│   ├── CANN_8.0.1.xml  → bugfix 发布,仅变化了出 bug 的仓库
│   └── CANN_8.0.2.xml
├── releases/8.5.x(8.5 stable 线)
│   └── CANN_8.5.0.xml
└── releases/rc(RC 线)
    ├── CANN_8.5.0-rc1.xml
    ├── CANN_8.5.0-rc2.xml
    └── CANN_8.5.0-rc3.xml

关键原则:bugfix 发布只修改出 bug 的仓库的 revision,其余仓库不变。

# CANN_8.0.1.xml 相对于 CANN_8.0.0.xml 的 diff
# 只有一个仓库的 revision 变了

--- releases/CANN_8.0.0.xml
+++ releases/CANN_8.0.1.xml
@@ -5,7 +5,7 @@
-    <project name="ops-nn" path="src/ops-nn" revision="a1b2c3d" />
+    <project name="ops-nn" path="src/ops-nn" revision="x9y8z7w" />

只有 ops-nn 仓库在 8.0.1 中更新了(修复了 MatMul 的一个 bug),其余 54 个仓库不变。

CI 自动生成 Manifest

Manifest 不该手动编辑——55 个仓库的 commit hash 手动写容易出错。CI 自动生成:

# .github/workflows/generate-manifest.yml(manifest 仓库自身的 CI)

name: Generate Release Manifest
on:
  workflow_dispatch:
    inputs:
      version:
        description: 'CANN version (e.g. 8.0.0, 8.0.1)'
        required: true
      pre_release:
        type: boolean
        default: false

jobs:
  generate:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout manifest repo
        uses: actions/checkout@v3

      - name: Resolve revisions
        run: |
          python3 tools/resolve_manifest.py \
              --version=${{ inputs.version }} \
              --output=releases/CANN_${{ inputs.version }}.xml

      - name: Validate manifest
        run: |
          python3 tools/validate_manifest.py \
              --manifest=releases/CANN_${{ inputs.version }}.xml
          # 验证:
          # 1. 所有 55 个仓库的 commit 都存在(没有 rebase 丢掉的)
          # 2. 仓库之间的依赖关系正确
          # 3. 没有循环依赖

      - name: Tag and commit
        run: |
          git tag CANN_${{ inputs.version }}
          git push --tags

tools/resolve_manifest.py 的逻辑:

# manifest/tools/resolve_manifest.py

import subprocess
import xml.etree.ElementTree as ET

# 55 个仓库的清单
REPOS = [
    ("opbase", "src/opbase"),
    ("ops-math", "src/ops-math"),
    # ... 全部 55 个仓库
]

def resolve_manifest(version_tag_prefix):
    doc = ET.Element("manifest")
    ET.SubElement(doc, "remote", name="atomgit", fetch="https://atomgit.com/cann/")

    for name, path in REPOS:
        # 获取仓库在 version_tag 下的 commit hash
        url = f"https://atomgit.com/cann/{name}"
        tag = f"v{version_tag_prefix}"

        # git ls-remote 获取 tag 对应的 commit
        commit = subprocess.check_output(
            ["git", "ls-remote", url, f"refs/tags/{tag}"],
            text=True
        ).split()[0][:7]  # 取短 hash

        ET.SubElement(doc, "project",
            name=name, path=path, revision=commit)

    return ET.tostring(doc, encoding="unicode")

每个仓库的 release tag(如 v8.0.0)指向该仓库在 CANN 8.0.0 时的 commit。Manifest 把所有这些 commit 汇成一份清单。

跨仓库兼容性检查

55 个仓库各自独立迭代——但 CANN 是一个整体。ops-transformer 依赖 ops-nn 的一个特定 API——如果 ops-nn 的 API 改了,ops-transformer 也必须更新。Manifest 的兼容性检查确保所有仓库的 commit 之间是兼容的。

# manifest/tools/validate_manifest.py

def validate_compatibility(manifest_xml):
    # 解析 manifest
    tree = ET.parse(manifest_xml)
    repos = {}
    for proj in tree.findall("project"):
        repos[proj.get("name")] = proj.get("revision")

    # 已知的跨仓库依赖关系
    deps = {
        "ops-nn": ["opbase"],
        "ops-transformer": ["opbase", "ops-nn"],
        "ascend-transformer-boost": ["ops-transformer", "ge"],
        "cann-recipes-infer": ["ascend-transformer-boost", "runtime"],
    }

    for repo, req_deps in deps.items():
        # 检查被依赖的仓库是否在 manifest 中
        for dep in req_deps:
            if dep not in repos:
                raise ValueError(f"{repo} depends on {dep}, but {dep} not in manifest")

        # 从仓库的 API 版本文件中读取依赖的版本约束
        dep_versions = read_api_versions(repo, repos[repo])
        for dep_name, min_version in dep_versions.items():
            actual_version = read_api_version(dep_name, repos[dep_name])
            if actual_version < min_version:
                raise ValueError(
                    f"{repo}@{repos[repo]} requires {dep_name}>={min_version}, "
                    f"but manifest has {dep_name}@{repos[dep_name]} (v{actual_version})"
                )

兼容性检查在 CI 中跑——Manifest 生成后自动验证,验证失败则不允许发布。

踩坑一:仓库 rebase 后 Manifest 中的 commit hash 失效

开发者对 ops-nn 的 main 分支做了 rebase(压缩 commit 历史)。Manifest 中的 ops-nn commit a1b2c3d 不再存在于远程仓库——repo sync 会报错。

修复:保护 release 分支的 commit 不被 rebase 删除。

# 在每个仓库设置 GitHub 保护规则
# main 分支:禁止 force push(保护 release tag 的 commit 不被重写)
# 每个 release tag(如 v8.0.0):创建后不可删除、不可移动

# 如果确实需要修改 release 分支(严重安全漏洞):
# 1. 更新 Manifest,指向新的 commit
# 2. 撤销旧 release tag,打新 tag
# 3. 发布 hotfix 版本(CANN 8.0.0-hotfix1)

踩坑二:Submodule vs Manifest 的选择

Git submodule 也能管理多仓库——在父仓库里 git submodule add 每个子仓库,submodule 的 commit hash 记录在父仓库中。为什么 CANN 不用 submodule 而用 Manifest + repo 工具?

关键原因:CANN 有 55 个仓库。git submodule update --recursive 在 55 个仓库上是串行的(逐个 clone)——耗时长。repo sync -j16 是并行的(16 个仓库同时 clone)——快得多。Manifest 的另一个优势是分支管理:同一个 manifest 仓库可以维护多条 release 线的 manifest 文件(8.0.x / 8.5.x / rc),而 submodule 的 commit hash 直接绑在父仓库的 Git 历史上——更难做多线维护。

踩坑三:Release Tag 命名不一致

不同的 CANN 仓库用不同的 release tag 格式:

  • ops-nnv8.0.0
  • hcclv2.1.3(版本号独立于 CANN 版本)
  • driver1.76.22.3.220(固件版本号格式)

Manifest 的 resolve_manifest.py 假设所有仓库都用 v{CANN_VERSION} 格式的 tag——碰到 hccl 的 v2.1.3 和 driver 的 1.76.22.3.220 就找不到。

修复:每个仓库加 manifest.tag 配置文件。

# hccl/manifest.tag
# 在 hccl 仓库根目录存放 tag 映射
# CANN 版本 → hccl 的 release tag
8.0.0 = v2.1.3
8.5.0 = v2.2.0

resolve_manifest.py 读取各仓库的 manifest.tag 做映射——CANN 版本和仓库自身的 tag 之间通过这个文件桥接。


Manifest 不是一个会写代码的仓库——它是 55 个仓库的「快照」。每一个 CANN 发行版的构建、测试、发布、部署、回滚——都从这份 manifest XML 文件开始。自动化是 manifest 的核心:manifest 由 CI 自动生成、跨仓库兼容性自动检验、repo sync 自动拉取全部源码。从 manifest 出发,可以重建任意一个 CANN 版本的完整源码树——这是软件工程里的 reproducibility(可重现性),开源社区的基础设施。

Logo

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

更多推荐