跳至主要內容

裝置後端

CodefyUI 執行於 PyTorch 之上,因此繼承了 PyTorch 的裝置後端:CPU、NVIDIA CUDA、Apple Silicon(MPS) 與 AMD ROCm(Linux)。關於安裝正確的 wheel,請參閱 GPU 與裝置設定;本頁說明裝置選擇在執行時的行為。

裝置選擇​

預設是 CPU,而且不會有任何機制替你切走。 一次執行依下列順序決定裝置,先命中者為準:

  1. 節點自己的 device 參數,且不是 auto 時(位於「進階」)。auto 會依序落到下面的圖裝置與設定。它的說明是:「此節點的裝置。auto 跟隨這次執行的裝置。送到 worker 且拿到多個裝置的執行,會依列出的順序把它們看成 cuda:0..N-1,所以可以用這個位置把節點固定在其中任一個。」在 worker 上拿到多張卡的 run,會依目標列出的順序把它們看成 cuda:0 到 cuda:N-1(見 cdui agent),所以在拿到 cuda:0 與 cuda:2 的 run 裡,設成 cuda:1 的節點會跑在這台機器的 cuda:2。參數值不會被改寫,重新編號是 run 的 CUDA_VISIBLE_DEVICES 做的。
  2. 圖自己的裝置,也就是圖檔裡的 settings.device(格式見下方),由執行旁邊的裝置選單設定。它會跟著圖一起存檔,所以 git 會追蹤它,圖在哪裡打開都跑在同一個裝置上。選單的第一個選項(設定為 CPU 時是跟隨設定(CPU))會讓圖不指定自己的裝置。
  3. 設定裡的裝置(這個瀏覽器記住的值,改動前是 cpu)。新圖與尚未指定裝置的圖會使用它;設定面板也會提示這台伺服器能看到的最佳裝置。設定的裝置若不在這台伺服器的清單上,兩個選單都會標示出來;參見裝置無法使用時。
  4. cpu:請求完全沒有指定裝置時伺服器採用的值。

設定、圖的控制項與節點參數三個下拉選單列的都是同一份清單,也就是 PyTorch 實際看得到的裝置(device_utils.describe_accelerator()),多卡機器上也包含每張卡的 cuda:N。被請求的裝置會與可用的裝置比對,若不存在則退回 CPU 並發出警告(超出範圍的 cuda:N 例外,會改用目前的 CUDA 裝置,見下方)。只有明確指定 auto(cdui run 或匯出腳本的 --device auto、run API 的 "device": "auto")才會解析成目前最好的加速器。更改圖的裝置會讓互動快取失效,所有節點都會重新執行。

圖的 settings 物件​

圖自己的裝置與亂數種子存在圖 JSON 最上層的一個選用物件中,也就是 settings.device 與 settings.seed:"settings": {"device": "cuda:1", "seed": 0}。

  • 可用值:cpu、auto、cuda、cuda:N、mps 或 mps:N,不分大小寫。空字串代表未指定。Mac 只有一個 MPS 裝置,所以 mps:N 會在 mps 上執行。
  • 只在有指定時寫入。 存檔只在圖有指定裝置或種子時寫入 settings,而且只寫有設定的鍵(settings.seed 為 0 也算),所以兩者都沒有的圖會與原本逐位元組相同。在專案目錄中,它寫在受 git 追蹤的 graphs/<name>.graph.json,不在 layout 檔。
  • 其他值一律拒絕:POST /api/graph/save、/api/graph/validate 與 /api/graph/export 回傳 422;POST /api/runs 與 POST /api/sweeps 回傳 400(因此 cdui run 會回報提交失敗);畫布執行這張圖時會收到 execution_error 訊框;cdui project validate 會回報 invalid_settings。離線執行器(backend/run_graph.py)、POST /api/graph/run/{name} 與 POST /api/apps/{slug}/invoke 只在呼叫沒有指定裝置(--device 或 body 的 device)時讀取 settings.device;此時會忽略無效值、記錄警告,並在 CPU 上執行。
  • 種子:settings.seed 是 0 到 4294967295 的整數;其他值會讓存檔、驗證與匯出回傳 422。它就是分頁的亂數種子(設定 → 訓練行為):存檔會寫入它,開啟與匯入會用它設定分頁的種子,匯出 Python 也會把它寫進腳本。沒有 settings.seed 的檔案(例如 2.8.9 之前存的圖)不會改動分頁原本的種子。無介面執行讀取它的方式與 settings.device 相同:backend/run_graph.py、cdui run、POST /api/runs、POST /api/graph/run/{name} 與 POST /api/apps/{slug}/invoke 在呼叫沒有指定種子時使用圖檔的種子。明確指定的 --seed N 或 "seed": N 優先;--no-seed 或 "seed": null 會讓有種子的圖以不設種子的方式執行。sweep 的 variant 例外:它們的種子由 sweep 管理(sweep_spec.seed 搭配 seed_variants),因此不讀 settings.seed。POST /api/runs 與 POST /api/sweeps 會以 400 拒絕無效的 settings.seed;離線執行器與兩個函式路由則記錄警告後忽略它,不設種子執行。

不帶 --device 的 cdui run 會把圖檔的裝置顯示為 <device> (graph);不帶 --seed 時,會把圖檔的種子顯示為 <seed> (graph),run_graph.py 的日誌也一樣。

裝置對齊由引擎保證​

你不需要去推敲某個張量到底在哪個裝置上。節點執行前,graph_engine.invoke_node 會把它輸入裡的每個張量搬到該節點要跑的裝置——節點自己宣告了 device 參數且不是 auto 時就用它的,否則用這次執行的裝置(依上述順序解析)。由於所有進入節點的路徑都經過那一個函式,這個保證同時涵蓋內建節點、外掛節點與你自己的自訂節點。

這項機制很重要,因為只有 CPU 的開發環境不會發生裝置不一致,問題通常到其他人的 GPU 機器上才會出現。在輸入對齊移入引擎前,已有兩個隨附 graph 因此失敗,錯誤為 Input type (torch.FloatTensor) and weight type (torch.cuda.FloatTensor) should be the same。

對齊刻意不碰的東西:

  • 模組。 nn.Module.to() 會就地修改模組,因此搬移從其他節點收到的模型,可能會在擁有該模型的節點不知情時改變權重所在裝置。需要讓模型使用自身裝置的節點,必須明確呼叫 to_device。
  • Dataset、DataLoader、環境等非張量值。 它們原樣通過,所以 dataset 維持惰性,TrainingLoop 仍然是一次一個 batch 串流到 GPU,而不是整份常駐 VRAM。
  • 宣告了 align_inputs = False 的節點。 本質上就在 host 端做事的節點——直接把輸入交給 numpy、sklearn、matplotlib 或 PIL——會選擇退出,因為 Tensor.numpy() 在非 CPU 上會直接丟例外。內建的例子是 TrainTestSplit。你自己的節點若是同一類,就寫上 align_inputs = False;參見自訂節點。
  • 節點在自己的 execute 中建立的張量。 對齊只處理節點輸入,不會處理節點內部建立的 torch.zeros(...)。若新張量要與輸入張量一起運算,請用 device=<the input>.device 建立。

:::note 匯出的腳本也遵守同一條規則 不帶 --device 執行 python graph.py 時,會用圖存檔時的裝置(settings.device),沒有的話就是 CPU,也就是設定為 cpu 時 canvas 對這張圖給的答案。想在執行的那台機器上用最好的加速器請傳 --device auto,想釘在某一張卡請傳 --device cuda:1。 :::

在多張卡之中指定其中一張​

在有一張以上 CUDA 裝置的機器上,每個下拉選單也會逐張列出——cuda:0、cuda:1 等等——並與單純的 cuda 並列,後者代表「torch 目前指向的那一張」。只有一張 GPU 的機器只會顯示 cuda,因為在那裡兩者指的是同一塊硬體。

指定了這台機器上不存在的編號時,會退回目前的 CUDA 裝置而不是 CPU:一張在工作站上釘在 cuda:2 的圖,在筆電上打開時仍然應該用 GPU 訓練。每張卡也各自有自己的執行佇列。完整說明(包含多張 GPU 帶來的改變)請參閱 訓練記憶體。

:::note 裝置集合 送到 worker agent 的 queued run 可以指定同一種類的多張卡,也就是 target.devices(cdui run 的 --devices cuda:2,cuda:0)。run 的子程序以 CUDA_VISIBLE_DEVICES 拿到這些卡,並依列出的順序看成 cuda:0 到 cuda:N-1,所以 device 設為 cuda:1 的節點會在集合中的第二張卡上執行。節點的值從不被改寫,上面的規則在 run 內照樣適用。在伺服器程序內執行的 run 只用集合中的第一個裝置。worker run 中 strategy: ddp 的 TrainingLoop 會在兩張以上的集合中使用每一張卡,每張卡一個程序(多 GPU 執行)。匯出的腳本只接受一個 --device;要讓它使用部分的卡,請在啟動前自行設定 CUDA_VISIBLE_DEVICES。請參閱多 GPU 執行。 :::

float64 + MPS 的限制​

MPS 是 float32 原生的,會拒絕 float64 張量。CodefyUI 在 device_utils.to_device 中將其正規化,但如果你撰寫一個直接建立張量的自訂節點,請在 Apple GPU 上將它們維持為 float32,以避免執行時錯誤。

CodefyUI 也會在 import torch 前設定 PYTORCH_ENABLE_MPS_FALLBACK=1。MPS 不支援某項運算時,PyTorch 會改在 CPU 上執行。速度會較慢,但不會因不支援該運算而發生錯誤。若要讓不支援的運算拋出錯誤,請在執行 cdui start 前將此變數匯出為 0。

Apple Silicon 上的效能​

以下數字在 M3 MacBook Air(24 GB、torch 2.11)上量測,三個內建範例各跑一個 epoch,執行之間留冷卻間隔。套用前兩項後:mps 上 CNN-MNIST 11.2 s → 4.1 s、ResNet-CIFAR10 12.1 s → 5.1 s、GPT-Mini 19.9 s → 13.1 s;cpu 上 13.7 s → 11.5 s、27.4 s → 24.4 s、22.4 s → 20.8 s。

  • 訓練迴圈不會每個 batch 讀回 loss。 loss.item() 是一次 host/device 同步。在 MPS 上它會排空 Metal 指令佇列,CPU 因此無法在 GPU 處理目前 batch 時準備下一個。TrainingLoop、EvaluateModel 與 DiffusionTrainingLoop 在裝置上累計 loss,只在每個 epoch 結束、每個 batch_metrics 取樣點、以及每個進度訊框(每秒最多兩次)時讀回。單獨這一項:mps 上每個 epoch CNN-MNIST 11.2 s → 5.5 s、ResNet-CIFAR10 12.1 s → 5.6 s、GPT-Mini 19.9 s → 15.8 s。
  • 預設的 ToTensor + Normalize 以 batch 為單位執行。 MNIST、FashionMNIST、CIFAR10、CIFAR100 在 transform 埠沒有接線時,Dataset 對整個 batch 一次套用這兩步。輸出與逐樣本路徑逐位元相同;MNIST 每個 epoch 的主機時間從 1.5 s 降到 0.13 s。有接線的 transform 鏈使用 torchvision 的逐樣本路徑。
  • 記憶體內資料集的 num_workers 維持 0。 macOS 以 spawn 啟動 DataLoader worker。對 MNIST 規模的資料,啟動與逐 batch 的 IPC 成本高於節省的時間:CNN-MNIST 在 0 個 worker 時 11 s、2 個 25 s、4 個 16 s。worker 對需要解碼影像檔的資料集(ImageFolderDataset)有幫助。

自行量測時:一個 process 的第一次 MPS 執行需要 0.2–0.6 s 初始化 Metal,每種新的 kernel 形狀再加 0.1–0.2 s。同一台 Mac 之後的 process 會較快,因為 macOS 會快取編譯好的 shader。無風扇的 Mac 在持續 GPU 負載幾分鐘後會熱降頻;比較不同執行時請在中間留冷卻間隔。

MPS 上不使用混合精度。bf16 與 fp16 autocast 在 torch 2.11 可以執行,但量測結果比 fp32 慢 1.8–3.3 倍且不省記憶體。詳見訓練記憶體。

ROCm 呈現為 CUDA​

在 AMD + Linux 上搭配 ROCm 版本的 PyTorch 時,torch.cuda.is_available() 會回傳 True,因為 ROCm 暴露了一個與 CUDA 相容的介面。設定與圖的裝置選單會把這個裝置標示為 AMD ROCm;存進圖檔並由執行使用的值是 cuda(多卡時為各卡的 cuda:N),節點的 device 參數也會列為 cuda。這是預期的行為。

Intel GPU(XPU)​

cdui install --gpu xpu 會安裝 PyTorch 的 Intel GPU build,在該 venv 中可使用 torch.xpu。執行的裝置控制項目前還沒有 xpu:裝置選項為 cpu、auto、cuda(或 cuda:N)與 mps,auto 依序解析為 CUDA、MPS、CPU,裝置設為 xpu 的執行會因裝置無效而被拒絕,節點參數設為 xpu 時則會退回 CPU,並在 log 中留下警告。因此在裝了 xpu build 的電腦上,執行仍在 CPU 上進行。

實驗性:原生 MLX(spike)​

有一個概念驗證 (proof-of-concept),把一個小型 MLP 的前向推論從 PyTorch 移植到 Apple 的 MLX 框架,產生數值上完全相同的結果(最大絕對差約 1.9e-7)。重點如下:

  • 真正圖引擎中的 Apple 加速是 PyTorch MPS,它已接好並完成端到端驗證。MLX 並非已交付的執行後端。

  • MLX 是一個獨立的陣列框架,並非 PyTorch 後端——並沒有 torch.device("mlx")——所以它無法成為任何裝置下拉選單(它們驅動的是 torch)中的一個值。

  • 此 spike 僅供推論且為 float32,可臨時執行:

    uv pip install mlx # Apple Silicon only
    python scripts/mlx_spike.py
  • mlx 並非已納入的相依套件;主應用程式從不匯入它。只透過 device_utils.mlx_available()(偵測)與 spike 腳本來呈現它。

建議: 將 MPS 維持為所有執行(訓練 + 推論)的 Apple 預設;把 MLX 當作選用的推論加速器,只在推論密集的教學示範上有可量測的效益時才回頭考慮。