Ch26: 开源竞赛贡献路径——如何向 ncnn 提交第一个 PR
Part 5: 实战与竞赛 前置章节:Ch25: MNN 实战——转换、推理、量化一条龙 后续章节:Ch27: 跨赛道 YOLO 到端侧——MoE 导出与引擎选型
本章目标
前面几章教你怎么”用”推理引擎。这一章换一个方向:怎么向推理引擎项目贡献代码,把”用户”变成”贡献者”。
以 ncnn 为例(因为它代码量最小、架构最简单、最适合新人入手),这一章会覆盖:从理解项目治理结构、到选择合适的贡献方向、到写出第一个能被合并的 PR 的完整路径。
这不是一个泛泛的”如何参与开源”指南。ncnn 有它独特的项目文化——单人维护者(nihui)、没有 good-first-issue 标签、代码风格非常古典(不用 C++11)、PR review 周期可能很长。这一章的建议都是针对这些具体特点给出的。
读完这一章,你应该能:
- 理解 ncnn 项目的治理结构和代码规范
- 评估五个贡献方向(Python bindings → PNNX → 文档 → C++ Layer → Vulkan)的难度和合并概率
- 从 Issue 列表中识别适合新人的候选任务
- 知道”从用户问题反推文档 PR”这个最低风险的贡献策略
- 执行一个完整的”第一周行动计划”
前置准备
- 已完成 Ch24(能在本地编译运行 ncnn)
- 有 GitHub 账号
- 了解 Git 基本操作(fork、branch、commit、push、PR)
- 可选:读过 ncnn 的部分源码(Ch11 的内容)
ncnn 项目的独特文化
在向 ncnn 贡献代码之前,必须先理解这个项目的运作方式——它和你想象中的”大型开源项目”可能很不一样。
单人维护者模式
ncnn 的核心维护者只有一个人:nihui(GitHub: @nihui)。他是这个项目的创建者、主要贡献者、唯一有合并权限的人。所有 PR 都由他一个人 review 和合并。
这意味着:
- PR 的 review 周期不可预测——nihui 可能一天合并十个 PR,也可能两周不看
- 不需要讨好多个 reviewer,只需要理解 nihui 的代码偏好
- PR 描述要简洁直接——nihui 的 review 风格是”看代码说话”,不需要长篇大论
- 如果 PR 长时间没有回应,可以礼貌地 ping 一下,但不要催
没有 good-first-issue
大多数开源项目会用 good-first-issue 标签标记适合新人的任务。ncnn 没有这个标签——nihui 没有精力去整理和标注这些任务。这意味着你需要自己从 Issue 列表中挖掘适合入手的任务,后面会讲怎么挖。
代码风格:古典 C++
ncnn 的代码风格是非常有意识的选择,不是”不知道有 C++11”:
// ncnn 的风格(必须遵守)
for (int i = 0; i < n; i++) // 不用 range-based for
{ // 花括号换行
float* ptr = (float*)data; // C-style cast, 不用 static_cast
// 不用 auto
// 不用 std::vector(用 ncnn::Mat 或 raw array)
// 不用 std::shared_ptr(用 raw pointer + 手动管理)
// 不用 lambda
// 不用 constexpr
}
为什么这样做?回忆 Ch11 的”零依赖”哲学——ncnn 要在所有 C++ 编译器上编译通过,包括一些很老的嵌入式编译器。使用 C++11 特性会排除这些编译器。这不是技术债,是有意的设计决策。
具体的格式规范:
- Tab 转 4 空格:不使用 Tab 字符
- if-else 花括号换行:花括号独占一行
- 头文件:先 ncnn 内部头文件,再标准库头文件
- 命名:类名 PascalCase,函数名和变量名 snake_case
提交 PR 后,restyled-io bot 会自动检查代码格式。如果格式不对,bot 会提交一个修复 PR 到你的分支——你需要 merge 它,或者直接在本地用 clang-format 修复后 push。
CLA 签署
第一次向 ncnn 提交 PR 时,需要签署 Contributor License Agreement(CLA)。GitHub 上会有一个 bot 自动提示你签署,按照指引操作就行。签署一次永久有效。
五个贡献方向
向 ncnn 贡献代码有多个切入点,难度和合并概率差异很大。按照从易到难的顺序:
方向 1: Python bindings(难度:低,合并率:高)
ncnn 的 Python 绑定是一个相对独立的子系统,代码在 python/ 目录下。贡献 Python bindings 的好处是:不需要理解 ncnn 的 C++ 核心代码,只需要理解 pybind11 的使用方式。
可能的贡献点:
- 补充 Python API 的文档字符串
- 为新增的 C++ API 添加 Python 绑定
- 修复 Python 绑定的 bug(比如某些参数类型的转换问题)
- 改善 pip 打包流程(比如支持更多平台的预编译 wheel)
// python/ncnn/ncnn.cpp 中的一个典型绑定示例
py::class_<Mat>(m, "Mat")
.def(py::init<>())
.def(py::init<int>())
.def(py::init<int, int>())
.def(py::init<int, int, int>())
// 贡献点:为缺少的构造函数添加绑定
// 贡献点:改善文档字符串
.def("fill", &Mat::fill, "Fill the mat with a value")
;
方向 2: PNNX 算子支持(难度:中,合并率:中高)
PNNX 是 ncnn 的 PyTorch 导出工具,代码在 tools/pnnx/ 目录下。随着 PyTorch 版本更新,新的算子和模块不断出现,PNNX 需要持续跟进支持。
贡献方式:
- 在 PNNX 的 Issue 或测试中找到不支持的 PyTorch 算子
- 在
tools/pnnx/src/pass_level1/或pass_level2/中添加对应的转换逻辑 - 添加测试用例
// tools/pnnx/src/pass_level2/F_silu.cpp
// 一个典型的 PNNX pass 实现
#include "pass_level2.h"
namespace pnnx {
class F_silu : public GraphRewriterPass
{
public:
const char* match_pattern_graph() const
{
return R"PNNXIR(
%output = aten::silu(%input)
)PNNXIR";
}
const char* type_str() const
{
return "F.silu";
}
};
REGISTER_GLOBAL_PNNX_GRAPH_REWRITER_PASS(F_silu, 10)
} // namespace pnnx
这个方向的好处是:每个算子是一个独立的文件,修改范围明确,不容易影响其他功能。
方向 3: 文档和示例(难度:低,合并率:高)
ncnn 的文档一直是被诟病的地方——官方 wiki 内容有限,很多高级用法只能从源码或 Issue 讨论中找到。贡献文档是最低风险的方式:不会引入 bug,nihui 也欢迎文档改善。
可能的贡献点:
- 完善 wiki 页面的使用说明
- 为常见问题写 FAQ
- 为新功能写使用教程
- 翻译已有的英文文档(ncnn 的用户群体中中文开发者占很大比例)
“从用户问题反推文档 PR”策略: 这是最低风险、最高合并率的贡献路径。具体做法:
- 浏览 ncnn 的 Issue 列表和 Discussions
- 找到反复出现的问题(比如”如何在 Apple Silicon 上编译”、”量化参数怎么设置”)
- 把这些问题的解答整理成文档,提交 PR
这种 PR 几乎不可能被拒绝——它不改变任何代码,不引入任何风险,但切实帮助了用户。
方向 4: C++ Layer 实现(难度:高,合并率:中)
在 ncnn 中添加一个新的 C++ Layer(算子)是更深层次的贡献,需要理解 ncnn 的 Layer 抽象(Ch11 和 Ch18 的内容)。
一个新 Layer 通常包含以下文件:
src/
├── layer/
│ ├── mylayer.h # Layer 声明
│ └── mylayer.cpp # 通用实现
│ ├── arm/
│ │ └── mylayer_arm.cpp # ARM NEON 优化实现
│ └── x86/
│ └── mylayer_x86.cpp # x86 SSE/AVX 优化实现
// layer/mylayer.h
#include "layer.h"
namespace ncnn {
class MyLayer : public Layer
{
public:
MyLayer();
virtual int load_param(const ParamDict& pd);
virtual int forward(const Mat& bottom_blob, Mat& top_blob,
const Option& opt) const;
public:
int param_a;
float param_b;
};
} // namespace ncnn
这个方向的挑战是:ncnn 对性能要求极高,你的通用实现(mylayer.cpp)需要正确,但如果没有 ARM/x86 的优化实现,实际使用中性能可能不够好。nihui 可能会要求你同时提供优化实现,或者自己后续添加。
方向 5: Vulkan shader(难度:极高,合并率:低)
Vulkan 是 ncnn 的 GPU 计算后端,shader 代码在 src/layer/vulkan/ 和 src/gpu.cpp 中。编写 Vulkan compute shader 需要理解 SPIR-V、descriptor set、pipeline layout 等 GPU 编程概念。
除非你已经有 Vulkan 开发经验,否则不建议从这个方向入手。
难度-收益矩阵
| 方向 | 技术难度 | 合并概率 | 学习价值 | 推荐优先级 |
|---|---|---|---|---|
| Python bindings | 低 | 高 | 中 | 新手首选 |
| 文档/FAQ | 低 | 很高 | 低 | 最低风险 |
| PNNX 算子 | 中 | 中高 | 高 | 有一定基础后 |
| C++ Layer | 高 | 中 | 很高 | 理解架构后 |
| Vulkan shader | 极高 | 低 | 极高 | GPU 专家 |
从 Issue 列表挖掘任务
ncnn 没有 good-first-issue 标签,但 Issue 列表中有很多适合新人的任务。关键是知道怎么筛选。
候选 Issue 类型
类型 1: 编译/安装问题(最容易切入)
比如 Issue #6783(Android whl 构建问题)。这类 Issue 通常涉及 CMake 配置、平台兼容性、依赖管理,不需要理解 ncnn 的核心推理逻辑。
筛选关键词:build、compile、install、wheel、pip、cmake
# 在 GitHub Issue 搜索框中搜索
is:issue is:open label:build
is:issue is:open "pip install" OR "cmake" OR "compile error"
类型 2: PNNX 算子缺失
比如 Issue #6741(repeat_interleave 不支持)。PNNX 每增加一个 PyTorch 算子的支持,就是一个独立的、范围清晰的 PR。
筛选关键词:pnnx、unsupported、not supported、export
类型 3: 转换工具的边界情况
比如 Issue #6614(PNNX 产生异常浮点数值)。这类 Issue 通常是某个特定模型触发了转换工具的 bug,修复范围明确。
类型 4: 文档和 troubleshooting
比如 Issue #6665(troubleshooting 指南请求)。这类 Issue 明确要求文档改善,是最容易贡献的类型。
Issue 分析示例
以 Issue #6741 为例,分析它是否适合作为第一个 PR:
标题: PNNX does not support torch.repeat_interleave
内容: 用户报告 PNNX 在转换含 repeat_interleave 的模型时失败
分析:
- 范围明确:只需要在 PNNX 中添加 repeat_interleave 的转换逻辑
- 参考先例:可以参考已有的类似算子(如 repeat、tile)的实现
- 测试方便:用一个简单的 PyTorch 模型就能验证
- 风险低:不影响 ncnn 核心推理逻辑
结论:适合作为 PNNX 方向的第一个 PR
不适合新人的 Issue 特征
- 标题含 “performance”、”slow”、”optimization”——性能优化需要深入理解内部实现
- 标题含 “vulkan”、”shader”——GPU 相关的改动门槛极高
- 标题含 “memory leak”、”crash”——内存相关的 bug 通常根因复杂
- nihui 已经说了 “I will fix this”——说明他计划自己处理
从用户问题反推文档 PR
这个策略值得单独展开讲,因为它是最适合新人的贡献方式。
步骤 1: 收集高频问题
花一两个小时浏览 ncnn 的 Issues 和 Discussions,找出反复出现的问题。以下是一些真实的高频问题:
频次高的问题:
1. "How to build ncnn on Apple Silicon Mac?" (每月至少 3 次)
2. "What are the mean and norm values for my model?" (每月至少 5 次)
3. "onnx2ncnn reports unsupported op, what to do?" (每月至少 4 次)
4. "benchncnn shows different results each run" (每月至少 2 次)
5. "How to use ncnn in Android Studio project?" (每月至少 3 次)
步骤 2: 整理解答
把这些问题的解答整理成结构化的文档。不需要从零写——很多解答散落在 Issue 评论、nihui 的回复、以及其他贡献者的博客中,你的工作是把它们汇总、验证、整理。
## FAQ: Apple Silicon Mac 上编译 ncnn
### 问题描述
在 M1/M2/M3 Mac 上 `pip install ncnn` 失败,或者编译出来的库在运行时报错。
### 原因
PyPI 上没有 Apple Silicon (arm64) 的预编译 wheel。如果你在 Rosetta 2
环境下编译,会得到 x86_64 版本的库,无法在 arm64 Python 中使用。
### 解决方案
从源码编译,确保使用原生 arm64 工具链:
1. 确认 Terminal 是原生 arm64 模式:
$ arch
arm64 # 如果显示 i386,说明在 Rosetta 下
2. 确认 Python 是 arm64 版本:
$ python3 -c "import platform; print(platform.machine())"
arm64
3. 从源码编译:
(具体命令...)
步骤 3: 提交 PR
# Fork ncnn 仓库后
git clone https://github.com/your-username/ncnn.git
cd ncnn
git checkout -b docs/apple-silicon-faq
# 编辑文档
vim docs/faq.md
# 提交
git add docs/faq.md
git commit -m "docs: add FAQ for building on Apple Silicon Mac"
git push origin docs/apple-silicon-faq
# 在 GitHub 上创建 PR
PR 描述模板:
## 说明
整理了 Apple Silicon Mac 上编译 ncnn 的常见问题和解决方案。
这些问题在 Issue 中反复出现(#xxxx, #yyyy, #zzzz),
希望一个集中的 FAQ 能帮助新用户少走弯路。
## 内容
- 编译环境检查(arch、Python 版本)
- 从源码编译的完整步骤
- 常见报错的排查方法
## 相关 Issue
- #xxxx: Build fails on M1 Mac
- #yyyy: pip install ncnn error on arm64
代码贡献的完整流程
如果你决定做代码贡献(比如 PNNX 算子或 Python bindings),完整的流程如下:
步骤 1: Fork 和克隆
# 在 GitHub 上 Fork ncnn 仓库
# 克隆你的 Fork
git clone https://github.com/your-username/ncnn.git
cd ncnn
git submodule update --init
# 添加上游仓库
git remote add upstream https://github.com/Tencent/ncnn.git
步骤 2: 创建分支
# 从最新的 master 分支创建你的工作分支
git fetch upstream
git checkout -b fix/pnnx-repeat-interleave upstream/master
分支命名建议:
fix/xxx:修复 bugfeat/xxx:新功能docs/xxx:文档改善pnnx/xxx:PNNX 相关
步骤 3: 编写代码和测试
# 编写代码
vim tools/pnnx/src/pass_level2/torch_repeat_interleave.cpp
# 添加测试
vim tools/pnnx/tests/test_torch_repeat_interleave.py
# 本地编译验证
mkdir -p build && cd build
cmake -DNCNN_BUILD_TOOLS=ON ..
make -j$(nproc 2>/dev/null || sysctl -n hw.ncpu)
# 运行测试
cd ../tools/pnnx/tests
python test_torch_repeat_interleave.py
步骤 4: 格式化代码
# 确保代码格式符合 ncnn 规范
# Tab → 4 空格
find . -name "*.cpp" -o -name "*.h" | xargs sed -i 's/\t/ /g'
# 花括号换行
# 这个需要手动检查,自动格式化工具可能处理不好
步骤 5: 提交和推送
# 一个 PR 一件事——不要把多个不相关的改动塞在一个 commit 里
git add tools/pnnx/src/pass_level2/torch_repeat_interleave.cpp
git add tools/pnnx/tests/test_torch_repeat_interleave.py
git commit -m "pnnx: support torch.repeat_interleave"
git push origin fix/pnnx-repeat-interleave
步骤 6: 创建 PR 并等待 review
PR 创建后,CI 会自动运行。需要注意:
- CI 必须全绿——如果有 CI 失败,先修复再请求 review
- restyled-io bot 可能提交格式修复——接受它或自己修复
- CLA bot 会要求签署 CLA——按提示操作
- nihui 的 review 可能来得很快,也可能需要等一两周
如果一周后没有回应,可以在 PR 中 @nihui 礼貌地请求 review:
@nihui Could you please take a look when you have time? Thanks!
第一周行动计划
如果你决定向 ncnn 贡献代码,这是一个推荐的五天行动计划:
Day 1: 编译和熟悉
目标:在本地编译成功 ncnn(含所有工具和测试)
1. Fork + 克隆仓库
2. 编译 ncnn(开启全部工具):
cmake -DCMAKE_BUILD_TYPE=Release \
-DNCNN_BUILD_TOOLS=ON \
-DNCNN_BUILD_BENCHMARK=ON \
-DNCNN_PYTHON=ON \
..
make -j$(nproc)
3. 运行 benchncnn 确认编译成功
4. 跑几个 example(squeezenet 分类)
5. 记录遇到的任何问题(这些问题本身就是文档贡献的素材)
Day 2: 阅读贡献文档和 Issue
目标:理解项目规范 + 选定贡献方向
1. 阅读 CONTRIBUTING.md(如果有的话)
2. 浏览最近 50 个 Issue,标记感兴趣的
3. 浏览最近 20 个已合并的 PR,学习 nihui 的 review 风格
4. 特别关注被快速合并的 PR——它们的共同点是什么?
5. 决定自己的贡献方向(建议从文档或 Python bindings 开始)
Day 3: 复现一个 Issue
目标:深入理解一个具体问题
1. 选择一个你标记的 Issue
2. 在本地复现这个问题
3. 阅读相关源码,理解问题的根因
4. 如果能修复,写修复代码
5. 如果不能修复,记录你的分析(这也可以作为 Issue 评论提交)
Day 4: 写 PR
目标:提交第一个 PR
1. 确保代码正确、测试通过
2. 确保代码格式符合规范
3. 写清晰的 PR 描述
4. 签署 CLA
5. 提交 PR,等待 CI
Day 5: 响应 CI 和 review
目标:让 PR 达到可合并状态
1. 检查 CI 结果,修复失败的测试
2. 接受 restyled-io 的格式修复
3. 如果 nihui 有 review 意见,及时响应
4. 如果还没有 review,开始准备第二个 PR
5. 在 Issue 中帮助其他用户解答问题(建立社区信任)
代码规范速查
提交 PR 前,对照这个清单自检:
[ ] 不使用 C++11 及以上特性(auto、lambda、range-for、constexpr 等)
[ ] Tab 已转换为 4 空格
[ ] if-else 的花括号独占一行
[ ] 不使用 STL 容器(用 ncnn::Mat 或 raw array)
[ ] C-style cast(不用 static_cast/dynamic_cast/reinterpret_cast)
[ ] 头文件顺序:ncnn 内部 → 标准库
[ ] 变量名 snake_case,类名 PascalCase
[ ] 一个 commit 做一件事
[ ] commit message 简洁明了(如 "pnnx: support torch.repeat_interleave")
[ ] 新增代码有对应的测试
[ ] CI 全绿
[ ] CLA 已签署
超越 ncnn:其他引擎的贡献机会
如果你对 ncnn 之外的引擎也感兴趣,这里简要介绍其他引擎的贡献特点:
MNN(阿里巴巴):
- 维护团队更大(多人 review)
- 有正式的贡献指南和代码规范
- 使用现代 C++(C++11/14),门槛相对 ncnn 更低
- 算子覆盖面广,贡献新算子的机会相对少,但性能优化的空间大
- Issue 响应通常比 ncnn 快
TNN(腾讯):
- 与 ncnn 同属腾讯,但团队不同
- 代码风格更现代
- 贡献活跃度近年有所下降
- Metal(iOS)后端的贡献机会较多
ONNX Runtime(微软):
- 大型开源项目,有完善的贡献流程
- 有 good-first-issue 标签
- PR review 严格且规范
- 适合想要参与大型企业级开源项目的开发者
常见问题排查
Q: PR 提交后 CI 一直是 pending 状态?
A: ncnn 的 CI 有时候会排队。如果超过 24 小时还是 pending,可能是 CI 系统的问题,在 PR 中 comment 说一下就好。
Q: nihui 要求改动很大、不确定怎么改?
A: 直接在 PR 评论中问。nihui 的回复通常很简洁但准确。也可以参考他在其他 PR 中对类似问题的 review 意见。
Q: 提交的 PR 被关闭了怎么办?
A: 不要灰心。可能的原因:PR 的方向不符合项目规划、代码质量需要大幅改进、或者 nihui 有不同的解决方案。从关闭的 PR 中学习反馈,准备下一个 PR。
Q: 不确定自己的贡献方向?
A: 从文档 PR 开始。零风险、高合并率、让你熟悉贡献流程。等你对项目更熟悉后,再转向代码贡献。
Q: 英文不好,PR 描述和 commit message 怎么写?
A: ncnn 社区对中文友好,你可以用中文写 Issue 评论和 PR 描述。但 commit message 建议用简单的英文,因为它会进入 git 历史。格式参考已有的 commit:pnnx: support xxx、fix xxx for arm、docs: add xxx guide。
本章小结
向 ncnn 贡献代码的核心策略是:理解项目文化 → 选择低风险方向 → 快速提交第一个 PR → 建立信任后逐步深入。
ncnn 的独特文化决定了贡献策略:单人维护者意味着要理解 nihui 的偏好;没有 good-first-issue 意味着要自己挖掘任务;古典 C++ 风格意味着要克制使用现代特性的冲动。
五个贡献方向中,文档和 Python bindings 是最佳入门选择。”从用户问题反推文档 PR”是最低风险、最高合并率的策略——你不需要理解引擎内部实现,只需要把散落在 Issue 中的知识整理成结构化的文档。
第一周行动计划的核心节奏是:编译(Day 1)→ 调研(Day 2)→ 复现(Day 3)→ 写 PR(Day 4)→ 迭代(Day 5)。五天之内提交第一个 PR,不管大小——行动比完美更重要。
下一步建议
- 想把 YOLO 模型部署到手机上?→ Ch27: YOLO 到端侧
- 想回顾 ncnn 的编译和推理?→ Ch24: ncnn 实战
- 想回顾 MNN 的使用流程?→ Ch25: MNN 实战
导读目录:README.md 上一章:Ch25: MNN 实战——转换、推理、量化一条龙 下一章:Ch27: 跨赛道 YOLO 到端侧——MoE 导出与引擎选型