尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于ROCm与LoRA的Gemma 2B大模型微调及Ollama部署全流程实践

基于ROCm与LoRA的Gemma 2B大模型微调及Ollama部署全流程实践 1. 项目概述一次完整的大模型微调与部署实践最近在折腾大模型本地化应用目标很明确想基于一个轻量级的开源大模型用我自己的数据训练一个垂直领域的助手然后把它变成一个可以随时对话的本地服务。经过一番调研我锁定了Google的Gemma 2B/7B模型它开源、性能不错而且对个人开发者相对友好。整个流程走下来涉及从底层驱动环境搭建、模型微调到最后的服务化部署算是一次比较完整的实践。今天就把从零开始在支持AMD显卡的Linux服务器上基于ROCm环境对Gemma 2B模型进行LoRA微调并最终通过Ollama部署上线的全过程记录下来。如果你手头有AMD显卡比如RX 6000/7000系列或Instinct系列或者单纯想了解大模型微调与部署的完整链路这篇记录应该能提供不少参考。整个过程可以拆解为三个核心阶段首先是搭建ROCm计算环境这是让AMD显卡能跑PyTorch深度学习训练的前提其次是使用LoRA技术对Gemma模型进行微调这是本次实践的核心旨在用较小的代价让模型学会特定领域的知识或技能最后是将微调好的模型整合进Ollama框架实现本地化的模型服务与管理。每个阶段都有不少细节和坑我会结合自己的实操把关键步骤、原理和避坑心得都摊开来讲清楚。2. 环境准备ROCm在Linux下的搭建与踩坑实录我的实验环境是一台搭载了AMD Radeon RX 7900 XTX显卡的Ubuntu 22.04 LTS服务器。选择ROCm是因为它是AMD官方推出的开源GPU计算平台对标NVIDIA的CUDA是让PyTorch等框架在AMD显卡上运行的基础。2.1 ROCm安装与系统配置安装ROCm的第一步是确认显卡兼容性。并非所有AMD显卡都受官方支持我查阅了ROCm的官方支持列表确认RX 7900 XTX在支持范围内。然后需要添加ROCm的APT仓库并安装核心包。这里命令并不复杂但系统层面的依赖和配置是关键。# 添加ROCm官方APT仓库 wget https://repo.radeon.com/amdgpu-install/latest/ubuntu/jammy/amdgpu-install_6.1.60100-1_all.deb sudo apt install ./amdgpu-install_6.1.60100-1_all.deb sudo amdgpu-install --usecaserocm --no-dkms安装完成后需要将当前用户添加到render和video组以便有权限访问GPU设备。sudo usermod -a -G render,video $LOGNAME之后必须重启系统这是很多教程里轻描淡写但极其重要的一步不重启用户组变更和内核模块加载可能不会完全生效。重启后验证安装是否成功rocminfo这个命令会输出详细的GPU信息。更直观的验证是运行rocm-smi它类似于NVIDIA的nvidia-smi可以查看GPU状态、温度、功耗和显存占用。注意驱动版本匹配问题。ROCm版本、Linux内核版本、显卡固件版本之间需要匹配。我曾因内核自动升级到较新版本导致与特定版本的ROCm驱动不兼容出现kfd模块加载失败的错误。解决方案是暂时锁定内核版本或者寻找与当前内核匹配的ROCm版本。对于生产环境建议在部署初期就确定一个稳定的版本组合并冻结更新。2.2 PyTorch与深度学习环境配置ROCm只是底层驱动和运行时上层还需要适配了ROCm的PyTorch。PyTorch官方提供了预编译的ROCm版本。pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/rocm6.1这里的rocm6.1需要与你安装的ROCm主版本号对应。安装后在Python中运行以下代码进行验证import torch print(torch.__version__) print(torch.cuda.is_available()) # 对于ROCm这里仍返回True但实际使用的是ROCm后端 print(torch.cuda.get_device_name(0))如果一切正常会打印出版本号、True以及你的AMD显卡型号。这表明PyTorch已经可以识别并调用你的AMD GPU进行计算了。接下来安装微调所需的其他库主要是Hugging Face的transformers,datasets,accelerate以及LoRA相关的peft和bitsandbytes用于量化。这里有个大坑bitsandbytes对ROCm的支持并不像CUDA那样完善。经过测试在ROCm 6.1环境下直接pip install bitsandbytes可能会编译失败或运行时出错。一个可行的替代方案是使用pip install bitsandbytes-rocm这是一个社区维护的针对ROCm的移植版本或者考虑使用其他量化库如auto-gptq或gptq-for-llama但这需要模型本身有对应的GPTQ量化版本。对于本次Gemma 2B的LoRA微调由于参数量不大我选择先不使用量化直接在全精度FP16下进行这对24GB显存的7900 XTX来说是可行的。3. 模型微调核心LoRA技术解析与实战环境就绪后进入核心环节——微调。我们采用LoRALow-Rank Adaptation方法它因其高效和轻量化成为微调大模型的首选技术之一。3.1 LoRA原理浅析为什么它能省显存在深入代码之前有必要理解LoRA为什么有效。传统微调Full Fine-Tuning会更新模型的所有参数对于动辄数十亿参数的大模型这需要存储两份模型参数原始参数和优化器状态显存开销巨大。LoRA提出了一种巧妙的“旁路”机制。它冻结预训练模型的所有原始参数不进行更新。然后在模型的某些关键层通常是Transformer结构中的注意力模块的Query, Key, Value和输出投影层旁插入一组可训练的“低秩适配器”。具体来说对于一个预训练权重矩阵 ( W \in \mathbb{R}^{d \times k} )LoRA不直接改变 ( W )而是用一个低秩分解来表示其更新量( \Delta W BA )其中 ( B \in \mathbb{R}^{d \times r} ), ( A \in \mathbb{R}^{r \times k} )秩 ( r \ll min(d, k) )。在正向传播时实际执行的运算是 ( h Wx BAx )。这样做的好处极其明显显存效率只需存储和优化适配器参数 ( A ) 和 ( B )其数量远少于原始参数。例如对于70亿参数的模型LoRA参数可能只有几百万到几千万显存占用从几十GB降到几GB。训练效率由于大部分参数被冻结只需要计算适配器部分的梯度训练速度更快。模块化与切换不同的适配器对应不同的任务可以像插件一样在同一个基础模型上快速加载和切换便于管理多任务。减轻灾难性遗忘因为基础模型参数不变它原有的广泛知识得以保留主要学习的是针对新任务的新特征。3.2 数据准备与处理我准备了一个关于某个垂直领域比如咖啡知识的指令微调数据集。数据格式遵循常见的指令-响应对话格式每条数据是一个JSON对象包含instruction、input可选、output字段。例如{ instruction: 请介绍手冲咖啡的步骤。, input: , output: 1. 研磨咖啡豆将咖啡豆研磨至白砂糖粗细... 2. 湿润滤纸用热水冲洗滤纸去除纸味并温壶... 3. 闷蒸注入少量热水让咖啡粉充分排气约30秒... 4. 分段注水以画圈方式缓慢注入剩余热水... 5. 完成萃取等待水滴完移开滤杯即可享用。 }使用Hugging Facedatasets库加载和预处理数据from datasets import load_dataset dataset load_dataset(json, data_filescoffee_qa.json)接着需要将文本数据转换为模型可接受的token ID序列。这里使用Gemma的tokenizer。from transformers import AutoTokenizer tokenizer AutoTokenizer.from_pretrained(google/gemma-2b-it) # 使用指令微调版本 tokenizer.pad_token tokenizer.eos_token # 设置填充token def tokenize_function(examples): # 构建指令格式的文本 prompts [] for inst, inp, outp in zip(examples[instruction], examples[input], examples[output]): if inp: prompt fstart_of_turnuser\n{inst}\n{inp}end_of_turn\nstart_of_turnmodel\n else: prompt fstart_of_turnuser\n{inst}end_of_turn\nstart_of_turnmodel\n prompts.append(prompt) # 将输出部分也拼接上但只在计算loss时考虑输出部分 full_text prompt outp tokenizer.eos_token examples[text] full_text return tokenizer(examples[text], truncationTrue, paddingmax_length, max_length512) tokenized_dataset dataset.map(tokenize_function, batchedTrue, remove_columnsdataset[train].column_names)这里的关键是构建正确的对话格式Gemma-IT模型特定的start_of_turn标签和设置正确的损失掩码。我们只希望在模型输出部分即output字段计算损失而在指令和输入部分忽略损失。这通常通过attention_mask和labels的巧妙设置来实现确保梯度只来自我们希望模型学习的响应部分。3.3 LoRA微调脚本与参数详解接下来是核心的训练脚本。我们使用peft库来方便地配置LoRA。from transformers import AutoModelForCausalLM, TrainingArguments, Trainer from peft import LoraConfig, get_peft_model, TaskType import torch # 1. 加载基础模型 model AutoModelForCausalLM.from_pretrained( google/gemma-2b-it, torch_dtypetorch.float16, # 使用FP16节省显存 device_mapauto, # 让accelerate自动分配模型层到设备 trust_remote_codeFalse, ) # 2. 配置LoRA参数 lora_config LoraConfig( task_typeTaskType.CAUSAL_LM, # 因果语言模型任务 r8, # LoRA秩即低秩矩阵的维度r。值越小参数量越少但能力可能越弱。通常从8、16开始尝试。 lora_alpha32, # 缩放因子与学习率相关。通常设置为r的2-4倍。 lora_dropout0.1, # LoRA层的dropout率用于防止过拟合。 target_modules[q_proj, k_proj, v_proj, o_proj], # 将LoRA适配器注入到注意力层的这些线性模块中。 biasnone, # 是否训练偏置项。none表示不训练。 ) # 3. 将基础模型转换为PEFT模型 model get_peft_model(model, lora_config) model.print_trainable_parameters() # 打印可训练参数数量确认只有一小部分参数被激活 # 4. 定义训练参数 training_args TrainingArguments( output_dir./gemma-2b-coffee-lora, num_train_epochs3, per_device_train_batch_size4, # 根据显存调整。RX 7900 XTX 24G对于2B模型batch_size4是安全的。 gradient_accumulation_steps4, # 梯度累积模拟更大的batch size。 warmup_steps100, logging_steps10, save_steps200, evaluation_strategyno, # 如果没有验证集设为no save_total_limit2, learning_rate2e-4, # LoRA学习率通常比全量微调大例如1e-4到5e-4。 fp16True, # 使用混合精度训练ROCm环境下确保torch版本支持。 remove_unused_columnsFalse, push_to_hubFalse, # 本地训练不上传 report_totensorboard, ) # 5. 创建Trainer并开始训练 trainer Trainer( modelmodel, argstraining_args, train_datasettokenized_dataset[train], data_collatorDataCollatorForLanguageModeling(tokenizertokenizer, mlmFalse), ) trainer.train()关键参数解析与调优经验r(秩)这是LoRA最重要的超参数之一。它决定了适配器的表达能力。对于2B的模型r8或16是常见的起点。如果任务简单或数据量少可以尝试更小的r如4如果任务复杂或希望模型有更强的适应能力可以增大到32甚至64但这会增加参数量和过拟合风险。我的咖啡知识QA任务相对明确r8效果已足够。lora_alpha可以理解为适配器输出的缩放因子。在代码实现中LoRA的输出会乘以alpha/r。因此固定alpha增大r会减小缩放比例。通常将alpha设为r的2倍或4倍是一个经验法则。我设置为32。target_modules指定将LoRA适配器加到哪些层。对于Decoder-only的模型如Gemma, LLaMA通常选择注意力机制中的q_proj,k_proj,v_proj,o_proj。有些研究也建议加入FFN层的gate_proj,up_proj,down_proj但这会显著增加可训练参数量。对于初步实验只加在注意力层是标准做法。per_device_train_batch_size与gradient_accumulation_steps实际有效的batch size是per_device_train_batch_size * gradient_accumulation_steps * GPU数量。受限于显存单卡batch size可能只能设到1或2。通过梯度累积gradient_accumulation_steps4我们可以每4步才更新一次权重相当于模拟了batch size为4或8的训练有助于训练稳定。需要权衡的是累积步数越多更新越慢但梯度估计更准。learning_rateLoRA训练的学习率通常比全量微调大例如1e-4到5e-4因为只更新一小部分参数。我从2e-4开始如果训练损失下降很慢或不降可以尝试提高到3e-4或4e-4。实操心得监控显存与调整batch size。在ROCm环境下使用rocm-smi或watch -n 1 rocm-smi来实时监控显存占用。如果训练开始不久显存就接近爆满需要降低per_device_train_batch_size。如果显存还有富余但训练速度慢可以尝试增大batch size以提高硬件利用率。另外开启fp16混合精度训练能有效节省显存并加速但要注意数值稳定性如果训练中出现损失NaN可以尝试关闭fp16或使用bf16如果硬件支持。4. 模型合并与转换为Ollama部署做准备训练完成后我们得到的是LoRA适配器权重通常是一个adapter_model.bin或safetensors文件而不是一个完整的模型文件。Ollama目前主要支持加载完整的模型GGUF或类似格式的文件。因此我们需要将LoRA权重与原始的基础模型合并得到一个完整的、经过微调的新模型。4.1 合并LoRA权重使用peft库可以方便地合并权重from peft import PeftModel from transformers import AutoModelForCausalLM, AutoTokenizer import torch # 加载原始基础模型 base_model AutoModelForCausalLM.from_pretrained( google/gemma-2b-it, torch_dtypetorch.float16, device_mapauto, ) # 加载训练好的LoRA适配器 model PeftModel.from_pretrained(base_model, ./gemma-2b-coffee-lora/checkpoint-600) # 将适配器权重合并到基础模型中 merged_model model.merge_and_unload() # 保存合并后的完整模型 merged_model.save_pretrained(./gemma-2b-coffee-merged) tokenizer.save_pretrained(./gemma-2b-coffee-merged)这个过程会在指定目录下生成完整的PyTorch模型文件pytorch_model.bin和config.json等。4.2 转换为Ollama支持的GGUF格式Ollama推荐使用GGUFGPT-Generated Unified Format格式这是一种为GGML推理引擎设计的二进制格式支持量化能在CPU和GPU上高效运行。我们需要使用llama.cpp项目的工具来进行转换。首先需要将PyTorch模型转换为GGUF支持的中间格式通常是HF格式我们已经有了。然后使用llama.cpp的convert.py脚本或convert-hf-to-gguf.py进行转换。# 1. 克隆 llama.cpp 仓库 git clone https://github.com/ggerganov/llama.cpp cd llama.cpp # 2. 编译项目确保已安装cmake等编译工具 make # 3. 将Hugging Face模型转换为GGUF格式 python convert-hf-to-gguf.py ../gemma-2b-coffee-merged --outtype f16 --outfile ../gemma-2b-coffee-f16.gguf这里--outtype f16指定输出为FP16精度。你也可以选择量化版本以减小模型体积、降低部署资源需求例如q4_04位整数量化、q8_08位整数量化等。量化会轻微损失精度但能极大提升推理速度并降低内存占用。对于本地部署q4_0或q5_0是一个不错的权衡。# 量化示例将FP16模型量化为Q4_0格式 ./quantize ../gemma-2b-coffee-f16.gguf ../gemma-2b-coffee-q4_0.gguf q4_0注意事项版本兼容性。llama.cpp和其转换脚本在快速迭代中不同版本对模型架构如Gemma的支持程度可能不同。如果转换失败常见错误是“不支持的张量类型”或架构识别错误可以尝试回退到llama.cpp的某个稳定发布版本或者查看其GitHub Issues中关于Gemma转换的讨论。我使用的是llama.cpp较新的提交成功转换了Gemma 2B模型。5. Ollama本地部署与模型服务化Ollama的出现极大地简化了在本地运行大模型的过程。它类似于一个模型管理器可以拉取、运行和管理各种GGUF格式的模型。5.1 Ollama安装与自定义模型创建首先在Linux上安装Ollamacurl -fsSL https://ollama.com/install.sh | sh安装后Ollama服务会自动启动。默认情况下Ollama会从官方仓库下载模型。但我们需要运行自己微调的模型这就需要创建一个自定义的Modelfile。在包含我们GGUF模型文件的目录下创建一个名为Modelfile的文本文件FROM ./gemma-2b-coffee-q4_0.gguf TEMPLATE start_of_turnuser {{ .Prompt }}end_of_turn start_of_turnmodel PARAMETER temperature 0.7 PARAMETER top_p 0.9 PARAMETER num_ctx 4096FROM指定模型文件的路径。这里使用相对路径指向我们量化后的GGUF文件。TEMPLATE定义对话模板。这是至关重要的一步必须与模型训练时使用的格式一致。Gemma指令微调版本使用了特定的start_of_turn标签。这个模板确保了用户输入被正确包装模型才能生成符合预期的回复。PARAMETER设置推理参数如temperature创造性值越高越随机、top_p核采样影响词汇选择多样性、num_ctx上下文长度。5.2 创建并运行自定义模型使用ollama create命令基于Modelfile创建自定义模型ollama create my-gemma-coffee -f ./Modelfilemy-gemma-coffee是你给这个自定义模型起的名字。然后就可以像使用任何其他Ollama模型一样运行它ollama run my-gemma-coffee运行后会进入一个交互式对话界面。你可以输入问题测试例如“手冲咖啡应该如何闷蒸” 模型应该会基于我们微调的数据集给出专业、准确的回答。5.3 集成与API调用Ollama不仅提供命令行交互还内置了一个HTTP API服务器默认在11434端口这使得它可以轻松集成到其他应用中。启动Ollama服务后可以通过curl进行测试curl http://localhost:11434/api/generate -d { model: my-gemma-coffee, prompt: 请介绍手冲咖啡的步骤。, stream: false }也可以使用Python的requests库进行调用构建简单的应用程序import requests import json def ask_ollama(prompt, modelmy-gemma-coffee): url http://localhost:11434/api/generate data { model: model, prompt: prompt, stream: False, options: { temperature: 0.7, top_p: 0.9 } } response requests.post(url, jsondata) if response.status_code 200: return response.json()[response] else: return fError: {response.status_code} answer ask_ollama(拿铁和卡布奇诺有什么区别) print(answer)部署优化与问题排查GPU加速Ollama在Linux下默认可能使用CPU推理。要启用AMD GPU加速需要确保系统已安装ROCm并在运行Ollama时设置环境变量OLLAMA_GPU_DRIVERrocm。可以通过ollama run时添加--verbose标志或在Ollama服务日志中查看是否成功加载了GPU后端。显存不足如果遇到显存不足错误可以考虑使用量化程度更高的模型如q4_0替代q8_0或f16或者在Modelfile中减少num_gpu_layers参数该参数控制有多少模型层被卸载到GPU减少它会让更多层留在CPU内存中。响应速度慢首先确认是否使用了GPU。如果仍在CPU上运行速度必然慢。其次检查num_ctx是否设置过高过长的上下文会消耗更多计算资源。对于简单的QA任务2048可能就足够了。模型回复格式不对最常见的原因是TEMPLATE定义错误。务必与模型训练时使用的对话格式完全匹配。可以回顾训练数据集的构建方式和tokenize函数中的prompt模板。6. 全流程回顾与进阶思考走完从ROCm环境搭建、LoRA微调到Ollama部署的整个流程相当于打通了大模型“炼”与“用”的本地化闭环。这个过程里最深的体会是细节决定成败。无论是ROCm驱动版本的选择、LoRA超参数的调整、数据格式的严格对齐还是GGUF转换时的版本兼容性任何一个环节的疏忽都可能导致失败。对于想复现类似流程的朋友我的建议是环境隔离强烈建议使用Conda或Docker创建独立的环境避免系统级依赖冲突。ROCm的安装尤其容易受系统状态影响。小步快跑及时验证不要一次性准备大量数据、设置过长epoch。先用一个极小的数据集比如50条跑通1个epoch验证从训练到推理的整个流程是否正常。然后再逐步增加数据量、调整参数。善用监控工具训练时用rocm-smi监控GPU状态用Tensorboard或WB看损失曲线部署时关注Ollama的日志和系统资源占用。理解原理而非死记命令明白LoRA为什么能省显存理解对话模板的作用知道量化会带来什么影响。这样当遇到问题时你才有思路去排查而不是盲目搜索错误信息。这次实践也让我思考下一步的优化方向。例如尝试QLoRA量化版的LoRA能否在保持性能的同时进一步降低显存需求如何构建更高质量、多样化的指令微调数据集来提升模型泛化能力如何将Ollama服务通过更友好的Web UI如OpenAI WebUI或Chatbot UI暴露出来提供更好的用户体验这些都是可以在现有基础上深入探索的课题。本地大模型应用的生态还在快速演进但亲手搭建并调优一个专属模型所带来的掌控感和定制能力是使用云端API无法比拟的。
返回列表