異質計算叢集程式設計 練習題 (Practice - MPI Cluster Background, Stencil Example, and MPI Basics)
Related Concepts
- 20-Heterogeneous-Computing-Cluster/01-MPI-Cluster-Background-and-Basics — MPI 叢集背景、Stencil 執行範例與 MPI Basics
- 20-Heterogeneous-Computing-Cluster/02-MPI-Point-to-Point-Communication — MPI Point-to-Point 通訊與資料分發 (MPI_Send / MPI_Recv)
- 20-Heterogeneous-Computing-Cluster/03-Overlapping-Computation-and-Communication — 計算與通訊重疊 (CUDA Streams 與 Pinned Memory)
- 20-Heterogeneous-Computing-Cluster/04-Collective-Communication-and-CUDA-Aware-MPI — MPI Collective Communication 與 CUDA-aware MPI
| 關鍵字 / 觸發點 | 答案要點 |
|---|---|
| MPI 記憶體模型 | distributed memory + message passing;process 不共享變數 |
| MPI rank ↔ CUDA | blockIdx.x*blockDim.x+threadIdx.x(但 rank 是一維) |
| 五大設定 API | MPI_Init / MPI_Comm_rank / MPI_Comm_size / MPI_Abort / MPI_Finalize |
| 誰當 data server | rank np-1(最大 rank);compute = 0…np-2,共 np-1 個 |
MPI_COMM_WORLD |
預設 communicator,涵蓋所有跑此程式的 process |
MPI_Send / MPI_Recv 參數 |
Send 6 個;Recv 7 個(多 MPI_Status*,第 4 參數為 source) |
| 元素大小反推 | bytes / count(例 4000/1000 = 4 = MPI_FLOAT) |
| 25-point stencil 每側 halo | 4 片 slice;internal +8、edge +4(缺側用 ghost=0,不傳) |
MPI_Recv count 可大於實收 |
合法;只放入實際收到位元組 → edge 仍可配滿尺寸 buffer |
| 兩階段重疊策略 | stage 1 先算鄰居要的 boundary → stage 2 一邊通訊一邊算 internal |
| pinned / page-locked memory | cudaHostAlloc;DMA 用實體位址,防 page out → 可非同步、免額外複製 |
cudaMemcpyAsync 前提 |
host buffer 必須 pinned,否則非同步語意不成立 |
| 同 stream vs 不同 stream | 同 stream 循序;不同 stream 可並行(boundary→stream0,internal→stream1) |
MPI_Sendrecv / MPI_PROC_NULL |
一次同時送+收;neighbor 設 MPI_PROC_NULL → runtime 自動跳過,免特判 |
| double buffering / 反 swap | swap d_input↔d_output;迴圈後再 swap 一次抵銷多餘 swap |
| collective communication | barrier / broadcast / reduce / gather / scatter,全 communicator 共同參與 |
| CUDA-aware MPI 移除什麼 | halo 交換的 D2H/H2D staging copy 與 pinned buffer(每 iter 省 4 次 copy) |
| 支援 CUDA-aware 的實作 | MVAPICH2、IBM Platform MPI、OpenMPI |
Question 1 - MPI 記憶體模型與 SPMD [recall]
情境/題目:MPI 假設哪一種記憶體模型?process 之間如何交換資料?「所有 process 跑同一支程式卻能扮演不同角色」是什麼程式模型,對應 CUDA 的什麼?
MPI 假設 distributed memory model:每個 process 有獨立位址空間,不能透過 shared memory 存取同一變數,只能靠送/收訊息 (message passing) 交換資料。程式模型是 SPMD(Single Program Multiple Data):所有 process 跑同一支程式,靠 rank 走不同控制流與函式呼叫路徑——對應 CUDA kernel 內所有 thread 跑同一個 kernel、靠 thread id 分支。
Question 2 - 五大設定 API 與角色分工 [recall]
情境/題目:列出建立/拆除 MPI 通訊系統的五個核心 API。在本章 stencil 範例中,
np個 process 有幾個在算 stencil?誰當 data server?
五大 API:MPI_Init(初始化)、MPI_Comm_rank(取本 process rank)、MPI_Comm_size(取總 process 數)、MPI_Abort(中止,錯誤碼非 0)、MPI_Finalize(釋放資源)。角色分工:rank np-1(最大 rank)保留當 data server 做 I/O;rank 0…np-2 共 np-1 個 compute process 算 stencil。注意傳給 compute 的 dimz 是 dimz/(np-1)。
Question 3 - MPI_Send 與 MPI_Recv 參數對比 [recall]
情境/題目:
MPI_Send與MPI_Recv各有幾個參數?兩者第 4 個參數有何不同?若接收端不想限定 tag 該用什麼?
MPI_Send 有 6 個參數 (buf, count, datatype, dest, tag, comm);MPI_Recv 有 7 個,多了最後的 MPI_Status*。第 4 參數:MPI_Send 是 dest(目的 rank),MPI_Recv 是 source(來源 rank)。不限 tag 用 MPI_ANY_TAG。傳輸位元組數 = count × sizeof(datatype)。
Question 4 - Halo cells、Ghost cells 與 Edge/Internal [recall]
情境/題目:在 25-point stencil 的 domain partitioning 中,halo cells 與 ghost cells 有何不同?edge process 與 internal process 各需收到多少 z slice?
Halo cells = 計算邊界所需、來自鄰居 partition 的資料切片(某 partition 的 halo 即鄰居的 boundary)。Ghost cells = domain 最外緣、無真實鄰居處,如 convolution 邊界填 0,不需透過網路傳。25-point 每方向 4 鄰居 → 每側 4 片 halo。令 S = dimz/num_comp:internal 兩側皆有鄰居 → 收 dimx*dimy*(S+8);edge 僅單側 → 收 dimx*dimy*(S+4),缺側用 ghost。
Question 5 - 為何要兩階段重疊計算與通訊 [recall]
情境/題目:天真做法「整個 partition 算一步 → 交換 halo → 重複」為何效率不佳?兩階段策略中 stage 1 與 stage 2 各做什麼?
天真做法讓系統永遠處於兩種模式之一:大家都在算時網路閒置,大家都在換 halo 時運算硬體閒置,兩者交替浪費。兩階段策略:stage 1 先算鄰居「下一步」需要的 boundary slices(用 stream0);stage 2 一邊把新 boundary 送出(D2H copy + MPI),一邊算剩下的 internal points(用 stream1)。理想時間 T_iter ≈ T_stage1 + max(T_internal_compute, T_halo_comm),只要內部運算夠久就能把通訊延遲完全藏住。
Question 6 - Pinned Memory、DMA 與 cudaMemcpyAsync [recall]
情境/題目:為何 halo bounce buffer 要用
cudaHostAlloc而非malloc?這與 DMA 和cudaMemcpyAsync有何關聯?釋放時要用哪個 API?
cudaMemcpy 底層用 DMA 硬體,DMA 在 host 端操作的是實體位址;但 OS 的 virtual memory 可能把 pageable 記憶體換出 (page out),實體位址被重指派 → DMA 資料損毀。cudaHostAlloc(..., cudaHostAllocDefault) 配置 pinned / page-locked memory,標記為不可換出,DMA 可安全直接搬運且免去「先複製到內部 pinned 暫存」的額外步驟。cudaMemcpyAsync 要求 host buffer 必須是 pinned,否則非同步語意不成立。釋放要用 cudaFreeHost(不是 free)。
Question 7 - CUDA Streams 的並行語意 [recall]
情境/題目:CUDA stream 的執行順序規則是什麼?為何 boundary kernel 放 stream0、internal kernel 放 stream1?
cudaStreamSynchronize與cudaDeviceSynchronize有何差別?
Stream = 有序操作序列:同一 stream 內依放入順序循序執行;不同 stream 間無順序限制,可並行。把「短而急」的 boundary 運算 + 通訊鏈放 stream0、把「長」的 internal 運算放 stream1,硬體即可同時推進兩者達成重疊。cudaStreamSynchronize(s) 只等單一 stream s(確保 boundary 已 D2H 完成再做 MPI);cudaDeviceSynchronize() 等所有 device 活動(iteration 結尾確認 d_output 全就緒)。
Question 8 - MPI_Sendrecv 與 MPI_PROC_NULL [recall]
情境/題目:halo 交換為何用
MPI_Sendrecv而非分開的MPI_Send/MPI_Recv?edge process 沒有某側鄰居時如何免去 if-then-else 特判?
MPI_Sendrecv = MPI_Send + MPI_Recv 合一,一次呼叫同時送出 boundary 與收進 halo,減少呼叫數並避免死結 (deadlock)。對 edge process,把缺少的鄰居設成 MPI_PROC_NULL(process 0 的 left_neighbor、process np-2 的 right_neighbor),MPI runtime 會自動跳過該方向的送/收,因此不需特別的 if-then-else 分支:left_neighbor = (pid>0)?(pid-1):MPI_PROC_NULL。
Question 9 - Collective Communication 與 CUDA-aware MPI [recall]
情境/題目:列出常用的 collective communication 原語。
MPI_Barrier在 stencil 範例的用途為何?CUDA-aware MPI 移除了 halo 交換中的哪些步驟,哪些實作支援它?
Collective 原語:barrier、broadcast (MPI_Bcast)、reduce (MPI_Reduce)、gather (MPI_Gather)、scatter (MPI_Scatter),全 communicator 共同參與。MPI_Barrier 確保所有 compute process 都收到輸入、就緒後才一起開始交換 halo,避免落後者拖累全體(類比 __syncthreads() 但作用於 process)。CUDA-aware MPI 讓 MPI 直接讀寫 GPU memory,移除 halo 交換的 D2H/H2D staging copy 與 pinned buffer(每 iteration 省 4 次 copy),只需把 device 位址傳給 MPI_Sendrecv。支援的實作:MVAPICH2、IBM Platform MPI、OpenMPI。
Question 10 - Exercise 1:叢集 stencil 規模計算 [application]
情境/題目:64×64×2048 的 grid,17 個 MPI rank(16 個 compute + 1 個 data server)。求:(a) 每個 compute process 算幾個輸出點?(b) internal 與 edge process 各需多少 halo 點?(c) stage 1 各算多少 boundary 點?(d) stage 2 各算多少 internal 點?(e) stage 2 各送出多少 bytes?
num_comp = 16,S = 2048/16 = 128 片,一片 = 64×64 = 4096 點,float = 4 bytes。
(a) 每 compute process 輸出 = 4096×128 = 524,288 點。
(b) halo:internal 兩側各 4 片 = 8×4096 = 32,768;edge 單側 4 片 = 4×4096 = 16,384。
(c) stage 1 boundary:internal 8 片 = 32,768;edge 4 片 = 16,384。
(d) stage 2 internal:internal (128-8)×4096 = 491,520;edge (128-4)×4096 = 507,904。
(e) stage 2 送出:internal 兩側各 16,384×4 = 65,536 bytes → 131,072 bytes;edge 單側 65,536 bytes。
Question 11 - Exercise 2 & 3:元素大小與 blocking 語意 [application]
情境/題目:(a)
MPI_Send(ptr_a, 1000, MPI_FLOAT, 2000, 4, MPI_COMM_WORLD)共傳 4000 bytes,每個資料元素幾 bytes?(b) 下列何者為真:MPI_Send 預設 blocking / MPI_Recv 預設 blocking / MPI 訊息至少 128 bytes / MPI process 可透過 shared memory 存同一變數?
(a) 每元素 = 4000 / 1000 = 4 bytes(即 MPI_FLOAT,單精度浮點)。傳輸位元組 = count × sizeof(datatype)。
(b) 「MPI_Recv 預設是 blocking」 為真——MPI_Recv 回傳即代表資料已收到。其餘皆錯:MPI 無 128-byte 下限;MPI 是 distributed memory,process 不共享變數、無 shared memory。
Question 12 - Data Server 的 send_address 位移邏輯 [application]
情境/題目:data server 沿 z 切成 4 個 partition(P0…P3,每片
S個 z slice)。送完 P0 後send_address該前進多少?internal 迴圈中每次又前進多少?為何兩者不同?
送完 P0(edge,只含右 halo)後,前進 dimx*dimy*(S-4):因 P1 的 partition 從 input + S 起,但須退回 4 片納入左 halo,故淨位移 S-4 片。internal 迴圈中每次前進 dimx*dimy*S(一個完整 partition)。差別在於:第一次位移要從「無左 halo 的 edge」切換到「有左 halo 的 internal」對齊;之後每個 internal 起點都同樣退 4 片,退的效果一致抵銷,故相鄰起點淨距仍是一個 partition (S 片)。
Question 13 - 天真 vs 重疊策略的效能分析 [analysis]
情境/題目:比較「整片算完再換 halo」的天真策略與「stage1/stage2 重疊」策略在硬體利用率上的差異。重疊策略在什麼條件下能把通訊延遲完全藏住?若內部 slice 太少會怎樣?
天真策略把執行硬性分成兩相:全算階段→網路閒置,全換 halo 階段→運算硬體閒置,兩者輪流空轉,T ≈ T_compute + T_comm(相加)。重疊策略先算 boundary(stage1),再用 stream1 算 internal、同時用 stream0 + MPI 交換 halo,使網路與運算同時忙碌,T ≈ T_stage1 + max(T_internal_compute, T_halo_comm)。完全藏住通訊的條件:T_halo_comm < T_internal_compute,即內部 slice 夠多(dimz-8 夠大)讓內部運算時間蓋過通訊時間。若內部 slice 太少 → 內部運算太短,網路又變成瓶頸、通訊外露,退化回接近天真策略。
Question 14 - 傳統 staging MPI vs CUDA-aware MPI 的取捨 [analysis]
情境/題目:傳統(非 CUDA-aware)MPI 在每次 halo 交換要做哪些 staging copy?CUDA-aware MPI 省下什麼?換用 CUDA-aware 後是否還需要 CUDA streams 與兩段式 kernel?為什麼?
傳統 staging MPI 每次 halo 交換需 4 次 copy:送出前 2 次 D2H(cudaMemcpyAsync 把 left/right boundary 搬到 pinned host bounce buffer),收到後 2 次 H2D(把 halo 搬回 device),外加每 compute process 4 個 cudaHostAlloc pinned buffer。CUDA-aware MPI 讓 MPI_Sendrecv 直接收送 device 位址(GPUDirect),移除全部 4 次 staging copy 與 pinned buffer,簡化主機端程式碼與記憶體佈局。但仍需保留 CUDA streams 與兩段式 kernel:CUDA-aware 移除的是 staging copy,不是 overlap 策略——還是要先算 boundary、再算 internal,才能在 halo 一算完就立刻開始跨節點通訊,維持計算/通訊重疊。
Question 15 - Pageable vs Pinned 記憶體複製路徑分析 [analysis]
情境/題目:為何用一般
malloc(pageable)記憶體的cudaMemcpy會被強制成同步且較慢?pinned memory 如何同時解決「資料損毀」與「無法非同步」兩個問題?代價是什麼?
Pageable 路徑:DMA 只能在實體位址上搬,但 pageable 記憶體隨時可能被 OS 換出 → 為避免損毀,CUDA runtime 必須先把資料複製到內部 pinned 暫存再 DMA,多一次複製;且這個兩步流程被實作成 synchronous(host 必須等 copy 完才能繼續),所有複製被序列化。Pinned memory 標記為不可換出:DMA 可直接存取(免額外複製,解決速度)、且資料不會在搬運中被 page out(解決損毀),於是 cudaMemcpyAsync 的非同步語意才能成立,複製可與 kernel 並行。代價:pinned 記憶體不可換出,過度配置會壓縮系統可用實體記憶體、影響其他程序,因此只用於真正需要非同步傳輸的 bounce buffer。
| 主題 (節) | 核心 API / 機制 | 一句話重點 |
|---|---|---|
| 背景 / 基礎 (20.1–20.3) | MPI_Init/Comm_rank/Comm_size/Abort/Finalize |
distributed memory + SPMD;rank 分流,np-1 算、1 個 server |
| Point-to-point (20.4) | MPI_Send(6) / MPI_Recv(7) |
沿 z 切 partition,附 halo;edge +4、internal +8 |
| 重疊計算/通訊 (20.5) | streams + cudaMemcpyAsync + MPI_Sendrecv |
stage1 算 boundary、stage2 邊通訊邊算 internal |
| Pinned / DMA (20.5) | cudaHostAlloc / cudaFreeHost |
page-locked bounce buffer,DMA 安全且可非同步 |
| Collective (20.6) | MPI_Barrier/Bcast/Reduce/Gather/Scatter |
全 communicator 共同參與;能用 collective 就別手刻 |
| CUDA-aware MPI (20.7) | device pointer 傳給 MPI_Sendrecv |
省 4 次 staging copy + pinned buffer,但保留 overlap |
核心觀念鏈:單卡 CUDA 放大成多節點叢集 → domain partitioning(沿 z 切,一片 z slice 連續)→ point-to-point 分發 partition + halo → 兩階段 + streams + pinned 讓計算與通訊重疊 → double buffering 換 buffer → collective / CUDA-aware MPI 簡化協作與資料搬移。
關鍵公式:S = dimz/num_comp、partition = dimx*dimy*S、一側 halo = 4*dimx*dimy、internal 收 (S+8)/edge 收 (S+4)、bytes = count*sizeof(datatype)、T_iter ≈ T_stage1 + max(T_internal, T_comm)。
CUDA ↔ MPI 對照:SPMD ↔ 同 kernel、MPI rank ↔ blockIdx*blockDim+threadIdx(一維)、MPI_Barrier ↔ __syncthreads()、send/recv ↔ global memory 讀寫(但 MPI 不共享)。
一句話總結:joint CUDA/MPI 把 stencil 從單卡擴展到叢集,靠 domain partitioning 切資料、halo exchange 換資料,並用 streams + pinned memory(或 CUDA-aware MPI)把通訊藏進運算。