
花兩個多小時我在一張 RTX 4090 上從零訓練了一個 64M 參數(shù)的中文小模型。聽到這件事的人第一反應基本都一樣你圖什么圖它參數(shù)少還是圖它訓練快其實都有但最根本的原因是——當大模型的門檻已經(jīng)被推到千億參數(shù)、上萬張顯卡的時候反而應該有人踏踏實實地把一個小模型從數(shù)據(jù)到權(quán)重的全過程完整跑一遍。minimind 這個開源項目就是干這個的而我這次實測想搞清楚的問題只有一個2 小時的時間投入換來的到底是一個會蹦字的玩具還是一個真能看出門道的東西。先說結(jié)論2 小時后我拿到的不只是一個玩具。它能接續(xù)一段話能完成基礎的中文問答偶爾還會給出讓我意外的連貫表達——但它也會一本正經(jīng)地胡說八道。如果你有 24G 顯存或者手里只有一張 8G 顯存的卡也想完整見識一下大模型訓練全流程這篇文章值得你看完。我會把環(huán)境準備、數(shù)據(jù)選擇、Tokenizer 訓練、stage1 預訓練、stage2 指令微調(diào)的完整過程以及我踩過的三個比較隱蔽的坑全部攤開來講該給參數(shù)給參數(shù)該給命令給命令盡量不讓你走彎路。1. 為什么我把學習重心放在 64M 參數(shù)而不是一步到位追大模型1.1 一個完整但不貴的訓練鏈路很多人聊到大模型訓練第一反應是我沒有 A100這事跟我沒關(guān)系。但 minimind 這類項目恰好證明了另一條路大模型訓練的完整鏈路——數(shù)據(jù)清洗、分詞器訓練、預訓練、指令微調(diào)、偏好對齊——在小模型上同樣存在而且成本低到個人開發(fā)者完全承受得起。我這次選 64M 參數(shù)版本核心原因是它處在學習價值和時間成本的平衡點上。26M 參數(shù)連基礎句式都容易學崩很多模型層面的問題會被參數(shù)規(guī)模掩蓋訓練完你只會覺得它笨是正常的200M 參數(shù)訓練時間會拉長到一天級別不適合2 小時見結(jié)果的定位。64M 則剛好模型結(jié)構(gòu)足夠復雜過擬合、梯度不穩(wěn)定、數(shù)據(jù)敏感這些真實問題都會一一暴露同時顯存占用只要 5GB 左右4090 跑起來非常輕松甚至一些舊一點的顯卡也能跟上。這個選擇還有一層更現(xiàn)實的考慮模型越大排查問題越困難。64M 參數(shù)下你對數(shù)據(jù)做任何一點改動都能在很短的時間內(nèi)看到 loss 曲線的變化。這種即時反饋是學習階段最珍貴的東西。等你在小模型上練出了手感再切換到 200M、1B 甚至更大的模型很多操作其實是同一套邏輯。1.2 2 小時的時間預算是怎么算出來的關(guān)于標題里的2 小時我需要說明這不是隨便定的而是有一個粗略的算賬過程。訓練一個大模型的計算量近似等于 6 倍參數(shù)量乘以訓練 token 數(shù)也就是 6ND。64M 參數(shù)訓練 2 億 token總計算量大約就是6 × 64M × 2億 ≈ 7.68 × 10^16 FLOPsRTX 4090 的 BF16 理論算力是 165 TFLOPs。實際訓練過程中由于小模型的計算密度低顯存帶寬和數(shù)據(jù)加載會拖后腿整體利用率通常只有三成到四成。按 40% 利用率算每秒實際算力大約 66 TFLOPs那么理論耗時是7.68 × 10^16 / (66 × 10^12) ≈ 1164 秒也就是大約 20 分鐘等一下這個結(jié)果比我實際花的時間少很多。原因在于我計算的理論算力沒有計入數(shù)據(jù)加載、日志打印、評測、tokenizer 訓練以及頻繁的中間保存開銷。把這些問題算進去2 小時是一個比較務實的預估。如果你是 3090 或者 4080時間會拉長一些但整體仍然可控。不過這里也要潑一盆冷水64M 參數(shù)不是縮小版的 ChatGPT它更像一塊教學芯片用來理解訓練機制。你要指望它像 7B 模型那樣聊得面面俱到肯定不現(xiàn)實。它的價值在于訓練過程中每一步都會反饋到模型行為上參數(shù)變化是可觀察的適合拿來做實驗和積累直覺。2. 實測環(huán)境與依賴安裝一張 4090 能解決的事別想太多2.1 硬件與軟件棧先交代一下我這次實測的環(huán)境項目配置GPUNVIDIA RTX 4090 24GBCPUIntel i7-13700K內(nèi)存64GB DDR5系統(tǒng)Ubuntu 22.04 LTSPython3.10PyTorch2.1.0 CUDA 12.1transformers4.36.0datasets2.16.0tokenizers0.15.0accelerate0.26.0實際跑下來最大的感受是64M 模型對顯存幾乎沒有壓力。stage1 預訓練過程中顯存峰值大概在 5.2GB 左右加上數(shù)據(jù)緩存也沒有超過 6GB。所以如果你手里只有一塊 8G 顯存的顯卡也可以跑通整個流程只是訓練時間會翻倍尤其要留意數(shù)據(jù)加載和序列長度的設置。2.2 兩個容易卡住的依賴問題第一個坑是 PyTorch 版本。很多人新裝環(huán)境時圖方便直接pip install torch結(jié)果裝成了 CPU 版訓練時發(fā)現(xiàn)一晚上跑不完一個 epoch。檢查方法很簡單在 Python 里執(zhí)行torch.cuda.is_available()返回 False 就說明裝錯版本了。正確做法是到 PyTorch 官網(wǎng)找對應 CUDA 版本的安裝命令安裝。第二個坑是 flash-attention。網(wǎng)上不少教程一上來就推薦裝這個依賴結(jié)果不少人卡在編譯環(huán)節(jié)折騰半小時。對于 64M 這種小模型注意力計算量非常小默認的 attention 實現(xiàn)已經(jīng)足夠快完全不需要 flash-attention。省掉這個依賴環(huán)境配置能少走很多彎路。裝完依賴之后建議先跑一次模型 forward確認能正常輸出再做任何訓練from transformers import AutoConfig, LlamaForCausalLM config AutoConfig.from_pretrained(minimind_64_config.json) model LlamaForCausalLM(config) print(model.num_parameters())如果你看到的參數(shù)數(shù)量在 6400 萬左右說明環(huán)境基本就緒。這一步提前驗證可以避免訓練到一半才發(fā)現(xiàn)配置不對白白浪費時間。3. 數(shù)據(jù)與 Tokenizer 先行小模型的偏科從這里埋下3.1 數(shù)據(jù)量怎么定才是符合 2 小時約束的做法很多人第一次訓練小模型看到網(wǎng)上說訓練數(shù)據(jù)越多越好就直接把幾個 GB 的語料丟進去。結(jié)果就是 2 小時連一個 epoch 都跑不完最后只能草草收場。我這次一開始就按參數(shù)量 × 訓練總 token 數(shù)這條線來配數(shù)據(jù)64M 參數(shù)對應 2 億 token 的訓練量是比較務實的文本文件大約在 500MB 左右。實際準備數(shù)據(jù)時我用了兩部分一部分是從開源中文語料中抽出來的通用文本另一部分是自己攢的問答對。在喂給模型之前做了三件事去掉重復段落避免模型把高頻樣本背下來而不是學到規(guī)律過濾明顯亂碼、純英文廣告和過短的無意義句子按 512 token 的長度切塊超出部分直接截斷。這里比較推薦直接用 Hugging Face datasets 的Dataset.map來做切塊速度和內(nèi)存都友好。處理完的數(shù)據(jù)統(tǒng)一轉(zhuǎn)成 jsonl 格式每一行是一條訓練樣本。數(shù)據(jù)量不用貪多關(guān)鍵在于干凈和覆蓋度。還有一點值得注意小模型的數(shù)據(jù)混合比例會直接影響它的偏科方向。如果通用語料占 90%問答對只占 10%最后模型會更像一個續(xù)寫機器而不是一個問答助手。這個比例在 stage2 指令微調(diào)前要重新考量很多人在小模型上做得不理想不是參數(shù)太小而是數(shù)據(jù)配比一開始就沒想清楚。3.2 為什么不用現(xiàn)成 tokenizer而要自己訓一個minimind 項目支持使用現(xiàn)成的中文詞表但如果你真的想從零訓練我建議自己訓一個 BPE tokenizer。原因很簡單通用 tokenizer 對中文支持往往不理想一個常用漢字會被拆成 2 到 3 個字節(jié)級 token等于白白放大序列長度訓練效率會下降 30% 以上。我用的策略是訓練一個 vocab_size20000 的 BPE tokenizermin_frequency 設為 2。代碼非常簡單幾分鐘就能訓完from tokenizers import Tokenizer, models, pre_tokenizers, trainers tokenizer Tokenizer(models.BPE(unk_tokenunk)) tokenizer.pre_tokenizer pre_tokenizers.Metaspace() trainer trainers.BpeTrainer( vocab_size20000, min_frequency2, special_tokens[s, /s, unk] ) tokenizer.train(files[data/all_corpus.txt], trainertrainer) tokenizer.save(model/tokenizer.json)訓完之后我自己統(tǒng)計過一個平均 20 個漢字的短句在這個 tokenizer 下會被切成 18 個 token 左右效率比直接套用通用詞表高不少。如果你發(fā)現(xiàn)某些常用字頻繁變成unk說明語料覆蓋不夠或者 vocab_size 太小可以適當把 min_frequency 降到 1 重新訓練。這一步的重要性很容易被低估。我見過很多人花大力氣準備訓練數(shù)據(jù)卻在 tokenizer 上草草了事最后模型生成質(zhì)量上不去還找不到原因。Tokenizerr 是模型看到的第一層世界這一步偷懶后面所有調(diào)參都要加倍還回去。4. 2 小時訓練過程全記錄stage1 預訓練 stage2 指令微調(diào)4.1 stage1 預訓練讓模型先學會中文的語感minimind 的訓練鏈路分三個階段stage1 預訓練、stage2 指令微調(diào)、stage3 偏好對齊。我這次在 2 小時內(nèi)完成了前兩個階段。stage1 的目標是讓模型學會中文句子的基本統(tǒng)計規(guī)律——不是理解世界而是先形成語感。我用的模型配置沒有大調(diào)直接參考項目倉庫里 64M 的默認配置詞表大小是 20000。關(guān)鍵超參數(shù)如下參數(shù)數(shù)值effective batch size64micro batch size32gradient_accumulation_steps2max_seq_len512learning_rate5e-4warmup_steps2000schedulercosine decay末值 1e-4optimizerAdamWbeta(0.9, 0.95)weight_decay0.1訓練命令大概長這樣python train.py \ --model_type minimind_64 \ --data_file data/pretrain_data.jsonl \ --tokenizer model/tokenizer.json \ --max_seq_len 512 \ --batch_size 32 \ --gradient_accumulation_steps 2 \ --learning_rate 5e-4 \ --max_steps 6100這里我解釋一下 6100 步怎么來的。effective batch 是 64每條樣本最長 512 token那么每步吃掉 64 × 512 32768 token。目標訓練 2 億 token用除法一算就是約 6100 步。實際跑下來stage1 大約用了一個半小時。訓練日志里能很清晰地看到模型在進入狀態(tài)前 2000 步 loss 從 8.4 快速下降到 3.0 左右這是 warmup 階段學習率從 0 爬升模型開始抓住中文的高頻詞匯搭配2000 到 5000 步loss 緩慢降到 1.8 附近下降速度明顯變慢說明模型開始學習更隱晦的語義結(jié)構(gòu)5000 步之后 loss 曲線出現(xiàn)小幅波動屬于正?,F(xiàn)象。4.2 stage2 指令微調(diào)讓模型學會回答而不是接話stage1 結(jié)束后模型更像一個續(xù)寫器。你給它一句開頭它本能地想把句子寫完但如果你問它問題它大概率不會回答而是繼續(xù)預測最可能的下一個 token。所以必須做 stage2 指令微調(diào)用大量問題-回答的數(shù)據(jù)教會它角色轉(zhuǎn)換。SFT 數(shù)據(jù)我用了約 12 萬條中文指令數(shù)據(jù)考慮到 2 小時的時間預算stage2 只訓練 600 步大約 15 分鐘就結(jié)束。SFT 階段的關(guān)鍵是學習率要用小很多否則預訓練學到的語感會被沖掉出現(xiàn)訓練幾分鐘就變回傻瓜的災難性遺忘現(xiàn)象。SFT 參數(shù)如下python train_sft.py \ --model_weights model_ckpt.pt \ --data_file data/sft_data.jsonl \ --batch_size 16 \ --max_seq_len 512 \ --learning_rate 2e-5 \ --max_steps 600數(shù)據(jù)格式很簡單每行一個 JSON 對象{prompt: 請用一句話介紹自己, response: 我是一個用 minimind 訓練的中文小模型。}我的切身體會SFT 階段不是訓練步數(shù)越多越好。600 步跑完我拿同樣的 prompt 測試模型已經(jīng)能穩(wěn)定給出像模像樣的回答。如果繼續(xù)加到 1200 步回答反而會變得死板因為模型開始過擬合訓練數(shù)據(jù)里的固定句式。所以小模型的 SFT 階段寧可少訓一點也不要貪多。4.3 訓練過程中的實測數(shù)字整個訓練過程的幾個關(guān)鍵數(shù)字我記錄如下方便你對照自己的機器估算指標stage1stage2顯存峰值5.2GB6.8GB平均吞吐約 28k token/s約 22k token/s總步數(shù)6100600耗時約 1.5 小時約 15 分鐘stage2 的吞吐比 stage1 低原因是 prompt 和 response 拼接后輸入序列的平均長度更長訓練數(shù)據(jù)也更集中。整體看下來2 小時的時間預算非常緊湊但老話說得好——時間越緊越能逼你少做無用功。5. 2 小時后它到底能干嘛生成、問答與能力邊界實測5.1 續(xù)寫與主題生成有語感沒邏輯訓練結(jié)束后我沒有馬上上測試集做量化評估而是先做了一輪人的直覺評估。因為對于 64M 參數(shù)這種規(guī)模BLEU 和困惑度只能告訴你有沒有學會統(tǒng)計規(guī)律但用戶最關(guān)心的其實是它說的話像不像人話。第一輪測試是文本續(xù)寫。輸入是人工智能正在改變世界它不僅改變了人工智能正在改變世界它不僅改變了我們的生活方式還改變了我們的思維方式。這句話語法通順邏輯也基本成立盡管內(nèi)容比較安全沒什么信息量。對 64M 參數(shù)來說能穩(wěn)定輸出這種句子說明 stage1 的語感學習是有效果的。第二輪測試是主題生成。我給了一個開放主題冬天的早晨模型生成了一段話大意是冬天的早晨寒冷的風吹過街道雪地上沒有行人只有幾棵老樹在風中搖曳。這段文字里寒冷、雪、風、樹這些詞都和冬天強相關(guān)句子也有畫面感但整體沒有故事線更像是在堆疊高頻詞匯。小模型的生成能力就是這樣的它能抓住詞的搭配抓不住事件的發(fā)展。如果你想讓它寫一個完整的小故事第三句話開始就會出現(xiàn)重復或者跑偏。這不是 bug而是參數(shù)容量決定的必然結(jié)果。5.2 指令問答簡單任務能哄住人復雜推理原形畢露經(jīng)過 stage2 指令微調(diào)之后模型倒是學會了一個非常重要的能力知道自己是在被提問而不是在續(xù)寫。我測試了幾個典型問題。問請用一句話介紹自己它能答出我是一個人工智能助手。問中國的首都是哪個城市它答北京。這類高頻事實性問題只要在訓練語料里出現(xiàn)得足夠多模型就能記住并正確復現(xiàn)。但到了邏輯推理環(huán)節(jié)問題就出來了。我問它小王有 3 個蘋果又買了 2 個現(xiàn)在一共有幾個它回答5 個——這個僥幸答對了。但同樣的題目換成小王有 7 個蘋果用掉了 3 個又收到 4 個現(xiàn)在一共有幾個它的回答就開始混亂有時甚至給出一個大于 10 的答案。這說明它并沒有真正學會加法只是記住了一些高頻問答組合。有一個更典型的例子我問如果一個數(shù)除以 3 余 2除以 4 余 3問這個數(shù)最小是多少小模型直接開始編完全意識不到這是個數(shù)學問題。這個現(xiàn)象恰恰說明語言模型的知識本質(zhì)上還是統(tǒng)計關(guān)聯(lián)不是形式邏輯。5.3 和 7B 模型放在一起看差距到底在哪為了讓64M 到底能干嘛這個問題有參照系我把手頭一個 7B 量級的開源模型拉出來做了同樣的測試結(jié)果對比如下能力維度64M 模型7B 參考模型單次回答穩(wěn)定長度50 字以內(nèi)可長可短邏輯推理極弱中等知識覆蓋面僅訓練語料高頻內(nèi)容更廣重復問題頻繁出現(xiàn)偶發(fā)FP16 部署顯存約 0.2GB約 14GB單次推理耗時CPU毫秒級秒級這張表不是為了打擊 64M 模型的信心而是為了說明小模型的作用半徑是短小、高頻、簡單的任務超出這個半徑的任務就會頻繁答非所問。對我來說這個結(jié)果已經(jīng)值回 2 小時的投入了因為我看清了它的能力邊界在哪里——比背一百遍論文更直觀。6. 這段訓練里踩過的三個坑顯存、過擬合與評估失真6.1 顯存 OOM 的元兇不是模型而是序列長度小模型顯存是真的夠用但我第一版 stage2 配置把 max_seq_len 設成了 1024部分長指令樣本被填充得很滿顯存瞬間沖到 11GB。雖然 4090 扛得住但如果你手頭的卡只有 8G 顯存這一步已經(jīng) OOM 了。排查過程其實很有意思。我一開始以為是 batch size 設太大了從 16 一路降到 4結(jié)果顯存只降了一點點。后來仔細看數(shù)據(jù)加載器里每個 batch 的 token 數(shù)才發(fā)現(xiàn)問題出在序列長度訓練數(shù)據(jù)里有些 prompt 特別長被 padding 到 1024 之后顯存開銷成倍增加。改成 512 長度并把超過長度的樣本直接截斷顯存立刻降回 6GB 以下。所以給大家一個經(jīng)驗小模型出現(xiàn) OOM優(yōu)先檢查序列長度和數(shù)據(jù)加載器而不是立刻懷疑模型本身。很多時候模型占用的顯存只占小頭padding 帶來的無效計算才是大頭。6.2 SFT 階段一多訓就過擬合生成開始復讀機第一次跑 SFT我把 12 萬條數(shù)據(jù)訓了 3 個 epoch結(jié)果生成質(zhì)量反而變差任何 prompt 都會引向訓練集中幾個高頻回答的變體你問它什么是人工智能它回一段套話你問它今天天氣怎么樣它還是回一段套話。整個模型就像一個壞掉的復讀機。這個問題的本質(zhì)是 64M 參數(shù)的容量太小裝不下 12 萬條問答模式的全部多樣性。模型學習了高頻回答的統(tǒng)計規(guī)律卻無法為每條 prompt 建立獨立的特征映射于是所有輸入都被折疊到少數(shù)幾個輸出模式上。把 epoch 改成 1學習率保持 2e-5 之后復讀機癥狀明顯緩解。后來我翻訓練日志發(fā)現(xiàn)第二個 epoch 的評估 loss 已經(jīng)開始上升——典型的過擬合信號。所以對于 64M 參數(shù)這種規(guī)模SFT 階段 1 個 epoch 通常是夠用的如果不夠你更應該懷疑數(shù)據(jù)質(zhì)量而不是盲目加訓練輪數(shù)。6.3 數(shù)據(jù)污染讓測試正確率虛高另一個很隱蔽的坑出現(xiàn)在評估環(huán)節(jié)。我想測模型知不知道中國首都是北京這個問題就把這句問話寫進測試集結(jié)果模型答對了。但后來檢查數(shù)據(jù) pipeline 才發(fā)現(xiàn)這句問話在某個開源語料里出現(xiàn)過已經(jīng)被訓練數(shù)據(jù)吃進去了——也就是說我做了一次標準的開卷考試但以為是閉卷。在大模型評測里數(shù)據(jù)污染同樣存在而且更防不勝防因為網(wǎng)上開源數(shù)據(jù)太多你很難保證測試集里的句子沒在預訓練語料里出現(xiàn)過。但小模型對這種污染的敏感度更高因為它的泛化能力弱很容易通過背題拿到高分本質(zhì)上并沒有真正學會對應能力。我后來改用自己現(xiàn)寫的 20 條問題做盲測并明確排除所有在語料中出現(xiàn)過的問句得到的結(jié)果才算可信。所以做小模型評估時一個基本的紀律是測試數(shù)據(jù)必須在訓練完成后再準備或者至少保證測試樣本沒有出現(xiàn)在來源語料中。7. 從 2 小時到下一步用 DPO 和量化把項目推向可用7.1 用 DPO 補上偏好對齊這一步stage1 學會生成stage2 學會回答但這還不夠。實際測試中我注意到模型對同一個問題可能給出兩個互相矛盾的答案因為它只是學到了答案的統(tǒng)計分布沒有學會挑選更符合人類偏好的那個。minimind 的 stage3 做的就是這件事DPO 偏好對齊。我計劃再花 1 到 2 小時用大約 2 萬條偏好對數(shù)據(jù)把這一步補上。偏好對數(shù)據(jù)的組織方式很簡單同一個 prompt配一個模型自己的回答和一個更優(yōu)的人工回答DPO 會讓模型逐漸加大優(yōu)質(zhì)回答的概率。對小模型來說DPO 的效果通常比 RLHF 更容易穩(wěn)定也不需要復雜的 PPO 工程實現(xiàn)是個人開發(fā)者做對齊的首選方案。7.2 導出 GGUF部署到本地離線環(huán)境訓練完模型之后我準備走一遍訓練到部署的最后一公里把權(quán)重導出成 GGUF 格式再用 llama.cpp 跑 CPU 推理。64M 模型量化后大約只有 30 到 40MB完全可以在沒有 GPU 的機器上實時運行這正好發(fā)揮它低延遲、低資源消耗的優(yōu)勢。這個步驟對個人開發(fā)者來說很有參考價值。很多人在本地部署的所謂小模型動輒幾個 GB 甚至十幾個 GB而 64M 模型在 CPU 上幾乎可以做到即時響應。雖然能力有限但作為個人知識庫問答、離線文本輔助工具便宜好用反而變成了它的核心競爭力。7.3 再做一輪 64M 與 200M 的對照實驗最后我還想做一個對照實驗同樣的數(shù)據(jù)同樣的腳本把模型從 64M 換成 200M 再訓一次。兩個模型在統(tǒng)一測試集上的差異會比任何論文里的 Scaling Law 曲線都直觀。畢竟在真實的數(shù)據(jù)上親手做一次規(guī)模對比比讀一百遍理論都管用。這個對照實驗我還打算加上一組數(shù)據(jù)量減半的變體用來觀察到底是參數(shù)規(guī)模對結(jié)果影響更大還是訓練數(shù)據(jù)量對結(jié)果影響更大。到手之后我可以把四個模型的生成結(jié)果、loss 曲線、推理速度和顯存占用放在同一張表里那會是一份很有參考價值的實測筆記。跑完這 2 小時我心里對大模型訓練這件事的認知發(fā)生了一個關(guān)鍵變化它不再是黑箱而是一個由數(shù)據(jù)、tokenizer、超參數(shù)、訓練步數(shù)共同決定的系統(tǒng)工程。64M 參數(shù)的小模型不會改變世界但它把大模型訓練從玄學拉回了工程。如果你也想搞懂從零訓練的每一步到底在干嘛我給你一個最直接的實操建議別糾結(jié)參數(shù)太小、顯存不夠先從 minimind 的 64M 配置開跑先跑通再跑大。我自己就是從這次實測開始才真正理解了數(shù)據(jù)、參數(shù)和訓練步數(shù)三者之間那個微妙的三角形關(guān)系。