執行人:
EricccTaiwan
,charliechiou
簡報
Linux v6.12 引入的 sched_ext (scx
) 允許開發者藉由 eBPF
,在使用者空間動態載入或抽換 CPU 排程器。本任務嘗試結合機器學習,利用 BPF map 彙整 CPU 排程相關事件資料,依據推論動態調整 time slice 、 CPU affinity 與 task migration。預計探討以下:
sched_ext
的創新和相關機制參考 《Demystifying the Linux CPU Scheduler》 - by Ching-Chun (“Jserv”) Huang
CFS 是 Linux 自 v2.6.23 起採用的預設排程器;其核心理念是以 (virtual runtime) 模擬理想狀態下所有可執行工作同時執行、均分 CPU 的情形:每個任務根據權重 (由 值換算) 計算
並累加至 ,權重較高 ( 值越小,即越不 nice) 累積速度較慢。
CFS 為每個任務維護 , 排程時優先挑選 最小 (即目前最「落後」的任務),確保任務間的 fairness (公平) 。再以 (sched_latency_ns
) [1] 和 (sched_min_granularity_ns
) [2] 決定實際 time slice,使得高權重任務獲得更多 CPU time、低權重工作也不致飢餓 (starvation) ;並持續更新最小的 min_vruntime
來初始化新建立或剛醒來的任務,避免該任務的 與佇列最小值產生過大的落差。
如此一來, CFS 同時達成按權重公平分配 CPU time ,並在負載升高時於 latency 與 throughput 之間取得動態平衡。
[1]: the minimum amount of time idealized to an infinitely small duration required for every runnable task to get at least one turn on the processor
[2]: minimum amount of time that can be assigned to a task
EEVDF 是自 Linux v6.6 起接替 CFS 的新預設排程器;其核心理念是先以「資格 (eligible) 」 維持公平,再以「虛擬截止時間 () 」排序時效。對每個任務計算
其中 為佇列 (queue) 中所有任務 的加權平均, 表示任務 CPU time 落後(under-served)且具執行資格 (eligible) 。排程器先從 eligible 任務中挑選 最小者;若 相同,再取 最早的任務執行。任務實際執行 後,更新其 與新的 ,並重新插入紅黑樹。高權重( 值小)任務,可一次取得較長 time slice () ;低權重則 time slice 較短但輪替更頻繁。新建立或剛喚醒的任務以當前 為初始 ,並保留先前 ,避免與佇列最小值產生過大落差。
透過「eligible + 」的雙層機制,EEVDF 延續 CFS 的權重公平,並在低負載時提供更低延遲、在高負載時延長 time slice 以減少 context switch 次數並提升 throughput。
sched_ext
的創新和相關機制sched_ext
?CFS 和 EEVDF 是 Linux 核心的預設通用排程器,關注於任務全體的吞吐量 (throughput) ,但並非所有應用場景都在意整體的吞吐量。
因此 sched_ext
就是為了解決上述的痛點,提供了更多的設計彈性,開發者們能針對不同場景設計對應的排程器,例如 scx_flash
, scx_rusty
和 scx_lavd
等等,都是針對不同的應用所設計的排程器。
以 scx_lavd
為例,著重於提昇使用者的遊戲體驗 (e.g., 在 Steam Deck 上安裝 Arch Linux 玩遊戲)。玩遊戲時,我們在意的是如何降低遊戲 (i.e., latency-critical tasks) 的延遲 (latency) ,而非關注於提昇遊戲和背景程式的整體吞吐量,背景應用顯然不是我們在意的事情。
scx_lavd
is introduced by Changwoo Min (Igalia)
lavd: Latency-criticality Aware Virtual Deadline scheduling algo
如果任務是位於一連串的 task chain 中央,此任務就是 latency-critical task ,影響整個 task chain 的延遲,但如何決定一個任務是否為 latency-critical ?
Changwoo 教授透過判斷任務被喚醒 (wakee) 和喚醒其他任務 (waker) 的頻率,藉此調整 latency-critical 任務的可用 time-slice 、 可搶佔等等。此設計,確實會產生不公平 (unfairness) 的情形發生,但如上所述,此排程器目的在於提高遊戲體驗,降低 latency-critical task 的延遲,因此整體吞吐量不是優先考量。
圖片來源: sched_ext: scheduler architecture and interfaces (Part 2)
圖片來源: Crafting a Linux kernel scheduler in Rust - Andrea Righi
sched_ext
是在 Linux 核心中的一個排程類別 (kernel/sched/ext.c
),其核心理念是將排程決策邏輯從 kernel-space 移至 user-space ,透過 eBPF
作為橋階層,其作用就是進行 message passing,讓使用者在不更改核心程式碼的情況下,在 user-space 實作各種排程演算法。整體架構如圖所示,可分為三個主要層級:
sched_ext
模組):提供一個最小但功能齊全的排程框架,負責處理與上下文切換相關的低階作業,並透過 DiSpatch Queues(DSQs)作為執行單位佇列,將具體的排程決策委託給上層。sched_ext
與 user-space 之間的邏輯中介,定義了 struct sched_ext_ops
中的一組調度操作(e.g., select_cpu
、enqueue
、dispatch
等),這些操作函式由 eBPF
程式實作,用來控制任務該放入哪個 DSQ、應由哪個 CPU 執行、或在 DSQ 無工作時如何補充等行為。libbpf
或 libbpf-rs
操作 eBPF object 與 maps,並可自由設計、實作、更新不同排程策略。簡而言之,user-space 透過 sched_ext
所實作的排程器,本質上就像是一種可動態載入的 kernel module。不同的是,它並非以 C 語言撰寫並編譯成傳統 kernel module,而是以 eBPF
實作,經由核心中的 sched_ext
框架載入與執行。
首先,如果想實作/開發 scx
排程器,強烈建議加入官方的 Discord
群組,能直接尋求開發者們 (諸位大神, e.g., Tejun Heo ) 的協助,也有每週的 Office hours
可以當面詢問 (開發著們也會回報各自的進度)。
其次,此 repo 的主力開發是 Rust Scheduler,而非 C Scheduler,除非 C 語言排成器有很嚴重的錯誤需修正,否則相關的 PR 對於此 repo 來說 no tangible benifit ,可詳見 scx#1827。
電腦的 CPU 如果是 12-th 後,大小核的設計會影響到實驗結果,把小核關掉暫時不考慮暫時避免受到小核影響。
關閉 E-core 前
關閉 E-core 後
sched_ext is supported by the upstream kernel starting from version 6.12,因此核心版本要求 6.12+,至於 6.13、6.14 的核心版本在 scx
上的表現是否有差異?
Tejun Heo ( scx
的 maintainer ) 給了以下回覆,
There are new features introduced which may improve performance in some cases (e.g. queued_wakeup support) but for the most part, the kernel versions wouldn't cause noticeable differences. All schedulers should work fine across the kernel versions.
Tejun Heo
我們將實驗環境從 Ubuntu 24.04 24.10 25.04 一路向上升級 (一次只能升級一個版本,所以要升級兩次) ; 如果空間有限,可用舊版 Ubuntu 搭配 kernel v6.12+ 作為實驗環境 (e.g. Ubuntu 24.04 w/ kernel v6.12)
目前最新的 LTS 為 Ubuntu 24.04,因此需調整升級設定以對應此版本:
把最後一列的 Prompt=lts
改成 Prompt=normal
,
接著執行
升級後檢查是否升級成功
由於每次升級僅能跨一個版本,因此若從 24.04 升級至 25.04,需先升級至 24.10,再進一步升級至 25.04,共需進行兩次升級。
先下載對應的核心檔案
接著把原本作業系統中的設定檔複製並編譯
編譯核心(可能會花上很多時間)
最後更新 grub 並重開機。
確認升級後的核心版本
以 scx_rustland
為例,執行 scx_rustland
前後輸出結果如下:
sched-ext
(簡稱 scx
)以 Ubuntu/Debian 環境為例
meson
Note: Many distros only have earlier versions of meson, in that case just clone the meson repo and call
meson.py
e.g./path/to/meson/repo/meson.py compile -C build
.Alternatively, use pip e.g.
pip install meson
orpip install meson --break-system-packages
(if needed).
libbpf
(preferred)這個方式,C 和 Rust 都可以編譯完成
meson always uses a separate build directory. Running the following commands in the root of the tree builds and installs all schedulers under
~/bin
.
執行 meson compile 後會把執行檔及中間的建構檔存在對應的資料夾中,若要把可執行的檔案安裝下來則需要使用 meson install 並透過 -C
切換到 build 的資料夾中做安裝,而安裝的位置則是前述用 setup 設定的位置 (i.e., ~
)。
接著執行 sudo ~/bin/<schedule name>
便可執行對應的 scheduler。
詳細的安裝步驟可以查看
meson.build
的腳本,其中在if enable_rust
的段落中會把 rust 的排程器建構在
@OUTDIR@
,而@OUTDIR@
則是由 meson 傳入的 target。 因此也可以在 target 資料夾中的release-fast
中找到我們的 rust 排程器。 另一方面 c 語言所建構的排程器則會安置在 build 資料夾底下。
可以參考 Developer Guide
會出現下方的 TUI 界面,按下 q
即可離開
執行 scx
排程器的過程中,按下 a
可以儲存 trace 檔案,接著打開 Perfetto UI ,點選左側的 Open trace file
開啟剛剛生成的檔案,便可看到下圖的 CPU 排程結果。
若不想使用到 scxtop
提供的 TUI 介面,也可以利用以下指令產生 trace 檔案
time_slice
) .time_slice
) .可以作為排程器的效能測試工具 (e.g. FPS),詳見 scx#1912。 (主要用來測試 scx_bpfland
)
Usual "gaming while building the kernel" scenario, using the WebGL Acquarium demo (15000 fishes) on an system with 8 CPUs Intel i7-1195G7 @ 2.90GHz, running
make -j 32
.
方便實驗並觀察 workload 在 cpu 上的排程及 time slice 分配是否如預期
地雷
isolcpus
,用 CPUSETS
。
isolcpus
地雷
以隔離 CPU 為例,方便進行測試和觀察
重新開機後,再次執行 ./bin/scx_rlfifo
卻跳出了以下 error,
詢問後,才知道 isolcpu 已經被棄用了 Doc/admin-guide/kernel-parameters.txt
Linux 核心專題: sched_ext 研究 (by otteryc) : 重現 FCFS / RR scheduler 實驗。
目的: 確認 time slice 可以被scx
正確設定。
地雷 : scx
排程器皆有預設的最大等待時間 timeout_ms
。
以 scx_rustland_core
為例,最大的等待時間是 秒 , Andrea 建議讓這個參數 configurable ,透過更改 timeout_ms
,給與 DispatchTaks
的 time_slice
有更大的調整空間。
TODO: 提交 PR 修改
為了確認 scx
可以正確設定 time_slice
因此我們更改原先 scx_rlfifo
的設定。
原先是依照任務數量平均分配,為了更好觀察 time_slice
設定是否符合 Perfetto
的觀察結果,我們將其設定為定值,分別為 ms, ms, ms。結果如下,
可以確認 scx
可以有效設定 time_slice
。
scx_rlfifo
以下重現 otteryc 期末專題的設計概念:
scx_rusty
?起初閱讀 scx_lavd
,並試圖引入機器學習,但詳細閱讀後發現,scx_lavd
的整個運作函式 (或是說 eBPF
hook) 都是實作在 *.bpf.c
(e.g., loadbalance.bpf.c
) ,而 main.rs
只是在進行函式調用的呼叫,因此在引入機器學習上會有很多的難點須克服 :
*.bpf.c
可以想像成是 underlayered ,而 *.rs
則是 overlayered ,Rust
層可以呼叫 eBPF
層的函式進行使用,但如果要把機器學習也放在 Rust
層,必須要有輸入資料 因此問題在於,eBPF
可以將資料傳給 Rust
層嗎?eBPF
無法將 task 資訊傳給 Rust
層,那 eBPF
層有辦法實做 ML ,取代 loadbalance.bpf.c
嘛?或許還有更多問題,主要也是對於 eBPF
的不了解,因此轉向使用 scx_rusty
,其 load_balance.rs
實作於 Rust
層,因此可以輕易的用 Rust-ML
取代,不需要考慮 Rust-ML <--> eBPF
之間的問題。
scx_rusty
load balance 可改進之處 ( by David Vernet )When deciding whether to migrate a task, we're only looking at its impact on addressing load imbalances. In reality, this is a very complex, multivariate cost function.
- For example, a domain with sufficiently low load to warrant having an imbalance / requiring more load maybe should not pull load if it's running tasks that are much better suited to isolation.
- Or, a domain may not want to push a task to another domain if the task is co-located with other tasks that benefit from shared L3 cache locality.
在機器學習的部份,我們選擇了 Candle 作為使用的框架。
Candle's core goal is to make serverless inference possible. Full machine learning frameworks like PyTorch are very large, which makes creating instances on a cluster slow. Candle allows deployment of lightweight binaries.
Candle
由 Hugging Face
提供,已經實作了許多不同領域的機器學習任務,諸如 YOLO 、 Mamba 或 LLM 等常見的任務 。另一方面, Burn
則希望可以支援各種不同的 backend ,而 Candle
也在其中。雖然支援各種後端是件實用的特性,但對於我們要完成的任務而言,我們專注於機器學習本身,因此選擇較多實作範例及較輕便的 Candle
。
Compared to other frameworks, Burn has a very different approach to supporting many backends. By design, most code is generic over the Backend trait, which allows us to build Burn with swappable backends. This makes composing backend possible, augmenting them with additional functionalities such as autodifferentiation and automatic kernel fusion.
我們選擇將 ML 實作在和 migration 最相關的 try_find_move_task
中。無論是 NUMA Node 之間的平衡或是 Last Level Cache (LLC) 下的平衡都需要藉由該函式來選出適當的任務,若能在該函式中提昇 migrate 的效率便能夠提高整體的 load balance 。
try_find_move_task
輸入值為目標/來源 domain 及理想上被 migrate 任務的 load 。 先使用 filter 來過濾不適合的任務,其過濾條件如下
在所有能被 migrate 的任務挑出後,再從中挑選出一個 load 與目標最接近的任務,最後比較選出的任務 migrate 前及後是否能夠達到較好的 load balance。
我們的目標便是在 filter 引入 Machine Learning ,進一步過濾掉後面可能導致不適合 load balance 的任務。
scx_rusty
Data collection資料蒐集我們使用原本的 scx_rusty
且在判斷為不可 migrate 的 if-statement 內蒐集 label 為 0 的資料。
另一方面則在可以 migrate 的地方蒐集 label 為 1 的資料。
在 workload 部份我們使用了 stress-ng 來給定 30 個 cpu 的 workload
ML 的部份我們使用了 ResNet 來當作模型, 另一方面由於不同的 input features 有不同的數值範圍,因此我們將所有的特徵正規化並紀錄最大最小值,以在 inference 時使用相同的轉換關係。
scx_rusty
scx_rusty
scx_rusty-l2
L2-balance (domain catch)scx_rusty-l2-ml
(stress-ng set)EEVDF
original rusty-l2
ml_rusty
sched-ext/scx
的貢獻sched_ext
實作客製化 Linux CPU 排程器scx
overview HOW