cuda編程筆記(7)--多GPU上的CUDA

零拷貝內存

在流中,我們介紹了cudaHostAlloc這個函數,它有一些標志,其中cudaHostAllocMapped允許內存映射到設備,也即GPU可以直接訪問主機上的內存,不用額外再給設備指針分配內存

通過下面的操作,即可讓設備指針也可訪問主機內存

cudaHostAlloc((void**)&a, N * sizeof(float), cudaHostAllocWriteCombined | cudaHostAllocMapped);
cudaHostGetDevicePointer(&dev_a, a, 0); // 將主機指針映射為設備可用指針

由于GPU虛擬內存空間和CPU不同,不能直接使用指針a,必須調用cudaHostGetDevicePointer函數;這樣 dev_a 就是設備端可以直接訪問的 host 內存。

原理簡介

  • 在調用 cudaHostAllocMapped 時,CUDA 會在主機申請一塊 頁鎖定內存(pinned memory);

  • 再通過 cudaHostGetDevicePointer 把這塊主機內存映射為設備端地址空間中的指針

  • 當 GPU 訪問 dev_a[i] 時,會通過 PCIe 總線從主機 RAM 中取數據,實現 零拷貝訪問

所以它雖然“看起來像顯存指針”,但其實訪問的是主機內存。

下面用該機制重寫cuda編程筆記(2.5)--簡易的應用代碼-CSDN博客里的矢量點乘


#ifndef __CUDACC__
#define __CUDACC__
#endif
#include <cuda_runtime.h>
#include <device_launch_parameters.h>#include <iostream>
#include<cstdio>#define threadsPerBlock 256
const int Blocks = 32;
const int N = Blocks * threadsPerBlock;void error_handling(cudaError_t res) {if (res !=cudaSuccess) {std::cout << "error!" << std::endl;}
}
__global__ void dot(float* a, float* b, float* c) {__shared__ float cache[threadsPerBlock];int tid = threadIdx.x + blockIdx.x * blockDim.x;int cacheIndex = threadIdx.x;float temp = 0;if (tid < N) temp = a[tid] * b[tid];cache[cacheIndex] = temp;__syncthreads();for (int stride = blockDim.x / 2; stride > 0; stride>>= 1) {if (cacheIndex < stride)cache[cacheIndex] += cache[cacheIndex + stride];__syncthreads();}// 將每個 block 的結果寫入全局內存if (cacheIndex == 0) {c[blockIdx.x] = cache[0];}}
int main() {cudaEvent_t start, stop;float* a, * b, c, * partial_c;float* dev_a, * dev_b, * dev_partial_c;float elapsedTime;error_handling(cudaEventCreate(&start));error_handling(cudaEventCreate(&stop));//在cpu上分配內存error_handling(cudaHostAlloc((void**)&a, N * sizeof(float),cudaHostAllocWriteCombined|cudaHostAllocMapped));error_handling(cudaHostAlloc((void**)&b, N * sizeof(float), cudaHostAllocWriteCombined | cudaHostAllocMapped));error_handling(cudaHostAlloc((void**)&partial_c, Blocks * sizeof(float), cudaHostAllocWriteCombined | cudaHostAllocMapped));for (int i = 0; i < N; i++) {a[i] = i;b[i] = i * 2;}error_handling(cudaHostGetDevicePointer(&dev_a, a, 0));error_handling(cudaHostGetDevicePointer(&dev_b, b, 0));error_handling(cudaHostGetDevicePointer(&dev_partial_c, partial_c, 0));error_handling(cudaEventRecord(start, 0));dot << < Blocks, threadsPerBlock >> > (dev_a, dev_b, dev_partial_c);error_handling(cudaDeviceSynchronize());error_handling(cudaEventRecord(stop, 0));error_handling(cudaEventSynchronize(stop));error_handling(cudaEventElapsedTime(&elapsedTime, start, stop));c = 0;for (int i = 0; i < Blocks; i++)c += partial_c[i];error_handling(cudaFreeHost(a));error_handling(cudaFreeHost(b));error_handling(cudaFreeHost(partial_c));error_handling(cudaEventDestroy(start));error_handling(cudaEventDestroy(stop));printf("Value calculated: %f\n", c);printf("Time consumed:%f\n", elapsedTime);
}
優點說明
減少顯式 cudaMemcpy 調用主機 → 設備零拷貝
避免重復申請/釋放顯存數據只分配一次
簡化代碼結構多個內核之間共享同一 host 指針
適合小規模、實時更新場景如 GUI 控件、攝像頭圖像
缺點說明
訪問速度遠慢于 global memory因為要通過 PCIe
僅適用于某些 GPU(如支持 UVA)非所有設備支持
最佳性能只在小數據量/零延遲訪問場景比如小型圖像處理、調試等
受限于 CPU 內存頁頁大小影響效率,不能高并發

?使用條件

要點說明
GPU 必須支持 UVA(統一虛擬地址空間)可用 cudaGetDeviceProperties() 查詢 unifiedAddressing 是否為 1
最好配合 WriteCombined適合只寫不讀場景(如從主機寫入,GPU 讀取)
不適合大規模數據訓練/推理會嚴重拖慢 GPU 性能,PCIe 帶寬遠小于顯存帶寬

啟動多GPU

使用多個線程,就可以同時啟動多個 GPU 來并行計算,這是現代 CUDA 編程中非常推薦且常用的做法。?

CUDA 的執行模型是:

  • 每個 CPU 線程 通過 cudaSetDevice(id) 綁定到某個 GPU

  • 每個線程可以在綁定的 GPU 上:

    • 分配顯存

    • 啟動 kernel

    • 執行 memcpy

    • 做同步

CUDA runtime 為每個 CPU 線程維護獨立的 GPU 上下文(context),所以 不同線程綁定不同 GPU,就可以各自獨立調度、執行自己的 kernel

#include <thread>
#include <iostream>__global__ void kernel(int id) {printf("Hello from GPU %d, thread %d\n", id, threadIdx.x);
}void gpu_task(int device_id) {cudaSetDevice(device_id);kernel<<<1, 4>>>(device_id);cudaDeviceSynchronize();  // 等待 GPU 完成
}int main() {int num_devices = 0;cudaGetDeviceCount(&num_devices);std::vector<std::thread> threads;for (int i = 0; i < num_devices; ++i) {threads.emplace_back(gpu_task, i);  // 每個線程負責一個 GPU}for (auto& t : threads) t.join(); // 等待所有線程完成return 0;
}

多 GPU 場景下共享主機內存

cudaHostAlloc中當flags傳入cudaHostAllocPortable時

就意味著:

? 分配出的主機內存是跨 GPU 可見(portable)的,不屬于某個特定的 GPU 上下文。

為什么多 GPU 編程中需要 cudaHostAllocPortable

在默認情況下(無 cudaHostAllocPortable):

  • 使用 cudaHostAlloc() 分配的內存只綁定到當前 GPU 上下文

  • 如果你在另一個 GPU 上使用該內存(比如調用 cudaMemcpyAsync),就會報錯或性能下降。

加上 cudaHostAllocPortable 后:

  • 這塊頁鎖定內存在所有 GPU 上都能直接訪問(只要硬件支持 UVA)。

典型用法:多 GPU + Portable 內存

float *host_ptr;
cudaHostAlloc((void**)&host_ptr, N * sizeof(float), cudaHostAllocPortable);

?然后每個線程可以這樣操作:

void run_on_device(int device_id, float* shared_host) {cudaSetDevice(device_id);float *dev_ptr;cudaMalloc(&dev_ptr, N * sizeof(float));// 每個 GPU 從共享主機內存拷貝數據cudaMemcpy(dev_ptr, shared_host, N * sizeof(float), cudaMemcpyHostToDevice);kernel<<<blocks, threads>>>(dev_ptr);cudaDeviceSynchronize();cudaFree(dev_ptr);
}

這樣,每個 GPU 都能用同一塊主機內存 shared_host 來做數據初始化、寫回、交換數據等操作。

常見組合:

cudaHostAllocPortable | cudaHostAllocWriteCombined

GPU A 寫結果,GPU B 讀取驗證

GPU A 寫入 shared host memory,GPU B 讀取驗證是完全可能出現同步問題的

線程之間需要加同步

#include <cuda_runtime.h>
#include <iostream>
#include <thread>
#include <vector>
#include <cassert>#define N 16__global__ void write_kernel(int *data, int val) {int idx = threadIdx.x;if (idx < N) {data[idx] = val * 100 + idx;}
}__global__ void read_kernel(int *data) {int idx = threadIdx.x;if (idx < N) {printf("GPU 1 reads: data[%d] = %d\n", idx, data[idx]);}
}// GPU 0 線程函數:寫入共享主機內存
void gpu0_writer(int *host_data, cudaEvent_t write_done_event) {cudaSetDevice(0);cudaStream_t stream;cudaStreamCreate(&stream);int *dev_data;cudaMalloc(&dev_data, N * sizeof(int));write_kernel<<<1, N, 0, stream>>>(dev_data, 1);// 將數據從設備拷貝到共享主機內存cudaMemcpyAsync(host_data, dev_data, N * sizeof(int), cudaMemcpyDeviceToHost, stream);// 記錄寫入完成事件cudaEventRecord(write_done_event, stream);cudaStreamSynchronize(stream);cudaFree(dev_data);cudaStreamDestroy(stream);std::cout << "[GPU 0] 寫入完成\n";
}// GPU 1 線程函數:等待事件后讀取共享主機內存
void gpu1_reader(int *host_data, cudaEvent_t write_done_event) {cudaSetDevice(1);cudaStream_t stream;cudaStreamCreate(&stream);// 等待 GPU 0 寫入完成cudaStreamWaitEvent(stream, write_done_event, 0);int *dev_data;cudaMalloc(&dev_data, N * sizeof(int));// 從共享主機內存拷貝到 GPU 1 上的顯存cudaMemcpyAsync(dev_data, host_data, N * sizeof(int), cudaMemcpyHostToDevice, stream);read_kernel<<<1, N, 0, stream>>>(dev_data);cudaStreamSynchronize(stream);cudaFree(dev_data);cudaStreamDestroy(stream);std::cout << "[GPU 1] 讀取完成\n";
}int main() {int gpu_count = 0;cudaGetDeviceCount(&gpu_count);if (gpu_count < 2) {std::cerr << "需要至少兩個 GPU!\n";return -1;}// 分配共享主機內存(portable)int *shared_host_data;cudaHostAlloc((void**)&shared_host_data, N * sizeof(int), cudaHostAllocPortable);// 創建用于跨 GPU 通信的事件cudaEvent_t write_done_event;cudaEventCreateWithFlags(&write_done_event, cudaEventDisableTiming); // faster event// 啟動兩個線程std::thread t0(gpu0_writer, shared_host_data, write_done_event);std::thread t1(gpu1_reader, shared_host_data, write_done_event);t0.join();t1.join();cudaEventDestroy(write_done_event);cudaFreeHost(shared_host_data);return 0;
}

cudaEventCreateWithFlags

事件創建:cudaEventCreateWithFlags

cudaEvent_t evt;
cudaEventCreateWithFlags(&evt, cudaEventDisableTiming); // 推薦帶標志創建更輕量
標志含義說明
cudaEventDefault默認行為會記錄耗時,可用于性能計時
cudaEventDisableTiming禁用計時功能更輕量,推薦用于同步控制
cudaEventInterprocess可用于多進程共享事件不常用于多 GPU 同步(屬于高級功能)

cudaEventRecord

表示 之前所有 stream中的操作都完成時,該事件被標記完成。?

cudaStreamWaitEvent

cudaError_t cudaStreamWaitEvent(cudaStream_t stream, cudaEvent_t event, unsigned int flags);
參數類型含義
streamcudaStream_t要等待事件的 CUDA 流。這個 stream 將在 event 被觸發后才開始執行其后續任務。
eventcudaEvent_t要等待的事件。這個事件應該在其他設備或流上通過 cudaEventRecord 創建。
flagsunsigned int當前必須設為 0。(CUDA 12.4 以前不支持其他選項)

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/90901.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/90901.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/90901.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

IP地址混亂?監控易IPAM實現全網地址自動化管理與非法接入告警

IP地址出現混亂狀況&#xff1f;監控易IPAM能夠達成對全網地址予以自動化管理的目標&#xff0c;同時還可針對非法接入的情況發出告警信息。辦公室毫無預兆地突然斷網了&#xff0c;經過一番仔細排查之后&#xff0c;發現原來是IP地址出現了沖突的情況。有人私自接了路由器&…

安全監測預警平臺的應用場景

隨著城市化進程加快和基礎設施規模擴大&#xff0c;各類安全風險日益突出。安全監測預警平臺作為現代安全管理的重要工具&#xff0c;通過整合物聯網、大數據、人工智能等先進技術&#xff0c;實現對各類安全隱患的實時監測、智能分析和精準預警。本文將詳細探討安全監測預警平…

007_用例與應用場景

用例與應用場景 目錄 內容創作編程開發數據分析客戶服務教育培訓商業智能研究輔助 內容創作 文案撰寫 應用場景&#xff1a; 營銷文案和廣告語產品描述和說明書社交媒體內容郵件營銷內容 實際案例&#xff1a; 任務&#xff1a;為新款智能手表撰寫產品描述 輸入&#x…

Unity物理系統由淺入深第一節:Unity 物理系統基礎與應用

Unity物理系統由淺入深第一節&#xff1a;Unity 物理系統基礎與應用 Unity物理系統由淺入深第二節&#xff1a;物理系統高級特性與優化 Unity物理系統由淺入深第三節&#xff1a;物理引擎底層原理剖析 Unity物理系統由淺入深第四節&#xff1a;物理約束求解與穩定性 Unity 引擎…

《[系統底層攻堅] 張冬〈大話存儲終極版〉精讀計劃啟動——存儲架構原理深度拆解之旅》-系統性學習筆記(適合小白與IT工作人員)

&#x1f525; 致所有存儲技術探索者筆者近期將系統攻克存儲領域經典巨作——張冬老師編著的《大話存儲終極版》。這部近千頁的存儲系統圣經&#xff0c;以庖丁解牛的方式剖析了&#xff1a;存儲硬件底層架構、分布式存儲核心算法、超融合系統設計哲學等等。喜歡研究數據存儲或…

flutter鴻蒙版 環境配置

flutter支持開發鴻蒙,但是需要專門的flutter鴻蒙項目, Flutter鴻蒙化環境配置&#xff08;windows&#xff09;_flutter config --ohos-sdk-CSDN博客

Java 高級特性實戰:反射與動態代理在 spring 中的核心應用

在 Java 開發中&#xff0c;反射和動態代理常被視為 “高級特性”&#xff0c;它們看似抽象&#xff0c;卻支撐著 Spring、MyBatis 等主流框架的核心功能。本文結合手寫 spring 框架的實踐&#xff0c;從 “原理” 到 “落地”&#xff0c;詳解這兩個特性如何解決實際問題&…

Codeforces Round 855 (Div. 3)

A. Is It a Cat? 去重&#xff0c; 把所有字符看成大寫字符&#xff0c; 然后去重&#xff0c; 觀察最后結果是不是“MEOW” #include <bits/stdc.h> #define int long longvoid solve() {int n;std::cin >> n;std::string ans, t;std::cin >> ans;for (int…

Scrapy選擇器深度指南:CSS與XPath實戰技巧

引言&#xff1a;選擇器在爬蟲中的核心地位在現代爬蟲開發中&#xff0c;??選擇器??是數據提取的靈魂工具。根據2023年網絡爬蟲開發者調查數據顯示&#xff1a;??92%?? 的數據提取錯誤源于選擇器編寫不當熟練使用選擇器的開發效率相比新手提升 ??300%??同時掌握CSS…

Windos服務器升級MySQL版本

Windos服務器升級MySQL版本 1.備份數據庫 windows下必須以管理員身份運行命令行工具進行備份&#xff0c;如果沒有配置MySQL的環境變量&#xff0c;需要進入MySQL Server 的bin目錄輸入指令&#xff0c; mysqldump -u root -p --all-databases > backup.sql再輸入數據庫密碼…

告別頻繁登錄!Nuxt3 + TypeScript + Vue3實戰:雙Token無感刷新方案全解析

前言 在現代 Web 應用中&#xff0c;身份認證是保障系統安全的重要環節。傳統的單 Token 認證方式存在諸多不足&#xff0c;如 Token 過期后需要用戶重新登錄&#xff0c;影響用戶體驗。本文將詳細介紹如何在 Nuxt3 TypeScript Vue3 項目中實現無感刷新 Token 機制&#xff…

Linux——Redis

目錄 一、Redis概念 1.1 Redis定義 1.2 Redis的特點 1.3 Redis的用途 1.4 Redis與其他數據庫的對比 二、Redis數據庫 三、Redis五個基本類型 3.1 字符串 3.2 列表(list) ——可以有相同的值 3.3 集合(set) ——值不能重復 3.4 哈希(hash) ——類似于Map集合 3.5 有序…

【AI大模型】部署優化量化:INT8壓縮模型

INT8&#xff08;8位整數&#xff09;量化是AI大模型部署中最激進的壓縮技術&#xff0c;通過將模型權重和激活值從FP32降至INT8&#xff08;-128&#xff5e;127整數&#xff09;&#xff0c;實現4倍內存壓縮2-4倍推理加速&#xff0c;是邊緣計算和高并發服務的核心優化手段。…

LFU 緩存

題目鏈接 LFU 緩存 題目描述 注意點 1 < capacity < 10^40 < key < 10^50 < value < 10^9對緩存中的鍵執行 get 或 put 操作&#xff0c;使用計數器的值將會遞增當緩存達到其容量 capacity 時&#xff0c;則應該在插入新項之前&#xff0c;移除最不經常使…

檢查輸入有效性(指針是否為NULL)和檢查字符串長度是否為0

檢查輸入有效性&#xff08;指針是否為NULL&#xff09;和檢查字符串長度是否為0 這兩個檢查針對的是完全不同的邊界情況&#xff0c;都是必要的防御性編程措施&#xff1a; 1. 空指針檢查 if(!src) 目的&#xff1a;防止解引用空指針場景&#xff1a;當調用者傳入 NULL 時風險…

Apache POI 的 HSSFWorkbook、SXSSFWorkbook和XSSFWorkbook三者的區別

HSSFWorkbook 專用于處理Excel 97-2003&#xff08;.xls&#xff09;格式的二進制文件。基于純Java實現&#xff0c;所有數據存儲在內存中&#xff0c;適合小規模數據&#xff08;通常不超過萬行&#xff09;。內存占用較高&#xff0c;但功能完整&#xff0c;支持所有舊版Exce…

冷凍電鏡重構的GPU加速破局:從Relion到CryoSPARC的并行重構算法

點擊 “AladdinEdu&#xff0c;同學們用得起的【H卡】算力平臺”&#xff0c;H卡級別算力&#xff0c;按量計費&#xff0c;靈活彈性&#xff0c;頂級配置&#xff0c;學生專屬優惠。 一、冷凍電鏡重構的算力困局 隨著單粒子冷凍電鏡&#xff08;cryo-EM&#xff09;分辨率突破…

算法學習筆記:16.哈希算法 ——從原理到實戰,涵蓋 LeetCode 與考研 408 例題

在計算機科學中&#xff0c;哈希算法&#xff08;Hash Algorithm&#xff09;是一種將任意長度的輸入數據映射到固定長度輸出的技術&#xff0c;其輸出稱為哈希值&#xff08;Hash Value&#xff09;或散列值。哈希算法憑借高效的查找、插入和刪除性能&#xff0c;在數據存儲、…

16018.UE4+Airsim仿真環境搭建超級詳細

文章目錄 1 源碼下載2 下載安裝軟件2.1 安裝 UE4 軟件2.2 安裝visual studio 20223 編譯airsim源碼4 進入AirSim工程,打開工程5 UE4 工程創建5.1 下載免費場景 CityPark,并創建工程5.2 工程編譯5.2.1 將airsim 插件拷貝到 UE4工程路徑中5.2.2 修改工程配置文件5.2.3 創建c++類…

Python 實戰:構建 Git 自動化助手

在多項目協作、企業級工程管理或開源社區維護中&#xff0c;經常面臨需要同時管理數十甚至上百個 Git 倉庫的場景&#xff1a;多倉庫需要統一 pull 拉取更新定期向多個項目批量 commit 和 push自動備份 Git 項目批量拉取私有倉庫并管理密鑰為解決這類高頻、重復、機械性工作&am…