文件導航

執行一次無人值守的研究會話

這是一份無人值守研究會話的執行程式:一個過夜會話,或白天的一次長時間交接。它取代了逐夜提示詞(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 分鐘用來閱讀,不是啟動。

  1. AGENTS.md,全部(命令、凍結套件、規範主頁、Modal 設定)。
  2. PLAN.md:我們處在什麼位置、學到了什麼、常設規則、資料政策和 Next。對你想據以構建的發現,讀它在歸檔裡的證據:git show research-archive-2026-09-24:PLAN.md。
  3. .agents/skills/ 裡的技能:kev-modal-study(啟動、觀察和拉取 GPU 工作;讀它的 Gotchas)、kev-verify(證明一次程式碼改動沒有迴歸)、kev-pr-description(任何 PR 之前)、thermonuclear-code-review。
  4. kev/rounds.py(其 docstring 就是 spec schema)、experiments/rounds/ 裡最接近的過往 spec,以及 kev/autoresearch.py(session)。
  5. 交接本身:授權(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 的 study budget 必須至少是其上界(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,在任何訓練或讀取之前一起提交。

  1. 寫 PLAN.md 小節:為什麼(測得的差距及其證據)、資料(先凍結,帶 manifest)、各臂、規則(主判據、按每個套件設定閾值的護欄、排名)、確認階段,以及預算。用常設規則;不要為一輪發明一個新統計量。
  2. 通過複製最接近的過往 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 在該套件最後一個仍命名它的輪次之後拒絕一輪。
  3. 新資料是 evals/ 下的一個新目錄,帶一個 manifest.json(每個檔案的 sha256、輸入的 hash)。在 SFT 資料政策(PLAN.md)下,私有語料在 git 裡只保留 manifest,用一個 "mirror" 條目指向私有資料集。
  4. 必須:每個被服務或釋出的溫度都來自一個留出資料集池,絕不來自訓練語料的某個劃分。 一輪若讀取校準(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」)。
  5. 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(spec locked_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。