从 7B 到 Baichuan2我算是把百川大模型系列的部署和微调都摸了一遍。最早拿到 Baichuan-7B 的时候我还是拿单卡 A100 在跑推理后来 Baichuan2 系列出来又在 vLLM 和 LoRA 微调上折腾了好几个星期。如果你现在正盯着百川的模型列表犹豫该下哪个权重或者已经下载完却在加载时报了一堆错那这篇文章应该能帮你省下不少时间。我尽量把选型逻辑、部署步骤、微调原理和踩坑过程串起来讲不讲空话。1. 先搞清楚百川各个版本之间的真实差距1.1 参数、词表与上下文7B 和 13B 不是简单翻倍百川系列最常被拿出来对比的两组尺寸就是 7B 和 13B。很多人以为 13B 就是 7B 的“加大杯”部署时把显存预算乘个两倍就行。但真实情况是参数量的变化只影响权重体积真正决定你能不能跑得动还得看词表大小、上下文长度、激活值占用这些容易被忽略的指标。模型加载时的权重占用大概可以按“参数数量 x 字节数”来估算。BF16 精度下每个参数占 2 字节所以 7B 的权重大约是 14GB13B 大约 26GB。这里说的还是纯权重没算 KV Cache 和激活值。百川系列的词表做到 64K 以上这比很多英文界模型要大好处是中文压缩率高、编码碎片少代价是 embedding 矩阵本身吃掉的显存比同参数量英文模型更多。上下文方面初代 Baichuan 默认是 4096Baichuan2 相同。如果你要处理长文档只看权重估算是不行的序列长度一旦拉长生成阶段的 KV Cache 会指数级往上走。这时你会发现7B 和 13B 的区别不仅仅是“13B 更强”这么简单。同样的任务13B 在复杂推理和指令遵循上确实有可感知的提升但推理速度会下降微调显存也会紧很多。我的建议很简单如果你的任务以中文问答、信息抽取、摘要为主7B 微调之后完全够用如果要做复杂多轮推理或长文本分析再考虑 13B。1.2 代际差异Baichuan2 比初代强在哪Baichuan2-7B 和 Baichuan-7B 虽然参数规模差不多但两代模型在能力分布上差别不小。官方发布时给过一组评测对比Baichuan2 在数学、代码、逻辑推理这些维度上普遍领先初代尤其在中文场景下的指令跟随能力提升明显。从使用角度说Baichuan2 的生成文本更稳同样的提示词下不太容易出现答非所问或上下文漂移。我实测下来的感觉是初代 Baichuan-7B 更适合做文本表征类任务比如向量化、信息抽取它的基座属性更强。Baichuan2-7B 则在对话类场景表现更好你直接拿去接客服问答、写文案、做知识库回答都比初代省事。Baichuan2-Chat 系列在加载时用官方指定的对话模板不需要你自己拼复杂的历史消息这也在一定程度上减少了工程上的坑。另外要注意Baichuan2 的对齐程度更高但也意味着它更“听话”输出风格更收敛。如果你需要多样化表达比如生成营销文案或创意故事反倒可以试着把温度调到 0.8 以上。而如果只是做结构化输出温度保持在 0.3 以下会更稳。这些参数细节后面部署时候专门讲。1.3 按你的显存和任务来选型我把自己用下来的选型方法整理成了一张表你可以直接对着参考。这里说的显存都是指推理时至少需要预留的量如果还要做 LoRA 微调显存需求另算。你的硬件推荐模型说明8GB 显存Baichuan-7B / Baichuan2-7B 4bit 量化只做推理不要指望微调16GB 显存Baichuan2-7B BF16 推理可以跑 LoRA 微调24GB 显存Baichuan2-13B 4bit 推理 / Baichuan2-7B BF16 微调13B 量化推理更实际40GB 以上Baichuan2-13B BF16 推理 LoRA 微调14B 全参微调仍然紧张多卡每张 24GBBaichuan2-13B 全参微调需要 DeepSpeed ZeRO 或 FSDP这张表不是绝对的但至少能帮你避开“下载了 13B 才发现 8GB 显存根本塞不下”的尴尬。选型时优先看任务复杂度再看显存最后才是参数尺寸。很多人一上来就追求最大模型结果卡在部署环节反而耽误时间。2. 部署前的准备工作显存估算与环境安装2.1 权重、KV Cache、峰值显存怎么算部署之前把显存算清楚比报错了再查日志高效得多。推理时的显存占用大致是三块之和模型权重、激活值、KV Cache。模型权重最简单BF16 下就是参数量(10^9) x 2 字节。激活值和 KV Cache 则跟序列长度强相关。KV Cache 的计算公式大约是2 x 层数 x 多头数 x head_dim x 序列长度 x batch_size x 字节数百川的层数和头数不同版本有差异你跑起来之后可以通过nvidia-smi实测。我习惯直接用「权重 2GB 保底」来粗估比如 7B BF16 我就按至少 16GB 去准备13B 按至少 32GB。这不是精确计算但在选机器阶段够用了。如果你只是做一次性的离线推理一次性加载权重后马上释放就行峰值显存和持续显存差距不大。但如果要做 API 服务多个并发请求会成倍放大 KV Cache 需求。我遇到过单请求没超显存、并发 4 个请求直接 OOM 的情况所以生产部署我一般会在权重预算上再预留 30% 给并发。不要贪宁可把 batch_size 调小一点。2.2 CUDA、PyTorch 与 Python 版本搭配百川模型加载用到的依赖其实不复杂简单说就是 PyTorch transformers 相关工具库。但版本搭配如果不当你会在from_pretrained那一步遇到各种莫名其妙的报错比如找不到BaichuanForCausalLM或FlashAttention编译失败。我目前验证过比较稳的组合是Python 3.10、PyTorch 2.1.x、CUDA 12.1、cuDNN 8.9、transformers 4.35 以上、accelerate 0.25 以上。如果你已经有 PyTorch 2.0 环境也能跑但部分新版模型代码会对trust_remote_code的处理方式有要求老版本容易踩坑。注意baichuan模型是走自定义代码加载的所以你还需要把trust_remote_codeTrue打开。这个参数后面单独说这里先记住它是必须的。如果你要用 vLLM 做服务化vLLM 的版本也要和 CUDA、PyTorch 对齐。我第一次装 vLLM 时直接用最新版结果和已有的 CUDA 11.8 不兼容折腾了一下午。后来我学乖了先在干净环境里建一个虚拟环境把 CUDA 和 PyTorch 装好之后再装 vLLM出错概率低很多。2.3 国内镜像与依赖安装顺序环境安装的顺序我建议是先确认驱动和 CUDA 工具包再装 PyTorch之后装 transformers 和加速库最后才装 vLLM 这类推理框架。依赖顺序如果乱掉比如先装 vLLM 再装 PyTorch很可能会把 PyTorch 版本改掉到时候新旧版本冲突更麻烦。国内环境下直接用官方源下载大模型依赖可能比较慢用镜像源会是顺手的选择。我通常在 pip 里配置清华源pip install torch会快很多尤其是一些大体积的 CUDA 版本 wheel 包。另外模型的权重文件建议用huggingface-cli download或者直接从镜像站拉取断点续传的工具我习惯用hfd比一次次跑git clone省心。整包下载后放到本地目录加载时直接填本地路径不要每次都走在线加载。3. 第一次推理用 transformers 把百川跑起来3.1 基础模型和对话模型的加载差异百川系列的加载方式分两类一类是基础模型比如 Baichuan-7B、Baichuan2-7B另一类是对话模型比如 Baichuan2-7B-Chat。如果你是做文本生成建议直接用 Chat 版本因为它在基座上做了指令对齐输出更完整。如果你只是做特征提取或微调底座那再考虑基础版本。代码上两类模型在加载时都要加trust_remote_codeTrue但对话模板的拼接方式不同。基础模型直接输入纯文本就行Chat 模型则要按官方定义的格式组织用户消息和助手消息。这是最容易出错的地方你直接丢一句帮我写个文案给 Chat 模型它也能回答但多轮对话时因为历史消息没按模板拼接模型会越聊越糊涂。所以我建议把模板封装好而不是零散地拼字符串。3.2 最小推理代码与 chat 模板下面这段代码是我验证过的能用最少步骤跑通 Baichuan2-7B-Chatimport torch from transformers import AutoModelForCausalLM, AutoTokenizer model_id ./baichuan2-7b-chat tokenizer AutoTokenizer.from_pretrained(model_id, trust_remote_codeTrue) model AutoModelForCausalLM.from_pretrained( model_id, torch_dtypetorch.bfloat16, device_mapauto, trust_remote_codeTrue ) messages [ {role: user, content: 介绍一下长征二号F火箭的技术特点} ] prompt tokenizer.apply_chat_template(messages, tokenizeFalse) inputs tokenizer(prompt, return_tensorspt).to(model.device) outputs model.generate( inputs.input_ids, max_new_tokens256, do_sampleTrue, temperature0.6, top_p0.85 ) response tokenizer.decode(outputs[0][inputs.input_ids.shape[1]:], skip_special_tokensTrue) print(response)这里有几个细节值得注意。apply_chat_template是新的 transformers 推荐做法百川的 tokenizer 代码里定义了对应模板不需要你手动拼human之类的符号。max_new_tokens要按任务调摘要生成 200 就够开放式写作可以给 512 甚至更多。skip_special_tokensTrue建议开着否则输出里会漏出 BOS/EOS 等特殊标记干扰后面的文本处理。3.3 trust_remote_code 为什么必须开百川和不少中文大模型在 HuggingFace 上打包时并不是用标准的LlamaForCausalLM结构而是带了一份自定义建模文件只有加载时开启trust_remote_codeTruetransformers 才会临时加载模型目录里的modeling_baichuan.py和tokenization_baichuan.py。如果你不开它会直接报错提示你要不要用远程代码。这个设计听起来吓人但本质上是给模型开发者预留更灵活的定制空间。我个人的建议是不要因为安全问题就绕开这个机制。你只要确认是从可信渠道下的模型那trust_remote_codeTrue就是必要步骤。如果你真的很谨慎可以先下载模型文件打开modeling_baichuan.py扫一眼再决定要不要加载。绝大部分情况下这个文件就是常规的 transformer 层堆叠和采样逻辑没有隐藏的高风险行为。4. 生产环境部署vLLM 与量化方案4.1 vLLM 带来的吞吐量提升如果只是自己在笔记本上跑一跑transformers 的generate足够。但一旦要把模型做成接口给多个用户调用直接用 transformers 的 naive 方式会有明显问题生成请求串行排队、GPU 利用率低、显存被 KL Cache 占光。这时候就该换 vLLM。vLLM 的核心思路是 PagedAttention把 KV Cache 划分成固定大小的块按需分配不像 transformers 那样一次性预留完整空间。实测下来我在单张 40GB 显存的卡上用 Baichuan2-7B-Chat 部署 vLLM并发 8 个请求依然能保持每请求几百 token 的输出整体吞吐比原生 transformers 部署高了不少。对于这种体量的模型vLLM 是最省事的生产方案。启动时用 OpenAI 兼容的接口客户端完全可以用openai库来对接代码层面没有任何学习成本。下面就是一个典型的启动命令python -m vllm.entrypoints.openai.api_server \ --model ./baichuan2-7b-chat \ --tokenizer ./baichuan2-7b-chat \ --tensor-parallel-size 1 \ --dtype bfloat16 \ --max-model-len 4096 \ --gpu-memory-utilization 0.9 \ --trust-remote-code--gpu-memory-utilization可以按显存实际情况调整0.9 意味着让 vLLM 尽量用满 90% 的显存剩下的留给其他进程。如果同时跑多个服务可以调低到 0.7。另外一个需要注意的参数是--max-model-len它直接决定了最大序列长度上限设得太大反而会限制并发的 batch 数。我建议按你的业务需求来不要盲目追求长文本。4.2 GPTQ 量化与 bitsandbytes 量化的取舍低显存机器上跑百川量化几乎是必经之路。目前用的比较多的两条路线一是离线用 GPTQ 把模型压成 4bit二是加载时用 bitsandbytes 做 4bit/8bit 推理。GPTQ 需要先在 A100 这类高显存设备上对数据集做校准产出一份量化后的权重之后加载就变得很轻。7B 模型 4bit 量化后权重约 4GB13B 约 7-8GB8GB 显存卡也能勉强跑 7B 量化版。bitsandbytes 则更方便直接在from_pretrained里传load_in_4bitTrue就行适合快速验证。如果要做生产服务我建议优先用 GPTQ它推理时无需动态量化速度和稳定性更好。但要注意GPTQ 量化后的模型在复杂数学和代码任务上可能比 BF16 版本有明显下降生产前一定要对齐测试集做回归评估。4.3 部署成 OpenAI 兼容 APIvLLM 启动成功后可以用一行 Python 代码验证from openai import OpenAI client OpenAI(base_urlhttp://localhost:8000/v1, api_keyEMPTY) resp client.chat.completions.create( modelbaichuan2-7b-chat, messages[{role: user, content: 用一句话解释什么是大模型}], temperature0.6, max_tokens128 ) print(resp.choices[0].message.content)接口路径、返回结构都兼容 OpenAI 标准格式。这样做的好处是你后续想换成其他模型只要改一下base_url和model业务代码几乎不用动。我在多个项目里都用这一套省掉了重复写前端适配层的成本。5. 微调之前必须掌握的机制5.1 全参微调的成本计算很多人一听说“微调”第一反应就是整个模型重新训练一轮。但 7B 模型全参微调对显存的需求远超你想象除了权重和梯度你还得保存优化器状态。以 AdamW 为例每个参数需要额外保存一阶动量、二阶动量以及对应浮点状态加起来大概占参数的 12 字节。这意味着 7B 模型仅优化器状态就可能超过 80GB 显存单卡根本塞不下。即便你用 DeepSpeed ZeRO-3 或 FSDP 做多卡分片团队也要有足够工程能力去处理稳定性、断点保存和梯度同步问题。对于大多数个人开发者或中小企业全参微调不是性价比最高的方案。反而是 LoRA 这类参数高效微调方法能把可训练参数量压缩到全部参数的 0.1%-1%单卡就能跑起来。5.2 LoRA 和 QLoRA 为什么省显存LoRA 的核心是冻结原始权重仅在注意力层旁边插入低秩矩阵。这么做的好处是梯度和优化器状态只跟低秩矩阵有关显存占用大幅下降。QLoRA 更进一步把原始权重先用 NF4 量化成 4bit再叠加 LoRA 训练。这样一来原本 7B 模型需要 80GB 才能做的全参微调被压缩到 16GB 甚至更低的显存就能训练。我用 QLoRA 微调过 Baichuan2-7B在单张 24GB 卡上设置lora_rank64、lora_alpha16、max_length1024、batch_size取 4再用梯度累积到 16整个训练过程的显存峰值大约在 22GB 上下。这个参数组合不是随便拍的rank 越高拟合能力越强但显存和训练时间同步上升batch_size 受显存限制不能设太大改成梯度累积来虚拟放大 batch 是常见的折中。5.3 微调样本怎么组织微调百川这类模型时数据组织方式比模型选型更影响效果。我不建议直接丢一堆纯文本让模型练生成而是按指令格式组织。以 Alpaca 格式为例每条样本包含 instruction、input、output 三个字段。Chat 模型微调则偏好更完整的对话历史。下面是一个日常任务微调的样本示例{ instruction: 请根据商品信息生成一段吸引人的商品描述, input: 品牌某户外用品货名轻量折叠椅重量0.9kg承重120kg特点收纳体积小适合露营钓鱼。, output: 这款轻量折叠椅仅重0.9kg收纳后体积不及一瓶水却能承重120kg。无论是露营还是钓鱼带上它都能让你随时坐下来享受户外。 }数量上我自己的经验是 2 千到 1 万条高质量样本就能看到明显效果关键在多样性把不同长度、不同表达风格、不同行业的内容混合进去避免模型对某个句式过拟合。另外无监督语料和指令数据按比例混合也很重要训练时别把上万条纯文本语料全扔掉保留 20%-30% 让模型保持原有语言能力效果会稳定很多。6. 手把手微调一个 Baichuan2-7B-Chat6.1 用 QLoRA 做中文指令微调我以一个具体的“商品文案生成助手”为例子。目标是把一个通用 Chat 模型调成更懂电商文案口吻的模型。数据是几千条“商品信息 描述文案”的配对。准备阶段要做三件事清洗文本、删除过长样本、切分训练集和验证集。这里有个容易被忽略的点如果原始样本超过配置的max_length训练时会被截断模型学到不完整指令输出往往也会戛然而止。所以我建议把max_length1024但只保留 900 token 以内的样本留一点余量给生成目标。6.2 训练脚本与关键参数我基于 HuggingFace 的 Trainer 写了下面的训练脚本这也是目前最通用的方式。LoRA 层主要加在注意力层的q_proj、k_proj、v_proj、o_proj上部分实现会把gate_proj、up_proj也加上。以我的经验加上 MLP 层能提升一点性能但显存消耗也会上涨按需取舍。import torch from transformers import ( AutoModelForCausalLM, AutoTokenizer, TrainingArguments, Trainer, DataCollatorForSeq2Seq ) from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training model_id ./baichuan2-7b-chat tokenizer AutoTokenizer.from_pretrained(model_id, trust_remote_codeTrue) model AutoModelForCausalLM.from_pretrained( model_id, torch_dtypetorch.bfloat16, device_mapauto, trust_remote_codeTrue, load_in_4bitTrue ) model prepare_model_for_kbit_training(model) lora_config LoraConfig( r64, lora_alpha16, 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-baichuan2-7b, per_device_train_batch_size4, gradient_accumulation_steps4, learning_rate2e-4, num_train_epochs3, lr_scheduler_typecosine, warmup_ratio0.03, logging_steps20, save_steps500, fp16True, grad_checkpointingTrue, ) trainer Trainer( modelmodel, argstraining_args, train_datasettrain_dataset, eval_dataseteval_dataset, data_collatorDataCollatorForSeq2Seq(tokenizer), ) trainer.train()挑几个参数说明一下lora_alpha16对应r64时缩放因子是 16/640.25效果相当于让低秩矩阵对原始权重的影响不至于过猛。学习率 2e-4 是 LoRA 微调常用的起点如果数据量小可以降到 1e-4数据量大且噪声多可以提到 3e-4但再高就容易震荡。fp16True能在部分硬件上减少显存同时提一点速度如果生成质量波动改成bf16True也可以。6.3 合并权重、转格式与部署训练完成后模型权重还保存在低秩分支里推理时可以用 PeftModel 动态加载但如果你要在 vLLM 等服务上使用更推荐把 LoRA 权重合并回主模型导出一份完整的权重文件。合并代码也比较短from peft import PeftModel base_model AutoModelForCausalLM.from_pretrained( ./baichuan2-7b-chat, trust_remote_codeTrue, torch_dtypetorch.bfloat16, device_mapauto ) merged_model PeftModel.from_pretrained(base_model, ./lora-baichuan2-7b) merged_model merged_model.merge_and_unload() merged_model.save_pretrained(./baichuan2-7b-chat-merged, safe_serializationTrue) tokenizer.save_pretrained(./baichuan2-7b-chat-merged)合并后的模型可以直接被 vLLM 加载也能继续用from_pretrained读取。这个过程最怕的是 LoRA 适配器和 base 模型版本不匹配。我的建议是微调前记录好基础模型的 commit id 或文件名合并时用同一个目录避免出现维度对不上的情况。另外merge_and_unload()之后要检查一下 PEFT 的配置文件是否还残留如果加载时报错说找不到 adapter可以先删除未知键再重试。7. 那些文档里没写的坑7.1 OOM不要只调 batch_size用 QLoRA 微调 Baichuan2 时最常见的错误是CUDA out of memory。很多人第一反应是把per_device_train_batch_size调成 1但 OOM 不一定只由 batch_size 引起。最有效的三板斧是开gradient_checkpointingTrue减少激活值缓存把max_length从 2048 降到 1024再配合梯度累积保持有效 batch 不变。三者组合之后24GB 卡跑 7B QLoRA 是完全可行的。还要注意一个隐蔽问题load_in_4bitTrue在部分驱动下会触发慢速且高显存的kbit算子。如果训练特别慢并且显存异常可以检查是否安装了最新的bitsandbytes版本或者尝试切换到 8bit。实测下来不同版本之间显存占用差距能达到几个 GB。7.2 中文乱码与分词效果异常百川系列对中文的支持总体不错但仍有特殊情况。当训练数据里混入大量特殊符号、emoji、生僻字时AutoTokenizer的默认处理方式可能把文本切成大量 unk 标记导致生成内容变烂。我的经验是清洗数据时不要只做“去 HTML 标签”这种基础处理还要跑一遍 tokenizer统计文本里的 unk token 数超过一定比例就单独清理。另外如果你在微调时给不同角色加了im_start之类的自定义标记需要确保 tokenizer 和模型训练时的 special token 列表一致。否则推理时会碰到tokenizer.decode输出一堆奇怪符号的问题这个问题排查起来非常折磨但通常就是模板字符串和 tokenizer 的 special token 映射没对上。7.3 生成效果总是重复或跑题生成阶段的问题往往不是模型不够好而是采样参数没调对。我在部署商品文案生成服务时遇到的典型问题是句子重复。这时我会先用no_repeat_ngram_size3做硬性拦截再把温度从默认值降到 0.6-0.8top_p调到 0.85 左右。如果效果仍然跑题问题大概率在提示词模板提示词里给的信息必须精确。你要让模型生成“卖点描述”就要在指令里明确指定“控制在50字以内突出轻便和承重能力”之类约束而不是笼统说“写一段广告”。还有一个小建议把微调数据和生成评估放在一起看。你训练时的输出格式是商品信息 商品描述推理时也要保持同样的字段次序和提示格式。模型对格式的记忆比我们想象中强得多你只要在推理时换个模板输出质量就会掉一截。从我自己的经历来看百川系列的部署和微调并没有想象中那么高门槛但每一步都藏着小陷阱。如果你只是做推理先按选型表把模型定下来然后把 vLLM 服务跑通如果你需要更贴合业务的效果再考虑 QLoRA 微调那套流程。吴恩达那句“先用最简方案跑通再考虑优化”放在这里非常适用。希望这篇攻略能让你少走几个弯路。最后提醒一句模型文件比较大下载时记得校验 SHA256 人哈希值免得文件损坏之后排查半天找不到原因。
阅读完成 · 觉得有帮助?