简介围绕开源大模型从环境配置到落地应用的全链路实践合集面向具备一定Python基础的AI算法工程师、应用开发者和学习者聚焦环境搭建、私有化部署、LoRA微调及LangChain集成四大核心场景。资源共79个文件压缩包约23.88MB其中包含45个Python脚本、12个Jupyter Notebook、9个JSON配置文件以及模型权重、Markdown说明等覆盖DeepSeek、Qwen、Yi、Baichuan、ChatGLM、miniCPM等主流开源模型的下载、训练、推理与接口封装代码。除核心脚本外还提供向量数据库示例、嵌入式模型配置、依赖需求清单及一个轻量语音样例文件可帮助读者快速跑通本地大模型环境。已有858人学习浏览适合用来复现微调实验、搭建私有化服务或借鉴LangChain结合业务的应用思路。整体目录结构清晰按模型与任务划分模块从数据准备、LoRA训练、模型合并到API服务均有对应参考实现能够缩短上手周期减少环境与兼容性踩坑具有较强的工程参考价值。1. 先别急着下数据集这条链路才是开源大模型项目的完整骨架开源大模型从拿到手到能产生业务价值中间隔着环境配置、私有化部署、lora微调、langchain应用四道工序。这个标题把这四件事用一个 zip 包串起来说明真正值得关注的不只是某一个环节而是整个闭环怎么跑通。很多项目死在第一步——conda 环境冲突、CUDA 版本不匹配、transformers 加载不出模型更多项目倒在最后一公里——模型部署好了却不知道怎么用 langchain 把它变成能回答问题的 Agent。这篇文章按实际推进顺序拆解这条链路先在裸机上把 Python、CUDA、PyTorch 环境配好再拉模型做私有化部署接着用 LoRA 低成本微调让它懂你的业务最后用 langchain 把这套模型接入应用。适合想在自己的机器或内网服务器上完整跑通开源大模型项目的工程师也适合刚接触这一整套流程、需要一份可复现路线图的人。2. 环境配置conda 隔离、CUDA 匹配、PyTorch 验证半小时搭出可用环境2.1 为什么环境配置是第一个真正的坑开源大模型项目回传的报错绝大多数发生在模型加载之前。undefined symbol、CUDA error: no kernel image、GLIBCXX not found这些问题看着像代码问题实际全是环境问题。深度学习框架对 CUDA 工具包版本、cuDNN 版本、Python 版本、GCC 版本都有隐性要求混装很容易把整个系统搞乱。常见做法是用 conda 给每个项目建独立环境把系统级 Python 和项目依赖隔离开。这样做的理由很直接不同项目可能锁定不同版本的 PyTorch 和 transformers一个环境一套版本互相完全不影响。即使本机已经装了 Python 3.10也不要直接pip install torch因为系统 Python 可能被其他服务依赖贸然升级或装包会污染全局环境。2.2 最小可复现的环境安装命令安装 Miniconda 时我一般会装到用户目录下避免写/usr/bin需要的权限问题。以下是完整命令序列# 安装 Miniconda 到用户目录 wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh bash Miniconda3-latest-Linux-x86_64.sh -b -p $HOME/miniconda3 echo export PATH$HOME/miniconda3/bin:$PATH ~/.bashrc source ~/.bashrc # 创建独立 Python 环境Python 版本定为 3.10 conda create -n llm python3.10 -y conda activate llm # 安装 PyTorch按 CUDA 版本选对应命令 # CUDA 11.8 用 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # CUDA 12.1 用 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121这里有几个参数需要重点理解。-b -p $HOME/miniconda3表示静默安装并指定安装目录llm是环境名称可以按项目名改。PyTorch 的安装命令里--index-url指定了配套 CUDA 版本的预编译包源这是避免no kernel image报错的关键——用默认 PyPI 源装的 torch 是 CPU 版用错 CUDA 版本则会在运行时找不到核函数。安装完成后必须验证 CUDA 是否真的通了import torch print(torch.__version__) print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0))如果torch.cuda.is_available()返回 False优先查nvidia-smi显示的驱动版本是否支持目标 CUDA 版本别急着重装。驱动版本不用和 CUDA 工具包版本完全一致只要驱动支持的 CUDA 版本不低于 PyTorch 要求的就行。2.3 安装项目依赖时的顺序和版本锁定策略下载好开源项目或微调框架后依赖安装不能无脑pip install -r requirements.txt。我一般会在激活环境后先装基本依赖再逐项核对关键包的版本pip install transformers datasets accelerate peft bitsandbytes pip install langchain langchain-community langchain-core langchainhub pip install huggingface_hub sentencepiece protobuf关键是 transformers、peft、accelerate 三者的版本兼容。LoRA 微调需要 peft 配合 transformers 使用peft 太新或太旧都会出现AttributeError: PeftModel object has no attribute merge_and_unload这类问题。保险起见可以用pip install transformers4.45.0 peft0.13.0 accelerate0.34.0这样锁定一组经过验证的版本。提示生产环境不要用pip install -r requirements.txt后不做任何记录。安装完依赖后用pip freeze requirements-lock.txt把实际版本导出后续复现环境时可以直接用。3. 私有化部署模型下载、transformers 加载、GPU 加速与推理接口3.1 私有化部署要解决的核心问题私有化部署的核心诉求是数据不出内网。企业选择开源模型而不是调用云端 API通常是因为业务数据有保密要求或者推理频次高到按 Token 计费不划算。这也决定了部署方案的选择方向模型必须能完全跑在自己的 GPU 服务器上推理接口要可控、可监控、可替换。模型的获取渠道主要是 Hugging Face 和 ModelScope。国内网络环境访问 Hugging Face 不稳定时我一般优先从 ModelScope 下载速度快且不需要额外配置。下载时用和推理环境中完全一致的版本避免模型权重和 transformers 版本不兼容导致的key mismatch。下面以 Qwen2.5-7B-Instruct 为例演示完整部署流程。3.2 用 transformers 在本地加载和推理下载模型到本地目录后推理脚本只做三件事加载模型、加载分词器、生成回复。先看最小可用的推理代码import torch from transformers import AutoModelForCausalLM, AutoTokenizer model_path /data/models/qwen2.5-7b-instruct tokenizer AutoTokenizer.from_pretrained(model_path, trust_remote_codeTrue) model AutoModelForCausalLM.from_pretrained( model_path, torch_dtypetorch.float16, device_mapauto, trust_remote_codeTrue ) prompt 介绍一下锂离子电池的工作原理 messages [ {role: system, content: 你是一个专业的电池技术顾问。}, {role: user, content: prompt} ] text tokenizer.apply_chat_template(messages, tokenizeFalse, add_generation_promptTrue) model_inputs tokenizer([text], return_tensorspt).to(model.device) generated_ids model.generate( model_inputs.input_ids, max_new_tokens512, do_sampleTrue, temperature0.7, top_p0.9 ) response tokenizer.batch_decode(generated_ids[:, model_inputs.input_ids.shape[1]:], skip_special_tokensTrue)[0] print(response)这段代码里值得细看的是三个点。device_mapauto让 accelerate 自动把模型分配到所有可见 GPU 上单卡和多卡场景都能跑不需要手写cuda:0。torch_dtypetorch.float16把权重加载为半精度显存占用直接减半。apply_chat_template是关键——直接用模型自带的对话模板组装 system 和 user 消息如果手动拼模板很容易因为格式不匹配导致模型回答风格崩坏或重复生成。3.3 GPU 显存预估和量化方案部署前需要预计显存是否够用Qwen2.5-7B 的权重约 15GBfloat16加上 KV Cache 和推理中间变量7B 模型实际需要约 16GB 到 20GB 显存。也就是说单张 24GB 的 3090/4090 可以勉强跑两张才舒服。显存不够时优先做法是加载 4-bit 量化版本。用 bitsandbytes 的load_in_4bit参数把模型量化加载7B 模型的显存占用能降到 6GB 左右from transformers import BitsAndBytesConfig bnb_config BitsAndBytesConfig( load_in_4bitTrue, bnb_4bit_compute_dtypetorch.float16, bnb_4bit_quant_typenf4, bnb_4bit_use_double_quantTrue ) model AutoModelForCausalLM.from_pretrained( model_path, quantization_configbnb_config, device_mapauto, trust_remote_codeTrue )这里的参数含义值得逐个理清。load_in_4bitTrue表示以 4-bit 精度加载权重bnb_4bit_compute_dtypetorch.float16指定计算时反量化到 fp16保证运算精度不至于掉太多bnb_4bit_quant_typenf4用的是 NF4 量化格式相比普通 4-bit 整数量化在低比特下保留更多精度bnb_4bit_use_double_quantTrue对量化常量再量化一次进一步减少显存占用。量化会让生成速度略有下降但换来的是小显存跑大模型的能力。3.4 把推理封装成 HTTP 服务部署到内网供其他服务调用需要把推理脚本封装成接口。常见做法是基于 FastAPI 起一个轻量的推理服务from fastapi import FastAPI, Request from pydantic import BaseModel import uvicorn, torch from transformers import AutoModelForCausalLM, AutoTokenizer app FastAPI() model_path /data/models/qwen2.5-7b-instruct tokenizer AutoTokenizer.from_pretrained(model_path, trust_remote_codeTrue) model AutoModelForCausalLM.from_pretrained( model_path, torch_dtypetorch.float16, device_mapauto, trust_remote_codeTrue ) class Query(BaseModel): prompt: str max_new_tokens: int 512 temperature: float 0.7 app.post(/generate) async def generate(query: Query): messages [{role: user, content: query.prompt}] text tokenizer.apply_chat_template(messages, tokenizeFalse, add_generation_promptTrue) inputs tokenizer([text], return_tensorspt).to(model.device) with torch.no_grad(): outputs model.generate(**inputs, max_new_tokensquery.max_new_tokens, temperaturequery.temperature) response tokenizer.batch_decode(outputs[:, inputs.input_ids.shape[1]:], skip_special_tokensTrue)[0] return {response: response} if __name__ __main__: uvicorn.run(app, host0.0.0.0, port8000)启动后可以用 curl 快速验证接口是否可用curl -X POST http://localhost:8000/generate \ -H Content-Type: application/json \ -d {prompt: 什么是LangChain, max_new_tokens: 256}这个最小封装已经够内网联调使用。host0.0.0.0表示监听所有网卡接口内网其他机器可通过服务器 IP 访问。后面接了 langchain 后这个接口就是 Agent 的推理后端。提示with torch.no_grad()在推理时要始终带着否则显存会被梯度计算撑爆。4. lora微调低秩适应原理、数据集准备与单卡可跑的实战脚本4.1 从原理上理解为什么 LoRA 比全参微调省资源大模型全参微调意味着更新 7B 个参数优化器状态加梯度加上权重本身光训练态显存就是推理态的 3 到 4 倍。LoRA 的策略是冻结原模型权重只在注意力层的 Wq、Wv 等线性层旁边插入两个低秩矩阵 A 和 B。前向计算时输出变成原输出加 BA 的投影训练时只更新 A 和 B。秩 r 通常取 8 到 64参数量只有原模型的 0.1% 到 1%7B 模型微调也只需要单张 24GB 显卡。4.2 数据集指令微调用的格式和构造方法LoRA 微调的数据集一般用指令格式组织每一条包含 instruction、input 和 output 三段。开源社区常用的格式是 Alpaca 格式的 JSON[ { instruction: 解释什么是低秩适应, input: , output: 低秩适应是一种参数高效微调技术通过冻结原模型权重并训练低秩矩阵来实现领域适配。 }, { instruction: 判断下面这段代码的时间复杂度, input: for i in range(n): for j in range(n): print(i*j), output: O(n^2)。存在两层循环每层循环次数为 n。 } ]指令数据不是越多越好。我一般建议单领域 1000 条以上开始有效果但超过 1 万条后收益会明显递减。关键是数据质量——格式统一、答案准确、覆盖真实业务场景这三点比数量重要得多。如果只有原始文档没有问答对可以先让模型用 few-shot 方式生成候选指令对再做人工筛选这是最常见的冷启动做法。4.3 可运行的 LoRA 微调脚本QLoRA Qwen2.5-7B下面的脚本在单张 24GB 显卡上可以跑通 7B 模型的 LoRA 微调使用 4-bit 量化加载节省显存import torch from datasets import load_dataset from transformers import ( AutoModelForCausalLM, AutoTokenizer, TrainingArguments, BitsAndBytesConfig ) from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training from trl import SFTTrainer # 4-bit 量化配置减少显存占用 bnb_config BitsAndBytesConfig( load_in_4bitTrue, bnb_4bit_compute_dtypetorch.float16, bnb_4bit_quant_typenf4 ) model_path /data/models/qwen2.5-7b-instruct model AutoModelForCausalLM.from_pretrained( model_path, quantization_configbnb_config, device_mapauto, trust_remote_codeTrue ) tokenizer AutoTokenizer.from_pretrained(model_path, trust_remote_codeTrue) tokenizer.pad_token tokenizer.eos_token # 为 4-bit 训练做准备冻结原权重启用梯度检查点 model prepare_model_for_kbit_training(model) # LoRA 配置目标模块按 Qwen 结构设置 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) # 加载本地数据集JSON 格式 dataset load_dataset(json, data_files./train_data.json, splittrain) def format_instruction(example): if example[input]: prompt f### 指令:\n{example[instruction]}\n\n### 输入:\n{example[input]}\n\n### 回答:\n else: prompt f### 指令:\n{example[instruction]}\n\n### 回答:\n return {text: prompt example[output]} dataset dataset.map(format_instruction) training_args TrainingArguments( output_dir./qwen-lora-output, per_device_train_batch_size1, gradient_accumulation_steps16, learning_rate2e-4, num_train_epochs3, logging_steps10, save_steps200, fp16True, remove_unused_columnsFalse ) trainer SFTTrainer( modelmodel, argstraining_args, train_datasetdataset, tokenizertokenizer, dataset_text_fieldtext, max_seq_length1024 ) trainer.train() model.save_pretrained(./qwen-lora-adapter) tokenizer.save_pretrained(./qwen-lora-adapter)这个脚本里需要特别留意的参数r16是 LoRA 秩秩越高表达能力越强但显存和过拟合风险同步上升领域数据量不足时建议降到 8。lora_alpha32是缩放系数权重更新幅度和alpha / r成正比一般设为r的 2 倍。target_modules指定插入 LoRA 的注意力层模块名不同模型结构这个列表不一样Qwen、Llama 的模块名都以_proj结尾但具体命名要看model.config或模型源码。gradient_accumulation_steps16配合per_device_train_batch_size1达到等效 batch size 16 的效果这是单卡训练的标准做法用小显存模拟大 batch。4.4 微调后如何和基础模型合并或单独加载训练结束会生成adapter_model.safetensors和adapter_config.json。加载微调后的模型有两个路径。不想动原模型文件就用 peft 加载 adapter 叠加在基础模型上部署性能优先把 adapter 和基础模型合并成一个完整模型文件再部署。合并的代码from peft import PeftModel from transformers import AutoModelForCausalLM, AutoTokenizer base_model_path /data/models/qwen2.5-7b-instruct adapter_path ./qwen-lora-adapter base_model AutoModelForCausalLM.from_pretrained( base_model_path, torch_dtypetorch.float16, device_mapauto, trust_remote_codeTrue ) tokenizer AutoTokenizer.from_pretrained(base_model_path, trust_remote_codeTrue) model PeftModel.from_pretrained(base_model, adapter_path) merged_model model.merge_and_unload() merged_model.save_pretrained(./qwen2.5-7b-merged) tokenizer.save_pretrained(./qwen2.5-7b-merged)提示合并后的模型体积和原模型一致部署方式和未微调模型完全相同直接用AutoModelForCausalLM.from_pretrained加载即可。这也说明 LoRA 在生产部署上不增加任何额外负担。5. langchain接入用 HuggingFacePipeline 把本地模型变成 Agent 推理后端5.1 为什么用 langchain 包一层而不是直接调接口模型部署好之后业务方需要的是问答、文档处理、工具调用这些能力而不是一个裸的 HTTP 接口。langchain 提供了标准化的 Chain 和 Agent 抽象可以把模型、提示词模板、外部工具、记忆组件串成一条流水线。这样做的直接收益是模型可以随时替换——今天用 Qwen明天用 Llama上层业务代码一行不用改。langchain 和 langgraph 的区别也值得在这里说清楚。langchain 是组件库提供模型封装、提示词管理和工具集成langgraph 是在其之上构建有状态、可控制循环的图执行框架适合复杂的多步 Agent 流程。这个标题的场景用 langchain 的链式调用就够了不需要引入 langgraph 的状态机复杂度。5.2 用 HuggingFacePipeline 接入本地推理服务langchain 接入方式主要分两种直接加载本地模型走HuggingFacePipeline或者通过HuggingFaceEndpoint调用已部署的推理服务。先看直接在 langchain 里加载本地模型的方式from langchain_huggingface import HuggingFacePipeline from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline import torch model_path /data/models/qwen2.5-7b-instruct tokenizer AutoTokenizer.from_pretrained(model_path, trust_remote_codeTrue) model AutoModelForCausalLM.from_pretrained( model_path, torch_dtypetorch.float16, device_mapauto, trust_remote_codeTrue ) pipe pipeline( text-generation, modelmodel, tokenizertokenizer, max_new_tokens512, do_sampleTrue, temperature0.7, top_p0.9 ) llm HuggingFacePipeline(pipelinepipe)如果你的模型已经封装成了 HTTP 服务更好的方式是HuggingFaceEndpoint这样 langchain 进程和模型推理进程解耦模型更新不影响上层应用from langchain_huggingface import HuggingFaceEndpoint llm HuggingFaceEndpoint( endpoint_urlhttp://localhost:8000/generate, tasktext-generation, max_new_tokens512, temperature0.7 )注意endpoint_url填的是第 3 章中 FastAPI 服务的地址。这里有个容易踩的坑FastAPI 的输入格式是{prompt: ...}而 HuggingFaceEndpoint 默认会发送{inputs: ...}的 payload两者字段名不一致会导致 422 错误。需要改造一下第 3 章的服务接口让它兼容inputs字段或者在 langchain 侧自定义请求体。字段兼容问题是这个环节最常见的联调失败原因。5.3 用 LangChain 搭一个带上下文记忆的客服问答链把 LLM 接入 Prompt 模板和记忆组件构建一个带历史对话能力的问答链from langchain.memory import ConversationBufferWindowMemory from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain.chains import LLMChain prompt ChatPromptTemplate.from_messages([ (system, 你是一名耐心、专业的技术客服回答问题时先给出结论再解释原因。), MessagesPlaceholder(variable_namehistory), (human, {input}) ]) memory ConversationBufferWindowMemory( k3, return_messagesTrue, memory_keyhistory ) chain LLMChain( llmllm, promptprompt, memorymemory, verboseTrue ) # 轮对话测试 resp1 chain.invoke({input: 我的服务器显卡显存不够怎么办}) print(resp1[text]) resp2 chain.invoke({input: 那如果我用4-bit量化呢}) print(resp2[text])这段代码里MessagesPlaceholder是对话历史注入的位置ConversationBufferWindowMemory的k3表示只保留最近 3 轮对话防止上下文无限膨胀超出模型窗口。return_messagesTrue表示历史以消息列表形式返回和 ChatPromptTemplate 配合时必须开启。这里的分工很清晰langchain 管对话流程和状态模型只需要做好生成这一件事。替换模型时只需修改llm的初始化方式Chain 的代码完全不动这就是用 langchain 封装推理后端的最大优势。5.4 本地知识库问答的常见组合标题场景中另一个高频需求是让模型回答私有文档内容。一种常见做法是 RAG用 embedding 模型把文档向量化存入向量库查询时先检索相关片段再和问题一起交给 LLM 生成回答。部署在公网的 embedding API 不一定适合内网本地可以用BAAI/bge-large-zh-v1.5这类中文 embedding 模型from langchain_community.embeddings import HuggingFaceBgeEmbeddings from langchain_community.vectorstores import FAISS from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_core.documents import Document # 加载本地 embedding 模型 embedding_model HuggingFaceBgeEmbeddings( model_name/data/models/bge-large-zh-v1.5, encode_kwargs{normalize_embeddings: True} ) documents [Document(page_contentLoRA 是一种低秩适应微调方法冻结原模型……, metadata{source: lora_doc.txt})] splitter RecursiveCharacterTextSplitter(chunk_size500, chunk_overlap50) chunks splitter.split_documents(documents) vector_store FAISS.from_documents(chunks, embedding_model) retriever vector_store.as_retriever(search_kwargs{k: 4}) from langchain.chains import RetrievalQA qa_chain RetrievalQA.from_chain_type( llmllm, retrieverretriever, chain_typestuff ) response qa_chain.invoke(LoRA 微调需要多少显存) print(response[result])RecursiveCharacterTextSplitter按 500 字符切块并保留 50 字符重叠保证跨块语义不分裂。k4表示检索返回 4 个相关片段。这套组合的全部组件都跑在内网数据和推理不经过任何外部服务正好符合私有化场景的安全要求。6. 一个真正的进阶技巧Quantized LoRA 合并时的 shape mismatch 修复最后落在一个实际工程中几乎必然会遇到的问题使用 QLoRA4-bit 量化基座微调后合并 adapter或者把 adapter 单独打包部署时出现size mismatch for base_model.model.model.layers.0.self_attn.q_proj.lora_A.weight: copying a param with shape torch.Size([16, 4096]) from checkpoint, the shape in current model is torch.Size([8, 4096])。这类报错几乎都指向同一个原因训练时指定的r和加载 adapter 时指定的r不一致或者 target_modules 列表与训练时不匹配。lora_A.weight的形状是[r, hidden_size]r 变了形状自然对不上。修复方法很直接加载 adapter 时显式指定和训练时一致的LoraConfig。假设训练时用的是r16、target_modules[q_proj,k_proj,v_proj,o_proj]加载代码应该这样写from peft import PeftModel, LoraConfig from transformers import AutoModelForCausalLM, AutoTokenizer base_model_path /data/models/qwen2.5-7b-instruct adapter_path ./qwen-lora-adapter base_model AutoModelForCausalLM.from_pretrained( base_model_path, torch_dtypetorch.float16, device_mapauto, trust_remote_codeTrue ) # 加载 adapter 前先重建相同的 LoRA 配置 lora_config LoraConfig( r16, lora_alpha32, target_modules[q_proj, k_proj, v_proj, o_proj], task_typeCAUSAL_LM ) model PeftModel.from_pretrained(base_model, adapter_path, configlora_config)另一个常见问题是训练时用的分词器带自定义特殊 token比如加了系统提示 token而加载环境的 tokenizer 是从 base model 重新加载的导致词表不一致合并后推理时出现unk_token刷屏。解决方法是把训练环境的 tokenizer 一并保存并随 adapter 一起部署加载时优先用 adapter 目录下的 tokenizertokenizer AutoTokenizer.from_pretrained(adapter_path, trust_remote_codeTrue)验证微调和部署效果最直接的方法是写一个并排对比脚本分别加载 base model 和 merged model输入同一组测试 prompt对比生成结果。重点看领域术语是否准确、回答风格是否贴合业务、是否出现原模型不曾有的重复或胡言乱语。对比脚本也正好可以作为回归测试的基础后续每次微调迭代都跑一遍数据说话比肉眼感觉可靠得多。本文还有配套的精品资源点击获取
