據(jù)清洗到RAG:AI工程從零實(shí)戰(zhàn)學(xué)習(xí)路徑)
別人問我在做什么我說在做AI工程他們會先入為主地覺得我天天在訓(xùn)練模型、調(diào)參數(shù)。做了一整年之后我想說真正的AI工程大多數(shù)時間其實(shí)都在跟數(shù)據(jù)、接口、異常處理較勁模型反而是最好搞定的一環(huán)。這篇內(nèi)容就是從我自己的經(jīng)歷出發(fā)聊聊一條“from scratch”的成長路徑以及一個完全沒有AI基礎(chǔ)的人到底該怎么一步步把AI工程這件事跑通。這里的“from scratch”不是說非要自己從零手寫神經(jīng)網(wǎng)絡(luò)的每一層而是指不依賴現(xiàn)成的“保姆級平臺”——真正把數(shù)據(jù)、模型、訓(xùn)練、部署、調(diào)優(yōu)全鏈路理解一遍親手打通一套最小可用的系統(tǒng)。很多新人上來就想挑戰(zhàn)大規(guī)模語言模型微調(diào)或者多模態(tài)系統(tǒng)結(jié)果被環(huán)境配置、數(shù)據(jù)清洗、顯存溢出三座大山直接勸退。我的建議是換一條路走先把小模型的小任務(wù)做熟再一步步擴(kuò)展到LLM應(yīng)用。這篇文章適合幾類人剛?cè)腴T想做AI應(yīng)用開發(fā)的學(xué)生工作中需要把算法落地成服務(wù)的工程師以及想轉(zhuǎn)型做AI產(chǎn)品但不知道從哪下手的朋友。我會盡量把每一步的“為什么”也講清楚而不只是給一段可以抄的代碼。1. 從“調(diào)包俠”到AI工程一條反常識的學(xué)習(xí)路徑1.1 為什么說“從零開始”不等于“從算法開始”多數(shù)人學(xué)習(xí)AI的第一反應(yīng)是找一本深度學(xué)習(xí)教材從頭啃反向傳播、注意力機(jī)制啃完再去看PyTorch文檔。這條路不是不對而是它對一個目標(biāo)是“做工程”的人來說效率實(shí)在太低了。工程視角的“從零開始”應(yīng)該是你想讓一個系統(tǒng)完成什么任務(wù)把這個任務(wù)拆成數(shù)據(jù)、模型、服務(wù)、評估四塊然后逐塊打通。算法細(xì)節(jié)可以在用到的時候再補(bǔ)而不是在最開始就把自己淹沒在數(shù)學(xué)公式里。我見過太多人卡在“我覺得我還沒學(xué)好理論不敢動手寫代碼”這個心態(tài)上。實(shí)際上AI工程是典型的“先跑通再優(yōu)化”的領(lǐng)域你第一次寫的訓(xùn)練循環(huán)哪怕很丑、哪怕只在玩具數(shù)據(jù)集上能跑它給你的正反饋也比啃三章教材強(qiáng)得多。先建立完整的工程閉環(huán)感再回去補(bǔ)理論你會突然發(fā)現(xiàn)很多公式都有了落地的意義。1.2 以終為始先看清一條生產(chǎn)級流水線長什么樣如果不清楚終點(diǎn)長什么樣很容易在中間走彎路。一條哪怕是初級的AI工程流水線通常都包含下面這些環(huán)節(jié)數(shù)據(jù)獲取與清洗原始數(shù)據(jù)永遠(yuǎn)臟到你難以想象格式不統(tǒng)一、缺字段、標(biāo)簽錯誤、編碼亂這些才是日常。特征工程與預(yù)處理文本要分詞、過濾停用詞圖片要縮放、歸一化表格數(shù)據(jù)要處理缺失值。這塊往往比模型本身更影響最終效果。模型選擇與基線先有一個最簡單的模型跑通流程拿到一個不漂亮的基線分?jǐn)?shù)再去換更復(fù)雜的模型。訓(xùn)練與驗(yàn)證訓(xùn)練循環(huán)、驗(yàn)證集、早停、超參調(diào)整保證模型不僅能跑還能穩(wěn)定收斂。部署與服務(wù)化把模型封裝成接口處理并發(fā)、限流、日志、異常。監(jiān)控與迭代上線之后觀察真實(shí)數(shù)據(jù)分布漂移定期評估持續(xù)迭代。一個人做全流程可能聽起來很嚇人但關(guān)鍵是先把這個閉環(huán)跑起來哪怕每個環(huán)節(jié)都簡陋一點(diǎn)。我自己的第一個項(xiàng)目只用了一千條數(shù)據(jù)、一個非常小的文本分類模型但完整經(jīng)歷了清洗、訓(xùn)練到部署的過程之后再看任何AI系統(tǒng)腦子里都會自動浮現(xiàn)出它處在流水線的哪一環(huán)。1.3 我現(xiàn)在推薦的學(xué)習(xí)順序如果讓我重新走一遍我會這樣安排學(xué)習(xí)路徑保證每個階段都有看得見的產(chǎn)出Python基礎(chǔ)與數(shù)據(jù)處理不用等到精通能熟練用pandas、numpy處理表格和文本就夠了一周時間足夠。機(jī)器學(xué)習(xí)最小閉環(huán)用scikit-learn跑一個分類/回歸任務(wù)理解訓(xùn)練集、驗(yàn)證集、準(zhǔn)確率這些核心概念。深度學(xué)習(xí)框架入門選PyTorch也可以選JAX但生態(tài)還是PyTorch最順跑通一個圖像或文本的小模型訓(xùn)練循環(huán)。部署最小實(shí)踐用FastAPI封裝模型在本地跑通接口調(diào)用理解前后端是怎么連起來的。再回頭補(bǔ)理論這時候再讀關(guān)于損失函數(shù)、反向傳播、Transformer架構(gòu)的資料你會覺得每一段都似曾相識且恍然大悟。最后再上LLM與RAG有前面完整的工程直覺再接觸Prompt設(shè)計(jì)、向量檢索、大模型API就不會覺得它們是玄學(xué)。這套順序的最大特點(diǎn)是每兩周都能有成果而不是學(xué)了三個月還在“打基礎(chǔ)”。信心這個東西在工程入門階段比智商重要多了。2. 最小可行項(xiàng)目用文本分類跑通第一條訓(xùn)練循環(huán)2.1 項(xiàng)目設(shè)定為什么選文本分類當(dāng)?shù)谝粋€項(xiàng)目文本分類幾乎是AI工程入門的最佳項(xiàng)目形態(tài)。它不需要處理圖像那樣復(fù)雜的數(shù)據(jù)增強(qiáng)也不需要像生成模型那樣擔(dān)心輸出的多樣性任務(wù)定義清晰評估指標(biāo)直觀而且數(shù)據(jù)集很容易通過公開渠道獲取。我第一次完成的項(xiàng)目是“客服工單自動分類”把客服收到的用戶反饋分成“賬單問題”“網(wǎng)絡(luò)故障”“設(shè)備維修”“其他”四類。數(shù)據(jù)集不過幾百條Excel表格都是人工標(biāo)注過的歷史工單。這個項(xiàng)目看似簡單但它強(qiáng)行讓我把整條流水線的每個環(huán)節(jié)都親手做了一遍從CSV里各種奇怪的換行符到模型上線后被人投訴分類不準(zhǔn)每一步都是真實(shí)世界的毒打。為了讀者復(fù)現(xiàn)方便我建議你找一個公開的數(shù)據(jù)集比如新聞分類、情感分析都可以。重要的是數(shù)據(jù)量不要太大幾千條就夠因?yàn)槟愕哪繕?biāo)不是刷精度是打通流程。2.2 數(shù)據(jù)準(zhǔn)備先別碰模型至少花一半時間在數(shù)據(jù)上多數(shù)新手最容易犯的錯誤是急著把數(shù)據(jù)丟給模型結(jié)果發(fā)現(xiàn)訓(xùn)練出來的東西根本不能用然后又不知道為什么。我可以負(fù)責(zé)任地說AI工程里70%的坑都出在數(shù)據(jù)處理階段而不是模型階段。以我當(dāng)時的工單數(shù)據(jù)為例里面的真實(shí)情況包括同一類問題有七八種不同的描述方式、有的字段里混入了HTML標(biāo)簽、有些標(biāo)簽明顯標(biāo)錯了、還有大量空值和重復(fù)記錄。我想在真實(shí)工單數(shù)據(jù)集上訓(xùn)練出一個能用的模型第一步就是做數(shù)據(jù)探索。import pandas as pd df pd.read_csv(tickets.csv) print(df.head()) print(df.info()) print(df[category].value_counts()) # 清洗基本問題去空白、去重、統(tǒng)一文本大小寫 df[text] df[text].astype(str).str.strip() df[text] df[text].replace(r\s, , regexTrue) df df.dropna(subset[category]) df df.drop_duplicates(subset[text])清洗完之后我還干了件很關(guān)鍵的事把類別不均衡的情況列出來看看。“網(wǎng)絡(luò)故障”的樣本可能是“設(shè)備維修”的五倍如果不處理模型學(xué)到的就是“永遠(yuǎn)猜網(wǎng)絡(luò)故障”。最簡單的處理方式是過采樣少數(shù)類或者給損失函數(shù)加類別權(quán)重后者的實(shí)現(xiàn)成本更低。我在處理時用了class_weightbalanced這個參數(shù)讓少數(shù)類樣本在計(jì)算損失時獲得更大的權(quán)重這個操作直接讓“設(shè)備維修”這一類的F1分?jǐn)?shù)提高了十幾個百分點(diǎn)。建議你也在自己的項(xiàng)目里試一下你會很明顯地看到分類報(bào)告里各類指標(biāo)的變化。2.3 訓(xùn)練循環(huán)的骨架與其復(fù)制粘貼不如手打一遍在第一次跑通訓(xùn)練循環(huán)時我強(qiáng)烈建議你不要直接復(fù)制現(xiàn)成的完整代碼而是自己動手敲一遍邏輯。原因很簡單訓(xùn)練循環(huán)里的每一步都對應(yīng)著概念手敲一遍之后你才能從“背代碼”進(jìn)步到“理解它在干什么”。一個標(biāo)準(zhǔn)的PyTorch訓(xùn)練循環(huán)骨架是這樣的import torch import torch.nn as nn from torch.utils.data import DataLoader, Dataset class TicketDataset(Dataset): def __init__(self, texts, labels): self.texts texts self.labels labels def __len__(self): return len(self.texts) def __getitem__(self, idx): return self.texts[idx], self.labels[idx] # 模型不復(fù)雜Embedding LSTM 全連接 class SimpleClassifier(nn.Module): def __init__(self, vocab_size, emb_dim, num_classes): super().__init__() self.embedding nn.Embedding(vocab_size, emb_dim) self.lstm nn.LSTM(emb_dim, hidden_size64, batch_firstTrue) self.fc nn.Linear(64, num_classes) def forward(self, x): embedded self.embedding(x) output, (hidden, cell) self.lstm(embedded) last_hidden hidden[-1] return self.fc(last_hidden) # 訓(xùn)練 model SimpleClassifier(vocab_sizevocab_size, emb_dim64, num_classes4) optimizer torch.optim.Adam(model.parameters(), lr1e-3) loss_fn nn.CrossEntropyLoss() for epoch in range(10): total_loss 0 for batch_texts, batch_labels in DataLoader(train_dataset, batch_size32, shuffleTrue): optimizer.zero_grad() logits model(batch_texts) loss loss_fn(logits, batch_labels) loss.backward() optimizer.step() total_loss loss.item() print(fEpoch {epoch1}, loss: {total_loss})這個循環(huán)有四個關(guān)鍵動作缺一環(huán)都不行zero_grad()清掉上一輪的梯度forward前向傳播拿到輸出backward()計(jì)算梯度step()用梯度更新參數(shù)。新手最容易忘掉zero_grad()后果就是梯度在批次之間不斷累加loss曲線飄忽不定。在這個項(xiàng)目里我用的詞表是自己基于訓(xùn)練數(shù)據(jù)構(gòu)建的。簡單做法是給每個出現(xiàn)過的詞編一個索引加一個UNK給沒見過的詞。值得注意的一點(diǎn)是驗(yàn)證集里如果出現(xiàn)詞表外的詞就映射到UNK不要跳過樣本否則你的dataloader會在評估時報(bào)錯。2.4 驗(yàn)證與過擬合準(zhǔn)確率不是唯一的答案訓(xùn)練跑通之后我的第一個模型在訓(xùn)練集上準(zhǔn)確率高達(dá)98%當(dāng)時還挺興奮結(jié)果放到驗(yàn)證集上一看只有72%。這就是典型的過擬合模型把訓(xùn)練樣本背下來了而不是學(xué)到了模式。應(yīng)對過擬合的思路按見效速度排增加訓(xùn)練數(shù)據(jù)哪怕做一個簡單的數(shù)據(jù)增強(qiáng)。減小模型容量比如把LSTM的hidden_size從64降到32。加Dropout或L2正則化。用早停監(jiān)控驗(yàn)證集loss連續(xù)若干輪不下降就停。我當(dāng)時加了nn.Dropout(0.3)并采用早停驗(yàn)證集準(zhǔn)確率穩(wěn)定在了85%左右。這個數(shù)字并不驚艷但對一個小模型來說已經(jīng)是能上線服務(wù)的水平了。還有一個新手容易忽略的點(diǎn)只盯著準(zhǔn)確率會被坑。因?yàn)闃颖静痪鈺r模型全部預(yù)測多數(shù)類也能拿到很高的準(zhǔn)確率。我當(dāng)時就是看了分類報(bào)告才發(fā)現(xiàn)“設(shè)備維修”的召回率是0。正確的做法是看每個類別的精確率、召回率和F1分?jǐn)?shù)對自己模型的效果有一個更精確的把握。3. RAG工作流從玩具到可用系統(tǒng)的關(guān)鍵轉(zhuǎn)折3.1 什么時候該從“訓(xùn)練小模型”升級到“LLM應(yīng)用”當(dāng)你已經(jīng)能在自己的小模型上完成訓(xùn)練、評估和部署的閉環(huán)再面對“我要做一個客服問答機(jī)器人”這類需求你很快會發(fā)現(xiàn)傳統(tǒng)小模型的天花板——它能分類但沒法生成內(nèi)容、沒法理解沒見過的問法更沒法回答需要外部知識的問題。這時候就該接觸到LLM。很多人以為用LLM就是從OpenAI或開源模型調(diào)用API然后把Prompt一寫就結(jié)束了。實(shí)際上把一個LLM從“聊天玩具”變成“可用的業(yè)務(wù)系統(tǒng)”關(guān)鍵要看兩點(diǎn)私有知識怎么接入以及輸出質(zhì)量怎么保持穩(wěn)定。這就很自然地引出了RAG檢索增強(qiáng)生成。3.2 RAG的基本流程檢索拼接生成RAG的思路一句話講清楚不要指望大模型記住所有知識而是讓它在回答之前先從知識庫里檢索到相關(guān)文本然后把這些文本連同用戶問題一起喂給模型生成答案。它的好處是知識庫可以隨時更新不需要重新訓(xùn)練模型模型的回答有依據(jù)不容易隨意編造而且你可以追溯它用的哪一篇資料便于處理“胡說八道”的爭議。我在實(shí)踐中搭RAG用的流程非常簡單把文檔比如產(chǎn)品的FAQ、操作手冊切成片段一般按段落或固定長度切片。用Embedding模型把每個片段轉(zhuǎn)成一個向量存進(jìn)向量數(shù)據(jù)庫。用戶提問時把問題也轉(zhuǎn)成向量在庫里做相似度檢索取Top 5相關(guān)片段。把這些片段和用戶問題拼成一個Prompt交給LLM生成答案。用代碼實(shí)現(xiàn)的話核心部分大概長這樣from sentence_transformers import SentenceTransformer import numpy as np # 加載Embedding模型這里選的是小巧易用的開源模型 encoder SentenceTransformer(BAAI/bge-small-zh-v1.5) # 文檔切片與向量化 chunks split_documents(documents, chunk_size300) chunk_vectors encoder.encode(chunks) # 檢索把問題和所有片段向量的余弦相似度算一遍取最高分的幾個 def retrieve(query, top_k5): query_vec encoder.encode([query])[0] scores cosine_similarity(query_vec, chunk_vectors) top_indices np.argsort(scores)[::-1][:top_k] return [chunks[i] for i in top_indices]如果是當(dāng)作演示直接用numpy算相似度就夠用。但真的要上到幾十萬條文檔、還要做并發(fā)檢索的級別就該換用專門的向量數(shù)據(jù)庫在工程上更靠譜不然檢索性能會成為瓶頸。3.3 檢索質(zhì)量比模型大小更重要一個常被忽視的真相在RAG系統(tǒng)里很多人把注意力全放在“用哪個生成模型”上結(jié)果忽略了一個更關(guān)鍵的問題檢索結(jié)果到底準(zhǔn)不準(zhǔn)。如果檢索回來的5個片段里有3個是無關(guān)內(nèi)容再強(qiáng)的生成模型也只能被帶到溝里去。我自己驗(yàn)證過一個很典型的案例同一個客服知識庫換掉檢索策略之后回答的正確率提升了將近30%。提升點(diǎn)主要來自三處切片策略把固定長度切片改成按語義段落切片避免一句話被切成兩半。檢索數(shù)量原來只取Top 3改為Top 5并讓模型在Prompt里注明“如果下文沒有相關(guān)信息請直接說不清楚”。查詢改寫把一次檢索改成針對用戶問題關(guān)鍵詞的兩次檢索合并結(jié)果去重。這里的關(guān)鍵認(rèn)知是RAG系統(tǒng)的質(zhì)量天花板主要由檢索環(huán)節(jié)決定而不是由生成模型決定。模型再聰明檢索的內(nèi)容不對它也只能巧婦難為無米之炊。在這個階段你還需要建立“評估”的閉環(huán)。我當(dāng)時的做法是維護(hù)一個固化的問答測試集每次改完檢索策略都跑一遍這個測試集統(tǒng)計(jì)答案中“包含正確知識片段”的比例寧可慢一點(diǎn)也要保證每一次改動帶來的都是正向效果。4. 部署與推理優(yōu)化模型能用和好用是兩碼事4.1 模型包裝成服務(wù)FastAPI是最順手的解法模型訓(xùn)練好只是萬里長征第一步在生產(chǎn)環(huán)境里別人只能通過接口來使用它。無論前面做得多么出色沒有穩(wěn)定的服務(wù)一切都等于零。所以要把“模型能用”變成“模型好用”部署服務(wù)這一環(huán)我強(qiáng)烈推薦用FastAPI異步、輕量又省事而且自帶接口文檔。from fastapi import FastAPI from pydantic import BaseModel app FastAPI() model load_model() class Query(BaseModel): text: str class Response(BaseModel): label: str score: float app.post(/predict) def predict(query: Query): label, score model.predict(query.text) return Response(labellabel, scorescore)如果只是在本地跑一跑你可能體會不到服務(wù)化的意義。但一旦要對接前端、給別的同事調(diào)用、讓腳本批量處理數(shù)據(jù)這個接口就是整個系統(tǒng)的門面。我當(dāng)時第一次部署完用curl試了一下接口能返回正確結(jié)果那種感覺和“模型在Jupyter Notebook里跑出準(zhǔn)確率”是完全不同的——前者意味著真的被使用了。部署環(huán)節(jié)有四個最容易出問題的點(diǎn)提前處理能省下大量時間模型預(yù)熱服務(wù)啟動后先跑一次推理避免第一個真實(shí)請求因?yàn)槟P蛻屑虞d而超時。輸入校驗(yàn)用Pydantic做好類型與字段校驗(yàn)別讓臟數(shù)據(jù)跑到模型內(nèi)部才報(bào)錯。異常隔離把模型推理包在try/except里返回友好的錯誤信息而不是直接把堆棧拋給調(diào)用方。版本管理模型文件和數(shù)據(jù)預(yù)處理方式一起記錄版本否則有一天改了數(shù)據(jù)預(yù)處理邏輯線上模型效果突然變了你都不知道為什么。4.2 量化、批處理與并發(fā)把成本打下來的實(shí)踐調(diào)好接口之后你可能很快發(fā)現(xiàn)模型推理有點(diǎn)慢尤其是當(dāng)模型體積稍大、請求量一上來一次推理幾百毫秒就會成為瓶頸。優(yōu)化推理性能的常見手段主要包括模型量化把浮點(diǎn)權(quán)重從FP32壓到FP16甚至INT8速度能提升兩三倍效果損失很多時候在可接受范圍內(nèi)。批處理把多個請求合并成一個批次推理能用滿GPU并行能力。這在文本分類這類小模型上尤其明顯批量推理吞吐量能提升一個量級。緩存對同樣的輸入直接返回緩存結(jié)果適合重復(fù)提問比例高的場景。我在文本分類那個項(xiàng)目里試用過最簡單的PyTorch量化代碼很短import torch model torch.quantization.quantize_dynamic( model, {torch.nn.Linear}, dtypetorch.qint8 )模型體積縮小近四倍推理延遲從13ms下降到5ms準(zhǔn)確率幾乎沒有變化。這個優(yōu)化對一套小服務(wù)來說收益非常明顯。有一點(diǎn)要提醒量化不是、也不應(yīng)該是無腦開啟的。如果是復(fù)雜的生成模型量化后可能出現(xiàn)輸出質(zhì)量下降尤其是代碼生成、數(shù)學(xué)推理這類任務(wù)非常明顯。所以量化前后一定要保留評估集用數(shù)字對比說話而不是感覺“差不多就行”。4.3 上線后的監(jiān)控與回歸測試沒有評估就沒有迭代模型一旦上線事情并不會就此結(jié)束。真實(shí)用戶的數(shù)據(jù)分布和你的訓(xùn)練數(shù)據(jù)很可能不一樣用戶提問的方式、術(shù)語、長度都可能漂移。所以從上線第一天起就要記錄兩樣?xùn)|西輸入日志和預(yù)測日志。我當(dāng)時在日志里發(fā)現(xiàn)了一個特別有意思的現(xiàn)象很多用戶會發(fā)超長文本而模型在訓(xùn)練時基本沒見過超過200字的樣本。于是把所有超過最大長度的輸入做了截?cái)嗵幚砟P烷_始逐步輸出合理結(jié)果。這種問題如果不在線上持續(xù)觀察靠離線評估是根本發(fā)現(xiàn)不了的。另外一個工程上的好習(xí)慣是準(zhǔn)備一套回歸測試集。每次更新模型或者調(diào)整預(yù)處理邏輯都先跑一遍回歸測試集確保老的正常功能沒被自己改壞。我第一次更新模型時就沒跑回歸測試改完亂整了熱詞表結(jié)果之前好好的中性評論被分到負(fù)面類別還被產(chǎn)品同事發(fā)現(xiàn)了。這碗毒打嘗過一次就夠了之后我再也沒跳過回歸測試。5. 半年踩坑實(shí)錄數(shù)據(jù)泄漏、顯存溢出與不穩(wěn)定的Prompt5.1 數(shù)據(jù)泄漏準(zhǔn)確率虛高還會被當(dāng)成榮耀數(shù)據(jù)泄漏是AI工程里最隱蔽也最致命的錯誤之一。它的本質(zhì)是模型在訓(xùn)練時“偷看”了本該在測試時才能見到的信息導(dǎo)致訓(xùn)練時看起來很強(qiáng)真實(shí)場景里卻一碰就碎。我在做文本分類時遇到過兩個例子。第一個是把文本做TF-IDF向量化的時候?qū)φ麄€數(shù)據(jù)集先fit再拆分訓(xùn)練集和測試集實(shí)際上測試集的信息已經(jīng)滲進(jìn)了訓(xùn)練過程模型表現(xiàn)虛高得離譜。正確的做法是先拆分?jǐn)?shù)據(jù)再單獨(dú)對訓(xùn)練集fit、對測試集transform。第二個例子是數(shù)據(jù)去重時把同一用戶的多條工單一起處理結(jié)果同屬一個用戶的樣本同時出現(xiàn)在訓(xùn)練和測試集合里模型記住的是用戶特征而不是內(nèi)容特征。如果你發(fā)現(xiàn)模型在訓(xùn)練集上表現(xiàn)極好、驗(yàn)證集上也不錯但一上線就崩建議第一時間排查數(shù)據(jù)預(yù)處理過程里有沒有“全局統(tǒng)計(jì)量”被提前計(jì)算了。5.2 顯存溢出從無腦換大卡到優(yōu)雅地用盡每一字節(jié)早年在沒有顯存概念的時候我拿顯卡跑模型動不動就“CUDA out of memory”。我的第一反應(yīng)是提高顯卡配置但后來發(fā)現(xiàn)問題沒那么簡單顯存溢出其實(shí)是有很多優(yōu)雅應(yīng)對方式的。主要方案有幾個減小batch size、序列截?cái)?、梯度累積、使用混合精度訓(xùn)練。很多人不知道“梯度累積”是怎么解決的思路其實(shí)原理是如果batch size32會爆顯存那就先用batch size8每4步才更新一次參數(shù)效果在數(shù)學(xué)上和一次看32個樣本基本等價。accumulation_steps 4 for i, batch in enumerate(DataLoader(ds, batch_size8)): loss model(batch) loss loss / accumulation_steps # 歸一化 loss.backward() if (i 1) % accumulation_steps 0: optimizer.step() optimizer.zero_grad()混合精度訓(xùn)練則是用torch.cuda.amp把前向計(jì)算的一部分放到FP16顯存占用能降低不少同時保留FP32的精度用于梯度計(jì)算。這套組合技對付顯存不足的問題比直接換顯卡更務(wù)實(shí)。5.3 LLM輸出不穩(wěn)定Prompt工程要有工程化的紀(jì)律大模型輸出的隨機(jī)性是個繞不開的問題。同樣一個問題加熱度參數(shù)調(diào)成0.8它可能給出三種不同風(fēng)格的回答這讓“穩(wěn)定輸出”成了工程上必須解決的事情。我的基本做法是把生成參數(shù)固定下來包括temperature、top_p、max_tokens等不讓它們隨業(yè)務(wù)狀態(tài)隨意改動。另一個更關(guān)鍵的問題是讓模型輸出結(jié)構(gòu)化內(nèi)容。需要模型返回JSON時我會在Prompt里要求它只輸出JSON并在代碼里加入解析失敗時重試一次或降級返回默認(rèn)值的邏輯。import json, re def safe_json_parse(raw_output): # 有些模型會輸出Markdown代碼塊包裹的JSON需要剝掉 cleaned re.sub(rjson|, , raw_output).strip() try: return json.loads(cleaned) except json.JSONDecodeError: return {error: parse_failed, raw: raw_output}Prompt的調(diào)試也要有工程化的方式。我自己維護(hù)了一個測試Prompt的清單每次修改系統(tǒng)提示詞都會用同一組問題跑一遍對比輸出質(zhì)量而不是憑感覺看著“這次回答好像更好”。只有把Prompt當(dāng)成代碼一樣去管理、去測試大模型應(yīng)用才算真正進(jìn)入了“工程”的范疇。在整個學(xué)習(xí)路徑里最容易讓人放棄的時刻不是第一次見到復(fù)雜公式也不是環(huán)境配置反復(fù)報(bào)錯而是同時面對數(shù)據(jù)臟、顯存爆、模型效果差、接口沒人用的多重打擊。我的經(jīng)驗(yàn)是每次只拆解一個小問題解決之后再往前推進(jìn)一小步半年之后回頭看你已經(jīng)走完了最艱難的那一截。把第一個小項(xiàng)目完整跑通比收藏一百份學(xué)習(xí)資料都有用。