AI-LECTURER 速報課|2026-09-14|約 26 分鐘

沒有 NVIDIA 顯卡也能練 AI?工程師怎麼用「翻譯層」讓 AMD 顯卡假裝自己是 CUDA

取材:CUDA for AMD on Windows(Hacker News(AI 高人氣))
📍 真實場景
阿翔,小型遊戲外包工作室的技術美術(TA),資工系畢業但平常主要寫特效跟工具腳本

他想把公司拿來訓練「NPC 巡邏路徑」的強化學習模型,從公司舊筆電搬到自己新組的桌機上,利用下班時間加速訓練

😖 卡住的地方:他新組的桌機裝的是 AMD 顯卡(省了快一半預算),結果照官方教學裝 PyTorch,一執行就跳出「CUDA not available」,顯卡完全被晾在一邊,重買一張 NVIDIA 顯卡又要多花兩三萬
💡 這篇會告訴阿翔(也告訴你),一群工程師怎麼硬是做出一層「翻譯層」,讓只認 NVIDIA 的程式,在 AMD 顯卡上真的能跑、還能拿來訓練 AI

🧭 這到底是什麼(白話版)

如果你查過 AI 訓練的教學,大概常看到一句話:「請先安裝 CUDANVIDIA 顯卡專用的一套運算工具,讓顯卡能幫忙做大量數學計算,AI 模型訓練幾乎都靠它加速」。這其實是一種隱性綁架——幾乎所有主流 AI 訓練框架,例如 PyTorch,預設都只認 NVIDIA 家的這套系統。你手上如果是 AMD 顯卡,官方安裝指令裝出來的版本,跑起來就是直接說「找不到可用的運算裝置」。

這篇素材講的,是一群工程師自己動手解決這個困境:他們做出一套叫 ZLUDA一支扮演「翻譯官」的軟體,它假裝自己是 CUDA,實際上偷偷把每一個運算指令轉交給 AMD 顯卡的系統去處理 的翻譯層,讓原本寫給 NVIDIA 用的程式,不用改一行程式碼就能在 AMD 顯卡上執行。真正在背後負責運算的,是 AMD 官方提供的一套工具,叫 ROCm/HIPAMD 官方推出、專門讓 AMD 顯卡也能做 AI 運算與科學計算的軟體工具組,可以想成是「AMD 版的 CUDA」

光是「裝得起來」還不夠讓人放心,工程師還做了一次紮實的驗證:他們拿一個超過兩百二十萬個參數的 PPO一種強化學習演算法,讓 AI 透過不斷嘗試、犯錯、拿到獎勵分數來學會策略,常用來訓練遊戲 AI 或機器人 網路,在這套翻譯層上真的完成了 65,536 個時間步的訓練,證明這不只是「能開機」,是「真的能拿來訓練 AI」。

不過素材也很老實地說:目前只有在一款特定型號的顯卡(AMD Radeon RX 9060 XT)上完全驗證成功,其他型號「可能可以」,但沒人保證。這種先誠實劃出保證範圍、再邀請大家一起測試回報的做法,本身就是值得學的工程態度。

🎯 為什麼值得你花時間

省下真金白銀的升級成本AMD 顯卡通常比同等級的 NVIDIA 便宜不少。如果 AMD 顯卡也能拿來訓練 AI,等於幫預算有限的學生、獨立開發者、小工作室多開一條路,不用被迫多花錢換顯卡。
打破「一定要用 NVIDIA」的預設立場很多 AI 教學、框架、甚至公司內部訓練腳本,都直接假設你用 NVIDIA 顯卡。這個專案證明「相容層」這條路走得通,讓其他硬體廠商跟開源社群多了切入點,長期可能鬆動單一廠商在 AI 訓練市場的獨佔地位。
示範一種很實用的工程解法:不重寫,用「轉接頭」與其要求全世界的 AI 框架都重寫一份支援 AMD 的版本(成本高到不可能發生),不如做一個轉接頭,讓舊程式碼原封不動接上新硬體。這種思路不只適用顯卡,很多老系統接新技術的場景都能套。

⚙️ 它是怎麼運作的

1
認出你的顯卡是誰安裝腳本第一步會先偵測電腦裡裝的是哪張 AMD 顯卡,記下它的 gfx 架構代號AMD 顯卡依照晶片設計分類的代號,像身分證字號,不同代號的顯卡在底層計算指令上可能有差異,因為翻譯層要處理的細節,會依代號不同而不同。
2
確認地基有沒有打好檢查有沒有先裝好 AMD 官方驅動程式,還有一套叫 HIP SDK 的工具組跟裡面需要的數學運算函式庫。這些是翻譯層運作的地基,地基沒打好,後面全部會出錯。
3
下載版本固定的官方翻譯層本體去下載一個版本號被釘死(pinned)的官方 ZLUDA 版本,而不是隨便抓網路上流傳的版本,避免版本不合或被動過手腳的檔案。
4
順便準備好訓練 AI 要用的核心工具下載 LibTorch(PyTorch 拿掉 Python 介面、給程式直接呼叫的核心版本),檔案大約 2.66 GB,這是真正拿來訓練 AI 模型的部分。
5
核對檔案指紋,防止被動手腳下載完,腳本會拿 SHA-256 雜湊把檔案內容做一次數學運算,產生一串固定長度的「指紋」,用來確認下載回來的檔案沒有被竄改或半途損毀 去核對,指紋對不上就代表檔案有問題,不能用。
6
實際跑一次驗證,不是嘴巴說說全部裝好後,執行官方附的 cuda_check.exe 小工具,實際跑一次運算,確認翻譯層真的有把指令正確送到 AMD 顯卡上執行,而不是裝完就假設「應該可以」。
CUDA 生態系 vs. 這套翻譯層方案裡各角色的對應關係
原本要找的 NVIDIA 元件這套方案裡實際接手的角色扮演的翻譯功能
CUDA Driver APIZLUDA偽裝成 NVIDIA 驅動,接收原本要給 NVIDIA 的指令
cuBLAS / cuSPARSE / cuFFT(NVIDIA 數學運算庫)ZLUDA 相容層把呼叫轉換成 AMD 看得懂的格式,再轉交出去
實際執行運算的 GPU 底層rocBLAS / hipBLASLt / rocSPARSE / HIP(AMD 官方工具組)真正在 AMD 顯卡上完成數學運算

🔍 程式碼漫遊(點有 ● 的行看白話講解)

下面是簡化過的示意版本,說明官方安裝腳本 install.ps1 大致分幾步在做事(真正的腳本內容更完整,這裡只抓出素材有提到的關鍵動作,方便你看懂邏輯,不是逐字照抄原始碼)。

$gpu = Get-AmdGpuInfo
💬 先問系統,你裝的是哪張 AMD 顯卡、對應的 gfx 架構代號是什麼。
Test-HipSdkInstalled -MinVersion '6.4'
💬 檢查 HIP SDK 有沒有裝、版本夠不夠新,這是翻譯層能運作的地基。
Invoke-WebRequest $ZludaPinnedUrl -OutFile zluda.zip
💬 下載版本號固定(pinned)的官方 ZLUDA 版本,不抓來路不明的檔案。
Invoke-WebRequest $LibTorchUrl -OutFile libtorch.zip
💬 下載 LibTorch(約 2.66 GB),這是實際拿來訓練 AI 模型用的核心工具。
if ((Get-FileHash libtorch.zip -Algorithm SHA256).Hash -ne $expectedHash) { throw '雜湊核對失敗' }
💬 核對下載檔案的 SHA-256 指紋,對不上就直接中止,不讓有問題的檔案繼續往下裝。
New-RuntimeConfig -Gpu $gpu | Out-File runtime-config.json
💬 把偵測到的顯卡資訊寫成設定檔,讓翻譯層知道要用哪一套底層指令。
& .\cuda_check.exe
💬 實際執行官方附的檢查工具,跑一次真的運算,驗證翻譯層有沒有正常運作。

🛠️ 動手做:動手做:打造一個「CUDA 翻譯層」玩具版,體會轉接的原理

這是一個真實小專案:下載(或複製)檔案,照步驟在你電腦上跑起來。

📄 shim_demo.py ⬇ 下載
# -*- coding: utf-8 -*-
"""
shim_demo.py
玩具版「CUDA 翻譯層」示範:模擬 ZLUDA 如何把「寫給 NVIDIA 的呼叫」
轉譯成「AMD 顯卡看得懂的指令」,並用一個迷你神經網路前向計算
當作驗證,呼應素材裡用 PPO 網路做真實驗證的做法。
"""

import hashlib
import random
import time

# 素材裡驗證過的顯卡型號,只有這張是「保證能動」
VALIDATED_GPU = "AMD Radeon RX 9060 XT (gfx1200)"

# 目前你的「顯卡」(可以自己改這行,模擬換一張卡會發生什麼事)
CURRENT_GPU = "AMD Radeon RX 9060 XT (gfx1200)"


def rocblas_gemm(a, b):
    """AMD 官方數學運算庫做矩陣乘法,真正動手算的人。"""
    rows_a, cols_a = len(a), len(a[0])
    cols_b = len(b[0])
    result = [[0.0] * cols_b for _ in range(rows_a)]
    for i in range(rows_a):
        for j in range(cols_b):
            result[i][j] = sum(a[i][k] * b[k][j] for k in range(cols_a))
    return result


def rocblas_relu(matrix):
    return [[max(0.0, v) for v in row] for row in matrix]


# 翻譯表:左邊是程式「以為」自己在呼叫的 CUDA 函式,
# 右邊是真正負責執行的 AMD 函式(呼應素材裡的架構圖)
TRANSLATION_TABLE = {
    "cublasGemm": rocblas_gemm,
    "cublasRelu": rocblas_relu,
}


def cuda_call(cuda_func_name, *args):
    """
    這支函式假裝自己是 CUDA API。
    任何程式呼叫它,其實都會被偷偷轉發到 AMD 的實作。
    這就是 ZLUDA 在素材裡做的「翻譯層」角色。
    """
    if cuda_func_name not in TRANSLATION_TABLE:
        raise NotImplementedError(f"這個翻譯層還沒學會翻譯:{cuda_func_name}")

    backend_func = TRANSLATION_TABLE[cuda_func_name]
    start = time.perf_counter()
    result = backend_func(*args)
    elapsed_ms = (time.perf_counter() - start) * 1000
    print(f"[翻譯層] 收到 CUDA 呼叫 {cuda_func_name} -> 轉交給 AMD 後端執行,耗時 {elapsed_ms:.3f} ms")
    return result


def verify_file_integrity(fake_file_bytes, expected_hash):
    """模擬素材裡下載完 LibTorch 後核對 SHA-256 雜湊的步驟。"""
    actual_hash = hashlib.sha256(fake_file_bytes).hexdigest()
    return actual_hash == expected_hash


def toy_ppo_forward_pass():
    """
    迷你版「強化學習網路前向計算」,呼應素材裡
    2,216,347 個參數的 PPO 網路做 forward/inference 驗證的做法,
    這裡把參數量縮小到肉眼看得懂的程度。
    """
    random.seed(6)
    # 假裝這是「觀察到的環境狀態」,例如遊戲 NPC 看到的 3 個數值
    state = [[0.5, -0.2, 0.8]]
    weights_1 = [[random.uniform(-1, 1) for _ in range(4)] for _ in range(3)]
    weights_2 = [[random.uniform(-1, 1) for _ in range(2)] for _ in range(4)]

    hidden = cuda_call("cublasGemm", state, weights_1)
    hidden = cuda_call("cublasRelu", hidden)
    action_scores = cuda_call("cublasGemm", hidden, weights_2)
    return action_scores


def main():
    print(f"驗證用的顯卡型號:{VALIDATED_GPU}")
    print(f"目前偵測到的顯卡型號:{CURRENT_GPU}")

    if CURRENT_GPU != VALIDATED_GPU:
        print("警告:這張顯卡沒有在素材裡被驗證過,接下來的結果只能參考,不保證正確。")

    print("\n--- 第一步:模擬下載檔案,核對雜湊,防止檔案被動手腳 ---")
    fake_file = b"pretend-this-is-a-2.66GB-libtorch-package"
    expected_hash = hashlib.sha256(fake_file).hexdigest()
    ok = verify_file_integrity(fake_file, expected_hash)
    print(f"雜湊核對結果:{'通過' if ok else '失敗,中止安裝'}")

    print("\n--- 第二步:透過翻譯層執行一次迷你 PPO 網路前向計算 ---")
    scores = toy_ppo_forward_pass()
    print(f"網路輸出的行動分數:{scores}")

    print("\n--- 驗證結論 ---")
    print("所有翻譯呼叫都成功轉交給 AMD 後端執行,沒有丟出例外,")
    print("這就是素材裡「不只是能開機,是真的能訓練 AI」的縮小版證明。")


if __name__ == "__main__":
    main()
  1. 在 D 槽(或任何你熟悉的位置)新增一個資料夾,例如 D:\shim_demo,把下面的 shim_demo.py 內容存進去。
  2. 按 Win+R,輸入 powershell,開啟 PowerShell。
  3. 輸入 cd D:\shim_demo,切換到剛剛存放 shim_demo.py 的資料夾(路徑請換成你自己的位置)。
  4. 輸入 python shim_demo.py 執行(如果出現「找不到 python」,先確認有安裝 Python,或改用 py shim_demo.py)。
  5. 觀察畫面上每一行「[翻譯層] 收到 CUDA 呼叫...」,這就是在模擬翻譯層偷偷把呼叫轉發給 AMD 後端的過程。
  6. 試著把程式裡 CURRENT_GPU 那一行改成別的顯卡型號字串,重新執行一次,感受素材裡「只有一款顯卡被完全驗證過」是什麼意思。

🧠 工程思維透鏡(資深工程師看到的是什麼)

🔭 為什麼工程師選擇做一層「翻譯層」,而不是說服官方框架直接支援 AMD?
說服官方框架改版需要多方廠商配合,時程完全不可控;「翻譯層」只需要一個人或一個小團隊,鎖定「把呼叫轉發到對的地方」這一件事就好。這是資源不對等下的典型工程策略:無法改變上游,就在自己能控制的那一層插入相容層,用局部的努力換到整體可用的結果。
🔭 為什麼素材要特別強調「目前只驗證過一款顯卡」,而不是直接宣稱「支援所有 AMD 顯卡」?
不同 gfx 架構代號的顯卡,底層指令集可能有落差,貿然宣稱「全面支援」,一旦有人試了失敗,會讓整個專案的可信度崩掉。誠實劃出「目前保證的範圍」,再開放大家回報其他型號的相容性報告,是用眾包驗證分攤風險,也保護了專案本身的信譽。
🔭 下載檔案還要多一道 SHA-256 雜湊核對,是不是多此一舉?
這套流程會下載近 3GB 的官方執行檔跟函式庫,一旦下載過程被中間人竄改、或抓到假冒版本,使用者等於在自己電腦上執行了來路不明的程式碼,是典型的 供應鏈攻擊駭客不直接攻擊你,而是在你會用到的工具、函式庫、安裝包裡動手腳,等你自己把有問題的東西裝進電腦 破口。核對雜湊只多花幾秒鐘,卻能擋掉這整類風險,是低成本高報酬的工程習慣。
🔭 為什麼驗證方式選「訓練一個真的 PPO 網路跑 65,536 步」,而不是隨便印一句話證明程式沒當掉?
「能不能開機」跟「能不能真的拿來做事」是兩個完全不同層級的問題。如果只驗證程式沒有當掉,很可能漏掉「算出來的數字是錯的」這種更隱藏、更危險的問題。用一個真實會被拿來訓練 AI 的工作負載去驗證,才能確保這套翻譯層不只是「看起來能動」,而是「數學算得對、拿來訓練真的有效」。

📝 隨堂考(點選答案,立即回饋)

Q1. 在這篇素材的架構裡,ZLUDA 扮演的角色最接近下面哪一個?
✅ ZLUDA 本身不負責真正算數學,它的工作是接住原本要送給 NVIDIA 的呼叫,轉手交給 AMD 這邊的 rocBLAS / HIP 等工具去實際執行。
Q2. 素材裡提到目前「驗證過」能正常運作的顯卡型號是?
✅ 素材明講目前驗證過的硬體只有 AMD Radeon RX 9060 XT (gfx1200) 這一款,其他型號是候選,沒有保證。
Q3. 為什麼安裝流程要核對下載檔案的 SHA-256 雜湊?
✅ 雜湊核對是用來確認檔案完整性與真實性,如果雜湊對不上,代表檔案可能在傳輸過程中壞掉或被動過手腳,不能拿來用。
Q4. 文中拿來驗證「翻譯層真的能訓練 AI」的做法是?
✅ 素材明確提到用一個真實規模的 PPO 強化學習網路完成前向、學習、優化器運作,並完成 65,536 個時間步,這是實際工作負載等級的驗證,不是紙上談兵。
Q5. 如果你的 AMD 顯卡型號不在「已驗證」清單上,最合理的態度是?
✅ 素材鼓勵大家測試未驗證的型號並回報相容性結果,這是開源專案擴大驗證範圍常見的做法,但使用者要清楚知道這是「候選、非保證」的狀態。

🃏 翻牌記憶卡(先想答案,再點開對答)

CUDA點我翻面
NVIDIA 顯卡專用的運算工具組,AI 訓練框架大多預設只支援它。
ZLUDA點我翻面
讓程式以為自己在跟 CUDA 溝通,其實偷偷把呼叫轉發給其他廠牌顯卡執行的翻譯層。
ROCm / HIP點我翻面
AMD 官方提供、可以想成「AMD 版 CUDA」的運算工具組。
gfx 架構代號點我翻面
AMD 顯卡依晶片設計分類的代號,像身分證字號,不同代號底層指令可能不同。
SHA-256 雜湊核對點我翻面
用數學運算幫檔案產生指紋,確認下載回來的檔案沒被竄改或損毀。
PPO點我翻面
一種讓 AI 透過反覆嘗試、拿獎勵分數學會策略的強化學習演算法。

✅ 離開前自測(全勾=這課真的學會了)

0%