设备#

Warp 为系统中所有支持的计算设备分配唯一的字符串别名。当前暴露了一个 CPU 设备,别名为 "cpu"。每个支持 CUDA 的 GPU 都会获得一个形式为 "cuda:i" 的别名,其中 i 是 CUDA 设备的序号。对于 PyTorch 等其他流行框架的用户来说,这种约定应该很熟悉。

可以通过 device 参数在每次 Warp API 调用中显式指定目标设备

a = wp.zeros(n, device="cpu")
wp.launch(kernel, dim=a.size, inputs=[a], device="cpu")

b = wp.zeros(n, device="cuda:0")
wp.launch(kernel, dim=b.size, inputs=[b], device="cuda:0")

c = wp.zeros(n, device="cuda:1")
wp.launch(kernel, dim=c.size, inputs=[c], device="cuda:1")

注意

一个 Warp CUDA 设备("cuda:i")对应于设备 i 的主 CUDA 上下文。这与 PyTorch 等使用 CUDA Runtime API 的框架及其他软件兼容。这使得互操作性变得容易,因为 GPU 资源(如内存)可以与 Warp 共享。

class warp.context.Device(runtime, alias, ordinal=-1, is_primary=False, context=None)[source]#

一个用于分配 Warp 数组和启动内核的设备。

ordinal#

设备的 Warp 特定标签。CPU 设备为 -1

类型

int

name#

设备的标签。默认情况下,CPU 设备将根据处理器名称命名,如果无法确定处理器名称,则命名为 "CPU"

类型

str

arch#

计算能力版本号,计算公式为 10 * major + minor。CPU 设备为 0

类型

int

is_uva#

指示设备是否支持统一寻址。CPU 设备为 False

类型

bool

is_cubin_supported#

指示 Warp 的 NVRTC 版本是否可以直接为该设备的架构生成 CUDA 二进制文件 (cubin)。CPU 设备为 False

类型

bool

is_mempool_supported#

指示设备是否支持使用 cuMemAllocAsynccuMemPool 系列 API 进行流有序内存分配。CPU 设备为 False

类型

bool

is_ipc_supported#

指示设备是否支持 IPC。

  • 如果支持,则为 True

  • 如果不支持,则为 False

  • 如果无法确定 IPC 支持(例如 CUDA 11),则为 None

类型

Optional[bool]

is_primary#

指示该设备的 CUDA 上下文是否也是设备的主上下文。

类型

bool

uuid#

CUDA 设备的 UUID。UUID 格式与 nvidia-smi -L 使用的格式相同。CPU 设备为 None

类型

str

pci_bus_id#

CUDA 设备的标识符,格式为 [domain]:[bus]:[device],其中 domainbusdevice 均为十六进制值。CPU 设备为 None

类型

str

__init__(
runtime,
alias,
ordinal=-1,
is_primary=False,
context=None,
)[source]#
get_allocator(pinned=False)[source]#

获取此设备的内存分配器。

参数

pinned (bool) – 如果为 True,将返回一个用于固定内存的分配器。仅当此设备是 CPU 设备时适用。

property is_cpu: bool[source]#

一个布尔值,指示设备是否为 CPU 设备。

property is_cuda: bool[source]#

一个布尔值,指示设备是否为 CUDA 设备。

property is_capturing: bool[source]#

一个布尔值,指示此设备的默认流当前是否正在捕获图。

property context[source]#

与设备关联的上下文。

property has_context: bool[source]#

一个布尔值,指示设备是否关联了 CUDA 上下文。

property stream: Stream[source]#

与 CUDA 设备关联的流。

引发

RuntimeError – 该设备不是 CUDA 设备。

set_stream(stream, sync=True)[source]#

设置此 CUDA 设备的当前流。

当前流将默认用于此设备上的所有内核启动和内存操作。

如果这是外部流,调用者负责保证流的生命周期。

考虑改用 warp.ScopedStream

参数
  • stream (Stream) – 要设置为此设备的当前流。

  • sync (bool) – 如果为 True,则 stream 将与设备的上一个当前流进行设备端同步。

返回类型

None

property has_stream: bool[source]#

一个布尔值,指示设备是否关联了流。

property total_memory: int[source]#

设备上可用的总内存量(字节)。

此函数目前仅针对 CUDA 设备实现。如果在 CPU 设备上调用,将返回 0。

property free_memory: int[source]#

根据操作系统指示的设备上空闲内存量(字节)。

此函数目前仅针对 CUDA 设备实现。如果在 CPU 设备上调用,将返回 0。

make_current()[source]#
can_access(other)[source]#

Warp 还提供了可用于查询系统中可用设备的函数

warp.get_devices()[source]#

返回此环境中支持的设备列表。

返回类型

List[Device]

warp.get_cuda_devices()[source]#

返回此环境中支持的 CUDA 设备列表。

返回类型

List[Device]

warp.get_cuda_device_count()[source]#

返回此环境中支持的 CUDA 设备数量。

返回类型

int

默认设备#

为了简化代码编写,Warp 引入了**默认设备**的概念。当 Warp API 调用中省略 device 参数时,将使用默认设备。

不带参数调用 wp.get_device() 将返回默认设备的 warp.context.Device 实例。

在 Warp 初始化期间,如果 CUDA 可用,默认设备将设置为 "cuda:0"。否则,默认设备为 "cpu"。如果默认设备被更改,可以使用 wp.get_preferred_device() 来获取原始默认设备。

wp.set_device() 可用于更改默认设备

wp.set_device("cpu")
a = wp.zeros(n)
wp.launch(kernel, dim=a.size, inputs=[a])

wp.set_device("cuda:0")
b = wp.zeros(n)
wp.launch(kernel, dim=b.size, inputs=[b])

wp.set_device("cuda:1")
c = wp.zeros(n)
wp.launch(kernel, dim=c.size, inputs=[c])

注意

对于 CUDA 设备,wp.set_device() 执行两项操作:设置 Warp 默认设备,并使设备的 CUDA 上下文成为当前上下文。这有助于最小化针对单个设备的代码块中 CUDA 上下文切换的次数。

对于 PyTorch 用户,此函数类似于 torch.cuda.set_device()。仍然可以在单个 API 调用中指定不同的设备,如下面的代码片段所示

# set default device
wp.set_device("cuda:0")

# use default device
a = wp.zeros(n)

# use explicit devices
b = wp.empty(n, device="cpu")
c = wp.empty(n, device="cuda:1")

# use default device
wp.launch(kernel, dim=a.size, inputs=[a])

wp.copy(b, a)
wp.copy(c, a)
warp.set_device(ident)[source]#

设置由参数标识的默认设备。

参数

ident (Device | str | None)

返回类型

None

warp.get_device(ident=None)[source]#

返回由参数标识的设备。

参数

ident (Device | str | None)

返回类型

Device

warp.get_preferred_device()[source]#

返回首选计算设备,如果可用则为 cuda:0,否则为 cpu

返回类型

Device

作用域设备#

管理默认设备的另一种方法是使用 wp.ScopedDevice 对象。它们可以任意嵌套,并在退出时恢复先前的默认设备

with wp.ScopedDevice("cpu"):
    # alloc and launch on "cpu"
    a = wp.zeros(n)
    wp.launch(kernel, dim=a.size, inputs=[a])

with wp.ScopedDevice("cuda:0"):
    # alloc on "cuda:0"
    b = wp.zeros(n)

    with wp.ScopedDevice("cuda:1"):
        # alloc and launch on "cuda:1"
        c = wp.zeros(n)
        wp.launch(kernel, dim=c.size, inputs=[c])

    # launch on "cuda:0"
    wp.launch(kernel, dim=b.size, inputs=[b])
class warp.ScopedDevice(device)[source]#

一个上下文管理器,用于临时更改当前默认设备。

对于 CUDA 设备,此上下文管理器会使设备的 CUDA 上下文成为当前上下文,并在退出时恢复先前的 CUDA 上下文。这在将 Warp 脚本作为更大管道的一部分运行时很方便,因为它避免了在封闭代码中更改 CUDA 上下文的任何副作用。

参数

device (Devicelike)

device#

将在上下文内临时成为默认设备的设备。

类型

Device

saved_device#

先前的默认设备。退出上下文时,此设备将恢复为默认设备。

类型

Device

__init__(device)[source]#

使用设备初始化上下文管理器。

参数

device (Device | str | None) – 将在上下文内临时成为默认设备的设备。

示例:将 wp.ScopedDevice 与多个 GPU 结合使用#

以下示例展示了如何在所有可用 CUDA 设备上分配数组和启动内核。

import warp as wp


@wp.kernel
def inc(a: wp.array(dtype=float)):
    tid = wp.tid()
    a[tid] = a[tid] + 1.0


# get all CUDA devices
devices = wp.get_cuda_devices()
device_count = len(devices)

# number of launches
iters = 1000

# list of arrays, one per device
arrs = []

# loop over all devices
for device in devices:
    # use a ScopedDevice to set the target device
    with wp.ScopedDevice(device):
        # allocate array
        a = wp.zeros(250 * 1024 * 1024, dtype=float)
        arrs.append(a)

        # launch kernels
        for _ in range(iters):
            wp.launch(inc, dim=a.size, inputs=[a])

# synchronize all devices
wp.synchronize()

# print results
for i in range(device_count):
    print(f"{arrs[i].device} -> {arrs[i].numpy()}")

当前 CUDA 设备#

Warp 使用设备别名 "cuda" 来指定当前 CUDA 设备。这允许外部代码管理执行 Warp 脚本的 CUDA 设备。这类似于 PyTorch 的 "cuda" 设备,Torch 用户应该对此熟悉,并且可以简化互操作。

在此代码片段中,我们使用 PyTorch 管理当前 CUDA 设备并在该设备上调用 Warp 内核

def example_function():
    # create a Torch tensor on the current CUDA device
    t = torch.arange(10, dtype=torch.float32, device="cuda")

    a = wp.from_torch(t)

    # launch a Warp kernel on the current CUDA device
    wp.launch(kernel, dim=a.size, inputs=[a], device="cuda")

# use Torch to set the current CUDA device and run example_function() on that device
torch.cuda.set_device(0)
example_function()

# use Torch to change the current CUDA device and re-run example_function() on that device
torch.cuda.set_device(1)
example_function()

注意

如果在代码运行环境中,另一部分代码可能不可预测地更改 CUDA 上下文,使用设备别名 "cuda" 可能会有问题。建议使用显式 CUDA 设备(如 "cuda:i")来避免此类问题。

设备同步#

CUDA 内核启动和内存操作可以异步执行。这允许在不同设备上重叠计算和内存操作。Warp 允许主机与特定设备上未完成的异步操作进行同步

wp.synchronize_device("cuda:1")

wp.synchronize_device() 提供了比 wp.synchronize() 更细粒度的同步,因为后者等待所有设备完成其工作。

warp.synchronize_device(device=None)[source]#

同步调用 CPU 线程与指定设备上所有未完成的 CUDA 工作

此函数允许主机应用程序代码确保设备上的所有内核启动和内存复制都已完成。

参数

device (Device | str | None) – 要同步的设备。

warp.synchronize()[source]#

手动同步调用 CPU 线程与所有设备上所有未完成的 CUDA 工作

此方法允许主机应用程序代码确保任何内核启动或内存复制都已完成。

自定义 CUDA 上下文#

Warp 设计用于处理任意 CUDA 上下文,因此可以轻松集成到不同的工作流程中。

基于 CUDA Runtime API 构建的应用程序针对每个设备的主上下文。Runtime API 在底层隐藏了 CUDA 上下文管理。在 Warp 中,设备 "cuda:i" 代表设备 i 的主上下文,这与 CUDA Runtime API 一致。

基于 CUDA Driver API 构建的应用程序直接使用 CUDA 上下文,并可以在任何设备上创建自定义 CUDA 上下文。可以创建具有特定亲和性或互操作性功能的自定义 CUDA 上下文,这些功能对应用程序有利。Warp 也可以使用这些 CUDA 上下文。

特殊的设备别名 "cuda" 可用于指定当前的 CUDA 上下文,无论这是主上下文还是自定义上下文。

此外,Warp 允许使用 wp.map_cuda_device() 为自定义 CUDA 上下文注册新的设备别名,以便可以通过名称显式指定它们。如果 CUcontext 指针可用,可以使用它来创建新的设备别名,如下所示

wp.map_cuda_device("foo", ctypes.c_void_p(context_ptr))

或者,如果自定义 CUDA 上下文已被应用程序设为当前上下文,则可以省略该指针

wp.map_cuda_device("foo")

无论哪种情况,映射自定义 CUDA 上下文都允许我们使用分配的别名直接指定该上下文

with wp.ScopedDevice("foo"):
    a = wp.zeros(n)
    wp.launch(kernel, dim=a.size, inputs=[a])
warp.map_cuda_device(alias, context=None)[source]#

为 CUDA 上下文分配设备别名。

此函数可用于为外部 CUDA 上下文创建 wp.Device。如果给定的上下文已存在 wp.Device,则其别名将更改为给定值。

参数
  • alias (str) – 用于标识设备的唯一字符串。

  • context (c_void_p | None) – 一个 CUDA 上下文指针 (CUcontext)。如果为 None,将使用当前绑定的 CUDA 上下文。

返回

关联的 wp.Device。

返回类型

Device

warp.unmap_cuda_device(alias)[source]#

移除具有给定别名的 CUDA 设备。

参数

alias (str)

返回类型

None

CUDA 对等访问#

如果系统硬件配置支持,CUDA 允许不同 GPU 之间直接访问内存。通常,GPU 应为同类型,并且可能需要特殊的互连(例如 NVLINK 或 PCIe 拓扑)。

在初始化期间,Warp 会报告多 GPU 系统上是否支持对等访问

Warp 0.15.1 initialized:
   CUDA Toolkit 11.5, Driver 12.2
   Devices:
     "cpu"      : "x86_64"
     "cuda:0"   : "NVIDIA L40" (48 GiB, sm_89, mempool enabled)
     "cuda:1"   : "NVIDIA L40" (48 GiB, sm_89, mempool enabled)
     "cuda:2"   : "NVIDIA L40" (48 GiB, sm_89, mempool enabled)
     "cuda:3"   : "NVIDIA L40" (48 GiB, sm_89, mempool enabled)
   CUDA peer access:
     Supported fully (all-directional)

如果消息报告 CUDA 对等访问为 Supported fully,则表示系统中的每个 CUDA 设备都可以访问其他所有 CUDA 设备。如果报告为 Supported partially,则其后将显示访问矩阵,显示哪些设备可以相互访问。如果报告为 Not supported,则表示任意设备之间都不支持访问。

在代码中,我们可以像这样检查支持情况并启用对等访问

if wp.is_peer_access_supported("cuda:0", "cuda:1"):
    wp.set_peer_access_enabled("cuda:0", "cuda:1", True):

这将允许设备 cuda:0 的内存可以在设备 cuda:1 上直接访问。对等访问是单向的,这意味着从 cuda:1 启用对设备 cuda:0 的访问并不会自动启用从 cuda:0 对设备 cuda:1 的访问。

启用对等访问的好处是允许设备之间进行直接内存传输 (DMA)。这通常是复制数据的更快方法,否则需要使用 CPU 暂存缓冲区进行传输。

缺点是启用对等访问可能会降低分配和释放的性能。不依赖对等内存传输的程序应保持此设置禁用。

可以使用作用域管理器临时启用或禁用对等访问

with wp.ScopedPeerAccess("cuda:0", "cuda:1", True):
    ...

注意

对等访问不会加速使用 Warp 0.14.0 中引入的流有序内存池分配器分配的数组之间的内存传输。要加速内存池传输,应改为启用内存池访问

warp.is_peer_access_supported(target_device, peer_device)[source]#

检查此系统上 peer_device 是否可以直接访问 target_device 的内存。

这适用于使用默认 CUDA 分配器分配的内存。对于使用 CUDA 池化分配器分配的内存,请使用 is_mempool_access_supported()

返回

一个布尔值,指示此系统是否支持此对等访问。

参数
返回类型

bool

warp.is_peer_access_enabled(target_device, peer_device)[source]#

检查 peer_device 当前是否可以访问 target_device 的内存。

这适用于使用默认 CUDA 分配器分配的内存。对于使用 CUDA 池化分配器分配的内存,请使用 is_mempool_access_enabled()

返回

一个布尔值,指示当前是否启用了此对等访问。

参数
返回类型

bool

warp.set_peer_access_enabled(target_device, peer_device, enable)[源]#

启用或禁用从 peer_devicetarget_device 内存的直接访问。

启用对等访问可以提高对等内存传输的速度,但可能对内存消耗和分配性能产生负面影响。

这适用于使用默认 CUDA 分配器分配的内存。对于使用 CUDA 池化分配器分配的内存,请使用 set_mempool_access_enabled()

参数
返回类型

None