贡献指南#

这是一份面向所有 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

版本 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.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 分支。 稳定版本的每个发布也会合并到开发版本,以便更改也反映到下一个主版本。

功能反向移植 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.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 装饰器。如果指定了 -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 克隆仓库。