在 GPU 伺服器、SLURM 叢集或 NAT 後的機器上訓練
把 Potato 的訓練執行傳送到 ssh GPU 伺服器、SLURM 計算節點或輪詢代理,附每種後端的配置、檢查命令和常見故障。
v2.14.0 新增
本教程把 Potato 的模型訓練從標註伺服器移到帶 GPU 的機器上。教程寫給這樣的研究管理員:標註伺服器是一臺小型雲虛擬機器或一臺筆記本,而 GPU 在實驗室伺服器、大學叢集,或伺服器無法連線的工作站上。對於三種遠端後端,本頁依次介紹何時選擇該後端、遠端機器需要安裝什麼、配置、檢查命令、執行期間發生什麼、如何取消執行,以及你最可能遇到的故障。教程假定你已有一個能在標註伺服器上訓練的項目,例如訓練你的第一個模型中的評論項目。
執行可以在四個地方執行
後端是訓練執行執行的地方。Potato 始終有 local 後端,即標註伺服器上的一個子程序。其他後端在 model_training.backends 下命名:
| 類型 | 訓練器在哪裡執行 | 誰啟動遠端一側 | 模型檔案最終在哪裡 |
|---|---|---|---|
local | 標註伺服器,在子程序中 | Potato | 該次執行的 artifacts/ 目錄 |
ssh | Potato 能通過 ssh 到達的機器,例如實驗室 GPU 伺服器 | Potato,通過 ssh | 複製回 artifacts/ |
slurm | 叢集計算節點,作為批處理作業 | Potato,通過 ssh 連到登入節點,再 sbatch | 複製回 artifacts/ |
agent | Potato 無法到達的機器,例如 NAT 後的工作站 | 你,用 potato training agent | 在訓練機器上 |
當標註伺服器能用 ssh 金鑰登入 GPU 機器時,選擇 ssh。當 GPU 位於 SLURM 排程器之後,而你能登入的機器是登入節點時,選擇 slurm。當伺服器無法向 GPU 機器發起任何連線時,選擇 agent,因為所有連線都由代理自己建立。家用路由器或校園防火牆後的工作站是使用代理的常見情形。無論哪種後端,預測和該次執行的結果都會到達伺服器,所以不管訓練器在哪裡執行,回寫和留出得分的工作方式都相同。除 decision-calibrate 外,每個訓練器都可以在任何後端上執行。
項目配置
下面的配置是第一個教程中的評論項目,加上了全部三種遠端後端。真實項目只需要你用到的後端,每種後端在下文各有一節。max_wall_seconds: 3600 會停止任何一小時內沒有完成的執行。在 ssh 或 agent 後端上,伺服器每秒檢查一次該限制,所以在遠端機器上卡住的執行佔用執行佇列的時間不會超過這個限制:
annotation_task_name: "Review Sentiment"
task_dir: "."
data_files:
- "data/reviews.json"
item_properties:
id_key: "id"
text_key: "text"
output_annotation_dir: "annotation_output"
admin_api_key: "change-me"
annotation_schemes:
- annotation_type: "radio"
name: "sentiment"
description: "Is this review positive or negative?"
labels: ["positive", "negative"]
model_training:
enabled: true
backend: gpu-server # the default for runs; leave out to keep runs local
max_wall_seconds: 3600
writeback:
schemas: ["sentiment"]
include_unlabeled: true
min_confidence: 0.6
backends:
gpu-server:
type: ssh
host: gpu.example.org
python: /opt/conda/bin/python3
gpus: auto
min_free_gpu_mb: 4000
cluster:
type: slurm
host: login.cluster.example.org
python: /opt/conda/bin/python3
job_python: /shared/envs/train/bin/python
remote_dir: /shared/projects/me/potato-training
gres: gpu:1
time: "04:00:00"
prelude: ["module load cuda/12.4"]
lab-workstation:
type: agent
queue_timeout_s: 3600啟動伺服器之前先驗證配置:
potato validate config.yaml --strict驗證器會報告後端類型不接受的鍵、沒有 host 的 ssh 或 slurm 後端、位於 /tmp 下的 SLURM remote_dir,以及指向不存在的後端的 model_training.backend。遇到同樣的錯誤,伺服器也不會啟動,所以拼錯的 pyhton 會在啟動時讓伺服器停下,而不是把執行送到預設的 python3。
Runs on 選單和 API
/admin/training 的訓練頁面有一個 Runs on 選單,列出每個後端及其類型和主機,例如 gpu-server (ssh to gpu.example.org)。model_training.backend 決定選單最先選中哪一項,Runs 表則在 Ran on 列中記錄每次執行在哪裡執行。通過 API 啟動的執行,在請求體的 backend 欄位中指明它的後端:
curl -X POST -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
http://localhost:8000/admin/training/api/runs \
-d '{"trainer": "hf-text-classification", "schemas": ["sentiment"], "backend": "cluster"}'配置中不存在的後端名稱會被以 409 拒絕。自動重新訓練使用 model_training.backend,除非 auto_retrain.backend 指定了另一個後端,auto_retrain 的全部內容見迴圈中的模型。無論哪種後端,同一時間只執行一次執行,在另一次執行進行期間啟動的執行會在佇列中等待。
在本機訓練
在把執行移到別處之前,先看一下本地執行會用到哪個裝置。使用預設的 device: auto 時,本地執行先用 CUDA,再用 Apple 的 Metal(mps),最後用 CPU,而 device: cuda:1 則指定一塊 GPU。檢查命令會列印本地執行將使用的裝置:
potato training check local config.yaml在 Apple silicon 的 Mac 上,該命令會列印 local: this machine, Apple Metal (mps)。你所要求的裝置如果不存在,會退回到 CPU 或另一個加速器,並在執行日誌中給出警告。當結果是 CPU,而訓練器要微調 transformer 時,請把執行移到下面的某個遠端後端。配置列出了本地執行的 device、num_threads 和 max_ram_mb。
通過 ssh 連線的 GPU 伺服器
ssh 後端在標註伺服器能通過 ssh 到達的機器上執行訓練器,通常是實驗室 GPU 伺服器。Potato 自己啟動遠端一側的一切,所以除 Python 和訓練器的包之外,無需手工在那裡安裝或啟動任何東西。GPU 主機無需安裝 Potato,因為 Potato 會在第一次執行時把自己的 Python 原始碼複製到主機上,並在原始碼改變之前一直複用該副本。
ssh 後端的要求
標註伺服器所用的賬戶需要一個能無提示登入 GPU 主機的 ssh 金鑰。Potato 用 BatchMode=yes 執行 ssh,所以出現密碼提示時執行會立即失敗,而不會卡住。以該賬戶執行 ssh gpu.example.org true 必須成功,帶口令的金鑰在 ssh-agent 持有該金鑰時可用。host 的值可以是 ssh 接受的任何名稱,~/.ssh/config 中的別名也可以。user、port 和 identity_file 這幾個鍵會分別以 -l、-p 和 -i 傳給 ssh。
GPU 主機需要 Python 3.8 或更新版本,以及你所執行訓練器的包。對 hf-text-classification 而言,這些包是 torch、transformers 和 peft,即 potato-annotation[train-text] 這個 extra 的內容。訓練器頁面寫明瞭每個訓練器的安裝方式。當主機的直譯器缺少某個包時,可以把 python 指向一個有該包的直譯器,或把該包列在 packages 下。設定了 packages 後,Potato 會在 remote_dir 下用 --system-site-packages 建立一個虛擬環境,並把列出的包一次性裝進該環境。主機自帶的 CUDA 版 torch 在環境裡仍然可見:
model_training:
enabled: true
backends:
gpu-server:
type: ssh
host: gpu.example.org
python: /opt/conda/bin/python3
packages: ["transformers>=4.46.0", "peft>=0.13.0"]
gpus: auto
min_free_gpu_mb: 4000
num_threads: 8使用 gpus: auto 時,每次執行選取空閒視訊記憶體最多的 GPU,當沒有 GPU 有 min_free_gpu_mb(預設 2000)的空閒視訊記憶體時拒絕啟動。Potato 比較的是空閒視訊記憶體而不是利用率,因為在共享伺服器上,利用率低的顯示卡也可能所剩視訊記憶體無幾。請把 min_free_gpu_mb 設在接近訓練器所需視訊記憶體的位置。gpus: "1" 固定使用 1 號 GPU,"0,1" 固定使用兩塊 GPU,gpus: none 則在 CPU 上訓練。完全沒有 GPU 的主機在 auto 下也會在 CPU 上訓練。num_threads 為該次執行設定 OMP_NUM_THREADS、MKL_NUM_THREADS 和 OPENBLAS_NUM_THREADS。有些共享伺服器在登入 shell 中設定 OMP_NUM_THREADS=1,而 num_threads 會覆蓋登入 shell 的值。
ssh 後端的檢查命令
在標註伺服器上,以伺服器所用的賬戶執行檢查:
potato training check gpu-server config.yaml該命令會連線並報告遠端 Python 版本、每塊 GPU 的空閒視訊記憶體和利用率、一次執行會分到的 GPU,以及遠端直譯器缺少的訓練包。已安裝的 torch 沒有對應核心的 GPU 會標註為 (torch there has no kernels for it),gpus: auto 會跳過這樣的 GPU。--json 以 JSON 列印同樣的報告。當一次執行能夠啟動時,命令以 0 退出,否則以 1 退出。Runs on 選單旁的 Check 按鈕會在訓練頁面上運行同樣的檢查。
ssh 執行的步驟
每次執行,Potato 都執行這些步驟:
- 用
ssh連線,並開啟一個反向轉發,從遠端/tmp中的 unix 套接字回到標註伺服器。 - 檢查直譯器,讀取
nvidia-smi,並選定 GPU。 - 當主機缺少該版本時,把 Potato 的原始碼複製到
remote_dir/code/<hash>;當設定了packages而環境不存在時,建立虛擬環境。remote_dir預設為~/.cache/potato-training。 - 為這次執行啟動
python -m potato.training.agent。執行令牌通過 stdin 交給代理,從不出現在命令列上,因為共享伺服器上的每個賬戶都能看到命令列。 - 代理下載這次執行的條目、標籤和媒體包。代理執行與本地後端相同的訓練 worker,並通過套接字把進度、預測和結果送回。
- 執行成功後,Potato 把產物目錄複製回該次執行的
artifacts/,刪除遠端作業目錄,並關閉連線。
訓練頁面上的步驟行會跟隨這些階段,例如 copying Potato's code to gpu.example.org 和 waiting for the agent on gpu.example.org。執行日誌會寫明主機的 Python 和這次執行所分到的 GPU。
完整微調會把整個模型複製回來,在慢速鏈路上,一個 330 MB 的模型需要幾分鐘。步驟行會顯示模型已到達多少。大於 max_artifact_mb(預設 2048)的檔案會留在 GPU 主機上,並在執行日誌中列出。fetch_artifacts: false 會把每個模型檔案都留在 GPU 主機上,keep_remote: true 則保留遠端作業目錄以供檢查。執行頁面上推送到 Hugging Face Hub 的操作需要模型檔案在標註伺服器上,所以留在 GPU 主機上的模型無法從執行頁面推送。
unix 套接字以 0600 許可權建立,所以只有 Potato 登入時所用的賬戶才能通過該套接字連線。通過套接字發來的請求以迴環請求的身份到達標註伺服器。以 debug: true 啟動的伺服器會把管理員許可權授予迴環請求。於是在執行進行期間,任何能使用 GPU 主機上那個賬戶的人,都對除錯伺服器擁有管理員許可權。通過 ssh 訓練的伺服器上請保持 debug 關閉。
取消與心跳超時
訓練頁面上的 Cancel run 通過套接字到達代理,體現在對代理下一次進度報告的響應中。代理向 worker 傳送 SIGTERM,如果 worker 在 model_training.grace_seconds(預設 20,由伺服器隨作業傳送)之後仍在執行,則傳送 SIGKILL。來自舊版 Potato 伺服器的作業不帶 grace_seconds,此時代理會等待 20 秒。在伺服器上,一旦過了 grace_seconds,這次執行就以已取消結束,伺服器隨即關閉代理的 ssh 會話。代理在 heartbeat_timeout_s(預設 120)內沒有傳送任何內容的執行會失敗,執行目錄中的 agent.log 儲存著代理最後的輸出。
SLURM 叢集
slurm 後端適用於 GPU 位於 SLURM 排程器之後的叢集。在這樣的叢集上,Potato 能通過 ssh 到達的機器是沒有 GPU 的登入節點,而計算節點根本無法到達 Potato。因此 Potato 像 ssh 後端那樣在登入節點上啟動代理,由代理只把訓練 worker 用 sbatch 提交到計算節點。進度通過登入節點和計算節點共享的儲存上的一個檔案傳回。
SLURM 後端的要求
登入節點需要一個無提示的 ssh 金鑰,以及供代理使用的 Python 3.8 或更新版本,與 ssh 後端的主機相同。登入節點還需要 sbatch、squeue 和 scancel 在 PATH 上。在叢集提供 scontrol 命令的地方,代理也會呼叫它。Potato 從不呼叫 srun 或 sacct,所以停用了 srun 且沒有計費資料庫的叢集也能工作。remote_dir 是必需的,並且必須位於計算節點共享的儲存上,因為作業要在那裡讀取資料包並寫入模型。位於 /tmp 或 /var/tmp 下的 remote_dir 會被驗證器報錯,因為這些目錄對每個節點都是本地的。
計算節點需要一個帶有訓練器所需包的直譯器,由 job_python 指定。SLURM 後端不支援 packages,因為在登入節點上建立的虛擬環境未必能在計算節點上執行。請把 job_python 指向計算節點上有的環境,並把模組載入或環境啟用放在 prelude 中:
model_training:
enabled: true
backends:
cluster:
type: slurm
host: login.cluster.example.org
python: /opt/conda/bin/python3 # on the login node, for the agent
job_python: /shared/envs/train/bin/python # on the compute nodes, for the worker
remote_dir: /shared/projects/me/potato-training # storage the compute nodes share
partition: gpu
gres: gpu:1
time: "04:00:00"
prelude: ["module load cuda/12.4"]除 gpus 和 packages 外,每個 ssh 鍵都適用於 SLURM 後端。GPU 來自 gres,排程器會為它所分配的 GPU 設定 CUDA_VISIBLE_DEVICES。沒有 gres 的作業得不到 GPU。SLURM 設定對應到 sbatch 的標誌:
| 鍵 | 預設值 | 含義 |
|---|---|---|
job_python | 後端的 python | worker 在計算節點上的直譯器。 |
gres | 無 | sbatch --gres,例如 gpu:1 或 gpu:A6000:1。 |
time | 04:00:00 | sbatch --time。超過 6 小時時,potato validate 和伺服器啟動會報告一條警告,詳見下一段。 |
partition | 叢集的預設值 | sbatch --partition。 |
nice | 0 | sbatch --nice,取值 0 到 2000。 |
cpus_per_task, mem | 叢集的預設值 | sbatch --cpus-per-task 和 --mem。 |
sbatch_options | [] | 更多標誌,例如 ["--requeue", "--constraint=a100"]。Potato 自己設定 --output、--chdir、--job-name 和 --parsable,驗證器會對這裡出現的這些標誌報錯。 |
prelude | [] | 在作業中、worker 之前執行的 shell 行。 |
poll_interval_s | 30 | 兩次 squeue 呼叫之間的秒數,至少為 10。 |
exit_grace_s | 180 | 等待已結束作業的退出碼出現在登入節點上的秒數,至少為 20。 |
job_name | potato-train | 作業名的字首,後面是執行 id 的前 8 個字元。 |
time 超過 6 小時時,potato validate 和伺服器啟動會報告 model_training.backends.cluster.time is 12:00:00. A job that holds a GPU for more than 6 hours is long on a shared cluster; check its policy,而 potato validate --strict 會因這條警告而失敗。一旦叢集的政策允許更長的作業,就在頂層的 validation 塊中為這一個後端確認該警告,該塊只有 potato validate 會讀取:
validation:
acknowledge:
- "model_training.backends.cluster.time is"每個條目都按區分大小寫、至少 12 個字元的子串與警告文本匹配,匹配上的警告會移入已確認列表,不再使 --strict 失敗。寫明後端名稱的條目只為該後端確認警告。過短或沒有匹配任何警告的條目本身會成為一條警告。
SLURM 後端的檢查命令
potato training check cluster config.yaml該檢查會登入到登入節點,確認 sbatch、squeue 和 scancel 在 PATH 上,並確認 remote_dir 不在 /tmp 下。報告會寫明登入節點上的 Python 版本,以及作業將執行的 job_python。檢查無法看到計算節點有哪些包,所以計算節點上缺少的包會表現為執行失敗,匯入錯誤記在執行日誌中。請用較小的 time 先跑一個簡短的作業,儘早發現這類問題。
SLURM 執行的步驟
Potato 連線到登入節點並把程式碼複製過去,與 ssh 後端相同。隨後代理執行這些步驟:
- 把資料包下載到
remote_dir/jobs/<run>/。 - 寫出
job.sh,並用sbatch --parsable提交該指令碼。執行日誌會記錄Submitted SLURM job,附上作業 id 和各標誌。 - 在作業目錄中,計算節點上的 worker 把事件寫入
events.jsonl,把錯誤寫入stderr.log,把退出碼寫入exit_code。SLURM 自己的輸出寫入slurm.out。 - 代理隨檔案增長讀取
events.jsonl,並把進度轉發給伺服器。代理每隔poll_interval_s執行一次squeue,執行日誌會記錄作業佇列狀態的每次變化,例如SLURM job 4242: PENDING (Priority),然後是RUNNING on node07。 - 作業結束時,代理上傳預測並報告結果。產物回到伺服器,作業目錄被刪除,與 ssh 後端相同。
Cancel run 會向作業傳送 scancel --full --signal=TERM,如果 worker 尚未停止,再發一個普通的 scancel。當到登入節點的 ssh 會話斷開時,代理會取消自己的作業,GPU 分配隨之釋放。當排程器自己結束一個作業時,這次執行會以排程器給出的原因失敗,原因讀自 scontrol show job 或 slurm.out。Potato 報告的原因包括超出時間限制、節點故障、被搶佔、記憶體耗盡,以及來自 Potato 之外的 scancel。
Potato 的測試套件對 sbatch、squeue 和 scancel 的替身執行 SLURM 流程,覆蓋作業提交、佇列狀態、進度轉發和取消。分割槽限制和搶佔政策因叢集而異,所以在新叢集上請留意第一個作業的執行日誌。
在一個能到達伺服器的登入節點上手工啟動的代理,同樣可以向 SLURM 提交作業,命令是 potato training agent --server ... --backend <name> --workdir <shared dir> --slurm-config slurm.json。檔案 slurm.json 以 JSON 物件的形式儲存上表中的設定,--workdir 必須位於計算節點共享的儲存上。
Potato 無法到達的機器
agent 後端適用於標註伺服器無法連線的機器,例如 NAT 後的工作站。有人在那臺機器上啟動 potato training agent,代理便向伺服器輪詢作業。代理不需要任何入站埠,因為所有連線都由代理自己建立。取消請求會在伺服器對代理下一次進度報告的響應中到達代理。模型檔案留在訓練機器上,只有預測和結果會傳回。
agent 後端的要求
訓練機器需要安裝 Potato、你所執行訓練器的包,並且要有到標註伺服器 URL 的網路路徑。對 hf-text-classification 而言,一條安裝命令就涵蓋了 Potato 和訓練器的包:
pip install "potato-annotation[train-text]"伺服器只需要後端條目。agent 後端接受 type、description、heartbeat_timeout_s(預設 120)和 queue_timeout_s,不接受其他鍵:
model_training:
enabled: true
backends:
lab-workstation:
type: agent
queue_timeout_s: 3600註冊並啟動代理
在標註伺服器上為該後端簽發一個令牌:
potato training register lab-workstation config.yaml該命令只打印一次令牌,並附上要在訓練機器上執行的命令:
Token for backend 'lab-workstation' (shown once; registering again replaces it):
<token>
On the training machine:
export POTATO_TRAINING_TOKEN=<token>
potato training agent --server http://<this-server>:8000 --backend lab-workstation在訓練機器上用伺服器的真實 URL 啟動代理:
export POTATO_TRAINING_TOKEN=<the token>
potato training agent --server https://annotate.example.org --backend lab-workstation代理會為它的後端執行作業,直到你停止代理。--token-file 從檔案而不是 POTATO_TRAINING_TOKEN 讀取令牌,--python 指定訓練 worker 執行所用的直譯器,預設是代理自己的直譯器。--once 在一個作業之後退出,--workdir 把代理的檔案從 ~/.cache/potato-training 移到別處,--keep 則保留每個作業的資料包。再次註冊同一名稱會簽發新令牌,並使舊令牌失效。
potato training check lab-workstation config.yaml 會列印該後端的令牌記錄,一旦有代理連線過,last_seen 就會被設定。註冊之前,該命令會列印 no token issued yet 以及要執行的註冊命令。
agent 執行的步驟
針對 agent 後端的執行會停留在 queued 狀態,直到有代理來領取工作。設定了 queue_timeout_s: 3600 時,沒有代理的執行會在一小時後失敗,沒有 queue_timeout_s 時,執行會一直等到你取消它。代理以 tar.gz 檔案的形式下載資料包,並從中斷處恢復被中斷的下載,最多嘗試五次。隨後代理執行 worker,釋出進度,上傳 predictions.jsonl,並報告退出碼。對於大於 max_predictions_mb(預設 512)的預測檔案,伺服器回應 413。
模型檔案留在訓練機器的 --workdir 下,執行記錄的 artifact_uri 寫明主機和目錄。如果執行期間代理有 300 秒無法連線伺服器,代理會自行取消該次執行。代理在 heartbeat_timeout_s 內沒有傳送任何內容的執行,會在伺服器上以 No word from the agent on lab-workstation for 120 seconds 失敗。
常見故障
每一項先給出執行日誌或檢查報告中的訊息,再給出修復辦法。適用於每種後端和訓練器的完整列表在上游的故障排查一節。
ssh to gpu.example.org failed: Permission denied。 Potato 需要一個無提示即可使用的金鑰。從伺服器的賬戶執行ssh gpu.example.org true必須成功。/opt/conda/bin/python3 was not found on gpu.example.org。 把後端中的python設為主機上存在的直譯器。No GPU has 4000 MB free。 主機上的每塊 GPU 都比min_free_gpu_mb允許的更忙。potato training check gpu-server config.yaml會列出這些 GPU。可以等待、降低閾值,或用gpus: "1"固定一塊 GPU。Trainer 'hf-text-classification' could not be imported。 遠端直譯器缺少訓練器需要的某個包。把python指向有該包的直譯器,或把該包列在packages下。在 SLURM 後端上,請修復job_python所指的環境。This host's torch has no kernels for any of its GPUs。 遠端 torch 是為比主機所擁有的更新的 GPU 構建的。請安裝適配這些 GPU 的 torch 構建,或改用另一臺主機。No word from the agent on gpu-server for 120 seconds。 遠端機器或網路消失了。在 ssh 後端上,執行目錄中的agent.log有代理最後的輸出。The copy from gpu.example.org sent nothing for 180 s。 模型被複制回來的過程中,到主機的網路中斷了。這次執行的預測已經儲存,artifacts/中只缺少模型檔案。login.cluster.example.org has no sbatch on PATH, so it cannot submit jobs。host不是叢集的登入節點,或者這些命令所在的PATH只有登入 shell 才會設定。請在後端的env下設定PATH。SLURM job 4242 ended before the worker finished: it reached its time limit。 提高後端中的time,或用更少的條目訓練。設定keep_remote: true可保留slurm.out,以便在其他排程器故障之後閱讀。- agent 後端上的執行一直處於排隊狀態。 沒有使用該後端名稱的代理在輪詢。請用
potato training agent --backend lab-workstation啟動代理,並通過訓練頁面上的 Check 檢查令牌。 nvrtc: error: failed to open libnvrtc-builtins.so.13.0。 主機的 torch 與它的nvidia-cuda-nvrtc包對應不同的 CUDA 版本。請安裝與 torch 構建匹配的nvidia-cuda-nvrtc,或把python指向另一個直譯器。