第一次给 CANN 社区做贡献?从 community 仓库入手
前言
开源社区是个奇妙的地方。你用着别人免费分享的代码,享受着别人免费提供的文档,突然有一天你想:我是不是也能为这个社区做点贡献?
但紧接着你就被一堆问题拦住了:怎么提 Issue?怎么提 PR?代码规范是什么?社区治理模式是怎样的?第一次贡献会不会被人嘲笑?
昇腾 CANN 社区的 community 仓库,就是为你准备的"新手村指南"。它详细记录了 CANN 社区的治理模式、贡献流程、代码规范、踩坑经验等,让你可以顺畅地完成第一次贡献。
1. CANN 社区治理模式:谁在管理这个社区?
要贡献开源社区,首先要了解这个社区的治理模式。CANN 社区采用的是贡献者驱动+核心团队审核的治理模式。
具体来说,CANN 社区的决策权分为以下几个层级:
1.1 贡献者(Contributor)
只要你给 CANN 社区的任何仓库提过 PR(Pull Request)并且被合并,你就成为了贡献者。
贡献者的权利包括:
- 可以在任意仓库提 Issue 和 PR
- 可以参与技术讨论(在 Issue 和 PR 的评论区)
- 可以获得贡献者证书(电子版,可在简历中展示)
贡献者的义务包括:
- 遵守社区行为准则(Code of Conduct)
- 提 PR 之前先提 Issue 讨论(避免做无用功)
- 及时回复维护者的 Review 意见
1.2 核心贡献者(Core Contributor)
如果你持续给 CANN 社区做贡献(如过去 6 个月内合并了至少 5 个 PR),并且你的贡献被核心团队认可,你可以申请成为核心贡献者。
核心贡献者的权利包括:
- 以上所有贡献者的权利
- 可以 Review 其他人的 PR(但还不能合并 PR)
- 可以参与社区的技术决策投票(如 API 设计、Roadmap 规划等)
核心贡献者的义务包括:
- 以上所有贡献者的义务
- 及时 Review 其他人的 PR(通常在 3 个工作日内完成)
- 参与技术决策投票(如果不投票,视为弃权)
1.3 维护者(Maintainer)
如果你在某个仓库的持续贡献被核心团队认可,并且你愿意承担维护责任,你可以被提名成为维护者。
维护者的权利包括:
- 以上所有核心贡献者的权利
- 可以合并 PR(拥有 Write 权限)
- 可以管理 Issue(关闭、打标签、指派负责人等)
- 可以参与社区的治理决策投票(如 Release 计划、重大技术方向等)
维护者的义务包括:
- 以上所有核心贡献者的义务
- 及时响应 Issue 和 PR(通常在 2 个工作日内)
- 保证代码质量(不合并低质量的 PR)
- 参与社区的治理决策投票
1.4 核心团队(Core Team)
核心团队是 CANN 社区的最终决策机构,由华为昇腾部门的资深工程师组成。
核心团队的权利包括:
- 以上所有维护者的权利
- 可以管理仓库(创建、归档、修改权限等)
- 可以修改社区治理规则
- 可以任命/罢免维护者
核心团队的义务包括:
- 以上所有维护者的义务
- 保证社区的健康发展(技术方向、生态建设等)
- 及时响应重大技术问题(如安全漏洞、许可证问题等)
2. 如何提 Issue:让你的声音被听到
在 CANN 社区,Issue 是技术讨论的起点。无论你是想报告 Bug、请求新功能、提问使用方法,还是想讨论技术方向,都应该先提 Issue。
2.1 Issue 的类型
CANN 社区的 Issue 分为以下几种类型:
Bug Report(Bug 报告):你发现了一个可复现的 Bug,想让维护者修复它
Feature Request(功能请求):你想要一个新功能,但不知道维护者愿不愿意实现
Question(使用提问):你不知道怎么用某个功能,想请教学社区
Documentation Improvement(文档改进):你发现文档有错误/不完善,想帮它改进
Technical Discussion(技术讨论):你想讨论某个技术方向,看看社区的意见
2.2 如何提一个高质量的 Issue?
提 Issue 很容易,但提一个高质量的 Issue 不容易。以下是提 Issue 的最佳实践:
1. 先搜索,再提问
在提 Issue 之前,先用搜索功能看看有没有人提过类似的问题。如果有,直接在那个 Issue 下面评论,不要重复提。
2. 选对仓库
CANN 社区有很多仓库(如 ops-math、ops-nn、ATB 等),每个仓库负责不同的功能模块。提 Issue 之前,先确认你的问题属于哪个仓库。如果不确定,可以提在community仓库,维护者会帮你转移。
3. 用清晰的标题
标题要简洁明了,让人一眼就能看出你的问题是什么。例如:
- ❌ 不好:
一个 Bug - ✅ 好:
[Bug] ops-math: matmul_fp16 在 M=1024, N=1024, K=512 时结果错误
4. 提供完整的复现步骤
如果是 Bug Report,一定要提供完整的复现步骤,包括:
- 环境信息(CANN 版本、NPU 型号、操作系统等)
- 最小可复现代码(Minimal Reproducible Example)
- 期望行为 vs 实际行为
- 错误日志(如果有)
以下是一个高质量的 Bug Report 模板:
## Bug 描述 ops-math 的 matmul_fp16 算子在计算形状为 (1024, 512) x (512, 1024) 的矩阵乘法时,结果跟 NumPy 的参考实现不一致,最大相对误差达到 5%。 ## 环境信息 - CANN 版本:6.0.RC1 - NPU 型号:昇腾 910 - 操作系统:Ubuntu 20.04 - Python 版本:3.8 ## 复现步骤 运行以下代码: ```python import numpy as np import asnumpy as anp np.random.seed(42) A_np = np.random.randn(1024, 512).astype(np.float16) B_np = np.random.randn(512, 1024).astype(np.float16) A_npu = anp.array(A_np).to_npu() B_npu = anp.array(B_np).to_npu() C_np = np.matmul(A_np, B_np) C_npu = anp.matmul(A_npu, B_npu).to_cpu() rel_error = np.abs(C_np - C_npu) / (np.abs(C_np) + 1e-8) print(f"最大相对误差: {rel_error.max():.6f}")期望行为
最大相对误差应该 < 1e-3。
实际行为
最大相对误差 = 0.05(5%)。
错误日志
(如果有报错,贴在这里)
**这段代码背后的 WHY**: 提供最小可复现代码的目的是:**让维护者能够用最少的精力复现你的问题**。如果维护者无法复现你的问题,他们就无法修复它。所以,你提供的信息越详细,你的 Issue 被修复的概率就越高。 ### 2.3 Issue 的生命周期 一个 Issue 从创建到关闭,通常会经历以下几个状态: 1. **Open(打开)**:你刚创建了 Issue,等待维护者响应 2. **Triaged(已分类)**:维护者已经看到了 Issue,给它打上了标签(如 `bug`、`feature request`、`question` 等) 3. **In Progress(进行中)**:维护者正在处理这个 Issue(或者有人认领了这个 Issue) 4. **Resolved(已解决)**:维护者已经修复了这个 Bug(或者实现了这个新功能),并且合并了对应的 PR 5. **Closed(已关闭)**:Issue 已经解决,维护者关闭了它 ## 3. 如何提 PR:让你的代码进入官方仓库 PR(Pull Request)是向 CANN 社区贡献代码的方式。无论你是修复 Bug、实现新功能、改进文档,都需要提 PR。 ### 3.1 PR 的生命周期 一个 PR 从创建到合并,通常会经历以下几个状态: 1. **Draft(草稿)**:你还在写代码,不想让别人 Review。这时候可以提 Draft PR 2. **Open(打开)**:你写完了代码,想让别人 Review。这时候可以点击 "Ready for Review" 3. **In Review(审查中)**:维护者正在 Review 你的代码,可能会提出修改意见 4. **Changes Requested(需要修改)**:维护者提出了修改意见,你需要按照意见修改代码 5. **Approved(已批准)**:维护者认为你的代码质量合格,批准了你的 PR 6. **Merged(已合并)**:维护者把你的代码合并到了主分支,你的贡献正式生效了! ### 3.2 如何提一个高质量的 PR? 提 PR 很容易,但提一个高质量的 PR 不容易。以下是提 PR 的最佳实践: **1. 先提 Issue 讨论,再提 PR** 除非是微小的改动(如修复文档 Typo),否则一定要先提 Issue 讨论。这样可以确保: 1. 你要做的事跟社区的技术方向一致(避免做无用功) 2. 维护者认可你的方案(避免写了几千行代码后被要求重写) **2. 一个 PR 只做一件事** 不要在一个 PR 里同时修复 Bug、实现新功能、重构代码。这样会让 Review 变得困难,也会增加合并冲突的概率。 **3. 写好 Commit Message** Commit Message 要清晰明了,让人一眼就能看出你做了什么改动。推荐的格式是:():
其中:
<type>:改动类型,如fix(修复 Bug)、feat(新功能)、docs(文档改动)、refactor(重构)等<scope>:改动范围,如ops-math、ATB、docs等<subject>:简短描述(不超过 50 个字符)<body>:详细描述(可以有多行,说明为什么要做这个改动、怎么做的、有什么影响等)<footer>:关联 Issue(如Closes #123、
以下是一个高质量的 Commit Message 示例:
fix(ops-math): 修复 matmul_fp16 在 K%64!=0 时的精度问题 问题原因: matmul_fp16 算子在 K 维度不是 64 的倍数时,会触发 Fallback 到 CPU 计算的逻辑,导致性能下降 10 倍以上。 解决方案: 修改 Tiling 策略,让 K 维度的分块大小可以不是 64 的倍数。 具体改动: 1. 修改 `tiling_strategy.cpp` 中的 `ComputeTileSizeK()` 函数 2. 添加单元测试 `test_matmul_fp16_tiling.py`,覆盖 K%64!=0 的场景 性能影响: 在 K=1024 的场景下,延迟从 18 ms 降低到 12 ms(提升 33%)。 Closes #4564. 添加单元测试
没有单元测试的 PR 通常不会被合并。你需要为你新增/修改的代码添加单元测试,确保:
- 功能正确性(跟参考实现对比)
- 边界条件正确性(如输入形状为 0、输入数据类型不支持等)
- 性能没有回退(跟之前的版本对比)
以下是一个高质量的单元测试示例:
importnumpyasnpimportasnumpyasanpimportpytestdeftest_matmul_fp16_correctness():"""测试 matmul_fp16 的计算正确性"""np.random.seed(42)A_np=np.random.randn(1024,512).astype(np.float16)B_np=np.random.randn(512,1024).astype(np.float16)A_npu=anp.array(A_np).to_npu()B_npu=anp.array(B_np).to_npu()C_np=np.matmul(A_np,B_np)C_npu=anp.matmul(A_npu,B_npu).to_cpu()rel_error=np.abs(C_np-C_npu)/(np.abs(C_np)+1e-8)assertrel_error.max()<1e-3,f"最大相对误差超标:{rel_error.max():.6f}"deftest_matmul_fp16_tiling_k_not_multiple_of_64():"""测试 K 维度不是 64 的倍数时,matmul_fp16 仍然正确"""np.random.seed(42)A_np=np.random.randn(1024,520).astype(np.float16)# K=520,不是 64 的倍数B_np=np.random.randn(520,1024).astype(np.float16)A_npu=anp.array(A_np).to_npu()B_npu=anp.array(B_np).to_npu()C_np=np.matmul(A_np,B_np)C_npu=anp.matmul(A_npu,B_npu).to_cpu()rel_error=np.abs(C_np-C_npu)/(np.abs(C_np)+1e-8)assertrel_error.max()<1e-3,f"最大相对误差超标:{rel_error.max():.6f}")deftest_matmul_fp16_performance():"""测试 matmul_fp16 的性能没有回退"""np.random.seed(42)A_np=np.random.randn(1024,1024).astype(np.float16)B_np=np.random.randn(1024,1024).astype(np.float16)A_npu=anp.array(A_np).to_npu()B_npu=anp.array(B_np).to_npu()# 预热_=anp.matmul(A_npu,B_npu)anp.cuda.synchronize()# 计时start=time.time()_=anp.matmul(A_npu,B_npu)anp.cuda.synchronize()end=time.time()latency=end-startassertlatency<0.015,f"延迟回退:{latency:.3f}s (阈值: 0.015 s)"这段代码背后的 WHY:
写单元测试的目的是:确保你新增/修改的代码不会引入 Bug,也不会导致性能回退。CANN 社区非常看重代码质量,没有单元测试的 PR 通常不会被合并。
3.3 Code Review 的常见意见
当你提了 PR 之后,维护者会对你的代码进行 Review。以下是 Code Review 中常见的意见:
1. 代码风格不符合规范
CANN 社区有详细的代码规范(在community仓库的CODE_STYLE.md文件中)。如果你的代码风格不符合规范,维护者会要求你修改。
常见的代码风格问题包括:
- 缩进不正确(应该用 4 个空格,而不是 Tab)
- 变量命名不规范(应该用
snake_case,而不是camelCase) - 注释不足(每个函数都应该有 Docstring)
- 行长度超限(每行不应该超过 80 个字符)
2. 缺少单元测试
如前所述,没有单元测试的 PR 通常不会被合并。
3. 性能有回退
如果你的 PR 导致了性能回退(如某个算子的延迟增加了 10%),维护者会要求你解释原因,或者要求你优化性能。
4. 兼容性问题
如果你的 PR 破坏了向后兼容性(如修改了某个 API 的函数签名),维护者会要求你提供迁移指南,或者要求你用 Deprecation 的方式渐进式修改。
4. 代码规范:让你的代码融入社区
CANN 社区有详细的代码规范,涵盖代码风格、命名约定、注释规范、测试规范等。
4.1 C++ 代码规范
CANN 社区的 C++ 代码规范基于 Google C++ Style Guide,但有一些自定义的规则。
以下是几个关键点:
1. 缩进和空格
// ❌ 不好:用了 Tab 缩进voidfoo(){intx=1;// Tab}// ✅ 好:用了 2 个空格缩进voidfoo(){intx=1;// 2 个空格}2. 变量命名
// ❌ 不好:用了 camelCaseintmyVariable=1;// ✅ 好:用了 snake_caseintmy_variable=1;3. 函数命名
// ❌ 不好:首字母小写voidmyFunction(){// ...}// ✅ 好:首字母大写voidMyFunction(){// ...}4. 注释规范
// ❌ 不好:注释太少intAdd(inta,intb){returna+b;}// ✅ 好:有完整的 Docstring// 计算两个整数的和。//// 参数:// a: 第一个加数// b: 第二个加数//// 返回:// a 和 b 的和//// 示例:// Add(1, 2) 返回 3intAdd(inta,intb){returna+b;}4.2 Python 代码规范
CANN 社区的 Python 代码规范基于 PEP 8,但有一些自定义的规则。
以下是几个关键点:
1. 缩进和空格
# ❌ 不好:用了 Tab 缩进deffoo():intx=1# Tab# ✅ 好:用了 4 个空格缩进deffoo():x=1# 4 个空格2. 变量命名
# ❌ 不好:用了 camelCasemyVariable=1# ✅ 好:用了 snake_casemy_variable=13. 函数命名
# ❌ 不好:用了 camelCasedefmyFunction():pass# ✅ 好:用了 snake_casedefmy_function():pass4. 注释规范
# ❌ 不好:注释太少defadd(a,b):returna+b# ✅ 好:有完整的 Docstringdefadd(a,b):"""计算两个数的和。 参数: a: 第一个加数 b: 第二个加数 返回: a 和 b 的和 示例: >>> add(1, 2) 3 """returna+b5. 典型贡献流程:从 Idea 到 Merged PR
讲了这么多规则和流程,你可能会问:到底怎么完成第一次贡献?这里给出一个完整的、端到端的贡献流程。
5.1 阶段 1:找到可以贡献的方向
如果你不知道从哪里开始贡献,以下几个方向适合新手:
- 修复文档 Typo:这是最简单的贡献,适合第一次贡献的人
- 修复简单的 Bug:在 Issue 列表中找标签为
good first issue的 Bug - 改进文档:很多仓库的文档都不完善,你可以帮它改进
- 添加单元测试:很多算子都缺少单元测试,你可以帮它补充
5.2 阶段 2:提 Issue 讨论
找到可以贡献的方向后,先提 Issue 讨论。这可以避免做无用功。
Issue 的内容应该包括:
- 你想做什么贡献
- 为什么要做这个贡献(解决了什么问题)
- 打算怎么做(技术方案)
5.3 阶段 3:Fork 仓库并克隆到本地
Issue 讨论通过后,可以开始写代码了。
第一步是 Fork 仓库(点击 GitHub/Gitee 页面右上角的 “Fork” 按钮),然后克隆到本地:
# 克隆你 Fork 的仓库gitclone https://atomgit.com/<your-username>/<repo-name>.gitcd<repo-name># 添加上游仓库gitremoteaddupstream https://atomgit.com/cann/<repo-name>.git5.4 阶段 4:创建分支并写代码
不要直接在main分支上写代码。创建一个新分支:
# 创建并切换到新分支gitcheckout-bfix-matmul-fp16-accuracy# 写代码...写代码的过程中,记得经常提交:
# 查看修改gitstatusgitdiff# 暂存修改gitadd<modified-files># 提交gitcommit-m"fix(ops-math): 修复 matmul_fp16 在 K%64!=0 时的精度问题"5.5 阶段 5:推送分支并提 PR
写完了代码,推送到你的 Fork 仓库,然后提 PR:
# 推送分支到你的 Fork 仓库gitpush origin fix-matmul-fp16-accuracy推送完成后,打开 GitHub/Gitee 页面,你会看到一个 “Compare & Pull Request” 的按钮。点击它,填写 PR 描述,然后提交。
PR 描述应该包括:
- 这个 PR 做了什么(简短描述)
- 为什么做这个 PR(解决了什么问题)
- 怎么做的(技术方案)
- 关联 Issue(如
Closes #123)
5.6 阶段 6:参与 Code Review 并修改代码
提了 PR 之后,维护者会对你的代码进行 Review。如果维护者提出了修改意见,按照意见修改代码:
# 修改代码...# 提交修改gitadd<modified-files>gitcommit-m"address review comments"# 推送修改gitpush origin fix-matmul-fp16-accuracy5.7 阶段 7:PR 被合并,成为贡献者!
当维护者批准了你的 PR 并合并了它,恭喜你,你正式成为了 CANN 社区的贡献者!
你可以把你的贡献者证书(在community仓库的contributors/目录下)加到你的简历里去。
6. 踩坑经验全记录
最后,分享一些在 CANN 社区贡献时常见的坑,以及对应的解决方案。
6.1 坑 1:PR 的 CI/CD 失败
现象:提了 PR 之后,CI/CD(持续集成/持续部署)失败了,导致无法合并 PR。
原因:CI/CD 会自动运行单元测试、代码风格检查、性能基准测试等。如果任何一个测试失败了,CI/CD 就会失败。
解决方案:
- 在本地运行单元测试,确保它们都通过:
cd<repo-name>python-mpytest tests/ - 在本地运行代码风格检查,确保没有风格问题:
cd<repo-name>python-mflake8.# Python 代码python-mcpplint.# C++ 代码 - 如果本地测试都通过了,但 CI/CD 还是失败,可能是 CI/CD 环境的问题。这时候可以联系维护者帮忙排查。
6.2 坑 2:PR 跟 main 分支有合并冲突
现象:提了 PR 之后,发现跟main分支有合并冲突,导致无法合并 PR。
原因:在你写代码的过程中,main分支可能有其他人提交了新的代码,跟你的修改冲突了。
解决方案:
- 拉取上游仓库的最新代码:
gitfetch upstream - 合并上游仓库的
main分支到你的分支:gitmerge upstream/main - 如果有合并冲突,手动解决冲突(编辑冲突文件,然后
git add和git commit) - 推送解决冲突后的代码到你的 Fork 仓库:
gitpush origin fix-matmul-fp16-accuracy
community 仓库地址:https://atomgit.com/cann/community,欢迎访问获取最新代码和文档。如果你在使用过程中遇到问题,欢迎在仓库提 Issue,社区会及时响应。
