人開發(fā)者LLM領(lǐng)域適配實(shí)戰(zhàn):從繼續(xù)預(yù)訓(xùn)練到RAG部署)
做這行的時(shí)間長(zhǎng)了會(huì)發(fā)現(xiàn)很多人一提到“預(yù)訓(xùn)練語言模型”就自動(dòng)把它和“幾千張顯卡、幾百億參數(shù)”綁定在一起覺得這跟個(gè)人開發(fā)者毫無關(guān)系。但實(shí)際情況完全不是這樣。開源生態(tài)成熟之后個(gè)人開發(fā)者完全有能力走通一條從預(yù)訓(xùn)練到領(lǐng)域適配的完整鏈路拿開源基座模型做繼續(xù)預(yù)訓(xùn)練灌入領(lǐng)域語料再通過指令微調(diào)做行為對(duì)齊最后接上RAG知識(shí)庫并部署成可用服務(wù)。這篇文章是我最近半年跑完這條鏈路的完整復(fù)盤不是路線圖是干完活之后回頭寫的總結(jié)會(huì)盡量多說些文檔里不寫的東西。這套流程適合三類人想把LLM真正用在自己業(yè)務(wù)里的開發(fā)者想搞懂預(yù)訓(xùn)練內(nèi)部機(jī)制但沒有大集群的學(xué)習(xí)者以及接了垂直領(lǐng)域項(xiàng)目、需要從通用模型做出專用模型的交付團(tuán)隊(duì)。整條鏈路涉及的核心關(guān)鍵詞就那么幾個(gè)LLM、預(yù)訓(xùn)練、領(lǐng)域適配但每一個(gè)詞展開后都是一大堆細(xì)節(jié)。下面按我實(shí)際執(zhí)行的順序來講。1. 先想清楚個(gè)人開發(fā)者到底需不需要“從零預(yù)訓(xùn)練”1.1 從Karpathy的llm wiki項(xiàng)目談起我是在GitHub上刷到karpathy的llm wiki項(xiàng)目時(shí)才真正把“個(gè)人開發(fā)者也能碰LLM訓(xùn)練”這件事想明白的。那個(gè)項(xiàng)目不是讓你去復(fù)現(xiàn)一個(gè)GPT級(jí)別的東西而是把LLM背后的每一個(gè)關(guān)鍵環(huán)節(jié)拆成可運(yùn)行、可實(shí)驗(yàn)的最小單元tokenizer怎么訓(xùn)練、embedding怎么初始化、訓(xùn)練穩(wěn)定性怎么保證、loss怎么解讀。它更像一本能跑的教科書。llm wiki項(xiàng)目給我最大的啟發(fā)不是代碼本身而是一句話預(yù)訓(xùn)練不是非黑即白的事你可以只訓(xùn)練一個(gè)十幾層的迷你模型也可以只做其中一個(gè)階段。對(duì)個(gè)人開發(fā)者來說最實(shí)用的理解方式是——預(yù)訓(xùn)練是一個(gè)“連續(xù)譜”從零訓(xùn)練、階段訓(xùn)練、繼續(xù)預(yù)訓(xùn)練、低成本微調(diào)都是這條譜系上的不同位置。你沒必要每次都從零開始但你必須知道每個(gè)位置發(fā)生了什么。1.2 三條路線怎么選實(shí)際操作中個(gè)人開發(fā)者面前有三條路對(duì)應(yīng)不同目標(biāo)和預(yù)算從零預(yù)訓(xùn)練自己造數(shù)據(jù)、自己定詞表、從頭初始化參數(shù)。這條路的數(shù)據(jù)處理量和調(diào)參成本極高通常要幾十億token起步的語料才能讓模型具備基本語言能力。個(gè)人如果抱著學(xué)習(xí)目的推薦用幾千萬token跑一個(gè)小模型做實(shí)驗(yàn)如果是為了交付項(xiàng)目強(qiáng)烈不建議。繼續(xù)預(yù)訓(xùn)練domain-adaptive pretraining在開源基座權(quán)重上用領(lǐng)域語料繼續(xù)訓(xùn)練。這是我在垂直項(xiàng)目里的首選因?yàn)橘Y源需求大幅下降還能讓模型補(bǔ)上領(lǐng)域知識(shí)。直接基座加SFT完全不碰預(yù)訓(xùn)練只做指令微調(diào)。最便宜見效最快但基礎(chǔ)知識(shí)的缺失會(huì)讓效果天花板很低。我的默認(rèn)選擇是第二條路。原因很樸素從零預(yù)訓(xùn)練的大部分成本根本不在顯卡上而在數(shù)據(jù)清洗和訓(xùn)練穩(wěn)定性調(diào)試上。對(duì)一個(gè)只有幾塊消費(fèi)級(jí)顯卡的個(gè)人開發(fā)者來說把時(shí)間花在“讓訓(xùn)練過程不崩潰”上遠(yuǎn)不如把開源基座當(dāng)成“已經(jīng)學(xué)會(huì)了人類語言的大腦”然后用領(lǐng)域語料給它做專業(yè)深造。有個(gè)現(xiàn)象值得注意現(xiàn)在很多人搜“yolo預(yù)訓(xùn)練模型下載”、“resnet預(yù)訓(xùn)練模型權(quán)重”下意識(shí)覺得計(jì)算機(jī)視覺的預(yù)訓(xùn)練模型才是自己夠得著的而LLM的預(yù)訓(xùn)練權(quán)重一樣可以從HuggingFace直接拉下來用本質(zhì)上沒有區(qū)別。預(yù)訓(xùn)練模型的意義在于它已經(jīng)掌握了語言和世界知識(shí)的基礎(chǔ)分布你要做的不是重新教它說話而是讓它懂你的領(lǐng)域。想通這一點(diǎn)個(gè)人開發(fā)者的重心自然就從“如何訓(xùn)練”轉(zhuǎn)移到“如何適配”。2. 數(shù)據(jù)整條流程里最該花時(shí)間的環(huán)節(jié)2.1 數(shù)據(jù)清洗與抽樣領(lǐng)域適配的成敗一半以上由數(shù)據(jù)決定。我從一開始就記住了這個(gè)教訓(xùn)與其花兩周調(diào)參不如花兩周處理數(shù)據(jù)。數(shù)據(jù)工作的優(yōu)先級(jí)遠(yuǎn)高于模型結(jié)構(gòu)、學(xué)習(xí)率這些看著更“技術(shù)”的東西。預(yù)訓(xùn)練和繼續(xù)預(yù)訓(xùn)練對(duì)數(shù)據(jù)質(zhì)量的要求極高臟數(shù)據(jù)帶來的損失往往要訓(xùn)練很久之后才暴露出來那時(shí)想回頭就晚了。我的清洗規(guī)則大致有四條按行去重按段落模糊去重防止同一知識(shí)在不同頁面里的重復(fù)文本反復(fù)訓(xùn)練過濾低質(zhì)量文本包括無標(biāo)點(diǎn)長(zhǎng)串、純亂碼、廣告導(dǎo)航類內(nèi)容以及明顯機(jī)器生成的重復(fù)文本按來源分層抽樣讓權(quán)威文獻(xiàn)、操作手冊(cè)、知識(shí)庫文章、問答記錄按比例混合避免單一來源失衡統(tǒng)一格式去掉多余空行、統(tǒng)一換行符、清理HTML標(biāo)簽殘留。這里有個(gè)細(xì)節(jié)容易忽略清洗環(huán)節(jié)的過濾規(guī)則一定要記錄成配置不要用完就丟。訓(xùn)練效果不好時(shí)90%的排查方向會(huì)指向數(shù)據(jù)如果連“數(shù)據(jù)是怎么來的”都說不清排查就沒法進(jìn)行。2.2 tokenizer詞表到底要不要擴(kuò)繼續(xù)預(yù)訓(xùn)練里最容易被忽略的是tokenizer問題。中文領(lǐng)域項(xiàng)目的痛點(diǎn)尤其典型通用模型的詞表大多是中英混合遇到醫(yī)學(xué)、法律、工程這些專業(yè)領(lǐng)域一個(gè)詞組會(huì)被切成一堆碎片不僅增加序列長(zhǎng)度還讓模型很難學(xué)到穩(wěn)定的“詞義”。比如“肝細(xì)胞癌”這個(gè)詞如果詞表里沒有它分詞器會(huì)切成“肝”加“細(xì)胞”加“癌”每個(gè)token單獨(dú)看都認(rèn)識(shí)但模型要花很大力氣才能把它們關(guān)聯(lián)成一個(gè)完整概念。領(lǐng)域語料里這類專業(yè)復(fù)合詞組一多訓(xùn)練效率就會(huì)明顯下降。解決辦法是擴(kuò)展詞表。在基座模型的tokenizer里新增一批領(lǐng)域詞元然后把新增embedding和lm_head矩陣做隨機(jī)初始化并拼到原有權(quán)重上。這里有個(gè)關(guān)鍵點(diǎn)不能只加詞元不調(diào)權(quán)重否則模型遇到新詞元會(huì)不知所措訓(xùn)練初期loss會(huì)異常飆升。擴(kuò)展之后需要用包含大量新詞元的語料做幾步warmup讓模型先適應(yīng)新embedding的分布。不過詞表擴(kuò)展不是越多越好。我試過一次性加了三萬個(gè)詞元結(jié)果基礎(chǔ)英文能力明顯下降因?yàn)樾略鲈~元稀釋了原詞表的概率分布。后來控制在兩千到五千個(gè)詞元效果最穩(wěn)。2.3 怎么把wiki知識(shí)庫變成訓(xùn)練語料Karpathy llm wiki里有個(gè)思路我特別認(rèn)同把高質(zhì)量、結(jié)構(gòu)化程度高的知識(shí)庫作為預(yù)訓(xùn)練語料的首選來源。實(shí)際項(xiàng)目里我處理過大量wiki類知識(shí)庫總結(jié)出一個(gè)重要差異——同是知識(shí)庫RAG用的源文檔和訓(xùn)練用的語料不是一回事。RAG的源文檔要求短、結(jié)構(gòu)化、檢索友好每個(gè)片段相對(duì)獨(dú)立。訓(xùn)練語料則要求完整、連貫、敘述性強(qiáng)因?yàn)槟P托枰ㄟ^長(zhǎng)文本學(xué)到知識(shí)之間的承接關(guān)系。把wiki上的碎片章節(jié)直接拼起來喂給模型效果通常不好。我的做法是把知識(shí)庫條目重寫成“陳述句段落”。以中藥處方審核方向?yàn)槔也粫?huì)直接把“藥品A與藥品B存在配伍禁忌”這樣的條目丟進(jìn)訓(xùn)練集而是改寫成“在處方審核中藥品A與藥品B聯(lián)合使用時(shí)存在明確的配伍禁忌風(fēng)險(xiǎn)臨床應(yīng)避免同時(shí)開具若確有聯(lián)用需求必須在審方系統(tǒng)中進(jìn)行風(fēng)險(xiǎn)提示。”改寫后模型學(xué)到的不僅是事實(shí)還有事實(shí)的使用語境。這個(gè)過程叫“語料敘述化”是領(lǐng)域適配里性價(jià)比極高的一步。3. 預(yù)訓(xùn)練實(shí)操框架選型與訓(xùn)練細(xì)節(jié)3.1 框架選型與顯存控制個(gè)人開發(fā)者做繼續(xù)預(yù)訓(xùn)練不需要一上來就用重型分布式框架。當(dāng)前可用選項(xiàng)大致有四類框架優(yōu)點(diǎn)缺點(diǎn)適合場(chǎng)景HuggingFace Trainer生態(tài)好、上手快、文檔全大規(guī)模效率一般個(gè)人項(xiàng)目、模型實(shí)驗(yàn)DeepSpeedZeRO分片成熟、顯存可控配置項(xiàng)多、調(diào)試有門檻單機(jī)多卡、中等規(guī)模Megatron-LM3D并行強(qiáng)、訓(xùn)練效率高學(xué)習(xí)成本大、模板重多機(jī)多卡大集群torch titan 類新框架簡(jiǎn)潔、擴(kuò)展性強(qiáng)生態(tài)還在積累愿意折騰的技術(shù)型選手我的選擇是HuggingFace Trainer加DeepSpeed理由特別實(shí)際出問題時(shí)最容易搜到答案。個(gè)人開發(fā)者最怕的不是性能低而是報(bào)錯(cuò)后找不到參考。顯存控制是必答題。繼續(xù)預(yù)訓(xùn)練通常吃顯存最多的部分是優(yōu)化器狀態(tài)光AdamW的fp32狀態(tài)就要占參數(shù)量的12倍空間。用ZeRO Stage 2把優(yōu)化器狀態(tài)分片到多卡再配合梯度累積幾張24GB的卡也能跑7B模型。3.2 關(guān)鍵參數(shù)怎么定分享一組我用下來比較穩(wěn)的起點(diǎn)參數(shù)基于8卡24GB顯存、7B基座模型的場(chǎng)景全局batch size128通過micro batch 4加梯度累積8實(shí)現(xiàn)序列長(zhǎng)度2048不貪長(zhǎng)夠用就行長(zhǎng)了顯存和數(shù)據(jù)處理成本都會(huì)翻倍學(xué)習(xí)率1e-5到2e-5明顯低于SFT階段的學(xué)習(xí)率繼續(xù)預(yù)訓(xùn)練本質(zhì)是“微調(diào)基礎(chǔ)上的微調(diào)”學(xué)率太大會(huì)破壞原有權(quán)重warmup比例3%讓學(xué)習(xí)率先緩后升再緩降。有個(gè)容易被忽略的點(diǎn)是學(xué)習(xí)率調(diào)度器在繼續(xù)預(yù)訓(xùn)練中的作用。我的經(jīng)驗(yàn)是先用500步warmup觀察loss有沒有“跳水式下降”如果沒有問題大概率出在數(shù)據(jù)而不是學(xué)習(xí)率上。訓(xùn)練過程里loss的下降應(yīng)該是平滑的如果曲線出現(xiàn)明顯鋸齒我會(huì)先檢查是不是數(shù)據(jù)批次混合不均而不是急著調(diào)學(xué)習(xí)率。3.3 訓(xùn)練監(jiān)控與loss解讀監(jiān)控是預(yù)訓(xùn)練里唯一能讓“不崩潰”變成“受控”的手段。除了常規(guī)的loss曲線我會(huì)額外記錄三樣?xùn)|西梯度范數(shù)、token級(jí)困惑度、學(xué)習(xí)率實(shí)時(shí)值。loss下降慢并不一定代表訓(xùn)練失敗。有一回我訓(xùn)練一個(gè)法律領(lǐng)域模型loss絕對(duì)值降得很慢但生成質(zhì)量的提升非常明顯。原因是loss值受高頻通用詞影響大領(lǐng)域術(shù)語本身的低頻屬性讓它在全局loss里的占比很小。所以不要只看loss數(shù)字要看生成案例的實(shí)際變化。訓(xùn)練中斷是很常見的事。建議開啟自動(dòng)保存并把checkpoint存在獨(dú)立磁盤分區(qū)上。我吃過一次虧中途磁盤寫滿整個(gè)checkpoint目錄損壞前三天白跑。從那以后我養(yǎng)成了“先把checkpoint同步到另一塊盤再繼續(xù)訓(xùn)練”的習(xí)慣。4. 領(lǐng)域適配繼續(xù)預(yù)訓(xùn)練、指令微調(diào)與偏好對(duì)齊4.1 繼續(xù)預(yù)訓(xùn)練和SFT的分工很多初學(xué)者會(huì)把“預(yù)訓(xùn)練”和“微調(diào)”攪在一起實(shí)際這兩件事解決的是完全不同的問題繼續(xù)預(yù)訓(xùn)練解決“模型知不知道你的領(lǐng)域知識(shí)”指令微調(diào)SFT解決“模型能不能按你的要求回答問題”偏好對(duì)齊DPO等解決“模型更愿意給什么樣的回答”。先后順序有講究。先做繼續(xù)預(yù)訓(xùn)練再做SFT最后做偏好對(duì)齊。如果反過來模型在SFT階段學(xué)到的指令遵循能力很容易被后續(xù)的預(yù)訓(xùn)練破壞掉。這個(gè)順序我實(shí)測(cè)過不可逆。4.2 SFT數(shù)據(jù)格式與label設(shè)計(jì)指令微調(diào)數(shù)據(jù)的基本格式是三段式instruction、input、output。我把它比喻成“教一個(gè)新人做事”說出要求給出背景然后給標(biāo)準(zhǔn)答案。SFT階段的主要工作不是收集海量數(shù)據(jù)而是設(shè)計(jì)高質(zhì)量label。我寫label時(shí)踩過不少坑總結(jié)下來這么幾條label必須只含最終回答不要出現(xiàn)“好的讓我來回答這個(gè)問題”這類寒暄話訓(xùn)練時(shí)模型會(huì)學(xué)這種廢話答案風(fēng)格要統(tǒng)一同一批數(shù)據(jù)里不要一會(huì)兒用正式報(bào)告風(fēng)格、一會(huì)兒用口語風(fēng)格模型學(xué)到的輸出風(fēng)格會(huì)混亂問題要貼近真實(shí)使用場(chǎng)景不要只寫“什么是XX”這種教科書式問題更要覆蓋“我手里的處方里有A和B要不要緊”這類真實(shí)業(yè)務(wù)提問復(fù)雜問題要帶限制條件比如“在不考慮患者過敏史的前提下”之類的約束否則模型會(huì)給一個(gè)籠統(tǒng)模糊的答案。做領(lǐng)域微調(diào)時(shí)我有一個(gè)技巧把之前敘述化的知識(shí)庫段落用prompt模板批量改寫成問答對(duì)。比如給定一段關(guān)于配伍禁忌的陳述要求生成對(duì)應(yīng)的“藥師問、系統(tǒng)答”數(shù)據(jù)。生成之后必須人工抽查清洗因?yàn)槟0迳傻臄?shù)據(jù)會(huì)有不少廢話式回答直接訓(xùn)練會(huì)污染模型風(fēng)格。4.3 DPO偏好對(duì)齊的實(shí)操經(jīng)驗(yàn)偏好對(duì)齊階段個(gè)人開發(fā)者首選DPO而不是傳統(tǒng)RLHF。理由很樸素RLHF需要單獨(dú)訓(xùn)練一個(gè)獎(jiǎng)勵(lì)模型成本高、穩(wěn)定性差DPO只需要構(gòu)造“好回答/壞回答”成對(duì)數(shù)據(jù)直接在原有SFT模型上微調(diào)即可。DPO的數(shù)據(jù)主體是偏好對(duì)。我自己的做法是讓模型針對(duì)同一問題生成多個(gè)回答然后人工標(biāo)注排序選出最好和最差各一個(gè)。標(biāo)注標(biāo)準(zhǔn)要具體比如“是否直接給出結(jié)論”、“是否包含必要風(fēng)險(xiǎn)提示”、“是否在不確定時(shí)明確說不確定”。DPO訓(xùn)練里有兩個(gè)容易出錯(cuò)的地方。一個(gè)是beta參數(shù)控制偏好對(duì)齊的強(qiáng)度我一般從0.1開始效果不理想再微調(diào)。另一個(gè)是參考模型一定要凍結(jié)如果把參考模型也訓(xùn)練了DPO的損失函數(shù)就沒有了參照基準(zhǔn)。我在早期犯過這個(gè)錯(cuò)誤結(jié)果訓(xùn)練出來的模型輸出變得極端且不可控。5. 知識(shí)庫落地的關(guān)鍵一步RAG與GraphRAG5.1 為什么微調(diào)之后還要RAG領(lǐng)域適配完成后模型對(duì)領(lǐng)域知識(shí)的理解能力會(huì)上一個(gè)臺(tái)階但你很快會(huì)發(fā)現(xiàn)另一個(gè)問題模型里的知識(shí)是凍結(jié)的。知識(shí)庫今天更新了一條審方規(guī)則模型不會(huì)自動(dòng)知道。重新訓(xùn)練一次的成本又太高。這正是RAG存在的意義。預(yù)訓(xùn)練或繼續(xù)預(yù)訓(xùn)練的本質(zhì)是“把知識(shí)壓縮進(jìn)參數(shù)”RAG的本質(zhì)是“把知識(shí)放在模型外面需要時(shí)檢索出來送進(jìn)上下文”。兩者互補(bǔ)。我在實(shí)際項(xiàng)目里的分工是高頻、穩(wěn)定、需要深度推理的知識(shí)放進(jìn)模型參數(shù)低頻、頻繁更新、按條件檢索的知識(shí)放進(jìn)RAG。比如配伍禁忌這類相對(duì)穩(wěn)定的規(guī)則靠預(yù)訓(xùn)練掌握而藥品說明書里的廠商信息、庫存狀態(tài)這類高頻變化的信息靠RAG解決。5.2 本地wiki知識(shí)庫的構(gòu)建流程構(gòu)建wiki知識(shí)庫的流程我梳理成了五步解析wiki dump或爬取頁面把正文抽取出來去掉模板、目錄、導(dǎo)航等噪音做文本切分按語義段落切而不是按固定字?jǐn)?shù)切向量化常用方案是bge或text-embedding類模型把段落轉(zhuǎn)成向量建索引我用過faiss和milvus數(shù)據(jù)量不大時(shí)faiss足夠接檢索把用戶問題和知識(shí)片段同時(shí)向量化做相似度檢索后拼進(jìn)prompt。切分這里要特意講一下。固定256字切分是我用過最省事但效果最差的辦法經(jīng)常把一個(gè)完整概念切斷。我現(xiàn)在傾向于先按markdown標(biāo)題或段落語義切再把過短的片段合并過長(zhǎng)的二次切分。切分的粒度直接影響回答的準(zhǔn)確程度值得花時(shí)間調(diào)。我在一個(gè)本地ERP產(chǎn)品檢索項(xiàng)目里也驗(yàn)證過這條路。把產(chǎn)品手冊(cè)、歷史工單、售前問答整理成知識(shí)庫再通過RAG給客服系統(tǒng)做檢索增強(qiáng)完全沒有重新訓(xùn)練模型只靠prompt模板就做到了“回答帶出處”的效果。對(duì)大多數(shù)業(yè)務(wù)場(chǎng)景來說這已經(jīng)是最劃算的落地方式。5.3 GraphRAG和本體RAG到底好在哪傳統(tǒng)RAG的短板是“只認(rèn)相似不認(rèn)關(guān)系”。向量相似度能幫你找到包含“布洛芬”的段落但很難回答“哪些藥和布洛芬存在相互作用”這種涉及多跳關(guān)系的問題。GraphRAG的思路就是先抽取出文本里的實(shí)體和關(guān)系構(gòu)建成圖再檢索。GraphRAG的落地成本不低。先要把知識(shí)庫里的實(shí)體關(guān)系抽取出來這本身就是一次小型的LLM工程然后存儲(chǔ)圖結(jié)構(gòu)以支持子圖檢索最后把檢索到的路徑拼進(jìn)context。但它對(duì)多跳問題的提升確實(shí)明顯。我在審方知識(shí)庫上試過傳統(tǒng)RAG能回答“A和B能否同服”GraphRAG則能回答“A代謝受影響后與依賴該代謝酶的C藥同服是否有風(fēng)險(xiǎn)”這種問題靠純向量是答不出來的。本體RAG是進(jìn)一步的進(jìn)階方案它用領(lǐng)域本體一套預(yù)先定義好的概念、實(shí)體、關(guān)系框架來約束圖構(gòu)建和檢索過程。不用本體時(shí)GraphRAG抽出來的關(guān)系可能是“A與B相關(guān)”這種泛化關(guān)系用本體約束后關(guān)系會(huì)被規(guī)范成“抑制”、“誘導(dǎo)”、“配伍禁忌”這樣明確的類型檢索結(jié)果的質(zhì)量差別很大。這里的經(jīng)驗(yàn)是本體設(shè)計(jì)要簡(jiǎn)單實(shí)用先覆蓋核心關(guān)系別貪全。貪全的下場(chǎng)是本體維護(hù)工作量爆炸而實(shí)際推理用不上那么多關(guān)系。6. 部署上線ONNX、推理框架與量化6.1 ONNX導(dǎo)出LLM模型的幾個(gè)坑很多個(gè)人開發(fā)者習(xí)慣用PyTorch直接跑模型但交付項(xiàng)目時(shí)ONNX是繞不開的。ONNX的推理性能、跨平臺(tái)能力、與邊緣設(shè)備適配性都好于直接跑PyTorch。我自己在導(dǎo)出時(shí)踩過三個(gè)坑第一個(gè)坑是動(dòng)態(tài)軸沒配置。LLM的輸入長(zhǎng)度是可變的導(dǎo)出時(shí)必須把序列長(zhǎng)度維度標(biāo)記為動(dòng)態(tài)軸否則推理時(shí)換個(gè)長(zhǎng)度就報(bào)錯(cuò)。第二個(gè)坑是注意力掩碼的拼接錯(cuò)誤。生成式推理需要維護(hù)一個(gè)不斷增長(zhǎng)的past key values如果在導(dǎo)出時(shí)就把這部分邏輯寫死后續(xù)擴(kuò)展性會(huì)很差。第三個(gè)坑是精度問題。ONNX默認(rèn)用fp32導(dǎo)出模型文件和推理速度都不理想實(shí)際部署至少要轉(zhuǎn)成fp16。導(dǎo)出完成后一定要先在本地用真實(shí)請(qǐng)求測(cè)一遍確認(rèn)輸出與PyTorch原版一致。我遇到過ONNX導(dǎo)出后個(gè)別token和原模型對(duì)不上的情況排查了很久才發(fā)現(xiàn)是把a(bǔ)ttention mask當(dāng)普通tensor一起量化導(dǎo)致的。6.2 推理框架選型模型部署方式我試過三種純ONNX Runtime、vLLM、llama.cpp。各有各的適用場(chǎng)景。方案特點(diǎn)推薦場(chǎng)景ONNX Runtime跨平臺(tái)、可嵌入桌面/移動(dòng)端單機(jī)或邊緣設(shè)備部署vLLM連續(xù)批處理吞吐高線上API服務(wù)llama.cpp量化支持好CPU可跑本地個(gè)人使用、低配機(jī)器如果服務(wù)是給多人用的API我首選vLLM。它的continuous batching能把多個(gè)請(qǐng)求打包進(jìn)一個(gè)batch吞吐量比逐請(qǐng)求推理高很多。如果只是自己在本地用llama.cpp加GGUF量化是最省心的組合一張16GB顯卡就能流暢跑7B模型。項(xiàng)目交付給客戶做內(nèi)網(wǎng)部署時(shí)我一般選ONNX Runtime因?yàn)樗灰蕾嘝ython環(huán)境打包成一個(gè)服務(wù)就行。6.3 量化與顯存的配合量化是在顯存和效果之間做權(quán)衡。我常用的幾檔配置是fp16效果無損一個(gè)7B模型大概需要14GB到16GB顯存int8效果損失極小顯存降到7GB到9GBint4如GGUF Q4_K_M效果會(huì)有可見損失但顯存只需4GB到6GB。量化對(duì)知識(shí)型任務(wù)的影響相對(duì)較小對(duì)邏輯推理和數(shù)學(xué)任務(wù)影響更明顯。我在一個(gè)需要計(jì)算劑量的審方場(chǎng)景里對(duì)比過int4模型在簡(jiǎn)單計(jì)算上的錯(cuò)誤率明顯高于fp16。所以我的原則是除非顯存實(shí)在不夠否則至少保留int8關(guān)鍵場(chǎng)景絕不壓到int4。部署階段還有個(gè)常被忽略的問題請(qǐng)求日志。無論是ONNX服務(wù)還是vLLM服務(wù)我都會(huì)記錄完整的請(qǐng)求和響應(yīng)log。有一次客戶反饋“模型回答不靠譜”排查下來不是模型問題而是上游調(diào)用方把參數(shù)傳錯(cuò)了。如果沒有日志這種問題要查很久。7. 常見問題與排查技巧實(shí)錄7.1 loss不降或震蕩訓(xùn)練中最常遇到的三個(gè)現(xiàn)象是loss不降、loss下降后反彈、loss劇烈震蕩。我的排查順序是先看數(shù)據(jù)數(shù)據(jù)是否重復(fù)、語料是否過短、上下文是否完整再看學(xué)習(xí)率繼續(xù)預(yù)訓(xùn)練階段如果學(xué)習(xí)率大于5e-5很容易把原有權(quán)重沖亂接著看梯度如果梯度范數(shù)異常大加梯度裁剪我一般設(shè)在1.0最后看tokenizer如果大量輸入被切成單字說明詞表擴(kuò)展沒做好。loss不降有一半以上是數(shù)據(jù)問題。不要一上來就懷疑模型結(jié)構(gòu)或框架先拿一段干凈語料在基座模型上做小規(guī)模對(duì)比實(shí)驗(yàn)如果基座在小語料上也降不下去那問題就在數(shù)據(jù)預(yù)處理。7.2 領(lǐng)域能力上來通用能力卻崩了這是繼續(xù)預(yù)訓(xùn)練最容易出的副作用。模型訓(xùn)練了一個(gè)星期領(lǐng)域問題回答得頭頭是道但寫一段通用文案反而退步了。原因是訓(xùn)練語料過于單一把模型的通用能力“覆蓋”掉了。解決辦法是混合通用語料。我實(shí)踐下來比較好用的比例是領(lǐng)域語料與通用語料約1比3到1比5通用語料可以來自開源的中文語料庫或清洗后的通用網(wǎng)頁文本。這樣既確保了領(lǐng)域知識(shí)的占比也不至于把基座模型的通用能力沖垮。7.3 provider rejected the request schema這類部署報(bào)錯(cuò)部署時(shí)我遇到過很多次“provider rejected the request schema or tool payload”之類的報(bào)錯(cuò)。這類問題的根源通常是你用的網(wǎng)關(guān)或代理框架對(duì)工具調(diào)用的schema有嚴(yán)格校驗(yàn)而模型生成的工具參數(shù)沒有嚴(yán)格符合JSON Schema格式于是請(qǐng)求在到達(dá)模型前就被攔下了。排查思路是三步。第一步確認(rèn)工具調(diào)用的schema定義和模型輸出格式是否一致重點(diǎn)檢查必填字段名大小寫第二步給模型打開JSON mode或約束解碼讓輸出嚴(yán)格按schema生成第三步如果你是靠SFT來教模型調(diào)用工具需要在SFT數(shù)據(jù)里加入大量工具調(diào)用格式樣例讓模型學(xué)到“什么時(shí)候調(diào)用什么工具、參數(shù)長(zhǎng)什么樣”。單純改prompt治標(biāo)不治本。還有一個(gè)隱藏原因網(wǎng)關(guān)層對(duì)tool payload字段有類型校驗(yàn)比如某個(gè)字段預(yù)期是string模型輸出卻是array被拒就是必然的。這類問題多用在線JSON Schema校驗(yàn)工具驗(yàn)證一遍再改模型側(cè)會(huì)比較高效。7.4 一份個(gè)人項(xiàng)目的排錯(cuò)速查表我把踩過的坑整理成一張速查表后續(xù)項(xiàng)目直接照著排查現(xiàn)象優(yōu)先排查項(xiàng)常用解法訓(xùn)練loss不降數(shù)據(jù)質(zhì)量、學(xué)習(xí)率、梯度范數(shù)干凈小語料對(duì)比實(shí)驗(yàn)降低學(xué)習(xí)率任務(wù)效果差但loss正常數(shù)據(jù)分布單一增加領(lǐng)域語料多樣性通用能力退化語料中通用語料占比低通用語料比例提到3至5倍尾部token生成異常tokenizer詞表未適配領(lǐng)域詞擴(kuò)展并warmup新詞元部署報(bào)schema rejected輸出格式與工具schema不一致開啟JSON模式、約束解碼多輪對(duì)話記憶差上下文長(zhǎng)度不足擴(kuò)大序列長(zhǎng)度上限并調(diào)整訓(xùn)練策略量化后計(jì)算錯(cuò)誤int4精度損失改int8或?qū)υ搱?chǎng)景單獨(dú)做校驗(yàn)以上所有排查項(xiàng)都對(duì)應(yīng)著一句話不要假設(shè)最壞的情況從最簡(jiǎn)單、最可控的環(huán)節(jié)開始切。訓(xùn)練和部署這類長(zhǎng)鏈路任務(wù)問題往往不在最顯眼的位置而是藏在某個(gè)被默認(rèn)忽略的配置里。跑完這套流程我個(gè)人最大的體會(huì)是預(yù)訓(xùn)練和領(lǐng)域適配之間的邊界沒有想象中那么大。個(gè)人開發(fā)者的優(yōu)勢(shì)在于靈活不需要像大團(tuán)隊(duì)那樣維護(hù)復(fù)雜的基建可以快速試錯(cuò)、快速調(diào)整。如果條件有限可以從“最貴的步驟”反推——把預(yù)算和精力優(yōu)先砸在數(shù)據(jù)上訓(xùn)練框架追求穩(wěn)定夠用即可部署方案從最小可行版本開始效果驗(yàn)證之后再往上加成本。這套路線我現(xiàn)階段跑得很順后續(xù)如果再深入我會(huì)優(yōu)先補(bǔ)兩件事一是把數(shù)據(jù)清洗和檢測(cè)流程進(jìn)一步自動(dòng)化二是給RAG加上更細(xì)粒度的知識(shí)版本管理。先分享到這兒等有新坑再回來填。