首页 > 编程问答 >如何在 2 个 GPU 之间发送缓冲区?

如何在 2 个 GPU 之间发送缓冲区?

时间:2024-07-21 04:52:11浏览次数:15  
标签:python cuda pycuda multi-gpu cupy

我尝试将缓冲区 A 和缓冲区 B 分配给 GPU 0,然后将它们(缓冲区 A 和缓冲区 B)分配给 GPU 1,并运行它们添加 A + B 的内核,并将其分配给缓冲区 D(我之前在 GPU 中分配) 1)

我的问题是它在 python 中不起作用,在 pycuda 中不起作用,在 cup 中不起作用。

我在 PyCuda 中尝试了这段代码,它可以工作,但我不能证明它在 GPU1 上分配缓冲区

import pycuda.driver as cuda
import pycuda.autoinit
from pycuda.compiler import SourceModule
import numpy as np
import time

cuda.init()

array_size = 1000000
h_a = np.random.randint(0, 101, size=array_size).astype(np.float32)
h_b = np.random.randint(0, 101, size=array_size).astype(np.float32)
h_c = np.random.randint(0, 101, size=array_size).astype(np.float32)

dev0 = cuda.Device(0)
ctx0 = dev0.make_context()
ctx0.push()
d_a_gpu0 = cuda.mem_alloc(h_a.nbytes)
d_b_gpu0 = cuda.mem_alloc(h_b.nbytes)
d_c_gpu0 = cuda.mem_alloc(h_c.nbytes)
cuda.memcpy_htod(d_a_gpu0, h_a)
cuda.memcpy_htod(d_b_gpu0, h_b)
cuda.memcpy_htod(d_c_gpu0, h_c)
ctx0.pop()

dev1 = cuda.Device(1)
ctx1 = dev1.make_context()
ctx1.push()
d_a_gpu1 = cuda.mem_alloc(h_a.nbytes)
d_b_gpu1 = cuda.mem_alloc(h_b.nbytes)
d_c_gpu1 = cuda.mem_alloc(h_c.nbytes)
ctx1.pop()

ctx0.push()
start_peer_transfer = time.time()
cuda.memcpy_peer(d_a_gpu1, dest_context=ctx1, src=d_a_gpu0, src_context=ctx0, size=h_a.nbytes)
cuda.memcpy_peer(d_b_gpu1, dest_context=ctx1, src=d_b_gpu0, src_context=ctx0, size=h_b.nbytes)
end_peer_transfer = time.time()
ctx0.pop()

kernel_code = """
__global__ void add_arrays(float *a, float *b, float *c)
{
    int idx = threadIdx.x + blockIdx.x * blockDim.x;
    if (idx < 1000000)
    {
        c[idx] = a[idx] + b[idx];
    }
}
"""

mod = SourceModule(kernel_code)
add_arrays = mod.get_function("add_arrays")

block_size = 256
grid_size = (array_size + block_size - 1) // block_size
ctx0.push()
start_gpu0_kernel = time.time()
add_arrays(d_a_gpu0, d_b_gpu0, d_c_gpu0, block=(block_size, 1, 1), grid=(grid_size, 1))
cuda.Context.synchronize()
end_gpu0_kernel = time.time()
ctx0.pop()

ctx1.push()
start_gpu1_kernel = time.time()
add_arrays(d_a_gpu1, d_b_gpu1, d_c_gpu1, block=(block_size, 1, 1), grid=(grid_size, 1))
cuda.Context.synchronize()
end_gpu1_kernel = time.time()
ctx1.pop()

h_c_result = np.empty_like(h_c)
h_d_result = np.empty_like(h_c)
ctx0.push()
cuda.memcpy_dtoh(h_c_result, d_c_gpu0)
ctx0.pop()

ctx1.push()
cuda.memcpy_dtoh(h_d_result, d_c_gpu1)
ctx1.pop()

comparison = np.allclose(h_c_result, h_d_result)
print("Results are identical:", comparison)

print(f"Time for cuda.memcpy_peer: {end_peer_transfer - start_peer_transfer:.6f} seconds")
print(f"Time for kernel execution on GPU 0: {end_gpu0_kernel - start_gpu0_kernel:.6f} seconds")
print(f"Time for kernel execution on GPU 1: {end_gpu1_kernel - start_gpu1_kernel:.6f} seconds")

d_a_gpu0.free()
d_b_gpu0.free()
d_c_gpu0.free()
d_a_gpu1.free()
d_b_gpu1.free()
d_c_gpu1.free()
ctx1.pop()
ctx0.pop()

For CuPy 我尝试了这段代码,该代码在“deviceCanAccessPeer”上返回错误

import cupy as cp
import numpy as np
import time

try:
    with cp.cuda.Device(0):
        A = cp.random.rand(1000)
        B = cp.random.rand(1000)
        C = cp.empty_like(A)
        
        start_time = time.time()
        
        C = A + B
        
        C_cpu = cp.asnumpy(C)
        
        print("Device 0 computation time:", time.time() - start_time)

    if cp.cuda.runtime.getDeviceCount() > 1:
        cp.cuda.runtime.deviceCanAccessPeer(1, 0)
        cp.cuda.runtime.deviceEnablePeerAccess(1)

    with cp.cuda.Device(1):
        D = cp.empty_like(A)
        
        start_time = time.time()
        
        A_device1 = cp.empty_like(A)
        B_device1 = cp.empty_like(B)
        
        cp.cuda.runtime.memcpyPeer(A_device1.data.ptr, 1, A.data.ptr, 0, A.nbytes)
        cp.cuda.runtime.memcpyPeer(B_device1.data.ptr, 1, B.data.ptr, 0, B.nbytes)
        
        D = A_device1 + B_device1
        
        D_cpu = cp.asnumpy(D)
        
        print("Device 1 computation time:", time.time() - start_time)

    comparison = np.array_equal(C_cpu, D_cpu)
    print("Comparison result:", comparison)

except cp.cuda.runtime.CUDARuntimeError as e:
    print(f"CUDA Runtime Error: {e}")
except Exception as e:
    print(f"An error occurred: {e}")


当然可以,我可以帮助你弄清楚如何在两个 GPU 之间传输缓冲区。

你遇到的错误是因为虽然你正在使用 cuda.memcpy_peer cp.cuda.runtime.memcpyPeer ,但你没有在使用 CuPy 时启用对等访问,也没有在你的代码的关键部分设置当前上下文。

以下是如何在 PyCUDA 和 CuPy 中正确执行此操作的方法:

PyCUDA

import pycuda.driver as cuda
import pycuda.autoinit
from pycuda.compiler import SourceModule
import numpy as np
import time

cuda.init()

array_size = 1000000
h_a = np.random.randint(0, 101, size=array_size).astype(np.float32)
h_b = np.random.randint(0, 101, size=array_size).astype(np.float32)
h_c = np.random.randint(0, 101, size=array_size).astype(np.float32)

dev0 = cuda.Device(0)
ctx0 = dev0.make_context()
ctx0.push()
d_a_gpu0 = cuda.mem_alloc(h_a.nbytes)
d_b_gpu0 = cuda.mem_alloc(h_b.nbytes)
cuda.memcpy_htod(d_a_gpu0, h_a)
cuda.memcpy_htod(d_b_gpu0, h_b)
ctx0.pop()

dev1 = cuda.Device(1)
ctx1 = dev1.make_context()
ctx1.push()
d_a_gpu1 = cuda.mem_alloc(h_a.nbytes)
d_b_gpu1 = cuda.mem_alloc(h_b.nbytes)
d_c_gpu1 = cuda.mem_alloc(h_c.nbytes)
ctx1.pop()

# 在进行任何内存传输之前启用对等访问
cuda.enable_peer_access(src_device=dev0, dest_device=dev1)

ctx0.push()
cuda.memcpy_peer(d_a_gpu1, dest_context=ctx1, src=d_a_gpu0, src_context=ctx0, size=h_a.nbytes)
cuda.memcpy_peer(d_b_gpu1, dest_context=ctx1, src=d_b_gpu0, src_context=ctx0, size=h_b.nbytes)
ctx0.pop()

kernel_code = """
__global__ void add_arrays(float *a, float *b, float *c)
{
    int idx = threadIdx.x + blockIdx.x * blockDim.x;
    if (idx < 1000000)
    {
        c[idx] = a[idx] + b[idx];
    }
}
"""

mod = SourceModule(kernel_code)
add_arrays = mod.get_function("add_arrays")

block_size = 256
grid_size = (array_size + block_size - 1) // block_size

ctx1.push()
start_gpu1_kernel = time.time()
add_arrays(d_a_gpu1, d_b_gpu1, d_c_gpu1, block=(block_size, 1, 1), grid=(grid_size, 1))
cuda.Context.synchronize()
end_gpu1_kernel = time.time()
ctx1.pop()

h_d_result = np.empty_like(h_c)
ctx1.push()
cuda.memcpy_dtoh(h_d_result, d_c_gpu1)
ctx1.pop()

# 清理(其他释放保持不变)
cuda.disable_peer_access(src_device=dev0, dest_device=dev1)

print(f"Time for kernel execution on GPU 1: {end_gpu1_kernel - start_gpu1_kernel:.6f} seconds")

# ...(其余的清理代码)

CuPy

import cupy as cp
import numpy as np
import time

with cp.cuda.Device(0):
    A = cp.random.rand(1000).astype(cp.float32)
    B = cp.random.rand(1000).astype(cp.float32)

if cp.cuda.runtime.getDeviceCount() > 1:
    # 检查对等访问可用性
    if cp.cuda.runtime.deviceCanAccessPeer(0, 1):
        cp.cuda.runtime.deviceEnablePeerAccess(1)
        cp.cuda.runtime.deviceEnablePeerAccess(0)  # 在两个方向上启用

        with cp.cuda.Device(1):
            A_device1 = cp.empty_like(A)
            B_device1 = cp.empty_like(B)
            C_device1 = cp.empty_like(A)

            # 使用流进行异步复制
            with cp.cuda.Stream() as stream:
                A_device1.data.copy_from(A.data, stream=stream)
                B_device1.data.copy_from(B.data, stream=stream)
                stream.synchronize()  # 等待复制完成

            C_device1 = A_device1 + B_device1

            print(C_device1)

        cp.cuda.runtime.deviceDisablePeerAccess(1)
        cp.cuda.runtime.deviceDisablePeerAccess(0)
    else:
        print("GPU 0 无法访问 GPU 1 的内存")

说明:

  • 对等访问和检查: 在使用 cuda.memcpy_peer cp.cuda.runtime.memcpyPeer 之前,你需要启用对等访问。这对 PyCUDA 和 CuPy 都至关重要。还要确保 GPU 可以通过 deviceCanAccessPeer 函数相互访问。
  • 上下文管理: PyCUDA 需要明确的上下文管理。在执行任何操作之前,请确保在要使用的正确设备/上下文中。
  • 错误检查: 始终包含错误检查!CUDA 函数通常返回错误代码,并且包含 cuda.Context.synchronize() (对于 PyCUDA)等同步点可以帮助识别你的代码中出现问题的位置。
  • 性能: 考虑使用 CUDA 流来实现异步内存传输,从而有可能提高性能,尤其是在执行多个传输操作时。

通过这些更改,你的代码应该可以在两个 GPU 之间正确传输数据。请记住在使用完对等访问后禁用它,以防止潜在的冲突。

如有任何其他问题,请告诉我!

标签:python,cuda,pycuda,multi-gpu,cupy
From: 78773865

相关文章

  • 除了curses之外,是否有一个python包可以轻松控制终端的输出?
    我现在正在处理一些小项目,我对GUI的偏好是终端中漂亮的文本界面。我宁愿不强迫用户处理Windowscurses二进制文件,所以我正在寻找不同的选项。我已经发现了asciimatics,但我想考虑所有可能的选择。如果有人有任何经验或知道解决此用例的包,我将不胜感激。谢谢你说的没错......
  • 当值来自函数 python unittest 时,如何模拟全局变量
    我必须在python中模拟全局变量,但变量值来自另一个函数。当我导入文件时,这个函数正在运行,但我想要那里的模拟值。secrets.pyimporttracebackimportloggingimportboto3importosimportjsonlogger=logging.getLogger()logger.setLevel(logging.INFO)secret_......
  • 使用 python print 和 gdb 时出现 BrokenPipeError
    我正在尝试在Linux中运行应用程序并使用Python生成输入:python3-c'print(".....")'|./someapp但出现下一个错误:Exceptionignoredin:<_io.TextIOWrappername='<stdout>'mode='w'encoding='utf-8'>BrokenPipeError:......
  • python 舰队容器
    我正在尝试使用容器在flet中制作一个菜单,它应该是半透明的,但其中的项目不是。我尝试将opacity=1分配给元素,但没有成功-它们与容器一样透明感谢任何帮助我的代码:nickname=ft.TextField(label="xxx",hint_text="xxx")column=ft.Column(controls=[nickname......
  • Python应用程序跨子包共享的配置文件
    我正在构建一个应用程序来控制一些硬件。我在包中实现了不同类型的硬件:电机和测量设备。我的文件结构如下:name_of_my_app/__init__.pymain.pyconfig.iniCONFIG.pymotors/__init__.pyone_kind_of_motor.pymeasurement_devices/......
  • python中时间序列数据的梯度计算
    我正在尝试编写一个函数,它可以从最适合下面的线返回梯度dataframe在浏览了谷歌的几个资源之后,我仍然不确定这是如何完成的。我明白最佳拟合线的计算公式为:y=mx+b将因变量(y)设置为foos,将自变量(x)设置为DateTimeDatafram......
  • 调试用 C 编写的 Python 扩展
    我非常熟悉编写C代码,并且很擅长编写Python代码。我正在尝试学习如何用C编写可以从OSX10.15.7上的Python-3.9.X调用的模块。我已经得到了几个“helloworld”类型的示例,但是对于复杂的示例,我正在努力弄清楚如何调试我编写的C扩展。MWE:src/add.c//......
  • 具有块大小选项的 Python pandas read_sas 因索引不匹配而失败并出现值错误
    我有一个非常大的SAS文件,无法容纳在我的服务器内存中。我只需要转换为镶木地板格式的文件。为此,我使用pandas中chunksize方法的read_sas选项分块读取它。它主要是在工作/做它的工作。除此之外,一段时间后它会失败并出现以下错误。此特定SAS文件有794......
  • 使用 requests 包 python 时打开文件太多
    我正在使用Pythonrequests包向API发出大量请求。然而,在某些时候,我的程序由于“打开的文件太多”而崩溃。当我明确关闭我的会话时,我真的不知道这是怎么回事。我使用以下代码:importrequestsimportmultiprocessingimportnumpyasnps=requests.session()s.keep......
  • Python 是一种选择性解释语言吗?为什么下面的代码不起作用?
    由于程序是从上到下运行的,为什么下面的代码不执行块中的第一行就直接抛出错误?if5>2:print("TwoislessthanFive!")print("Fiveisgreaterthantwo!")错误:文件“/Users/____/Desktop/Pythonpractise/practise.py”,第3行print("五比二大!")Indentati......