並行計算之路——CUDA的硬件架構

如果你正在準備建立支持CUDA的新平臺並在上面進行編程,建議你精讀CUDA的硬件架構——《CUDA專家手冊》Nicholas Wilt 著。

CUDA入門很簡單,上手不到三天,我就寫些CUDA程序。但是都沒有進行效率上的分析,也只是單單的窺視了GPU加速的魅力。要成爲一個充分榨取GPU處理能力的並行計算工程師也絕非易事,至此不再單純編寫CUDA程序,重點放在優化上。

閱讀《CUDA專家手冊》第二章硬件架構後的一個筆記

CPU配置

南橋:連接大多外圍設備和系統,例如磁盤,鼠標,鍵盤燈。
北橋:圖像總線和內存控制器。
這裏寫圖片描述

對稱處理器蔟

對稱處理器蔟系統共享同一個通往CPU內存的路徑,不同CPU的內存訪問性能相對一致。
這裏寫圖片描述

非一致內存訪問

北橋的內存控制器直接集成到CPU中,這種結構的變化,提高了CPU的內存。
這裏寫圖片描述
文中提到:由於PCIe寬帶往往是整體應用性能的瓶頸,許多系統將使用獨立的I/O集線器服務更多的PCIe總線。由於GPU需要巨大的寬帶,DMA操作會降低HT/QPI爲其主要對象服務的能力。和僞共享相比,對於CUDA應用程序而言,GPU的非本地內存複製操作對性能的影響有可能更爲要命。

集成的PCIe

通過將I/O集線器集成到CPU中。集成的PCIe導致的結果是,不同CPU上的GPU之間無法執行點對點操作。而優點是,CPU緩存可以直接參與PCIe總線通信:DMA讀請求可以直接讀取緩存,並且GPU寫入的數據會放入緩存中。
這裏寫圖片描述

集成GPU

這裏的“集成”的意思是“集成到芯片組”,先前只屬於CPU的內存池現在可以被集成到芯片組的CPU和GPU所共享。
這裏寫圖片描述
在這樣的系統中,CUDA更傾向於在獨立的GPU上運行,因爲大多數的CUDA應用程序在獨立的GPU上部署。

多GPU

這裏寫圖片描述
由於SLI導致多GPU表現爲單個GPU,並且CUDA應用程序不能像圖形應用程序那樣透明地得到加速,所以CUDA開發人員一般不適用SLI。

CUDA中的地址空間

CPU和GPU的地址空間是分開的。CPU不能讀取或寫入GPU的設備內存,反過來,GPU也無法讀取或寫入CPU的內存。

虛擬尋址簡史

指定內存位置的16位值稱爲地址,地址的計算和相應內存位置上的操作過程則統稱爲尋址。16位地址空間簡圖如下:
這裏寫圖片描述
虛擬尋址能使一個連續的虛擬地址空間映射到物理內存並不連續的一些頁。虛擬地址空間:
這裏寫圖片描述
第一個索引指向頁表的“頁目錄”,第二索引代表第一個索引約束下選擇的頁表。這種分層設計減少了頁表所需的內存量,把不活躍的頁表標記爲非常駐狀態並交換到磁盤上。

頁表:操作系統爲每個進程維護一個頁表描述進程中頁與幀的對應,邏輯地址分爲了頁號和偏移量兩部分。一般情況下頁表的大小位頁的大小,頁表中每條記錄稱爲頁表實體(PTE,page table entry)。頁表可以是多級頁表,受制於頁大小的限制頁表的大小不能大於一頁(也不可能把巨大的頁表存放在主存中),因此頁表做多級處理,根頁表始終在主存中,當次級頁表不在主存中時從輔存加載對應的頁表進主存。

操作系統使用虛擬地址硬件實現的功能:

  • 緩式分配(lazy allocation):通過設置PTE運行分配無物理內存支持的頁,從而可以分配大容量的內存。如果請求內存的應用程序碰巧訪問這些頁面,操作系統會立刻找到一個有物理內存的頁面並解決這個故障。
  • 請求式調頁(demand paging):內存可以被複制到磁盤中並且頁面被標記爲“非常駐”。如果這樣的內存再次被引用,硬件會產生“頁面故障”信號,而且操作系統會將數據複製到一個物理頁中並修正PTE指向該頁,然後繼續執行。如此,故障得以解決。
  • 寫時複製(copy-on-write):通過創建第二組映射到相同的物理頁面的PTE,並將兩組PTE標記爲只讀,虛擬內存得以“複製”。如果硬件捕獲到一個試圖寫入這些頁面的操作,操作系統將會複製該組頁面到另一組物理頁面,並再次標誌這兩組PTE爲可寫,然後恢復執行。如果應用程序只寫入一個很小比例的“複製頁面”,寫時複製也就在性能上具有明顯優勢。
  • 映射文件I/O(mapped file I/O):文件可以被映射到地址空間,並且通過訪問文件可以解決頁面故障。對進行隨機訪問相關文件的應用程序,通過委託操作系統中高度優化的VNM代碼進行內存管理是非常有用的,特別是因爲它是緊密耦合的大容量存儲驅動程序。

不相交的地址空間

GPU並不支持請求式調頁,所以被CUDA分配的每一個字節虛擬內存都必須對應一個字節的物理內存。
由於每個GPU有它自己的內存和地址轉換硬件,GPU的地址空間和CUDA應用程序中的CPU地址空間是相互分開的。CPU和GPU都有自己的地址空間,用來映射各自設備自身的頁表,兩者的設備都要通過顯式的內存複製命令來交換數據。
這裏寫圖片描述

映射鎖頁內存

映射鎖頁內存是被映射到CUDA地址空間的鎖頁主存,在CUDA內核程序裏可以直接對其讀取或寫入。CPU和GPU的頁表更新了,以便CPU和GPU中擁有指向相同主機內存緩衝區的地址區間。由於地址空間不同,GPU指向該緩衝區的指針必須使用cuMemHostGetDevicePointre()或cudaHostGetDevicePointer()函數來查詢。
這裏寫圖片描述

可分享鎖頁內存

設置鎖頁內存“可分享”,會導致CUDA驅動把該內存映射給系統中的所有CPU,而不僅僅是當前上下文的GPU。
這裏寫圖片描述

統一尋址

統一虛擬尋址(unified virtual addressing,UVA)。當UVA生效時,CUDA會從相同的虛擬地址空間CPU和GPU分配內存。CUDA驅動程序通過以下兩步來完成上述任務:第一,初始化程序執行基於CPU地址空間的大型虛擬分配,該分配過程中可能會碰到無物理內存支持的情況;第二,將GPU分配的內存映射到上述地址空間。由於64位CPU支持48位虛擬地址空間,而CUDA GPU只支持40位,應用程序使用UVA時應確保CUDA被提前初始化,以保證CUDA所需的虛擬地址先於CPU代碼的分配請求而被滿足。
這裏寫圖片描述

點對點映射

點對點可以是費米架構GPU讀寫另一個費米架構GPU的內存。點對點映射僅支持啓用UVA的平臺,並且只對連接到相同I/O集線器上的GPU有效。由於使用點對點映射時UVA始終是有效的,不同設備的地址空間範圍不重疊,並且驅動程序(和運行時)可以從指針推斷出所駐留的設備。

點對點內存尋址是非對稱的。上圖所示,對於1號GPU的內存分配,0號GPU是可見的,反之則不行。爲了讓GPU之間能夠看到對方的內存,每個GPU必須顯式地映射其它GPU的內存。
這裏寫圖片描述

CPU/GPU交互

  • 鎖頁主機內存:GPU可以直接訪問的CPU內存。
  • 命令緩衝區:由CUDA驅動程序寫入命令,GPU從此緩衝區讀取命令並控制其執行。
  • CPU/GPU同步:指的是CPU如何跟蹤GPU的進度。

鎖頁主機內存和命令緩衝區

GPU可以通過直接內存訪問(direct memory access, DMA)方式來訪問CPU中的鎖頁內存。鎖頁是操作系統常用的操作,可以使硬件外設直接訪問CPU內存,從而避免過多的複製操作。“被鎖定”的頁面已被操作系統標記爲不可操作系統換出的,所以設備驅動程序給這些外設編程時,可以使用頁面的物理地址直接訪問內存。而CPU仍然可以訪問上述鎖頁內存,但是此內存是不能移動或換頁到磁盤上的。

CPU/GPU併發

  • 阿姆達爾法則
    =1(rs+rp/N)

    其中,rs+rp=1rs 代表的是串行部分比率。因爲對研究CPU/GPU併發等小規模的性能場合時,這一公式形式似乎不太方便。所以,將其變形成如下公式:
    =N(N(1rp)+rp)
  • 錯誤處理
    開發人員可以手動執行CPU/GPU同步作爲輔助,具體通過調用cudaThreadSynchronize(), cuCtxSynchronize()等函數來完成。cudaFree()或cuMemFree()的函數調用也會導致CPU/GPU的同步。對於調試代碼,如果難以通過同步操作來隔離故障,開發人員可以設置CUDA_LAUNCH_BLOCKING的環境變量,以迫使所有啓動的內核同步。

  • CPU/GPU同步
    上下文範圍的同步通過簡單調用cudaThreadSynchronize(), cuCtxSynchronize()等函數來檢查GPU的請求的最近同步值,並且一直等待,直到同步位置獲得該值。
    cuEventRecord()函數的作用是將一個命令加入隊列使得一個新的同步值寫入共享同步位置中,cuEventQuery()和cuEventSynchronize()則分別用於檢查和等待這個事件的同步值。
    通過指定CU_CTX_BLOCKING_SYNC到cuCtxCreate()或指定cudaDeviceBlockingSync到cudaSetDeviceFlags(),應用程序可以強制使用上下文範圍的同步進入阻塞狀態。然而,使用阻塞的CUDA事件(指定CU_EVENT_BLOCKING_SYNC到cuEventCreate()或指定cudaEventBlockingSync到cudaEventCreate())更可取,因爲它們粒度更細且可以與任何類型的CUDA上下文進行無縫互操作。

  • 事件和時間戳
    主機接口有一個機載高分辨計時器,它可以在寫入一個32位的同步值時同時寫一個時間戳。CUDA使用這個硬件實施實現CUDA事件的異步計時功能。

主機接口和內部GPU同步

foreach stream
   Memcpy device<-host
   Launch kernel
   Memcpy host<-device

GPU間同步

在CUDA 4.0中,Nvidia能夠在cudaStreamWaitEvent()和cuStreamWaitEvent()函數的形式中添加GPU之間的同步。這些API調用導致驅動程序爲主機接口將等待命令插入當前GPU的命令緩衝區中,使得GPU一直等待,直到事件的給定同步值被寫入爲止。從CUDA 4.0開始,事件不一定會被等待中的同一個GPU用信號喚醒。流原先只能在單個GPU的硬件單元之間同步執行,現在已經提升到可以在GPU之間同步執行了。

GPU架構

  • 特斯拉架構(Tesla)
  • 費米架構(Fermi)
  • 開普勒架構(Kepler)

從深度學習選擇什麼樣的gpu來談談gpu的硬件架構

尾巴

似乎跟以前看《操作系統》一樣苦難,抽象難記。說不定三天後,我就忘的一乾二淨。看的時候也是非常枯燥泛味,看第一遍的時候草草就得翻過去了。所以在第二遍,邊寫邊看似乎是一個好的選擇,雖然時間拉的長,但至少是看完了。

參考:
《GPGPU編程技術——從GLSL、CUDA到OpenCL》♥♥♥♥♥
《數字圖像處理高級應用——基於MATLAB與CUDA的實現》♥♥♥
《基於CUDA的並行程序設計》♥♥♥
《CUDA專家手冊》♥♥♥♥♥
《高性能CUDA應用設計與開發》♥♥♥♥

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章