贡献指南#

这是关于所有对CuPy贡献的指南。CuPy的开发正在 GitHub上的官方仓库 上进行。任何想要提交问题或发送拉取请求的人都应该阅读本文档。

贡献分类#

有几种方式可以为CuPy社区做出贡献:

  1. 注册一个问题

  2. 发送一个拉取请求 (PR)

  3. CuPy 的 Gitter 频道CuPy 用户组StackOverflow 发送问题

  4. 开源一个外部示例

  5. 撰写一篇关于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.0Z.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.0Z.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版本中进行了更新。那些为旧版本做出贡献的人应该再次阅读这些指南。

我们使用 PEP8OpenStack 风格指南 中与一般编码风格相关的一部分作为我们的基本风格指南。

你可以使用 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.Devicecupy.cuda.device.Device 的快捷方式。在 ``cupy`` 库的实现中不允许使用此类快捷方式。请注意,您仍然可以在 testsexamples 目录中使用它们。

一旦你发送了一个拉取请求,你的编码风格会自动被 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 克隆仓库。