上海交大《动手学大模型》教程实战:从Transformer到LoRA微调与RAG部署
1. 这套教程为什么值得你花时间GitHub上每天都有新项目冒出来但能让我连续三天熬夜刷完的教程不多。上海交大那套《动手学大模型》就是其中一个。我第一次看到它的时候star数还在几千现在已经冲到榜单前列issue区里全是“跟着做完一遍终于知道大模型是怎么回事了”之类的反馈。这套教程解决了一个很实际的问题市面上讲大模型的内容要么是论文式的理论推导看完还是不知道怎么跑代码要么是碎片化的博客东一榔头西一棒子学完连个完整的pipeline都串不起来。《动手学大模型》走的是中间路线——从Transformer基础到LoRA微调再到RAG实战和本地部署每一步都有可运行的notebook和配套讲解。你不需要先啃完《深度学习》花书才能上手只要会基本的Python和PyTorch就能跟着走完全程。适合谁来学我总结了三类人第一类是想转行做大模型应用开发的后端或算法工程师这套教程能帮你把知识体系补全第二类是在校学生想找个靠谱的实战项目写进简历第三类是对大模型好奇但一直被各种术语劝退的技术爱好者。如果你属于这三类中的任何一类接下来的内容值得你认真看完。2. 教程整体架构与学习路线拆解2.1 从Transformer到微调为什么这样安排章节这套教程的章节顺序不是随便排的。我仔细对比过它的目录结构和主流的大模型学习路线发现它遵循了一个很朴素的逻辑先让你理解模型是怎么工作的再教你如何让模型为你工作。第一部分讲Transformer架构包括self-attention、位置编码、残差连接这些核心组件。很多人觉得这部分枯燥想直接跳到微调。但我实测下来如果你不理解attention的计算过程后面调LoRA参数的时候就是盲人摸象。教程里用了一个很巧妙的做法先用几十行PyTorch代码实现一个mini版的Transformer再逐步扩展到完整的GPT结构。这种“手写一遍”的方式比看十遍论文都管用。第二部分进入预训练模型的使用包括如何加载HuggingFace上的模型、tokenizer的工作原理、如何做推理。这里有个细节值得注意教程没有一上来就用最大的模型而是从GPT-2这种小模型开始让你在消费级显卡上就能跑起来。等理解了整个流程再切换到Qwen或LLaMA这类更大的模型。第三部分是微调实战重点讲LoRA和QLoRA。为什么选LoRA而不是全量微调因为全量微调一个7B模型至少需要几张A100而LoRA只需要一张24G显存的卡就能跑起来。教程里给出了详细的参数计算过程比如rank取多少、alpha怎么设、target_modules选哪些层这些在实际项目中都是要反复调的。第四部分是RAG检索增强生成实战把大模型和外部知识库结合起来。这部分内容在当前的企业应用中需求很大因为很多场景下你不可能每次都重新训练模型用RAG来注入领域知识是更务实的方案。第五部分是部署包括用vLLM做推理加速、用Ollama做本地化部署。教程里对比了几种部署方案的吞吐量和延迟给出了不同场景下的选型建议。2.2 环境准备避开依赖冲突的坑在开始之前环境配置是第一个拦路虎。我见过太多人卡在pip install这一步然后就没有然后了。教程里推荐用conda创建独立环境这个建议一定要听。conda create -n llm-tutorial python3.10 conda activate llm-tutorial pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 pip install transformers datasets accelerate peft bitsandbytes这里有几个关键点Python版本选3.10而不是3.11或3.12因为很多大模型相关的库对3.10的支持最稳定。PyTorch的CUDA版本要根据你的显卡驱动来选cu118对应CUDA 11.8如果你的驱动比较新也可以用cu121。bitsandbytes这个库是做量化微调必须的但它对CUDA版本很敏感装错了会报各种奇怪的错误。注意如果你用的是Windows系统bitsandbytes的安装可能会遇到问题。建议在WSL2里操作或者直接用Linux服务器。我试过在Windows原生环境下折腾了半天最后还是切到WSL2才跑通。还有一个容易忽略的点磁盘空间。下载模型权重、数据集、缓存文件加起来轻松超过100G。建议提前清理出足够的空间或者把HF_HOME环境变量指向一个大容量磁盘。export HF_HOME/path/to/your/large/disk/huggingface这个设置能帮你把模型缓存从默认的~/.cache/huggingface转移到指定位置避免系统盘被撑爆。2.3 学习节奏建议别想着三天速成我见过太多人立flag说“一周拿下大模型”结果第三天就放弃了。这套教程的内容量不小我的建议是给自己留出三到四周的时间。第一周专注Transformer基础和模型推理。把教程里的notebook逐个跑一遍不要只是复制粘贴要试着改改参数看看输出有什么变化。比如把attention的head数从12改成6观察模型效果的变化。第二周进入微调部分。先跑通教程里的LoRA示例然后用你自己的数据集替换进去。这里会遇到很多问题数据格式不对、显存不够、loss不下降等等。别慌这些问题教程的issue区里基本都有讨论。第三周RAG和部署。这部分更偏工程需要你对向量数据库、API服务这些有一定了解。如果你之前做过后端开发这部分会轻松很多。第四周做一个完整的项目。把前面学的东西串起来比如用LoRA微调一个模型再用RAG增强它的知识最后用vLLM部署成API服务。这个完整的pipeline写进简历里比什么都管用。3. 核心实操环节从跑通第一个notebook到完成微调3.1 Transformer手写实现理解attention的本质教程里第一个让我眼前一亮的地方是用不到100行代码实现了一个完整的self-attention。很多人学Transformer的时候被各种矩阵维度搞晕其实核心就三步计算Q、K、V算attention score加权求和。import torch import torch.nn as nn import math class SelfAttention(nn.Module): def __init__(self, embed_size, heads): super(SelfAttention, self).__init__() self.embed_size embed_size self.heads heads self.head_dim embed_size // heads assert self.head_dim * heads embed_size, Embed size needs to be divisible by heads self.values nn.Linear(self.head_dim, self.head_dim, biasFalse) self.keys nn.Linear(self.head_dim, self.head_dim, biasFalse) self.queries nn.Linear(self.head_dim, self.head_dim, biasFalse) self.fc_out nn.Linear(heads * self.head_dim, embed_size) def forward(self, values, keys, query, mask): N query.shape[0] value_len, key_len, query_len values.shape[1], keys.shape[1], query.shape[1] values values.reshape(N, value_len, self.heads, self.head_dim) keys keys.reshape(N, key_len, self.heads, self.head_dim) queries query.reshape(N, query_len, self.heads, self.head_dim) values self.values(values) keys self.keys(keys) queries self.queries(queries) energy torch.einsum(nqhd,nkhd-nhqk, [queries, keys]) if mask is not None: energy energy.masked_fill(mask 0, float(-1e20)) attention torch.softmax(energy / (self.embed_size ** (1/2)), dim3) out torch.einsum(nhql,nlhd-nqhd, [attention, values]).reshape( N, query_len, self.heads * self.head_dim ) out self.fc_out(out) return out这段代码的关键在于torch.einsum的使用。nqhd,nkhd-nhqk这个表达式看起来吓人其实就是在做批量矩阵乘法。n是batch sizeq是query长度k是key长度h是head数d是每个head的维度。理解了这个einsum你就理解了attention的核心。教程里还特意留了一个练习把mask去掉看看模型在生成任务上会出什么问题。这个练习很有价值因为在实际微调中mask设置错误是导致loss不下降的常见原因之一。3.2 LoRA微调实战用一张24G显卡跑通7B模型LoRA是这套教程的重头戏。我先说结论用QLoRA4-bit量化LoRA微调一个7B模型24G显存的3090或4090完全够用。教程里给出了详细的显存计算过程我在这里复现一下。假设模型有7B参数全量微调需要存储模型权重7B * 2字节 14GBfp16、梯度14GB、优化器状态Adam需要存储动量和方差2 * 14GB 28GB加起来至少56GB。这还没算激活值的内存占用。用LoRA之后你只需要训练新增的低秩矩阵。假设rank8target_modules只选q_proj和v_proj那么可训练参数大概只有几百万显存占用降到20GB以下。如果再叠加4-bit量化模型权重压缩到3.5GB左右显存占用进一步降到10GB出头。from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig bnb_config BitsAndBytesConfig( load_in_4bitTrue, bnb_4bit_quant_typenf4, bnb_4bit_compute_dtypetorch.float16, bnb_4bit_use_double_quantTrue ) model AutoModelForCausalLM.from_pretrained( Qwen/Qwen2.5-7B, quantization_configbnb_config, device_mapauto, trust_remote_codeTrue ) model prepare_model_for_kbit_training(model) lora_config LoraConfig( r8, lora_alpha32, target_modules[q_proj, v_proj, k_proj, o_proj], lora_dropout0.05, biasnone, task_typeCAUSAL_LM ) model get_peft_model(model, lora_config) model.print_trainable_parameters()这里有几个参数需要解释。r8是LoRA的秩秩越大可训练参数越多效果可能更好但也更容易过拟合。lora_alpha32是缩放因子通常设为r的2到4倍。target_modules选哪些层很关键教程里建议至少覆盖attention的q、k、v、o四个投影层如果显存允许还可以加上FFN层。实操心得我第一次跑LoRA的时候loss一直不下降。排查了半天发现是target_modules只选了q_proj和v_proj模型容量不够。后来加上k_proj和o_projloss就正常下降了。所以如果你的微调效果不好先检查target_modules是不是覆盖得够全。数据准备方面教程用的是alpaca格式的指令数据。格式很简单就是一个JSON列表每条包含instruction、input、output三个字段。你可以把自己的业务数据整理成这个格式几百条就能看到效果。[ { instruction: 将以下文本分类为正面或负面情感, input: 这家餐厅的服务态度很好菜品也很新鲜, output: 正面 } ]训练脚本用HuggingFace的Trainer就行教程里给出了完整的配置。关键参数包括per_device_train_batch_size设为1或2取决于显存gradient_accumulation_steps设为8或16来模拟更大的batch sizelearning_rate用2e-4左右num_train_epochs设3到5。3.3 RAG实战让大模型回答你的私有知识RAG部分的实战价值很高。很多企业场景下你不可能把内部文档全部塞进模型训练这时候RAG就是标准方案。教程里用LangChain和ChromaDB搭了一个完整的RAG pipeline我跟着做了一遍流程很清晰。第一步是文档处理。把PDF、Word、Markdown等格式的文档读进来切成合适大小的chunk。教程建议chunk_size设为500到1000个tokenoverlap设为100到200。这个参数很关键chunk太小会丢失上下文chunk太大检索精度会下降。from langchain.text_splitter import RecursiveCharacterTextSplitter text_splitter RecursiveCharacterTextSplitter( chunk_size800, chunk_overlap150, length_functionlen, separators[\n\n, \n, 。, , , , , , ] ) chunks text_splitter.split_documents(documents)注意separators里加了中文标点因为教程主要面向中文场景。这个细节很实用很多RAG教程直接用英文的separator处理中文文档时切分效果很差。第二步是向量化。教程用的是BGE-M3或者text-embedding-3-small这类embedding模型。BGE-M3的好处是支持中文且可以本地部署不需要调用外部API。向量化之后存入ChromaDB查询的时候用相似度检索。from langchain.vectorstores import Chroma from langchain.embeddings import HuggingFaceEmbeddings embeddings HuggingFaceEmbeddings( model_nameBAAI/bge-m3, model_kwargs{device: cuda}, encode_kwargs{normalize_embeddings: True} ) vectorstore Chroma.from_documents( documentschunks, embeddingembeddings, persist_directory./chroma_db ) retriever vectorstore.as_retriever(search_kwargs{k: 5})第三步是生成。把检索到的文档和用户问题一起塞进prompt让大模型基于这些文档来回答。教程里给了一个很实用的prompt模板明确要求模型“只根据提供的文档回答如果文档中没有相关信息就说不知道”。这个约束很重要能有效减少幻觉。注意RAG的效果很大程度上取决于检索质量。如果检索到的文档不相关模型再强也答不对。教程里建议用混合检索向量检索关键词检索来提升召回率这个技巧在实际项目中很管用。3.4 本地部署vLLM和Ollama怎么选部署部分教程对比了两种方案vLLM适合生产环境的高吞吐场景Ollama适合个人开发者的本地快速验证。vLLM的核心优势是PagedAttention能把推理吞吐量提升好几倍。教程里给了一组实测数据同样的7B模型用HuggingFace的generate方法每秒处理5个请求用vLLM能到20个以上。部署命令很简单pip install vllm python -m vllm.entrypoints.openai.api_server \ --model Qwen/Qwen2.5-7B-Instruct \ --dtype float16 \ --max-model-len 4096 \ --gpu-memory-utilization 0.9启动之后就是一个兼容OpenAI API的服务你可以用openai的Python SDK直接调用。gpu-memory-utilization这个参数控制显存占用比例设成0.9意味着留10%的余量给系统。Ollama的优势是安装简单一条命令就能跑起来。适合在MacBook或者没有NVIDIA显卡的机器上做原型验证。但它的吞吐量不如vLLM不适合高并发场景。ollama pull qwen2.5:7b ollama run qwen2.5:7b教程里还提到了一个容易被忽略的点模型量化。如果显存不够可以用GPTQ或AWQ量化把模型压缩到4-bit显存占用降到原来的四分之一效果损失通常在可接受范围内。4. 踩坑实录与常见问题排查4.1 显存不够用怎么办这是被问得最多的问题。我整理了一个排查顺序按优先级从高到低问题现象可能原因解决方案OOM at model loading模型太大fp16加载不下改用4-bit量化加载OOM during trainingbatch size太大减小batch size增大gradient accumulationOOM during inferencemax_model_len太大减小max_model_len或启用量化训练中途OOM激活值累积开启gradient checkpointinggradient checkpointing是一个很实用的技巧用计算时间换显存空间。开启之后显存占用能降低30%到50%代价是训练速度慢20%左右。在Trainer里设置gradient_checkpointingTrue就行。还有一个容易被忽略的点PyTorch的缓存分配器会保留已释放的显存。如果你在notebook里反复加载模型显存会越占越多。这时候需要手动清理import torch import gc del model gc.collect() torch.cuda.empty_cache()4.2 loss不下降的排查思路微调的时候loss不下降原因可能有很多。我按排查顺序列一下先看数据格式对不对。教程里用的是alpaca格式如果你自己的数据字段名不对模型学到的就是噪声。检查方法很简单把tokenize之后的input_ids打印出来看看是不是符合预期。再看learning_rate是不是太大或太小。LoRA微调常用的学习率是1e-4到3e-4太大loss会震荡太小loss下降很慢。可以先用一个小数据集跑100步观察loss曲线。然后检查target_modules。前面说过只选q_proj和v_proj可能不够建议至少加上k_proj和o_proj。如果显存允许把FFN层也加上。还有一个隐蔽的问题padding token的设置。很多模型没有专门的padding token需要手动设置。如果padding token和eos token冲突loss计算会出问题。tokenizer.pad_token tokenizer.eos_token model.config.pad_token_id tokenizer.eos_token_id4.3 模型下载慢或中断的应对方法国内下载HuggingFace模型经常遇到网络问题。教程里提到了几种方案我补充一下实操细节。第一种是用镜像站。设置环境变量HF_ENDPOINThttps://hf-mirror.com然后正常用from_pretrained加载就行。这个方案最简单但镜像站有时候会同步延迟。第二种是用huggingface-cli download命令支持断点续传。如果下载中断了重新执行命令会从断点继续。huggingface-cli download Qwen/Qwen2.5-7B --local-dir ./models/Qwen2.5-7B --resume-download第三种是提前把模型下载到本地然后用本地路径加载。这种方式最稳定适合需要反复加载模型的场景。实操心得我习惯把常用的模型都下载到本地的一个统一目录然后用符号链接管理不同项目的模型路径。这样既节省磁盘空间又避免了重复下载。4.4 RAG检索不准的优化技巧RAG检索不准通常有三个原因chunk切分不合理、embedding模型不适合中文、检索策略太单一。chunk切分方面教程建议用RecursiveCharacterTextSplitter并加入中文标点作为分隔符。我实测下来chunk_size设在600到800之间效果比较好。太小会丢失上下文太大会引入噪声。embedding模型方面BGE-M3是目前中文场景下综合表现最好的开源模型之一。如果追求更好的效果可以用BGE-Large或者GTE-Large。但模型越大推理越慢需要权衡。检索策略方面单一向量检索容易漏掉关键词匹配的情况。建议加上BM25做混合检索然后用RRFReciprocal Rank Fusion融合两路结果。LangChain里有现成的EnsembleRetriever可以用。from langchain.retrievers import EnsembleRetriever, BM25Retriever bm25_retriever BM25Retriever.from_documents(chunks) bm25_retriever.k 5 ensemble_retriever EnsembleRetriever( retrievers[bm25_retriever, vector_retriever], weights[0.4, 0.6] )这个混合检索的方案我在几个项目里用过召回率比单一向量检索提升了15%到20%。5. 从教程到项目如何把学到的内容落地5.1 构建自己的微调数据集教程里的示例数据是alpaca格式的通用指令数据但你要做实际项目需要构建自己的数据集。我分享一下我的做法。第一步是收集原始数据。可以是客服对话记录、产品文档、技术手册等。数据来源越贴近你的业务场景越好。第二步是清洗和格式化。把原始数据整理成instruction-input-output的格式。如果原始数据没有input字段留空就行。第三步是质量筛选。不是所有数据都适合用来微调。我一般会过滤掉太短少于10个字或太长超过2000个字的样本以及包含敏感信息的样本。第四步是数据增强。如果数据量不够可以用大模型来生成一些变体。比如把同一个问题的不同问法都列出来增加数据的多样性。注意数据质量比数据数量重要得多。我试过用1000条高质量数据微调效果比用10000条低质量数据好很多。宁缺毋滥。5.2 模型评估怎么知道微调效果好不好微调完了怎么评估教程里提到了几种方法我补充一下实操细节。最直接的方法是人工评估。准备一个测试集让微调前后的模型分别回答然后人工打分。这个方法最可靠但成本最高。自动评估可以用BLEU、ROUGE这些指标但它们和人类判断的相关性有限。对于生成任务我更推荐用GPT-4或者Claude作为裁判来打分。还有一种方法是看loss曲线。如果训练loss和验证loss都在下降且没有发散说明训练过程是正常的。但如果验证loss开始上升说明过拟合了需要减少训练轮数或增大dropout。from transformers import Trainer, TrainingArguments training_args TrainingArguments( output_dir./results, num_train_epochs3, per_device_train_batch_size2, gradient_accumulation_steps8, learning_rate2e-4, warmup_ratio0.03, logging_steps10, evaluation_strategysteps, eval_steps50, save_steps100, fp16True, gradient_checkpointingTrue, report_tonone )warmup_ratio0.03这个参数值得说一下。它让学习率在前3%的步数里从0线性增加到设定值避免训练初期loss震荡。这个技巧在微调大模型时很管用。5.3 部署上线从notebook到API服务把微调好的模型部署成API服务才算真正完成了一个项目。教程里用FastAPI搭了一个简单的服务我在此基础上补充一些生产环境的考虑。首先是并发处理。FastAPI默认是单线程的如果同时来多个请求会排队。可以用uvicorn的workers参数启动多个进程或者用vLLM的异步接口。uvicorn app:app --host 0.0.0.0 --port 8000 --workers 4其次是超时和重试。大模型推理有时候会比较慢需要设置合理的超时时间。客户端也要有重试机制避免偶发的网络问题导致请求失败。然后是监控。记录每个请求的延迟、token数、错误率这些指标能帮你发现性能瓶颈。可以用Prometheus加Grafana搭一个简单的监控面板。最后是安全。API服务要加认证避免被滥用。可以用API key或者JWT token来做鉴权。输入也要做过滤防止prompt注入攻击。from fastapi import FastAPI, HTTPException, Depends from fastapi.security import APIKeyHeader app FastAPI() api_key_header APIKeyHeader(nameX-API-Key) async def verify_api_key(api_key: str Depends(api_key_header)): if api_key ! your-secret-key: raise HTTPException(status_code403, detailInvalid API key) return api_key app.post(/generate) async def generate(request: GenerateRequest, api_key: str Depends(verify_api_key)): # 推理逻辑 pass这套组合拳打下来你的模型服务就具备了上生产环境的基本条件。5.4 持续迭代模型更新与数据回流模型上线不是终点而是起点。你需要持续收集用户反馈把bad case整理成新的训练数据定期更新模型。我一般会建一个反馈表记录每次用户觉得回答不好的case。每周整理一次把确认有问题的case加入训练集。每个月做一次增量微调用新数据继续训练模型。数据回流的关键是形成闭环。用户使用产生数据数据用来改进模型改进后的模型提供更好的服务吸引更多用户使用。这个飞轮转起来之后模型效果会越来越好。实操心得增量微调的时候不要只用新数据要混合一部分旧数据一起训练。否则模型会遗忘之前学到的知识这在学术上叫“灾难性遗忘”。我一般按新数据:旧数据1:3的比例混合。6. 学习资源与进阶方向6.1 配套资源怎么用教程本身是开源的GitHub上可以找到完整的notebook和代码。我建议的用法是先看一遍讲解然后自己从头写一遍代码遇到问题再回去查教程。不要直接运行notebook那样学不到东西。除了教程本身还有几个资源值得配合使用。HuggingFace的官方文档是必看的特别是Transformers和PEFT这两个库的文档。LangChain的文档也很重要RAG部分的内容基本都依赖它。论文方面Transformer的原始论文《Attention Is All You Need》是必读的。LoRA的论文《LoRA: Low-Rank Adaptation of Large Language Models》也建议读一遍理解低秩分解的原理。6.2 进阶方向多模态与Agent学完这套教程之后你可以往两个方向进阶。第一个方向是多模态。教程主要讲的是文本大模型但多模态是明显的趋势。可以学习LLaVA、Qwen-VL这类视觉语言模型了解如何把图像和文本对齐。第二个方向是Agent。大模型作为推理引擎配合工具调用和记忆机制可以完成复杂的任务。LangChain和AutoGPT是这方面的代表项目。教程里没有深入讲Agent但RAG部分的基础可以让你快速上手。这两个方向都需要你先扎实掌握教程里的内容。基础不牢直接上多模态或Agent很容易变成调包侠遇到问题不知道怎么排查。6.3 社区参与从学习者到贡献者最后说一个容易被忽略的点参与开源社区。教程的GitHub仓库接受PR如果你发现了bug或者有改进建议可以提issue或PR。这个过程能让你更深入地理解代码也能结识志同道合的人。我自己的经验是给开源项目提PR是提升技术能力最快的方式之一。因为你需要读懂别人的代码理解设计意图还要写出符合规范的代码。这个过程比单纯看教程收获大得多。从跑通第一个notebook到微调出自己的模型再到部署成API服务这套教程能带你走完大模型应用开发的全流程。剩下的就是动手去做了。