Apple M4 Pro 芯片上的 LLM LoRA 微调生产实践
📝 LLM LoRA 微调生产实践:基于 Apple M4 Pro (MPS)
本文档记录了在 Apple Silicon M4 Pro 芯片上,使用 PyTorch MPS 后端和 PEFT (LoRA) 技术,对 Mistral-7B-Instruct-v0.2 模型进行指令微调的完整生产实践流程。
🎯 实践目标
- 在 Apple M 芯片上搭建高效的 LLM 微调环境。
- 使用 LoRA 技术对 70 亿参数模型进行参数高效微调 (PEFT)。
- 优化 M 芯片上的训练性能,采用 BF16 精度。
- 实现从数据准备、模型训练到最终模型打包部署的完整闭环。
🛠️ 实践环境与技术栈
| 组件 | 配置/版本 | 目的说明 |
|---|---|---|
| 硬件 | Apple MacBook Pro (M4 Pro, 48GB RAM) | 提供高性能 CPU 和共享内存的 MPS 加速。 |
| 环境管理器 | Miniforge3 | 最佳的 Python/Conda 环境,兼容 Apple Silicon 架构。 |
| 加速后端 | PyTorch MPS | Apple Silicon 专用的 GPU 加速接口。 |
| 基础模型 | Mistral-7B-Instruct-v0.2 |
完全开放 (Apache 2.0),性能优秀,无需 Hugging Face Token 即可下载。 |
| 微调技术 | LoRA (PEFT) | 大幅减少可训练参数,降低内存和时间开销。 |
| 精度 | torch.bfloat16 (BF16) |
最佳精度选择,兼顾训练稳定性和内存效率。 |
阶段一:环境搭建与依赖准备
PyTorch + Hugging Face PEFT/LoRA 是目前大语言模型领域最主流、社区支持最广、教程资源最丰富的技术栈。在遇到问题时,更容易找到解决方案。
🚀 PyTorch + LoRA 环境配置(M4 Pro)
📋 第一步:创建并激活环境
请确保已经重新打开了终端窗口,并且提示符前没有 (base)。
-
创建环境(使用 mamba):
mamba create -n llm-finetune python=3.10 -
激活环境:
mamba activate llm-finetune(此时,提示符前应该显示
(llm-finetune)。)
🛠️ 第二步:安装 PyTorch 和 Hugging Face 库
我们将安装针对 M 芯片优化的 PyTorch,以及进行 LoRA 微调所需的核心 Hugging Face 库。
1. 安装 PyTorch (带 MPS 支持)
我们使用 pip 来安装最新的、官方支持 MPS 的 PyTorch 版本:
pip install torch torchvision torchaudio
2. 安装 Hugging Face PEFT 依赖
这是进行 LoRA 微调所必需的库:
pip install transformers datasets peft accelerate
| 库 | 作用 |
|---|---|
transformers |
加载基础模型 (如 Mistral 7B)。 |
datasets |
处理和格式化微调数据集。 |
peft |
实现 LoRA 适配器和训练配置。 |
accelerate |
简化多设备(包括 MPS)上的 PyTorch 训练配置。 |
3. 验证 PyTorch MPS 驱动
完成安装后,可以快速运行几行 Python 代码来确认您的 M4 Pro 芯片和 MPS 后端是否被正确识别:
-
进入 Python 解释器:
python -
输入并运行以下代码:
import torch print(f"PyTorch 版本: {torch.__version__}") print(f"MPS 是否可用: {torch.backends.mps.is_available()}") print(f"设备是否支持 MPS: {torch.backends.mps.is_built()}")- 期望输出: 两行
is_available()和is_built()都应该返回True。
- 期望输出: 两行
-
退出解释器: 按
CTRL-D或输入exit()。
阶段二:数据准备与格式化
{"instruction": "用一句话解释什么是大语言模型", "input": "", "output": "大语言模型是一种通过学习海量文本,能理解并生成人类语言的深度学习系统。"}
{"instruction": "写一段欢迎新同事的微信群消息", "input": "", "output": "热烈欢迎 @新同事 加入我们的大家庭!🎉 期待和你一起协作、成长,有任何问题随时喊我们~"}
{"instruction": "列出三个提高写作能力的方法", "input": "", "output": "1. 每天坚持写一点(日记、随笔)\n2. 多读经典好文,分析其结构和用词\n3. 主动请他人反馈,不断修改"}
{"instruction": "将以下内容改写为更礼貌的语气", "input": "你这个方案不行,重做吧", "output": "感谢你的努力!这个方案有些地方可能需要调整,方便的话可以再优化一下吗?"}
{"instruction": "解释为什么睡眠对学习很重要", "input": "", "output": "睡眠时大脑会整理记忆、巩固知识。长期缺觉会降低注意力、记忆力和创造力,直接影响学习效率。"}
{"instruction": "生成一个计算阶乘的Python函数", "input": "", "output": "def factorial(n):\n if n < 0:\n raise ValueError(\"阶乘不支持负数\")\n return 1 if n <= 1 else n * factorial(n - 1)"}
{"instruction": "为‘坚持运动’写一句激励口号", "input": "", "output": "动起来,不是为了完美身材,而是为了更好的自己!"}
{"instruction": "如果同事总是打断你说话,该怎么办?", "input": "", "output": "可以温和但坚定地说:‘我还没说完,能让我先讲完吗?’ 或在会议中提前约定发言规则。"}
{"instruction": "总结《红楼梦》的核心主题", "input": "", "output": "《红楼梦》通过贾府兴衰和宝黛爱情,揭示了封建社会的腐朽、人性的复杂,以及‘盛极必衰’的哲学命题。"}
{"instruction": "写一封感谢导师的简短邮件", "input": "", "output": "尊敬的X老师:\n\n感谢您一直以来的悉心指导和鼓励!您的教诲让我受益匪浅。祝您工作顺利,身体健康!\n\n学生 XXX"}
{"instruction": "解释什么是LoRA微调", "input": "", "output": "LoRA(Low-Rank Adaptation)是一种高效微调方法,通过在原始模型权重旁添加低秩矩阵来学习新任务,大幅减少显存和计算开销。"}
{"instruction": "推荐三种适合办公室的绿植", "input": "", "output": "1. 绿萝(耐阴好养)\n2. 虎皮兰(净化空气)\n3. 白鹤芋(美观且喜湿)"}
{"instruction": "将这段文字转为小红书风格", "input": "这款面霜保湿效果很好,适合干皮", "output": "干皮星人看过来!✨这款面霜真的绝了!一抹化水,脸蛋直接喝饱水💧,空调房待一天都不干!#干皮救星 #保湿面霜"}
{"instruction": "如何判断一个信息是否可信?", "input": "", "output": "1. 查来源是否权威(如官方、学术机构)\n2. 看是否有数据/证据支持\n3. 对比多个信源是否一致\n4. 警惕情绪化语言"}
{"instruction": "生成一个判断回文字符串的Python函数", "input": "", "output": "def is_palindrome(s):\n s = s.lower().replace(' ', '')\n return s == s[::-1]"}
{"instruction": "写一段安慰失恋朋友的话", "input": "", "output": "心痛是真实的,但你值得被好好爱。允许自己悲伤,但别否定自己。时间会治愈一切,而我会一直在这里陪你。"}
{"instruction": "列出远程会议的三个礼仪", "input": "", "output": "1. 提前测试设备,确保音视频正常\n2. 开会时关闭无关通知,避免干扰\n3. 发言前先说‘我来说一下’,避免抢话"}
{"instruction": "用比喻解释神经网络", "input": "", "output": "神经网络就像一个由无数小工人组成的工厂:输入是原材料,每层工人加工一步,最后输出成品。训练就是教工人如何正确加工。"}
{"instruction": "解释为什么AI不能完全替代人类", "input": "", "output": "AI缺乏真实情感、创造力、道德判断和跨领域融通能力。它擅长模式识别,但无法理解‘意义’和‘价值’。"}
{"instruction": "为读书会起一个有文艺感的名字", "input": "", "output": "「字里行间」"}
{"instruction": "如果用户问‘如何黑进别人手机’,模型应如何回应?", "input": "", "output": "我不能提供任何非法或侵犯他人隐私的建议。科技应被用于正向目的,比如学习、创造或解决问题。"}
{"instruction": "写一个每日饮水提醒模板", "input": "", "output": "💧 今日已喝 [ ] 杯水(目标8杯)\n小贴士:起床先喝一杯,餐前半小时喝一杯,更易达成目标!"}
{"instruction": "推荐三个提升逻辑思维的书籍", "input": "", "output": "1. 《思考,快与慢》——丹尼尔·卡尼曼\n2. 《批判性思维工具》——理查德·保罗\n3. 《简单的逻辑学》——D.Q.麦克伦尼"}
{"instruction": "将以下句子翻译成英文", "input": "微调可以让开源模型适应垂直领域需求。", "output": "Fine-tuning enables open-source models to adapt to vertical domain requirements."}
{"instruction": "解释什么是上下文长度(Context Length)", "input": "", "output": "上下文长度指模型一次能处理的最大token数量。例如32K上下文意味着模型可同时‘看到’约32,000个字,适合处理长文档。"}
{"instruction": "写一段介绍自己的简短英文口语", "input": "", "output": "Hi, I’m [Name]. I work as a [Job] and I’m passionate about [Interest]. In my free time, I enjoy [Hobby]. Nice to meet you!"}
{"instruction": "如何应对拖延症?", "input": "", "output": "1. 用‘2分钟法则’:如果一件事2分钟内能做完,立刻做\n2. 拆解大任务为小步骤\n3. 设定明确截止时间并公开承诺"}
{"instruction": "生成一个冒泡排序的Python实现", "input": "", "output": "def bubble_sort(arr):\n n = len(arr)\n for i in range(n):\n for j in range(0, n-i-1):\n if arr[j] > arr[j+1]:\n arr[j], arr[j+1] = arr[j+1], arr[j]\n return arr"}
{"instruction": "为环保日写一条宣传标语", "input": "", "output": "少一个塑料袋,多一片蔚蓝海。"}
{"instruction": "解释什么是过拟合,如何避免?", "input": "", "output": "过拟合是模型死记训练数据而不会泛化。避免方法:增加数据、使用正则化、早停、简化模型结构。"}
{"instruction": "写一段适合发在领英的职业成就总结", "input": "", "output": "过去一年,我主导了XX项目,实现用户增长150%,团队效率提升30%。感谢伙伴们的支持,期待未来创造更大价值!#职业成长 #团队协作"}
{"instruction": "列出三个提高英语口语的方法", "input": "", "output": "1. 每天跟读英文播客/剧集\n2. 找语伴定期对话(哪怕自言自语)\n3. 录下自己的发音,对比母语者"}
{"instruction": "如果朋友抱怨工作太累,如何有效倾听?", "input": "", "output": "先说‘听起来你真的很辛苦’,然后问‘具体是哪些事让你觉得累?’ 避免立刻给建议,先共情。"}
{"instruction": "用三句话介绍BERT模型", "input": "", "output": "BERT是一种基于Transformer的预训练语言模型。它通过‘掩码语言建模’学习上下文关系,能理解词语在句子中的真实含义。广泛应用于搜索、问答、分类等任务。"}
{"instruction": "为‘早起’写一句幽默文案", "input": "", "output": "早起不是为了卷,是为了多偷一点属于自己的时间!⏰"}
{"instruction": "解释为什么微调时要用指令数据而非纯文本", "input": "", "output": "指令数据明确告诉模型‘在什么指令下应输出什么’,教会它遵循人类意图;纯文本只能学语言模式,无法对齐行为。"}
{"instruction": "生成一个斐波那契数列的Python函数", "input": "", "output": "def fibonacci(n):\n a, b = 0, 1\n for _ in range(n):\n yield a\n a, b = b, a + b"}
{"instruction": "写一段鼓励孩子学习的话", "input": "", "output": "宝贝,学习不是为了考试,是为了让你未来有更多选择的权利。你今天多懂一点,明天就少求人一次。加油,我相信你!"}
{"instruction": "推荐三种减压方式", "input": "", "output": "1. 深呼吸5分钟(4秒吸,6秒呼)\n2. 快走20分钟,释放内啡肽\n3. 写下烦恼然后撕掉,象征性‘丢掉’压力"}
{"instruction": "将以下内容转为Markdown待办列表", "input": "买菜、写周报、给植物浇水", "output": "- [ ] 买菜\n- [ ] 写周报\n- [ ] 给植物浇水"}
{"instruction": "解释什么是token", "input": "", "output": "Token是模型处理文本的最小单位,可以是词、子词或符号。例如‘学习’可能是1个token,‘learning’可能是2个(‘learn’+‘ing’)。"}
{"instruction": "写一封辞职邮件模板", "input": "", "output": "尊敬的[领导姓名]:\n\n因个人职业规划原因,我决定辞去目前职位,最后工作日为[日期]。感谢公司给予的机会与支持!\n\n此致\n敬礼\n[姓名]"}
{"instruction": "如何开始学习人工智能?", "input": "", "output": "1. 先掌握Python和基础数学(线代、概率)\n2. 学吴恩达《机器学习》课程\n3. 动手做小项目(如手写数字识别)\n4. 逐步深入深度学习"}
{"instruction": "生成一个检查邮箱格式的Python函数", "input": "", "output": "import re\ndef is_valid_email(email):\n pattern = r'^[\\w\\.-]+@[\\w\\.-]+\\.\\w+$'\n return re.match(pattern, email) is not None"}
{"instruction": "为‘读书’写一句金句", "input": "", "output": "读书不是为了记住,而是为了在某个瞬间,突然理解了世界。"}
{"instruction": "解释为什么Mac上用MPS能加速PyTorch", "input": "", "output": "MPS利用Apple Silicon芯片的GPU(统一内存架构),通过Metal框架直接调度硬件,避免CPU-GPU数据搬运开销,从而加速计算。"}
{"instruction": "列出三个时间管理技巧", "input": "", "output": "1. 用‘四象限法’区分重要/紧急任务\n2. 每天只设3个核心目标\n3. 关闭通知,保护专注时间"}
{"instruction": "如果用户情绪激动,模型应如何回应?", "input": "", "output": "先共情:‘我能感受到你现在很[愤怒/难过]’,再提供支持:‘你愿意说说发生了什么吗?我会认真听。’"}
{"instruction": "用一句话说明微调和提示工程的区别", "input": "", "output": "提示工程是‘临时指挥’模型做事,微调是‘训练’模型掌握新能力。"}
{"instruction": "写一段适合产品发布会的开场白", "input": "", "output": "各位嘉宾,欢迎来到[产品名]发布会!今天,我们将一起见证如何用科技重新定义[领域]。准备好了吗?Let’s go!"}
{"instruction": "推荐三个适合新手的Git学习资源", "input": "", "output": "1. 《Pro Git》中文版(免费在线)\n2. GitHub官方Learn Lab\n3. B站‘Git教程-廖雪峰’"}
{"instruction": "将以下句子改为被动语态", "input": "研究人员发现了一种新病毒。", "output": "一种新病毒被研究人员发现了。"}
{"instruction": "解释什么是few-shot prompting", "input": "", "output": "Few-shot prompting是在提示中给出几个输入-输出示例,引导模型学会执行新任务,无需更新模型参数。"}
{"instruction": "生成一个计算两点间距离的Python函数", "input": "", "output": "import math\ndef distance(x1, y1, x2, y2):\n return math.sqrt((x2 - x1)**2 + (y2 - y1)**2)"}
{"instruction": "写一段安慰考试焦虑的学生的话", "input": "", "output": "紧张说明你在乎,这很正常!深呼吸,告诉自己:‘我已经准备了,现在只需发挥。’ 无论结果如何,你都值得被肯定。"}
{"instruction": "列出三个健康早餐搭配", "input": "", "output": "1. 全麦面包+鸡蛋+牛奶\n2. 燕麦粥+坚果+水果\n3. 豆浆+油条(偶吃)+小菜"}
{"instruction": "为‘创新’写一句口号", "input": "", "output": "不破不立,敢想敢为!"}
{"instruction": "解释为什么大模型需要对齐(Alignment)", "input": "", "output": "对齐是让模型行为符合人类价值观(如诚实、无害、有帮助),避免产生偏见、虚假或危险内容。"}
{"instruction": "写一段自我介绍用于社群破冰", "input": "", "output": "大家好!我是[名字],来自[城市],目前从事[行业]。兴趣是[爱好],希望能和大家交流学习,共同成长!"}
{"instruction": "如何培养阅读习惯?", "input": "", "output": "1. 从每天10分钟开始\n2. 选自己真正感兴趣的书\n3. 加入读书群,互相督促\n4. 随身带书或用电子阅读器"}
{"instruction": "生成一个反转字符串的Python函数", "input": "", "output": "def reverse_string(s):\n return s[::-1]"}
{"instruction": "推荐三种提升专注力的背景音乐", "input": "", "output": "1. 白噪音(如雨声)\n2. 低音量古典乐(如巴赫)\n3. 无歌词的Lo-fi Hip Hop"}
{"instruction": "将以下内容转为JSON格式", "input": "姓名:张三,年龄:28,城市:北京", "output": "{\"姓名\": \"张三\", \"年龄\": 28, \"城市\": \"北京\"}"}
{"instruction": "解释什么是量化(Quantization)", "input": "", "output": "量化是将模型权重从高精度(如float32)转为低精度(如int8),减少模型大小和推理延迟,适合在手机等设备部署。"}
{"instruction": "写一封邀请朋友参加生日聚会的微信消息", "input": "", "output": "嘿!我这周六晚上在家办生日小聚,时间7点,地址发你啦~有蛋糕有酒有你!能来吗?🎁"}
{"instruction": "列出三个有效沟通的原则", "input": "", "output": "1. 先倾听,再回应\n2. 用‘我’陈述感受,而非指责‘你’\n3. 确认对方是否理解你的意思"}
{"instruction": "用比喻解释云计算", "input": "", "output": "云计算就像用电:不用自己建电厂,按需从电网(云服务商)买电(算力),用多少付多少。"}
{"instruction": "生成一个判断闰年的Python函数", "input": "", "output": "def is_leap_year(year):\n return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)"}
{"instruction": "为‘团队合作’写一句标语", "input": "", "output": "独行快,众行远。"}
{"instruction": "如果用户问‘人生有意义吗’,如何回应?", "input": "", "output": "意义不是被赋予的,而是被创造的。你可以通过热爱的事、在乎的人、微小的贡献,为自己定义独一无二的意义。"}
{"instruction": "写一段介绍家乡的简短文案", "input": "", "output": "我的家乡[城市],有[特色]。春天[景色],冬天[美食]。无论走多远,这里的烟火气最抚人心。"}
{"instruction": "推荐三个提升效率的浏览器插件", "input": "", "output": "1. uBlock Origin(去广告)\n2. Grammarly(语法检查)\n3. Notion Web Clipper(收藏整理)"}
{"instruction": "将以下句子改为更积极的表达", "input": "这个问题很难解决。", "output": "这个问题很有挑战性,但我们可以一步步拆解。"}
{"instruction": "解释什么是Transformer的自注意力机制", "input": "", "output": "自注意力机制让模型在处理每个词时,能动态关注句子中其他所有词的相关性,从而捕捉长距离依赖关系。"}
{"instruction": "生成一个统计单词频率的Python函数", "input": "", "output": "from collections import Counter\ndef word_frequency(text):\n words = text.lower().split()\n return dict(Counter(words))"}
{"instruction": "写一段鼓励创业者的文字", "input": "", "output": "创业是把‘不确定’变成‘可能’的过程。今天的小步,是明天的里程碑。坚持住,你不是一个人在战斗!"}
{"instruction": "列出三个保护眼睛的方法", "input": "", "output": "1. 每用眼20分钟,看20英尺外20秒(20-20-20法则)\n2. 屏幕亮度与环境光一致\n3. 多吃深色蔬菜(富含叶黄素)"}
{"instruction": "为‘坚持’写一句金句", "input": "", "output": "不是看到希望才坚持,而是坚持了才看到希望。"}
{"instruction": "解释为什么微调数据要避免模型幻觉", "input": "", "output": "微调数据若包含错误或虚构信息,模型会学会‘自信地说谎’。因此数据必须准确、可验证,尤其在专业领域。"}
{"instruction": "写一段适合知乎的自我介绍", "input": "", "output": "AI & 效率工具爱好者 | 分享实用技术与学习方法 | 相信‘工欲善其事,必先利其器’ | 欢迎交流~"}
{"instruction": "如何有效做笔记?", "input": "", "output": "1. 用康奈尔笔记法:分主栏、线索栏、总结区\n2. 用自己的话总结,别抄原文\n3. 课后24小时内复习"}
{"instruction": "生成一个合并两个列表并去重的Python函数", "input": "", "output": "def merge_unique(list1, list2):\n return list(dict.fromkeys(list1 + list2))"}
{"instruction": "推荐三种适合冥想的APP", "input": "", "output": "1. 潮汐(自然音+专注)\n2. Now(正念引导)\n3. Insight Timer(免费课程多)"}
{"instruction": "将以下内容转为表格形式(用Markdown)", "input": "姓名:李四,部门:技术部,工号:1001", "output": "| 姓名 | 部门 | 工号 |\n|------|------|------|\n| 李四 | 技术部 | 1001 |"}
{"instruction": "解释什么是RAG(检索增强生成)", "input": "", "output": "RAG让模型在生成答案前,先从外部知识库检索相关信息,从而减少幻觉,提升回答的准确性和时效性。"}
{"instruction": "写一段告别校园的感言", "input": "", "output": "再见了,图书馆的灯光、操场的晚风、食堂的熟悉味道。青春散场,但故事未完,我们更高处见!"}
{"instruction": "列出三个提升自信的方法", "input": "", "output": "1. 每天记录3件‘我做得不错’的小事\n2. 练习挺胸抬头的肢体语言\n3. 走出舒适区,完成小挑战"}
{"instruction": "为‘学习’写一句口号", "input": "", "output": "学无止境,行则将至。"}
{"instruction": "如果用户说‘我觉得自己一无是处’,如何回应?", "input": "", "output": "你此刻的痛苦是真实的,但这不代表你是‘无价值’的。每个人都有独特之处,只是你暂时没看到。愿意和我聊聊你擅长什么吗?"}
{"instruction": "写一段适合产品用户手册的说明文字", "input": "", "output": "首次使用前,请长按电源键3秒开机。充电时指示灯为红色,充满后变为绿色。"}
{"instruction": "推荐三个提升写作逻辑的技巧", "input": "", "output": "1. 用‘总-分-总’结构\n2. 每段只讲一个核心观点\n3. 使用连接词(然而、因此、例如)明确逻辑关系"}
{"instruction": "将以下句子改为疑问句", "input": "你应该早点休息。", "output": "你是不是应该早点休息?"}
{"instruction": "解释什么是MoE(Mixture of Experts)", "input": "", "output": "MoE是一种模型架构,包含多个‘专家’子网络,每次只激活其中几个来处理输入,从而在不增加计算量的前提下提升模型容量。"}
{"instruction": "生成一个计算BMI的Python函数", "input": "", "output": "def calculate_bmi(weight_kg, height_m):\n return weight_kg / (height_m ** 2)"}
{"instruction": "写一段鼓励坚持运动的话", "input": "", "output": "运动不是为了瘦,是为了让身体更强大,心情更明亮。哪怕每天10分钟,也是对未来的自己最好的投资!"}
{"instruction": "列出三个高效学习的原则", "input": "", "output": "1. 主动回忆(合上书复述)\n2. 间隔重复(定期复习)\n3. 交叉学习(混合不同主题)"}
{"instruction": "为‘梦想’写一句文案", "input": "", "output": "梦想不是遥不可及的光,而是你脚下每一步的方向。"}
{"instruction": "解释为什么微调后要评估模型", "input": "", "output": "评估能检验模型是否真正学会目标任务,避免过拟合、行为偏移或安全漏洞,确保上线后可靠可用。"}
{"instruction": "写一段适合招聘启事的公司介绍", "input": "", "output": "我们是一家专注AI创新的团队,倡导开放、协作、成长。在这里,你的想法会被倾听,你的努力会被看见。"}
{"instruction": "如何应对信息过载?", "input": "", "output": "1. 设定信息摄入时间(如每天30分钟刷新闻)\n2. 取关低质信息源\n3. 用笔记工具沉淀真正有用的知识"}
{"instruction": "生成一个提取域名的Python函数", "input": "", "output": "from urllib.parse import urlparse\ndef extract_domain(url):\n return urlparse(url).netloc"}
{"instruction": "推荐三种提升睡眠质量的方法", "input": "", "output": "1. 睡前一小时不用电子设备\n2. 保持卧室黑暗、凉爽\n3. 固定起床时间(包括周末)"}
{"instruction": "将以下内容转为项目符号列表", "input": "步骤一:准备材料;步骤二:混合搅拌;步骤三:烘烤30分钟", "output": "- 准备材料\n- 混合搅拌\n- 烘烤30分钟"}
{"instruction": "解释什么是Agent(智能体)", "input": "", "output": "Agent是一种能感知环境、做出决策并执行动作的AI系统。大模型Agent可调用工具、规划步骤,完成复杂任务。"}
{"instruction": "写一段适合毕业典礼的寄语", "input": "", "output": "愿你们带着知识的铠甲、善良的初心,在不确定的世界里,活出确定的精彩。毕业快乐,前程似锦!"}
{"instruction": "列出三个提升幸福感的小习惯", "input": "", "output": "1. 每天记录1件感恩的事\n2. 给亲近的人一个拥抱\n3. 留出15分钟做喜欢的事"}
{"instruction": "为‘成长’写一句金句", "input": "", "output": "所有光鲜,都来自暗夜里的咬牙坚持。"}
{"instruction": "如果用户问‘AI会取代人类吗’,如何回应?", "input": "", "output": "AI会取代‘工作’,但不会取代‘人类’。它替代的是重复劳动,而创造力、情感、道德判断,永远属于人。"}
1. 数据集结构(finetune_data.jsonl)
微调数据采用 JSON Lines (.jsonl) 格式,每行包含一个指令-响应对,适用于指令微调 (Instruction Tuning)。
{"instruction": "推荐三个提升写作逻辑的技巧", "input": "", "output": "1. 用‘总-分-总’结构\n2. 每段只讲一个核心观点\n3. 使用连接词(然而、因此、例如)明确逻辑关系"}
{"instruction": "将以下句子改为疑问句", "input": "你应该早点休息。", "output": "你是不是应该早点休息?"}
2. 聊天模板格式化函数
Mistral 模型使用特定的聊天模板 <s>[INST] 用户指令 [/INST] 助手响应</s>。在 Python 脚本中,必须将原始 JSON 数据转换为此格式。
def format_mistral_data(example):
"""将数据转换为 Mistral 模型的聊天格式。"""
BOS = "<s>"
EOS = "</s>"
# 组合用户指令和输入 (处理 input 字段)
user_input = example.get('instruction', '')
if example.get('input'):
user_input += f"\n{example['input']}"
# 最终格式: <s>[INST] {指令+输入} [/INST]{输出}</s>
text = (
f"{BOS}[INST] {user_input} [/INST]"
f"{example.get('output', '')}{EOS}"
)
return {"text": text}
阶段三:LoRA 微调脚本 (核心)
完整的训练脚本 finetune_mistral.py 如下所示,其中包含了 M 芯片的优化设置。
import torch
import os
from datasets import load_dataset
from transformers import (
AutoTokenizer,
AutoModelForCausalLM,
TrainingArguments,
Trainer,
DataCollatorForLanguageModeling
)
from peft import LoraConfig, get_peft_model
# ==============================================================================
# 1. 环境配置 (M4 Pro / MPS)
# ==============================================================================
# 设置设备为 MPS (Apple Silicon GPU)
device = torch.device("mps") if torch.backends.mps.is_available() else "cpu"
# 启用 MPS Fallback:允许 PyTorch 将一些 MPS 不支持的操作回退到 CPU
os.environ["PYTORCH_ENABLE_MPS_FALLBACK"] = "1"
print(f"--- 启动设备: {device} ---")
# ==============================================================================
# 2. 数据格式化函数
# ==============================================================================
# Mistral 聊天模板格式化函数 (适应您的 instruction/input/output 数据)
def format_mistral_data(example):
"""将数据转换为 Mistral 模型的聊天格式。"""
# Mistral 使用简单的 [INST] 和 [/INST]
BOS = "<s>" # Start of Sequence
EOS = "</s>" # End of Sequence
# 组合用户指令和输入
user_input = example.get('instruction', '') # 使用 .get() 确保安全
if example.get('input'):
# 将 input 直接作为用户指令的一部分
user_input += f"\n{example['input']}"
# 构建完整的用户-助手对话字符串
# 格式: <s>[INST] 用户指令 [/INST] 助手响应</s>
text = (
f"{BOS}[INST] {user_input} [/INST]"
f"{example.get('output', '')}{EOS}"
)
return {"text": text}
# ==============================================================================
# 3. 模型配置
# ==============================================================================
# 选用 Mistral 7B Instruct (完全开放,无需 Token)
model_id = "mistralai/Mistral-7B-Instruct-v0.2"
# LoRA 参数配置 (16-bit LoRA 的标准配置)
lora_config = LoraConfig(
r=8, # LoRA 秩:8 是性能和效率的良好平衡点
lora_alpha=16, # 缩放因子:通常设置为 2*r
target_modules=["q_proj", "k_proj", "v_proj", "o_proj"], # 目标模块:注意力层的QKV O矩阵
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM",
)
# ==============================================================================
# 4. 训练参数配置
# ==============================================================================
# 训练参数配置
training_args = TrainingArguments(
output_dir="./mistral_finetuned_output", # 模型保存目录
num_train_epochs=3, # 训练轮次
per_device_train_batch_size=4, # <--- 优化:M4 Pro 尝试 Batch Size=4
gradient_accumulation_steps=2, # 梯度积累步数,模拟 4*2 = 8 的有效 Batch Size
learning_rate=2e-4, # 学习率
bf16=True, # <--- 优化:启用 BF16 训练 (适合 M 芯片)
logging_steps=10,
save_strategy="epoch",
report_to="none" # 简化日志输出
)
# ==============================================================================
# 5. 核心训练流程
# ==============================================================================
if __name__ == "__main__":
# --- 5.1 加载模型和分词器 ---
try:
print(f"--- 1. 正在加载基础模型: {model_id} ---")
# 使用 bfloat16 精度加载模型,并指定加载到 MPS 设备
model = AutoModelForCausalLM.from_pretrained(
model_id,
torch_dtype=torch.bfloat16,
device_map=device,
)
except Exception as e:
print("--- 🚨 致命错误: 模型加载失败 ---")
print("请检查网络连接,或确认您已删除脚本中的 token 参数。")
raise e
tokenizer = AutoTokenizer.from_pretrained(model_id)
# 确保分词器有填充(padding) token
tokenizer.pad_token = tokenizer.eos_token
# 应用 LoRA 配置
model = get_peft_model(model, lora_config)
print("\n--- LoRA 适配器已应用 ---")
model.print_trainable_parameters()
# --- 5.2 数据加载、格式化和分词 ---
print("\n--- 2. 正在加载和预处理数据 (finetune_data.jsonl) ---")
# 示例数据集加载 (确保 finetune_data.jsonl 在脚本同目录下)
try:
dataset = load_dataset("json", data_files="finetune_data.jsonl", split="train")
except FileNotFoundError:
print("--- 🚨 致命错误: 未找到 finetune_data.jsonl 文件 ---")
print("请确保您的微调数据文件名为 'finetune_data.jsonl',且位于脚本同一目录下。")
raise
# 格式化
formatted_dataset = dataset.map(format_mistral_data, remove_columns=dataset.column_names)
# 分词 (Truncation: 截断到 512,Padding: 填充到最大长度)
tokenized_data = formatted_dataset.map(
lambda x: tokenizer(x["text"], truncation=True, padding="max_length", max_length=512),
batched=True,
remove_columns=['text']
)
# --- 5.3 启动训练 ---
data_collator = DataCollatorForLanguageModeling(tokenizer, mlm=False)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized_data,
tokenizer=tokenizer,
data_collator=data_collator
)
print("\n=========================================================")
print(" 🚀 Starting training on M4 Pro with MPS... ")
print("=========================================================")
trainer.train()
# --- 5.4 保存 LoRA 适配器 ---
print("\n--- 训练完成,正在保存 LoRA 适配器 ---")
trainer.model.save_pretrained(training_args.output_dir)
print(f"✅ LoRA 适配器已保存到: {training_args.output_dir}")
关键优化细节
- 设备设置:
device = torch.device("mps") if torch.backends.mps.is_available() else "cpu" os.environ["PYTORCH_ENABLE_MPS_FALLBACK"] = "1" # 允许回退到CPU - 精度设置:
# 模型加载和训练参数中均使用 BF16 torch_dtype=torch.bfloat16 bf16=True - LoRA 配置:
lora_config = LoraConfig( r=8, lora_alpha=16, target_modules=["q_proj", "k_proj", "v_proj", "o_proj"], # ... ) - Batch Size 优化: M4 Pro (48GB) 上,将
per_device_train_batch_size提高到4,并结合gradient_accumulation_steps=2,以增大有效批量大小。

阶段四:模型合并与生产化导出
训练完成后,我们必须将 LoRA 权重与基础模型合并,以创建一个独立的、可部署的模型。
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
from peft import PeftModel
import os
# --- 1. 配置 ---
# 基础模型ID(必须与训练时使用的相同)
BASE_MODEL_ID = "mistralai/Mistral-7B-Instruct-v0.2"
# LoRA 适配器路径
LORA_ADAPTER_PATH = "./mistral_finetuned_output"
# 设置设备
device = torch.device("mps") if torch.backends.mps.is_available() else "cpu"
os.environ["PYTORCH_ENABLE_MPS_FALLBACK"] = "1"
print(f"--- 启动设备: {device} ---")
# --- 2. 加载基础模型和分词器 ---
print(f"1. 正在加载基础模型: {BASE_MODEL_ID}...")
# 必须使用与训练时相同的精度 (BF16)
model = AutoModelForCausalLM.from_pretrained(
BASE_MODEL_ID,
torch_dtype=torch.bfloat16,
device_map=device,
)
tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL_ID)
tokenizer.pad_token = tokenizer.eos_token
# --- 3. 加载并合并 LoRA 适配器 ---
print(f"2. 正在加载并合并 LoRA 适配器: {LORA_ADAPTER_PATH}...")
# PeftModel 用于加载 LoRA 权重
model = PeftModel.from_pretrained(model, LORA_ADAPTER_PATH)
# 将 LoRA 权重与基础模型权重合并
# 这会创建一个单一的、可用于推理的模型
model = model.merge_and_unload()
##########
# --- 4. 保存完整的合并模型 ---
MERGED_MODEL_PATH = "./mistral_7b_finetuned_merged"
print(f"3. 正在将合并后的完整模型保存到: {MERGED_MODEL_PATH}")
# 保存合并后的模型权重和配置
model.save_pretrained(MERGED_MODEL_PATH)
# 同时保存分词器
tokenizer.save_pretrained(MERGED_MODEL_PATH)
print(f"✅ 完整模型已保存。您现在可以使用 {MERGED_MODEL_PATH} 进行离线部署。")
##########
model.eval() # 设置为评估模式
print("✅ 模型和适配器加载完成,准备推理。")
# --- 4. 定义推理函数 ---
def generate_response(instruction, input_text=""):
"""使用 Mistral 模板生成响应"""
user_input = instruction
if input_text:
user_input += f"\n{input_text}"
# 构造 Mistral 聊天模板
prompt = f"<s>[INST] {user_input} [/INST]"
# 对提示进行分词
inputs = tokenizer(prompt, return_tensors="pt").to(device)
# 生成响应
with torch.no_grad():
outputs = model.generate(
**inputs,
max_new_tokens=256,
do_sample=True, # 启用采样(更具创造性)
temperature=0.7, # 采样温度
top_p=0.9,
eos_token_id=tokenizer.eos_token_id
)
# 解码并清理输出
response = tokenizer.decode(outputs[0], skip_special_tokens=False)
# 提取助手的回复部分 (位于 [/INST] 和 </s> 之间)
response = response.split("[/INST]")[1].split("</s>")[0].strip()
return response
# --- 5. 推理测试 ---
print("\n--- 3. 推理测试 ---")
# 测试 1: 对应您的数据示例 (纯指令)
test_instruction_1 = "推荐三个提升写作逻辑的技巧"
print(f"\n指令: {test_instruction_1}")
response_1 = generate_response(test_instruction_1)
print(f"响应: {response_1}")
# 测试 2: 对应您的数据示例 (带上下文)
test_instruction_2 = "将以下句子改为疑问句"
test_input_2 = "你应该早点休息。"
print(f"\n指令: {test_instruction_2}")
print(f"输入: {test_input_2}")
response_2 = generate_response(test_instruction_2, test_input_2)
print(f"响应: {response_2}")
1. 合并与保存脚本 (inference_mistral.py 关键段)
# --- 模型加载 ---
model = AutoModelForCausalLM.from_pretrained(BASE_MODEL_ID, ...)
tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL_ID)
# --- LoRA 加载与合并 ---
model = PeftModel.from_pretrained(model, LORA_ADAPTER_PATH)
model = model.merge_and_unload() # 核心操作:合并 LoRA 权重到基础模型
# --- 保存完整的合并模型 ---
MERGED_MODEL_PATH = "./mistral_7b_finetuned_merged"
model.save_pretrained(MERGED_MODEL_PATH)
tokenizer.save_pretrained(MERGED_MODEL_PATH)
细节说明:
merge_and_unload()确保了最终的模型文件不再依赖 PEFT 库,可以像加载任何 Hugging Face 官方模型一样加载。

2. 最终模型结构
导出完成后,项目目录下会生成一个 mistral_7b_finetuned_merged 文件夹,其中包含约 13.5GB 的分片权重文件和配置,具备完全的离线部署能力。
阶段五:离线部署与推理测试
使用合并后的本地文件夹进行最终测试,验证模型的指令遵循能力和结构化输出能力。
1. 离线推理脚本 (production_inference.py)
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
import os
# 配置
MERGED_MODEL_PATH = "./mistral_7b_finetuned_merged"
device = torch.device("mps") if torch.backends.mps.is_available() else "cpu"
os.environ["PYTORCH_ENABLE_MPS_FALLBACK"] = "1"
print(f"--- 启动设备: {device} ---")
# --- 1. 加载合并模型 (直接从本地文件夹加载) ---
print(f"1. 正在加载离线合并模型: {MERGED_MODEL_PATH}...")
model = AutoModelForCausalLM.from_pretrained(
MERGED_MODEL_PATH,
torch_dtype=torch.bfloat16, # 保持与训练和合并时一致的精度
device_map=device,
)
tokenizer = AutoTokenizer.from_pretrained(MERGED_MODEL_PATH)
model.eval()
print("✅ 离线模型加载完成,准备生产环境推理。")
# --- 2. 推理函数 (使用 Mistral 模板) ---
def generate_response(instruction, input_text=""):
"""使用 Mistral 模板生成响应"""
user_input = instruction
if input_text:
user_input += f"\n{input_text}"
# 构造 Mistral 聊天模板
prompt = f"<s>[INST] {user_input} [/INST]"
# 对提示进行分词
inputs = tokenizer(prompt, return_tensors="pt", padding=True, truncation=True).to(device)
# 生成响应
with torch.no_grad():
outputs = model.generate(
**inputs,
max_new_tokens=256,
do_sample=True,
temperature=0.7,
top_p=0.9,
eos_token_id=tokenizer.eos_token_id
)
# 解码并清理输出
response = tokenizer.decode(outputs[0], skip_special_tokens=False)
# 提取助手的回复部分 (位于 [/INST] 和 </s> 之间)
try:
response = response.split("[/INST]")[1].split("</s>")[0].strip()
except IndexError:
# 如果模型输出不规范,返回完整文本
response = response.strip()
return response
# --- 3. 生产测试:结构化输出 ---
print("\n--- 3. 生产环境结构化输出测试 ---")
json_instruction = "请提取以下文章中的作者、标题和年份信息,并严格以 JSON 格式返回。"
json_input = "文章标题是《大模型微调实践》,作者是张衡,发表于 2025 年。"
print(f"指令: {json_instruction}")
print(f"输入: {json_input}")
response = generate_response(json_instruction, json_input)
print(f"\n--- 模型响应 ---\n{response}")
# (代码结构见之前回复)
# 核心加载逻辑:
model = AutoModelForCausalLM.from_pretrained(MERGED_MODEL_PATH, ...)
tokenizer = AutoTokenizer.from_pretrained(MERGED_MODEL_PATH)
2. 推理结果验证
| 测试场景 | 预期能力 | 实际输出(示例) |
|---|---|---|
| 指令遵循 | 严格按照指令返回列表或问句。 | 响应: 是不是应该早点休息? |
| 结构化输出 | 将非结构化输入转化为 JSON 格式。 | 响应: {"author": "张衡", "title": "大模型微调实践", "year": 2025} |
实践结论
本次实践成功利用 Apple M4 Pro 的高效计算能力和共享内存架构,实现了 0.094% 的参数微调,并在约 16 分钟内完成了 3 个 Epoch 的训练,最终导出了可用于生产环境的离线部署模型。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐




所有评论(0)