贡献指南#
这是关于所有对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 |
ver Y |
ver 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
分支。每个稳定版本的发布也会合并到开发版本中,以便更改也反映到下一个主要版本中。
功能回溯 PRs#
我们基本上不会将开发版本中的任何新功能回溯到稳定版本。如果您希望将该功能包含到当前的稳定版本中,并且您可以进行回溯工作,我们欢迎这样的贡献。在这种情况下,您需要向最新的 vN
分支发送一个回溯PR。请注意,我们不接受任何向旧版本回溯功能的PR,因为我们没有为旧版本运行质量保证工作流程(例如CI),因此我们无法确保PR是否正确回溯。
发送回溯PR有一些规则。
从前缀 [backport] 开始 PR 标题。
在PR描述中明确原始PR编号(类似于“这是#XXXX的后端移植”)。
(可选)在PR描述中写明将功能回溯到稳定版本的动机。
在创建功能回溯PR时,请遵循以下规则。
注意:不包含任何API更改/添加的PR(例如,错误修复、文档改进)通常由核心开发成员进行回溯移植。虽然任何贡献者进行此类回溯移植PR也非常受欢迎,以便整体开发进展更加顺利!
问题和拉取请求#
在本节中,我们将解释如何发送拉取请求(PR)。
如何发送拉取请求#
如果你能编写代码来修复问题,我们鼓励你发送一个PR。
首先,在开始编写任何代码之前,不要忘记确认以下几点。
阅读 编码指南 和 测试指南。
根据 Git 分支 选择你应该发送PR的适当分支。如果你对选择分支没有任何想法,请选择
main
分支。
特别是,在编写任何代码之前检查分支。 所选分支的当前源树是更改的起点。
编写完代码 (包括单元测试和希望有的文档!) 后,在GitHub上发送一个PR。你必须写一个关于 什么 和 如何 修复的精确解释;这是开发者阅读的关于你代码的第一份文档,这是你PR中非常重要的一部分。
一旦你发送了PR,它会在 GitHub Actions
上自动进行测试。自动测试通过后,核心开发者将开始审查你的代码。请注意,这个自动PR测试仅包括CPU测试。
备注
我们还为 main
分支和最新主要版本的版本化分支运行带有GPU测试的持续集成。由于此服务目前运行在我们的内部服务器上,为了保持服务器的安全,我们不将其用于自动PR测试。
如果你计划添加新功能或修改现有API,建议先开一个issue并讨论设计。 设计讨论对于核心开发者来说成本低于代码审查。根据讨论的结果,你可以发送一个PR,该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
文件中列出的各种检查,包括 linting(检测潜在错误)、格式化(强制执行 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
装饰器。被 slow
装饰的测试函数在给出 -m='not 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源代码的一些提示。
以可编辑方式安装#
在开发过程中,我们建议使用 pip
并带有 -e
选项以可编辑模式安装:
$ pip install -e .
请注意,即使使用 -e
,如果您修改了 Cython 源文件(例如, *.pyx
文件),您仍然需要重新运行 pip install -e .
以使用 Cython 重新生成 C++ 源代码。
使用 ccache#
NVCC
环境变量可以在构建时指定,以使用自定义命令代替 nvcc
。你可以通过以下方式使用 `ccache <https://ccache.dev/>`_(v3.4 或更高版本)来加速重新构建:
$ export NVCC='ccache nvcc'
限制架构#
使用 CUPY_NVCC_GENERATE_CODE
环境变量通过限制目标CUDA架构来减少构建时间。例如,如果你只使用NVIDIA P100和V100来运行你的CuPy构建,你可以使用:
$ 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
克隆仓库。