犀牛鸟 2026 研究笔记 estelledc.github.io

Ch26: 开源竞赛贡献路径——如何向 ncnn 提交第一个 PR

Part 5: 实战与竞赛 前置章节:Ch25: MNN 实战——转换、推理、量化一条龙 后续章节:Ch27: 跨赛道 YOLO 到端侧——MoE 导出与引擎选型


本章目标

前面几章教你怎么”用”推理引擎。这一章换一个方向:怎么向推理引擎项目贡献代码,把”用户”变成”贡献者”。

以 ncnn 为例(因为它代码量最小、架构最简单、最适合新人入手),这一章会覆盖:从理解项目治理结构、到选择合适的贡献方向、到写出第一个能被合并的 PR 的完整路径。

这不是一个泛泛的”如何参与开源”指南。ncnn 有它独特的项目文化——单人维护者(nihui)、没有 good-first-issue 标签、代码风格非常古典(不用 C++11)、PR review 周期可能很长。这一章的建议都是针对这些具体特点给出的。

读完这一章,你应该能:


前置准备


ncnn 项目的独特文化

在向 ncnn 贡献代码之前,必须先理解这个项目的运作方式——它和你想象中的”大型开源项目”可能很不一样。

单人维护者模式

ncnn 的核心维护者只有一个人:nihui(GitHub: @nihui)。他是这个项目的创建者、主要贡献者、唯一有合并权限的人。所有 PR 都由他一个人 review 和合并。

这意味着:

没有 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 特性会排除这些编译器。这不是技术债,是有意的设计决策。

具体的格式规范:

提交 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/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 需要持续跟进支持。

贡献方式:

  1. 在 PNNX 的 Issue 或测试中找到不支持的 PyTorch 算子
  2. tools/pnnx/src/pass_level1/pass_level2/ 中添加对应的转换逻辑
  3. 添加测试用例
// 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 也欢迎文档改善。

可能的贡献点:

“从用户问题反推文档 PR”策略: 这是最低风险、最高合并率的贡献路径。具体做法:

  1. 浏览 ncnn 的 Issue 列表和 Discussions
  2. 找到反复出现的问题(比如”如何在 Apple Silicon 上编译”、”量化参数怎么设置”)
  3. 把这些问题的解答整理成文档,提交 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 的核心推理逻辑。

筛选关键词:buildcompileinstallwheelpipcmake

# 在 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。

筛选关键词:pnnxunsupportednot supportedexport

类型 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 特征


从用户问题反推文档 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

分支命名建议:

步骤 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 会自动运行。需要注意:

如果一周后没有回应,可以在 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(阿里巴巴):

TNN(腾讯):

ONNX Runtime(微软):


常见问题排查

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 xxxfix xxx for armdocs: 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,不管大小——行动比完美更重要。


下一步建议


导读目录:README.md 上一章:Ch25: MNN 实战——转换、推理、量化一条龙 下一章:Ch27: 跨赛道 YOLO 到端侧——MoE 导出与引擎选型