Introducing Gemma 3 270M: The compact model for hyper-efficient AI
Introducing Gemma 3 270M: The compact model for hyper-efficient AI
https://developers.googleblog.com/en/introducing-gemma-3-270m/
https://zhuanlan.zhihu.com/p/1985797288883405826
https://zhuanlan.zhihu.com/p/1939694335269507103
trl + unsloth
https://github.com/thomas-chong/fine-tuning-gemma-with-unsloth/tree/main
🚀 谷歌推出 Gemma 3 270M:超高效 AI 模型
2025年8月14日,谷歌在其开发者博客上正式发布了 Gemma 3 270M。这是一款专为特定任务微调而设计的紧凑型 AI 模型,旨在提供强大的指令遵循能力和极高的能效。
🌟 核心亮点
- 小巧而强大:模型仅包含 2.7亿 参数,但具备出色的指令遵循和文本结构化能力。它拥有 25.6 万的大词表,能更好地处理特定和罕见词汇,是进行特定领域和语言微调的坚实基础。
- 极致能效:能效表现突出。在 Pixel 9 Pro 芯片上的内部测试显示,INT4 量化模型进行 25 次对话仅消耗 0.75% 的电量,是 Gemma 系列中能效最高的模型。
- 生产就绪:提供 量化感知训练 (QAT) 的检查点,支持以 INT4 精度运行,在性能损失极小的情况下,非常适合在资源受限的设备上部署。
🛠️ 适用场景
Gemma 3 270M 遵循“合适的工具做合适的事”的理念,特别适合以下场景:
- 高容量、定义明确的任务:如情感分析、实体提取、文本分类、数据提取和合规性检查等。
- 对成本和延迟敏感的应用:可大幅降低推理成本,实现快速响应,甚至可在轻量级设备或本地运行。
- 需要快速迭代和部署:模型体积小,微调实验速度快,可在数小时内找到最佳配置。
- 注重用户隐私:支持完全在设备端运行,无需将敏感数据发送到云端。
- 构建专用模型集群:可以低成本构建和部署多个针对不同任务优化的专用小模型。
🚀 快速上手
开发者可以通过多种途径获取和使用 Gemma 3 270M:
- 下载模型:可从 Hugging Face、Ollama、Kaggle 等平台下载预训练和指令微调版本。
- 尝试模型:支持在 Vertex AI 以及 llama.cpp、Gemma.cpp、LiteRT 等多种推理工具上运行。
- 开始微调:可使用 Hugging Face、UnSloth、JAX 等工具进行微调。
- 部署方案:微调后的模型可部署在本地环境或 Google Cloud Run 等平台上。
https://unsloth.ai/docs/get-started/fine-tuning-llms-guide
这篇 Unsloth 的 LLM 微调指南是一份面向初学者的教程,介绍了使用其工具高效微调大语言模型(LLM)的完整流程。
以下是该指南的核心内容总结:
🧠 什么是微调 (Fine-tuning)
微调是在预训练模型的基础上,通过特定数据集进行再训练,以定制模型行为、注入新知识并优化其在特定任务上的表现。
- 主要方法:监督式微调 (SFT) 是最常用的方法。此外还包括基于人类反馈的强化学习 (RLHF),如 DPO、ORPO、GRPO 等。
- 核心优势:微调可以更新模型知识、定制其语气和风格,并优化特定任务的准确性。与 RAG(检索增强生成)不同,微调直接改变模型权重,使其成为更专业的“智能体”。
- LoRA/QLoRA:这是两种高效的微调技术。它们不改变模型的全部权重,而是通过训练少量新增的“适配器”参数来实现。QLoRA 在 LoRA 的基础上引入了 4-bit 量化,能大幅节省显存,让在资源有限的设备上微调大模型成为可能。
🚀 微调的 8 个步骤
指南将微调过程分解为 8 个清晰的步骤:
- 理解微调:了解其基本概念、优势以及与 RAG 的区别。
- 选择模型与方法:
- 模型:建议初学者从较小的指令模型(如 Llama 3.1 8B)开始。
- 方法:推荐从 QLoRA 开始,因为它资源消耗低且效果出色。LoRA 是另一种高效选择,而全参数微调 (FFT) 计算成本高昂,通常不必要。
- 准备数据集:数据质量至关重要。通常需要准备问答(QA)对格式的数据。可以使用工具合成数据,并对数据进行清洗和结构化处理。
- 理解超参数:学习如何设置影响训练效果的关键参数,如学习率 (
learning_rate)、批次大小 (batch_size) 和训练步数 (max_steps)。 - 安装与环境:可以通过 Unsloth 提供的免费 Notebook(如 Colab、Kaggle)快速上手,也可以在本地通过
pip或 Docker 安装。 - 训练与评估:
- 训练:运行训练脚本,并观察训练损失(Training Loss)。通常损失值在 0.5-1.0 之间是较好的迹象。
- 评估:可以通过与模型对话进行手动评估,或设置验证集进行自动评估。
- 运行与部署:
- 推理:训练完成后,可以直接加载模型进行对话测试。
- 保存:模型可以保存为小巧的 LoRA 适配器文件(约 100MB),也可以合并后保存为完整模型。
- 部署:提供了多种部署方案,如转换为 GGUF 格式用于
llama.cpp或Ollama,或使用vLLM进行高性能服务部署。
- 完成:成功完成模型的微调与部署。
此外,Unsloth 还推出了一个名为 Unsloth Studio 的开源 Web UI,允许用户通过图形界面进行无代码的模型微调和监控。
https://zhubert.com/intro-to-transformers/fine-tuning-a-transformer/sft-introduction/
这篇网页主要介绍了监督微调(Supervised Fine-Tuning, SFT),这是让大模型学会“听懂人话”并遵循指令的关键步骤。
简单来说,如果把预训练(Pre-training)比作让模型“博览群书”来学习语言和知识,那么 SFT 就是给模型上“行为训练课”,教它如何根据指令给出恰当的回复。
以下是该网页内容的思维导图:
以下是详细总结:
🎯 一、核心概念与数学原理
1. 什么是监督微调(SFT)
SFT 的核心在于“监督学习”,即向模型展示正确的输入 - 输出对(类似于使用抽认卡教学)。其目的是通过示例让模型学会如何回答问题或编写代码。
2. 数学公式
- :指令或提示(如“法国的首都是哪里?”)。
- :完整的回复(如“法国的首都是巴黎。”)。
- 目标:最小化负对数概率,即最大化模型预测正确 token 的概率。
⚖️ 二、SFT 与预训练的区别
虽然两者都是训练过程,但存在本质差异:
| 方面 | 预训练 (Pre-Training) | 监督微调 (SFT) |
|---|---|---|
| 数据 | 来自各处的原始文本(书籍、网站等) | 精心策划的(指令,回复)对 |
| 目标 | 预测任何文本中的下一个 token | 生成对指令有帮助的回复 |
| 规模 | 万亿级 token | 数千到数百万个样本 |
| 成本/时长 | 昂贵(数周/月,巨大 GPU 集群) | 相对便宜(数小时/天,单 GPU) |
| 学习率 | 较高(约 1e-4) | 较低(1e-5 到 3e-4) |
| 本质 | 教模型语言(词汇、语法、事实) | 教模型行为(如何回应、格式化) |
📚 三、主流 SFT 数据集
网页介绍了三个推动开源指令跟随运动的数据集:
- Alpaca (Stanford, 2023):包含 52,000 条指令。由 GPT-3.5 基于种子示例生成。虽然有时略显公式化,但成本低(Stanford 用不到 600 美元训练了 LLaMA 模型)。
- Dolly (Databricks, 2023):包含 15,000 条指令。由 Databricks 员工人工编写,质量更高、更自然,但规模较小。
- OpenAssistant (LAION, 2023):包含 161,000 条消息。通过网页界面由社区贡献,包含多轮对话和人类质量评分。
🛠️ 四、SFT 训练循环步骤
实施 SFT 的具体流程如下:
- 加载预训练模型:使用已有的模型(如 GPT-2, LLaMA),无需从头训练。
- 格式化指令数据:使用聊天模板(Chat Template)添加特殊 token(如
<|user|>)将数据转换为模型可理解的格式。 - Token 化:将文本转换为数字(token IDs)。
- 应用 Loss Masking(关键技巧):仅计算回复部分 token 的损失,忽略指令部分的 token。这样模型只学习如何生成好的回复,而不是学习预测指令。
- 训练:使用标准的交叉熵损失进行监督学习。
- 保存模型:获得一个经过微调、能遵循指令的模型。
这篇文章清晰地界定了预训练与微调的边界,特别是 Loss Masking 的引入,解释了为何模型能在保留原有知识的同时,学会特定的交互行为。
https://github.com/huggingface/trl
TRL 是一个由 Hugging Face 维护的开源库,专门用于使用强化学习(RL)技术对 Transformer 语言模型进行后训练(post-training)。
🚀 核心功能
TRL 提供了多种先进的后训练方法,旨在优化基础模型的性能和对齐能力。其主要特点包括:
- 多种训练器 (Trainers): 提供了多种易于使用的训练器,以支持不同的训练策略,例如:
SFTTrainer: 用于监督式微调 (Supervised Fine-Tuning)。GRPOTrainer: 实现了组相对策略优化 (Group Relative Policy Optimization),一种比 PPO 更节省内存的算法。DPOTrainer: 实现了直接偏好优化 (Direct Preference Optimization),一种流行的对齐算法。KTOTrainer: 实现了卡尼曼-特沃斯基优化 (Kahneman-Tversky Optimization),用于处理二元反馈。RewardTrainer: 用于训练奖励模型。
- 高效与可扩展:
- 与 🤗 Accelerate 集成,支持从单 GPU 到多节点集群的分布式训练(如 DDP, DeepSpeed)。
- 与 🤗 PEFT 完全集成,支持通过量化和 LoRA/QLoRA 等技术,在有限的硬件资源上训练大型模型。
- 集成了 🦥 Unsloth 库,通过优化的内核加速训练过程。
- 命令行界面 (CLI): 提供了一个简单的命令行工具,无需编写代码即可快速开始模型的微调。
🛠️ 快速开始
你可以通过 pip 安装 TRL,并使用其提供的训练器快速上手。
安装
pip install trl
使用示例
以下是一个使用 SFTTrainer 进行监督式微调的简单代码示例:
from trl import SFTTrainer
from datasets import load_dataset
# 加载数据集
dataset = load_dataset("trl-lib/Capybara", split="train")
# 初始化并启动训练
trainer = SFTTrainer(
model="Qwen/Qwen2.5-0.5B",
train_dataset=dataset,
)
trainer.train()
该库采用 Apache-2.0 许可证,并拥有活跃的社区和持续的更新。
https://github.com/fanqingsong/fine-tuning-gemma-with-unsloth/blob/master/src/train.py
#!/usr/bin/env python3 """ 使用 Unsloth + LoRA 微调 Gemma 3 270M(逻辑来自 live_demo.ipynb)。 微调流程概览(建议按 main() 中的顺序阅读): 1. 加载基座模型与分词器(可选 4bit 量化以省显存) 2. 加载并清洗训练数据,统一成 Gemma 对话格式 3. 用 chat template 把对话转成模型看到的「一整段文本」 4. 在注意力/MLP 等层挂上 LoRA 适配器(只训练少量新增参数) 5. SFT(监督微调):用 SFTTrainer 在文本上做 next-token 预测 6. train_on_responses_only:只在 assistant 回复部分计算 loss(不训练用户问题) 7. 保存 LoRA 权重;可选合并 LoRA 到完整模型便于部署 """ from __future__ import annotations import argparse import os import sys from pathlib import Path # 保证同目录下的 hf_setup 可被导入 sys.path.insert(0, str(Path(__file__).resolve().parent)) # 必须在 unsloth / transformers / trl 之前:清理空的 HF 环境变量,避免下载失败 import hf_setup # noqa: E402 — sanitize HF env before unsloth # unsloth 会 patch 底层库以加速训练;导入顺序有要求,故放在 trl 之前 import unsloth # noqa: F401,E402 — must precede trl / transformers / peft from datasets import Dataset, load_dataset # noqa: E402 from trl import SFTConfig, SFTTrainer # noqa: E402 from unsloth import FastModel # noqa: E402 from unsloth.chat_templates import ( # noqa: E402 get_chat_template, train_on_responses_only, ) # 系统提示:定义模型在对话中的「角色」,会写入每条训练样本 SYSTEM_PROMPT = ( "You are a master storyteller. Write a short, imaginative story based on " "the user's request. The story should be concise and suitable for a general audience." ) # 默认 Hugging Face 上的示例数据集(也可换成本地 jsonl,见 --dataset) PREGENERATED_DATASET_NAME = "chongcht/synthetic-creative-writing" def parse_args() -> argparse.Namespace: """命令行超参数:模型、数据、LoRA、优化器与训练步数等。""" parser = argparse.ArgumentParser(description="Fine-tune Gemma 3 270M with Unsloth") parser.add_argument( "--model-name", default=hf_setup.model_name_from_env(), help="Base model on Hugging Face", ) parser.add_argument( "--dataset", default=PREGENERATED_DATASET_NAME, help="HF dataset id or local json/jsonl path", ) parser.add_argument("--dataset-split", default="train") # 单条样本最大 token 数;过长会被截断,过短浪费算力 parser.add_argument("--max-seq-length", type=int, default=2048) parser.add_argument( "--load-in-4bit", action=argparse.BooleanOptionalAction, default=False, help="270M fits in memory without 4-bit; notebook trains in full precision", ) # LoRA 秩 r:越大可表达能力越强,但可训练参数和显存也更多 parser.add_argument("--lora-r", type=int, default=16) parser.add_argument( "--lora-alpha", type=int, default=None, help="Defaults to 2 * lora-r (notebook heuristic)", ) parser.add_argument("--lora-dropout", type=float, default=0.0) parser.add_argument("--per-device-train-batch-size", type=int, default=8) # 梯度累积:有效 batch = batch_size × accumulation_steps(显存不够时增大此项) parser.add_argument("--gradient-accumulation-steps", type=int, default=2) parser.add_argument("--learning-rate", type=float, default=2e-4) parser.add_argument("--warmup-steps", type=int, default=10) # 固定训练步数;若指定 num-train-epochs 则按 epoch 跑满整个数据集 parser.add_argument("--max-steps", type=int, default=100) parser.add_argument( "--num-train-epochs", type=float, default=None, help="If set, overrides --max-steps for a full epoch run", ) parser.add_argument("--logging-steps", type=int, default=1) parser.add_argument("--seed", type=int, default=3407) parser.add_argument("--output-dir", default="outputs/lora") # 合并 LoRA 到基座权重,得到单目录完整模型(体积大,推理更简单) parser.add_argument("--save-merged", action="store_true") parser.add_argument("--merged-dir", default="outputs/merged") parser.add_argument("--max-samples", type=int, default=None) return parser.parse_args() def load_training_dataset(path_or_id: str, split: str) -> Dataset: """ 加载训练集:本地 .json/.jsonl,或 Hugging Face dataset id。 返回 Hugging Face Dataset,后续用 .map / .filter 做预处理。 """ local = Path(path_or_id) if local.exists(): suffix = local.suffix.lower() if suffix in {".jsonl", ".json"}: return load_dataset("json", data_files=str(local), split="train") return load_dataset(str(local), split=split) return load_dataset(path_or_id, split=split) def format_for_gemma(example: dict) -> dict: """ 把各种常见字段名统一成 Gemma3 需要的 conversations 列表: system / user / assistant 三轮消息。 若缺少 prompt 或 response,返回 conversations=None,后续会被 filter 掉。 """ prompt_text = example.get("prompt") or "" response_text = example.get("response") or "" # ShareGPT 风格:conversations 里 role 为 user / assistant if not prompt_text and "conversations" in example: for message in example["conversations"]: if message["role"] == "user": prompt_text = message["content"] elif message["role"] in {"assistant", "model"}: response_text = message["content"] # OpenAI 风格:messages 数组 if not prompt_text and "messages" in example: for message in example["messages"]: if message["role"] == "user": prompt_text = message["content"] elif message["role"] in {"assistant", "model"}: response_text = message["content"] # Alpaca 风格:instruction + input -> 用户侧;output -> 模型要学的回复 if not prompt_text: instruction = example.get("instruction") or example.get("question") or "" extra = example.get("input") or "" prompt_text = instruction if not extra else f"{instruction}\n{extra}".strip() response_text = ( example.get("output") or example.get("answer") or response_text ) if not prompt_text or not response_text: return {"conversations": None} return { "conversations": [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": prompt_text}, {"role": "assistant", "content": response_text}, ] } def has_error_response(example: dict) -> bool: """过滤合成数据里标记为 error 的坏样本(response 字符串含 error 则丢弃)。""" response = example.get("response") if isinstance(response, str): return "error" not in response.lower() return True def main() -> None: args = parse_args() token = os.environ.get("HF_TOKEN") or None # LoRA 缩放系数,常用经验:alpha = 2 * r lora_alpha = args.lora_alpha if args.lora_alpha is not None else args.lora_r * 2 # ---------- 1. 加载基座模型 ---------- # full_finetuning=False:后面只加 LoRA,不更新全部 270M 参数 model, tokenizer = hf_setup.load_fast_model( FastModel, args.model_name, max_seq_length=args.max_seq_length, load_in_4bit=args.load_in_4bit, load_in_8bit=False, full_finetuning=False, token=token, ) # ---------- 2. 数据集:加载 -> 清洗 -> 统一格式 ---------- dataset = load_training_dataset(args.dataset, args.dataset_split) if "response" in dataset.column_names: dataset = dataset.filter(has_error_response) dataset = dataset.map(format_for_gemma, remove_columns=dataset.column_names) dataset = dataset.filter(lambda example: example.get("conversations") is not None) if args.max_samples: dataset = dataset.select(range(min(args.max_samples, len(dataset)))) # Gemma3 专用 chat template(控制 <start_of_turn> 等特殊 token) tokenizer = get_chat_template(tokenizer, chat_template="gemma3") def formatting_prompts_func(examples): """ SFT 需要一列纯文本 `text`:把多轮对话渲染成模型训练时看到的字符串。 add_generation_prompt=False:assistant 回复已在模板里,用于 teacher forcing。 """ texts = [ tokenizer.apply_chat_template( convo, tokenize=False, add_generation_prompt=False, ).removeprefix("<bos>") for convo in examples["conversations"] ] return {"text": texts} dataset = dataset.map(formatting_prompts_func, batched=True) # ---------- 3. LoRA:在指定线性层旁路低秩矩阵,只训练这些增量权重 ---------- model = FastModel.get_peft_model( model, r=args.lora_r, lora_alpha=lora_alpha, target_modules=[ "q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj", ], lora_dropout=args.lora_dropout, bias="none", use_gradient_checkpointing="unsloth", # 用算力换显存 random_state=args.seed, ) output_dir = Path(args.output_dir) output_dir.mkdir(parents=True, exist_ok=True) # ---------- 4. SFT 训练配置 ---------- # 本质:对 text 做因果语言建模,预测下一个 token sft_kwargs = dict( dataset_text_field="text", per_device_train_batch_size=args.per_device_train_batch_size, gradient_accumulation_steps=args.gradient_accumulation_steps, warmup_steps=args.warmup_steps, learning_rate=args.learning_rate, logging_steps=args.logging_steps, optim="adamw_8bit", weight_decay=0.01, lr_scheduler_type="linear", seed=args.seed, output_dir=str(output_dir), report_to="none", ) if args.num_train_epochs is not None: sft_kwargs["num_train_epochs"] = args.num_train_epochs sft_kwargs["max_steps"] = -1 else: sft_kwargs["max_steps"] = args.max_steps trainer = SFTTrainer( model=model, tokenizer=tokenizer, train_dataset=dataset, args=SFTConfig(**sft_kwargs), ) # ---------- 5. 只在 model 回复段算 loss ---------- # 用户/系统 turn 的 token 不参与 loss,避免模型「学会复读问题」 trainer = train_on_responses_only( trainer, instruction_part="<start_of_turn>user\n", response_part="<start_of_turn>model\n", ) trainer.train() # LoRA adapter + tokenizer 配置(推理时用同一 base model + 加载此目录) model.save_pretrained(str(output_dir)) tokenizer.save_pretrained(str(output_dir)) print(f"Saved LoRA adapter to {output_dir}") if args.save_merged: merged_dir = Path(args.merged_dir) merged_dir.mkdir(parents=True, exist_ok=True) model.save_pretrained_merged(str(merged_dir), tokenizer) print(f"Saved merged model to {merged_dir}") if __name__ == "__main__": main()

浙公网安备 33010602011771号