RUN:AI 企業級 AI 資源調度實戰例析
Run:ai 八項機制實測:每個機制的觸發條件與邊界
前言:閒置的 GPU,是最昂貴的浪費
GPU 伺服器是企業 AI 基礎設施中金額可觀的一筆投資,但在許多環境裡,這些算力有相當比例的時間處於閒置。問題通常不是「用不到」,而是「分不好」。
以最常見的兩類工作負載,也就是模型訓練與推論服務為例:
動態調整工作負載:有無 Run:ai 的訓練與推論負載分布(示意圖)
沒有 Run:ai 時,常見的做法是把 GPU 固定切分給訓練與推論,彼此互不相通。問題是,這個比例怎麼切都不對:
- 訓練切多了,白天上班時段推論資源不足,每一筆請求的回應時間都被拉長。員工等待 AI 回應的每一秒,對企業來說都是生產效率的損失。
- 訓練切少了,訓練工作要跑更久;更嚴重的是,推論負載跟著上班時間走,分給推論的那一塊在下班後將近三分之二的時間都空著,切得越多,浪費越多。
以圖中各切一半的配置來算一筆帳。一台 B300 伺服器的導入成本直逼 2,000 萬元,再加上專責的 AI 平台管理團隊、廠商維護合約、電力與空調,每月營運費用抓 10 萬元並不誇張。以 5 年計算,總持有成本約 2,600 萬元。
分給推論的那一半,下班後將近三分之二的時間閒置,算下來 5 年約有 870 萬元的算力在空轉,相當於每個月白白燒掉 14 萬元以上,比整個月的營運費用還多。換句話說,整筆投資有三分之一買了卻沒在用。這還沒算進週末與國定假日;若把週末整天也算進去,推論那一半的閒置比例會超過七成。
有 Run:ai 時,GPU 成為可以動態調度的共用資源池。夜間推論流量低,訓練工作借用閒置算力,把整台機器跑滿;早上推論需求回升,排程器自動把借出的資源收回給推論服務,部分訓練工作讓出位置,待資源釋出後再從 checkpoint 接續;傍晚推論退潮,訓練再度擴張。一整天下來,GPU 幾乎沒有空檔,同一筆硬體投資能產出的運算量大幅提升,設備的投資效益才真正發揮出來。原本空轉的這三分之一,就能轉成實際的訓練產出。
延伸閱讀:K3 出現了,要自建嗎?我們先來算帳!
為什麼要實測
當然,示意圖描繪的是理想狀態。要讓它在真實環境中成立,企業最常遇到的不是效能問題,而是治理問題:
- 正式服務會不會被測試工作影響?
- 各單位的資源怎麼分才公平,又不會有人閒置浪費?
- 離峰時段能不能把閒置算力借給別人,白天再自動收回?
- 高優先權的工作進來時,正在跑的訓練會不會前功盡棄?
這些問題在規劃階段都可以「按照文件說明」回答,但文件寫的是設計意圖,實際行為往往有前提條件。我們決定把整套規劃在實驗環境完整建置一次,逐項驗證。
本文的價值不在於「Run:ai 有這些功能」,而在於每個機制的觸發條件與邊界——這些是文件不會寫、但實際導入時一定會踩到的地方。
驗證環境與縮放方法
驗證環境刻意用了最小配置:
| 項目 | 規格 |
|---|---|
| 叢集管理 | NVIDIA Base Command Manager 11.33.1 |
| 調度平台 | Run:ai 2.25.28 |
| Kubernetes | v1.34.10 |
| GPU | 單張 NVIDIA L40S 48GB |
| 推論框架 | vLLM 0.27.1 |
目標架構是 8 張 GPU 的雙分區規劃(正式區 2 張、開發測試區 6 張)。我們採用等比例縮放:
1 張 L40S ≙ 8 張目標 GPU
正式區分到 fraction 0.25(等於 2 張卡),開發測試區 0.75(等於 6 張卡)。
這樣做的前提是:調度機制與卡的絕對數量無關。配額怎麼分、誰能搶誰、什麼時候回收,這些邏輯在一張卡和八張卡上完全一樣。至於容量能塞多少參數的模型,那是另一個維度的問題,不在本次驗證範圍。
因為切片變小,實測用的是 0.5B 到 2B 等級的模型,不影響任何機制的驗證。
架構設計:用配額階層取代物理分區
Run:ai 的資源治理有兩層:Department(部門)定義分區總量,Project(專案)定義用途額度。
| 層級 | 名稱 | Quota | Over-Quota | 對應目標 |
|---|---|---|---|---|
| Department | prod |
0.25 | — | 2 張卡 |
| └ Project | prod-inference |
0.25 | 關閉 | 整卡配置、24/7 常駐 |
| └ Project | prod-training |
0 | 開啟 | 僅借用閒置、自動歸還 |
| Department | dev-test |
0.75 | — | 6 張卡 |
| └ Project | dev-shared |
0.37 | 關閉 | 開發公用區(3 卡) |
| └ Project | 四個研發單位 | 各 0.09 | 開啟 | 各單位保障額度 |
Department 層級:兩個新建部門與各自的配額
Project 層級:七個專案,可見各自的 GPU quota 與已配置量
這個設計的關鍵在於 prod-training 的配置。它的保證配額是 0,但開啟 over-quota——意思是它只吃 prod-inference 沒用滿的部分。當推論服務擴張需要資源時,訓練會被自動回收。這正好對應「推論最優先,訓練少但可能會有」的需求,而且不需要犧牲推論任何保證配額。
觀念釐清:Run:ai 有兩種「優先權」
這是導入時最容易設錯的地方,值得先講清楚。
Workload Priority:掛在工作負載上
建立工作負載時可設定的優先權,共七個等級
系統預設值:
| 工作類型 | 優先權 | 可否被中斷 |
|---|---|---|
| Inference(推論) | very-high (150) | Non-preemptible |
| Workspace(工作區) | high (125) | Non-preemptible |
| Training(訓練) | low (40) | Preemptible |
這個決定誰擠掉誰,是真正的搶佔依據。
Over-Quota Priority:掛在專案/部門上
Project 層級的 GPU over quota 設定:開關、Max GPU allocation、Rank 與 Weight
同一 Rank 內的多個專案依 Weight 比例分配閒置資源
這個設定由兩部分組成:Rank(High / Medium High / Medium / Medium Low / Low)決定排序,Weight(數值)決定同一 Rank 內的分配比例。介面上的長條圖會即時顯示「這個專案的權重佔多少比例」。
關鍵區別:Over-Quota Priority 只在借用閒置資源時生效,它是「分配權重」而非「位階」。設定得再高,也不會讓一個專案變得不可被回收;設定得低,也不影響它拿到自己的保證配額。
一個容易踩的坑
有人會想「我這個訓練很重要,把 priority 調成 high」。結果反而更糟——一旦調高,該工作負載就失去使用 over-quota 的資格。如果它的保證配額是 0,就會永遠停在 Pending。
驗證一:權限邊界
我們建立了一個研究員帳號,角色為 L1 Researcher,Scope 僅限一個專案。
研究員視角:標題旁的 View only 標記,清單中只有自己被授權的專案
實測差異:
| 面向 | 管理者 | L1 Researcher |
|---|---|---|
| 側邊選單 | Analytics / Resources / Organization / Access / Policies / Admin | 僅 Workload manager |
| Projects 頁面 | 可編輯配額 | View only |
| 可見專案 | 全部 7 個 | 僅 1 個 |
| 建立 Workspace / Training | 可 | 可 |
| 建立 Inference | 可 | 權限不足 |
最後一項最有價值。許多企業的需求是「研究員可以自由做實驗,但把模型變成對外服務必須循程序申請」——Run:ai 的內建角色就達成了這件事,不需要額外的流程管控或客製開發。
另外值得注意:其他專案對這個帳號來說是完全不可見的,不是「看得到但點不進去」。這降低了誤操作的可能性,也減少研究員需要理解的複雜度。
驗證二:多模型併行與統一入口
三個推論服務同時運行,各佔不同 fraction:
三個 vLLM 服務分屬不同專案,GPU compute request 分別為 0.2 / 0.12 / 0.1
從實體卡的角度看:
單張 L40S 上同時有三個 VLLM::EngineCore 程序,記憶體用量分別為 5028 / 9060 / 4606 MiB
模型權重透過 Data Source 資產掛載——資產定義一次,所有專案都能重複使用,不需要每個單位各自設定路徑。
對使用者而言,不需要記住三個不同位址。我們在前面架設了 LiteLLM 作為統一閘道:
統一入口列出所有可用模型,並可做金鑰、成本與團隊管控
驗證三:配額上限是結構性保證
在一個保證配額只有 0.09 的專案裡,一口氣提交 7 個訓練任務(每個 0.09)。
七個 Training 工作負載,優先權皆為 low (40)、Preemptible
結果:5 個 Running,2 個 Pending。
各 namespace 的 fraction 加總:0.45 + 0.30 + 0.12 = 0.87
排程器對 Pending 的工作給出了明確原因:
排程器事件:dev-test 部門配額已達上限
OverLimit: dev-test quota has reached the allowable limit of GPUs.
Limit is 0.76 GPUs, currently 0.75 GPUs allocated and workload requested 0.09 GPUs.
這裡最值得注意的數字是 0.87——整台機器還有 0.13 是空的。機器有空卡,但制度不讓開發測試區拿,因為那部分落在正式區的保留範圍內。
這就是結構性保證:不依賴任何人的自律,是排程器根本不給。
實測補充:over-quota 上限怎麼算
實測確認 over-quota 的可用空間是「部門 limit 減去部門已用量」,而不是「其他專案未使用的 quota 加總」。兩種算法會得出不同的結果(5 個 vs 4 個),這個差異在容量規劃時會有影響。
驗證四:跨部門的資源隔離
延續上一個情境——開發測試區已經觸頂,此時在正式區提交一個推論服務。
結果:立即 Running。 因為兩個部門的配額完全獨立,開發區滿不滿與正式區無關。
正式區增加負載後:0.45 + 0.30 + 0.24 = 0.99,機器接近滿載
同時,先前的訓練任務完全不受影響——epoch 持續遞增、loss 持續下降、累計訓練時間連續,沒有中斷或重啟的跡象。
記憶體隔離的實證
訓練容器啟動時會印出它看到的顯存總量:
| 視角 | 顯存 |
|---|---|
容器內 visible_mem_total |
4146 MiB |
實體卡 memory.total |
46068 MiB |
容器只看得到自己被分配的那一份,看不到卡上其他工作負載的存在。這是驅動層的硬隔離,不是靠應用層自律。
一個常見的疑問
nvidia-smi 顯示的實際用量(18713 MiB)低於 fraction 加總(0.99 ≈ 45GB),這正常嗎?
正常。fraction 是排程層的預留額度,而非實際寫入量;nvidia-smi 顯示的是當下實際寫入的顯存。當時的 5 個訓練任務雖然把 GPU 使用率跑滿,但每個只佔約 300 MB 的 VRAM,卡上實際寫入的記憶體幾乎全來自三個 vLLM 服務,而推論的 KV cache 也未滿載,所以實際佔用低於配額。
換個說法:額度是承諾,不是浪費。而 Run:ai 的 Workloads 清單有一個 Idle GPU devices 欄位,正是用來抓「拿了配額卻沒在用」的情況。
驗證五:跨單位借調與回收(Reclaim)
這一段驗證「離峰借用、原擁有者需要時自動歸還」的實際行為。
前置:A 單位(保證配額 0.09)借用了大量他人閒置額度,跑著 4 個訓練任務。
第一個實驗:B 單位提交 3 個工作
結果:只有 1 個 Running,另外 2 個 Pending,而且 A 單位完全沒有被動到。
第二個實驗:C 單位(完全未使用資源)提交 1 個工作
結果:立即觸發回收。
A 單位有一個工作轉為 Pending,C 單位的工作同時 Running
A 單位某個 pod 年齡歸零(2m1s)並轉 Pending,C 單位的工作同時起來(2m5s)
關鍵發現:回收的觸發條件
兩個實驗的差異解釋了整個機制:
| 情境 | 是否觸發回收 |
|---|---|
| B 單位已拿到保證配額,再要更多 | 不觸發,Pending 等待 |
| C 單位一分未拿,提出保證配額需求 | 立即觸發 |
回收只在某個單位拿不到它的「保證配額」時才發生,不是「有人提出需求」就發生。
已經拿到保證額度的單位,再多要的部分屬於借用,排程器不會為此中斷別人的工作。
這對導入規劃很重要。如果客戶期待的是「一提需求就能立刻拿回全部額度」,實際行為會有落差。但反過來說,這也意味著回收是最後手段,不會沒事就打斷正在跑的任務——對訓練工作的穩定性是好事。
另外值得注意,被回收的是 A 單位借來的部分;B 單位那個正在使用自己保證配額的工作完全未受影響。回收非常精準。
驗證六:同單位內的搶佔與訓練續跑(Preemption)
前一段是單位之間的借調。這一段是單位內部的搶佔,機制完全不同。
前置:某專案配額用滿,內含一個推論服務(150)與一個訓練任務(40)。此時提交第二個推論服務。
新推論服務 Initializing,原推論服務不受影響,訓練任務轉為 Pending
訓練被中止時的行為
訓練跑到 epoch 21,收到 SIGTERM 並確認 checkpoint 已落盤
[03:22:19] epoch 21/3000 loss=0.6989 test_acc=0.8223 best=0.8223 cum_train_time=63.0s
[03:22:22] !!! received SIGTERM (preempted) - latest checkpoint is already on disk
資源釋放後自動續跑
刪除新的推論服務後,訓練自動重新排程:
RESUMED 後三個數值與中斷前完全一致,epoch 從 22 繼續
[03:23:05] >>> RESUMED from checkpoint: epoch=21 elapsed=63.0s best_acc=0.8223
[03:23:09] epoch 22/3000 loss=0.7013 test_acc=0.8225 best=0.8225 cum_train_time=66.4s
epoch、elapsed、best_acc 三個數值與中斷前最後一行完全一致。訓練成果沒有任何損失。
Reclaim ≠ Preemption
這兩個機制經常被混為一談,但觸發條件和行為都不同:
| Reclaim | Preemption | |
|---|---|---|
| 範圍 | 跨佇列(單位之間) | 同佇列(單位內部) |
| 驅動因素 | 保證配額不足 | 優先權高低 |
| 對象 | over-quota 借用的部分 | 同佇列中優先權較低者 |
搞混這兩者會導致測試往錯誤方向進行——例如在開著 over-quota 的專案裡試圖驗證搶佔,排程器會直接走借用路徑,永遠不會觸發搶佔。
實作重點:搶佔的必要條件
搶佔要成立,佇列必須沒有 over-quota 的出路。
實測確認:Project 層的 limit == quota 就足夠,不需要對 Department 層做額外設定。原因是工作負載在 Project 層就被擋住,根本走不到 Department 那一層。
更方便的是,在介面上關閉 Project 的「Allow workloads to exceed the project quota」時,系統會自動把 limit 設為等於 quota——不需要任何額外的手動操作。
驗證七:擴縮與閒置治理
Autoscaling
設定 min/max replica 與觸發條件(Concurrency > 2)
壓測期間擴張到 3 個 pod
Desired 與 Actual 曲線,1 → 3 的擴張過程
擴縮條件除了併發數(Concurrency),也支援 Throughput、Latency 與自訂指標。
Scale-to-Zero
Minimum 設為 0
Running / requested pods 顯示 0/0-3
冷啟動實測:
第一次請求(冷啟動)56.6 秒,第二次(已有 replica)4.6 秒
| 情境 | 時間 |
|---|---|
| 冷啟動(scale from zero) | 56.6 秒 |
| 熱請求 | 4.6 秒 |
56 秒的延遲來自 vLLM 重新載入模型與啟動推論引擎。
這個數字直接決定了適用場景:對 24/7 低延遲的正式服務,Scale-to-Zero 不可接受,應維持 min replica ≥ 1;對開發測試區的實驗性服務,這是很好的資源回收手段。
Idle GPU Timeout
Workspace 在閒置逾時後自動轉為 Stopped
設定閒置門檻後,佔用 GPU 但沒有實際運算的工作負載會被自動停止。
適用範圍是這個功能的關鍵限制:Workload type 只有三個選項——Training、Preemptible workspaces、Non-preemptible workspaces,不包含 Inference。
原因是推論服務的 replica 數由 Knative 的自動擴縮機制掌控,Run:ai 的閒置回收不會去干預。所以推論服務的閒置治理,只能靠前面的 Scale-to-Zero。
驗證八:用量可視化與報表
叢集總覽:GPU 配置量、閒置量、各專案的平均配置與使用率
Overview 頁面同時顯示 Allocated GPU devices 0.51 與 Idle allocated GPU devices 0.42——後者是「已配置但沒在運算」的量。這個對比很有說明力:配置率高不等於使用率高。
有意思的是,長期閒置的往往是常駐推論服務(沒有流量時整份配額都空著),而訓練任務通常滿載。這正好解釋了為什麼推論需要 Scale-to-Zero 這類機制,也說明正式區的 GPU 使用率偏低是設計上的取捨(保證低延遲),而非浪費。
Consumption Report
可依 Project / Department / Cluster 分段,選擇時間區間
輸出欄位包含 GPU allocation / deserved quota / over quota / idle hours 與 CPU、記憶體用量
報表輸出的欄位設計值得一提——它同時給出 GPU deserved quota hours(應得配額)、GPU over quota hours(超額借用)與 GPU idle hours(閒置時數)。這三個數字放在一起,就能回答「這個單位是不是常常在借別人的資源」以及「借了之後有沒有真的在用」。
實作要點整理
以下是驗證過程中確認的行為,這些在文件上不一定寫得清楚:
排程行為
| 項目 | 實測結果 |
|---|---|
| Reclaim 觸發條件 | 某佇列拿不到保證配額,而非「有人提出需求」 |
| Preemption 觸發條件 | 同佇列內優先權高低,且無 over-quota 出路 |
| Preemption 前提 | Project 層 limit == quota 即足夠,不需設定 Department 層 |
| over-quota 可用上限 | 部門 limit − 部門已用量 |
| 調高 workload priority 的副作用 | 失去 over-quota 資格,quota 為 0 時將永久 Pending |
介面限制
| 項目 | 實測結果 |
|---|---|
| Quota 輸入精度 | 僅接受兩位小數 |
| Department 的 Max GPU allocation | 介面強制大於 quota,無法設為相等 |
| 關閉 Project over-quota | 系統自動將 limit 設為等於 quota |
關於 Department 的 Max GPU allocation 無法等於 quota:實測中部門配額 0.75、上限被迫設為 0.76。這 0.01 的差距在實務上無害——0.01 fraction 約等於 480MB,小於任何實際工作負載的需求,逃生口存在但寬度不足以通過。
功能適用範圍
| 功能 | 適用 | 不適用 |
|---|---|---|
| Idle GPU Timeout | Training、Workspace | Inference |
| Scale-to-Zero | Inference | Training、Workspace |
兩者剛好互補,涵蓋了所有工作負載類型的閒置治理。
結論
八項機制全部驗證通過,其中三項的實際行為與初始預期有差異,值得在規劃階段就納入考量:
一、回收是需求驅動,而非時鐘驅動。 平台沒有「依時段自動調整配額」的原生功能。實際運作是:夜間訓練以 over-quota 借用閒置資源,白天推論負載提出需求時排程器自動回收。若一定要時間驅動,需要外部排程呼叫 API 改寫配額,屬額外開發工項。
二、回收的門檻比想像中高。 只有在某個單位拿不到保證配額時才會發生。這對訓練工作的穩定性是好事,但與「一提需求就立刻拿回」的直覺不同。
三、閒置治理需要兩套機制。 Idle GPU Timeout 管不到推論服務,而推論服務恰恰是最容易長期閒置的類型。Scale-to-Zero 是唯一的手段,但要接受冷啟動延遲。
至於最核心的問題——正式服務會不會被測試工作影響——答案是三道獨立防線:
- 開發測試區的 Max GPU allocation:即使正式區完全閒置,開發區也拿不到那部分資源
- 正式區推論服務的保證配額:部門內部也保證拿得到
- 推論類別的預設值:Non-preemptible + 最高優先權,一旦排程即不被中斷
第一道是結構性的,不依賴任何人的自律。這也是整套設計中最值得強調的一點:好的資源治理不是靠規範,是靠排程器根本不給。
附錄:驗證用的模型與參數
| 模型 | fraction | --gpu-memory-utilization |
--max-model-len |
|---|---|---|---|
| Qwen2.5-1.5B-Instruct | 0.12 | 0.75 | 4096 |
| gemma-2-2b-it | 0.20 | 0.85 | 2048 |
| Qwen2.5-0.5B-Instruct | 0.10 | 0.80 | 4096 |
所有 vLLM 工作負載均加上 --enforce-eager。
在 GPU fraction 環境下,--gpu-memory-utilization 是相對於切分後的可見顯存,不是整張卡。此外,CUDA context 等固定開銷不隨 fraction 縮小,在小切片上的佔比會顯著放大——這是規劃小 fraction 時容易低估的部分。


