指南:面向調(diào)度器測試的 Harness API 設(shè)計規(guī)范)
SGLang Scripted Runtime 開發(fā)指南面向調(diào)度器測試的 Harness API 設(shè)計規(guī)范【免費下載鏈接】sglangSGLang is a high-performance serving framework for large language models and multimodal models.項目地址: https://gitcode.com/GitHub_Trending/sg/sglang導(dǎo)讀本文基于 SGLang 倉庫中.claude/skills/scripted-runtime-notes/SKILL.md展開系統(tǒng)講解 SGLang Scripted Runtime腳本化運行時測試基礎(chǔ)設(shè)施的設(shè)計哲學(xué)——什么情況下才應(yīng)該為它新增 Harness API、什么情況下不應(yīng)該。Scripted Runtime 允許測試以生成器腳本yield方式逐步驅(qū)動真實調(diào)度循環(huán)測試代碼直接讀取r.req.*與t._scheduler.*字段而非依賴封裝層。讀者將掌握三條 API 新增判據(jù)控制原語 / Hook 支撐 / 多結(jié)構(gòu)派生、反模式清單削弱斷言、探測實現(xiàn)細節(jié)、直接調(diào)用調(diào)度器私有方法以及如何通過yield推進引擎自身驅(qū)動的行為超時、空閑檢測等。Scripted Runtime 是什么無封裝邊界的調(diào)度器測試通道Scripted Runtime 是 SGLang 測試體系中專為調(diào)度器scheduler行為驗證設(shè)計的一套腳本化運行時。與常規(guī)的端到端 HTTP 測試不同它讓測試腳本以生成器generator的形式編寫腳本中每個yield讓調(diào)度循環(huán)前進一個 step測試可以像操作真實引擎一樣啟動請求、暫停生成、中止請求、驅(qū)逐 radix 緩存、耗盡 KV 池。它的核心設(shè)計前提是Tests readr.req.*andt._scheduler.*directly — there is no encapsulation boundary. A thin wrapper buys zero isolation; it only grows the surface.即測試直接讀取r.req.*請求句柄上的調(diào)度器 Req 對象字段和t._scheduler.*ScriptedContext 暴露的 Scheduler 內(nèi)部結(jié)構(gòu)刻意不設(shè)置封裝邊界。因為薄封裝不帶來任何隔離價值只會擴大 API 表面積、增加維護成本。從源碼結(jié)構(gòu)看Scripted Runtime 的完整實現(xiàn)分布在 python/sglang/test/scripted_runtime/ 目錄下核心組件包括context/api.py—ScriptedContext腳本入口封裝全部公開操作與查詢scheduler_hook.py—ScriptedSchedulerHook掛載進真實調(diào)度器、驅(qū)動腳本生成器執(zhí)行req_handle.py—ScriptedReqHandle指向某個請求的輕量句柄rid contexthttp_server.py—ScriptedHttpServer承載腳本執(zhí)行會話tokenizer_recv_proxy.py— tokenizer 接收代理用于 Hook 支撐類 API 的數(shù)值累計。它通過scheduler.maybe_init_scripted_scheduler_hook()惰性掛載見 scheduler.py并在每個 batch 執(zhí)行前回調(diào)scripted_scheduler_hook.on_run_batch(batch)見 scheduler.py從而讓腳本能觀測每一輪 forward 的 batch 構(gòu)成。何時該新增 Harness API三條判據(jù)SKILL.md 給出了一個決策框架只有當(dāng) API doing real work 時才新增并列舉了三種合規(guī)類型。判據(jù)一控制原語Control primitiveAPI 必須通過一條真實路徑驅(qū)動引擎例如start_req— 發(fā)起請求pause_generation— 暫停生成abort/abort_all— 中止請求evict_radix— 驅(qū)逐 radix 緩存exhaust_kv— 耗盡 KV 池制造壓力。判據(jù)要求復(fù)用真實路徑絕不手工篡改狀態(tài)never hand-mutate state。也就是說實現(xiàn)這些 API 時應(yīng)當(dāng)走真實的控制請求通道而不是直接修改調(diào)度器內(nèi)部字段。這一點在 context/lifecycle.py 中得到印證pause_generation、continue_generation、abort_all、abort、flush_cache全部通過_await_control向真實 HTTP 端點/pause_generation、/continue_generation、/abort_request、/flush_cache發(fā)送請求并等待對應(yīng)控制消息PauseGenerationReqInput、ContinueGenerationReqInput、AbortReq、FlushCacheReqInput到達后再返回確保控制指令確實被調(diào)度器消費而不是發(fā)完即忘。start_req的完整參數(shù)簽名見 context/api.py支持prompt_len、max_new_tokens、rid顯式請求 ID、ignore_eos、priority、dp_rank、prompt_token、return_logprob、logprob_start_len、top_logprobs_num、stop_token_ids、temperature、lora_path等參數(shù)。從 test_scripted_runtime_core.py 的用例可以看到這些參數(shù)的實際語義不傳rid時自動生成以scripted-開頭的 ridtest_start_req_auto_rid_and_finishespriority會真實傳播到調(diào)度器 Req 的req.priority字段test_start_req_priority_is_propagatedignore_eosTrue時即使模型輸出 EOS 也會解碼滿max_new_tokens長度test_start_req_ignore_eos_runs_full_length。判據(jù)二Hook 支撐Hook-backed如果某個值無法從快照中直接讀取需要通過以下兩種機制之一累計得到scheduler_hook.on_run_batch— 在每個 batch 執(zhí)行時被真實調(diào)度器回調(diào)scheduler.py可用于記錄 forward 輪次、batch 模式、rid 集合等recv proxychunks_done— 通過 tokenizer_recv_proxy.py 攔截 tokenizer 接收消息來累計數(shù)值。這類 API 必須是只讀的絕不 monkey-patch絕不運行時篡改調(diào)度器行為絕不向srt/生產(chǎn)代碼添加*_count之類的計數(shù)器字段。從scheduler_hook.py的實現(xiàn)看on_run_batch只做記錄將forward_iter、forward_mode、rids、chunked rid 追加到_batch_logScriptedBatchRecord不修改任何調(diào)度器狀態(tài)。chunks_done則用于回答某個請求的 prefill 被切成了幾塊這類無法從快照推導(dǎo)的問題——測試 test_scripted_runtime_core.py 中test_chunks_done_zero_for_unchunked_prompt、test_chunks_done_counts_two_chunks、test_chunks_done_scales_with_prompt驗證了chunk_size與chunks_done的換算關(guān)系如5 * chunk_size的 prompt 恰好產(chǎn)生 5 塊。判據(jù)三多結(jié)構(gòu)派生、被廣泛復(fù)用Multi-structure derivation, widely reusedAPI 需要同時掃描多個調(diào)度器內(nèi)部結(jié)構(gòu)才能回答查詢并且會被大量測試復(fù)用例如is_idle/is_fully_idle— 判斷引擎是否空閑status— 查詢請求狀態(tài)unknown/running/finished等batch_composition— 返回prefill/decode/chunked/running四類 rid 集合。以 context/queries.py 中的batch_composition為例它同時讀取scheduler.chunked_req、scheduler.running_batch、scheduler.last_batch及其forward_mode才能把請求歸入 prefill / decode / chunked / running 四個互不相交的子集。_get_all_reqs則橫跨chunked_req、waiting_queue、running_batch含 PP 流水線下的mbs/last_mbs/running_mbs等多個結(jié)構(gòu)。之所以要求被廣泛復(fù)用是因為多結(jié)構(gòu)掃描邏輯一旦內(nèi)聯(lián)進單個測試很容易寫錯且難以維護只有真正高頻復(fù)用才值得提煉為公共 API。不滿足任何判據(jù)別加如果 API 不符合上述三條判據(jù)那么不要新增——直接在測試?yán)镒x取r.req.X/t._scheduler.X或者內(nèi)聯(lián)一次性的單用途 accessor。反模式清單兩類絕不允許的行為SKILL.md 明確了兩條Never鐵律絕不為缺失的探針而削弱斷言Weaken an assertion to fit a missing probe.如果某個斷言由于缺少探針probe而無法通過正確做法是補一個能支撐斷言的探針按上述判據(jù)而不是把斷言改弱。例如在 test_scripted_runtime_core.py 中test_abort_single_handle_finishes_with_abort_reason斷言被中止的請求最終攜帶FINISH_ABORT的finished_reason來自 schedule_batch.py 的FINISH_ABORT這就是斷言結(jié)果而非過程的正面案例——它驗證的是中止請求的可觀察后果。絕不去探測實現(xiàn)細節(jié)Probe implementation details (field non-None, which branch ran) — assert the consequence.兩類典型的實現(xiàn)細節(jié)探測斷言某個字段非 None如field is not None——這只是在確認(rèn)內(nèi)部結(jié)構(gòu)形狀而非用戶可觀察行為斷言走了哪個分支——這會把測試與實現(xiàn)強耦合實現(xiàn)一重構(gòu)測試就碎。正確做法是斷言行為的后果consequence。例如test_flush_cache_clears_radix_tree沒有去探測flush_cache內(nèi)部執(zhí)行了什么分支而是斷言flush_cache之后get_all_node_hit_counts()為空——驗證radix 樹被清空這一結(jié)果test_get_all_node_lock_refs_held_during_run_released_after斷言請求運行期間 radix 節(jié)點lock_ref 1、完成后歸零同樣是驗證鎖引用的持有/釋放這一可觀察結(jié)果。其他要點引擎自身驅(qū)動的行為SKILL.md 最后一條提示針對引擎自身驅(qū)動engine-self-driven的行為這是最容易踩坑的地方Never synchronously call a scheduler private (e.g.scheduler._abort_on_waiting_timeout()) from the harness/test.即絕不要從 harness 或測試中同步調(diào)用調(diào)度器的私有方法如scheduler._abort_on_waiting_timeout()原因有三錯誤的循環(huán)相位私有方法往往被設(shè)計在調(diào)度循環(huán)的特定階段調(diào)用同步調(diào)用會發(fā)生在錯誤的 loop phase繞過注入通道直接調(diào)用會繞過有序的recv_requests→process_input_requests注入鏈路這條鏈路定義在 scheduler.py 及 request_receiver.py 中導(dǎo)致控制消息與請求處理失序可能觸發(fā)真實循環(huán)永遠不會達到的狀態(tài)例如在引擎已暫停paused期間調(diào)用超時處理會執(zhí)行到真實運行中不可能出現(xiàn)的狀態(tài)組合。對于引擎自身負(fù)責(zé)驅(qū)動的掃描行為如等待超時timeout、空閑檢測idle正確姿勢是啟用對應(yīng)的配置或環(huán)境變量然后通過yield推進調(diào)度循環(huán)讓真實循環(huán)在正確的時機自行觸發(fā)。這正體現(xiàn)在scheduler_hook.py的_drive_engine_through_warmup中它不斷yield并輪詢proxy.work_reqs_seen與scheduler.is_fully_idle()把服務(wù)端 warmup 請求驅(qū)動到完全排空且針對 PP流水線并行場景要求空閑狀態(tài)連續(xù)保持2 * (pp_size pp_async_batch_depth)個 microbatch 輪轉(zhuǎn)避免瞬時假空閑——全程沒有調(diào)用任何調(diào)度器私有方法。實戰(zhàn)示例一段完整的腳本化測試將上述規(guī)范落到實際一段典型的腳本化測試如下模式來自 test_scripted_runtime_core.pyfrom sglang.test.scripted_runtime.context import ScriptedContext from sglang.test.scripted_runtime.test_case import ScriptedTestCase from sglang.test.scripted_runtime_chunked_helpers import ( advance_to_decode_step, run_until_finished, base_engine_kwargs, ) class TestScriptedRuntimeDemo(ScriptedTestCase): ENGINE_KWARGS base_engine_kwargs(chunked_prefill_size64) def test_pause_retract_parks_in_waiting_queue(self): self.server.execute_script(self._script) staticmethod def _script(t: ScriptedContext): r t.start_req(prompt_len16, max_new_tokens8) yield from advance_to_decode_step(r, 1) # 推進到 decode 第 1 步 t.pause_generation(moderetract) # 控制原語真實路徑 yield assert r.req in t.scheduler.waiting_queue # 斷言可觀察后果 t.continue_generation() yield from run_until_finished(r) assert r.finished關(guān)鍵點回顧測試類繼承ScriptedTestCase必須設(shè)置非空的ENGINE_KWARGSsetUpClass會啟動ScriptedHttpServer會話見 test_case.py腳本是一個靜態(tài)生成器函數(shù)yield推進調(diào)度循環(huán)yield from復(fù)用輔助推進器斷言的是行為后果請求被 park 到 waiting_queue、最終 finished而不是調(diào)度器內(nèi)部字段是否非 None。會話管理方面ScriptedHttpServer保證shutdown()冪等可安全調(diào)用多次并在會話被標(biāo)記為 dirty 時拒絕執(zhí)行腳本——這些都是 http_server.py 提供的運行期保障。小結(jié)API 新增決策速查場景是否新增 API依據(jù)需要通過真實路徑驅(qū)動引擎啟停請求、中止、驅(qū)逐、耗盡 KV? 新增控制原語判據(jù)一復(fù)用真實路徑、禁止手工改狀態(tài)數(shù)值無法從快照讀取需on_run_batch或 recv proxy 累計? 新增只讀 Hook API判據(jù)二禁止 monkey-patch、禁止向srt/加*_count需同時掃描多結(jié)構(gòu)且被廣泛復(fù)用is_idle/status/batch_composition? 新增派生查詢判據(jù)三單次使用則內(nèi)聯(lián)僅讀取單個字段、一次性使用? 直接在測試中讀r.req.X/t._scheduler.XElse: dont 原則斷言無法通過? 絕不削弱斷言補一個能支撐斷言的探針想確認(rèn)字段形狀 / 分支走向? 絕不斷言實現(xiàn)細節(jié)改為斷言行為后果需要觸發(fā)超時 / 空閑等引擎自驅(qū)動行為? 絕不調(diào)用調(diào)度器私有方法啟用配置 yield推進真實循環(huán)這套規(guī)范的核心可以概括為一句話Scripted Runtime 的價值在于通過真實調(diào)度路徑驅(qū)動引擎、并斷言可觀察后果任何繞過真實路徑的私有調(diào)用、任何為遷就探針而削弱的斷言都會讓測試失去驗證意義。按照三條判據(jù)取舍 API測試既能保持對調(diào)度器內(nèi)部結(jié)構(gòu)的直接可見性又不至于把 harness 變成第二個需要維護的影子調(diào)度器。【免費下載鏈接】sglangSGLang is a high-performance serving framework for large language models and multimodal models.項目地址: https://gitcode.com/GitHub_Trending/sg/sglang創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考