贡献指南#
这是一份面向所有 CuPy 贡献者的指南。CuPy 的开发工作正在 GitHub 上的官方仓库 中进行。任何想要提交议题或发送拉取请求的人都应阅读本文档。
贡献分类#
有几种方式可以为 CuPy 社区做出贡献
注册议题
发送拉取请求 (PR)
向 CuPy 的 Gitter 频道、CuPy 用户组 或 StackOverflow 发送问题
开源外部示例
撰写关于 CuPy 的文章
本文档主要侧重于第 1 点和第 2 点,但也欢迎其他类型的贡献。
开发周期#
本节解释了 CuPy 的开发过程。在为 CuPy 做贡献之前,强烈建议了解其开发周期。
版本控制#
CuPy 的版本控制遵循 PEP 440 和部分 语义化版本控制 规范。版本号由三或四部分组成:X.Y.Zw
,其中 X
表示 主版本号,Y
表示 次版本号,Z
表示 修订号,可选的 w
表示预发布后缀。虽然主版本、次版本和修订号遵循语义化版本控制规则,但预发布后缀遵循 PEP 440,以便版本字符串更易于与 Python 生态系统兼容。
请注意,主版本更新基本上不包含与上一个发布候选版本 (RC) 相比的向后不兼容更改。但这并非严格规定;如果存在必须为主版本修复的关键 API 错误,我们可能会在主版本更新中添加不兼容更改。
有关向后兼容性的信息,请参阅API 兼容性政策。
发布周期#
第一个是 稳定版本 的轨道,它是最新主版本的一系列修订更新。第二个是 开发版本 的轨道,它是即将发布的主版本的一系列预发布版本。
假设 X.0.0
是最新的主版本,Y.0.0
、Z.0.0
是后续的主版本。更新的时间线由下表描述。
日期 |
版本 X |
版本 Y |
版本 Z |
---|---|---|---|
0 周 |
X.0.0rc1 |
– |
– |
4 周 |
X.0.0 |
Y.0.0a1 |
– |
8 周 |
X.1.0* |
Y.0.0b1 |
– |
12 周 |
X.2.0* |
Y.0.0rc1 |
– |
16 周 |
– |
Y.0.0 |
Z.0.0a1 |
(* 这些可能是修订版本)
最左侧列中显示的日期是相对于 X.0.0rc1
的发布日期。特别是,每个修订版本/次要版本都在同一主版本的上一个版本发布四周后发布,即将发布的主版本的预发布版本也同时发布。这些版本是修订版本还是次要版本取决于每次更新的内容。
请注意,版本 X.x.x
只有三个稳定版本。在 Y.0.0
和 Z.0.0a1
并行开发期间,版本 Y
被视为 准稳定版本,而 Z
被视为开发版本。
如果在停止版本 X
的开发后在 X.x.x
中发现关键错误,我们可能随时为此版本发布热修复。
我们会在 GitHub 上为即将发布的每个版本创建一个里程碑。GitHub 里程碑主要用于收集在该版本中解决的议题和 PR。
Git 分支#
main
分支用于开发预发布版本。这意味着 alpha、beta 和 RC 更新都在 main
分支上开发。此分支包含最新的源代码树,其中包括最新主版本后新添加的功能。
稳定版本在名为 vN
的独立分支上开发,其中“N”反映了版本号(我们称之为 版本化分支)。例如,v1.0.0、v1.0.1 和 v1.0.2 将在 v1
分支上开发。
贡献者须知:提交拉取请求时,基本上需要将其提交到 main
分支。如果更改也适用于稳定版本,核心团队成员会将相同的更改应用到稳定版本,以便更改也包含在下一个修订更新中。
如果更改仅适用于稳定版本而不适用于 main
分支,请将其发送到版本化分支。除非修复是关键性的,否则我们基本上只接受对最新版本化分支(稳定版本在此开发)的更改。
如果您想将 main
分支的新功能引入当前的稳定版本,请向稳定版本(最新的 vN
分支)发送 反向移植 PR。详细信息请参阅下一节。
注意:适用于两个分支的更改应发送到 main
分支。 稳定版本的每个发布也会合并到开发版本,以便更改也反映到下一个主版本。
功能反向移植 PR#
我们基本上不会将开发版本的任何新功能反向移植到稳定版本。如果您希望将该功能包含到当前稳定版本中,并且能够完成反向移植工作,我们欢迎此类贡献。在这种情况下,您需要向最新的 vN
分支发送反向移植 PR。请注意,我们不接受将任何功能反向移植到旧版本,因为我们不会为旧版本运行质量保证工作流程(例如 CI),因此无法确保 PR 已正确移植。
发送反向移植 PR 有一些规则。
PR 标题必须以 [backport] 前缀开头。
在 PR 描述中说明原始 PR 号(例如:“This is a backport of #XXXX”)。
(可选)在 PR 描述中说明将该功能反向移植到稳定版本的原因。
创建功能反向移植 PR 时,请遵循这些规则。
注意:不包含对 API 进行任何更改/添加的 PR(例如错误修复、文档改进)通常由核心开发人员反向移植。不过,也很欢迎任何贡献者制作此类反向移植 PR,这样可以使整体开发更加顺利!
议题和拉取请求#
本节我们将解释如何发送拉取请求 (PR)。
如何发送拉取请求#
如果您能够编写代码来修复议题,我们鼓励您发送 PR。
首先,在开始编写任何代码之前,请务必确认以下几点。
特别是,在编写任何代码之前检查分支。所选分支的当前源代码树是您更改的起点。
编写代码后 (包括单元测试以及可能的文档!),在 GitHub 上发送 PR。您必须详细说明您修复了 什么 以及 如何 修复;这是开发人员阅读的代码的第一份文档,也是您的 PR 中非常重要的一部分。
发送 PR 后,它会在 GitHub Actions
上自动测试。自动测试通过后,核心开发人员将开始审查您的代码。请注意,此自动 PR 测试仅包含 CPU 测试。
注意
我们还在为 main
分支和最新主版本的版本化分支运行带有 GPU 测试的持续集成。由于此服务当前运行在我们内部服务器上,为了保证服务器安全,我们不将其用于自动 PR 测试。
如果您计划添加新功能或修改现有 API,建议先开一个议题讨论设计。设计讨论对核心开发人员来说比代码审查的成本更低。遵循讨论的结果,您可以发送一个能在更短时间内顺利审查的 PR。
即使您的代码不完整,您也可以通过在 PR 标题前加上 [WIP]
前缀来发送一个 进行中的 PR。如果您对 PR 进行详细说明,核心开发人员和其他贡献者可以加入讨论如何推进 PR。WIP PR 也便于基于具体代码进行讨论。
编码指南#
注意
编码指南在 v5.0 更新。为旧版本贡献过的开发者应重新阅读指南。
我们将 PEP8 以及部分与通用编码风格相关的 OpenStack 风格指南 作为我们的基本风格指南。
您可以使用 pre-commit
检查您的代码。首先使用以下命令安装
$ pip install pre-commit
并使用以下命令检查您的代码
$ pre-commit run -a
上述命令运行 .pre-commit-config.yaml
文件中列出的各种检查,包括代码静态分析(检测潜在错误)、格式化(强制执行 PEP8)等。如果您想自动化检查过程,可以安装 pre-commit 钩子
$ pre-commit install
安装后,每次提交时都会检查您的代码。发送拉取请求之前,请务必检查您的代码是否通过了 pre-commit
检查。
请注意,pre-commit
检查并非完美无缺。它无法检查某些风格指南。这里列出了 pre-commit
无法检查的部分规则(非完整列表)。
禁止使用相对导入。[H304]
禁止导入非模块符号。
导入语句必须分为三个部分:标准库、第三方库和内部导入。[H306]
此外,我们限制在代码库中使用 快捷方式符号。它们是 cupy
包和子包导入的符号。例如,cupy.cuda.Device
是 cupy.cuda.device.Device
的快捷方式。在 ``cupy`` 库的实现中不允许使用此类快捷方式。请注意,您仍然可以在 tests 和 examples 目录中使用它们。
发送拉取请求后,您的编码风格将由 GitHub Actions 自动检查。检查通过后,审查过程开始。
CuPy 基于 NumPy 的 API 设计。CuPy 的源代码和文档包含原始 NumPy 的内容。撰写文档时请注意以下几点。
为了区分重叠部分,最好添加一些备注说明本文档仅是复制或修改自原始文档。最好在一个简短的段落中简要解释函数的规格,并引用 NumPy 中对应的函数,以便用户可以阅读详细文档。但是,如果用户无法这样概括,也可以包含带有此类备注的完整文档副本。
如果 CuPy 中的函数仅实现了原始函数中的有限功能,用户应在文档中明确说明仅实现的功能。
对于修改或添加新 Cython 文件的更改,请确保指针类型遵循这些指南(#1913)。
如果仅在 Cython 内部使用,指针应为
void*
;如果暴露给 Python 空间,则应为intptr_t
。内存大小应为
size_t
。内存偏移量应为
ptrdiff_t
。
注意
我们正在逐步强制执行上述规则,因此某些现有代码可能不遵循上述指南,但请确保所有新贡献都遵循。
单元测试#
测试是您的代码中最重要的部分之一。您必须按照我们的测试指南编写测试用例并验证您的实现。
请注意,我们使用 pytest 和 mock 包进行测试,因此在编写代码之前请安装它们
$ pip install pytest mock
如何运行测试#
为了在仓库根目录运行单元测试,您首先需要通过运行以下命令就地构建 Cython 文件
$ pip install -e .
注意
修改 *.pxd
文件时,在运行 pip install -e .
之前,必须使用以下命令清除 *.cpp
和 *.so
文件,因为 Cython 不会自动很好地重建这些文件
$ git clean -fdx
构建 Cython 模块后,您可以在仓库根目录运行以下命令来运行单元测试
$ python -m pytest
必须安装 CUDA 才能运行单元测试。
某些 GPU 测试需要 cuDNN 才能运行。为了跳过需要 cuDNN 的单元测试,请指定 -m='not cudnn'
选项
$ python -m pytest path/to/your/test.py -m='not cudnn'
某些 GPU 测试涉及多个 GPU。如果您想在 GPU 数量不足的情况下运行 GPU 测试,请将可用 GPU 的数量指定给 CUPY_TEST_GPU_LIMIT
。例如,如果您只有一个 GPU,请通过以下命令启动 pytest
以跳过多 GPU 测试
$ export CUPY_TEST_GPU_LIMIT=1
$ python -m pytest path/to/gpu/test.py
按照此命名约定,您可以在仓库根目录运行以下命令来运行所有测试
$ python -m pytest
或者您也可以指定一个根目录来搜索测试脚本
$ python -m pytest tests/cupy_tests # to just run tests of CuPy
$ python -m pytest tests/install_tests # to just run tests of installation modules
如果您修改了与现有单元测试相关的代码,则必须运行适当的命令。
测试文件和目录命名约定#
测试文件位于 tests/cupy_tests 目录下。为了让测试运行器正确找到测试脚本,我们对测试子目录和测试脚本使用了特殊的命名约定。
tests
的每个子目录名称必须以_tests
后缀结尾。每个测试脚本的名称必须以
test_
前缀开头。
为模块编写测试时,我们使用与被测试模块对应关系明确的测试脚本的适当路径和文件名。例如,如果您想为模块 cupy.x.y.z
编写测试,测试脚本必须位于 tests/cupy_tests/x_tests/y_tests/test_z.py
。
如何编写测试#
tests 目录下有很多单元测试示例,阅读其中一些是学习如何为 CuPy 编写测试的良好推荐方式。它们只使用了标准库的 unittest
包,而有些测试使用了 cupy.testing
中的工具。
除了上面提到的编码指南之外,测试代码还应用以下规则
所有测试类必须继承自
unittest.TestCase
。使用
unittest
功能编写测试,但以下情况除外使用
assert
语句代替self.assert*
方法(例如,写assert x == 1
而不是self.assertEqual(x, 1)
)。使用
with pytest.raises(...):
代替with self.assertRaises(...):
。
注意
我们正在逐步应用上述风格。某些现有测试可能仍使用旧风格(self.assertRaises
等),但所有新编写的测试都应遵循上述风格。
为了编写多 GPU 测试,请使用 cupy.testing.multi_gpu()
装饰器代替
import unittest
from cupy import testing
class TestMyFunc(unittest.TestCase):
...
@testing.multi_gpu(2) # specify the number of required GPUs here
def test_my_two_gpu_func(self):
...
如果您的测试耗时过长,请添加 cupy.testing.slow
装饰器。如果指定了 -m='not slow'
,则带有 slow
装饰器的测试函数将被跳过
import unittest
from cupy import testing
class TestMyFunc(unittest.TestCase):
...
@testing.slow
def test_my_slow_func(self):
...
发送拉取请求后,GitHub Actions 会自动检查您的代码是否符合上述编码指南。由于 GitHub Actions 不支持 CUDA,我们无法自动运行单元测试。审查过程在自动检查通过后开始。请注意,审查人员将在不检查 CUDA 相关代码的情况下测试您的代码。
注意
一些数值不稳定的测试可能会导致与您的更改无关的错误。在这种情况下,我们将忽略这些失败并继续审查过程,所以请不要担心!
文档#
向框架添加新功能时,您还需要在参考文档中记录它。
注意
如果您不确定如何修改文档,可以不修改文档就提交拉取请求。评审人员会帮助您适当地修改文档。
文档源文件存储在 docs 目录 下,并以 reStructuredText 格式编写。
要构建文档,您需要安装 Sphinx
$ pip install -r docs/requirements.txt
然后您可以在本地构建 HTML 格式的文档
$ cd docs
$ make html
HTML 文件生成在 build/html
目录下。用浏览器打开 index.html
查看是否按预期呈现。
注意
文档字符串(源代码中的文档注释)从安装的 CuPy 模块中收集。如果您修改了文档字符串,请务必在构建文档之前安装模块(例如,使用 pip install -e .)。
开发者提示#
这里有一些为修改 CuPy 源代码的开发者提供的提示。
安装为可编辑模式#
在开发过程中,我们建议使用带有 -e
选项的 pip
以可编辑模式安装
$ pip install -e .
请注意,即使使用 -e
,如果您修改了 Cython 源文件(例如 *.pyx
文件),也必须重新运行 pip install -e .
以使用 Cython 重新生成 C++ 源文件。
使用 ccache#
可以在构建时指定 NVCC
环境变量,以使用自定义命令代替 nvcc
。您可以使用 ccache (v3.4 或更高版本) 加快重建速度,方法是
$ export NVCC='ccache nvcc'
限制架构#
使用 CUPY_NVCC_GENERATE_CODE
环境变量通过限制目标 CUDA 架构来减少构建时间。例如,如果您的 CuPy 构建只在 NVIDIA P100 和 V100 上运行,可以使用
$ export CUPY_NVCC_GENERATE_CODE=arch=compute_60,code=sm_60;arch=compute_70,code=sm_70
有关描述,请参阅环境变量。
在 Microsoft Windows 上开发#
CuPy 在源代码树中使用符号链接。如果您在 Windows(非 WSL 环境下)上开发,克隆 CuPy 仓库之前需要额外的设置。
运行
git config --global core.symlinks true
以在 Git 中启用符号链接支持。激活开发者模式 以允许 Git 在没有管理员权限的情况下使用符号链接。
配置完成后,您可以通过 git clone --recursive https://github.com/cupy/cupy.git
克隆仓库。