存舊設(shè)備如何運(yùn)行大模型:量化與內(nèi)存映射實(shí)戰(zhàn))
1. 一臺(tái)8GB內(nèi)存的老機(jī)器憑什么還能跑大模型手里有臺(tái)老筆記本或者舊手機(jī)內(nèi)存只有8GB平時(shí)開幾個(gè)網(wǎng)頁就開始卡這是很多人的真實(shí)處境。但最近一段時(shí)間我陸續(xù)在幾臺(tái)這樣的設(shè)備上把大模型跑了起來從一臺(tái)2015年的ThinkPad到一部安卓手機(jī)全程沒有升級(jí)任何硬件。核心思路其實(shí)就一句話別讓模型把參數(shù)全塞進(jìn)內(nèi)存里而是讓它只加載當(dāng)前計(jì)算需要的那一小部分。這件事之所以能成立靠的是兩個(gè)東西的配合。一個(gè)是量化技術(shù)把原本每個(gè)參數(shù)占16位或32位的模型壓縮到4位甚至更低體積直接砍到原來的四分之一到八分之一。另一個(gè)是按需加載的推理框架它不會(huì)一次性把整個(gè)模型讀進(jìn)內(nèi)存而是像翻書一樣用到哪一頁讀哪一頁。這兩者結(jié)合讓一個(gè)原本需要十幾GB內(nèi)存才能運(yùn)行的模型在8GB設(shè)備上也能轉(zhuǎn)起來。我寫這篇東西是想把從零到跑通的全過程拆開講清楚。包括為什么選這個(gè)方案、命令背后到底發(fā)生了什么、8GB內(nèi)存的邊界在哪里、哪些操作會(huì)讓它直接崩掉。如果你手里正好有一臺(tái)閑置的舊電腦或者安卓手機(jī)想拿它做點(diǎn)有意思的事下面的內(nèi)容應(yīng)該能幫你少走不少彎路。提示本文討論的是在本地設(shè)備上運(yùn)行開源模型的技術(shù)方案所有操作均在離線環(huán)境下完成不涉及任何網(wǎng)絡(luò)代理或外部服務(wù)。2. 量化模型到底把什么給“砍”掉了2.1 從16位浮點(diǎn)到4位整數(shù)的壓縮邏輯要理解為什么8GB能跑大模型得先知道模型在內(nèi)存里到底占了多少地方。一個(gè)70億參數(shù)的模型如果每個(gè)參數(shù)用16位浮點(diǎn)數(shù)存儲(chǔ)那就是70億乘以2字節(jié)約等于14GB。這還沒算推理過程中產(chǎn)生的中間激活值和鍵值緩存實(shí)際占用會(huì)更高。8GB內(nèi)存連模型本身都裝不下更別說運(yùn)行了。量化做的事情本質(zhì)上是降低每個(gè)參數(shù)的數(shù)值精度。16位浮點(diǎn)能表示非常精細(xì)的小數(shù)但大模型里絕大多數(shù)參數(shù)并不需要那么高的精度。把16位壓縮到4位整數(shù)相當(dāng)于把每個(gè)參數(shù)的存儲(chǔ)空間從2字節(jié)降到0.5字節(jié)模型體積直接變成原來的四分之一。70億參數(shù)的模型量化到4位后大約只需要3.5GB到4GB的存儲(chǔ)空間。但這里有個(gè)關(guān)鍵點(diǎn)量化不是簡單地把數(shù)字截?cái)?。它需要先統(tǒng)計(jì)每一層參數(shù)的數(shù)值分布范圍然后在這個(gè)范圍內(nèi)做均勻或非均勻的映射。比如某一層的參數(shù)集中在-0.5到0.5之間量化算法會(huì)把這個(gè)區(qū)間切成16個(gè)等份4位能表示16個(gè)值每個(gè)參數(shù)落到最近的等份上。推理時(shí)再反向映射回近似值。這個(gè)過程會(huì)引入誤差但通過合理的分組策略——比如每128個(gè)參數(shù)共享一套量化參數(shù)——可以把精度損失控制在可接受范圍內(nèi)。2.2 量化等級(jí)的選擇Q4、Q5、Q8到底差在哪實(shí)際動(dòng)手時(shí)你會(huì)看到一堆量化版本常見的有Q4_K_M、Q5_K_M、Q8_0這些。數(shù)字越大精度越高體積也越大。Q4表示4位量化Q8表示8位量化。后面的K_M代表使用了K-quant方法中的中等粒度分組。我實(shí)測下來的感受是在8GB內(nèi)存的設(shè)備上Q4_K_M是甜點(diǎn)。一個(gè)70億參數(shù)的模型Q4_K_M版本大約4GB左右加載后加上推理開銷峰值內(nèi)存占用在5.5GB到6GB之間8GB設(shè)備勉強(qiáng)能扛住。如果換成Q5_K_M模型體積會(huì)漲到4.8GB左右峰值內(nèi)存逼近7GB系統(tǒng)本身還要占1GB多很容易觸發(fā)內(nèi)存交換速度直接掉一個(gè)數(shù)量級(jí)。Q8_0就更不用想了體積接近8GB8GB設(shè)備根本跑不起來。這里有個(gè)容易被忽略的細(xì)節(jié)內(nèi)存占用不等于模型文件大小。推理過程中框架需要額外的空間來存放鍵值緩存和中間計(jì)算結(jié)果。鍵值緩存的大小跟上下文長度直接相關(guān)上下文越長緩存越大。所以即使模型文件只有4GB如果你把上下文設(shè)成8192實(shí)際內(nèi)存占用可能會(huì)多出1GB以上。這也是為什么很多人模型能加載成功但一提問就崩——上下文設(shè)太大了。2.3 為什么舊CPU反而比新手機(jī)更穩(wěn)我同時(shí)在舊筆記本和安卓手機(jī)上試過。筆記本是i5-5200U雙核四線程主頻2.2GHz內(nèi)存8GB DDR3。手機(jī)是驍龍665內(nèi)存6GB。按理說手機(jī)的處理器更新但實(shí)際跑下來筆記本反而更穩(wěn)定。原因在于內(nèi)存帶寬和散熱。筆記本的DDR3內(nèi)存雖然老但帶寬有25.6GB/s而且有主動(dòng)散熱可以長時(shí)間滿負(fù)荷運(yùn)行。手機(jī)的LPDDR4X帶寬雖然也不低但散熱是硬傷跑幾分鐘后處理器就會(huì)降頻推理速度從每秒5個(gè)token掉到2個(gè)token。另外手機(jī)系統(tǒng)對(duì)后臺(tái)進(jìn)程的內(nèi)存管理更激進(jìn)大模型進(jìn)程很容易被系統(tǒng)殺掉。所以如果你手頭有舊筆記本優(yōu)先用它。手機(jī)適合做輕量級(jí)的嘗試比如跑一個(gè)30億參數(shù)的小模型或者作為臨時(shí)演示。真要長時(shí)間用還是得靠有主動(dòng)散熱的設(shè)備。3. 一條命令背后的完整執(zhí)行鏈路3.1 從拉取模型到加載進(jìn)內(nèi)存中間發(fā)生了什么很多人以為“一條命令跑大模型”就是敲個(gè)命令然后等結(jié)果但實(shí)際上這條命令背后觸發(fā)了一連串操作。以常見的命令行工具為例當(dāng)你執(zhí)行類似ollama run這樣的指令時(shí)系統(tǒng)會(huì)依次做這幾件事第一步是檢查本地是否已有模型文件。如果沒有它會(huì)從配置的模型倉庫拉取。這里有個(gè)坑默認(rèn)的模型倉庫地址在國內(nèi)訪問可能很慢甚至超時(shí)。解決辦法是配置國內(nèi)鏡像源把拉取地址指向國內(nèi)的鏡像服務(wù)器。具體做法是修改環(huán)境變量或者配置文件把倉庫地址替換成國內(nèi)可訪問的地址。這一步不做好后面所有操作都無從談起。第二步是加載模型到內(nèi)存??蚣軙?huì)讀取模型文件按照量化格式解析每一層的參數(shù)然后分配到內(nèi)存中。注意這里不是一次性全部讀入而是采用內(nèi)存映射的方式把模型文件映射到虛擬內(nèi)存空間實(shí)際物理內(nèi)存只加載當(dāng)前需要的部分。這就是為什么模型文件4GB但啟動(dòng)時(shí)內(nèi)存占用可能只有2GB左右——剩下的部分還在磁盤上用到時(shí)才換入。第三步是初始化推理引擎。包括設(shè)置線程數(shù)、上下文長度、批處理大小等參數(shù)。線程數(shù)一般設(shè)成物理核心數(shù)比如雙核四線程的i5就設(shè)4。上下文長度默認(rèn)可能是2048或40968GB設(shè)備建議設(shè)2048再大就容易爆內(nèi)存。第四步才是接收輸入并生成輸出。這時(shí)候模型開始逐token推理每生成一個(gè)token都要經(jīng)過所有層的計(jì)算。這個(gè)過程是串行的所以速度取決于單核性能和內(nèi)存帶寬。3.2 內(nèi)存映射機(jī)制為什么4GB模型只占2GB內(nèi)存內(nèi)存映射是操作系統(tǒng)提供的一個(gè)能力它允許程序把一個(gè)文件當(dāng)成內(nèi)存的一部分來訪問。程序不需要把整個(gè)文件讀進(jìn)物理內(nèi)存只需要在訪問到某個(gè)位置時(shí)操作系統(tǒng)自動(dòng)把對(duì)應(yīng)的文件內(nèi)容加載進(jìn)來。如果物理內(nèi)存不夠操作系統(tǒng)會(huì)把不常用的部分換出到磁盤。大模型推理框架正是利用了這個(gè)機(jī)制。模型文件被映射到進(jìn)程的地址空間但物理內(nèi)存只保留最近訪問過的那些層。當(dāng)推理進(jìn)行到某一層時(shí)如果這一層不在物理內(nèi)存里就會(huì)觸發(fā)一個(gè)缺頁中斷操作系統(tǒng)從磁盤讀取對(duì)應(yīng)的數(shù)據(jù)。這個(gè)過程會(huì)有延遲但比一次性加載整個(gè)模型要節(jié)省大量內(nèi)存。不過這里有個(gè)前提磁盤速度不能太慢。如果是機(jī)械硬盤隨機(jī)讀取延遲在10毫秒左右每次缺頁中斷都會(huì)造成明顯卡頓。換成固態(tài)硬盤后隨機(jī)讀取延遲降到0.1毫秒以下幾乎感覺不到。所以如果你打算在舊設(shè)備上跑大模型強(qiáng)烈建議把模型放在固態(tài)硬盤上。我試過把模型放在機(jī)械硬盤上推理速度直接減半而且經(jīng)常出現(xiàn)幾秒鐘的停頓。3.3 線程數(shù)與上下文長度的參數(shù)調(diào)優(yōu)線程數(shù)設(shè)置有個(gè)經(jīng)驗(yàn)公式物理核心數(shù)乘以1.5到2。比如雙核四線程的i5設(shè)4個(gè)線程比較合適。設(shè)太少浪費(fèi)性能設(shè)太多會(huì)導(dǎo)致線程切換開銷增加反而變慢。你可以從物理核心數(shù)開始試逐步增加觀察每秒生成的token數(shù)找到峰值。上下文長度是另一個(gè)關(guān)鍵參數(shù)。它決定了模型能記住多長的對(duì)話歷史。設(shè)成2048意味著模型能記住大約1500個(gè)漢字的內(nèi)容。對(duì)于日常問答夠用了。如果你需要處理長文檔可以設(shè)成4096但內(nèi)存占用會(huì)增加大約30%到50%。在8GB設(shè)備上4096是上限再高就會(huì)頻繁觸發(fā)內(nèi)存交換。還有一個(gè)隱藏參數(shù)是批處理大小。它決定了每次同時(shí)處理多少個(gè)請(qǐng)求。對(duì)于單用戶場景設(shè)成1就行。設(shè)大了會(huì)成倍增加內(nèi)存占用而且對(duì)單次推理速度沒有幫助。4. 8GB內(nèi)存的實(shí)際邊界能跑什么不能跑什么4.1 70億參數(shù)模型是上限30億才是舒適區(qū)經(jīng)過多輪測試我總結(jié)出一個(gè)粗略的對(duì)應(yīng)關(guān)系模型參數(shù)量量化等級(jí)模型體積峰值內(nèi)存8GB設(shè)備可行性3BQ4_K_M約2GB約3GB流暢7BQ4_K_M約4GB約6GB勉強(qiáng)可用7BQ5_K_M約4.8GB約7GB容易卡頓13BQ4_K_M約7.5GB超過8GB不可行從表格能看出來30億參數(shù)的模型在8GB設(shè)備上是舒適區(qū)加載快推理速度也能接受每秒能生成8到12個(gè)token。70億參數(shù)是上限能跑但比較吃力每秒3到5個(gè)token而且不能同時(shí)開其他程序。130億參數(shù)就別想了模型文件本身就超過8GB加載階段就會(huì)失敗。這里說的token你可以粗略理解為一個(gè)漢字或半個(gè)英文單詞。每秒5個(gè)token意味著生成100個(gè)漢字需要20秒。這個(gè)速度用來做文字問答可以接受但用來做實(shí)時(shí)對(duì)話就有點(diǎn)慢。4.2 上下文長度對(duì)內(nèi)存的隱形消耗前面提到上下文長度會(huì)影響內(nèi)存占用具體影響有多大我做了個(gè)實(shí)測同一個(gè)7B Q4_K_M模型上下文設(shè)2048時(shí)峰值內(nèi)存5.8GB設(shè)4096時(shí)峰值內(nèi)存7.2GB設(shè)8192時(shí)直接觸發(fā)內(nèi)存交換速度掉到每秒1個(gè)token以下。為什么會(huì)這樣因?yàn)殒I值緩存的大小跟上下文長度成正比。每一層都需要為每個(gè)token保存一份鍵和值層數(shù)越多、上下文越長緩存越大。一個(gè)7B模型通常有32層每層每token的鍵值緩存大約幾十KB2048個(gè)token就是幾十MB32層加起來就是1GB多。上下文翻倍緩存也翻倍。所以如果你發(fā)現(xiàn)模型能加載但一提問就卡死第一個(gè)要檢查的就是上下文長度。把它降到2048甚至1024往往就能解決問題。4.3 系統(tǒng)本身的內(nèi)存開銷不能忽略很多人算內(nèi)存賬的時(shí)候只算模型占用忘了操作系統(tǒng)本身也要吃內(nèi)存。Windows 10空載就要占2GB左右加上瀏覽器、輸入法、后臺(tái)服務(wù)很容易就到3GB。Linux桌面版稍微好點(diǎn)空載1GB左右。如果是純命令行環(huán)境可以壓到500MB以下。所以8GB設(shè)備實(shí)際能留給模型的內(nèi)存只有5GB到6GB。這也是為什么7B Q4_K_M是上限——它峰值就要6GB系統(tǒng)再占2GB剛好卡在8GB的邊緣。如果你用的是Windows建議跑模型前把不必要的后臺(tái)程序全關(guān)掉尤其是瀏覽器和聊天軟件。有條件的話裝一個(gè)輕量級(jí)Linux發(fā)行版能多擠出1GB到2GB的內(nèi)存。5. 舊設(shè)備跑大模型最容易踩的五個(gè)坑5.1 模型下載慢到懷疑人生這是第一個(gè)攔路虎。默認(rèn)的模型倉庫地址在國內(nèi)訪問速度很不穩(wěn)定有時(shí)候幾百M(fèi)B的模型要下幾個(gè)小時(shí)還經(jīng)常斷連。解決辦法是配置國內(nèi)鏡像源。具體操作是找到工具的配置文件把倉庫地址改成國內(nèi)可訪問的鏡像地址。不同工具的配置方式不一樣有的改環(huán)境變量有的改配置文件有的在啟動(dòng)參數(shù)里指定。如果工具支持?jǐn)帱c(diǎn)續(xù)傳盡量用支持?jǐn)帱c(diǎn)續(xù)傳的方式下載。萬一斷了不用從頭再來。另外建議在夜間或者網(wǎng)絡(luò)空閑時(shí)段下載速度會(huì)快很多。我試過在晚上八點(diǎn)下載一個(gè)4GB的模型速度只有幾百KB每秒換成凌晨兩點(diǎn)速度能到5MB每秒以上。5.2 內(nèi)存不足導(dǎo)致的進(jìn)程被殺模型加載到一半突然進(jìn)程消失或者剛開始推理就閃退大概率是內(nèi)存不足被系統(tǒng)殺掉了。Linux系統(tǒng)有個(gè)OOM Killer機(jī)制當(dāng)內(nèi)存嚴(yán)重不足時(shí)它會(huì)選擇占用內(nèi)存最多的進(jìn)程殺掉。大模型進(jìn)程往往就是那個(gè)倒霉蛋。排查方法是查看系統(tǒng)日志搜索OOM相關(guān)的記錄。如果確認(rèn)是內(nèi)存不足有幾個(gè)應(yīng)對(duì)方向降低量化等級(jí)、減小上下文長度、關(guān)閉其他占用內(nèi)存的程序、增加交換空間。交換空間是用磁盤模擬內(nèi)存雖然速度慢但能防止進(jìn)程被殺。在Linux上可以通過創(chuàng)建交換文件來增加交換空間建議設(shè)成4GB到8GB。注意交換空間只能救急不能根本解決性能問題。一旦開始大量使用交換空間推理速度會(huì)下降一個(gè)數(shù)量級(jí)。5.3 散熱不足導(dǎo)致的降頻舊設(shè)備散熱往往不理想跑大模型時(shí)CPU長時(shí)間滿載溫度很快升到90度以上然后觸發(fā)降頻保護(hù)。降頻后主頻可能從2.2GHz掉到1.2GHz推理速度直接減半。解決辦法有幾個(gè)墊高設(shè)備底部增加進(jìn)風(fēng)量、用外部風(fēng)扇吹、限制線程數(shù)降低CPU負(fù)載、在空調(diào)房里用。我試過在筆記本下面墊兩個(gè)礦泉水瓶蓋溫度能降5度左右。如果設(shè)備支持還可以用軟件限制CPU最大頻率犧牲一點(diǎn)性能換穩(wěn)定性。手機(jī)上的問題更嚴(yán)重因?yàn)槭謾C(jī)沒有風(fēng)扇全靠被動(dòng)散熱。跑大模型時(shí)手機(jī)背面會(huì)燙手幾分鐘后就會(huì)降頻。如果非要用手機(jī)跑建議摘掉手機(jī)殼放在金屬表面上幫助散熱并且不要邊充電邊跑。5.4 模型存儲(chǔ)路徑配置錯(cuò)誤默認(rèn)情況下模型會(huì)下載到系統(tǒng)盤的用戶目錄下。如果系統(tǒng)盤空間不夠下載到一半就會(huì)失敗。而且系統(tǒng)盤通常是固態(tài)硬盤空間本來就緊張。解決辦法是修改模型存儲(chǔ)路徑把它指向空間更大的數(shù)據(jù)盤。不同工具的修改方式不同。有的通過環(huán)境變量指定比如設(shè)置一個(gè)變量指向目標(biāo)目錄。有的通過配置文件修改。修改后記得把之前下載的模型文件也遷移過去否則工具會(huì)重新下載。遷移時(shí)注意保持目錄結(jié)構(gòu)一致否則工具可能識(shí)別不到。5.5 上下文設(shè)太大導(dǎo)致推理卡死這個(gè)坑前面提過但值得再強(qiáng)調(diào)一次。很多人看到上下文長度參數(shù)覺得越大越好直接設(shè)成8192甚至16384。結(jié)果模型加載成功但一提問就卡住不動(dòng)等幾分鐘才出一個(gè)token。上下文長度不是越大越好而是要跟你的實(shí)際需求匹配。如果你只是做簡單的問答2048足夠了。如果需要處理長文檔可以適當(dāng)調(diào)大但要先確認(rèn)內(nèi)存夠不夠。一個(gè)簡單的判斷方法是模型體積加上上下文長度乘以每token緩存大小再留出1GB給系統(tǒng)如果超過物理內(nèi)存就會(huì)出問題。6. 讓舊設(shè)備跑得更穩(wěn)的幾個(gè)實(shí)操技巧6.1 用輕量級(jí)Linux替代Windows同樣的硬件跑Linux比跑Windows能多出1GB到2GB的可用內(nèi)存。因?yàn)閃indows的圖形界面和后臺(tái)服務(wù)占用了大量內(nèi)存而Linux可以只裝命令行環(huán)境把內(nèi)存全部留給模型。我在一臺(tái)8GB的舊筆記本上做過對(duì)比Windows 10下跑7B Q4_K_M模型峰值內(nèi)存占用7.5GB經(jīng)常觸發(fā)交換換成Ubuntu Server無圖形界面后峰值內(nèi)存降到6.2GB運(yùn)行穩(wěn)定多了。如果不想重裝系統(tǒng)也可以考慮用U盤啟動(dòng)一個(gè)輕量級(jí)Linux發(fā)行版專門用來跑模型。6.2 把模型放在固態(tài)硬盤上前面提到過內(nèi)存映射機(jī)制依賴磁盤讀取速度。機(jī)械硬盤的隨機(jī)讀取延遲是固態(tài)硬盤的100倍每次缺頁中斷都會(huì)造成明顯卡頓。如果你還在用機(jī)械硬盤強(qiáng)烈建議換一塊固態(tài)硬盤哪怕是最便宜的SATA固態(tài)體驗(yàn)也會(huì)有質(zhì)的提升。如果設(shè)備不支持加裝固態(tài)硬盤可以考慮用USB 3.0外接固態(tài)硬盤。USB 3.0的帶寬有5Gbps實(shí)際讀取速度能到400MB/s以上比機(jī)械硬盤快得多。注意要用USB 3.0接口插在USB 2.0接口上速度會(huì)大打折扣。6.3 限制線程數(shù)避免過熱降頻線程數(shù)設(shè)得越高CPU負(fù)載越大溫度上升越快。在散熱不好的設(shè)備上設(shè)太多線程反而會(huì)導(dǎo)致降頻最終速度還不如少設(shè)幾個(gè)線程。我的經(jīng)驗(yàn)是先從物理核心數(shù)開始試如果溫度上升太快就減一個(gè)線程。比如雙核四線程的i5先設(shè)4個(gè)線程如果溫度很快到90度就改成3個(gè)或2個(gè)。雖然理論性能下降了但因?yàn)楸苊饬私殿l實(shí)際速度可能反而更穩(wěn)定。6.4 關(guān)閉不必要的后臺(tái)服務(wù)Linux下可以用systemctl命令查看正在運(yùn)行的服務(wù)把不需要的關(guān)掉。比如藍(lán)牙服務(wù)、打印服務(wù)、圖形界面相關(guān)的服務(wù)在純命令行環(huán)境下都可以關(guān)。Windows下可以通過任務(wù)管理器禁用啟動(dòng)項(xiàng)把不必要的后臺(tái)程序關(guān)掉。一個(gè)簡單的判斷方法跑模型前打開任務(wù)管理器或top命令看看內(nèi)存占用。如果空載內(nèi)存超過2GB說明后臺(tái)程序太多了還有優(yōu)化空間。7. 這套方案適合誰不適合誰如果你手里有一臺(tái)閑置的舊電腦內(nèi)存8GB左右想拿它做點(diǎn)有意思的事比如本地問答、文檔摘要、代碼輔助這套方案是可行的。它不需要聯(lián)網(wǎng)不需要付費(fèi)所有數(shù)據(jù)都在本地隱私性也有保障。30億參數(shù)的模型在8GB設(shè)備上跑得很流暢70億參數(shù)也能勉強(qiáng)用。但如果你期望的是跟在線服務(wù)一樣的響應(yīng)速度那可能會(huì)失望。舊設(shè)備跑大模型的速度是以秒為單位的生成一段幾百字的回答需要幾十秒。它適合不趕時(shí)間的場景比如晚上睡前問幾個(gè)問題或者處理一些不緊急的文檔。另外這套方案對(duì)動(dòng)手能力有一定要求。你需要會(huì)基本的命令行操作知道怎么改配置文件遇到問題能看日志排查。如果完全沒有接觸過Linux可能需要先花點(diǎn)時(shí)間熟悉基本操作。我在幾臺(tái)舊設(shè)備上反復(fù)折騰下來最大的體會(huì)是瓶頸往往不在模型本身而在內(nèi)存和散熱。把這兩個(gè)問題解決好舊設(shè)備跑大模型并沒有想象中那么難。量化技術(shù)已經(jīng)把門檻降得很低了剩下的就是耐心調(diào)參和反復(fù)測試。