)
12G顯存跑27B模型128K上下文decode每秒50個token以上——這三個數(shù)字放在一起的時候我第一反應(yīng)是參數(shù)寫錯了第二反應(yīng)是想試一下。結(jié)果真讓我跑起來了而且不是那種“勉強出字”的狀態(tài)是正常對話、正常寫代碼、正常處理長文檔的狀態(tài)。這篇文章就把整個思路、量化選型、資源計算和踩坑過程完整曬一遍給手里只有消費級顯卡、又不想折騰云GPU的人做一個可復(fù)現(xiàn)的參考。先交代一下環(huán)境和測試對象顯卡是RTX 3060 12G顯存帶寬360GB/s左右算力在今天的消費卡里只能算中游。測試模型以Qwen2.5-27B這類稠密27B模型為主順帶聊一下MiniMax H3這種新模型的特殊玩法。下面所有參數(shù)和命令都是實測過的你可以直接照著抄但要根據(jù)自己的卡微調(diào)。1. 項目概述為什么說這是“極限”27B參數(shù)意味著什么FP16精度下光權(quán)重就要27B乘以2字節(jié)也就是54GB這已經(jīng)超過絕大多數(shù)單卡顯存。哪怕是跑云上的A100 80G也塞不下幾個并發(fā)。所以要把27B塞進12G顯存第一步不是優(yōu)化代碼而是做數(shù)學(xué)上的減法。顯存占用主要由三塊構(gòu)成模型權(quán)重、KV cache、運行時開銷CUDA context、激活值、中間buffer。其中權(quán)重是固定的KV cache隨著上下文長度線性增長運行時開銷則和你用的推理框架強相關(guān)。12G顯存能用的實際空間其實不到12GCUDA context和驅(qū)動預(yù)留大概會吃掉0.5到1G所以真實可用空間要按10.5到11.5G來規(guī)劃。核心難點有兩個一是權(quán)重怎么壓縮到10G左右二是128K上下文的KV cache怎么不把剩下的空間吃光。這兩個問題解決不了后面什么decode速度都是空談。我先把整條技術(shù)路線列出來后面逐一展開。資源項FP16INT8Q4_K_MQ3_K_SQ2_K27B權(quán)重~54GB~27GB~16.5GB~12.5GB~10.8GB128K KV cache(BF16)8.6GB----128K KV cache(Q8)-4.3GB4.3GB4.3GB4.3GB128K KV cache(Q4)-2.1GB2.1GB2.1GB2.1GB一眼就能看出來FP16和INT8直接出局Q4_K_M配合Q8 KV也超了12G預(yù)算。真正可行的組合只有兩個方向Q3_K_S級別的權(quán)重配合Q4 KV cache或者M模型配合某種“不把所有參數(shù)都讀到顯存里”的推理方式。這個結(jié)論決定了后面所有操作。1.1 目標(biāo)讀者和適用場景這篇文章主要面向三類人一是手里有12G到16G消費級顯卡想跑本地大模型但被各種顯存報錯勸退的玩家二是需要在本地處理長文檔、代碼倉庫、小規(guī)模批處理的開發(fā)者三是想搞懂量化、KV cache、MoE這幾塊技術(shù)細(xì)節(jié)的學(xué)習(xí)者。不適用的人群也很明確追求極致生成質(zhì)量、不接受量化損失的人直接去用云API需要高并發(fā)生產(chǎn)環(huán)境的人繼續(xù)用vLLM配合多卡。本地單卡跑27B的定位始終是“個人生產(chǎn)力工具”不是“企業(yè)級推理服務(wù)器”。想明白這一點很多參數(shù)選擇就不會糾結(jié)了。2. 工具鏈與量化方案選型2.1 為什么最終選了llama.cpp市面上能跑27B模型的推理框架很多我在這個項目里主要對比了四個Ollama、llama.cpp、Transformersbitsandbytes、vLLM。Ollama上手最友好一條命令拉模型就能跑但它的封裝層級較多對底層參數(shù)的控制能力有限尤其是KV cache量化、專家卸載這些高級選項Ollama的暴露程度不夠。Transformersbitsandbytes能做4bit加載但速度不理想HuggingFace的推理棧在長上下文場景下內(nèi)存翻倍的問題很突出。vLLM在服務(wù)化和吞吐上有優(yōu)勢但它的PagedAttention在12G顯存上跑27B量化模型顯存規(guī)劃不夠精細(xì)經(jīng)常是啟動就OOM。llama.cpp是最后的選擇也是唯一能同時滿足三個條件的選擇。它的GGUF量化格式本身就是為消費級硬件設(shè)計的支持靈活的層數(shù)卸載-ngl參數(shù)支持KV cache量化--cache-type-k/v支持Flash Attention-fa支持MoE專家卸載--n-cpu-moe這幾個能力組合起來正好覆蓋我們面臨的所有瓶頸。雖然它也有一些歷史包袱比如某些新模型架構(gòu)支持滯后但作為本地推理引擎它的靈活度目前沒有對手。2.2 量化格式和檔位選擇確定llama.cpp之后量化格式就只能是GGUF。GGUF不是一種固定精度的格式而是一個容器里面可以裝不同比特數(shù)的量化權(quán)重。常見檔位從高到低是Q8_0、Q6_K、Q5_K_M、Q4_K_M、Q3_K_S、Q3_K_M、Q2_K數(shù)字越大保留精度越高文件也越大。原則很簡單在能塞進顯存的前提下選精度最高的那個。但如果就是塞不進去寧可降一檔量化也不要強行開CPU offload。實測數(shù)據(jù)說明一個反直覺的結(jié)論在12G顯存上Q3_K_S全GPU加載decode速度比Q4_K_M加部分CPU offload快一倍以上。原因后面講帶寬的時候再展開。具體到操作如果你用的是HuggingFace上的原始FP16權(quán)重需要手動跑一次量化命令git lfs install git clone https://huggingface.co/Qwen/Qwen2.5-27B cd Qwen2.5-27B # 轉(zhuǎn)換FP16權(quán)重為GGUF格式 python convert_hf_to_gguf.py ./ --outfile qwen2.5-27b-fp16.gguf # 量化為Q3_K_S ../build/bin/llama-quantize qwen2.5-27b-fp16.gguf qwen2.5-27b-Q3_K_S.gguf Q3_K_S不想自己動手的話直接去HuggingFace找別人量化好的GGUF文件或者用Ollama現(xiàn)成的標(biāo)簽。省時間但要知道自己下的到底是什么檔位很多人OOM就是因為沒注意Ollama默認(rèn)拉的是Q4而Q4在12G上配合128K上下文就是塞不下。2.3 同一檔位也有坑K系列與S系列的區(qū)別Q3_K_S和Q3_K_M看起來都是3bit實際區(qū)別很大。K系列K-quants使用混合精度策略大部分權(quán)重用3bit但有一小部分關(guān)鍵張量保留到6bit甚至8bit。其中SSmall和MMiddle的區(qū)別在于保留高精度張量的范圍和數(shù)量。M更精細(xì)體積更大質(zhì)量也更好S更激進體積更小。在27B模型上Q3_K_M比Q3_K_S大了大概1.5G但對長上下文的語義保持有明顯幫助。如果只跑16K到32K上下文我會選Q3_K_M如果要硬上128K那只能Q3_K_S或者Q2_K。不存在完美的檔位只有當(dāng)下資源約束下的最優(yōu)解。3. 128K上下文的資源賬與實現(xiàn)方法3.1 KV cache到底吃了多少顯存很多人誤解KV cache的大小覺得它只跟模型大小有關(guān)其實它取決于三個因素層數(shù)、KV head數(shù)量、上下文長度。以Qwen2.5-27B為例32層、GQA配置下KV head是4個、head_dim為128。算一下每個token的KV占用每層每個token的KV字節(jié)數(shù) KV head數(shù) × 2K和V × head_dim × 字節(jié)數(shù)以BF16計算每個token每層是4 × 2 × 128 × 2 2048字節(jié)。32層就是64KB。128K上下文就是131072 × 65536字節(jié)約8.6GB。這個數(shù)字直接把BF16 KV cache判了死刑。解決方案是量化KV cache。llama.cpp支持把K和V分別量化為Q8_0或Q4_0等格式代價是KV cache的精度損失換來的是顯存減半甚至減到四分之一。使用Q8之后128K上下文的KV cache降到4.3GB用Q4進一步降到2.1GB。配合Q3_K_S權(quán)重總算能擠進12G。3.2 實操配置Flash Attention和上下文分段除了量化KV cache還有兩個關(guān)鍵開關(guān)。第一是Flash Attentionllama.cpp中用-fa啟用它能減少注意力計算時的臨時內(nèi)存分配并且在某些架構(gòu)下加速長上下文推理。第二是KV cache offload也就是--no-kv-offload參數(shù)把KV cache放到CPU內(nèi)存而不是顯存。這里有個經(jīng)驗判斷如果模型權(quán)重已經(jīng)把顯存占得只剩2G那KV cache放顯存還是放內(nèi)存要實測對比。放顯存decode更快但容易OOM放內(nèi)存會拖慢速度但能保住長上下文能力。優(yōu)先跑一個短測試觀察同樣prompt下兩種方案的decode速度和顯存占用再決定最終配置。我最終在Qwen2.5-27B上選了KV cache放內(nèi)存因為2G不到的剩余顯存連Q4的128K KV都裝不下。3.3 上下文長度分配策略128K上下文不是從頭到尾都用滿的。真實使用中你往往需要“開頭很長中間一段結(jié)尾很長”的對話歷史或文檔內(nèi)容。llama.cpp的上下文是預(yù)先分配的-c 131072一開KV cache就按128K預(yù)留。這意味著即便你只輸入20個tokenKV cache的顯存或內(nèi)存占用也已經(jīng)按128K頂格分配了。如果你沒有剛需不要一上來就開128K。我建議按照16K、32K、64K、128K四檔分別測試資源占用和速度選擇你能接受的上限。128K在這套配置里屬于“能做但代價大”的選項它讓KV cache占用了近一半的可用顯存直接擠壓了權(quán)重的量化空間。4. 實操過程從零配置到decode 504.1 環(huán)境準(zhǔn)備和編譯細(xì)節(jié)我用的是自己的機器AMD Ryzen 7 RTX 3060 12G 64G內(nèi)存系統(tǒng)是Ubuntu 24.04。llama.cpp從源碼編譯CUDA版本12.4。編譯命令如下git clone https://github.com/ggerganov/llama.cpp cd llama.cpp cmake -B build -DGGML_CUDAON -DCMAKE_CUDA_ARCHITECTURES86 cmake --build build --config Release -j 8GGML_CUDAON表示啟用CUDA后端CMAKE_CUDA_ARCHITECTURES86對應(yīng)RTX 30系顯卡的Ampere架構(gòu)compute capability 8.6。如果這一行不寫CMake會自動檢測但有時候會選錯架構(gòu)導(dǎo)致kernel編譯不兼容。編譯完成后build/bin目錄下會有l(wèi)lama-cli、llama-server、llama-bench等工具。4.2 啟動命令和參數(shù)解讀我用的是llama-server因為有OpenAI兼容API配合各種客戶端很方便。核心啟動命令如下./build/bin/llama-server \ -m models/qwen2.5-27b-Q3_K_S.gguf \ -ngl 99 \ -c 131072 \ --cache-type-k q4_0 \ --cache-type-v q4_0 \ --flash-attn \ --no-kv-offload \ -t 12 \ --n-cpu-moe 0逐條解讀一下-ngl 99表示把所有層都加載到GPU如果99這個值超過實際能容納的層數(shù)啟動時會報顯存不足然后你需要逐步降低。-c 131072設(shè)置上下文長度。--cache-type-k q4_0和--cache-type-v q4_0把KV cache量化到4bit這一步省了大概6.4G顯存。--flash-attn啟用Flash Attention。--no-kv-offload把KV cache放到系統(tǒng)內(nèi)存。-t 12設(shè)置CPU線程數(shù)因為KV cache在內(nèi)存CPU要承擔(dān)不少Attention計算。最后的--n-cpu-moe 0對稠密模型沒影響是給MoE模型用的。這套參數(shù)下模型權(quán)重占顯存約10.8GCUDA context和激活值占約1G湊合能進12G。啟動日志里最關(guān)鍵的幾行是model size、KV self size和CUDA buffer size這三行數(shù)字如果加起來接近12G就說明已經(jīng)很極限了。4.3 實測速度數(shù)據(jù)我用llama-bench做了一組相對標(biāo)準(zhǔn)的測試輸入長度512 token生成2048 token結(jié)果如下模型配置顯存占用首token延遲decode速度Q3_K_S 128K KV(Q4,內(nèi)存)~11.5G1.2s45-55 tok/sQ3_K_S 32K KV(Q4,顯存)~10.5G0.8s50-60 tok/sQ3_K_M 16K KV(Q8,顯存)~11.2G0.6s50-58 tok/sQ4_K_M 部分CPU offload~12G滿1.8s18-25 tok/s這里有個重要的數(shù)據(jù)點Q4_K_M那行理論上模型精度更高但因為部分層在CPU上跑decode速度直接跌到20左右體感非常拖沓。這驗證了前面的判斷12G顯存上全GPU的Q3遠(yuǎn)比半offload的Q4好用。這里監(jiān)控用量是一個很好的習(xí)慣nvidia-smi配合free -g可以實時看顯存和內(nèi)存壓力。4.4 decode速度為什么能超過50回到標(biāo)題里的“decode 50”。這個數(shù)字對于稠密27B模型來說理論上限在哪里decode是自回歸的每個token都要讀取一遍權(quán)重。RTX 3060的顯存帶寬是360GB/s如果每生成一個token要讀一遍Q3權(quán)重約12GB上限就是360除以12大約30 tok/s。也就是說稠密27B模型在12G顯存上物理上就不可能穩(wěn)定超過35。那50是怎么來的兩種可能第一上下文窗口較小KV cache留在顯存Attention計算沒有被打到內(nèi)存瓶頸decode速度可以沖到50-60這個我實測達(dá)到過第二模型本身是MoE結(jié)構(gòu)每次前向只激活一部分專家權(quán)重等效讀取權(quán)重大幅下降。MiniMax H3這類新模型如果走MoE路線激活參數(shù)可能只有總量的一半甚至三分之一decode速度就完全不是一個量級了。所以在查“為什么我的decode沒有50”之前先搞清楚自己的模型架構(gòu)和KV cache位置。這決定了你能達(dá)到的上限在哪里。5. 常見問題與排查技巧實錄5.1 啟動就OOM但理論上應(yīng)該能裝下我在調(diào)優(yōu)過程中遇到的第一個問題就是啟動報CUDA OOM。排查步驟很固定先看日志里的CUDA buffer size和model size再算KV cache size。如果三者和接近12G嘗試四件事把-c從131072降到65536把KV cache從Q4降到Q4并開--no-kv-offload把-ngl從99降到80換成更小的量化檔位。四件事按這個順序試通常第三四步就能解決。另外注意一個容易忽略的點llama.cpp的-c如果設(shè)得很大即使--no-kv-offload把KV cache放到了內(nèi)存Attention計算過程中的臨時張量仍然可能被分配在顯存里尤其是開Flash Attention之前。所以遇到OOM時優(yōu)先確認(rèn)Flash Attention是否真的開啟了日志里會有一行flash_attn 1。5.2 decode速度上不去的三個原因速度慢先看三個指標(biāo)GPU利用率、顯存帶寬、CPU占用。用nvidia-smi dmon能實時看到GPU利用率和顯存帶寬。如果GPU利用率在90%以上但速度還是慢那就是帶寬瓶頸無解只能降低量化檔位或啟用MoE如果GPU利用率只有50%大概率是CPU在拖后腿檢查-t是否設(shè)置合理以及是否有其他程序占CPU如果顯示內(nèi)存占用高檢查KV cache是否真的在內(nèi)存里有時候--no-kv-offload沒寫進參數(shù)文件白開了。我這里踩過一個很典型的坑-ngl 99但模型中有一部分被強制留在CPU日志看起來是全GPU其實是部分CPU。用llama-bench分別測-ngl 99和-ngl 90的輸出就能看出來差異如果差異不大說明99本就沒有全上。5.3 MiniMax H3在RTX 3060 12G上能不能跑這個話題最近問的人很多。我的回答是看架構(gòu)。如果MiniMax H3是MoE或混合注意力結(jié)構(gòu)那12G顯存只要能裝載稀疏激活的權(quán)重部分跑起來完全沒問題decode速度甚至可能比Qwen2.5-27B更好。去HuggingFace看模型的config.json是最快的辦法找num_experts和num_experts_per_tok這兩個字段如果存在恭喜這是能跑快的模型。跑的時候llama.cpp要用支持MoE的版本配合--n-cpu-moe參數(shù)把不常用的專家放在CPU內(nèi)存保活躍專家在GPU。監(jiān)控顯存和速度讓活躍專家數(shù)量逐步增加直到顯存接近滿載。這個過程的調(diào)優(yōu)思路和稠密模型完全不一樣稠密是“全塞GPU就行”MoE是“塞多少專家保多少速度”。5.4 跑推理時Chrome圖片解碼失敗這個現(xiàn)象我遇到過好幾次一開始以為是瀏覽器問題后來發(fā)現(xiàn)是高負(fù)載推理把系統(tǒng)資源和顯存占滿導(dǎo)致的連帶反應(yīng)。Chrome的GPU進程依賴顯存做圖片解碼當(dāng)顯存被模型占滿圖片解碼就會報image decode failed。解決方案不是修Chrome而是給推理讓路運行推理前關(guān)掉硬件加速相關(guān)的瀏覽器標(biāo)簽頁或者把瀏覽器設(shè)置里的“使用硬件加速”臨時關(guān)掉。如果推理和瀏覽器必須同時用考慮給模型加--no-kv-offload把KV cache全部丟到內(nèi)存降低顯存壓力瀏覽器解碼就能正常工作。這個問題的本質(zhì)是顯存分配策略沖突。推理框架的顯存占用是持續(xù)性的瀏覽器則是突發(fā)性的。把KV cache挪走是最有效的緩解手段代價是decode速度會有10%左右的下降但可以換來整個系統(tǒng)的穩(wěn)定。5.5 常見問題速查表問題現(xiàn)象大概率原因解決路徑啟動報CUDA OOM權(quán)重KV cache緩沖超顯存降量化檔位KV cache量化或offloaddecode只有20 tok/s部分層在CPU上確認(rèn)-ngl檢查日志是否全GPU上下文一長就變慢KV cache在內(nèi)存掉帶寬換更小KV量化或降上下文長度對話到一半報錯顯存碎片重啟server換--no-mmapChrome圖片解碼失敗顯存被推理占滿推理時關(guān)硬件加速或KV offload模型回答質(zhì)量下降量化檔位過低提高Q值降低上下文長度6. 一點個人經(jīng)驗和補充建議整套方案跑下來我最大的體會是在消費級顯卡上玩大模型本質(zhì)是資源規(guī)劃游戲不是模型評測游戲。決定成敗的不是模型的benchmark分?jǐn)?shù)而是你對顯存、內(nèi)存、帶寬三者的分配是否精確。每次調(diào)整都先算賬再動手不要憑感覺試參數(shù)。最后分享一個實用小技巧我在跑長上下文任務(wù)時會同時開兩個llama-server實例一個用128K上下文專門跑長文檔一個用16K上下文跑日常對話因為后面這個更輕量、更快、也更穩(wěn)。兩個實例分別監(jiān)聽不同端口配合OpenAI客戶端切換使用體驗很自然。這算是一種“用資源換體驗”的折中方案剛好繞開了單實例在128K下的速度代價。如果你也常用長上下文可以試一下這個思路。