深度学习部署笔记(十五): CUDA_Run_Time_API_parallel_多流并行,以及多流之间互相同步等待的操作方式

此代码示例展示了CUDA中如何利用多个流进行异步计算,以及如何通过事件进行流之间的同步。`async`函数演示了在两个流上并发执行向量加法和乘法,提高效率。`sync`函数则展示了单个流内的串行执行。此外,`multi_stream_async`函数通过事件控制实现了多个流之间复杂的等待关系,确保特定操作按顺序执行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

// CUDA运行时头文件
#include <cuda_runtime.h>

#include <chrono>
#include <stdio.h>
#include <string.h>

using namespace std;

#define checkRuntime(op)  __check_cuda_runtime((op), #op, __FILE__, __LINE__)

bool __check_cuda_runtime(cudaError_t code, const char* op, const char* file, int line){
    if(code != cudaSuccess){    
        const char* err_name = cudaGetErrorName(code);    
        const char* err_message = cudaGetErrorString(code);  
        printf("runtime error %s:%d  %s failed. \n  code = %s, message = %s\n", file, line, op, err_name, err_message);   
        return false;
    }
    return true;
}

__global__ void add_vector(const float* a, const float* b, float* c, int count){

    int index = blockDim.x * blockIdx.x + threadIdx.x;
    if(index >= count) return;
    c[index] = a[index] + b[index];
}

__global__ void mul_vector(const float* a, const float* b, float* c, int count){

    int index = blockDim.x * blockIdx.x + threadIdx.x;
    if(index >= count) return;
    c[index] = a[index] * b[index];
}

cudaStream_t stream1, stream2;
float *a, *b, *c1, *c2;
const int num_element = 100000;
const size_t bytes = sizeof(float) * num_element;
const int blocks = 512;
const int grids = (num_element + blocks - 1) / blocks;
const int ntry = 1000;

// 多个流异步
void async(){

    cudaEvent_t event_start1, event_stop1;
    cudaEvent_t event_start2, event_stop2;
    checkRuntime(cudaEventCreate(&event_start1));
    checkRuntime(cudaEventCreate(&event_stop1));
    checkRuntime(cudaEventCreate(&event_start2));
    checkRuntime(cudaEventCreate(&event_stop2));

    auto tic = chrono::duration_cast<chrono::microseconds>(chrono::system_clock::now().time_since_epoch()).count() / 1000.0;
    checkRuntime(cudaEventRecord(event_start1, stream1));
    for(int i = 0; i < ntry; ++i)
        add_vector<<<grids, blocks, 0, stream1>>>(a, b, c1, num_element);
    checkRuntime(cudaEventRecord(event_stop1, stream1));
    
    checkRuntime(cudaEventRecord(event_start2, stream2));
    for(int i = 0; i < ntry; ++i)
        add_vector<<<grids, blocks, 0, stream2>>>(a, b, c2, num_element);
    checkRuntime(cudaEventRecord(event_stop2, stream2));

    checkRuntime(cudaStreamSynchronize(stream1));
    checkRuntime(cudaStreamSynchronize(stream2));
    auto toc = chrono::duration_cast<chrono::microseconds>(chrono::system_clock::now().time_since_epoch()).count() / 1000.0;

    float time1, time2;
    checkRuntime(cudaEventElapsedTime(&time1, event_start1, event_stop1));
    checkRuntime(cudaEventElapsedTime(&time2, event_start2, event_stop2));
    printf("async: time1 = %.2f ms, time2 = %.2f ms, count = %.2f ms\n", time1, time2, toc - tic);
}

// 单个流串行
void sync(){

    cudaEvent_t event_start1, event_stop1;
    checkRuntime(cudaEventCreate(&event_start1));
    checkRuntime(cudaEventCreate(&event_stop1));

    auto tic = chrono::duration_cast<chrono::microseconds>(chrono::system_clock::now().time_since_epoch()).count() / 1000.0;
    checkRuntime(cudaEventRecord(event_start1, stream1));
    for(int i = 0; i < ntry; ++i)
        add_vector<<<grids, blocks, 0, stream1>>>(a, b, c1, num_element);
    
    for(int i = 0; i < ntry; ++i)
        add_vector<<<grids, blocks, 0, stream1>>>(a, b, c2, num_element);
    checkRuntime(cudaEventRecord(event_stop1, stream1));

    checkRuntime(cudaStreamSynchronize(stream1));
    auto toc = chrono::duration_cast<chrono::microseconds>(chrono::system_clock::now().time_since_epoch()).count() / 1000.0;

    float time1;
    checkRuntime(cudaEventElapsedTime(&time1, event_start1, event_stop1));
    printf("sync: time1 = %.2f ms, count = %.2f ms\n", time1, toc - tic);
}

// 多个流之间并行
void multi_stream_async(){

    // 这个案例主要实现多个流之间互相等待,使用event控制实现
    // 存在step1  ->  step2 \ 
    //                      ->  step3   ->  step4
    //               stepa / 
    //
    // 这个案例中,存在流程1:step1 -> step2的流程
    //           存在流程2:stepa
    //           存在流程3:step3 -> step4,step3要求step2与stepa作为输入
    // 此时,可以让流程1使用stream1,流程2使用stream2,而流程3继续使用stream1,仅仅在stream1中加入等待(event的等待)

    // step1 = add_vector
    // step2 = mul_vector
    // step3 = add_vector
    // step4 = mul_vector
    // stepa = add_vector
    #define step1 add_vector
    #define step2 mul_vector
    #define step3 add_vector
    #define step4 mul_vector
    #define stepa add_vector

    cudaEvent_t event_async;
    checkRuntime(cudaEventCreate(&event_async));

    // stream1的执行流程
    step1<<<grids, blocks, 0, stream1>>>(a, b, c1, num_element);
    step2<<<grids, blocks, 0, stream1>>>(a, b, c1, num_element);

    // 等待event_async有事件
    checkRuntime(cudaStreamWaitEvent(stream1, event_async));
    step3<<<grids, blocks, 0, stream1>>>(a, b, c2, num_element);
    step4<<<grids, blocks, 0, stream1>>>(a, b, c2, num_element);

    // stream2的执行流程
    stepa<<<grids, blocks, 0, stream2>>>(a, b, c2, num_element);
    
    // 为event_async触发事件,通知cudaStreamWaitEvent函数可以继续了
    checkRuntime(cudaEventRecord(event_async, stream2));
    checkRuntime(cudaStreamSynchronize(stream1));

    printf("multi_stream_async done.\n");
}

int main(){

    // 本程序实现两个核函数的并行,通过多个流实现
    
    checkRuntime(cudaStreamCreate(&stream1));
    checkRuntime(cudaStreamCreate(&stream2));

    checkRuntime(cudaMalloc(&a, bytes));
    checkRuntime(cudaMalloc(&b, bytes));
    checkRuntime(cudaMalloc(&c1, bytes));
    checkRuntime(cudaMalloc(&c2, bytes));

    // 演示多流之间的异步执行
    async();

    // 演示单个流内的同步执行
    sync();

    // 演示多个流之间互相等待的操作
    multi_stream_async();
    return 0;
}

2. 单个流串行

void sync(){

    cudaEvent_t event_start1, event_stop1;
    checkRuntime(cudaEventCreate(&event_start1));
    checkRuntime(cudaEventCreate(&event_stop1));

    auto tic = chrono::duration_cast<chrono::microseconds>(chrono::system_clock::now().time_since_epoch()).count() / 1000.0;
    checkRuntime(cudaEventRecord(event_start1, stream1));
    for(int i = 0; i < ntry; ++i)
        add_vector<<<grids, blocks, 0, stream1>>>(a, b, c1, num_element);
    
    for(int i = 0; i < ntry; ++i)
        add_vector<<<grids, blocks, 0, stream1>>>(a, b, c2, num_element);
    checkRuntime(cudaEventRecord(event_stop1, stream1));

    checkRuntime(cudaStreamSynchronize(stream1));
    auto toc = chrono::duration_cast<chrono::microseconds>(chrono::system_clock::now().time_since_epoch()).count() / 1000.0;

    float time1;
    checkRuntime(cudaEventElapsedTime(&time1, event_start1, event_stop1));
    printf("sync: time1 = %.2f ms, count = %.2f ms\n", time1, toc - tic);
}
cuda count time: 12.26, cpp count time: 12.28

这个函数演示了单个流中的同步执行,具体解释如下:

cudaEvent_t 是 CUDA Runtime API 中的一个结构体,定义在 cuda_runtime_api.h 中。它用于表示一个 CUDA 事件对象,用于记录 GPU 上某个时间点的状态。

CUDA 事件可以用于两种目的:

记录一个时间点(如开始时间点或结束时间点)。
记录一个时间间隔(即时间差)。
通常情况下,CUDA 事件被用于在主机和设备之间进行同步,或在设备内部进行同步。例如,可以在主机代码中调用 cudaEventRecord() 来记录一个事件,然后在设备代码中使用 cudaStreamWaitEvent() 等待该事件,以确保某些设备操作发生在之前记录的事件之后。又或者,可以在设备代码中记录两个事件,然后在主机代码中使用 cudaEventElapsedTime() 计算它们之间的时间差。

首先创建两个事件 event_start1 和 event_stop1,用于记录同步执行的时间;

使用 cudaEventRecord 将 event_start1 记录在 stream1 中,表示从这个时间点开始,将会执行在 stream1 中的操作;

使用 for 循环调用 add_vector 核函数,在 stream1 中执行 ntry 次,计算向量 a 和 b 的加和,存储在向量 c1 和 c2 中;

使用 cudaEventRecord 将 event_stop1 记录在 stream1 中,表示到达这个时间点,stream1 中的操作都已经完成;

使用 cudaStreamSynchronize 等待 stream1 中的所有操作执行完毕;

计算同步执行的时间 time1,并输出时间和整个操作的时间。

可以看到,这个函数中只使用了一个流,因此 add_vector 的计算是按照顺序执行的,不能充分发挥 GPU 的并行计算能力。因此,这个函数的计算时间会比异步执行的 async 函数要长

这段代码中使用了两种方法来计算代码执行的时间。

第一种方法是使用了C++标准库中的chrono库来计算代码执行的起始时间和终止时间,通过计算时间差得到代码执行的时间,这个方法在计算异步执行时比较方便,因为我们需要分别记录多个异步操作的起始时间和终止时间。

第二种方法是使用了CUDA提供的API cudaEventElapsedTime,这个API可以计算CUDA事件的时间差,用于计算CUDA事件执行的时间。在这个例子中,我们使用了这个API来计算在单个流上串行执行的时间。

3. 向量相加相乘的kernel function

__global__ void add_vector(const float* a, const float* b, float* c, int count){

    int index = blockDim.x * blockIdx.x + threadIdx.x;
    if(index >= count) return;
    c[index] = a[index] + b[index];
}

__global__ void mul_vector(const float* a, const float* b, float* c, int count){

    int index = blockDim.x * blockIdx.x + threadIdx.x;
    if(index >= count) return;
    c[index] = a[index] * b[index];
}

count 是用来限制线程不要访问到超出数组的地址,因为数组的长度在我们开辟的时候就已经定义好了

checkRuntime(cudaMalloc(&a, bytes)); 

count是num_element, byte是num_element * sizeof(float), 超出地址会访问到虚拟地址

4. 多个流的异步

void async(){

    cudaEvent_t event_start1, event_stop1;
    cudaEvent_t event_start2, event_stop2;
    checkRuntime(cudaEventCreate(&event_start1));
    checkRuntime(cudaEventCreate(&event_stop1));
    checkRuntime(cudaEventCreate(&event_start2));
    checkRuntime(cudaEventCreate(&event_stop2));

    auto tic = chrono::duration_cast<chrono::microseconds>(chrono::system_clock::now().time_since_epoch()).count() / 1000.0;
    checkRuntime(cudaEventRecord(event_start1, stream1));
    for(int i = 0; i < ntry; ++i)
        add_vector<<<grids, blocks, 0, stream1>>>(a, b, c1, num_element);
    checkRuntime(cudaEventRecord(event_stop1, stream1));
    
    checkRuntime(cudaEventRecord(event_start2, stream2));
    for(int i = 0; i < ntry; ++i)
        add_vector<<<grids, blocks, 0, stream2>>>(a, b, c2, num_element);
    checkRuntime(cudaEventRecord(event_stop2, stream2));

    checkRuntime(cudaStreamSynchronize(stream1));
    checkRuntime(cudaStreamSynchronize(stream2));
    auto toc = chrono::duration_cast<chrono::microseconds>(chrono::system_clock::now().time_since_epoch()).count() / 1000.0;

    float time1, time2;
    checkRuntime(cudaEventElapsedTime(&time1, event_start1, event_stop1));
    checkRuntime(cudaEventElapsedTime(&time2, event_start2, event_stop2));
    printf("async: time1 = %.2f ms, time2 = %.2f ms, count = %.2f ms\n", time1, time2, toc - tic);
}
async: time1 = 6.97 ms, time2 = 6.94 ms, count = 9.32 ms

输出的内容中包含了在两个流上异步执行的两个内核函数的时间,分别为time1和time2,它们的值应该是相当接近的。同时,输出中还包含了整个函数执行的总时间count,可以看出相比于同步执行的情况,异步执行使得程序的总执行时间更短,效率更高。

5. 多个流之间互相等待的操作

// 这个案例主要实现多个流之间互相等待,使用event控制实现
    // 存在step1  ->  step2 \ 
    //                      ->  step3   ->  step4
    //               stepa / 
    //
    // 这个案例中,存在流程1:step1 -> step2的流程
    //           存在流程2:stepa
    //           存在流程3:step3 -> step4,step3要求step2与stepa作为输入
    // 此时,可以让流程1使用stream1,流程2使用stream2,而流程3继续使用stream1,仅仅在stream1中加入等待(event的等待)

    // step1 = add_vector
    // step2 = mul_vector
    // step3 = add_vector
    // step4 = mul_vector
    // stepa = add_vector
    #define step1 add_vector
    #define step2 mul_vector
    #define step3 add_vector
    #define step4 mul_vector
    #define stepa add_vector

    cudaEvent_t event_async;
    checkRuntime(cudaEventCreate(&event_async));

    // stream1的执行流程
    step1<<<grids, blocks, 0, stream1>>>(a, b, c1, num_element);
    step2<<<grids, blocks, 0, stream1>>>(a, b, c1, num_element);

    // 等待event_async有事件
    checkRuntime(cudaStreamWaitEvent(stream1, event_async));
    step3<<<grids, blocks, 0, stream1>>>(a, b, c2, num_element);
    step4<<<grids, blocks, 0, stream1>>>(a, b, c2, num_element);

    // stream2的执行流程
    stepa<<<grids, blocks, 0, stream2>>>(a, b, c2, num_element);
    
    // 为event_async触发事件,通知cudaStreamWaitEvent函数可以继续了
    checkRuntime(cudaEventRecord(event_async, stream2));
    checkRuntime(cudaStreamSynchronize(stream1));

    printf("multi_stream_async done.\n");

具体流程如下:

在stream1中先执行step1,然后执行step2,这两个步骤是串行执行的;

在stream1中调用cudaStreamWaitEvent函数等待event_async事件,此时流程3(step3和step4)还不能开始执行;

在stream2中执行stepa,此时stepa和之前的步骤是并行执行的;

在stream2中调用cudaEventRecord函数触发event_async事件,通知stream1可以开始执行流程3;

在stream1中执行step3和step4,这两个步骤是串行执行的;

在stream1中调用cudaStreamSynchronize函数等待所有在该流中的操作执行完毕,程序结束。

总结起来,这个多流程的示例展示了如何使用事件来控制不同流之间的顺序和同步,从而实现流程之间的依赖关系和并行执行。

<think>嗯,用户之前问过关于他们的笔记本是X1 Carbon集成显卡的问题,现在他们又提供了PyTorch的版本和CUDA可用的信息,说明他们可能按照之前的建议安装了CPU版本的PyTorch,现在在验证环境。 首先,用户现在给出的信息是PyTorch 2.6.0+cpu,CUDA可用,设备是CPU。这说明他们成功安装了CPU版本的PyTorch,这符合之前的指导,所以需要确认安装正确,并进一步帮助他们在CPU环境下进行深度学习任务。 用户可能的需求是什么?他们可能是在确认环境是否正确,或者在使用过程中遇到了性能问题,想知道如何优化,或者是否需要进一步调整配置。另外,用户可能对无法使用CUDA感到失望,想知道是否有其他替代方案,比如云GPU或者外接显卡坞,但之前的回复已经提到过这些,可能需要重复或补充。 接下来需要分析用户的问题。他们展示的是PyTorch环境的信息,可能需要指导如何在这种情况下进行开发,比如代码调整、性能优化,或者确认是否安装正确。此外,用户可能需要进一步的资源或工具来弥补CPU的足,比如轻量级模型、数据预处理技巧,或者并行计算的方法。 考虑到用户使用的是笔记本,性能有限,应该强调优化策略,比如减少批处理大小、使用更高效的数据加载、选择适合CPU的模型结构等。同时,可能还需要提醒他们内存管理,避免因为内存足导致程序崩溃。 另外,用户可能对当前的配置是否足够运行他们的项目有疑问,需要评估他们的使用场景。如果他们处理的是小规模数据或测试模型,CPU可能足够,但大规模训练可能需要考虑云服务。 最后,确保回答结构清晰,分步骤说明,让用户能一步步优化他们的环境,同时提供验证方法和替代方案,帮助他们根据自身情况做出选择。</think>--- ### **PyTorch 环境解析与优化指南** 根据你的输出信息,当前环境已正确配置为 **CPU 专属模式**。以下是详细分析和针对性建议: --- ### **1. 关键信息解读** | 字段 | 值 | 含义 | |------|----|------| | PyTorch版本 | `2.6.0+cpu` | 安装的 PyTorch 是 **CPU 专用版本** | | CUDA是否可用 | `False` | **无 GPU 加速支持**,所有计算依赖 CPU | | 当前设备 | `cpu` | 模型和数据处理强制运行于 CPU | --- ### **2. 当前环境的影响** #### **(1) 优势** - **兼容性高**:无需担心驱动/CUDA 版本冲突。 - **部署简单**:适合轻量级任务(如小型模型推理、教学实验)。 #### **(2) 局限性** - **速度瓶颈**: 复杂模型(如 ResNet、Transformer)的推理速度可能比 GPU 慢 **10~50 倍**。 - **内存限制**: 无法处理大批量数据(如 `batch_size > 32` 时易引发内存足)。 --- ### **3. CPU 环境下的代码适配** #### **(1) 显式指定设备** 在代码中强制使用 CPU,避免意外尝试调用 GPU: ```python import torch device = torch.device("cpu") # 明确声明设备 model = YourModel().to(device) inputs = data.to(device) ``` #### **(2) 禁用 GPU 相关功能** 某些库(如 Hugging Face Transformers)默认尝试检测 GPU,需手动关闭: ```python from transformers import pipeline # 强制使用 CPU classifier = pipeline("text-classification", device=-1) # device=-1 表示 CPU ``` --- ### **4. 性能优化技巧** #### **(1) 批处理与并行化** - **控制批处理大小**: 设置 `batch_size=8` 或更低(根据内存调整)。 - **启用并行**: 通过 OpenMP 或 PyTorch 内置并行: ```python torch.set_num_threads(4) # 限制线程数避免资源争用 ``` #### **(2) 模型轻量化** - **选择 CPU 友好型架构**: 优先使用 MobileNet、SqueezeNet 等轻量模型。 - **量化压缩**: 将 FP32 模型转换为 INT8 以提升速度: ```python quantized_model = torch.quantization.quantize_dynamic( model, {torch.nn.Linear}, dtype=torch.qint8 ) ``` #### **(3) 数据预处理加速** - **启用线程加载**: 在 DataLoader 中设置 `num_workers=4`(超过 CPU 逻辑核心数)。 ```python loader = DataLoader(dataset, batch_size=8, num_workers=4) ``` --- ### **5. 替代加速方案** #### **(1) ONNX Runtime 加速** 将 PyTorch 模型导出为 ONNX 格式,利用优化推理引擎: ```python # 导出模型 torch.onnx.export(model, dummy_input, "model.onnx") # 使用 ONNX Runtime 运行 import onnxruntime as ort session = ort.InferenceSession("model.onnx") outputs = session.run(None, {"input": data.numpy()}) ``` #### **(2) 混合精度计算(需 CPU 支持 AVX-512)** 部分 Intel CPU 支持低精度加速: ```python with torch.cpu.amp.autocast(): outputs = model(inputs) # 自动转换部分计算为 BF16/FP16 ``` --- ### **6. 环境验证脚本** 运行以下代码检查硬件利用状态: ```python import psutil, torch print(f"[CPU] 逻辑核心数: {psutil.cpu_count(logical=True)}") print(f"[内存] 可用空间: {psutil.virtual_memory().available / 1024**3:.1f} GB") print(f"[PyTorch] 后端优化库: {torch.__config__.parallel_info()}") ``` --- ### **7. 扩展建议** - **监控资源占用**: 使用 `htop`(Linux/macOS)或任务管理器(Windows)实时查看 CPU/内存负载。 - **硬件升级**: 若设备支持,升级至 32GB 内存和 NVMe SSD 可提升数据吞吐效率。 - **云服务备用方案**: 对耗时任务(如模型训练),可导出代码到 Google Colab(免费 GPU)或 AWS SageMaker。 --- ### **总结** - **当前状态**:PyTorch 已正确配置为 CPU 模式,适合轻量级任务。 - **推荐操作**: 1. 调整代码适配 CPU 特性(如批处理大小、线程)。 2. 优先使用量化模型和 ONNX 运行时加速。 3. 对计算密集型任务,建议迁移到云 GPU 环境。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值