執行一次無人值守的研究會話
這是一份無人值守研究會話的執行程式:一個過夜會話,或白天的一次長時間交接。它取代了逐夜提示詞(round-6 和 night-3 程式,保留在 git tag research-archive-2026-09-24 下的 docs/prompts/),並匯入了那些夜晚出的問題。它應用的規則在 PLAN.md 的「Standing rules for every round」裡;命令在 AGENTS.md 裡。
一次會話在註冊的規則下爬山並確認,留下 Jared 可以據以行動的記錄。它不釋出。
1. 開始
頭 30 分鐘用來閱讀,不是啟動。
AGENTS.md,全部(命令、凍結套件、規範主頁、Modal 設定)。PLAN.md:我們處在什麼位置、學到了什麼、常設規則、資料政策和 Next。對你想據以構建的發現,讀它在歸檔裡的證據:git show research-archive-2026-09-24:PLAN.md。.agents/skills/裡的技能:kev-modal-study(啟動、觀察和拉取 GPU 工作;讀它的 Gotchas)、kev-verify(證明一次程式碼改動沒有迴歸)、kev-pr-description(任何 PR 之前)、thermonuclear-code-review。kev/rounds.py(其 docstring 就是 spec schema)、experiments/rounds/裡最接近的過往 spec,以及kev/autoresearch.py(session)。- 交接本身:授權(Modal 美元、AI Gateway 美元)、範圍內是什麼、什麼需要 Jared。
然後設定:
- 在研究分支上的一個 worktree 裡工作(
git worktree add -b research/<session> /tmp/kev-<session> origin/main)。每次提交後都推,這樣機器休眠也不會丟東西。要進 main 的程式碼走它自己經過評審的 PR。 - 讀
uv run modal billing summary --json,並把metered_cost記為狀態檔案裡的基線(第 6 節)。 - 如果上一次會話留下了狀態檔案,先讀它並從它繼續;分離的 Modal 任務會在你不在時繼續跑。
2. 預算與花費規則
- 授權是這次會話的總量,把仍在執行的一切都算進去。每次啟動之前,再讀一次計費成本,如果
(metered_now - baseline) + sum(admission bounds of everything still running) >= authorization就不要啟動。 - 一次 study 的准入上界在啟動時列印,並儲存在
runs/<study>.spawn.json;一次 benchmark 呼叫的上界是compute_bound(gpu, timeout, trials)(kev/budget.py)。spec 的 studybudget必須至少是其上界(kev.rounds validate會檢查;modal_app.admit_study在任何東西執行之前就拒絕超預算的 study,且一個 study 上限為 $250 和 28,800 s)。 - 留一份儲備(約授權的 10 %),任何階段都不計劃動它:計費讀數滯後且會被修訂,而准入上界嚴重高估讀數(一個讀取批次攜帶其最慢任務的超時)。
- 把每次讀數連同其 UTC 時間記進狀態檔案。AI Gateway 花費(Jev 參考讀數、標籤評委)有自己的上限,由花它的指令碼強制執行,並記在
runs/<name>/usage.json。 - Modal 工作區花費上限只能從 dashboard 提高;撞上它會在訓練中途殺掉執行中的容器。
3. 註冊一輪
一輪就是一個 PLAN.md 小節加一個 spec,在任何訓練或讀取之前一起提交。
- 寫 PLAN.md 小節:為什麼(測得的差距及其證據)、資料(先凍結,帶 manifest)、各臂、規則(主判據、按每個套件設定閾值的護欄、排名)、確認階段,以及預算。用常設規則;不要為一輪發明一個新統計量。
- 通過複製最接近的過往 spec 來寫
experiments/rounds/r<N>.json(聯合增量用 r15,27B 用 r17,技能輪用 r10,無訓練的事後臂用 r20:一個溫度池、插值 checkpoint;向另一個 checkpoint 混合用 r23,其臂為兩個端點的訓練都命名trained_on)。省掉"archive":那個鍵標記已記錄的 5–18 輪。spec 命名的每個 plan 檔案、以及其規則需要的每個父讀數,都必須存在於這個 checkout 裡;如果某個父讀數缺失,launch-reads <spec> --parents會生成它。刪掉被移除套件(kev.suite.REMOVED_SUITES,附原因)的每個讀數:evals/external/scienthoon-v1已在 2026-09-27 移除,所以從第 23 輪起 scienthoon 讀數、面板和護欄都沒了;evals/external/wanli-v2和typesafe-v1已在 2026-09-30 移除,所以從第 27 輪起它們的讀數也沒了,SemIf 是剩下的唯一外部讀數(僅報告)。合併的外部讀數不是關卡:第 24 輪審計過的規則(第 23–26 輪遵循)把 SemIf、WANLI-v2 和 TypeSafe 報為可選面板。validate和launch在該套件最後一個仍命名它的輪次之後拒絕一輪。 - 新資料是
evals/下的一個新目錄,帶一個manifest.json(每個檔案的 sha256、輸入的 hash)。在 SFT 資料政策(PLAN.md)下,私有語料在 git 裡只保留 manifest,用一個"mirror"條目指向私有資料集。 - 必須:每個被服務或釋出的溫度都來自一個留出資料集池,絕不來自訓練語料的某個劃分。 一輪若讀取校準(ECE、Brier、自信錯誤、覆蓋率),就為其臂註冊一個
temperature池(照抄 r20:transfer-r3 校準劃分的八個留出公開來源 + transfer-v9 MMLU-Pro),而一次釋出提供scripts/calibrate_checkpoint.py在同一池上擬合的溫度。訓練來源的留出條目(一個訓練套件的calibration/development劃分)是分佈內的:第 19 輪用 T 0.955 服務其 SFT 臂,那是在sft-v1開發行上擬合的,結果所有校準判據都沒過(breadth-v1 ECE 0.059);第 20 輪的留出資料集池在同一 checkpoint 上給出 0.0085。什麼在強制它:- 從第 21 輪起,
kev.rounds validate和launch拒絕這樣的輪:其規則或確認有一個會被溫度移動的判據(ECE、Brier、NLL、自信錯誤、覆蓋率;除準確率外的一切),卻沒有temperature池。Rounds <= 20 只對每個在訓練語料上訓練的臂列印一條!!! warning,所以它們已記錄的 spec 仍然能通過驗證。 kev.rounds validate拒絕這樣的池讀數:(a) 是某個臂的訓練套件、其組成部分(sft-v1 的inputs.components)或其 plan 的data套件,(b) 彙集了任何臂訓練過的來源,或 (c) 讀取任何訓練語料的calibration或development劃分;它也拒絕無法檢查的池(訓練未知的臂、沒有 manifest 或未列來源的套件)。沒有試驗的 checkpoint 臂可以命名trained_on。- 讀數記錄每個臂的
temperature_source;表格對在訓練語料開發行上服務其試驗的臂列印!!!(第 5–19 輪全是如此;從現在起這樣的溫度僅供篩選)。 scripts/calibrate_checkpoint.py拒絕同樣的擬合集(對照 head.pt 的訓練套件檢查);--allow-in-distribution只用於復現舊的擬合,並記錄在head.pt["temperature_fit"]裡。- 試驗內的溫度(
result.json的calibration_fit)寫著role: in-trial screening ... not a served or shipped temperature。 - 父模型在其試驗開發行上擬合的溫度下服務(對 Kev-27B 就是它釋出的 1.38,在同一批行上擬合);讀數記錄這一點及其釋出的 head.pt T(
parent_temperature_source),當兩者在一個訓練語料的行上相差超過 0.05 時validate會警告。 - 不相交檢查按來源名稱(名義,而非語義):兩個套件以不同名字攜帶同一資料集也能通過。所以池必須使用在構造上於 Kev 中僅用於評測的來源,比如 transfer-r3 的八個留出公開來源和 transfer-v9 的 MMLU-Pro。池讀數的
sources白名單必須命名其套件列出的來源(打錯字是個問題),而檢查器列不出的訓練(evals/之外的data檔案、沒有來源的 manifest)對新的一輪是個問題。 calibrate_checkpoint.py --temperature T(一個手填值,什麼都沒擬合)需要--reason,記在head.pt["temperature_fit"](例如「copied from the pool fit of runs/r20-readout」)。
- 從第 21 輪起,
uv run python -m kev.rounds validate experiments/rounds/r<N>.json(加--partitions以校驗劃分)直到它列印ok。把 PLAN 小節和 spec 提交在一個 commit 裡,推。那個 commit 時間就是註冊時間。
4. 端到端跑它
KEV_GPU=H200 uv run modal deploy modal_app.py # after any change to kev/*.py or any new file under evals/
uv run python -m kev.rounds launch experiments/rounds/r<N>.json # one ::study per study, 60 s apart, logs in runs/<study>.log
caffeinate -i nohup uv run python -m kev.rounds watch experiments/rounds/r<N>.json > runs/r<N>.watch.log 2>&1 &
- 每次 study 的頭五分鐘裡,在
modal container logs <id>裡數每分鐘的最佳化器步數,並對照超時推算牆鍾時間(ep0 step N/M:M 是全部 epoch)。超時的容器什麼都存不下;取消(FunctionCall.from_id(cid).cancel())並以更少的記錄或更長的超時在新 study 名下重新啟動。 watch輪詢已生成的試驗,拉取每個完成的 study(同一時間一個 study 一次拉取),在該臂的讀數就緒時立即啟動(每個臂一次批次的::benchmarks呼叫,間隔 60 s),等它們完成並寫出runs/r<N>-readout/round<N>.json和一張表。它可重啟:狀態在runs/<study>.watch.json,啟動意圖在runs/r<N>-reads-<arm>.json。手工:launch-reads <spec> [--arms a,b] [--parents] [--dry-run]、readout <spec>。- 把讀數寫進 PLAN 小節:每個臂、每條判據及其區間、判決和什麼失敗了。
- 確認是刻意的,從不自動。 對讀數點名的候選,把選擇寫進 PLAN.md 並提交,然後按階段:
launch-reads <spec> --stage <stage> --arm <arm>,再confirm <spec> --stage <stage> --arm <arm>(→runs/r<N>-verdict/<size>-<stage>.json)。在鎖定讀數之前測試面板。每次讀一次,沒有例外。 - 在上限下依次跑幾個已註冊的輪:
uv run python -m kev.autoresearch session experiments/rounds/r19.json [...] --spend-start <baseline> --spend-cap <authorization>。它校验、启动并观察每一轮到其读数结束,在某轮预算会越过上限之前停下,追加到runs/autoresearch-sessions.jsonl,并打印确认命令;它从不运行它们。kev.autoresearch leaderboard刷新runs/leaderboard.{jsonl,md}(不提交),compare在迁移准确率上把试验与参考配对,release-check --study <name>篩選該 study 裡的每個配置(每個配置只有在它的所有 seed 都通過各自的關卡時才通過)。
5. 一次會話可以碰和不可以碰什麼
可以:寫 spec、plan 和 PLAN.md 小節;在新目錄下構建新的凍結資料;通過 modal_app.py 啟動 study 和讀數;改指令碼和 modal_app.py 基礎設施常量;為屬於 main 的程式碼開 PR。
不可以,未經 Jared 明確同意:
- 釋出或改動 Hub 上的任何東西(
kev.publish、hf upload、hf repos tag、scripts/publish_space.sh、一個已釋出的head.pt),把私有倉庫變公開,或部署公開端點; - 提交到 main、強推或合併 PR(程式碼通過經過評審、squash 合併、CI 全綠的 PR 到達 main);
- 編輯
evals/下已存在(凍結)的任何東西,或評測器:kev/experiment.py: EVALUATOR_FILES、各關卡、kev/metrics.py、kev/rounds.py的配對讀數。需要的評測器改動是它自己的 PR,在任何輪依賴它之前用kev-verify和tests/test_rounds.py驗證; - 在註冊的確認階段之外傳
--allow-test或執行locked_test; - 把任何 Jev 輸出,或任何閉源模型的生成,放進訓練資料;
- 本地訓練(32 GB Mac 裝不下這些模型)或在一臺機器上跑兩個訓練程序;
- 從 runs 捲上刪除一個 checkpoint 或快照(容器裡的
modal volume rm、shutil.rmtree),或在註冊的 spec 裡關掉一個完整權重試驗的快照("snapshot_fractions": "none")。完整權重試驗在其步數的 0.25、0.5 和 0.75 處保留快照(kev.experiment.SNAPSHOT_FRACTIONS),這樣讀數能在一次執行結束後找到它的最佳點:第 19 輪找不到,因為唯一的執行中狀態是一個 resume 點,執行結束就被刪了,而 AutoJev 的最佳 checkpoint 在 0.7 epoch。一個 27B 的快照每個試驗約佔 154 GB 卷;這個空間由 Jared 決定,不是會話。快照存在 runs 捲上(主);一個私有 Hub 映象(plan 裡的snapshot_hub_repo,或modal_app.py::mirror_snapshots)是值得保留的 checkpoint 的長期儲存,不是替代品:映象 27B checkpoint(每個約 51 GB,進一個私有倉庫如jaredpalmer/kev-snapshots)同樣由 Jared 決定,且絕不進公開倉庫。
如果一個臂被阻塞(認證、花費上限、30 分鐘內做不成的部署),寫下發生了什麼,轉到下一個臂。不要等人。
6. 韌性
- 狀態檔案
runs/<session>-state.json(runs/ 被 gitignore;在研究分支上git add -f它):基線與授權、帶 UTC 時間的花費讀數、每個 study 及其 spawn id、上界與狀態、已啟動和已拉取的讀數、候選、PR、待定決定。每次啟動、拉取和讀取後都更新它,並隨 PLAN 小節一起提交。 - 分離的任務。 Study 在已部署的應用上生成,能在本地客戶端消失後存活;
study之後的一個本地錯誤可能仍生成了試驗,所以重新啟動前先跑modal container list,絕不在同一 study 名下重啟。探測和 benchmark 用--detach跑。 - 觀察者是本地程序,隨機器或網路一起死。在
nohup和caffeinate下跑它們;任何中斷後都重啟watch(它從狀態恢復)。它自己重試 DNS 和連線錯誤;試驗自身的異常是失敗並被報告。 - 超時的完整權重試驗由觀察者續跑,不由 Modal。 試驗生成時關閉 Modal 的重試;當一個完整權重試驗的呼叫因超時結束時,
watch跑modal_app.py::resume --trial <label>,它會生成下一次嘗試(從最後提交的 resume 點繼續),用該 study 被准入時所用的 GPU 和超時,並記進runs/<study>.spawn.json(attempts,每個試驗最多 1 +kev.budget.FULL_FT_RETRIES,即准入上界計算時用的計數;當前呼叫仍在執行的試驗絕不被續跑)。觀察者停著的時候什麼都不續跑:重啟它,它就會撿起超時。原因:Modal 對每次超時的嘗試收兩次費(超時一次,然後 30 s 後它殺掉的任務再一次),所以Retries(2)給了第 22 輪試驗三次嘗試中的兩次,而那次殺掉的 retry 可能在一個正在執行的嘗試旁邊啟動(scripts/modal_retry_probe.py)。在 ledger 之前生成的 study 沒有計數:resume --trial <label> --beyond-bound手工續跑它,在任何上界之外,並說明這一點。兩次嘗試絕不共享一個試驗:每次在其生成前就記為 pending,各自在kev-leases捲上持有一個租約(每分鐘心跳);在新嘗試的租約新鮮時它會拒絕,而一次續跑在被殺嘗試最後一次心跳後最多等kev.budget.LEASE_STALE(15 分鐘)才生成。 - 網路中斷殺死本地客戶端,不殺遠端工作:客戶端死掉的讀數通常已在 Modal 上完成;從捲上拉它的目錄(
modal volume get kev-runs /<name> runs/<name>),而不是重啟它。 - 失敗的 benchmark 或探測會在捲上留下它的目錄;用新名字重試。
7. 彙報
會話結束時(並在進行中寫入狀態檔案):
- 每輪的 PLAN.md 小節帶著它的註冊、讀數表、確認結果和判決,無論正負,附報告路徑。
- 更新 PLAN.md 的「Where we stand」(已釋出和已確認的候選、執行中的任務、花費)和「What we have learned」(若有發現改變);把每一輪加進 Record 表。
- PLAN.md 裡的一份會話總結:花費(基線、最終讀數、執行中的上界)、Modal 上待處理的內容及完成它的確切命令、事故,以及至多三步下一步及其證據。
- 每個數字都帶 checkpoint、套件和劃分、n 和報告路徑;提交數字來源的讀數和判決(
.gitignore保留報告,不保留預測轉儲;為新讀數目錄加一條規則)。 - 時鐘戳:註冊和結果時間就是提交時間。不要在事情發生之前把時間寫進標題;night 3 的草稿本這麼做了,它的戳就沒法用。
8. 已知坑
- Modal 應用建立速率限制。 一分鐘內超過約三次分離的
modal run會以「App create rate limit exceeded」失敗,什麼都不跑。kev.rounds把啟動錯開 60 s,並把一個臂的讀數批成一次呼叫;手工也照做。 - benchmark 任務裡的
repo@sha過去會移動run@suite@name@flags的每個欄位;modal_app.parse_jobs現在從右邊解析,所以釘住的 Hub revision 是安全的。套件名和名稱不得包含@或,。 - 每個 study 一次拉取。 同一 study 的併發拉取會刪掉彼此的試驗目錄;
pull_study現在持有每個 study 的鎖。試驗仍在跑時拉取是安全的,只重新整理未完成的試驗。 - 拉取會把完整權重留在捲上。
::pull(以及watch)跳過完整權重分片(model*.safetensors,每個 27B checkpoint 或快照約 51 GB)和 resume 點;其餘全都拉下來(結果、行、head.pt、配置)。在捲上讀一個 checkpoint 或快照:::benchmarks --jobs "/runs/<study>/<trial>/snapshots/step-<N>/checkpoint@<suite>@<name>"。::pull --weights在本地確實需要它們時複製分片。 - 資料之後部署。 映象複製
evals/;啟動器只檢查kev/*.py的 hash,所以資料檔案在部署之後才加入的試驗會在容器裡失敗。study上的--gpu H200需要一個用KEV_GPU=H200部署的應用。 - 27B。 僅 H200(bf16 主幹,常駐 55 GB);study 超時最高 28,800 s(lr 2e-5 的 1-epoch 技能增量每個最佳化器步約 8.8 s);fp32 讀取約為 9B 的三倍(spec
read_timeout: {"27b": 14400});鎖定讀數在 H200 上需要--timeout 14400 --memory-mb 131072(speclocked_args)(GPU 來自 spec 的gpu/ 已部署的應用,或手工--gpu H200)。每個 bf16 權重試驗都過不了試驗內的isolation_and_packing關卡(一個 fp32 檢查);從行裡讀結果,並單獨在 bf16 下測量服務隔離。 locked_test命名。 當試驗內篩選關卡失敗時,工具要求-ungated字尾(kev-4b-r8-ungated);判決仍遵循註冊的規則。- 每個套件的讀取超時。
modal_app.READ_TIMEOUTS把長 state 面板設為 7,200 s、文件 5,400 s、transfer-v9 3,600 s,其餘 1,800 s。一個全域性--timeout會抬高批裡每個任務的准入上界。 - 預算準入。 超過其
--budget的啟動在任何東西執行之前就退出;用至少是打印出的上界的預算重啟。 - 外部伺服器是單飛的。 AutoJev 的伺服器一次只答一個請求(忙時 HTTP 529);在長
kev.benchmark --remote之前先探測一個外部端點,並把--remote-concurrency設成它能承受的值。把它拒絕的請求(例如超出其上下文的 422)計為覆蓋率,絕不靜默丟棄。 - 溫度:釋出的 vs 試驗內的。 一次試驗的
result.json及其鎖定摘要按試驗內擬合打分;一次釋出提供scripts/calibrate_checkpoint.py寫進head.pt的那個 T。第一次 AutoJev 正面交鋒用試驗內的 1.19 而非釋出的 1.38 服務 Kev-27B,不得不更正。說明每個數字用哪個 T,以及它在哪裡擬合(第 3 節規則 4:留出資料集,絕不是訓練語料自己的劃分)。 - 長上下文校準。 一個帶
"by_length": true的面板按 state-token 桶報告準確率、ECE、Brier 和自信錯誤(8k 以下到 64k+,以及 8k+/16k+/32k+ 尾部),一條判據可以卡其中一個(long.ece_16k_plus.candidate <= 0.05)。Tokens 從讀數的套件記錄裡計,所以兩邊共享桶。 - 不加新套件版本的套件修復。 一個面板可以在兩邊刪掉來源、任務或一份(私有、按 hash 註冊的)id 或來源列表(
exclude_sources、exclude_tasks、exclude_file),而一個僅報告的面板標為"optional": true,這樣缺一份報告讀數永遠不會讓候選不完整。在任何讀數之前按標籤有效性選擇排除項(第 24 輪取自 2026-09-27 的審計),絕不提交私有列表。 - 工作區容量。 工作區一次最多跑過約十個 GPU 容器;掛起的容器是容量,不是 bug,所以不要重啟它們。
- 小套件。 對 89 或 144 個問題的關卡無法分辨 2-3 pp 的下限;通過一個匯合面板來卡它們。
- Jev 讀數在執行中失敗,遇到閘道器 503;在新名字下重跑整個讀數,而不是拼接部分行。
- 軟目標資料。 寫構建器時,用眼睛檢查幾行記錄:
target之和為 1,且標籤的質量至少 0.5,除非該記錄不可知(kev.data.none_pair曾在軟目標上訓練零質量,已在 #60 修復)。 - 把
modal run ...::study的輸出重定向到日誌檔案;過濾器可能藏起解釋為什麼什麼都沒啟動的SystemExit。