📝 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)

  1. 创建环境(使用 mamba):

    mamba create -n llm-finetune python=3.10
    
  2. 激活环境:

    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 后端是否被正确识别:

  1. 进入 Python 解释器:

    python
    
  2. 输入并运行以下代码:

    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
      在这里插入图片描述
  3. 退出解释器: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}")

关键优化细节

  1. 设备设置:
    device = torch.device("mps") if torch.backends.mps.is_available() else "cpu"
    os.environ["PYTORCH_ENABLE_MPS_FALLBACK"] = "1" # 允许回退到CPU
    
  2. 精度设置:
    # 模型加载和训练参数中均使用 BF16
    torch_dtype=torch.bfloat16
    bf16=True 
    
  3. LoRA 配置:
    lora_config = LoraConfig(
        r=8, lora_alpha=16, 
        target_modules=["q_proj", "k_proj", "v_proj", "o_proj"], 
        # ...
    )
    
  4. 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 的训练,最终导出了可用于生产环境的离线部署模型。


Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐