# AGENTS.md — 續作指引 **這個檔案的存在理由:本機很不穩,agent 可能隨時崩潰。** 無論你(人或 agent)從哪裡開始,只要 clone 這個 repo 並讀完本檔,就能接著做。 ```bash git clone https://huggingface.co/HelloSun/SmallThinker4b cd SmallThinker4b cat STATUS.md # 進度真相來源,先讀這個 cat AGENTS.md # 本檔 ``` ## 專案一句話 SmallThinker-4B-A0.6B-Instruct(Q4_K_M,2508 MiB,MoE 32 層 × 32 experts) 在 llama.cpp 上跑,**熱的 expert 權重留在 RAM,冷的丟回 SSD**。 ## 最重要原則(聽到沒) **只要到能跑、能驗證的階段,就立刻上傳。後面每改到程式並編譯過,也要上傳。** 本機十分不穩,你必須保存工作成果,並且假設 agent 崩潰後要能指定 repo 繼續工作。 ```bash ./sync.sh "改了什麼" ``` `git clone https://huggingface.co/HelloSun/SmallThinker4b` 就能接著做。 ## 硬約束 1. 行程**總 RSS**(含 file-backed mmap 的權重頁)要受 RAM 預算約束。 只驗證匿名記憶體是不成立的 —— 權重頁會算進 RSS 且真的佔用系統 RAM。 2. **VmSwap = 0**。 3. **輸出必須與上游逐字相同**。分頁只該影響「權重住哪裡」, 不該影響任何一個 token 的數值。 實測成果(`validate/ab-test.json`,`all_passed: true`): peak RSS 從上游的 **2.565 GiB** 降到 **0.596 GiB**(預算 256 MiB,−76.8%), swap 全程 0,冷權重真的從 SSD 重讀 2.3 GiB,輸出四種設定逐字相同。 ## 已知的坑(不要重蹈) 完整清單在 `STATUS.md` 的「踩過的坑」表格,每一條都有實測證據。 最容易再犯的三個: 1. **`-DGGML_CPU_REPACK=OFF` 是硬性要求**。開著時 ggml 把權重放進匿名緩衝區, `madvise` 會把它**歸零**,輸出變亂碼但行程看起來正常。 2. **`init_mappings(!st::will_page(arch))` 這個 `!` 不能拿掉**。 它是整個機制裡最容易寫反的一行 —— 寫反的話 peak RSS 會變成 2.6 GiB, 但分頁統計看起來完全正常(sweep 有跑、帳面 resident 也對), 所以**光看分頁統計抓不到**。 3. **`fscanf` 讀 `/proc/self/io` 開頭要空格**,否則 `read_bytes` 永遠是 0。 4. **`will_page()` 只能看 arch,不能看 `enabled()`** —— `init_mappings()` 在權重載入之前跑,那時 `register_model()` 還沒被呼叫。 ## 環境 本機(開發/驗證用):16 vCPU、MemTotal 約 2 TB、cgroup `memory.max` = 104 GB、 無 GPU。SSD 是 **overlayfs**。 **換機器一定要用 `tools/io_probe.py` 重測讀取能力**,tok/s 會跟著全變。 ## 常用指令 ```bash # 完整流程 ST_HOME=~/.cache/smallthinker4b ./llama_server.sh --verify # 只看參數 ./llama_server.sh --plan # A/B 驗證 python3 tools/ab_test.py --bin --model \ --budget 1024 --budget 512 --out validate/ab-test.json # 對照組(關分頁) ST_PAGER=0 ./llama_server.sh # 上傳 ./sync.sh "改了什麼" ``` ## 下一步(可選,非必要) 1. **I/O 預取**:目前是 demand paging(第一次碰到缺頁才讀 SSD)。 可以在 sweep 裡對「下一個 token 極可能用到、但現在是冷的」expert 發 `MADV_WILLNEED`,讓 SSD 讀取與計算重疊。參考 sddqwen35a3b_v01 的結論: 它實測預取命中率 0%(相鄰 token 的 top-k 重疊率已達 70%,LRU 本來就留著), 所以不確定這邊有沒有價值 —— 要先量路由重疊率。 2. **KV cache 也放 SSD**:ctx 拉到 32768 時 KV 是 128 MiB(f16), 目前是留在 RAM 的。要更省可以加 KV 檔案 mmap(llama.cpp 有 `--kv-unified` 與部分 lazy load 的機制可利用)。 3. **prefill 的工作集**:prefill 會一次摸遍幾乎所有 expert,所以 prefill 階段 的峰值必然接近整個模型。要壓低只能限制 prefill 批次大小(`-ub`), 分批送 prompt。 ## 常數(改模型就要改) `llama_server.sh` 頂部。這些是模型事實,不是可調參數: ``` N_LAYER=32 N_EMBD=1536 N_EXPERT=32 N_EXPERT_USED=4 EXPERT_FFN=768 N_HEAD=12 N_HEAD_KV=2 KEY_LEN=128 VAL_LEN=128 MODEL_MAX_CTX=32768 MODEL_REPO=Tiiny/SmallThinker-4BA0.6B-Instruct-GGUF MODEL_FILE=SmallThinker-4B-A0.6B-Instruct.Q4_K.gguf MODEL_SIZE=2630212704 LLAMA_COMMIT=b9acf138a1e28ce1fc23b5a4fc4b12444b50f7ea ``` ⚠️ `EXPERT_BYTES` 只是**粗估**(0.6 B/elem),因為 `down_exps` 逐層量化型別不同。 真正精確的數字由 `st_pager.cpp` 在載入時從 GGUF metadata 算出來並印出。