API

cuMemCreate 创建一个handle,分配实际的物理内存
cuMemAddressReserve 创建一个地址范围,产生ptr
cuMemMap 将handle映射到ptr

NCCL使用UDS间接调用:
cuMemExportToShareableHandle 导出句柄
cuMemImportFromShareableHandle 导入句柄

这其中不涉及任何源buffer和目的buffer

因此源buffer向目的buffer的拷贝应当是先拷入ptr作为中转

使用示例

#include <cuda.h>
#include <iostream>
#include <vector>
#include <cstdlib>

#define CHECK_CUDA(call)                                                         \\
    {                                                                            \\
        CUresult err = call;                                                     \\
        if (err != CUDA_SUCCESS) {                                               \\
            const char *err_name;                                                \\
            cuGetErrorName(err, &err_name);                                      \\
            std::cerr << "CUDA error: " << err_name << " at " << __FILE__ << ":" \\
                      << __LINE__ << std::endl;                                  \\
            exit(EXIT_FAILURE);                                                  \\
        }                                                                        \\
    }

size_t BUFFER_SIZE = 1024 * 1024 * 1024; // 1 MB buffer

#define ROUND_UP(x,y) ((x+(y-1))/y*y)

__global__ void set(void *src){
    int *data = (int *)src;
    for(int i=0;i<=10;i++){
        // data[i] = 10 - i;
        data[i] = i;
    }
}

__global__ void get(void *src){
    int *data = (int *)src;
    for(int i=0;i<=10;i++){
        printf("%d ", data[i]);
    }
    printf("\\n");
}

int main() {
    // Initialize the CUDA driver
    CHECK_CUDA(cuInit(0));

    size_t granularity = 0;
    CUmemAllocationProp prop2 = {};
    prop2.type = CU_MEM_ALLOCATION_TYPE_PINNED;
    prop2.location.type = CU_MEM_LOCATION_TYPE_DEVICE;
    prop2.location.id = 0;
    cuMemGetAllocationGranularity(&granularity, &prop2,
                                            CU_MEM_ALLOC_GRANULARITY_MINIMUM);
    BUFFER_SIZE = ROUND_UP(BUFFER_SIZE, granularity);
    printf("BUFFER_SIZE = %ld\\n", BUFFER_SIZE);

    // Get device handles for two GPUs
    CUdevice src_device, dst_device;
    CHECK_CUDA(cuDeviceGet(&src_device, 0));
    CHECK_CUDA(cuDeviceGet(&dst_device, 1));

    // Create contexts for both GPUs
    CUcontext src_context, dst_context;
    CHECK_CUDA(cuCtxCreate(&src_context, 0, src_device));
    CHECK_CUDA(cuCtxCreate(&dst_context, 0, dst_device));

    // Switch to source device context
    CHECK_CUDA(cuCtxSetCurrent(src_context));

    // Create a virtual address range
    CUdeviceptr virtual_address;
    CHECK_CUDA(cuMemAddressReserve(&virtual_address, BUFFER_SIZE, 0, 0, 0));

    // Allocate physical memory on the source GPU
    CUmemAllocationProp prop = {};
    prop.type = CU_MEM_ALLOCATION_TYPE_PINNED;
    prop.location.type = CU_MEM_LOCATION_TYPE_DEVICE;
    prop.location.id = dst_device;

    CUdeviceptr physical_memory_src;
    CHECK_CUDA(cuMemCreate(&physical_memory_src, BUFFER_SIZE, &prop, 0));

    // Map the physical memory to the virtual address
    CHECK_CUDA(cuMemMap(virtual_address, BUFFER_SIZE, 0, physical_memory_src, 0));

    // Set access permissions for the source device
    CUmemAccessDesc access_desc = {};
    access_desc.location.type = CU_MEM_LOCATION_TYPE_DEVICE;
    access_desc.location.id = src_device;
    access_desc.flags = CU_MEM_ACCESS_FLAGS_PROT_READWRITE;

    CHECK_CUDA(cuMemSetAccess(virtual_address, BUFFER_SIZE, &access_desc, 1));

    CHECK_CUDA(cuCtxSetCurrent(dst_context));
    access_desc.location.type = CU_MEM_LOCATION_TYPE_DEVICE;
    access_desc.location.id = dst_device;
    access_desc.flags = CU_MEM_ACCESS_FLAGS_PROT_READWRITE;
    CHECK_CUDA(cuMemSetAccess(virtual_address, BUFFER_SIZE, &access_desc, 1));

    CHECK_CUDA(cuCtxSetCurrent(src_context));
    CUdeviceptr data, ans;
    cuMemAlloc(&data, BUFFER_SIZE);
    cuMemAlloc(&ans, BUFFER_SIZE);
    set<<<1,1>>>((void *)data);
    cudaMemcpy((void *)virtual_address, (void *)data, BUFFER_SIZE,            cudaMemcpyDeviceToDevice);
    cudaMemcpy((void *)ans, (void *)virtual_address,  BUFFER_SIZE, cudaMemcpyDeviceToDevice);
    get<<<1,1>>>((void *)ans);
    CHECK_CUDA(cuCtxSynchronize());

    // Clean up
    CHECK_CUDA(cuMemUnmap(virtual_address, BUFFER_SIZE));
    CHECK_CUDA(cuMemRelease(physical_memory_src));
    CHECK_CUDA(cuMemAddressFree(virtual_address, BUFFER_SIZE));

    CHECK_CUDA(cuCtxDestroy(src_context));
    CHECK_CUDA(cuCtxDestroy(dst_context));
    return 0;
}

执行结果

./test3
BUFFER_SIZE = 1073741824
0 1 2 3 4 5 6 7 8 9 10

内存占用

Chunck buffer

send/recv端分别分配一块内存,通过vmm技术将recv端内存[ global ]在两端映射,生成两f端的dptr

从最终映射的地址看proxy在做send chunck到dptr的拷贝 下图中[ set ]为dptr的地址

send端

ncclCudaCalloc

dptr

recv端

ncclP2pAllocateShareableBuffer

kernel 拷贝的同步 共享主机内存

Shm 步进控制(demo)

生产

#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

void process1() {
    const char* shm_name = "/shared_mem";
    size_t size = 1024 * sizeof(int); // 共享内存大小

    // 创建共享内存对象
    int shm_fd = shm_open(shm_name, O_CREAT | O_RDWR, 0666);
    ftruncate(shm_fd, size); // 设置共享内存大小

    // 映射共享内存到进程地址空间
    int* ptr = (int *)mmap(0, size, PROT_WRITE, MAP_SHARED, shm_fd, 0);
    for(int i=0; i < 10; i++){
        ptr[i] = 0;
    }
    int idx = 0;
    while(1){
        if(ptr[idx] == 0){
            // Copy
            printf("copy [ %d ]\\n", idx);
            ptr[idx] = 1;
            idx = (idx + 1) % 10;
        }
        sleep(1);
    }
}
int main(){
        process1();
}

消费

#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>

void process2() {
    const char* shm_name = "/shared_mem";
    size_t size = 1024 * sizeof(int); // 共享内存大小

    // 打开共享内存对象
    int shm_fd = shm_open(shm_name, O_RDWR, 0666);

    // 映射共享内存到进程地址空间
    int* ptr = (int *)mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);

    int idx = 0;
    while(1){
        // Paste
        if(ptr[idx] == 1){
            ptr[idx] = 0;
            idx = (idx + 1) % 10;
        }
    }

    // 释放共享内存(进程结束时)
    munmap(ptr, size);
    close(shm_fd);
    shm_unlink(shm_name);
}
int main(){
        process2();
}

设计

1、recv端没有proxy,需要两端启动proxy

2、完成源buffer到全局buffer(dptr),全局buffer到目的buffer的拷贝

3、buffer地址和收发参数在结构体中的传递

4、recv端需要同样找到这些信息

send端获取到全局buffer

最新进展

src->全局buffer / 全局buffer->dst获取了buffer的地址
需要添加两端的同步控制

这里验证了recv端可以从全局buffer获取到预制的数据

临时使用睡眠增加延时代替同步,4字节send/recv稳定跑通

code

d2:/root/waibibabu/p2p

验证脚本

d2:/root/waibibabu/runp2p

2025.2.6

TODO:找到chunck buffer size,合并机内机间代码

2025.2.12

机内机间打通

<http://183.207.7.174:8081/moon/vccl_2.21.51x/-/tree/nokernel?ref_type=heads>

执行脚本

#! /usr/bin/bash

MPIRUN=/root/waibibabu/mpich-4.2.3/build/bin/mpirun
LIBRARY_PATH=./mpich-4.2.3/build/lib/:./gitlab/vccl_2.21.51x/build/lib/
$MPIRUN -np 4 -host node131:2,node132:2 -genv NCCL_BUFFSIZE=33554432 -genv NCCL_DEBUG= -genv NCCL_DEBUG_SUBSYS=p2p -genv NCCL_IB_HCA=mlx5_0,mlx5_1 -genv NCCL_P2P_USE_CUDA_MEMCPY=1 -genv NCCL_PROTO=Simple -genv NCCL_IB_MERGE_NICS=0 -genv NCCL_MAX_NCHANNELS=1 -genv LD_LIBRARY_PATH=$LIBRARY_PATH:$LD_LIBRARY_PATH nccl-tests/build/sendrecv_perf -b 4M -e 512M -f 2 -w 0 -n 1

2025.2.14

支持alltoall

#! /usr/bin/bash

MPIRUN=/root/waibibabu/mpich-4.2.3/build/bin/mpirun
LIBRARY_PATH=./mpich-4.2.3/build/lib/:./gitlab/vccl_v2/build/lib/

$MPIRUN -np 4 -host node131:2,node132:2 -genv NCCL_BUFFSIZE=33554432 -genv NCCL_DEBUG= -genv NCCL_DEBUG_SUBSYS=init -genv NCCL_IB_HCA=mlx5_0,mlx5_1 -genv NCCL_P2P_USE_CUDA_MEMCPY=1 -genv NCCL_PROTO=Simple -genv NCCL_IB_MERGE_NICS=0 -genv NCCL_MAX_NCHANNELS=1 -genv LD_LIBRARY_PATH=$LIBRARY_PATH:$LD_LIBRARY_PATH nccl-tests/build/sendrecv_perf -b 4 -e 4G -f 3 -w 1 -n 1