版本說明:本文依據 Stanford CS149 Fall 2025 版:Assignment 3: A Simple CUDA Renderer(截止 10 月 30 日)與 Written Assignment 2(課程首頁列的日期是 10 月 21 日),2026-09-30 打開 README、
cloud_readme.md與 PDF 核對。存取等級 A3:題目、起始程式碼、評分腳本、書面作業 PDF 都公開;拿不到的是 Gradescope 評分、官方 AWS 額度與解答。需要自備 NVIDIA GPU。本文不提供任何解答。
系列位置:上一篇 第 8 講:資料平行思維|下一篇 第 9 講:在 GPU 上跑 DNN|系列總覽
第 7 講 講 CUDA 抽象怎麼落到 GPU,第 8 講 講怎麼用 scan、sort 這類原語取代鎖。PA3 把兩者一起考:README 開頭說這個渲染器很簡單,但把它平行化需要你設計並實作能被平行建構與操作的資料結構。接著用粗體重複了一次:真的要早點開始。
課程裡的位置與環境
PA3 在 10 月 30 日截止,前面剛上完第 7、8 講。依 course info,程式作業可以兩人一組,PA3 占總成績 12%;Written 2 必須三人一組、由助教隨機分組,每份書面作業占 3%。
環境:README 說效能測試要在 AWS 的 GPU 虛擬機上跑,並提到作業用 NVIDIA T4(compute capability 7.5)。cloud_readme.md 寫得更具體:
- instance 類型
g5g.xlarge,nvidia-smi範例顯示的 GPU 是 NVIDIA T4G,CUDA 12.8。 - AMI 是 Community AMIs 裡的
Deep Learning ARM64 Base OSS Nvidia Driver GPU AMI (Ubuntu 22.04) 20250613,磁碟開 65 GiB。 - 另外要裝
freeglut3-dev。 - 課程會發 AWS 學生額度,文件反覆提醒用完要關機。
這對校外自學者的意義是:AMI 是公開的社群 AMI,理論上可以自費照同樣步驟開機,這點和 PA4 那種課程私有 AMI 不同(我沒有實際開過)。或者用手邊的 NVIDIA GPU:repo 裡的參考解同時附了 ARM64 版(render_ref、cudaScan_ref)和 x86 版(render_ref_x86、cudaScan_ref_x86),render/checker.py 會依 platform.machine() 自動挑。checker.py 的分數是跟同一台機器上的參考解相比,所以換機器後分數仍有意義,只是時間數字無法跟 T4 上的直接比。
Part 1:SAXPY 暖身(5 分)
把 PA1 Program 5 的 SAXPY 改寫成 CUDA:配置 device 記憶體、把輸入複製過去、跑 kernel、把結果複製回來。
重點在計時。起始程式碼已經量了「複製過去 + kernel + 複製回來」的總時間,你要另外只量 kernel。README 特別警告:kernel launch 預設和 CPU 是非同步的,直接在 launch 前後讀時鐘,只會量到 API 呼叫本身,看起來快得驚人。要在 launch 後呼叫 cudaDeviceSynchronize()。cudaMemcpy() 在作業的用法下是同步的,不需要再同步。
兩個問題:
- 跟 PA1 的循序 CPU 版比,效能如何?
- 兩組計時差在哪?觀察到的頻寬跟機器各部分的規格大致吻合嗎?
README 給了一個線索:AWS 上記憶體匯流排的預期頻寬是 5.3 GB/s,跟 16 lane PCIe 3.0 的規格不符,原因包括主機板晶片組效能,以及來源的 host 記憶體是否 pinned。這一題真正要你看到的,是 第 6 講 那套 arithmetic intensity 的推論在 GPU 上的樣子:SAXPY 每個元素只做一次乘加,資料搬運才是大頭。
Part 2:prefix sum 與 find_repeats(10 分)
find_repeats 給一個整數陣列 A,回傳所有滿足 A[i] == A[i+1] 的 i。README 的例子:{1,2,2,1,1,1,3,5,3,3} 輸出 {1,3,4,8}。
規定做法是先實作平行 exclusive scan,再用它完成 find_repeats。README 附的虛擬碼就是 第 8 講投影片 那個 up-sweep / down-sweep 演算法,每個 parallel_for 對應一次 kernel launch。起始程式碼把陣列長度補到 2 的冪次,只複製 N 個元素回來,所以你可以只處理 2 的冪次。
讀題要注意的幾件事:
- 評分只看效能,而且要在參考解的 20% 以內才拿滿分。README 附的 scan 分數表是 K80 上簡單 CUDA 實作的時間。
- README 說這部分重點是練 CUDA 與資料平行思考,不是調效能,直接照虛擬碼移植應該就夠。只有一個陷阱:每一輪都開 N 個 thread、再用條件判斷誰該做事,效能會很差(想想 up-sweep 最後一輪只有兩個 thread 有事做)。滿分解只為最內層平行迴圈的每一次迭代開一個 thread。
- 評分時用
-i random跑隨機輸入。--thrust可以跑 Thrust 的實作當對照,做到跟 Thrust 有競爭力可以拿最多 2 分額外加分。
Part 3:圓形渲染器(85 分)
題目在問什麼
渲染器輸入一組圓(3D 位置、速度、半徑、顏色),每一格畫面的循序演算法是:清空影像;更新每個圓的位置;對每個圓算出螢幕上的 bounding box,對 box 裡每個像素,若像素中心在圓內,就算出顏色並混進這個像素。
關鍵在「混進」。圓是半透明的,用 RGBA 表示,疊色公式是 result = C_alpha * C + (1 - C_alpha) * P。README 強調這個合成不可交換:X 疊在 Y 上跟 Y 疊在 X 上看起來不一樣,所以必須照應用程式給的順序畫(可以假設輸入已按深度排好)。
起始程式碼有一個循序 C++ 參考版 refRenderer.cpp,和一個錯誤的 CUDA 版 cudaRenderer.cu:它一個 CUDA thread 負責一個圓,數學完全正確,卻有兩個重大錯誤。README 說跑 rgb 和 circles 場景會看到每一格都在變動的水平條紋。
兩條不變式
你的 CUDA 渲染器必須守住兩件在循序版裡自動成立的事:
- Atomicity:每次影像更新都要是 atomic。臨界區包括讀出像素的四個 32-bit float(RGBA)、混入目前的圓、寫回。
- Order:同一個像素的更新必須照圓的輸入順序。README 用粗體點出一個關鍵觀察:順序只約束同一個像素的更新;不碰同一個像素的圓之間沒有順序要求,可以獨立處理。
沒同時守住兩條的解法,Part 3 最多只拿 12 分。README 補了一句:「我們已經給你這樣的解法了!」
README 建議的節奏與提示
README 建議三步:先把起始程式碼改成邏輯正確(建議用不需要鎖或同步的做法);再找出你的解法效能問題在哪;然後「真正的思考才開始」。
提示整理(這些是 README 原本就給的方向,不是解答):
- 有兩條平行軸:跨像素與跨圓(跨圓要守順序)。README 說解法需要兩種都用上,可能在計算的不同階段。
circleBoxTest.cu_inl裡的圓與方框相交測試是你的朋友。exclusiveScan.cu_inl提供了 shared memory 裡的 exclusive scan,但只適用 2 的冪次長度,而且 block 的 thread 數必須等於陣列長度,README 用全大寫要你讀註解。shadePixel每次更新都做好幾次 global memory 操作,可以考慮在暫存器裡累加,最後只寫一次。- 可以用 Thrust,但達到參考效能不需要它。README 說常見解法一種用它給的 shared memory scan,另一種用 Thrust 的 prefix sum,兩種都合法。
- 渲染器裡有資料重用嗎?怎麼利用?
- CUDA 沒有能 atomic 完成整個像素更新的原語。用 global memory atomic 做一把鎖是一種辦法,但就算 atomic 了,順序還是得對。README 的建議是先想順序,再考慮 atomicity,如果那時它還是問題的話。
rand1M和micro2M圓很多,暫存結構小心別吃光 device 記憶體。沒檢查cudaMalloc的回傳值,程式會照跑,然後在正確性檢查失敗。README 附了一個cudaCheckError巨集,建議除錯時把所有 CUDA API 呼叫都包起來;kernel launch 本身不能包,錯誤會在下一個被包住的呼叫才出現。
評分
./checker.py 會在你的機器上同時跑參考解 render_ref 和你的版本。八個計分場景是 rgb、rand10k、rand100k、pattern、snowsingle、biglittle、rand1M、micro2M,每個 9 分:
- 2 分正確性(只測 256 倍數的影像大小)。
- 7 分效能,只有正確時才拿得到:T ≤ 1.2 × T_ref 滿分;T 是 T_ref 十倍以上零分;中間用
7 × T_ref / T計分。
合計 72 分。另外交一份 write-up:分工方式(怎麼分給 block、thread,甚至 warp)、同步發生在哪、做了什麼降低通訊、試過哪些方法、用什麼量測引導最佳化。額外加分最多 10 分:效能顯著超過要求的解最多 5 分,高品質的純 CPU 平行渲染器(要分析 GPU 與 CPU 版差異)最多 5 分。
README 的配分有兩處自相矛盾,讀的時候注意:Grading Guidelines 寫 write-up 18 分、prefix sum 10 分;緊接著的總表寫 Part 3 write-up 13 分,加上 Part 1 的 5 分;Part 2 的範例分數表又顯示 scan 滿分是 5 分(find_repeats 應是另一半)。以總表加總是 5 + 10 + 13 + 72 = 100 分,跟標題的 100 分一致。
Written 2:五題計分題在練什麼
Written Assignment 2 共 43 頁,前半是五道計分題,每題 20 分;後半是 14 題 PRACTICE PROBLEM。這裡只說每題在考哪個概念,不給答案。
| 題目 | 評分方式 | 在練什麼 |
|---|---|---|
| 1. To Fuse or Not to Fuse | 正確性 | 一串 parallel_for 中間夾一個循序的 max 迴圈。A 問:無限核心、無限頻寬下能否拿到 20 倍加速(Amdahl 定律)。B 換成 10 核、4 GB/s 頻寬、256 KB 共享 cache 的機器,可以改寫程式並用 atomicMax,求執行時間。提示直接問:這支程式是算力受限還是頻寬受限?哪裡能 fusion、哪個相依阻止 fusion? |
| 2. SPMD 與 SIMD | 正確性 | A:gang 大小 8 的 ISPC 程式有巢狀 if,找出讓 SIMD 利用率最差的輸入。B:1024 個 CUDA thread 各自隨機走一條深度 10 的二元樹路徑,warp 32,求 SIMD 利用率 |
| 3. A Barrier is Worth a 1000 Locks | 正確性 | 4 個 thread 算 10 個 bin 的直方圖,每次加一都搶同一把鎖。A 問效能問題在哪;B 要求不用鎖、只用一次 barrier() 重寫 |
| 4. 圖的資料平行思維 | 只看努力 | 用 CSR 形式的圖(edgeStarts、edges)算每個頂點鄰居的平均,只准用 gather、scatter、inclusive segmented scan、shiftLeft 與 map。題目說這是課堂 grid solver 的圖版本,也是 PageRank 的常見操作 |
| 5. 粒子模擬 | 只看努力 | 雙核跑 O(N²) 重力計算,利用對稱性只算 N²/2 次,卻做 N² 次加鎖。A 在不加變數、不改分工下把鎖的次數減半;B 找出另一個跟鎖次數無關的主要效能問題並提出解法 |
第 3、4 題幾乎就是第 8 講的延伸:第 3 題是「部分結果再合併」取代全域鎖,第 4 題是稀疏矩陣乘法那套 gather + segmented scan 的圖版本。
14 題練習題涵蓋更廣:用 sort/scatter/transpose 消除 ISPC 的 SIMD divergence、CUDA 走二元樹的 divergence、ISPC foreach 與 Cilk cilk_spawn 的抽象與實作差異、cache 與 LRU、Amdahl 定律、roofline 圖、非同步訊息傳遞、pipeline 等。它們沒有計分,但是很好的期中考複習材料。
自學怎麼做
- 沒有 GPU 的話,先決定用哪台機器:照
cloud_readme.md自費開g5g.xlarge,或用任何 NVIDIA GPU。記得每次用完關機。 - Part 1、2 當暖身,目標是熟悉
cudaMalloc、cudaMemcpy、kernel launch 與cudaDeviceSynchronize(),以及把第 8 講的 scan 虛擬碼變成可執行的 CUDA。 - Part 3 先跑
./render -r cuda rand10k和./render -r cpuref rand10k比對,親眼看到錯在哪。 - 動手前先在紙上寫下:你的平行化單位是像素、圓、還是畫面上的區塊?每個 thread 怎麼知道要處理哪些圓?順序怎麼保證?
- Written 2 可以跟 PA3 同時做,第 3、4 題的思路直接可以用在渲染器上。
今晚可以做的一件事:clone asst3,只讀 cudaRenderer.cu 裡的 kernelRenderCircles,用一句話寫下它為什麼同時違反 atomicity 與 order。
延伸閱讀
- 另一門課的 CUDA 入門作業:CMU 11-868 作業一:用 CUDA 寫 MiniTorch 的 map、zip、reduce 與 matmul
- thread、block 與 shared memory tiling 的另一種講法:CMU 11-868 L02–L04 GPU 程式模型與加速
- 五個作業的環境需求總表:Stanford CS149 導讀(系列總覽)
參考資料
- stanford-cs149/asst3 README — 三部分題目、配分、提示與 checker
- asst3 cloud_readme.md — AWS
g5g.xlarge、AMI、T4G 與 CUDA 12.8 - Written Assignment 2(PDF) — 五道計分題與 14 道練習題
- Stanford CS149 Fall 2025 課程首頁 — PA3 與 Written 2 的日期
- CS149 Fall 2025 Course Info — 分組規定與成績比重
- Lecture 8 slide 17(work-efficient scan) — README 引用的 scan 演算法
- NVIDIA CUDA C++ Programming Guide — README 推薦的參考手冊,含 compute capability 規格表
- CUDA Runtime API:memcpy 的同步行為 — README 對
cudaMemcpy同步性的說明來源
Loading...