遠端 Worker
本頁帶你把一台剛安裝好的 GPU 伺服器,變成替你執行圖的 worker。若 worker 與伺服器在同一台機器上,看多 GPU 執行就夠了;兩者背後的設計見執行架構。
概觀
Worker 是在擁有運算資源的機器上執行的 cdui agent start 程序。它主動連到你的 CodefyUI 伺服器,也就是控制面,接收 target 指定它(或 any)的 run,並把每個 run 放在自己的子程序中執行,所以崩潰或記憶體不足被終止只會結束那一個 run。Worker 機器不開任何連接埠:agent 一律主動連到控制面,可以直連、經過 proxy,或經過控制面啟動的 SSH 通道。
有些東西從不離開控制面:
- 資料庫(run、事件、指標、artifact 資料列、已加入的 worker):worker 從不開啟它;
- run 佇列與排程決策;
- 畫布:Run 一律在控制面執行,連同它的執行快取、持久化權重與 Inspector 輸出;
- session token:遠端 worker 以自己的憑證驗證身分。
送到 worker 的 run 帶著它的圖與選項,其中 SECRET 參數的值已填回;沒有共用資料夾時,也會帶上它使用的上傳檔(儲存)。請據此選擇連線方式(信任)。
Run 依它的 target 交給 worker:Run 旁的排入佇列...、cdui run --target <名稱>、POST /api/runs 的 options.target,或 sweep 與 study 的 options.target(執行佇列:Workers)。
加入 worker
-
在 GPU 伺服器上安裝與控制面相同版本的 CodefyUI。 先在控制面用
cdui --version查看版本,然後在 GPU 伺服器上執行:curl -fsSL https://raw.githubusercontent.com/CodefyUI/CodefyUI/main/install.sh | CODEFYUI_RELEASE_TAG=<version> bash把
<version>換成cdui --version印出的版本(例如3.1.0)。版本不同的 agent 連線時會被拒絕(讓 worker 保持同步)。需要特定 CUDA 或 ROCm 版本的 PyTorch 時,見 GPU 與裝置設定(cdui install --gpu <選項>)。 -
讓 GPU 伺服器連得到控制面,方式見連到控制面。使用 SSH 方式時,略過接下來兩步:控制面會自己啟動 agent。
-
產生 token。 開啟 Workers(Settings → 執行 → Workers → 管理,或 Runs 面板標題列的 Workers),按 新增 worker,停在 另一台機器。位址欄預設為此頁面使用的位址;若是
127.0.0.1或localhost,請改選伺服器的其他位址,或輸入 worker 連得到的位址。選擇 token 的有效期限(預設 24 小時)與可供幾個 agent 使用(預設 1),再按 產生 token。
-
在 GPU 伺服器上執行指令:
cdui agent start --control http://192.168.1.5:8000 --token abcdef.0123456789abcdefAgent 會用 enrollment token 換取自己的 worker 憑證,存在
<user-data>/agent/<host>-<port>.token(只有該作業系統使用者可讀;安裝版的<user-data>是.codefyui_dev/)。--name指定 worker 名稱(預設為主機名稱),--devices cuda:0,cuda:1限制它提供的 GPU,--mount data=<path>指定共用資料夾(儲存)。所有參數見 CLI 指令:Agent 指令。 -
確認它已上線。 幾秒內 worker 就會以 線上 狀態出現在 Workers 面板,並顯示版本、節點型別與裝置。用**排入佇列...**或
cdui run --target <名稱>送一個 run 給它。
Enrollment token 只顯示一次,伺服器只保存它的雜湊值。已用完、已過期或已撤銷的 token 會被拒絕,訊息會說明是哪一種。已發出的 token 會列在對話框中,直到你刪除為止。
重新啟動。 cdui agent start --control <url> 會找到已存的憑證並用它重新連線,不需要 token。伺服器重啟後也記得已加入的 worker:在它連線前列為 offline,指定它的 run 會在佇列中等待。cdui agent forget <url> 刪除已存的憑證,下次啟動時重新加入。
移除 worker。 Workers 面板的 移除 會撤銷該 worker 的憑證、中斷它的連線(執行中的 run 立即以 interrupted 結束,包括正在等它重新連線的 run;佇列中的 run 失敗)並從清單移除。之後它的 agent 都會被拒絕:仍以該憑證執行的 agent 會收到憑證已被撤銷的通知,說明後停止(結束代碼 2),這次嘗試也會列在 被拒的連線 下(同一憑證每小時最多一次)。因其他原因被拒的 token(該位址上是另一個控制面或資料庫、代理伺服器、伺服器重啟前的 session token)會繼續重試,並依 token 種類給出建議。重新核發後,worker 的舊憑證也以同樣方式被拒。
重新核發憑證。 名稱一經加入即被占用:一般 token 無法再次加入該名稱(伺服器回 409)。要讓某個 worker 換新憑證(重灌機器、遺失憑證檔),請按它卡片上的 重新核發:在那裡產生的 token 只能加入該 worker,指令也會帶上它的 --name。使用後舊憑證即失效。若該機器上還留有舊憑證檔,請先在那裡執行 cdui agent forget <url>。
面板本身(狀態、排空、名額、被拒的連線)的說明見多 GPU 執行:Workers 面板。
連到控制面
Agent 需要一個能連到控制面 HTTP 連接埠的網址:它以 POST /api/workers/enroll 加入,之後保持 /ws/worker WebSocket 連線。請從三種方式中擇一。
| 方式 | 適用情況 | 是否加密 |
|---|---|---|
| 直連 | 兩台機器在同一個可信任的區網或 VPN 上 | 否,除非 VPN 有加密 |
| TLS proxy 後方 | 控制面是其他人透過 HTTPS 連線的伺服器 | 是 |
| 透過 SSH | GPU 伺服器只能用 SSH 連到,或控制面是 NAT 後面的筆電 | 是(SSH 連線) |
直連:區網或 VPN
cdui start 預設只監聽 127.0.0.1,其他機器無法連線。請以 cdui start --host 0.0.0.0 啟動。伺服器會接受自己各網路介面的位址(例如 192.168.1.5:8000)作為 Host;若 worker 改用主機名稱連線(gpu-gw.lan:8000、VPN 名稱),請在啟動伺服器前把它加入 CODEFYUI_EXTRA_ALLOWED_HOSTS:
CODEFYUI_EXTRA_ALLOWED_HOSTS=gpu-gw.lan:8000 cdui start --host 0.0.0.0
任何連得到該連接埠的人都能操作這台伺服器,worker token 與每個 run 的輸入也會以明文經過網路:請只在可信任的網路使用,例如實驗室區網,或 WireGuard、Tailscale 等 VPN。以未加密的 HTTP 連到另一台機器時,agent 會印出一次警告,Workers 面板也會把該 worker 標成 未加密 HTTP。
TLS proxy 後方
若控制面要讓其他人透過網路連線,請依部署在反向代理後方把它放在有 TLS 的 nginx 後面。那份站台設定已經涵蓋 /ws/worker 與 artifact 上傳(這個 proxy 後方的 worker)。給 worker https:// 位址:
cdui agent start --control https://codefyui.example.com --token abcdef.0123456789abcdef
Agent 每次連線都會檢查 proxy 的憑證(信任);自簽憑證需要 --ca-fingerprint。
透過 SSH
若 GPU 伺服器只能用 SSH 連到,例如筆電在 NAT 後面、伺服器只開放 22 埠,可以讓控制面自己啟動 worker。它會用 ssh 連到 GPU 伺服器、在那裡執行 cdui agent start,agent 再經由同一個 SSH 連線中的反向通道(ssh -R)連回來。兩台機器都不需要開連接埠,控制面也可以維持只監聽 127.0.0.1。
開始之前:
-
GPU 伺服器上已用
install.sh安裝相同版本的 CodefyUI(上面第 1 步)。控制面不會替你安裝。 -
控制面所在的機器 PATH 上有 OpenSSH 用戶端(
ssh)。macOS 與 Linux 內建;Windows 請加入選用功能 OpenSSH 用戶端,並在 PATH 含有它的工作階段中啟動伺服器。 -
在控制面機器上、以執行
cdui start的作業系統使用者開終端機執行ssh <target>,不會出現任何提示就能登入。ssh 以BatchMode=yes執行:不能用密碼,有 passphrase 的金鑰須透過ssh-agent。你的~/.ssh/config會生效,所以Host別名、經過跳板機的ProxyJump與其中指定的金鑰都能用:# ~/.ssh/config on the control plane's machineHost gpu-boxHostName 10.0.3.17User aliceProxyJump bastion.labIdentityFile ~/.ssh/id_ed25519
新增。 開啟 Workers,按 新增 worker,選 透過 SSH:
| 欄位 | 說明 |
|---|---|
| SSH 目標 | user@host,或伺服器帳號 ~/.ssh/config 中的 Host 別名(上例的 gpu-box)。 |
| 連接埠 | ssh 的 -p。保持 22 時不會傳給 ssh,所以 ssh 設定中該主機的連接埠會生效。 |
| Worker 名稱 | 選填;預設為目標的主機名稱(alice@gpu1.lab 會成為 gpu1)。已被其他 worker 使用的名稱會被拒絕。 |
| 金鑰檔 | 選填;伺服器機器上的私鑰(ssh 的 -i)。留空則使用你的 ssh 設定與 agent。 |
| 遠端主機上的 cdui | 遠端啟動器的路徑;留空代表 ~/CodefyUI/cdui,也就是 install.sh 放置的位置。 |
按 新增並連線。Worker 卡片上會多一行 SSH 通道:ssh 執行中但 agent 尚未連線時為 連線中,連上後為 已連線,等待重試時,或遇到重試也無法解決的失敗而等你按 重試 時(見下文),為 失敗 並附上原因與 ssh 或 agent 最後輸出的幾行(金鑰被拒、未知主機、找不到 cdui),停用時為 已停止。最近的 ssh 輸出 可看到更多輸出。
實際執行的內容。 每個啟用中的 SSH worker,控制面都會維持一個像這樣的程序:
ssh -T -o BatchMode=yes -o ExitOnForwardFailure=yes -o ServerAliveInterval=15 \
-o StrictHostKeyChecking=accept-new \
-R /home/alice/.codefyui-ssh/3f9a12c4-18000/sock:/tmp/cdui-ssh-k2j4/s -- alice@gpu-box \
"sh -c '... ~/CodefyUI/cdui agent start --uds ~/.codefyui-ssh/3f9a12c4-18000/sock --foreground --control http://cp-3f9a12c4.invalid:18000 --name gpu-box'"
每次連線前,控制面會先另開一個短暫的 ssh,在遠端主機上建立 ~/.codefyui-ssh/<id>-<槽位>/(權限 0700,~/.codefyui-ssh 也是),刪掉中斷的連線留下的 socket,並回報這個資料夾的完整路徑(-R 需要它)。轉送的遠端端點是這個資料夾裡的 Unix socket,所以兩台機器都需要 OpenSSH 6.7 以上,遠端 sshd 也必須允許 AllowStreamLocalForwarding(預設允許);遠端主機不需要其他東西。<id> 是這個控制面的實例 ID(八個十六進位字元,存在它的使用者資料夾的 ssh-instance-id),所以在同一台 GPU 伺服器上使用同一個帳號的兩個控制面,不會共用資料夾、socket、憑證或 agent。轉送的本機端點(上例的 /tmp/cdui-ssh-k2j4/s)是控制面只為這個 worker 開的監聽端點:私有暫存資料夾裡的 Unix socket(Windows 上則是 127.0.0.1 的臨時連接埠),絕不是伺服器自己的連接埠。槽位編號取自 CODEFYUI_SSH_REMOTE_PORT_RANGE(18000-18100)並隨 worker 保存:它決定狀態資料夾,也是 agent 存放憑證所用的位址(--control http://cp-<id>.invalid:<槽位>,這個名稱不會解析到任何地方);agent 連的是 socket(--uds)。遠端指令會先把 ~/.local/bin 加到 PATH:非互動式的 ssh shell 不會讀取你的 shell 設定檔。ssh 結束後,控制面會在 2 秒後重新啟動它,間隔逐次加倍到最多一分鐘;控制面自己重啟後也會重新連線。若遠端 sshd 根本不允許轉送(AllowStreamLocalForwarding no、DisableForwarding yes;明確拒絕,或連續三次轉送失敗),通道會停在 失敗 並顯示原因,不再自動重試;請伺服器管理者允許轉送到 Unix socket 後按 重試。重試也無法解決的失敗同樣如此:主機金鑰驗證失敗,或遠端 shell 無法在指定路徑執行 cdui(連線以結束代碼 127 或 126 結束)。ssh 登入被拒(Permission denied)或主機名稱無法解析則可能自行恢復(之後才解鎖的 ssh-agent、之後才連上的 VPN 或網路):通道會照同樣的間隔繼續重試並顯示原因,直到這類失敗不間斷持續 10 分鐘才停止。每個失敗的通道都會顯示 重試,按下即立刻重新嘗試;它不會取消你對該 worker 設定的排空(只有 停用 後再 啟用 才會取消排空)。最近的 ssh 輸出 只保留最後一次嘗試的內容。agent 真正執行後,通道列也會顯示遠端 agent 的 PID:在遠端主機上 agent 的命令列是 python .../scripts/dev.py agent start --uds ...,可用 pgrep -af 'agent start' 找到它。
憑證。 每次啟動都會經由 ssh 的標準輸入送出一次性的 enrollment token(有效 10 分鐘),不會出現在任何命令列上,所以兩台機器的 ps 都看不到它。第一次啟動時 worker 會加入;agent 會把 worker 憑證以 http://cp-<id>.invalid:<槽位> 這個位址存在遠端主機上,之後每次啟動都用它重新連線,token 則不會被使用。Worker 加入後,token 會綁定到它,只能用來重新核發這個 worker 的憑證。這個 worker 第一次使用某個槽位時,會先對該位址執行 cdui agent forget,所以其他 worker 或其他控制面留下的憑證不會被送出。若仍送出了這種憑證(agent 回報控制面拒絕了它,或 agent 回報憑證屬於另一個 worker 名稱),控制面會先執行 cdui agent forget 再重新啟動 agent,讓它重新加入。所有通道都從控制面自己的位址連入,因此經由通道的加入嘗試會依 SSH worker 分開限速,彼此之間以及與那台機器上的 agent 互不影響。
連線中斷。 若連線不是因停用而結束(筆電睡眠、網路中斷),遠端 agent 與它的 run 會繼續執行;這些 run 在控制面上以 disconnected 等待並保留事件,如同連線中斷時所述。控制面會以相同的槽位重新啟動 ssh,轉送會回到同一個 socket 路徑,遠端腳本發現 agent 仍在執行,就會接上它而不另啟一個(通道列會顯示重新接上的 PID)。Agent 會自行經由新的通道重新連線,它的 run 也會繼續。若在 CODEFYUI_WORKER_RESUME_GRACE_S(5 分鐘)內沒有新的連線接上,agent 會停止。遠端腳本把 agent 的 PID 與日誌存在遠端主機的 ~/.codefyui-ssh/<id>-<槽位>/,與 socket 放在一起。
停用、啟用、刪除。 停用 會先讓 worker 停止接新 run,再結束它的 ssh 連線;之後即使有 agent 以它的名稱連線,停用中的 worker 仍維持不接新 run。停用會經由連線通知遠端 agent 停止(沒有連線時則另開一個短暫的 ssh),agent 會停止仍在執行的 run;若有需要,請先排空並等它完成。Agent 會要求每個 run 停止並給它 10 秒寬限:在寬限內停下的 run 以 cancelled 結束,超過寬限而被它終止的 run 以 interrupted 結束。Agent 會上傳 transfer run 寫出的檔案(它的中斷檢查點),回報每個 run 如何結束後才退出;控制面最多等它 60 秒再結束 ssh,屆時仍未回報的 run 會立即以 interrupted 結束,不必等 CODEFYUI_WORKER_RESUME_GRACE_S。移除也以同樣方式停止它。伺服器關閉時,會在伺服器自己的 run 收尾的同時停止所有 SSH worker(不是先停 SSH worker),SSH worker 總共最多 25 秒。cdui stop 約 2 秒後就會強制結束伺服器:agent 已先收到停止通知,ssh 連線會在它們結束後自行結束,但屆時尚未回報的 run 會在下次啟動時標為 interrupted。舊版留在 ~/.codefyui-ssh/<槽位>/ 舊配置、以 TCP 連接埠連線的遠端 agent,會在下一次連線的準備步驟中,確認沒有連線接著它之後被停止(仍有連線接著時只回報)。啟用 會重新啟動連線。在 Workers 面板中,停用的 worker 顯示為 已停用,不提供排空/取消排空;停用有 run 在執行的 worker、刪除與移除都要按第二次,卡片會先說明那一下會做什麼。刪除(尚未連線的 worker)與 移除(連線過的 worker,離線時也一樣)效果相同:結束連線、刪除 SSH 項目,並如移除 worker 所述撤銷該 worker,所以之後可以用同一個名稱再新增。兩者也會清理遠端主機,停用則不會(啟用會沿用已存的憑證):回應之後,控制面在背景再開一個短暫的 ssh,若 agent 仍在執行就先停止它,再刪除它為 http://cp-<id>.invalid:<槽位> 存的憑證(cdui agent forget),並刪除 ~/.codefyui-ssh/<id>-<槽位>/。只會動到這個控制面自己的槽位,不會碰其他控制面的;清理完成前,其他 SSH worker 也不會使用這個槽位。若仍有連線接在那裡,或 agent 停不下來,兩者都會保留。若遠端主機約一分鐘內沒有回應,伺服器日誌會列出留下的東西;要手動清除,請在遠端主機執行 cdui agent forget http://cp-<id>.invalid:<槽位> 與 rm -r ~/.codefyui-ssh/<id>-<槽位>。
安全性。
- 遠端主機上只有你的帳號能連到通道。它的遠端端點是
~/.codefyui-ssh/<id>-<槽位>/裡的 Unix socket,該資料夾權限為0700,所以共用 GPU 伺服器的其他使用者既無法連上它,也無法搶先占用它的位址來攔截 agent 的 token;若用遠端 loopback 上的 TCP 連接埠,這兩件事都做得到。 - 在控制面上,通道接到它專用的監聽端點:每個 SSH worker 各開一個,絕不是伺服器的連接埠;它是私有(
0700)暫存資料夾裡的 Unix socket,所以控制面那台機器的其他使用者也連不到(Windows 上則是127.0.0.1的臨時連接埠,他們連得到)。在那裡被拒的 token 不會觸發任何處理:只有 agent 自己的回報才會讓控制面清除它的憑證。這個監聽埠只回應 agent 會用到的路徑:帶 worker token 的/ws/worker(session token 在那裡一律被拒)、帶 enrollment token 的POST /api/workers/enroll,以及在帶有有效 worker token 時,transfer 後端使用的路徑(儲存:沒有共用檔案系統):三個輸入下載路徑(GET /api/files/download/...、/api/images/download/...、/api/models/download/...)與POST /api/runs/{id}/artifacts/upload。這些下載路徑在區網上是公開讀取,但經由通道時沒有 worker token 會回403。其他路徑一律回404,包括GET /api/auth/bootstrap與網頁介面,不論 request 帶的是哪個Host。在伺服器自己的連接埠上,沒有任何Host會讓 request 被當成來自通道:SSH worker 的位址在那裡只是不在 allowlist 內的Host(421)。 StrictHostKeyChecking=accept-new會在第一次見到主機時信任它,金鑰改變時拒絕連線。設定CODEFYUI_SSH_STRICT_HOST_KEY_CHECKING=yes則要求主機已在伺服器帳號的known_hosts中。- 控制面使用執行它的作業系統使用者的 SSH 存取權,就像 Source Control 分頁使用 git 一樣。能操作這台伺服器的人,就能在該使用者連得到的每台主機上啟動 SSH worker。
- 通道後面的 agent 從
127.0.0.1連線,但只有在它的 machine id 與伺服器相同時才會被視為控制面機器上的 worker,所以 GPU 伺服器不會被誤認為伺服器本機。它以自己的 worker token 驗證身分。
控制面放在哪裡
控制面保管佇列並記錄每個事件,所以 worker 上的 run 仰賴它持續運作、連得到。
-
筆電可以用,但有限制。 蓋上螢幕會關閉所有 worker 連線。worker 未在
CODEFYUI_WORKER_RESUME_GRACE_S(5 分鐘)內回來的 run 會以interrupted結束(連線中斷時);筆電換到另一個網路,直連的 worker 要連的位址也會改變(SSH worker 會跟著走,因為通道是從筆電發起)。短暫休眠可調高CODEFYUI_WORKER_RESUME_GRACE_S;過夜的訓練請讓筆電保持清醒,或使用常駐的控制面。 -
常駐伺服器是穩健的選擇。 在一台持續運作的機器(GPU 伺服器本身,或旁邊的小主機)上以 systemd 服務執行
cdui start(部署:systemd unit),從筆電使用編輯器。控制面維持在127.0.0.1時,瀏覽器透過 SSH 連接埠轉送即可,其他什麼都不必對外開放:ssh -N -L 8000:127.0.0.1:8000 gpu-box # then open http://localhost:8000同一台機器上的 worker 用同機指令;其他地方的 worker 依上述方式直連、經過 proxy,或透過 SSH。
儲存
不使用資料夾中檔案的 run(純 tensor 的圖、Dataset、HuggingFaceDataset 節點下載到自己快取的圖,或讀取 data/samples/iris.csv 這類內建範例的圖;每個安裝都有內建範例,worker 讀自己的那份)可以在任何 worker 上執行。節點參數中的相對路徑在每台機器上都從安裝目錄的 backend/ 讀取,與伺服器相同:不論 cdui agent start 在哪個目錄啟動(經 SSH 時是家目錄),它都會在那裡運作。啟動 agent 時給的設定若是相對路徑(例如 CODEFYUI_MODELS_DIR=models),仍代表啟動時所在的目錄:agent 會在切換目錄前把它轉成絕對路徑。會讀取上傳檔或寫出檔案的 run,需要兩者之一:兩台機器共用的資料夾,或經由 agent 連線搬運檔案的 transfer 後端(有設定時,改經由 S3 或 MinIO bucket)。有共用資料夾的 worker 優先。
| Run | 有 data 掛載的 worker | 沒有掛載的 worker |
|---|---|---|
| 不使用檔案 | 可排入 | 可排入 |
| 使用檔案,且全部是在檔案欄位中選取的上傳檔 | 可排入(優先) | 可排入:檔案經由 HTTP 傳送(沒有共用檔案系統)。any 會在有掛載的 worker 之後才試它 |
以其他方式使用檔案(一般路徑、這台伺服器沒有的上傳檔、輸入超過 CODEFYUI_TRANSFER_MAX_INPUT_BYTES),或 CODEFYUI_TRANSFER_ENABLED=0 | 可排入 | 指定該 worker:送出時 400,worker gpu-box-1 has no `data` mount; add `--mount data=...` to its agent, or make the run fit the transfer backend: <reason>。any:略過 |
共用檔案系統
Run 有以下節點時就算使用檔案:讀取上傳檔的節點(CSVReader、ImageReader、ModelLoader、CheckpointLoader 等上傳欄位)、會存檔的節點(CheckpointSaver、ModelSaver、ImageWriter、VideoWrite)、設定了 checkpoint_every、checkpoint_every_steps 或 tensorboard 的 TrainingLoop,或路徑指向資料夾的讀取節點。在兩台機器掛上同一個網路共用資料夾,並給它相同的 label:控制面的資料夾(使用 --project 時為專案目錄)的 label 是 data(CODEFYUI_MOUNT_LABEL),agent 以 cdui agent start --mount data=<path> 指定自己這邊的路徑。兩台機器的路徑可以不同。
NFS 範例,兩台機器的掛載路徑不同:
# Both machines
sudo mount -t nfs nas.lab:/export/codefyui /mnt/codefyui # on the GPU server: /nas/codefyui
# Control plane: keep the data folders on the share (the database stays local)
export CODEFYUI_DATA_FILES_DIR=/mnt/codefyui/data/files CODEFYUI_IMAGES_DIR=/mnt/codefyui/data/images \
CODEFYUI_MODELS_DIR=/mnt/codefyui/data/models CODEFYUI_MEDIA_DIR=/mnt/codefyui/data/media
cdui start
# GPU server: the label is saved, so later starts need no --mount
cdui agent start --mount data=/nas/codefyui/data
在 worker 上,run 的資料夾依 backend/data 的結構由掛載路徑推得(files/、images/、models/、media/、graphs/、presets/);路徑中有 codefyui.project.toml 時則視為該專案。Run 記錄的檔案(checkpoint、TensorBoard 資料夾)以相對於掛載點的路徑儲存(backend: "mount"),GET /api/runs/{id}/artifacts 與執行任務面板列出的是控制面上的路徑;控制面沒有該掛載的資料列 path 為 null,以 label 與相對路徑表示。與伺服器在同一台機器上的 agent 不需要 --mount 就擁有 data:它的 run 直接使用伺服器設定的資料夾,產出的檔案列為一般的本機檔案,GET /api/workers 會以伺服器的路徑顯示該 label。
掛載設定存在使用者資料目錄的 agent.json,之後啟動會沿用;--mount data= 會移除一個。Agent 啟動時已儲存的掛載資料夾若不存在(共用資料夾尚未掛上),在資料夾出現前不會提供該 label;agent 每次心跳都會重新檢查。Agent 執行中不能變更 --mount,請先停止它。使用 --project 時,上傳的資料檔仍在安裝目錄的 data/files,不在專案內,因此不會共用。
沒有共用檔案系統
會讀取上傳檔或寫出檔案的 run,也可以交給與控制面沒有任何共用資料夾的 worker。此時控制面就是檔案庫:把 run 用到的上傳檔送給 worker,並接收 run 寫出的檔案。只有 run 指定的 worker 沒有共用資料夾,或 any 找不到有空的此類 worker 時,才會走這條路。
輸入。 能用這種方式送過去的,是在檔案欄位中選取的上傳檔:CSVReader 等資料檔欄位、ImageReader、ModelLoader、CheckpointLoader。指派時,控制面以 SHA-256 雜湊值標示每個檔案。Agent 把下載過的檔案存在 <user-data>/agent-cache/<sha256>,之後使用同一個檔案的 run 不會再下載;快取中沒有的檔案,以 worker 的 token 從控制面既有的下載路徑取得,並核對雜湊值。下載期間,執行任務面板的紀錄會顯示 fetching inputs 2/3: train.csv (1.2 MiB, fetched)(kind 為 transfer_progress 的 run_warning);顯示 cached 而非 fetched 表示取自快取。下載期間按停止,run 以 cancelled 結束。
Run 的工作資料夾。 每個 run 在 worker 上有自己的資料夾 <user-data>/agent-runs/<run id>/data,結構與 backend/data 相同(files/、images/、models/、media/),每個輸入檔以圖中使用的名稱複製一份放在裡面(節點覆寫輸入檔也碰不到快取)。Worker 自己的資料夾不會被動到,只有 cache/ 資料夾(LMTokenizedDataset 的 token 區塊)由各 run 共用。Dataset、HuggingFaceDataset 下載的資料集仍放在 worker 自己的快取,與在伺服器上相同。
輸出。 在 run 的結束訊息送到控制面之前,agent 會上傳:
- run 記錄為 artifact 的每個檔案(定期 checkpoint、TensorBoard 資料夾則逐檔上傳),在 run 回報時上傳到
<資料夾>/runs/<run id>/<kind>/<name>; - run 結束後,它在
models/、media/、output/底下寫出的其他檔案(CheckpointSaver、ModelSaver、VideoWrite、ImageWriter),以及被它覆寫的輸入檔,上傳到控制面自己的資料夾、相同的相對路徑,也就是同一個節點在本機 run 時寫入的位置(CODEFYUI_MODELS_DIR中的models/best.pt)。該處已有不同的檔案時會保留原檔,新檔加上後綴(best-1.pt);內容相同則不重複存放。這些檔案的 kind 為output; - 最後是被停止的訓練迴圈寫出的 checkpoint,存到 run 的資料夾,讓已取消 run 的續訓點留在控制面上。
上傳與 run 並行,網路慢不會拖住 run;上傳完成後才回報 run 結束。GET /api/runs/{id}/artifacts 以 backend: "transfer" 和控制面上的路徑列出,執行任務面板在每一筆旁提供 下載(GET /api/runs/{id}/artifacts/{artifact_id}/download)。連不上控制面時(連線中斷,或 proxy 回應 502、503、504),agent 會持續重試,間隔逐次拉長,時間與控制面保留已斷線 worker 之 run 的時間相同(CODEFYUI_WORKER_RESUME_GRACE_S,預設 5 分鐘),所以在斷線期間結束的 run 仍會送回它的檔案。同一個 run 的檔案共用這段時間:用完之後,其餘每個檔案只再試一次。超過控制面上傳上限(CODEFYUI_MAX_UPLOAD_SIZE,500 MB)的檔案,或重試後仍未被接收的檔案,會留在 worker 上,以它在 worker 上的路徑列出,並附上 transfer_failed 警告。在最後一批上傳期間按停止,尚未送出的檔案就不再上傳,並以一則 transfer_failed 警告說明有幾個留在 worker 上。
清理。 Run 自己的資料夾屬於該 run:run 保留機制(CODEFYUI_RUN_RETENTION_KEEP_LAST)或 刪除 移除 run 時,它的 runs/<run id> 資料夾也一起刪除。想保留的檔案請下載或另行複製。以 interrupted 結束、之後被保留機制移除的 run 會保留 checkpoint 資料夾,與本機 run 保留續訓點的做法相同。放進資料夾的檔案屬於你,與本機 run 相同:兩者都不會刪除它們。在 worker 上,run 的資料夾跟著 agent 自己的 run 保留機制;快取不會自動清理,刪除 agent-cache 是安全的(需要時會重新下載)。
無法以此方式處理的 run,若指定了沒有共用資料夾的 worker,會在送出時被拒絕,訊息會說明原因:讀取節點用的是一般路徑而非上傳檔(FileReader、ImageFolderDataset、ImageBatchReader、VideoLoad、讀取資料夾的 DocumentLoader)、控制面上沒有該上傳檔,或輸入超過上限。例如:worker gpu-box-1 has no `data` mount; add `--mount data=...` to its agent, or make the run fit the transfer backend: its inputs add up to 3.10 GiB, over the transfer limit of 2.00 GiB (CODEFYUI_TRANSFER_MAX_INPUT_BYTES)。
輸入與輸出送往 agent 連線的同一個位址,帶著它的 worker token,並做相同的憑證檢查(釘選的憑證對它們同樣生效):沒有 TLS 時,它們會以明文經過網路。在 proxy 後方時,它的請求大小上限必須容得下最大的輸出(部署)。
S3 或 MinIO bucket
實驗室若已有 MinIO,或使用 S3,可以讓 bucket 取代控制面作為檔案存放處:worker 從 bucket 下載 run 的輸入,並把 run 寫出的檔案上傳到 bucket,控制面只保存物件的 key。如此 run 的檔案不會經過控制面的網路連線,唯一的例外是每個輸入檔,由控制面放進 bucket 一次。
它處理的 run 與 transfer 後端相同(在檔案欄位中選取的上傳檔,總大小在 CODEFYUI_TRANSFER_MAX_INPUT_BYTES 以內),對象是沒有共用資料夾的 worker;啟用時由它取代 transfer 後端。有共用資料夾的 worker 仍然優先。
設定方式。
- 在控制面與每台要使用 bucket 的 worker 上,把 boto3 安裝進 CodefyUI 的環境:在安裝資料夾執行
uv pip install --python backend/.venv/bin/python boto3,開發用的 checkout 則用uv pip install -e "backend[s3]"。沒有 boto3 的 agent 不會提供 S3(agent 的 log 會列出連線時使用的 capabilities),仍會收到 transfer 後端的 run,每個這樣的 run 會記下一則storage_fallback警告。 - 建立 bucket,以及一個可讀寫它的 CodefyUI 使用者。使用 MinIO 的 client:
mc mb lab/codefyui,接著mc admin user add lab codefyui <secret>與mc admin policy attach lab readwrite --user codefyui。 - 以 bucket 的設定啟動控制面:
export CODEFYUI_S3_ENABLED=1
export CODEFYUI_S3_ENDPOINT_URL=http://minio.lab:9000 # AWS S3 則留空
export CODEFYUI_S3_BUCKET=codefyui
export CODEFYUI_S3_ADDRESSING_STYLE=path # 多數 MinIO 環境
export CODEFYUI_S3_ACCESS_KEY_ID=codefyui
export CODEFYUI_S3_SECRET_ACCESS_KEY=<secret>
cdui start
伺服器 log 會顯示 S3 artifact backend on: bucket codefyui at http://minio.lab:9000 (credentials: auto),或說明後端為何關閉(沒有 bucket,或缺少 boto3)。Worker 不需要自己的設定:bucket、它的位址與憑證會隨每個 run 送達。控制面與每台 worker 都必須能以同一個位址連到 bucket。
檔案放在哪裡。 放在 CODEFYUI_S3_PREFIX 之下(預設為空;設為 codefyui/ 會把所有東西放在該資料夾下):
inputs/<sha256>:run 讀取的上傳檔。第一次有 run 需要它時由控制面放入,以內容命名,同一個檔案不會上傳兩次。Worker 從這裡把它下載到 transfer 後端使用的同一個agent-cache。runs/<run id>/<kind>/<name>:run 記錄為 artifact 的每個檔案(週期 checkpoint 是runs/<run id>/checkpoint/<name>.pt,TensorBoard 資料夾逐檔上傳),以及放在runs/<run id>/output/下、run 寫在models/、media/或output/中的其他檔案,保留它在那裡的路徑(ModelSaver 的iris/model.pt會是runs/<run id>/output/models/iris/model.pt)。Agent 的上傳方式與 transfer 後端相同:與 run 並行、中斷 checkpoint 最後上傳、在回報 run 結束之前完成,並以相同的重試度過斷線。暫時憑證過期時也以同樣方式重試,直到新憑證送達(MinIO 對過期憑證回應InvalidAccessKeyId;只有已到或超過期限的暫時憑證遇到這個回應才會重試)。被 bucket 拒絕的檔案(AccessDenied、bucket 不存在)會留在 worker 上,並附上transfer_failed警告。
GET /api/runs/{id}/artifacts 以 backend: "s3" 列出它們,path 為 s3://<bucket>/<key>,relative_path 為 key。在執行任務面板中,複製路徑 會複製 s3:// 位址(給 mc 或 aws s3 使用),下載 則取得檔案:預設經由控制面轉送(CODEFYUI_S3_DOWNLOAD=proxy),設為 presign 時會把瀏覽器導向有效 CODEFYUI_S3_PRESIGN_TTL_S 秒的預先簽署 URL,資料直接從 bucket 傳到瀏覽器(此時瀏覽器必須自己連得到 bucket 的位址)。與 transfer 後端不同,寫檔節點的檔案不會放進控制面的 models/ 資料夾:本機 run 要讀它之前,請先下載。
憑證。 CODEFYUI_S3_CREDENTIALS 決定 worker 拿到什麼:
sts:向 endpoint 的 STS 取得暫時憑證(AssumeRole),附帶 session policy,只允許寫入runs/<run id>/之下,以及讀取該 run 的輸入,有效期為CODEFYUI_S3_STS_DURATION_S(預設一小時)。只要 run 還在執行,控制面會在過期前送出新的憑證。MinIO 用一般使用者的 key 就能發出這種憑證;AWS 則需要可 assume 的 role(CODEFYUI_S3_STS_ROLE_ARN)。無法取得憑證的 run 會在送出之前失敗。static:控制面自己的 key。請給 CodefyUI 一個只能存取這個 bucket 的使用者。auto(預設):先用sts,STS 拒絕時改用控制面自己的 key,並在伺服器 log 留下警告;之後 10 分鐘內,或在 S3 設定變更之前,不再向 STS 要求。
憑證隨 run 的指派送出,與它的機密參數相同(Agent 與控制面之間的信任):控制面位址是一般的 http:// 時不會加密。Agent 只把它們放在記憶體中,不寫入檔案或 log,run 的子程序也拿不到。控制面從環境變數讀取自己的 key,不寫入任何資料庫或 log。
清理。 Run 保留機制與 刪除 會移除 run 在 runs/<run id>/ 下的物件,並中止它未完成的上傳,但保留 output/(寫檔節點的檔案會保留,與 transfer 後端把它們留在資料夾中相同),以及保留機制移除以 interrupted 結束的 run 時,它的 checkpoint/。當時連不上的 bucket 會記錄在 log 中,物件保留不動;刪除 時回應的 warning 也會說明。沒有任何 run 資料列指向的東西(保留下來的 output/ 資料夾、inputs/、已終止的 worker 留下的上傳)交給 bucket 的 lifecycle 規則處理。在 AWS S3 上:
{"Rules": [
{"ID": "codefyui-abandoned-uploads", "Status": "Enabled", "Filter": {"Prefix": "runs/"},
"AbortIncompleteMultipartUpload": {"DaysAfterInitiation": 1}},
{"ID": "codefyui-inputs", "Status": "Enabled", "Filter": {"Prefix": "inputs/"},
"Expiration": {"Days": 30}}
]}
(aws s3api put-bucket-lifecycle-configuration --bucket codefyui --lifecycle-configuration file://rules.json;若設定了 CODEFYUI_S3_PREFIX,prefix 要加在它之下)。MinIO 不接受 AbortIncompleteMultipartUpload:它會在 24 小時後自行移除未完成的上傳(mc admin config get lab api 會顯示 stale_uploads_expiry),因此只需要過期規則:mc ilm rule add lab/codefyui --prefix "inputs/" --expire-days 30。過期的輸入檔會由下一個需要它的 run 重新上傳。
每筆資料列會記下它的 bucket,key 也帶著當時的 prefix,因此之後變更 CODEFYUI_S3_BUCKET 或 CODEFYUI_S3_PREFIX 只影響新的 run:先前 run 的檔案仍在原處列出、下載與移除(控制面的 key 必須仍能存取舊的 bucket)。
讓 worker 保持同步
Worker 必須執行與控制面相同的 CodefyUI 版本,也應該認得相同的節點。Agent 每次連線時,控制面都會檢查這兩項。
版本。 版本不同的 agent 會被拒絕,原因中列出雙方版本,例如 version mismatch: worker 3.1.0, control plane 3.1.2。Agent 會停止而不重試,Workers 面板則在 被拒的連線 下列出該名稱與原因,所以一直沒出現的 worker 會說明原因;試圖搶用已連線 worker 名稱的 agent 會連同位址列在那裡,不會顯示在該 worker 的卡片上。版本會在 agent 第一則訊息中最先被讀取,所以即使訊息格式已改變的 agent 也會得到這個原因。要更新時,請在控制面與每個 worker 上執行 cdui update(或安裝相同的 CODEFYUI_RELEASE_TAG),再重新啟動 agent;該名稱連線成功後,拒絕紀錄就會清除。面板會保留每個名稱最近一次的拒絕,直到伺服器重新啟動。
節點型別。 外掛、Package Center 套件與自訂節點都是每台機器各自安裝。Agent 啟動時,一個短暫的輔助程序會以 run 的方式載入所有節點,回報節點型別與其定義的雜湊(節點目錄)。雜湊與控制面不同時,worker 會標為 Degraded,卡片列出 worker 缺少的型別,以及只有它有的型別。降級的 worker:
- 缺少控制面有的型別時,永遠不會收到 target 為
any的 run(只多出型別的 worker 仍可接收); - 只有在圖用到的每個節點型別它都有時,才會收到 run:包括展開 preset 與 subgraph 之後的節點,以及 Map 節點所執行的每個已安裝 preset 中的節點。否則送出時就會被拒絕並列出缺少的型別;佇列中的 run 在送出前也會以同樣方式失敗,以防 worker 在等待期間以較少的型別重新連線。
要解除降級,請在 worker 上安裝相同的外掛與套件(cdui plugin install ...、Package Center、相同的自訂節點檔)並重新啟動 agent:它只在啟動時回報節點。輔助程序失敗的 worker 不會回報節點目錄:它的卡片會顯示警告,any 會略過它,指定它的 run 則不經檢查直接送出(agent 的 log 會說明輔助程序失敗的原因)。目前還沒有把控制面的外掛組合複製到 worker 的指令。
環境指紋。 同一個輔助程序也會收集 worker 的環境指紋(CodefyUI commit、Python、torch build、加速器、外掛版本、影響數值的環境變數)。Workers 面板顯示它的摘要,worker 執行的每個 run 開始時都會記錄這個摘要(重新連線時未回報指紋的 worker 會顯示「未回報」,不會沿用舊的摘要)。接著 run 子程序回報它所載入之程式碼、外掛與自訂節點的指紋並取而代之(execution_facts.fingerprint_source: "worker-run"),所以 agent 啟動後 checkout 或外掛有變動的 worker,記錄的是實際執行的內容,而不是 agent 當時看到的內容。兩者描述的都是 agent 所見的機器裝置,在 run 的裝置集合縮小 CUDA_VISIBLE_DEVICES 之前,所以沒有任何變動時兩個摘要相同。子程序也會回報它的 torch 數值設定(execution_facts.resolved.numerics)。見執行環境。
連線中斷時
Worker 的連線關閉,或連續錯過三次心跳(30 秒)時,即視為斷線。它的 run 會在 worker 上繼續執行,worker 把事件存在磁碟上,直到控制面確認已經記錄。每個 run 的狀態變成 disconnected,並留下類型為 worker_disconnected 的 run_warning;名額仍然保留,執行任務面板會顯示 worker 已斷線多久(GET /api/runs/{id} 以 worker_status 回報)。接下來會是三種情形之一:
- Worker 在寬限時間內重新連線(
CODEFYUI_WORKER_RESUME_GRACE_S,5 分鐘):它依序補送控制面尚未記錄的事件,run 回到running,並留下類型為worker_reconnected的run_warning。事件紀錄沒有缺漏,也沒有重複。 - 沒有重新連線:run 以
interrupted結束,名額釋放。如果 worker 之後回來且 run 仍在執行,會被要求立即停止,讓這張卡留給下一個 run;run 在 worker 上的結束方式會記錄為 run 的late_result({status, error, worker_id, at},見GET /api/runs/{id}與execution_facts),狀態與事件紀錄維持原樣。 - 控制面在這段期間重新啟動:重新啟動的控制面會接手這個 run,如下所述。
控制面重新啟動時。 控制面若沒有先停止它的 run 就結束(被強制結束、當機、筆電斷電),worker 上的 run 會繼續執行:子程序照常進行,agent 也會保留它們的事件。控制面下次啟動時會接手每一個這樣的 run,而不是把它結束。這個 run 會顯示為 disconnected,並留下類型為 worker_disconnected、說明伺服器已重新啟動的 run_warning;它會重新占用原本的名額,事件紀錄也沿用同一份。它的 worker 重新連線(SSH worker 的通道由控制面自己重新啟動)且仍有這個 run 時,run 會回到 running,從控制面最後確認的位置接續:停止前最後約一秒內的事件可能出現兩次,但不會缺漏。若 worker 未在 CODEFYUI_WORKER_RESUME_GRACE_S(從重新啟動起算)內重新連線,run 會以 interrupted 結束,原因為 it did not reconnect with the run within 300 s after this server restarted;若 worker 重新連線時已沒有這個 run(它的 agent 也重新啟動過),run 會立即結束。Transfer run 的上傳會在重新啟動後繼續,之前已收到的檔案仍屬於這個 run。s3 run 的 worker 手上的憑證由先前的程序核發,因此它一重新連線就會收到新的憑證,之後照常續期。在控制面自身執行的 run、仍在佇列中等待的 run,以及由 CodefyUI 3.1.0 啟動的 worker run(它沒有記錄接續的位置),則與以前一樣以 interrupted 結束。在 worker 離線期間按下的停止會跨越重新啟動保留,worker 回來時再送出。被接手的 run 仍在等 worker 時若正常關閉控制面,這個 run 會立即以 interrupted 結束;在關閉的寬限時間內沒有停下的 worker run 也會以 interrupted 結束;worker 下次連線時會被要求停止它。正常關閉(systemctl stop、Windows 上的 Ctrl+C)仍會停止 worker 上的 run(執行佇列:伺服器停止時),所以不會留下需要接手的 run。
CODEFYUI_WORKER_RESUME_GRACE_S 由控制面讀取;設為 0 則 worker 一斷線就結束它的 run,重新啟動的控制面也不會接手任何 run。SSH worker 的 agent 也以同樣的寬限時間決定何時不再等待通道(連線中斷)。
Agent 與控制面之間的信任
送到 worker 的 run 會帶著填回 secret 參數值(API 金鑰、密碼)的圖,所以兩端都必須確認對方是誰。控制面以 worker 憑證認出 agent;agent 則以 TLS 憑證認出控制面,加入請求、每一條 /ws/worker 連線與每一次檔案傳輸都做同一個檢查。這對共用實例上的 SECRET 參數代表什麼,見共用實例:送到 worker 的執行。
CodefyUI 本身不提供 TLS;請把它放在 TLS proxy 後面(TLS proxy 後方),並給 worker https:// 位址,或使用 SSH 方式,由 SSH 連線加密通道。
Worker 的 CA 信任的憑證(公開 CA,或安裝在 worker 上的公司 CA):不需要做任何事。Agent 每次連線都像瀏覽器一樣檢查憑證機構與主機名稱,且不記錄任何東西,所以憑證續期(即使換了金鑰)仍可繼續使用。
自簽憑證,或 worker 沒有的 CA:釘選它的金鑰。 --ca-fingerprint 接受伺服器憑證公鑰(SubjectPublicKeyInfo)的 SHA-256,寫成 sha256:<64 個十六進位字元>,做法與 kubeadm join --discovery-token-ca-cert-hash 相同:
cdui agent start --control https://cp.lab --token abcdef.0123456789abcdef \
--ca-fingerprint sha256:96d448128bb7dfbceea1a628393cfdd0f1f86716ad81deb4fab0c0fe281a0690
參數名稱沿用,但它釘選的是伺服器自己(leaf)憑證的金鑰,從不是 CA 的:在任何 Python 版本上,釘選值只比對 TLS 交握證明過其金鑰的那張憑證。有釘選時,agent 不檢查憑證機構,也不檢查主機名稱。不符的憑證會在送出 token 之前就被拒絕,並印出兩個指紋,agent 以代碼 2 結束。釘選值會記在憑證檔(<user-data>/agent/<host>-<port>.token),之後啟動不帶參數也會沿用;存下的值若不是合法指紋,會在日誌中指名該檔案回報並忽略(worker 憑證仍可使用)。
要取得指紋,請在 proxy 主機上對 proxy 使用的憑證檔執行(fullchain.pem 中第一張是 leaf):
openssl x509 -in /path/to/fullchain.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 | awk '{print "sha256:" $NF}'
位址是 https:// 時,新增 worker 對話框會顯示這個指令。若控制面自己讀得到憑證檔,把 CODEFYUI_TLS_CERT_FILE 設為它的路徑:對話框就會顯示指紋,產生的指令也會帶上 --ca-fingerprint(伺服器只在檔案改變時重新讀取)。Worker 的 CA 信任該憑證時請不要設定,以免指令釘選一把續期時就會換掉的金鑰。
Worker 的 CA 不接受、又沒有釘選的憑證不會被使用:加入會失敗;執行中的 agent 會記錄錯誤與原因(第一次會附上伺服器出示的指紋,供你與對話框比對後用 --ca-fingerprint 帶入),並以平常的退避間隔重試,所以修好 proxy 或更新 CA 清單後不必重啟。
已釘選的憑證換了金鑰之後,先在 proxy 主機上確認新指紋,然後擇一:
- 執行
cdui agent start --control <url> --ca-fingerprint <新指紋>:保留 worker 憑證,連線成功後記錄新的釘選值;或 - 執行
cdui agent forget <url>:刪除憑證與其中的釘選值,再用新的 token 重新加入。
續期的憑證只有在沿用金鑰時(certbot 的 --reuse-key)指紋才不變。
未加密的 HTTP。 以 http:// 位址連到另一台機器時,cdui agent start 會印出一次警告:worker token 與每個指派中的 secret 參數都以明文傳輸。Agent 仍會連線。Workers 面板會把這種 worker 標成 未加密 HTTP:它的連線從另一台機器未加密地抵達伺服器。伺服器依連線的 scheme(只有 uvicorn 回報 wss 才算,uvicorn 只對 --forwarded-allow-ips 列出的 proxy 這麼做;CodefyUI 本身從不讀 X-Forwarded-Proto)與對端位址判斷:loopback 對端,例如同一台機器的 agent 或 SSH 通道的本機端,不會被標記,因為跨越網路的不是那條 socket。同樣的理由,http://127.0.0.1 位址不會讓 agent 印出警告。
在 worker 上。 Agent 會在記憶體中把 run 的 secret 值交給它的子程序,自己不會寫入任何檔案,但 worker 機器在 run 執行期間持有它們:管理那台機器的人等同被託付這些值。Worker 憑證檔只有 agent 的作業系統使用者可讀。
在控制面上。 已加入的 worker 可以把檔案寫進控制面的 models/、media/ 與 output/ 資料夾,並記錄為它的 run 可下載的 artifact(沒有共用檔案系統),所以只加入你信任的機器。
不涵蓋:mutual TLS(agent 由 worker 憑證識別)、由 CodefyUI 管理的憑證機構,以及使用者身分。
以服務執行 agent
在 Linux GPU 伺服器上,請以 systemd 執行 agent,讓它開機時啟動、崩潰後自動重啟。這與部署:systemd unit 中伺服器的 unit 對應:安裝在 /opt/codefyui,由 codefyui 使用者擁有。
先以服務使用者加入一次,讓憑證存進該使用者的資料目錄,再停止指令啟動的背景 agent:
sudo -u codefyui /opt/codefyui/cdui agent start --control https://codefyui.example.com --token abcdef.0123456789abcdef
sudo -u codefyui /opt/codefyui/cdui agent stop
需要時在這裡加上 --name、--mount 與 --ca-fingerprint:名稱與釘選值存在憑證檔,掛載存在 agent.json,所以 unit 不必重複。
[Unit]
Description=CodefyUI worker agent
After=network-online.target
Wants=network-online.target
# On an NFS-backed --mount, also: After=remote-fs.target
[Service]
Type=exec
User=codefyui
Group=codefyui
WorkingDirectory=/opt/codefyui
# --foreground on purpose: systemd is the supervisor, so the agent must not
# daemonize away from it. The stored credential (from the enrollment above)
# is used; no token is needed here.
ExecStart=/opt/codefyui/cdui agent start --foreground --control https://codefyui.example.com
Restart=on-failure
RestartSec=5s
# On SIGTERM the agent asks its runs to stop and terminates them after 10 s.
TimeoutStopSec=60s
# SIGTERM to the agent only: with the default (control-group) systemd also
# signals the run subprocesses directly, and a running run ends `failed`
# instead of `cancelled`. The rest of the group is killed after TimeoutStopSec.
KillMode=mixed
# Deliberately NOT PrivateDevices (breaks GPU access) or ProtectHome /
# ProtectSystem=strict (both break writes to the user data dir under
# /opt/codefyui/.codefyui_dev/, where the credential, spool and run folders live).
NoNewPrivileges=yes
PrivateTmp=yes
[Install]
WantedBy=multi-user.target
sudo install -m 644 codefyui-agent.service /etc/systemd/system/codefyui-agent.service
sudo systemctl daemon-reload
sudo systemctl enable --now codefyui-agent
journalctl -u codefyui-agent -f
--foreground 是必要的,理由與伺服器相同:沒有它時,cdui agent start 會啟動背景 agent 後就結束,而 systemd 會把 Type=exec 服務主程序的結束視為服務結束。因版本不同而被拒的 agent 會以錯誤結束,Restart=on-failure 會每 5 秒重試一次直到你更新;更新期間請先 systemctl stop codefyui-agent。請保留 KillMode=mixed:它讓 agent 自己停止它的 run,執行中的 run 會以 cancelled 結束(訓練迴圈會寫下中斷 checkpoint);使用 systemd 的預設值時,run 子程序會直接收到 SIGTERM,run 會以 failed 結束,錯誤為 run subprocess exited with code -15。SSH worker 不需要 unit:控制面會讓它的 agent 持續執行。
單次 worker:SLURM 與 ssh
cdui agent start --one-shot 只接一個 run,回報後就結束,類似以 --ephemeral 啟動的 GitHub runner。在批次 job 或 ssh 指令中啟動它:由批次系統決定 job 拿到哪台機器與哪張 GPU,CodefyUI 負責送出圖並記錄結果。
cdui agent start --one-shot --control https://codefyui.example.com
- 哪些 run 會送到它。 任一可用 不會把 run 送到單次 worker。只有指名該 worker(
"target": {"worker": "<名稱>"})或單次 worker 池的 run 會送到它:Queue on... 中的 單次 worker,或在POST /api/runs中使用"target": {"pool": "one-shot"}(並以device指定需要的裝置類型,例如cuda)。池中的 run 不會因為沒有 worker 而被拒絕:它會等待提供該裝置的單次 worker,queue_reason會說明,並在CODEFYUI_WORKER_ONE_SHOT_QUEUE_TIMEOUT_S(預設 24 小時,0表示一直等)後失敗。池中的 run 依裝置類型各排一列,先送出的先處理。--run-id <id>讓 agent 只接該 run,不論它在佇列中的哪個位置;該 run 必須在池中等待或指名此 worker,否則 agent 連線時就會被拒絕。 - 不錄製輸出。 開啟 錄製節點輸出 的 run 送往單次 worker 池或單次 worker 時會被拒絕:agent 在 run 結束時就會退出,檢視器永遠無法讀取它錄製的資料。
- 結束碼。
0:run 成功且控制面已記錄。1:run 失敗、被取消或以interrupted結束,或結果無法送達。2:控制面拒絕這個 agent(版本不同、名稱被另一個 agent 占用、--run-id指的 run 不是在等它),或參數錯誤。3:agent 在 run 送達前被停止(SIGTERM、Ctrl-C、job 的時間上限)。4:無法啟動(加入失敗,或--mount被拒)。agent 接了 run 之後才被拒絕(控制面拒絕它重新連線)為1:run 的結果沒有被記錄。 - 連線中斷。 run 會繼續執行,agent 會像一般 agent 一樣重新連線(連線中斷時)。它要等控制面確認收到 run 的結束才會退出;若 run 結束後
CODEFYUI_WORKER_RESUME_GRACE_S內仍未確認,它仍會以1退出,並把結果留在暫存檔,交給之後使用同一個 user-data 資料夾的 agent 補送。 - 結束之後。 Workers 面板以 單次 標記並顯示 離線,單次 run 連到它接的 run(或說明它未接 run 就結束)。下一個 job 以同一名稱啟動的 agent 會接下一個 run。控制面會記下該 worker 自己的上一個 agent 是單次 agent,所以控制面重新啟動後,指名它的 run 立即依
CODEFYUI_WORKER_ONE_SHOT_QUEUE_TIMEOUT_S計時,不必等它的下一個 agent 連線。執行個體(見下方)不會寫入這項紀錄:請透過單次池(target.pool: "one-shot")把 run 交給 array,不要用指名憑證的 run。指名執行個體(<名稱>.<ID>)的 run 一律依此計時。 - 同一名稱一次一個 agent。 憑證綁定 worker 名稱,所以用同一份憑證啟動的兩個 job 會共用名稱。單次 worker 連線中或它的 run 仍在進行時,以同一名稱連線的另一個 agent 會被拒絕,結束碼
2。要同時執行多個 job,請使用執行個體(見下一點)。 - 執行個體:一張憑證給多個 job。
--instance [ID](僅限單次 agent)讓 agent 成為共用同一張憑證與同一個 user-data 資料夾的多個 agent 之一,例如 SLURM array 的各個 task。它以<名稱>.<ID>登記;不給值時,ID在 array task 中為<SLURM_ARRAY_JOB_ID>-<SLURM_ARRAY_TASK_ID>,在其他 job 中為SLURM_JOB_ID,否則為 16 個隨機十六進位字元;在 job step(srun)中會再加上-<SLURM_STEP_ID>-<SLURM_PROCID>,所以srun -n 4的各個 task 各有自己的 ID。每個執行個體都是獨立的單次 worker:只接一個 run(池中的 run,或指名<名稱>.<ID>的 run);它占用名稱與 run 的期間,其他執行個體都拿不走,控制面重新啟動後也一樣。憑證上的設定適用於每個執行個體:Drain、每張卡的裝置准入策略(透過執行個體設定的策略會存在憑證上),以及CODEFYUI_RUN_QUEUE_MAX_CONCURRENT中的<名稱>/<裝置>上限(<名稱>.<ID>/<裝置>可為單一執行個體另行設定)。執行個體不會改動憑證本身的紀錄(它的裝置、單次旗標),所以憑證的一般 agent 可以與它的執行個體同時連線。Workers 面板把執行個體列在憑證的卡片內(共用憑證,並顯示幾個已連線、幾個執行中):仍在執行的各有一張卡片,已離開的各占一行。在憑證上按 移除 會撤銷憑證,所有執行個體都會中斷連線,正在執行的 run 以interrupted結束,等待任一執行個體的 run 則會失敗。執行個體要等它離開後才有 移除,而且只會把那一行從清單移除;已連線(或仍有 run 在進行)的執行個體無法單獨移除,因為它的 agent 會以手上的 token 重新連線。同時啟動、帶著 enrollment token 且尚未存有憑證的 task 會在 user-data 資料夾中取得一個鎖:第一個 task 加入(token 只需一次使用次數),其他 task 使用它存下的憑證。在不支援檔案鎖的檔案系統上(以noflock掛載的 Lustre、部分 NFS),agent 會記錄一則警告並在沒有鎖的情況下繼續:請先完成一次加入再啟動 array,並讓每個 task 使用不同的 ID。執行個體把 run 的檔案放在<user data>/agent-instances/<ID>/,結束時刪除,所以它沒能送達的結果之後不會由其他 agent 補送。被強制終止的執行個體(SIGKILL、OOM killer、節點重新開機)會留下這個資料夾;下一個啟動的執行個體會在沒有 agent 占用該 ID、且資料夾內 24 小時沒有變動時刪除它。若以同一個 user-data 資料夾中仍在執行的執行個體 ID 再啟動一個 agent,它會在動到該資料夾之前以2結束。面板為每張憑證保留最近 50 個已結束的執行個體,直到控制面重新啟動。 - 它永遠在前景執行,不寫 pidfile,所以不會與同一台機器上的背景 agent 衝突。沒有批次系統的時間上限時,沒有 run 送達的 agent 不會自行結束:若
ssh指令不能一直等待,請用timeout包住。
SLURM
先以 Add worker 產生 enrollment token(一次使用次數即可),再送出 job。下方 array 的所有 task 同時以同一張憑證執行,各自是一個執行個體(slurm.<array job>-<task>)。user-data 資料夾必須位於每個節點都共用的檔案系統上,家目錄通常就是:
#!/bin/bash
#SBATCH --job-name=codefyui-run
#SBATCH --gres=gpu:1
#SBATCH --cpus-per-task=8
#SBATCH --time=04:00:00
#SBATCH --array=0-3
#SBATCH --output=codefyui-%A_%a.log
module load cuda/12.4 # 依叢集的模組名稱
export CODEFYUI_USER_DATA_DIR="$HOME/.codefyui-agent" # 所有 task 共用
CDUI="$HOME/CodefyUI/cdui"
CONTROL=https://codefyui.example.com
# 最先啟動的 task 用 token 加入;其他 task 與之後的 job 都沿用已存的憑證。
"$CDUI" agent start --one-shot --instance --control "$CONTROL" \
--name slurm --token "$CODEFYUI_ENROLL_TOKEN"
先以 Queue on... → 單次 worker 排入 run(或以同一目標建立 New sweep:每個 task 接一個 variant),再用 CODEFYUI_ENROLL_TOKEN=abcdef.0123456789abcdef sbatch codefyui.sbatch 送出。SLURM 會把分配到的 GPU 設在 CUDA_VISIBLE_DEVICES,agent 以 cuda:0 提供那張卡。到達 job 的時間上限時 SLURM 會送出 SIGTERM:agent 會停止它的 run(interrupted,訓練迴圈會寫出中斷檢查點)並以 1 退出。要從腳本執行某個特定的 run,傳入它的 id:cdui agent start --one-shot --run-id <run id> ...;直接用 ssh gpu-box 'cd ~/CodefyUI && ./cdui agent start --one-shot --run-id ...' 也是這個寫法。
限制
- 畫布在控制面執行。 Run 一律在那裡執行,連同它的快取與持久化權重;要把分頁的圖送到 worker,請使用排入佇列...。
- 擷取的輸出保存在 worker 上。 開啟錄製節點輸出時,worker run 的擷取資料保存在 worker 上,Inspector 經由它的連線讀取,因此只有 worker 連線時才能讀取(#789,執行架構:Worker 上的錄製輸出)。Worker run 每次都從新的權重開始。
- 依記憶體准入需逐卡開啟。 Run 與在控制面上一樣,以每個裝置名額的數量准入,除非該卡設為 依記憶體共用(多 GPU:共用一張卡)。
- 一個 run,一台機器。 Run 只使用一個 worker 的裝置。
strategy: ddp的TrainingLoop會在該 worker 的多張卡上訓練(多 GPU 執行);沒有跨機器的訓練。 - 以 Linux 為主。 Worker 在 Linux 上開發與測試;macOS 可用,Windows 盡力支援。
- 正常停止控制面會結束 worker 上的 run。 關閉時會停止 worker 上正在執行的 run,與停止本機 run 相同(執行佇列:伺服器停止時);只有在沒有停止它們就結束的控制面,才會在下次啟動時接手(連線中斷時)。
設定
在控制面上:
| 設定 | 預設值 | 說明 |
|---|---|---|
CODEFYUI_EXTRA_ALLOWED_HOSTS | (空) | Worker(或瀏覽器)連線用的主機名稱,例如 gpu-gw.lan:8000 |
CODEFYUI_WORKER_RESUME_GRACE_S | 300 | 斷線 worker 的 run 等待它的秒數;0 立即結束 |
CODEFYUI_WORKER_OUTPUT_FETCH_TIMEOUT_S | 60 | 讀取 worker run 錄製輸出時等待 worker 回覆的秒數,逾時回 504 |
CODEFYUI_WORKER_ONE_SHOT_QUEUE_TIMEOUT_S | 86400 | run 等待單次 worker 的秒數,逾時即失敗(單次 worker);0 表示一直等 |
CODEFYUI_TLS_CERT_FILE | (空) | Proxy 的憑證(PEM,leaf 在前),讓新增 worker 對話框顯示它的指紋 |
CODEFYUI_MOUNT_LABEL | data | 控制面資料夾的 label |
CODEFYUI_TRANSFER_ENABLED | 1 | 0 關閉 transfer 後端 |
CODEFYUI_TRANSFER_MAX_INPUT_BYTES | 2 GiB | 一個 run 經由傳送的輸入檔總大小上限 |
CODEFYUI_MAX_UPLOAD_SIZE | 500 MB | Worker 可上傳的單一輸出檔上限;所有檔案上傳都適用同一上限 |
CODEFYUI_S3_ENABLED | 0 | 1 讓沒有共用資料夾的 worker 上的 run 經由 S3 或 MinIO bucket 傳送檔案(S3 或 MinIO bucket) |
CODEFYUI_S3_ENDPOINT_URL | (空) | Bucket 的 API 位址,例如 http://minio.lab:9000;AWS S3 則留空 |
CODEFYUI_S3_REGION | us-east-1 | Bucket 的 region |
CODEFYUI_S3_BUCKET | (空) | Bucket |
CODEFYUI_S3_PREFIX | (空) | 加在每個 key 前面,例如 codefyui/ |
CODEFYUI_S3_ADDRESSING_STYLE | auto | 多數 MinIO 環境用 path,或 virtual |
CODEFYUI_S3_ACCESS_KEY_ID、CODEFYUI_S3_SECRET_ACCESS_KEY | (空) | 控制面存取 bucket 的 key,只保存在記憶體中 |
CODEFYUI_S3_CREDENTIALS | auto | Worker 拿到的憑證:sts(暫時、只限它的 run)、static(上面的 key)或 auto(先 STS,否則用上面的 key) |
CODEFYUI_S3_STS_ROLE_ARN | (空) | AWS 上要 assume 的 role;MinIO 會忽略 |
CODEFYUI_S3_STS_DURATION_S | 3600 | 暫時憑證的有效秒數(900 到 43200);到期前會送出新的 |
CODEFYUI_S3_DOWNLOAD | proxy | Bucket 中檔案的 下載:proxy 經由控制面轉送,presign 導向預先簽署的 URL |
CODEFYUI_S3_PRESIGN_TTL_S | 900 | 預先簽署下載 URL 的有效秒數 |
CODEFYUI_SSH_REMOTE_PORT_RANGE | 18000-18100 | SSH worker 的槽位編號:決定 worker 在遠端主機上的狀態資料夾(~/.codefyui-ssh/<id>-<槽位>/,通道的 socket 就在裡面),也是它存放憑證所用的位址 |
CODEFYUI_SSH_STRICT_HOST_KEY_CHECKING | accept-new | 設為 yes 時要求主機已在 known_hosts 中 |
在 worker 上,CODEFYUI_RUN_OUTPUT_STORE_MAX_MB(2048)限制它保留的錄製輸出,CODEFYUI_WORKER_OUTPUTS_WRITE_TIMEOUT_S(300)是 run 結束後其子程序寫出錄製輸出的時間上限(正常的 cdui agent stop 最多等待這麼久讓寫入完成);未給 --token 時由 CODEFYUI_AGENT_TOKEN 提供 token(CLI 指令:環境變數)。
另請參閱
- 多 GPU 執行——伺服器本機上的 worker、裝置集合、名額與 Workers 面板
- 執行佇列:Workers——
target如何放置 run - 部署——TLS proxy 與伺服器的 systemd unit
- 執行架構——worker 協定、不變量與設計決策
- API 參考——
/api/workers端點與 worker token