ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

课题组私有AI落地:RAG、LoRA微调与vLLM部署实战

课题组私有AI落地:RAG、LoRA微调与vLLM部署实战 1. 课题组私有AI的落地路径拆解1.1 为什么通用大模型在课题组场景里总差一口气课题组做研究跟企业做产品有个本质区别数据量小、领域窄、但精度要求极高。你拿一个通用大模型去问它“这个材料的XRD衍射峰在2θ32.5°对应什么晶相”它大概率给你一段听起来很专业但完全不对的回答。原因不复杂——通用模型的训练语料里你这个细分方向的内容可能只占百万分之一模型根本没有足够信号去学到这层知识。我试过直接拿某国产开源模型去回答组里的专业问题十次里有六次在编。后来想明白了通用模型是“通才”它知道很多领域的常识但对你的课题组的“私有知识”——那些发在组内论文、实验记录本、师兄师姐毕业论文里的东西——它一无所知。这就是为什么必须走“领域微调知识库构建本地化部署”这条路。私有AI的核心价值在于三点第一把课题组多年积累的论文、实验记录、会议纪要变成可检索、可问答的知识资产第二让模型学会你们组的“行话”和推理范式第三数据不出本地实验数据的安全性有保障。适合谁来参考研究生、博后、课题组的技术负责人以及任何需要在小规模数据上构建专属AI能力的人。1.2 整体技术路线的选型逻辑整条路线可以拆成四层模型层、数据层、训练层、部署层。每一层都有坑我一个个说。模型层选型我建议优先考虑国产开源模型。Qwen系列Qwen2.5-7B/14B、ChatGLM系列、Baichuan系列、InternLM系列都是成熟选项。为什么优先国产一是中文语料占比高对中文学术文本的理解天然更好二是社区活跃遇到问题容易找到解决方案三是本地化部署的文档和工具链更完善。7B级别适合单卡24G显存做QLoRA微调14B级别需要双卡或A100 40G起步。数据层是整条路线里最容易被低估的环节。很多人一上来就想着调参结果数据没整理好训出来的模型还不如原版。领域语料构建包括论文PDF解析、实验记录结构化、问答对生成、数据清洗去重。这一步的工作量通常占总项目的60%以上。训练层有两条路RAG和微调。这两者不是二选一而是互补。RAG解决“知识检索”问题微调解决“行为对齐”问题。我的经验是先搭RAG把知识库跑通再根据RAG的bad case决定要不要微调。如果RAG已经能回答80%的问题微调的优先级可以降低如果模型总是答非所问、格式不对那就需要LoRA/QLoRA来教它“怎么说话”。部署层选vLLM做推理引擎配合GPTQ/AWQ量化压缩能在有限显存下实现高并发。量化后的7B模型在单张24G卡上可以跑到每秒几十个token的输出速度同时支撑十几个并发请求对课题组内部使用完全够用。2. 领域语料构建与知识库搭建实操2.1 论文与实验记录的清洗流水线课题组的数据来源通常很杂PDF论文、Word实验报告、Excel数据表、手写记录拍照、组会PPT。第一步是把这些异构数据统一成纯文本。PDF解析我踩过最大的坑是公式和表格。PyPDF2对学术论文的公式支持很差经常把公式拆成乱码。后来换成PyMuPDFfitz 针对性的正则清洗效果好很多。表格用pdfplumber单独提取保留结构信息。扫描版PDF必须走OCRPaddleOCR对中文学术文档的识别率明显优于Tesseract尤其是包含公式和上下标的场景。import fitz # PyMuPDF import re def extract_pdf_text(pdf_path): doc fitz.open(pdf_path) full_text [] for page in doc: text page.get_text(text) # 清理页眉页脚通常是重复出现的短行 lines text.split(\n) cleaned [l for l in lines if len(l.strip()) 5] full_text.append(\n.join(cleaned)) doc.close() return \n.join(full_text) def clean_academic_text(text): # 去除引用标记 [1] [2,3] text re.sub(r\[\d(,\d)*\], , text) # 合并被换行打断的句子 text re.sub(r(\w)\n(\w), r\1\2, text) # 去除多余空白 text re.sub(r\s, , text) return text.strip()清洗完之后要做去重。学术论文里方法部分高度相似如果不去重RAG检索时会返回一堆几乎一样的片段浪费上下文窗口。我用SimHash做近似去重阈值设在0.85左右既能去掉重复又不误杀。注意清洗阶段一定要保留原文的章节结构信息标题、小节号后面做chunk切分时这些结构信息能显著提升检索质量。2.2 文本切分策略固定长度还是语义切分文本切分是RAG的“隐形杀手”。切得不好检索出来的片段要么缺上下文要么包含太多无关信息。固定长度切分比如512 token一段重叠50 token是最简单的方案适合快速验证。但它的问题很明显可能把一个完整的论证切成两半检索时只命中一半模型拿到的是残缺信息。语义切分按段落、按章节切保持语义完整性。我的做法是先按标题层级切分成大块如果某块超过800 token再按句子边界切分。句子边界用中文标点。和换行符共同判断。def semantic_chunk(text, max_tokens800, overlap_sentences1): # 按章节标题切分 sections re.split(r\n(?#{1,3}\s|\d\.\d*\s), text) chunks [] for sec in sections: if len(sec) max_tokens: chunks.append(sec) else: # 按句子切分 sentences re.split(r(?[。]), sec) current for sent in sentences: if len(current) len(sent) max_tokens: chunks.append(current) # 保留上一句作为重叠 current sentences[max(0, sentences.index(sent)-overlap_sentences)] sent else: current sent if current: chunks.append(current) return chunks实际用下来语义切分的检索命中率比固定长度切分高15%到20%。代价是切分逻辑更复杂需要针对不同文档类型调参。2.3 向量化与检索embedding模型怎么选知识库的检索质量一半取决于切分一半取决于embedding模型。中文场景下BGE系列BAAI/bge-large-zh-v1.5是目前开源里综合表现最好的。M3E系列也可以但在长文本上略逊一筹。向量数据库选型数据量在百万级以下Chroma或FAISS完全够用。Chroma的优势是自带持久化和元数据过滤FAISS的优势是快。课题组场景我推荐Chroma因为元数据过滤很实用——你可以按“论文年份”“作者”“章节类型”过滤检索范围。from chromadb import Client from chromadb.utils import embedding_functions client Client() ef embedding_functions.SentenceTransformerEmbeddingFunction( model_nameBAAI/bge-large-zh-v1.5 ) collection client.create_collection( namelab_knowledge, embedding_functionef, metadata{hnsw:space: cosine} ) # 批量写入 collection.add( documentschunks, metadatas[{source: f, year: y, section: s} for f, y, s in meta_list], ids[fid_{i} for i in range(len(chunks))] )检索时用混合检索向量相似度 BM25关键词匹配加权融合。纯向量检索对专有名词比如某个材料牌号不敏感BM25能补上这个短板。实测混合检索的hit rate比纯向量高10%以上。3. LoRA与QLoRA微调实战3.1 什么时候该微调什么时候不该先泼一盆冷水不是所有场景都需要微调。如果你的需求是“让模型能回答课题组论文里的问题”RAG就够了。微调真正解决的问题是模型输出格式不对、推理风格不符合组内习惯、对特定术语的理解有偏差。我判断的标准很简单拿100条测试问题跑一遍RAG如果准确率超过75%且错误主要是“知识缺失”而非“格式错误”那就继续优化RAG。如果模型总是答非所问、输出格式混乱、或者对某些概念的理解系统性偏离那就上微调。微调的数据量要求LoRA微调通常需要500到2000条高质量问答对。少于500条效果不稳定多于5000条边际收益递减。数据质量比数量重要得多——100条精准的问答对效果可能好过1000条粗糙的。3.2 LoRA微调的关键参数与实操LoRA的核心思想是在原模型权重旁边挂一个小矩阵只训练这个小矩阵。这样显存占用大幅降低7B模型用LoRA微调只需要16G左右显存。关键参数参数推荐值说明lora_rank8-32秩越大容量越强但过拟合风险增加lora_alpha16-64通常设为rank的2倍lora_dropout0.05-0.1防止过拟合learning_rate1e-4到3e-4LoRA可以用比全量微调更大的学习率epochs3-5多了容易过拟合batch_size4-8根据显存调整配合梯度累积from peft import LoraConfig, get_peft_model from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments model AutoModelForCausalLM.from_pretrained( Qwen/Qwen2.5-7B-Instruct, torch_dtypeauto, device_mapauto ) tokenizer AutoTokenizer.from_pretrained(Qwen/Qwen2.5-7B-Instruct) lora_config LoraConfig( r16, lora_alpha32, target_modules[q_proj, k_proj, v_proj, o_proj], lora_dropout0.05, biasnone, task_typeCAUSAL_LM ) model get_peft_model(model, lora_config) training_args TrainingArguments( output_dir./lora_output, per_device_train_batch_size4, gradient_accumulation_steps4, learning_rate2e-4, num_train_epochs3, logging_steps10, save_strategyepoch, fp16True )target_modules的选择很关键。Qwen系列通常选q_proj、k_proj、v_proj、o_proj这四个注意力层的投影矩阵。如果效果不够可以加上gate_proj和up_proj。但加得越多显存占用越大训练越慢。3.3 QLoRA单卡24G跑14B模型的秘诀QLoRA在LoRA基础上加了两层优化4-bit量化基座模型 分页优化器。效果是显存占用进一步降低14B模型在单张24G卡上也能微调。from transformers import BitsAndBytesConfig import torch bnb_config BitsAndBytesConfig( load_in_4bitTrue, bnb_4bit_quant_typenf4, bnb_4bit_compute_dtypetorch.bfloat16, bnb_4bit_use_double_quantTrue ) model AutoModelForCausalLM.from_pretrained( Qwen/Qwen2.5-14B-Instruct, quantization_configbnb_config, device_mapauto )nf4量化类型是QLoRA论文推荐的对正态分布权重最友好。double_quant是二次量化进一步压缩量化常数省显存但不影响精度。实操心得QLoRA训练速度比LoRA慢30%到50%因为每次前向传播都要反量化。如果显存够用优先用LoRA显存紧张再上QLoRA。3.4 知识蒸馏让小模型学会大模型的本事知识蒸馏在课题组场景里有个特殊用法用大模型比如Qwen2.5-72B生成高质量的问答对然后拿这些数据去微调小模型7B。这样小模型能学到接近大模型的回答质量但推理成本低得多。具体做法准备500到1000个领域问题用大模型生成回答人工筛选后作为训练数据。温度设高一点0.7到0.9让大模型输出更多样的回答增加数据多样性。蒸馏数据的质量取决于两个因素大模型的能力和筛选标准。我通常会让大模型对每个问题生成3个回答然后人工选最好的那个或者用另一个模型做自动评分。4. 量化压缩与vLLM高并发部署4.1 GPTQ与AWQ量化怎么选量化是把模型权重从FP16压缩到INT4或INT8减少显存占用和推理延迟。GPTQ和AWQ是目前最主流的两种4-bit量化方案。GPTQ是逐层量化用校准数据集来最小化量化误差。优点是成熟、工具链完善缺点是量化过程较慢对校准数据敏感。AWQ是激活感知的量化核心思路是保护对激活值影响大的权重通道。优点是量化后精度通常比GPTQ高一点尤其是小模型缺点是工具链相对新一些。对比项GPTQAWQ量化速度较慢较快精度保持好略好工具链成熟度高中vLLM支持完善完善推荐场景通用小模型/高精度要求我的经验7B模型用AWQ14B以上用GPTQ差别不大。量化后的模型在vLLM上跑显存占用约为FP16的1/3推理速度提升2到3倍。# 用AutoAWQ量化 python -m awq.entry --model_path Qwen/Qwen2.5-7B-Instruct \ --w_bit 4 --q_group_size 128 \ --save_dir ./qwen2.5-7b-awq4.2 vLLM部署与并发调优vLLM的核心优势是PagedAttention把KV Cache分页管理显存利用率大幅提升并发能力比HuggingFace原生推理高一个数量级。python -m vllm.entrypoints.openai.api_server \ --model ./qwen2.5-7b-awq \ --quantization awq \ --dtype auto \ --max-model-len 8192 \ --gpu-memory-utilization 0.9 \ --max-num-seqs 16 \ --port 8000关键参数说明gpu-memory-utilization设0.9留10%给系统max-num-seqs控制并发序列数7B模型在24G卡上设16比较稳max-model-len根据实际需求设设太大浪费显存。注意vLLM启动时会预分配显存如果OOM先降max-model-len再降max-num-seqs。不要一上来就把gpu-memory-utilization设到0.95系统不稳定。4.3 RAG与微调模型的联合部署最终架构是vLLM跑微调后的模型Chroma做知识库检索中间加一层路由逻辑。import requests def rag_query(question, collection, top_k5): # 检索 results collection.query(query_texts[question], n_resultstop_k) context \n.join(results[documents][0]) # 构造prompt prompt f基于以下参考资料回答问题。如果资料中没有相关信息请明确说明。 参考资料 {context} 问题{question} 回答 # 调用vLLM response requests.post(http://localhost:8000/v1/completions, json{ model: ./qwen2.5-7b-awq, prompt: prompt, max_tokens: 1024, temperature: 0.3 }) return response.json()[choices][0][text]路由逻辑简单的事实性问题走RAG复杂的推理问题走微调模型直接回答混合问题先RAG检索再让微调模型整合。5. 常见问题与排查技巧实录5.1 RAG检索命中率低的排查思路这是被问得最多的问题。排查顺序先看切分再看embedding最后看检索策略。切分问题chunk太大检索到的片段包含太多无关信息chunk太小上下文不完整。用“问题-答案”对做测试看检索到的片段是否包含答案。如果不包含调整切分参数。embedding问题中文场景用BGE-large-zh不要用英文模型。如果领域术语多考虑在领域语料上微调embedding模型或者用BGE的instruction版本。检索策略问题纯向量检索对专有名词不敏感加BM25混合检索。如果还不行加rerank模型BGE-reranker做二次排序。症状可能原因解决方案检索不到相关片段切分太碎/embedding不匹配调整chunk size换中文embedding检索到无关片段切分太大/检索策略单一减小chunk加BM25混合检索答案不完整top_k太小增大top_k加rerank专有名词检索失败向量模型对术语不敏感加BM25或微调embedding5.2 微调后模型“变傻”了怎么办这是过拟合的典型表现。模型在训练集上表现很好但泛化能力下降甚至通用能力也退化了。解决方案降低lora_rank从32降到8或16增加lora_dropout从0.05升到0.1减少epochs从5降到3增加训练数据多样性。如果还不行在训练数据里混入10%到20%的通用指令数据防止模型“忘记”通用能力。另一个常见问题是灾难性遗忘。LoRA本身对原模型影响较小但如果target_modules选得太多比如把所有线性层都加上遗忘风险会增加。我的建议是只加注意力层的q_proj、k_proj、v_proj、o_proj不要动FFN层。5.3 量化后精度下降明显怎么调AWQ量化对校准数据敏感。如果量化后精度下降超过3%换一组更有代表性的校准数据。校准数据应该覆盖模型的典型使用场景不要只用通用语料。GPTQ量化可以调group_size默认128降到64或32能提升精度但显存占用增加。per-channel量化比per-tensor精度高但推理速度略慢。实操心得量化前先跑一遍FP16的baseline记录准确率。量化后再跑一遍对比下降幅度。下降在2%以内可以接受超过5%就要重新量化。5.4 vLLM部署的显存与并发调优vLLM的显存管理很智能但配置不当也会OOM。排查顺序先看max-model-len再看max-num-seqs最后看gpu-memory-utilization。max-model-len设太大是常见错误。如果实际输入输出不超过4096 token就不要设8192。每增加1024 token的max-model-lenKV Cache显存占用增加约10%。并发调优max-num-seqs不是越大越好。设太大每个序列分到的显存少可能触发抢占反而降低吞吐。7B模型在24G卡上max-num-seqs设16到24比较合适。用vLLM的metrics接口监控吞吐和延迟找到最优值。# 监控vLLM指标 curl http://localhost:8000/metrics | grep vllm关注vllm:num_requests_running和vllm:gpu_cache_usage_perc。如果cache usage持续超过90%说明显存紧张需要降低并发或max-model-len。5.5 知识库更新与版本管理课题组的论文在不断增加知识库需要定期更新。我的做法是每月跑一次增量更新新论文解析后追加到Chroma同时用SimHash去重。旧版本的知识库保留快照方便回溯。embedding模型如果换了整个知识库需要重新向量化。所以embedding模型的选择要慎重一旦确定就不要轻易换。如果必须换预留一天时间做全量重嵌入。微调模型的版本管理同样重要。每次微调后保存LoRA权重和对应的训练数据版本号方便复现和对比。我习惯用日期数据版本命名比如“lora_20250115_v2_data500”。6. 一些踩坑后的个人体会整条路线跑下来最大的体会是数据质量决定上限工程细节决定下限。我见过太多人花大量时间调参结果训练数据里有一半是重复的、格式混乱的。先把数据洗干净比什么都重要。另一个体会是不要追求一步到位。先搭一个最小可用的RAG跑通“解析-切分-检索-生成”全流程再逐步优化。微调可以放到最后等RAG的bad case积累够了再针对性地做微调。最后分享一个小技巧在RAG的prompt里加一句“如果参考资料中没有相关信息请明确说明”能显著降低幻觉率。模型在不知道答案时倾向于编造这句话给了它一个“安全出口”。这个方向后续还可以扩展的地方很多多模态知识库图片、表格的检索、Agent化的知识问答自动调用工具查数据、持续学习新论文自动增量微调。但那是下一步的事了先把当前这套跑稳。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进