99精品伊人亚洲|最近国产中文炮友|九草在线视频支援|AV网站大全最新|美女黄片免费观看|国产精品资源视频|精彩无码视频一区|91大神在线后入|伊人终合在线播放|久草综合久久中文

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

CUDA編程模型的統(tǒng)一內(nèi)存

星星科技指導(dǎo)員 ? 來源:NVIDIA ? 作者:Ken He ? 2022-05-07 14:47 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

N.1. Unified Memory Introduction

統(tǒng)一內(nèi)存是 CUDA 編程模型的一個(gè)組件,在 CUDA 6.0 中首次引入,它定義了一個(gè)托管內(nèi)存空間,在該空間中所有處理器都可以看到具有公共地址空間的單個(gè)連貫內(nèi)存映像。

注意:處理器是指任何具有專用 MMU 的獨(dú)立執(zhí)行單元。這包括任何類型和架構(gòu)的 CPUGPU。

底層系統(tǒng)管理 CUDA 程序中的數(shù)據(jù)訪問和位置,無需顯式內(nèi)存復(fù)制調(diào)用。這在兩個(gè)主要方面有利于 GPU 編程:

通過統(tǒng)一系統(tǒng)中所有 GPU 和 CPU 的內(nèi)存空間以及為 CUDA 程序員提供更緊密、更直接的語言集成,可以簡化 GPU 編程。

通過透明地將數(shù)據(jù)遷移到使用它的處理器,可以最大限度地提高數(shù)據(jù)訪問速度。

簡單來說,統(tǒng)一內(nèi)存消除了通過 cudaMemcpy*() 例程進(jìn)行顯式數(shù)據(jù)移動(dòng)的需要,而不會(huì)因?qū)⑺袛?shù)據(jù)放入零拷貝內(nèi)存而導(dǎo)致性能損失。當(dāng)然,數(shù)據(jù)移動(dòng)仍然會(huì)發(fā)生,因此程序的運(yùn)行時(shí)間通常不會(huì)減少;相反,統(tǒng)一內(nèi)存可以編寫更簡單、更易于維護(hù)的代碼。

統(tǒng)一內(nèi)存提供了一個(gè)“單指針數(shù)據(jù)”模型,在概念上類似于 CUDA 的零拷貝內(nèi)存。兩者之間的一個(gè)關(guān)鍵區(qū)別在于,在零拷貝分配中,內(nèi)存的物理位置固定在 CPU 系統(tǒng)內(nèi)存中,因此程序可以快速或慢速地訪問它,具體取決于訪問它的位置。另一方面,統(tǒng)一內(nèi)存將內(nèi)存和執(zhí)行空間解耦,以便所有數(shù)據(jù)訪問都很快。

統(tǒng)一內(nèi)存一詞描述了一個(gè)為各種程序提供內(nèi)存管理服務(wù)的系統(tǒng),從針對(duì)運(yùn)行時(shí) API 的程序到使用虛擬 ISA (PTX) 的程序。該系統(tǒng)的一部分定義了選擇加入統(tǒng)一內(nèi)存服務(wù)的托管內(nèi)存空間。

托管內(nèi)存可與特定于設(shè)備的分配互操作和互換,例如使用 cudaMalloc() 例程創(chuàng)建的分配。所有在設(shè)備內(nèi)存上有效的 CUDA 操作在托管內(nèi)存上也有效;主要區(qū)別在于程序的主機(jī)部分也能夠引用和訪問內(nèi)存。

注意:連接到 Tegra 的離散 GPU 不支持統(tǒng)一內(nèi)存。

N.1.1. System Requirements

統(tǒng)一內(nèi)存有兩個(gè)基本要求:

具有 SM 架構(gòu) 3.0 或更高版本(Kepler 類或更高版本)的 GPU

64 位主機(jī)應(yīng)用程序和非嵌入式操作系統(tǒng)Linux 或 Windows) 具有 SM 架構(gòu) 6.x 或更高版本(Pascal 類或更高版本)的 GPU 提供額外的統(tǒng)一內(nèi)存功能,例如本文檔中概述的按需頁面遷移和 GPU 內(nèi)存超額訂閱。 請注意,目前這些功能僅在 Linux 操作系統(tǒng)上受支持。 在 Windows 上運(yùn)行的應(yīng)用程序(無論是 TCC 還是 WDDM 模式)將使用基本的統(tǒng)一內(nèi)存模型,就像在 6.x 之前的架構(gòu)上一樣,即使它們在具有 6.x 或更高計(jì)算能力的硬件上運(yùn)行也是如此。 有關(guān)詳細(xì)信息,請參閱數(shù)據(jù)遷移和一致性。

N.1.2. Simplifying GPU Programming

內(nèi)存空間的統(tǒng)一意味著主機(jī)和設(shè)備之間不再需要顯式內(nèi)存?zhèn)鬏?。在托管?nèi)存空間中創(chuàng)建的任何分配都會(huì)自動(dòng)遷移到需要的位置。

程序通過以下兩種方式之一分配托管內(nèi)存: 通過 cudaMallocManaged() 例程,它在語義上類似于 cudaMalloc();或者通過定義一個(gè)全局 __managed__ 變量,它在語義上類似于一個(gè) __device__ 變量。在本文檔的后面部分可以找到這些的精確定義。 注意:在具有計(jì)算能力 6.x 及更高版本的設(shè)備的支持平臺(tái)上,統(tǒng)一內(nèi)存將使應(yīng)用程序能夠使用默認(rèn)系統(tǒng)分配器分配和共享數(shù)據(jù)。這允許 GPU 在不使用特殊分配器的情況下訪問整個(gè)系統(tǒng)虛擬內(nèi)存。有關(guān)更多詳細(xì)信息,請參閱系統(tǒng)分配器。 以下代碼示例說明了托管內(nèi)存的使用如何改變主機(jī)代碼的編寫方式。首先,一個(gè)沒有使用統(tǒng)一內(nèi)存的簡單程序:

__global__ void AplusB(int *ret, int a, int b) {
    ret[threadIdx.x] = a + b + threadIdx.x;
}
int main() {
    int *ret;
    cudaMalloc(&ret, 1000 * sizeof(int));
    AplusB<<< 1, 1000 >>>(ret, 10, 100);
    int *host_ret = (int *)malloc(1000 * sizeof(int));
    cudaMemcpy(host_ret, ret, 1000 * sizeof(int), cudaMemcpyDefault);
    for(int i = 0; i < 1000; i++)
        printf("%d: A+B = %d\n", i, host_ret[i]); 
    free(host_ret);
    cudaFree(ret); 
    return 0;
}

第一個(gè)示例在 GPU 上將兩個(gè)數(shù)字與每個(gè)線程 ID 組合在一起,并以數(shù)組形式返回值。 如果沒有托管內(nèi)存,則返回值的主機(jī)端和設(shè)備端存儲(chǔ)都是必需的(示例中為 host_ret 和 ret),使用 cudaMemcpy() 在兩者之間顯式復(fù)制也是如此。

將此與程序的統(tǒng)一內(nèi)存版本進(jìn)行比較,后者允許從主機(jī)直接訪問 GPU 數(shù)據(jù)。 請注意 cudaMallocManaged() 例程,它從主機(jī)和設(shè)備代碼返回一個(gè)有效的指針。 這允許在沒有單獨(dú)的 host_ret 副本的情況下使用 ret,大大簡化并減小了程序的大小。

__global__ void AplusB(int *ret, int a, int b) {
    ret[threadIdx.x] = a + b + threadIdx.x;
}
int main() {
    int *ret;
    cudaMallocManaged(&ret, 1000 * sizeof(int));
    AplusB<<< 1, 1000 >>>(ret, 10, 100);
    cudaDeviceSynchronize();
    for(int i = 0; i < 1000; i++)
        printf("%d: A+B = %d\n", i, ret[i]);
    cudaFree(ret); 
    return 0;
}

最后,語言集成允許直接引用 GPU 聲明的__managed__變量,并在使用全局變量時(shí)進(jìn)一步簡化程序。

__device__ __managed__ int ret[1000];
__global__ void AplusB(int a, int b) {
    ret[threadIdx.x] = a + b + threadIdx.x;
}
int main() {
    AplusB<<< 1, 1000 >>>(10, 100);
    cudaDeviceSynchronize();
    for(int i = 0; i < 1000; i++)
        printf("%d: A+B = %d\n", i, ret[i]);
    return 0;
}

請注意沒有明確的 cudaMemcpy() 命令以及返回?cái)?shù)組 ret 在 CPU 和 GPU 上都可見的事實(shí)。

值得一提的是主機(jī)和設(shè)備之間的同步。 請注意在非托管示例中,同步 cudaMemcpy() 例程如何用于同步內(nèi)核(即等待它完成運(yùn)行)以及將數(shù)據(jù)傳輸?shù)街鳈C(jī)。 統(tǒng)一內(nèi)存示例不調(diào)用 cudaMemcpy(),因此需要顯式 cudaDeviceSynchronize(),然后主機(jī)程序才能安全地使用 GPU 的輸出。

N.1.3. Data Migration and Coherency

統(tǒng)一內(nèi)存嘗試通過將數(shù)據(jù)遷移到正在訪問它的設(shè)備來優(yōu)化內(nèi)存性能(也就是說,如果 CPU 正在訪問數(shù)據(jù),則將數(shù)據(jù)移動(dòng)到主機(jī)內(nèi)存,如果 GPU 將訪問它,則將數(shù)據(jù)移動(dòng)到設(shè)備內(nèi)存)。數(shù)據(jù)遷移是統(tǒng)一內(nèi)存的基礎(chǔ),但對(duì)程序是透明的。系統(tǒng)將嘗試將數(shù)據(jù)放置在可以最有效地訪問而不違反一致性的位置。

數(shù)據(jù)的物理位置對(duì)程序是不可見的,并且可以隨時(shí)更改,但對(duì)數(shù)據(jù)的虛擬地址的訪問將保持有效并且可以從任何處理器保持一致,無論位置如何。請注意,保持一致性是首要要求,高于性能;在主機(jī)操作系統(tǒng)的限制下,系統(tǒng)被允許訪問失敗或移動(dòng)數(shù)據(jù),以保持處理器之間的全局一致性。

計(jì)算能力低于 6.x 的 GPU 架構(gòu)不支持按需將托管數(shù)據(jù)細(xì)粒度移動(dòng)到 GPU。每當(dāng)啟動(dòng) GPU 內(nèi)核時(shí),通常必須將所有托管內(nèi)存轉(zhuǎn)移到 GPU 內(nèi)存,以避免內(nèi)存訪問出錯(cuò)。計(jì)算能力 6.x 引入了一種新的 GPU 頁面錯(cuò)誤機(jī)制,可提供更無縫的統(tǒng)一內(nèi)存功能。結(jié)合系統(tǒng)范圍的虛擬地址空間,頁面錯(cuò)誤提供了幾個(gè)好處。首先,頁面錯(cuò)誤意味著 CUDA 系統(tǒng)軟件不需要在每次內(nèi)核啟動(dòng)之前將所有托管內(nèi)存分配同步到 GPU。如果在 GPU 上運(yùn)行的內(nèi)核訪問了一個(gè)不在其內(nèi)存中的頁面,它就會(huì)出錯(cuò),從而允許該頁面按需自動(dòng)遷移到 GPU 內(nèi)存?;蛘撸梢詫㈨撁嬗成涞?GPU 地址空間,以便通過 PCIe 或 NVLink 互連進(jìn)行訪問(訪問映射有時(shí)可能比遷移更快)。請注意,統(tǒng)一內(nèi)存是系統(tǒng)范圍的:GPU(和 CPU)可以從 CPU 內(nèi)存或系統(tǒng)中其他 GPU 的內(nèi)存中發(fā)生故障并遷移內(nèi)存頁面。

N.1.4. GPU Memory Oversubscription

計(jì)算能力低于 6.x 的設(shè)備分配的托管內(nèi)存不能超過 GPU 內(nèi)存的物理大小。

計(jì)算能力 6.x 的設(shè)備擴(kuò)展了尋址模式以支持 49 位虛擬尋址。 這足以覆蓋現(xiàn)代 CPU 的 48 位虛擬地址空間,以及 GPU 自己的內(nèi)存。 大的虛擬地址空間和頁面錯(cuò)誤能力使應(yīng)用程序可以訪問整個(gè)系統(tǒng)的虛擬內(nèi)存,而不受任何一個(gè)處理器的物理內(nèi)存大小的限制。 這意味著應(yīng)用程序可以超額訂閱內(nèi)存系統(tǒng):換句話說,它們可以分配、訪問和共享大于系統(tǒng)總物理容量的數(shù)組,從而實(shí)現(xiàn)超大數(shù)據(jù)集的核外處理。 只要有足夠的系統(tǒng)內(nèi)存可用于分配,cudaMallocManaged 就不會(huì)耗盡內(nèi)存。

N.1.5. Multi-GPU

對(duì)于計(jì)算能力低于 6.x 的設(shè)備,托管內(nèi)存分配的行為與使用 cudaMalloc() 分配的非托管內(nèi)存相同:當(dāng)前活動(dòng)設(shè)備是物理分配的主站,所有其他 GPU 接收到內(nèi)存的對(duì)等映射。這意味著系統(tǒng)中的其他 GPU 將以較低的帶寬通過 PCIe 總線訪問內(nèi)存。請注意,如果系統(tǒng)中的 GPU 之間不支持對(duì)等映射,則托管內(nèi)存頁面將放置在 CPU 系統(tǒng)內(nèi)存(“零拷貝”內(nèi)存)中,并且所有 GPU 都會(huì)遇到 PCIe 帶寬限制。有關(guān)詳細(xì)信息,請參閱 6.x 之前架構(gòu)上的多 GPU 程序的托管內(nèi)存。

具有計(jì)算能力 6.x 設(shè)備的系統(tǒng)上的托管分配對(duì)所有 GPU 都是可見的,并且可以按需遷移到任何處理器。統(tǒng)一內(nèi)存性能提示(請參閱性能調(diào)優(yōu))允許開發(fā)人員探索自定義使用模式,例如跨 GPU 讀取重復(fù)數(shù)據(jù)和直接訪問對(duì)等 GPU 內(nèi)存而無需遷移。

N.1.6. System Allocator

計(jì)算能力 7.0 的設(shè)備支持 NVLink 上的地址轉(zhuǎn)換服務(wù) (ATS)。 如果主機(jī) CPU 和操作系統(tǒng)支持,ATS 允許 GPU 直接訪問 CPU 的頁表。 GPU MMU 中的未命中將導(dǎo)致向 CPU 發(fā)送地址轉(zhuǎn)換請求 (ATR)。 CPU 在其頁表中查找該地址的虛擬到物理映射并將轉(zhuǎn)換提供回 GPU。 ATS 提供 GPU 對(duì)系統(tǒng)內(nèi)存的完全訪問權(quán)限,例如使用 malloc 分配的內(nèi)存、在堆棧上分配的內(nèi)存、全局變量和文件支持的內(nèi)存。 應(yīng)用程序可以通過檢查新的 pageableMemoryAccessUsesHostPageTables 屬性來查詢設(shè)備是否支持通過 ATS 一致地訪問可分頁內(nèi)存。

這是一個(gè)適用于任何滿足統(tǒng)一內(nèi)存基本要求的系統(tǒng)的示例代碼(請參閱系統(tǒng)要求):

int *data;
cudaMallocManaged(&data, sizeof(int) * n);
kernel<<>>(data);

具有 pageableMemoryAccess 屬性的系統(tǒng)支持這些新的訪問模式:

int *data = (int*)malloc(sizeof(int) * n);
kernel<<>>(data);
int data[1024];
kernel<<>>(data);
extern int *data;
kernel<<>>(data);

在上面的示例中,數(shù)據(jù)可以由第三方 CPU 庫初始化,然后由 GPU 內(nèi)核直接訪問。 在具有 pageableMemoryAccess 的系統(tǒng)上,用戶還可以使用 cudaMemPrefetchAsync 將可分頁內(nèi)存預(yù)取到 GPU。 這可以通過優(yōu)化數(shù)據(jù)局部性產(chǎn)生性能優(yōu)勢。

注意:目前僅 IBM Power9 系統(tǒng)支持基于 NVLink 的 ATS。

N.1.7. Hardware Coherency

第二代 NVLink 允許從 CPU 直接加載/存儲(chǔ)/原子訪問每個(gè) GPU 的內(nèi)存。結(jié)合新的 CPU 主控功能,NVLink 支持一致性操作,允許從 GPU 內(nèi)存讀取的數(shù)據(jù)存儲(chǔ)在 CPU 的緩存層次結(jié)構(gòu)中。從 CPU 緩存訪問的較低延遲是 CPU 性能的關(guān)鍵。計(jì)算能力 6.x 的設(shè)備僅支持對(duì)等 GPU 原子。計(jì)算能力 7.x 的設(shè)備可以通過 NVLink 發(fā)送 GPU 原子并在目標(biāo) CPU 上完成它們,因此第二代 NVLink 增加了對(duì)由 GPU 或 CPU 發(fā)起的原子的支持。

請注意,CPU 無法訪問 cudaMalloc 分配。因此,要利用硬件一致性,用戶必須使用統(tǒng)一內(nèi)存分配器,例如 cudaMallocManaged 或支持 ATS 的系統(tǒng)分配器(請參閱系統(tǒng)分配器)。新屬性 directManagedMemAccessFromHost 指示主機(jī)是否可以直接訪問設(shè)備上的托管內(nèi)存而無需遷移。默認(rèn)情況下,駐留在 GPU 內(nèi)存中的 cudaMallocManaged 分配的任何 CPU 訪問都會(huì)觸發(fā)頁面錯(cuò)誤和數(shù)據(jù)遷移。應(yīng)用程序可以使用帶有 cudaCpuDeviceId 的 cudaMemAdviseSetAccessedBy 性能提示來啟用對(duì)受支持系統(tǒng)上 GPU 內(nèi)存的直接訪問。

考慮下面的示例代碼:

__global__ void write(int *ret, int a, int b) {
    ret[threadIdx.x] = a + b + threadIdx.x;
}
__global__ void append(int *ret, int a, int b) {
    ret[threadIdx.x] += a + b + threadIdx.x;
}
int main() {
    int *ret;
    cudaMallocManaged(&ret, 1000 * sizeof(int));
    cudaMemAdvise(ret, 1000 * sizeof(int), cudaMemAdviseSetAccessedBy, cudaCpuDeviceId);  // set direct access hint

    write<<< 1, 1000 >>>(ret, 10, 100);            // pages populated in GPU memory
    cudaDeviceSynchronize();
    for(int i = 0; i < 1000; i++)
        printf("%d: A+B = %d\n", i, ret[i]);        // directManagedMemAccessFromHost=1: CPU accesses GPU memory directly without migrations
                                                    // directManagedMemAccessFromHost=0: CPU faults and triggers device-to-host migrations
    append<<< 1, 1000 >>>(ret, 10, 100);            // directManagedMemAccessFromHost=1: GPU accesses GPU memory without migrations
    cudaDeviceSynchronize();                        // directManagedMemAccessFromHost=0: GPU faults and triggers host-to-device migrations
    cudaFree(ret); 
    return 0;
}

寫內(nèi)核完成后,會(huì)在GPU內(nèi)存中創(chuàng)建并初始化ret。 接下來,CPU 將訪問 ret,然后再次使用相同的 ret 內(nèi)存追加內(nèi)核。 此代碼將根據(jù)系統(tǒng)架構(gòu)和硬件一致性支持顯示不同的行為:

在 directManagedMemAccessFromHost=1 的系統(tǒng)上:CPU 訪問托管緩沖區(qū)不會(huì)觸發(fā)任何遷移; 數(shù)據(jù)將保留在 GPU 內(nèi)存中,任何后續(xù)的 GPU 內(nèi)核都可以繼續(xù)直接訪問它,而不會(huì)造成故障或遷移。

在 directManagedMemAccessFromHost=0 的系統(tǒng)上:CPU 訪問托管緩沖區(qū)將出現(xiàn)頁面錯(cuò)誤并啟動(dòng)數(shù)據(jù)遷移; 任何第一次嘗試訪問相同數(shù)據(jù)的 GPU 內(nèi)核都會(huì)出現(xiàn)頁面錯(cuò)誤并將頁面遷移回 GPU 內(nèi)存。

N.1.8. Access Counters

計(jì)算能力 7.0 的設(shè)備引入了一個(gè)新的訪問計(jì)數(shù)器功能,該功能可以跟蹤 GPU 對(duì)位于其他處理器上的內(nèi)存進(jìn)行的訪問頻率。 訪問計(jì)數(shù)器有助于確保將內(nèi)存頁面移動(dòng)到最頻繁訪問頁面的處理器的物理內(nèi)存中。 訪問計(jì)數(shù)器功能可以指導(dǎo) CPU 和 GPU 之間以及對(duì)等 GPU 之間的遷移。

對(duì)于 cudaMallocManaged,訪問計(jì)數(shù)器遷移可以通過使用帶有相應(yīng)設(shè)備 ID 的 cudaMemAdviseSetAccessedBy 提示來選擇加入。 驅(qū)動(dòng)程序還可以使用訪問計(jì)數(shù)器來實(shí)現(xiàn)更有效的抖動(dòng)緩解或內(nèi)存超額訂閱方案。

注意:訪問計(jì)數(shù)器當(dāng)前僅在 IBM Power9 系統(tǒng)上啟用,并且僅用于 cudaMallocManaged 分配器。

N.2. Programming Model

N.2.1. Managed Memory Opt In

大多數(shù)平臺(tái)要求程序通過使用 __managed__ 關(guān)鍵字注釋 __device__ 變量(請參閱語言集成部分)或使用新的 cudaMallocManaged() 調(diào)用來分配數(shù)據(jù)來選擇自動(dòng)數(shù)據(jù)管理。

計(jì)算能力低于 6.x 的設(shè)備必須始終在堆上分配托管內(nèi)存,無論是使用分配器還是通過聲明全局存儲(chǔ)。 無法將先前分配的內(nèi)存與統(tǒng)一內(nèi)存相關(guān)聯(lián),也無法讓統(tǒng)一內(nèi)存系統(tǒng)管理 CPU 或 GPU 堆棧指針。

從 CUDA 8.0 和具有計(jì)算能力 6.x 設(shè)備的支持系統(tǒng)開始,可以使用相同的指針從 GPU 代碼和 CPU 代碼訪問使用默認(rèn) OS 分配器(例如 malloc 或 new)分配的內(nèi)存。 在這些系統(tǒng)上,統(tǒng)一內(nèi)存是默認(rèn)設(shè)置:無需使用特殊分配器或創(chuàng)建專門管理的內(nèi)存池。

N.2.1.1. Explicit Allocation Using cudaMallocManaged()

統(tǒng)一內(nèi)存最常使用在語義和語法上類似于標(biāo)準(zhǔn) CUDA 分配器 cudaMalloc() 的分配函數(shù)創(chuàng)建。 功能說明如下:

    cudaError_t cudaMallocManaged(void **devPtr,
                                  size_t size,
                                  unsigned int flags=0);

cudaMallocManaged() 函數(shù)保留托管內(nèi)存的 size 字節(jié),并在 devPtr 中返回一個(gè)指針。 請注意各種 GPU 架構(gòu)之間 cudaMallocManaged() 行為的差異。 默認(rèn)情況下,計(jì)算能力低于 6.x 的設(shè)備直接在 GPU 上分配托管內(nèi)存。 但是,計(jì)算能力 6.x 及更高版本的設(shè)備在調(diào)用 cudaMallocManaged() 時(shí)不會(huì)分配物理內(nèi)存:在這種情況下,物理內(nèi)存會(huì)在第一次觸摸時(shí)填充,并且可能駐留在 CPU 或 GPU 上。 托管指針在系統(tǒng)中的所有 GPU 和 CPU 上都有效,盡管程序訪問此指針必須遵守統(tǒng)一內(nèi)存編程模型的并發(fā)規(guī)則(請參閱一致性和并發(fā)性)。 下面是一個(gè)簡單的例子,展示了 cudaMallocManaged() 的使用:

__global__ void printme(char *str) {
    printf(str);
}
int main() {
    // Allocate 100 bytes of memory, accessible to both Host and Device code
    char *s;
    cudaMallocManaged(&s, 100);
    // Note direct Host-code use of "s"
    strncpy(s, "Hello Unified Memory\n", 99);
    // Here we pass "s" to a kernel without explicitly copying
    printme<<< 1, 1 >>>(s);
    cudaDeviceSynchronize();
    // Free as for normal CUDA allocations
    cudaFree(s); 
    return  0;
}

當(dāng) cudaMalloc() 被 cudaMallocManaged() 替換時(shí),程序的行為在功能上沒有改變; 但是,該程序應(yīng)該繼續(xù)消除顯式內(nèi)存拷貝并利用自動(dòng)遷移。 此外,可以消除雙指針(一個(gè)指向主機(jī),一個(gè)指向設(shè)備存儲(chǔ)器)。

設(shè)備代碼無法調(diào)用 cudaMallocManaged()。 所有托管內(nèi)存必須從主機(jī)或全局范圍內(nèi)分配(請參閱下一節(jié))。 在內(nèi)核中使用 malloc() 在設(shè)備堆上的分配不會(huì)在托管內(nèi)存空間中創(chuàng)建,因此 CPU 代碼將無法訪問。

N.2.1.2. Global-Scope Managed Variables Using managed

文件范圍和全局范圍的 CUDA __device__ 變量也可以通過在聲明中添加新的 __managed__ 注釋來選擇加入統(tǒng)一內(nèi)存管理。 然后可以直接從主機(jī)或設(shè)備代碼中引用它們,如下所示:

釋來選擇加入統(tǒng)一內(nèi)存管理。 然后可以直接從主機(jī)或設(shè)備代碼中引用它們,如下所示:

__device__ __managed__ int x[2];
__device__ __managed__ int y;
__global__ void kernel() {
    x[1] = x[0] + y;
}
int main() {
    x[0] = 3;
    y = 5;
    kernel<<< 1, 1 >>>();
    cudaDeviceSynchronize();
    printf("result = %d\n", x[1]); 
    return  0;
}

原始 __device__ 內(nèi)存空間的所有語義,以及一些額外的統(tǒng)一內(nèi)存特定約束,都由托管變量繼承(請參閱使用 NVCC 編譯)。

請注意,標(biāo)記為 __constant__ 的變量可能不會(huì)也標(biāo)記為 __managed__; 此注釋僅用于 __device__ 變量。 常量內(nèi)存必須在編譯時(shí)靜態(tài)設(shè)置,或者在 CUDA 中像往常一樣使用 cudaMemcpyToSymbol() 設(shè)置。

N.2.2. Coherency and Concurrency

在計(jì)算能力低于 6.x 的設(shè)備上同時(shí)訪問托管內(nèi)存是不可能的,因?yàn)槿绻?CPU 在 GPU 內(nèi)核處于活動(dòng)狀態(tài)時(shí)訪問統(tǒng)一內(nèi)存分配,則無法保證一致性。 但是,支持操作系統(tǒng)的計(jì)算能力 6.x 的設(shè)備允許 CPU 和 GPU 通過新的頁面錯(cuò)誤機(jī)制同時(shí)訪問統(tǒng)一內(nèi)存分配。 程序可以通過檢查新的 concurrentManagedAccess 屬性來查詢設(shè)備是否支持對(duì)托管內(nèi)存的并發(fā)訪問。 請注意,與任何并行應(yīng)用程序一樣,開發(fā)人員需要確保正確同步以避免處理器之間的數(shù)據(jù)危險(xiǎn)。

N.2.2.1. GPU Exclusive Access To Managed Memory

為了確保 6.x 之前的 GPU 架構(gòu)的一致性,統(tǒng)一內(nèi)存編程模型在 CPU 和 GPU 同時(shí)執(zhí)行時(shí)對(duì)數(shù)據(jù)訪問施加了限制。實(shí)際上,GPU 在執(zhí)行任何內(nèi)核操作時(shí)對(duì)所有托管數(shù)據(jù)具有獨(dú)占訪問權(quán),無論特定內(nèi)核是否正在積極使用數(shù)據(jù)。當(dāng)托管數(shù)據(jù)與 cudaMemcpy*() 或 cudaMemset*() 一起使用時(shí),系統(tǒng)可能會(huì)選擇從主機(jī)或設(shè)備訪問源或目標(biāo),這將限制并發(fā) CPU 訪問該數(shù)據(jù),而 cudaMemcpy*()或 cudaMemset*() 正在執(zhí)行。有關(guān)更多詳細(xì)信息,請參閱使用托管內(nèi)存的 Memcpy()/Memset() 行為。

不允許 CPU 訪問任何托管分配或變量,而 GPU 對(duì) concurrentManagedAccess 屬性設(shè)置為 0 的設(shè)備處于活動(dòng)狀態(tài)。在這些系統(tǒng)上,并發(fā) CPU/GPU 訪問,即使是不同的托管內(nèi)存分配,也會(huì)導(dǎo)致分段錯(cuò)誤,因?yàn)樵擁撁姹徽J(rèn)為是 CPU 無法訪問的。

__device__ __managed__ int x, y=2;
__global__  void  kernel() {
    x = 10;
}
int main() {
    kernel<<< 1, 1 >>>();
    y = 20;            // Error on GPUs not supporting concurrent access
                       
    cudaDeviceSynchronize();
    return  0;
}

在上面的示例中,當(dāng) CPU 接觸(這里原文中用的是touch這個(gè)詞) y 時(shí),GPU 程序內(nèi)核仍然處于活動(dòng)狀態(tài)。 (注意它是如何在cudaDeviceSynchronize()之前發(fā)生的。)由于 GPU 頁面錯(cuò)誤功能解除了對(duì)同時(shí)訪問的所有限制,因此代碼在計(jì)算能力 6.x 的設(shè)備上成功運(yùn)行。 但是,即使 CPU 訪問的數(shù)據(jù)與 GPU 不同,這種內(nèi)存訪問在 6.x 之前的架構(gòu)上也是無效的。 程序必須在訪問 y 之前顯式地與 GPU 同步:

__device__ __managed__ int x, y=2;
__global__  void  kernel() {
    x = 10;
}
int main() {
    kernel<<< 1, 1 >>>();
    cudaDeviceSynchronize();
    y = 20;            //  Success on GPUs not supporing concurrent access
    return  0;
}

如本例所示,在具有 6.x 之前的 GPU 架構(gòu)的系統(tǒng)上,CPU 線程可能不會(huì)在執(zhí)行內(nèi)核啟動(dòng)和后續(xù)同步調(diào)用之間訪問任何托管數(shù)據(jù),無論 GPU 內(nèi)核是否實(shí)際接觸相同的數(shù)據(jù)(或 任何托管數(shù)據(jù))。 并發(fā) CPU 和 GPU 訪問的潛力足以引發(fā)進(jìn)程級(jí)異常。

請注意,如果在 GPU 處于活動(dòng)狀態(tài)時(shí)使用 cudaMallocManaged() 或 cuMemAllocManaged() 動(dòng)態(tài)分配內(nèi)存,則在啟動(dòng)其他工作或同步 GPU 之前,內(nèi)存的行為是未指定的。 在此期間嘗試訪問 CPU 上的內(nèi)存可能會(huì)也可能不會(huì)導(dǎo)致分段錯(cuò)誤。 這不適用于使用標(biāo)志 cudaMemAttachHost 或 CU_MEM_ATTACH_HOST 分配的內(nèi)存。

N.2.2.2. Explicit Synchronization and Logical GPU Activity

請注意,即使內(nèi)核快速運(yùn)行并在上例中的 CPU 接觸 y 之前完成,也需要顯式同步。統(tǒng)一內(nèi)存使用邏輯活動(dòng)來確定 GPU 是否空閑。這與 CUDA 編程模型一致,該模型指定內(nèi)核可以在啟動(dòng)后的任何時(shí)間運(yùn)行,并且不保證在主機(jī)發(fā)出同步調(diào)用之前完成。

任何在邏輯上保證 GPU 完成其工作的函數(shù)調(diào)用都是有效的。這包括 cudaDeviceSynchronize(); cudaStreamSynchronize() 和 cudaStreamQuery()(如果它返回 cudaSuccess 而不是 cudaErrorNotReady),其中指定的流是唯一仍在 GPU 上執(zhí)行的流; cudaEventSynchronize() 和 cudaEventQuery() 在指定事件之后沒有任何設(shè)備工作的情況下;以及記錄為與主機(jī)完全同步的 cudaMemcpy() 和 cudaMemset() 的使用。

將遵循流之間創(chuàng)建的依賴關(guān)系,通過在流或事件上同步來推斷其他流的完成。依賴關(guān)系可以通過 cudaStreamWaitEvent() 或在使用默認(rèn) (NULL) 流時(shí)隱式創(chuàng)建。

CPU 從流回調(diào)中訪問托管數(shù)據(jù)是合法的,前提是 GPU 上沒有其他可能訪問托管數(shù)據(jù)的流處于活動(dòng)狀態(tài)。此外,沒有任何設(shè)備工作的回調(diào)可用于同步:例如,通過從回調(diào)內(nèi)部發(fā)出條件變量的信號(hào);否則,CPU 訪問僅在回調(diào)期間有效。

有幾個(gè)重要的注意點(diǎn):

在 GPU 處于活動(dòng)狀態(tài)時(shí),始終允許 CPU 訪問非托管零拷貝數(shù)據(jù)。

GPU 在運(yùn)行任何內(nèi)核時(shí)都被認(rèn)為是活動(dòng)的,即使該內(nèi)核不使用托管數(shù)據(jù)。如果內(nèi)核可能使用數(shù)據(jù),則禁止訪問,除非設(shè)備屬性 concurrentManagedAccess 為 1。

除了適用于非托管內(nèi)存的多 GPU 訪問之外,托管內(nèi)存的并發(fā) GPU 間訪問沒有任何限制。

并發(fā) GPU 內(nèi)核訪問托管數(shù)據(jù)沒有任何限制。

請注意最后一點(diǎn)如何允許 GPU 內(nèi)核之間的競爭,就像當(dāng)前非托管 GPU 內(nèi)存的情況一樣。如前所述,從 GPU 的角度來看,托管內(nèi)存的功能與非托管內(nèi)存相同。以下代碼示例說明了這些要點(diǎn):

int main() {
    cudaStream_t stream1, stream2;
    cudaStreamCreate(&stream1);
    cudaStreamCreate(&stream2);
    int *non_managed, *managed, *also_managed;
    cudaMallocHost(&non_managed, 4);    // Non-managed, CPU-accessible memory
    cudaMallocManaged(&managed, 4);
    cudaMallocManaged(&also_managed, 4);
    // Point 1: CPU can access non-managed data.
    kernel<<< 1, 1, 0, stream1 >>>(managed);
    *non_managed = 1;
    // Point 2: CPU cannot access any managed data while GPU is busy,
    //          unless concurrentManagedAccess = 1
    // Note we have not yet synchronized, so "kernel" is still active.
    *also_managed = 2;      // Will issue segmentation fault
    // Point 3: Concurrent GPU kernels can access the same data.
    kernel<<< 1, 1, 0, stream2 >>>(managed);
    // Point 4: Multi-GPU concurrent access is also permitted.
    cudaSetDevice(1);
    kernel<<< 1, 1 >>>(managed);
    return  0;
}

N.2.2.3. Managing Data Visibility and Concurrent CPU + GPU Access with Streams

到目前為止,假設(shè)對(duì)于 6.x 之前的 SM 架構(gòu):1) 任何活動(dòng)內(nèi)核都可以使用任何托管內(nèi)存,以??及 2) 在內(nèi)核處于活動(dòng)狀態(tài)時(shí)使用來自 CPU 的托管內(nèi)存是無效的。在這里,我們提出了一個(gè)用于對(duì)托管內(nèi)存進(jìn)行更細(xì)粒度控制的系統(tǒng),該系統(tǒng)旨在在所有支持托管內(nèi)存的設(shè)備上工作,包括 concurrentManagedAccess 等于 0 的舊架構(gòu)。

CUDA 編程模型提供流作為程序指示內(nèi)核啟動(dòng)之間的依賴性和獨(dú)立性的機(jī)制。啟動(dòng)到同一流中的內(nèi)核保證連續(xù)執(zhí)行,而啟動(dòng)到不同流中的內(nèi)核允許并發(fā)執(zhí)行。流描述了工作項(xiàng)之間的獨(dú)立性,因此可以通過并發(fā)實(shí)現(xiàn)更高的效率。

統(tǒng)一內(nèi)存建立在流獨(dú)立模型之上,允許 CUDA 程序顯式地將托管分配與 CUDA 流相關(guān)聯(lián)。通過這種方式,程序員根據(jù)內(nèi)核是否將數(shù)據(jù)啟動(dòng)到指定的流中來指示內(nèi)核對(duì)數(shù)據(jù)的使用。這為基于程序特定數(shù)據(jù)訪問模式的并發(fā)提供了機(jī)會(huì)??刂七@種行為的函數(shù)是:

    cudaError_t cudaStreamAttachMemAsync(cudaStream_t stream,
                                         void *ptr,
                                         size_t length=0,
                                         unsigned int flags=0);

前,length 必須始終為 0 以指示應(yīng)該附加整個(gè)區(qū)域。)由于這種關(guān)聯(lián),只要流中的所有操作都已完成,統(tǒng)一內(nèi)存系統(tǒng)就允許 CPU 訪問該內(nèi)存區(qū)域,而不管其他流是否是活躍的。實(shí)際上,這將活動(dòng) GPU 對(duì)托管內(nèi)存區(qū)域的獨(dú)占所有權(quán)限制為每個(gè)流活動(dòng)而不是整個(gè) GPU 活動(dòng)。

最重要的是,如果分配與特定流無關(guān),則所有正在運(yùn)行的內(nèi)核都可以看到它,而不管它們的流如何。這是 cudaMallocManaged() 分配或 __managed__ 變量的默認(rèn)可見性;因此,在任何內(nèi)核運(yùn)行時(shí) CPU 不得接觸數(shù)據(jù)的簡單案例規(guī)則。

通過將分配與特定流相關(guān)聯(lián),程序保證只有啟動(dòng)到該流中的內(nèi)核才會(huì)接觸該數(shù)據(jù)。統(tǒng)一內(nèi)存系統(tǒng)不執(zhí)行錯(cuò)誤檢查:程序員有責(zé)任確保兌現(xiàn)保證。

除了允許更大的并發(fā)性之外,使用 cudaStreamAttachMemAsync() 可以(并且通常會(huì))啟用統(tǒng)一內(nèi)存系統(tǒng)內(nèi)的數(shù)據(jù)傳輸優(yōu)化,這可能會(huì)影響延遲和其他開銷。

N.2.2.4. Stream Association Examples

將數(shù)據(jù)與流相關(guān)聯(lián)允許對(duì) CPU + GPU 并發(fā)進(jìn)行細(xì)粒度控制,但在使用計(jì)算能力低于 6.x 的設(shè)備時(shí),必須牢記哪些數(shù)據(jù)對(duì)哪些流可見。 查看前面的同步示例:

__device__ __managed__ int x, y=2;
__global__  void  kernel() {
    x = 10;
}
int main() {
    cudaStream_t stream1;
    cudaStreamCreate(&stream1);
    cudaStreamAttachMemAsync(stream1, &y, 0, cudaMemAttachHost);
    cudaDeviceSynchronize();          // Wait for Host attachment to occur.
    kernel<<< 1, 1, 0, stream1 >>>(); // Note: Launches into stream1.
    y = 20;                           // Success – a kernel is running but “y” 
                                      // has been associated with no stream.
    return  0;
}

在這里,我們明確地將 y 與主機(jī)可訪問性相關(guān)聯(lián),從而始終可以從 CPU 進(jìn)行訪問。 (和以前一樣,請注意在訪問之前沒有cudaDeviceSynchronize()。)GPU 運(yùn)行內(nèi)核對(duì) y 的訪問現(xiàn)在將產(chǎn)生未定義的結(jié)果。

請注意,將變量與流關(guān)聯(lián)不會(huì)更改任何其他變量的關(guān)聯(lián)。 例如。 將 x 與 stream1 關(guān)聯(lián)并不能確保在 stream1 中啟動(dòng)的內(nèi)核只能訪問 x,因此此代碼會(huì)導(dǎo)致錯(cuò)誤:

__device__ __managed__ int x, y=2;
__global__  void  kernel() {
    x = 10;
}
int main() {
    cudaStream_t stream1;
    cudaStreamCreate(&stream1);
    cudaStreamAttachMemAsync(stream1, &x);// Associate “x” with stream1.
    cudaDeviceSynchronize();              // Wait for “x” attachment to occur.
    kernel<<< 1, 1, 0, stream1 >>>();     // Note: Launches into stream1.
    y = 20;                               // ERROR: “y” is still associated globally 
                                          // with all streams by default
    return  0;
}

請注意訪問 y 將如何導(dǎo)致錯(cuò)誤,因?yàn)榧词?x 已與流相關(guān)聯(lián),我們也沒有告訴系統(tǒng)誰可以看到 y。 因此,系統(tǒng)保守地假設(shè)內(nèi)核可能會(huì)訪問它并阻止 CPU 這樣做。

N.2.2.5. Stream Attach With Multithreaded Host Programs

cudaStreamAttachMemAsync() 的主要用途是使用 CPU 線程啟用獨(dú)立任務(wù)并行性。 通常在這樣的程序中,CPU 線程為它生成的所有工作創(chuàng)建自己的流,因?yàn)槭褂?CUDA 的 NULL 流會(huì)導(dǎo)致線程之間的依賴關(guān)系。

托管數(shù)據(jù)對(duì)任何 GPU 流的默認(rèn)全局可見性使得難以避免多線程程序中 CPU 線程之間的交互。 因此,函數(shù) cudaStreamAttachMemAsync() 用于將線程的托管分配與該線程自己的流相關(guān)聯(lián),并且該關(guān)聯(lián)通常在線程的生命周期內(nèi)不會(huì)更改。

這樣的程序?qū)⒑唵蔚靥砑右粋€(gè)對(duì) cudaStreamAttachMemAsync() 的調(diào)用,以使用統(tǒng)一內(nèi)存進(jìn)行數(shù)據(jù)訪問:

// This function performs some task, in its own private stream.
void run_task(int *in, int *out, int length) {
    // Create a stream for us to use.
    cudaStream_t stream;
    cudaStreamCreate(&stream);
    // Allocate some managed data and associate with our stream.
    // Note the use of the host-attach flag to cudaMallocManaged();
    // we then associate the allocation with our stream so that
    // our GPU kernel launches can access it.
    int *data;
    cudaMallocManaged((void **)&data, length, cudaMemAttachHost);
    cudaStreamAttachMemAsync(stream, data);
    cudaStreamSynchronize(stream);
    // Iterate on the data in some way, using both Host & Device.
    for(int i=0; i>>(in, data, length);
        cudaStreamSynchronize(stream);
        host_process(data, length);    // CPU uses managed data.
        convert<<< 100, 256, 0, stream >>>(out, data, length);
    }
    cudaStreamSynchronize(stream);
    cudaStreamDestroy(stream);
    cudaFree(data);
}

在這個(gè)例子中,分配流關(guān)聯(lián)只建立一次,然后主機(jī)和設(shè)備都重復(fù)使用數(shù)據(jù)。 結(jié)果是比在主機(jī)和設(shè)備之間顯式復(fù)制數(shù)據(jù)時(shí)更簡單的代碼,盡管結(jié)果是相同的。

N.2.2.6. Advanced Topic: Modular Programs and Data Access Constraints

在前面的示例中,cudaMallocManaged() 指定了 cudaMemAttachHost 標(biāo)志,它創(chuàng)建了一個(gè)最初對(duì)設(shè)備端執(zhí)行不可見的分配。 (默認(rèn)分配對(duì)所有流上的所有 GPU 內(nèi)核都是可見的。)這可確保在數(shù)據(jù)分配和為特定流獲取數(shù)據(jù)之間的時(shí)間間隔內(nèi),不會(huì)與另一個(gè)線程的執(zhí)行發(fā)生意外交互。

如果沒有這個(gè)標(biāo)志,如果另一個(gè)線程啟動(dòng)的內(nèi)核恰好正在運(yùn)行,則新分配將被視為在 GPU 上使用。這可能會(huì)影響線程在能夠?qū)⑵滹@式附加到私有流之前從 CPU 訪問新分配的數(shù)據(jù)的能力(例如,在基類構(gòu)造函數(shù)中)。因此,為了啟用線程之間的安全獨(dú)立性,應(yīng)指定此標(biāo)志進(jìn)行分配。

注意:另一種方法是在分配附加到流之后在所有線程上放置一個(gè)進(jìn)程范圍的屏障。這將確保所有線程在啟動(dòng)任何內(nèi)核之前完成其數(shù)據(jù)/流關(guān)聯(lián),從而避免危險(xiǎn)。在銷毀流之前需要第二個(gè)屏障,因?yàn)榱麂N毀會(huì)導(dǎo)致分配恢復(fù)到其默認(rèn)可見性。 cudaMemAttachHost 標(biāo)志的存在既是為了簡化此過程,也是因?yàn)椴⒎强偸强梢栽谛枰牡胤讲迦肴制琳稀?/p>

N.2.2.7. Memcpy()/Memset() Behavior With Managed Memory

由于可以從主機(jī)或設(shè)備訪問托管內(nèi)存,因此 cudaMemcpy*() 依賴于使用 cudaMemcpyKind 指定的傳輸類型來確定數(shù)據(jù)應(yīng)該作為主機(jī)指針還是設(shè)備指針訪問。

如果指定了 cudaMemcpyHostTo* 并且管理了源數(shù)據(jù),那么如果在復(fù)制流 (1) 中可以從主機(jī)連貫地訪問它,那么它將從主機(jī)訪問;否則將從設(shè)備訪問。當(dāng)指定 cudaMemcpy*ToHost 并且目標(biāo)是托管內(nèi)存時(shí),類似的規(guī)則適用于目標(biāo)。

如果指定了 cudaMemcpyDeviceTo* 并管理源數(shù)據(jù),則將從設(shè)備訪問它。源必須可以從復(fù)制流中的設(shè)備連貫地訪問 (2);否則,返回錯(cuò)誤。當(dāng)指定 cudaMemcpy*ToDevice 并且目標(biāo)是托管內(nèi)存時(shí),類似的規(guī)則適用于目標(biāo)。

如果指定了 cudaMemcpyDefault,則如果無法從復(fù)制流中的設(shè)備一致地訪問托管數(shù)據(jù) (2),或者如果數(shù)據(jù)的首選位置是 cudaCpuDeviceId 并且可以從主機(jī)一致地訪問,則將從主機(jī)訪問托管數(shù)據(jù)在復(fù)制流 (1) 中;否則,它將從設(shè)備訪問。

將 cudaMemset*() 與托管內(nèi)存一起使用時(shí),始終從設(shè)備訪問數(shù)據(jù)。數(shù)據(jù)必須可以從用于 cudaMemset() 操作的流中的設(shè)備連貫地訪問 (2);否則,返回錯(cuò)誤。

當(dāng)通過 cudaMemcpy* 或 cudaMemset* 從設(shè)備訪問數(shù)據(jù)時(shí),操作流被視為在 GPU 上處于活動(dòng)狀態(tài)。在此期間,如果 GPU 的設(shè)備屬性 concurrentManagedAccess 為零值,則任何與該流相關(guān)聯(lián)的數(shù)據(jù)或具有全局可見性的數(shù)據(jù)的 CPU 訪問都將導(dǎo)致分段錯(cuò)誤。在從 CPU 訪問任何相關(guān)數(shù)據(jù)之前,程序必須適當(dāng)同步以確保操作已完成。

(1) 要在給定流中從主機(jī)連貫地訪問托管內(nèi)存,必須至少滿足以下條件之一:

給定流與設(shè)備屬性 concurrentManagedAccess 具有非零值的設(shè)備相關(guān)聯(lián)。

內(nèi)存既不具有全局可見性,也不與給定流相關(guān)聯(lián)。

(2) 要在給定流中從設(shè)備連貫地訪問托管內(nèi)存,必須至少滿足以下條件之一:

設(shè)備的設(shè)備屬性 concurrentManagedAccess 具有非零值。

內(nèi)存要么具有全局可見性,要么與給定的流相關(guān)聯(lián)。

###N.2.3. Language Integration

使用 nvcc 編譯主機(jī)代碼的 CUDA 運(yùn)行時(shí) API 用戶可以訪問其他語言集成功能,例如共享符號(hào)名稱和通過 《《《。..》》》 運(yùn)算符啟動(dòng)內(nèi)聯(lián)內(nèi)核。 統(tǒng)一內(nèi)存為 CUDA 的語言集成添加了一個(gè)附加元素:使用 __managed__ 關(guān)鍵字注釋的變量可以直接從主機(jī)和設(shè)備代碼中引用。

下面的例子在前面的 Simplifying GPU Programming 中看到,說明了 __managed__ 全局聲明的簡單使用:

// Managed variable declaration is an extra annotation with __device__
__device__ __managed__  int  x;
__global__  void  kernel() {
    // Reference "x" directly - it's a normal variable on the GPU.
    printf( "GPU sees: x = %d\n" , x);
} 
int  main() {
    // Set "x" from Host code. Note it's just a normal variable on the CPU.
    x = 1234;
 
    // Launch a kernel which uses "x" from the GPU.
    kernel<<< 1, 1 >>>(); 
    cudaDeviceSynchronize(); 
    return  0;
}

__managed__ 變量的可用功能是該符號(hào)在設(shè)備代碼和主機(jī)代碼中都可用,而無需取消引用指針,并且數(shù)據(jù)由所有人共享。這使得在主機(jī)和設(shè)備程序之間交換數(shù)據(jù)變得特別容易,而無需顯式分配或復(fù)制。

從語義上講,__managed__ 變量的行為與通過 cudaMallocManaged() 分配的存儲(chǔ)相同。有關(guān)詳細(xì)說明,請參閱使用 cudaMallocManaged() 進(jìn)行顯式分配。流可見性默認(rèn)為 cudaMemAttachGlobal,但可以使用 cudaStreamAttachMemAsync() 進(jìn)行限制。

__managed__ 變量的正確操作需要有效的 CUDA 上下文。如果當(dāng)前設(shè)備的上下文尚未創(chuàng)建,則訪問 __managed__ 變量可以觸發(fā) CUDA 上下文創(chuàng)建。在上面的示例中,在內(nèi)核啟動(dòng)之前訪問 x 會(huì)觸發(fā)設(shè)備 0 上的上下文創(chuàng)建。如果沒有該訪問,內(nèi)核啟動(dòng)將觸發(fā)上下文創(chuàng)建。

聲明為 __managed__ 的 C++ 對(duì)象受到某些特定約束,尤其是在涉及靜態(tài)初始化程序的情況下。有關(guān)這些約束的列表,請參閱 CUDA C++ 編程指南中的 C++ 語言支持。

N.2.3.1. Host Program Errors with managed Variables

__managed__ 變量的使用取決于底層統(tǒng)一內(nèi)存系統(tǒng)是否正常運(yùn)行。 例如,如果 CUDA 安裝失敗或 CUDA 上下文創(chuàng)建不成功,則可能會(huì)出現(xiàn)不正確的功能。

當(dāng)特定于 CUDA 的操作失敗時(shí),通常會(huì)返回一個(gè)錯(cuò)誤,指出失敗的根源。 使用 __managed__ 變量引入了一種新的故障模式,如果統(tǒng)一內(nèi)存系統(tǒng)運(yùn)行不正確,非 CUDA 操作(例如,CPU 訪問應(yīng)該是有效的主機(jī)內(nèi)存地址)可能會(huì)失敗。 這種無效的內(nèi)存訪問不能輕易地歸因于底層的 CUDA 子系統(tǒng),盡管諸如 cuda-gdb 之類的調(diào)試器會(huì)指示托管內(nèi)存地址是故障的根源。

N.2.4. Querying Unified Memory Support

N.2.4.1. Device Properties

統(tǒng)一內(nèi)存僅在具有 3.0 或更高計(jì)算能力的設(shè)備上受支持。程序可以通過使用 cudaGetDeviceProperties() 并檢查新的 managedMemory 屬性來查詢 GPU 設(shè)備是否支持托管內(nèi)存。也可以使用具有屬性 cudaDevAttrManagedMemory 的單個(gè)屬性查詢函數(shù) cudaDeviceGetAttribute() 來確定能力。

如果在 GPU 和當(dāng)前操作系統(tǒng)下允許托管內(nèi)存分配,則任一屬性都將設(shè)置為 1。請注意,32 位應(yīng)用程序不支持統(tǒng)一內(nèi)存(除非在 Android 上),即使 GPU 有足夠的能力。

支持平臺(tái)上計(jì)算能力 6.x 的設(shè)備無需調(diào)用 cudaHostRegister 即可訪問可分頁內(nèi)存。應(yīng)用程序可以通過檢查新的 pageableMemoryAccess 屬性來查詢設(shè)備是否支持連貫訪問可分頁內(nèi)存。

通過新的缺頁機(jī)制,統(tǒng)一內(nèi)存保證了全局?jǐn)?shù)據(jù)的一致性。這意味著 CPU 和 GPU 可以同時(shí)訪問統(tǒng)一內(nèi)存分配。這在計(jì)算能力低于 6.x 的設(shè)備上是非法的,因?yàn)槿绻?CPU 在 GPU 內(nèi)核處于活動(dòng)狀態(tài)時(shí)訪問統(tǒng)一內(nèi)存分配,則無法保證一致性。程序可以通過檢查 concurrentManagedAccess 屬性來查詢并發(fā)訪問支持。有關(guān)詳細(xì)信息,請參閱一致性和并發(fā)性。

N.2.5. Advanced Topics

N.2.5.1. Managed Memory with Multi-GPU Programs on pre-6.x Architectures

在計(jì)算能力低于 6.x 的設(shè)備的系統(tǒng)上,托管分配通過 GPU 的對(duì)等能力自動(dòng)對(duì)系統(tǒng)中的所有 GPU 可見。

在 Linux 上,只要程序正在使用的所有 GPU 都具有點(diǎn)對(duì)點(diǎn)支持,托管內(nèi)存就會(huì)在 GPU 內(nèi)存中分配。如果在任何時(shí)候應(yīng)用程序開始使用不支持對(duì)等支持的 GPU 與任何其他對(duì)其進(jìn)行了托管分配的 GPU,則驅(qū)動(dòng)程序會(huì)將所有托管分配遷移到系統(tǒng)內(nèi)存。

在 Windows 上,如果對(duì)等映射不可用(例如,在不同架構(gòu)的 GPU 之間),那么系統(tǒng)將自動(dòng)回退到使用零拷貝內(nèi)存,無論兩個(gè) GPU 是否都被程序?qū)嶋H使用。如果實(shí)際只使用一個(gè) GPU,則需要在啟動(dòng)程序之前設(shè)置 CUDA_VISIBLE_DEVICES 環(huán)境變量。這限制了哪些 GPU 是可見的,并允許在 GPU 內(nèi)存中分配托管內(nèi)存。

或者,在 Windows 上,用戶還可以將 CUDA_MANAGED_FORCE_DEVICE_ALLOC 設(shè)置為非零值,以強(qiáng)制驅(qū)動(dòng)程序始終使用設(shè)備內(nèi)存進(jìn)行物理存儲(chǔ)。當(dāng)此環(huán)境變量設(shè)置為非零值時(shí),該進(jìn)程中使用的所有支持托管內(nèi)存的設(shè)備必須彼此對(duì)等兼容。如果使用支持托管內(nèi)存的設(shè)備并且它與之前在該進(jìn)程中使用的任何其他托管內(nèi)存支持設(shè)備不兼容,則將返回錯(cuò)誤 ::cudaErrorInvalidDevice,即使 ::cudaDeviceReset 具有在這些設(shè)備上被調(diào)用。這些環(huán)境變量在附錄 CUDA 環(huán)境變量中進(jìn)行了描述。請注意,從 CUDA 8.0 開始,CUDA_MANAGED_FORCE_DEVICE_ALLOC 對(duì) Linux 操作系統(tǒng)沒有影響。

N.2.5.2. Using fork() with Managed Memory

統(tǒng)一內(nèi)存系統(tǒng)不允許在進(jìn)程之間共享托管內(nèi)存指針。 它不會(huì)正確管理通過 fork() 操作復(fù)制的內(nèi)存句柄。 如果子級(jí)或父級(jí)在 fork() 之后訪問托管數(shù)據(jù),則結(jié)果將不確定。

然而,fork() 一個(gè)子進(jìn)程然后通過 exec() 調(diào)用立即退出是安全的,因?yàn)樽舆M(jìn)程丟棄了內(nèi)存句柄并且父進(jìn)程再次成為唯一的所有者。 父母離開并讓孩子接觸句柄是不安全的。

N.3. Performance Tuning

為了使用統(tǒng)一內(nèi)存實(shí)現(xiàn)良好的性能,必須滿足以下目標(biāo):

應(yīng)避免錯(cuò)誤:雖然可重放錯(cuò)誤是啟用更簡單的編程模型的基礎(chǔ),但它們可能嚴(yán)重?fù)p害應(yīng)用程序性能。故障處理可能需要幾十微秒,因?yàn)樗赡苌婕?TLB 無效、數(shù)據(jù)遷移和頁表更新。與此同時(shí),應(yīng)用程序某些部分的執(zhí)行將停止,從而可能影響整體性能。

數(shù)據(jù)應(yīng)該位于訪問處理器的本地:如前所述,當(dāng)數(shù)據(jù)位于訪問它的處理器本地時(shí),內(nèi)存訪問延遲和帶寬明顯更好。因此,應(yīng)適當(dāng)遷移數(shù)據(jù)以利用較低的延遲和較高的帶寬。

應(yīng)該防止內(nèi)存抖動(dòng):如果數(shù)據(jù)被多個(gè)處理器頻繁訪問并且必須不斷遷移以實(shí)現(xiàn)數(shù)據(jù)局部性,那么遷移的開銷可能會(huì)超過局部性的好處。應(yīng)盡可能防止內(nèi)存抖動(dòng)。如果無法預(yù)防,則必須進(jìn)行適當(dāng)?shù)臋z測和解決。

為了達(dá)到與不使用統(tǒng)一內(nèi)存相同的性能水平,應(yīng)用程序必須引導(dǎo)統(tǒng)一內(nèi)存驅(qū)動(dòng)子系統(tǒng)避免上述陷阱。值得注意的是,統(tǒng)一內(nèi)存驅(qū)動(dòng)子系統(tǒng)可以檢測常見的數(shù)據(jù)訪問模式并自動(dòng)實(shí)現(xiàn)其中一些目標(biāo),而無需應(yīng)用程序參與。但是,當(dāng)數(shù)據(jù)訪問模式不明顯時(shí),來自應(yīng)用程序的明確指導(dǎo)至關(guān)重要。 CUDA 8.0 引入了有用的 API,用于為運(yùn)行時(shí)提供內(nèi)存使用提示 (cudaMemAdvise()) 和顯式預(yù)取 (cudaMemPrefetchAsync())。這些工具允許與顯式內(nèi)存復(fù)制和固定 API 相同的功能,而不會(huì)恢復(fù)到顯式 GPU 內(nèi)存分配的限制。

注意:Tegra 設(shè)備不支持 cudaMemPrefetchAsync()。

N.3.1. Data Prefetching

數(shù)據(jù)預(yù)取意味著將數(shù)據(jù)遷移到處理器的內(nèi)存中,并在處理器開始訪問該數(shù)據(jù)之前將其映射到該處理器的頁表中。 數(shù)據(jù)預(yù)取的目的是在建立數(shù)據(jù)局部性的同時(shí)避免故障。 這對(duì)于在任何給定時(shí)間主要從單個(gè)處理器訪問數(shù)據(jù)的應(yīng)用程序來說是最有價(jià)值的。 由于訪問處理器在應(yīng)用程序的生命周期中發(fā)生變化,因此可以相應(yīng)地預(yù)取數(shù)據(jù)以遵循應(yīng)用程序的執(zhí)行流程。 由于工作是在 CUDA 中的流中啟動(dòng)的,因此預(yù)計(jì)數(shù)據(jù)預(yù)取也是一種流操作,如以下 API 所示:

    cudaError_t cudaMemPrefetchAsync(const void *devPtr, 
                                     size_t count, 
                                     int dstDevice, 
                                     cudaStream_t stream);

其中由devPtr指針和count字節(jié)數(shù)指定的內(nèi)存區(qū)域,ptr向下舍入到最近的頁面邊界,count向上舍入到最近的頁面邊界,通過在流中排隊(duì)遷移操作遷移到dstDevice。 為dstDevice傳入cudaCpuDeviceId會(huì)導(dǎo)致數(shù)據(jù)遷移到 CPU 內(nèi)存。 考慮下面的一個(gè)簡單代碼示例:

void foo(cudaStream_t s) {
  char *data;
  cudaMallocManaged(&data, N);
  init_data(data, N);                                   // execute on CPU
  cudaMemPrefetchAsync(data, N, myGpuId, s);            // prefetch to GPU
  mykernel<<<..., s>>>(data, N, 1, compare);            // execute on GPU
  cudaMemPrefetchAsync(data, N, cudaCpuDeviceId, s);    // prefetch to CPU
  cudaStreamSynchronize(s);
  use_data(data, N);
  cudaFree(data);
}

如果沒有性能提示,內(nèi)核 mykernel 將在首次訪問數(shù)據(jù)時(shí)出錯(cuò),這會(huì)產(chǎn)生額外的故障處理開銷,并且通常會(huì)減慢應(yīng)用程序的速度。 通過提前預(yù)取數(shù)據(jù),可以避免頁面錯(cuò)誤并獲得更好的性能。 此 API 遵循流排序語義,即遷移在流中的所有先前操作完成之前不會(huì)開始,并且流中的任何后續(xù)操作在遷移完成之前不會(huì)開始。

N.3.2. Data Usage Hints

當(dāng)多個(gè)處理器需要同時(shí)訪問相同的數(shù)據(jù)時(shí),單獨(dú)的數(shù)據(jù)預(yù)取是不夠的。 在這種情況下,應(yīng)用程序提供有關(guān)如何實(shí)際使用數(shù)據(jù)的提示很有用。 以下咨詢 API 可用于指定數(shù)據(jù)使用情況:

    cudaError_t cudaMemAdvise(const void *devPtr, 
                              size_t count, 
                              enum cudaMemoryAdvise advice, 
                              int device);

其中,為從 devPtr 地址開始的區(qū)域中包含的數(shù)據(jù)指定的通知和計(jì)數(shù)字節(jié)的長度,四舍五入到最近的頁面邊界,可以采用以下值:

cudaMemAdviseSetReadMostly:這意味著數(shù)據(jù)大部分將被讀取并且只是偶爾寫入。 這允許驅(qū)動(dòng)程序在處理器訪問數(shù)據(jù)時(shí)在處理器內(nèi)存中創(chuàng)建數(shù)據(jù)的只讀拷貝。 同樣,如果在此區(qū)域上調(diào)用 cudaMemPrefetchAsync,它將在目標(biāo)處理器上創(chuàng)建數(shù)據(jù)的只讀拷貝。 當(dāng)處理器寫入此數(shù)據(jù)時(shí),相應(yīng)頁面的所有副本都將失效,但發(fā)生寫入的拷貝除外。 此建議忽略設(shè)備參數(shù)。 該建議允許多個(gè)處理器以最大帶寬同時(shí)訪問相同的數(shù)據(jù),如以下代碼片段所示:

char *dataPtr;
size_t dataSize = 4096;
// Allocate memory using malloc or cudaMallocManaged
dataPtr = (char *)malloc(dataSize);
// Set the advice on the memory region
cudaMemAdvise(dataPtr, dataSize, cudaMemAdviseSetReadMostly, 0);
int outerLoopIter = 0;
while (outerLoopIter < maxOuterLoopIter) {
    // The data is written to in the outer loop on the CPU
    initializeData(dataPtr, dataSize);
    // The data is made available to all GPUs by prefetching.
    // Prefetching here causes read duplication of data instead
    // of data migration
    for (int device = 0; device < maxDevices; device++) {
        cudaMemPrefetchAsync(dataPtr, dataSize, device, stream);
    }
    // The kernel only reads this data in the inner loop
    int innerLoopIter = 0;
    while (innerLoopIter < maxInnerLoopIter) {
        kernel<<<32,32>>>((const char *)dataPtr);
        innerLoopIter++;
    }
    outerLoopIter++;
}

cudaMemAdviseSetPreferredLocation:此建議將數(shù)據(jù)的首選位置設(shè)置為屬于設(shè)備的內(nèi)存。傳入設(shè)備的 cudaCpuDeviceId 值會(huì)將首選位置設(shè)置為 CPU 內(nèi)存。設(shè)置首選位置不會(huì)導(dǎo)致數(shù)據(jù)立即遷移到該位置。相反,它會(huì)在該內(nèi)存區(qū)域發(fā)生故障時(shí)指導(dǎo)遷移策略。如果數(shù)據(jù)已經(jīng)在它的首選位置并且故障處理器可以建立映射而不需要遷移數(shù)據(jù),那么遷移將被避免。另一方面,如果數(shù)據(jù)不在其首選位置,或者無法建立直接映射,那么它將被遷移到訪問它的處理器。請務(wù)必注意,設(shè)置首選位置不會(huì)阻止使用 cudaMemPrefetchAsync 完成數(shù)據(jù)預(yù)取。

cudaMemAdviseSetAccessedBy:這個(gè)advice意味著數(shù)據(jù)將被設(shè)備訪問。這不會(huì)導(dǎo)致數(shù)據(jù)遷移,并且對(duì)數(shù)據(jù)本身的位置沒有影響。相反,只要數(shù)據(jù)的位置允許建立映射,它就會(huì)使數(shù)據(jù)始終映射到指定處理器的頁表中。如果數(shù)據(jù)因任何原因被遷移,映射會(huì)相應(yīng)更新。此advice在數(shù)據(jù)局部性不重要但避免故障很重要的情況下很有用。例如,考慮一個(gè)包含多個(gè)啟用對(duì)等訪問的 GPU 的系統(tǒng),其中位于一個(gè) GPU 上的數(shù)據(jù)偶爾會(huì)被其他 GPU 訪問。在這種情況下,將數(shù)據(jù)遷移到其他 GPU 并不那么重要,因?yàn)樵L問不頻繁并且遷移的開銷可能太高。但是防止故障仍然有助于提高性能,因此提前設(shè)置映射很有用。請注意,在 CPU 訪問此數(shù)據(jù)時(shí),由于 CPU 無法直接訪問 GPU 內(nèi)存,因此數(shù)據(jù)可能會(huì)遷移到 CPU 內(nèi)存。任何為此數(shù)據(jù)設(shè)置了 cudaMemAdviceSetAccessedBy 標(biāo)志的 GPU 現(xiàn)在都將更新其映射以指向 CPU 內(nèi)存中的頁面。

每個(gè)advice也可以使用以下值之一取消設(shè)置:cudaMemAdviseUnsetReadMostly、cudaMemAdviseUnsetPreferredLocation 和 cudaMemAdviseUnsetAccessedBy。

N.3.3. Querying Usage Attributes

程序可以使用以下 API 查詢通過 cudaMemAdvise 或 cudaMemPrefetchAsync 分配的內(nèi)存范圍屬性:

    cudaMemRangeGetAttribute(void *data, 
                             size_t dataSize, 
                             enum cudaMemRangeAttribute attribute, 
                             const void *devPtr, 
                             size_t count);

此函數(shù)查詢從 devPtr 開始的內(nèi)存范圍的屬性,大小為 count 字節(jié)。內(nèi)存范圍必須引用通過 cudaMallocManaged 分配或通過 __managed__ 變量聲明的托管內(nèi)存??梢圆樵円韵聦傩裕?/p>

cudaMemRangeAttributeReadMostly:如果給定內(nèi)存范圍內(nèi)的所有頁面都啟用了重復(fù)讀取,則返回的結(jié)果將為 1,否則返回 0。

cudaMemRangeAttributePreferredLocation:如果內(nèi)存范圍內(nèi)的所有頁面都將相應(yīng)的處理器作為首選位置,則返回結(jié)果將是 GPU 設(shè)備 ID 或 cudaCpuDeviceId,否則將返回 cudaInvalidDeviceId。應(yīng)用程序可以使用此查詢 API 來決定通過 CPU 或 GPU 暫存數(shù)據(jù),具體取決于托管指針的首選位置屬性。請注意,查詢時(shí)內(nèi)存范圍內(nèi)頁面的實(shí)際位置可能與首選位置不同。

cudaMemRangeAttributeAccessedBy: 將返回為該內(nèi)存范圍設(shè)置了該建議的設(shè)備列表。

cudaMemRangeAttributeLastPrefetchLocation:將返回使用 cudaMemPrefetchAsync 顯式預(yù)取內(nèi)存范圍內(nèi)所有頁面的最后位置。請注意,這只是返回應(yīng)用程序請求將內(nèi)存范圍預(yù)取到的最后一個(gè)位置。它沒有指示對(duì)該位置的預(yù)取操作是否已經(jīng)完成或什至開始。

此外,還可以使用對(duì)應(yīng)的 cudaMemRangeGetAttributes 函數(shù)查詢多個(gè)屬性。

關(guān)于作者

Ken He 是 NVIDIA 企業(yè)級(jí)開發(fā)者社區(qū)經(jīng)理 & 高級(jí)講師,擁有多年的 GPU 和人工智能開發(fā)經(jīng)驗(yàn)。自 2017 年加入 NVIDIA 開發(fā)者社區(qū)以來,完成過上百場培訓(xùn),幫助上萬個(gè)開發(fā)者了解人工智能和 GPU 編程開發(fā)。在計(jì)算機(jī)視覺,高性能計(jì)算領(lǐng)域完成過多個(gè)獨(dú)立項(xiàng)目。并且,在機(jī)器人無人機(jī)領(lǐng)域,有過豐富的研發(fā)經(jīng)驗(yàn)。對(duì)于圖像識(shí)別,目標(biāo)的檢測與跟蹤完成過多種解決方案。曾經(jīng)參與 GPU 版氣象模式GRAPES,是其主要研發(fā)者。

審核編輯:郭婷

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • 嵌入式
    +關(guān)注

    關(guān)注

    5149

    文章

    19655

    瀏覽量

    317285
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11509

    瀏覽量

    213675
  • 操作系統(tǒng)
    +關(guān)注

    關(guān)注

    37

    文章

    7142

    瀏覽量

    125543
收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    拋棄8GB內(nèi)存,端側(cè)AI大模型加速內(nèi)存升級(jí)

    電子發(fā)燒友網(wǎng)報(bào)道(文/黃晶晶)端側(cè)AI大模型的到來在存儲(chǔ)市場產(chǎn)生了最直接的反應(yīng)。年初在我們對(duì)旗艦智能手機(jī)的存儲(chǔ)容量統(tǒng)計(jì)中,16GB內(nèi)存+512GB存儲(chǔ)成為幾乎所有旗艦機(jī)型都提供的選擇。畢竟AI大模型
    的頭像 發(fā)表于 11-03 00:02 ?5774次閱讀
    拋棄8GB<b class='flag-5'>內(nèi)存</b>,端側(cè)AI大<b class='flag-5'>模型</b>加速<b class='flag-5'>內(nèi)存</b>升級(jí)

    如何基于Kahn處理網(wǎng)絡(luò)定義AI引擎圖形編程模型

    本白皮書探討了如何基于 Kahn 處理網(wǎng)絡(luò)( KPN )定義 AI 引擎圖形編程模型。KPN 模型有助于實(shí)現(xiàn)數(shù)據(jù)流并行化,進(jìn)而提高系統(tǒng)的整體性能。
    的頭像 發(fā)表于 04-17 11:31 ?345次閱讀
    如何基于Kahn處理網(wǎng)絡(luò)定義AI引擎圖形<b class='flag-5'>編程</b><b class='flag-5'>模型</b>

    借助PerfXCloud和dify開發(fā)代碼轉(zhuǎn)換器

    隨著深度學(xué)習(xí)與高性能計(jì)算的迅速發(fā)展,GPU計(jì)算的廣泛應(yīng)用已成為推動(dòng)技術(shù)革新的股重要力量。對(duì)于GPU編程語言的選擇,CUDA和HIP是目前最為流行的兩種選擇。CUDA是由NVIDIA推
    的頭像 發(fā)表于 02-25 09:36 ?981次閱讀
    借助PerfXCloud和dify開發(fā)代碼轉(zhuǎn)換器

    【「大模型啟示錄」閱讀體驗(yàn)】對(duì)大模型更深入的認(rèn)知

    閱讀《大模型啟示錄》這本書,我得說,它徹底顛覆了我對(duì)大模型的理解。作為個(gè)經(jīng)常用KIMI和豆包這類AI工具來完成作業(yè)、整理資料的大學(xué)生,我原以為大模型就是這些工具背后的技術(shù)。但這本書讓
    發(fā)表于 12-20 15:46

    KerasHub統(tǒng)一、全面的預(yù)訓(xùn)練模型

    深度學(xué)習(xí)領(lǐng)域正在迅速發(fā)展,在處理各種類型的任務(wù)中,預(yù)訓(xùn)練模型變得越來越重要。Keras 以其用戶友好型 API 和對(duì)易用性的重視而聞名,始終處于這動(dòng)向的前沿。Keras 擁有專用的內(nèi)容庫,如用
    的頭像 發(fā)表于 12-20 10:32 ?493次閱讀

    養(yǎng)成良好的編程習(xí)慣|堆內(nèi)存初值不定是0

    ;} 代碼很簡單,使用 malloc 申請段堆內(nèi)存,假設(shè)內(nèi)存空間足夠大。 通過 getchar 配合 while 循環(huán),從標(biāo)準(zhǔn)輸入獲取個(gè)字符串,直到遇到換行符結(jié)束。 最后就是把獲取
    的頭像 發(fā)表于 12-18 09:14 ?366次閱讀

    CNN, RNN, GNN和Transformer模型統(tǒng)一表示和泛化誤差理論分析

    函數(shù)、參數(shù)調(diào)和函數(shù)和剩余函數(shù)。 我們先前的研究表明,RPN 在構(gòu)建不同復(fù)雜性、容量和完整性水平的模型方面具有很強(qiáng)的通用性,同時(shí)可以作為統(tǒng)一多種基礎(chǔ)模型(包括 PGM、核 SVM、MLP 和 KAN)的框架。 然而,先前的 RPN
    的頭像 發(fā)表于 12-06 11:31 ?1443次閱讀
    CNN, RNN, GNN和Transformer<b class='flag-5'>模型</b>的<b class='flag-5'>統(tǒng)一</b>表示和泛化誤差理論分析

    CNC系統(tǒng)一般可用幾種編程語言

    原文標(biāo)題:CNC系統(tǒng)一般可用幾種編程語言 文章出處:【微信公眾號(hào):電氣控制技術(shù)
    的頭像 發(fā)表于 10-23 15:52 ?1493次閱讀

    【「大模型時(shí)代的基礎(chǔ)架構(gòu)」閱讀體驗(yàn)】+ 第、二章學(xué)習(xí)感受

    今天閱讀了《大模型時(shí)代的基礎(chǔ)架構(gòu)》前兩章,還是比較輕松舒適的;再就是本書知識(shí)和我的工作領(lǐng)域沒有任何關(guān)聯(lián),切都是新鮮的,似乎每讀頁都會(huì)有所收獲,這種快樂的學(xué)習(xí)過程感覺也挺不錯(cuò)的。 第
    發(fā)表于 10-10 10:36

    接口芯片的編程模型方法是什么

    接口芯片的編程模型方法是個(gè)復(fù)雜的話題,涉及到硬件設(shè)計(jì)、軟件編程、通信協(xié)議等多個(gè)方面。 1. 接口芯片概述 接口芯片是用來連接不同硬件設(shè)備或系統(tǒng)的
    的頭像 發(fā)表于 09-30 11:30 ?647次閱讀

    怎么在TMDSEVM6678: 6678自帶的FFT接口和CUDA提供CUFFT函數(shù)庫選擇?

    請教下gpgpu上包括4個(gè)Riscv cpu和個(gè)DPU, 沒有6678,要替換原來信號(hào)處理用的6678,該怎么在6678自帶的FFT接口和CUDA提供CUFFT函數(shù)庫選擇?
    發(fā)表于 09-27 07:20

    統(tǒng)一多云管理平臺(tái)怎么用?

     統(tǒng)一多云管理平臺(tái)的使用主要涉及資源納管、費(fèi)用控制和智能運(yùn)維等方面。統(tǒng)一多云管理平臺(tái)是種能夠同時(shí)管理多種公有云、私有云以及傳統(tǒng)IT環(huán)境的資源,并實(shí)現(xiàn)自動(dòng)化和服務(wù)化交付的工具。它為企業(yè)提供了強(qiáng)大
    的頭像 發(fā)表于 08-14 11:28 ?472次閱讀

    【《大語言模型應(yīng)用指南》閱讀體驗(yàn)】+ 基礎(chǔ)篇

    的章節(jié)包括統(tǒng)一自然語言任務(wù)、大語言模型的訓(xùn)練過程和局限性分析,閱讀還算順利。 至此,基礎(chǔ)篇只能算是瀏覽完成,因?yàn)椴糠衷矸椒ú]有吃透,但盡管如此也是收獲頗豐,因?yàn)槲伊私饬舜笳Z言模型的基礎(chǔ)知識(shí)和應(yīng)用過程。
    發(fā)表于 07-25 14:33

    打破英偉達(dá)CUDA壁壘?AMD顯卡現(xiàn)在也能無縫適配CUDA

    電子發(fā)燒友網(wǎng)報(bào)道(文/梁浩斌)直以來,圍繞CUDA打造的軟件生態(tài),是英偉達(dá)在GPU領(lǐng)域最大的護(hù)城河,尤其是隨著目前AI領(lǐng)域的發(fā)展加速,市場火爆,英偉達(dá)GPU+CUDA的開發(fā)生態(tài)則更加穩(wěn)固,AMD
    的頭像 發(fā)表于 07-19 00:16 ?5931次閱讀

    英國公司實(shí)現(xiàn)英偉達(dá)CUDA軟件在AMD GPU上的無縫運(yùn)行

    7月18日最新資訊,英國創(chuàng)新科技企業(yè)Spectral Compute震撼發(fā)布了其革命性GPGPU編程工具包——“SCALE”,該工具包實(shí)現(xiàn)了英偉達(dá)CUDA軟件在AMD GPU上的無縫遷移與運(yùn)行,標(biāo)志著在GPU計(jì)算領(lǐng)域,NVIDIA長期以來的市場壟斷地位或?qū)⒂瓉碇卮筇魬?zhàn)。
    的頭像 發(fā)表于 07-18 14:40 ?1090次閱讀