Llama-3 本地部署与微调实战

本地部署大模型不仅能保障数据隐私,还能降低调用成本;而微调则能让通用大模型适配特定场景(如行业问答、风格化创作)。本文以 Meta 的 Llama-3 为例,完成本地部署与 Lora 微调全流程。

大模型与传统神经网络

大模型为何能处理复杂任务(如长文本理解、多轮对话),其与传统神经网络的核心差异 —— 架构革新

传统神经网络

传统神经网络(如 CNN、RNN)在处理序列数据时存在明显短板:

  • CNN(卷积神经网络):通过卷积核 “局部感知”,擅长图像等结构化数据,但对长文本(如 1000 字以上的文章)的 “全局关联” 捕捉能力弱(需多次卷积叠加,易丢失长距离信息);
  • RNN(循环神经网络):虽能按 “时序顺序” 处理序列(如文本),但存在 “梯度消失 / 爆炸” 问题 —— 处理长序列(如 500token 以上)时,早期信息会被稀释,且无法并行计算(前一时刻输出需作为后一时刻输入),效率极低。

Transformer 架构:大模型的 “发动机”

2017 年,Google 团队在《Attention Is All You Need》中提出的Transformer 架构,彻底突破了上述瓶颈。其核心是 “自注意力机制”,让模型能 “全局感知” 序列中每个元素的关联,且支持并行计算。

图1 Transformer架构

自注意力机制(Self-Attention)

传统 RNN 按 “时序” 逐个处理 token,而自注意力机制可同时计算每个 token 与其他所有 token 的 “关联权重”。例如处理 “小明喜欢吃苹果,他每天都买” 时,模型能通过权重计算直接关联 “他” 与 “小明”,无需依赖时序顺序。
计算公式中,通过 Query(查询)、Key(键)、Value(值)矩阵计算注意力权重:

\(Attention(Q,K,V) = softmax(\frac{QK^T}{\sqrt{d_k}})V\)

\(d_k\)为 Key 的维度,避免权重矩阵过大导致梯度不稳定

Encoder-Decoder 结构

Transformer 由 “Encoder(编码器)” 和 “Decoder(解码器)” 组成:

  • Encoder:负责 “理解输入”(如将文本转换为语义向量),通过多层自注意力 + 前馈网络,捕捉输入序列的全局特征;
  • Decoder:负责 “生成输出”(如根据输入生成回答),除自注意力外,还通过 “交叉注意力” 关联 Encoder 的输出,确保生成内容与输入对齐。

并行计算能力

自注意力机制无需依赖时序,可同时处理所有 token,配合 GPU 的并行计算能力,让模型能高效训练 “亿级 / 千亿级参数”—— 这也是大模型能实现 “复杂语义理解” 的基础。

Mata开源大模型Llama-3

Llama-3 是 Meta 在 2024 年推出的开源大模型系列,目前已发布 8B(80 亿参数)、70B(700 亿参数)等版本,凭借 “性能强、适配性高” 成为本地部署的热门选择。相比 Llama-2,训练数据量提升 40%,涵盖多语言(支持 26 种语言)、多领域(代码、科学文献等),基础语义理解能力更强;并且微调了 Transformer 的注意力层(如调整 q/k/v 投影矩阵维度),在相同参数下,推理速度提升 15%;

Instruct 版本(如 Meta-Llama-3-8B-Instruct)是在基础版上经过 “指令微调(SFT)” 的版本 —— 通过大量 “人类指令 – 回答” 数据(如 “用户问 XX,模型应答 XX”)训练,让模型能精准理解人类意图。

本地部署

硬件选择

大模型部署的核心限制是 “显存”—— 模型加载时需将参数、梯度等数据存入 GPU 显存。以 8B-Instruct 版本为例:

  • 参数规模:80 亿参数,若按 “半精度(float16)” 存储(每个参数占 2 字节),仅参数就需:8e9 × 2 字节 = 16e9 字节 ≈ 16GB 显存;
  • 额外开销:推理时还需存储输入 token、中间计算结果等,约需 4-6GB 显存;
  • 总需求:16+6=22GB 显存。

4090 单卡显存为 24GB,刚好覆盖需求;若用 3090(24GB 显存)也可,但 4090 的 CUDA 核心更多,推理速度更快(相同输入下,生成 100token 的时间比 3090 少 20%)。若显存不足(如 3060 12GB),可通过 “4bit 量化” 将显存需求压至 8-10GB(后续微调部分会讲)。

图2 服务器内存
图3 服务器显卡GeForce RTX 4090

环境配置

目前算力平台基本提供了基础镜像的安装,还是很方便的。如autodl提供基础镜像:

图4 镜像配置
  • 系统:Ubuntu 22.04
  • Python:3.10(大模型工具链兼容性最佳)
  • PyTorch:2.1.2(支持 Flash Attention,加速推理)
  • CUDA:11.8(4090 需 CUDA 11.7+,11.8 稳定性更优)

一些算力平台也默认提供conda工具,自己安装conda也比较简单

# 根目录
cd ~
# 获取conda安装包
wget https://repo.anaconda.com/archive/Anaconda3-2024.10-1-Linux-x86_64.sh
# 执行权限
chmod +x Anaconda3-2024.10-1-Linux-x86_64.sh
# 执行安装
./Anaconda3-2024.10-1-Linux-x86_64.sh
# 安装完成后,使环境变量生效
source .bashrc

# 创建conda环境
conda create -n llama3 python=3.10 -y
conda activate llama3

# 安装PyTorch(需匹配CUDA 11.8)
pip3 install torch==2.1.2 torchvision==0.16.2 torchaudio==2.1.2 --index-url https://download.pytorch.org/whl/cu118

# 安装模型加载与部署依赖
pip install transformers==4.38.2 accelerate==0.27.2 modelscope==1.9.5  # modelscope用于下载模型
pip install sentencepiece  # Llama-3分词器依赖

模型下载:从魔搭获取 Llama-3

Llama-3 需通过官方渠道获取授权,但国内可通过 “魔搭社区(ModelScope)” 下载适配后的 Instruct 版本。

from modelscope import snapshot_download

# 下载模型到本地(cache_dir为下载路径)
model_dir = snapshot_download(
    'LLM-Research/Meta-Llama-3-8B-Instruct', 
    cache_dir='/root/autodl-tmp'
)
print(f"模型下载完成,路径:{model_dir}")

图5 模型下载

下载完成后,模型文件(含权重、分词器配置等)会保存在/root/autodl-tmp/LLM-Research/Meta-Llama-3-8B-Instruct

部署测试:“who are you?”

从本地加载模型,测试基础对话功能:

from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline

# 模型路径
model_path = "/root/autodl-tmp/LLM-Research/Meta-Llama-3-8B-Instruct"

# 加载分词器
tokenizer = AutoTokenizer.from_pretrained(model_path)
tokenizer.pad_token = tokenizer.eos_token  # 设置pad token(Llama默认无pad token)

# 加载模型
model = AutoModelForCausalLM.from_pretrained(
    model_path,
    device_map="auto",
    torch_dtype="auto",
    load_in_4bit=False,  # 不启用4bit量化
    trust_remote_code=True
)

# 创建对话管道
chat_pipeline = pipeline(
    "text-generation",
    model=model,
    tokenizer=tokenizer,
    max_new_tokens=512  # 生成文本最大长度
)

# 测试对话
user_input = "who are you?"
messages = [{"role": "user", "content": user_input}]
response = chat_pipeline(messages)
output = response[0]["generated_text"][-1]  # 提取模型回答

print("-"*50)
print(f"用户查询:{user_input}")
print(f"Llama回答:\n{output}")
print("-"*50)

若能正常输出,说明部署成功!模型输出:

图6 who are you?

Lora 微调:让 Llama-3 “学梗”

部署只是第一步,微调能让模型 “个性化”—— 比如让 Llama-3 学会 “弱智吧” 的幽默回答风格(如 “为什么水开了会冒泡?因为它在学可乐生气”)。本文采用Lora 微调(低秩适应),无需全量训练模型,显存占用低、效果好。

微调基础

什么是 SFT ?

SFT(Supervised Fine-Tuning,有监督微调)是大模型对齐人类意图的核心手段:通过 “标注数据”(如 “用户问 XX,理想回答是 XX”)训练模型,让其输出更符合需求。例如用 “弱智吧” 的 “问题 – 搞笑回答” 数据微调,模型就能学会类似风格。

什么是 Lora ?

全量微调需更新模型所有参数(8B 模型约 80 亿参数),显存需求极高(8B 全量微调需至少 40GB 显存)。而Lora 微调通过 “冻结预训练模型权重,仅训练低秩矩阵” 大幅降低成本:

  • 原理:在 Transformer 的注意力层(如 q_proj、v_proj)中,给权重矩阵W添加低秩矩阵\(W_A\)(输入投影)和\(W_B\)(输出投影),训练时仅更新\(W_A\)和\(W_B\)(秩r通常设为 8-32,远小于原矩阵维度);
  • 优势:显存占用仅为全量微调的 1/10(8B 模型 Lora 微调需 8-12GB 显存),且微调后可导出小体积的 Lora 权重(仅几十 MB),方便部署。

实战:用 “弱智吧” 数据集微调 Llama-3

数据集下载

“弱智吧” 数据集包含大量搞笑问答,可以用来微调,让Llama变弱智幽默诙谐。魔搭上有相关数据集可以下载:

from modelscope.msdatasets import MsDataset

# 下载数据集
ds = MsDataset.load('nullskymc/ruozhiba_R1', subset_name='default', split='train')

图7 数据集下载

数据集遵循以下格式:

{
    "instruction": "······",
    "input": "······",
    "output": "······"
}

Lora微调

原本计划用纯 Lora 微调,但 8B 模型即使冻结主权重,加载时仍需约 16GB 显存(float16),加上训练时的梯度、优化器状态,24GB 显存会 “卡满”。因此改用QLoRA(量化 Lora):先将模型量化为 4bit(显存压至 4-5GB),再叠加 Lora,24GB 显存轻松运行。

import torch
from datasets import load_dataset
from trl import SFTTrainer
from peft import LoraConfig, get_peft_model
from transformers import (
    AutoModelForCausalLM,
    AutoTokenizer,
    TrainingArguments,
    BitsAndBytesConfig
)

# 路径配置
model_path = "/root/autodl-tmp/LLM-Research/Meta-Llama-3-8B-Instruct"  # 模型路径
data_path = "/path_to_your_dataset"  # 数据集路径

# 1. 加载数据集
dataset = load_dataset("json", data_files=data_path)["train"]

# 2. 加载分词器
tokenizer = AutoTokenizer.from_pretrained(model_path)
tokenizer.pad_token = tokenizer.eos_token  # 设置pad token

# 3. 4bit量化配置(核心:降低显存占用)
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,  # 4bit加载模型
    bnb_4bit_use_double_quant=True,  # 双量化(进一步降低误差)
    bnb_4bit_quant_type="nf4",  # 量化类型(nf4比fp4更适合大模型)
    bnb_4bit_compute_dtype=torch.bfloat16  # 计算时用bfloat16(精度更高)
)

# 4. 加载模型
model = AutoModelForCausalLM.from_pretrained(
    model_path,
    device_map="auto",
    quantization_config=bnb_config,
    torch_dtype=torch.bfloat16,
    trust_remote_code=True
)

# 5. Lora参数配置(核心:决定微调效果)
lora_config = LoraConfig(
    r=16,  # 秩(r越大,微调能力越强,但显存占用越高,建议8-32)
    lora_alpha=32,  # 缩放因子(alpha/r决定学习率缩放)
    target_modules=["q_proj", "v_proj", "k_proj", "o_proj"],  # Llama-3的注意力层(需对应模型结构)
    lora_dropout=0.05,  # dropout防止过拟合
    bias="none",  # 不训练偏置项
    task_type="CAUSAL_LM"  # 因果语言模型任务
)
model = get_peft_model(model, lora_config)  # 给模型添加Lora适配器

# 6. 确认可训练参数(仅Lora相关参数)
model.print_trainable_parameters()  # 输出:可训练参数占比(通常<1%)

# 7. 训练参数配置
training_args = TrainingArguments(
    output_dir="./llama3-lora-ruozhiba",  # 输出路径
    per_device_train_batch_size=1,  # 单设备batch size(显存小就设1)
    gradient_accumulation_steps=8,  # 梯度累积(等效batch size=8)
    learning_rate=2e-4,  # 学习率(Lora微调建议1e-4~3e-4)
    num_train_epochs=3,  # 训练轮次(数据集小就多训几轮)
    logging_steps=10,  # 每10步打一次日志
    save_strategy="epoch",  # 每轮保存一次模型
    bf16=True,  # 用bfloat16加速训练
    optim="paged_adamw_8bit",  # 8bit优化器(进一步降显存)
    report_to="none",  # 不启用wandb等日志工具
)

# 8. 初始化SFT Trainer
trainer = SFTTrainer(
    model=model,
    train_dataset=dataset,
    dataset_text_field="instruction",  # 数据集文本字段(按实际数据调整)
    max_seq_length=768,  # 最大序列长度(避免显存溢出)
    tokenizer=tokenizer,
    args=training_args,
    packing=False  # 不启用数据打包(简化流程)
)

# 9. 开始训练
torch.cuda.empty_cache()  # 清理显存
trainer.train()

# 10. 保存Lora权重
model.save_pretrained("./llama3-lora-ruozhiba-final")

关键点:

  • r=16:Lora 的秩,若想增强微调效果可增至 32(显存增加约 2GB);
  • gradient_accumulation_steps=8:小 batch size + 梯度累积,平衡显存与训练效率;
  • 4bit量化:是本代码能在 4090 上运行的核心,若用 3090 等 24GB 卡,此配置必加。

Llama学会玩梗了吗?

加载基础模型 + Lora 权重,测试:

import torch
from peft import PeftModel
from transformers import (
    AutoModelForCausalLM,
    AutoTokenizer,
    GenerationConfig
)

# 路径配置
base_model_path = "/root/autodl-tmp/LLM-Research/Meta-Llama-3-8B-Instruct"
lora_path = "./llama3-lora-ruozhiba-final"  # 微调后的Lora权重路径

# 1. 加载基础模型和分词器
tokenizer = AutoTokenizer.from_pretrained(base_model_path)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"

base_model = AutoModelForCausalLM.from_pretrained(
    base_model_path,
    device_map="auto",
    torch_dtype=torch.bfloat16,
    trust_remote_code=True
)

# 2. 加载Lora权重(合并到基础模型)
model = PeftModel.from_pretrained(base_model, lora_path, device_map="auto")
model.eval()  # 切换到推理模式

# 3. 定义生成函数(按Llama-3格式构造输入)
def generate(query: str) -> str:
    # 构造输入格式(与微调时一致,否则效果差)
    prompt = f"""<|begin_of_text|>
<|start_header_id|>user<|end_header_id|>

{query}

<|start_header_id|>assistant<|end_header_id|>

"""
    inputs = tokenizer(prompt, return_tensors="pt", padding=True, truncation=True, max_length=1024).to("cuda")
    
    # 生成配置
    generation_config = GenerationConfig(
        max_new_tokens=200,
        temperature=0.7,  # 随机性(0.7适合幽默风格)
        top_p=0.9,
        do_sample=True,
        repetition_penalty=1.1  # 避免重复
    )
    
    # 生成回答
    with torch.no_grad():
        outputs = model.generate(** inputs, generation_config=generation_config)
    
    # 提取回答(过滤输入部分)
    full_output = tokenizer.decode(outputs[0], skip_special_tokens=False)
    response = full_output.split("<|start_header_id|>assistant<|end_header_id|>\n\n")[-1]
    return response.replace("<|end_of_text|>", "").strip()

# 4. 测试
test_queries = [
    "得了灰指甲,一个传染俩,问我怎么办?马上用指甲油"
]

for query in test_queries:
    print(f"用户查询:{query}")
    print(f"Llama回答:{generate(query)}\n" + "-"*50)

模型生成对比

  • 微调前:
图8 微调前输出
  • 微调后:
图9 微调后输出

参考链接

  1. PEFT(Lora 工具)文档:https://huggingface.co/docs/peft/index
  2. TRL(SFT Trainer)文档:https://huggingface.co/docs/trl/index
  3. Meta Llama-3 官方文档:https://ai.meta.com/resources/models-and-libraries/llama-downloads/

发表评论

滚动至顶部