贡献指南#

以下是一些为 Warp 的开发做出贡献的方式:

  • GitHub 上报告错误和请求新功能。

  • GitHub (首选) 或 Discord 上提问、分享您的工作或参与讨论主题。

  • 向 Warp 仓库添加新的示例。

  • 改进文档。

  • 贡献错误修复或新功能。

代码贡献#

欢迎社区的代码贡献。与其要求正式的贡献者许可协议 (CLA),我们使用 开发者原创证明 来确保贡献者有权将他们的贡献提交给此项目。请确保所有提交都有一个 签名,并且电子邮件地址与提交作者匹配,以便同意每个特定贡献的 DCO 条款。

DCO 的全文如下:

Version 1.1

Copyright (C) 2004, 2006 The Linux Foundation and its contributors.

Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.


Developer's Certificate of Origin 1.1

By making a contribution to this project, I certify that:

(a) The contribution was created in whole or in part by me and I
    have the right to submit it under the open source license
    indicated in the file; or

(b) The contribution is based upon previous work that, to the best
    of my knowledge, is covered under an appropriate open source
    license and I have the right under that license to submit that
    work with modifications, whether created in whole or in part
    by me, under the same open source license (unless I am
    permitted to submit under a different license), as indicated
    in the file; or

(c) The contribution was provided directly to me by some other
    person who certified (a), (b) or (c) and I have not modified
    it.

(d) I understand and agree that this project and the contribution
    are public and that a record of the contribution (including all
    personal information I submit with it, including my sign-off) is
    maintained indefinitely and may be redistributed consistent with
    this project or the open source license(s) involved.

鼓励贡献者首先在 GitHub 上打开一个 issue,讨论拟议的功能贡献并衡量潜在的兴趣。

概述#

  1. 通过访问 NVIDIA/warp 创建 Warp GitHub 仓库的 fork

  2. 在您的本地机器上克隆您的 fork,例如 git clone git@github.com:username/warp.git

  3. 创建一个分支来开发您的贡献,例如 git checkout -b mmacklin/cuda-bvh-optimizations

    对分支名称使用以下命名约定:

    • 新功能: username/feature-name

    • 错误修复: bugfix/feature-name

  4. 进行您所需的更改。

  5. 将您的分支推送到您的 GitHub fork,例如 git push origin username/feature-name

  6. 在 GitHub 上提交一个拉取请求到 main 分支 (拉取请求准则)。与审阅者合作以确保拉取请求处于适合合并的状态。

通用编码准则#

  • 遵循 PEP 8 作为编码风格的基线,但优先考虑匹配正在修改的文件的现有风格和约定,以保持一致性。

  • 对所有函数名称使用 蛇形命名法

  • 对 Python 代码使用 Google 风格的文档字符串

  • 在所有新创建的文件中包含 NVIDIA 版权标头,并在初始文件创建时将年份更新为当前年份。

  • 力求变量和函数名称的一致性。

    • 命名新函数时,尽可能使用现有的术语(例如,使用 points 而不是 vertex_buffer)。

    • 如果代码库中已经存在缩写,则不要引入新的缩写。

    • 在命名局部函数变量时,还要注意一致性和清晰度。

  • 避免使用像 get_data() 这样的通用函数名称。

  • 遵循任何正在修改的 CUDA C++ 文件中的现有风格约定。

  • 在预计用于可微编程应用程序的函数中的 wp.launch() 中同时使用 inputsoutputs 参数,以帮助可视化和调试工具。

代码检查和格式化#

Ruff 用作 Warp 仓库中 Python 代码的代码检查器和代码格式化器。将自动检查拉取请求的内容,以确保符合我们的格式化和代码检查标准。

我们建议在打开拉取请求之前,首先在您的分支上本地运行 Ruff。从项目根目录运行

pip install pre-commit
pre-commit run --all

此命令将尝试修复任何代码检查违规行为,然后格式化代码。某些代码检查违规行为无法自动修复,需要手动解决。

要在与 git commit 同时运行 Ruff 检查,可以通过在项目根目录中运行此命令来安装 pre-commit 钩子

pre-commit install

构建文档#

在构建 Sphinx 文档之前,应首先通过运行 build_lib.py 在本地构建 Warp 库。然后可以通过从项目根目录运行以下命令来构建文档:

python -m pip install -e .[docs]
python build_docs.py --quick

-quick 标志跳过运行 doctest 测试,这需要一些时间才能运行。如果您的更改修改了核心库功能,那么最好在不使用 -quick 标志的情况下运行 build_docs.py,以确保文档代码片段仍然是最新的。

运行 build_docs.py 还会为 内核参考 页面重新生成存根文件 (warp/stubs.py) 和 reStructuredText 文件。构建文档后,建议运行 git status 以检查您的更改是否已修改这些文件。如果已修改,请将修改后的文件提交到您的分支。

拉取请求准则#

  • 确保您的拉取请求具有描述性标题,清楚地说明更改的目的。

  • 包含一个简短的描述,涵盖

    • 更改摘要。

    • 受更改影响的区域。

    • 正在解决的问题。

    • 更改中的任何限制或未处理的区域。

    • 更改正在解决的任何现有 GitHub issue。

测试 Warp#

运行测试套件#

Warp 的测试套件使用 unittest 单元测试框架,以及 unittest-parallel 以并行运行测试。

大多数 Warp 测试位于 warp/tests 目录中。作为测试套件的一部分,warp/examples 子目录中的大多数示例都通过 test_examples.py 进行测试。

在构建并安装 Warp 后(从项目根目录 pip install -e .),使用 python -m warp.tests 运行测试套件。测试应该需要 5-10 分钟才能运行。默认情况下,只运行在 default_suite()(在 warp/tests/unittest_suites.py 中)中定义的测试模块。要使用 测试发现 运行测试套件,请使用 python -m warp.tests -s autodetect,这将发现路径与 warp/tests/test*.py 匹配的模块中的测试。

运行测试的子集#

除了运行完整的测试套件之外,还有两种主要方法可以选择要运行的测试子集。这些选项必须与 -s autodetect 选项一起使用。

使用 -p PATTERN 定义一个模式来匹配测试文件。例如,要仅运行文件名中包含 mesh 的测试,请使用

python -m warp.tests -s autodetect -p '*mesh*.py'

使用 -k TESTNAMEPATTERNS 定义 通配符测试名称模式。可以多次使用此选项。例如,要仅运行名称中包含 mgpucuda 的测试,请使用

python -m warp.tests -s autodetect -k 'mgpu' -k 'cuda'

添加新测试#

对于应该在多个设备上运行的测试,例如 "cpu", "cuda:0", 和 "cuda:1",我们建议首先在模块作用域内定义一个测试函数,然后使用 add_function_test() 向测试类添加多个测试方法(每个设备对应一个单独的方法)。

# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://apache.ac.cn/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import unittest

import warp as wp
from warp.tests.unittest_utils import *


def test_amazing_code_test_one(test, device):
    pass

devices = get_test_devices()


class TestAmazingCode(unittest.TestCase):
    pass

add_function_test(TestAmazingCode, "test_amazing_code_test_one", test_amazing_code_test_one, devices=devices)


if __name__ == "__main__":
    wp.clear_kernel_cache()
    unittest.main(verbosity=2)

如果我们直接运行这个模块,我们会得到以下输出

python test_amazing_code.py
Warp 1.3.1 initialized:
CUDA Toolkit 12.6, Driver 12.6
Devices:
    "cpu"      : "x86_64"
    "cuda:0"   : "NVIDIA GeForce RTX 3090" (24 GiB, sm_86, mempool enabled)
    "cuda:1"   : "NVIDIA GeForce RTX 3090" (24 GiB, sm_86, mempool enabled)
CUDA peer access:
    Supported fully (all-directional)
Kernel cache:
    /home/nvidia/.cache/warp/1.3.1
test_amazing_code_test_one_cpu (__main__.TestAmazingCode) ... ok
test_amazing_code_test_one_cuda_0 (__main__.TestAmazingCode) ... ok
test_amazing_code_test_one_cuda_1 (__main__.TestAmazingCode) ... ok

----------------------------------------------------------------------
Ran 3 tests in 0.001s

OK

请注意,输出表明运行了三个测试,尽管我们只编写了一个名为 test_amazing_code_test_one() 的测试函数。仔细检查后发现,该测试函数在三个不同的设备上运行:"cpu""cuda:0"cuda:1。这是因为我们在测试脚本中使用了 devices=devices 参数调用了 add_function_test()add_function_test() 定义在 warp/tests/unittest_utils.py 中。

使用 add_function_test() 的一个注意事项是,它本身不足以确保注册的测试函数(例如 test_amazing_code_test_one())在不同的设备上运行。测试的主体必须使用 device 参数,以确保数据分配到,并且内核运行在测试预期的 device 上,例如:

def test_amazing_code_test_one(test, device):
    with wp.ScopedDevice(device):
        score = wp.zeros(1, dtype=float, requires_grad=True)

def test_amazing_code_test_one(test, device):
    score = wp.zeros(1, dtype=float, requires_grad=True, device=device)

检查预期行为#

由于使用了测试注册函数 add_function_test()test 参数实际上指的是测试类的实例,该实例始终是 unittest.TestCase 的子类。

unittest 库还提供了检查是否引发断言的方法,因为测试触发错误的代码路径也很重要。 assertRaises()assertRaisesRegex() 方法可用于测试代码块是否正确引发异常。

有时我们需要将 Warp 数组的内容与预期结果进行比较。一些有用的函数包括

  • assert_np_equal():接受两个 NumPy 数组作为输入参数,以及一个可选的绝对容差 tol,默认为 0。如果容差为 0,则使用 np.testing.assert_array_equal() 比较数组。否则,将两个 NumPy 数组展平,然后使用 np.testing.assert_allclose() 进行比较。

  • assert_array_equal():接受两个 Warp 数组作为输入参数,将每个数组转换为 CPU 上的 NumPy 数组,然后使用 np.testing.assert_equal() 比较数组。

  • wp.expect_eq():与前两个函数不同,数组将通过运行 Warp 内核进行比较,因此数据可以保留在 GPU 中。如果数组特别大,以至于在 CPU 上进行逐元素比较的速度会非常慢,这一点很重要。

跳过测试#

Warp 需要在包括 macOS 在内的多个操作系统上进行测试,而 macOS 不支持 NVIDIA GPU。 如果在任何设备上都无法执行特定测试,则可以使用某些机制将测试标记为已跳过

unittest 提供了一些方法来跳过测试。

如果使用 add_function_test() 将测试函数添加到测试类,我们可以将空列表作为参数传递给 device 参数。

最后一种常见技术是避免在测试函数上调用 add_function_test 以跳过它。示例包括 test_torch.py, test_jax.py, 和 test_dlpack.py。不建议使用这种技术,因为测试没有在 unittest 框架中标记为已跳过。相反,测试被视为不存在。这可能会导致我们不知道测试是否被跳过,因为它不会显示在已跳过的测试计数下(也不会显示在通过的测试计数下)。

除了测试需要 CUDA 的情况外,跳过测试的一些示例包括

  • usd-core 未安装在当前环境中。

  • 安装的 JAX 版本太旧。

  • 系统没有至少两个可用的 CUDA 设备(例如,多 GPU 测试需要)。

没有设备的测试#

回想一下,我们之前讨论过使用 add_function_test() 来注册一个测试函数,以便它可以在不同的设备上运行(例如 "cpu""cuda:0")。有时,测试函数不使用特定设备,我们只想运行一次。

如果我们仍然想使用 add_function_test() 注册测试,我们可以传递 devices=None 来表明该函数不使用设备。在这种情况下,该函数只会向传递给 add_function_test() 的测试类注册一次。

另一种方法是完全避免使用 add_function_test()直接在测试类中定义测试函数。以我们之前的 TestAmazingCode 示例为例,我们可以添加一个设备无关的函数,而不是简单地使用 pass 作为类的主体

class TestAmazingCode(unittest.TestCase):
    def test_amazing_code_no_device(self):
        self.assertEqual(True, True)

对于某些开发人员来说,这种技术可能更具可读性,因为它避免了 add_function_test(..., device=None) 的混淆。毕竟,使用 add_function_test() 是为了方便单个测试函数在不同设备上执行,而不必为每个设备定义一个单独的函数。