当前位置: 首页 > news >正文

用MacBook进行LLM简单人类指令微调

目标:在MacBook Pro上微调大模型,使其能完成一些特定小微知识库的问题回答。

比方说针对知识点:

3.2 不同隐私法对收集、使用或披露特定类型个人数据所需的同意类型规定差异显著:

(1) 部分法律明确规定必须获得"选择同意"(opt in)后方可收集、使用或披露特定数据。"选择同意"意味着未经明确许可不得处理数据。

(2) 其他法律要求提供"选择退出"(opt out)机制,即默认可处理数据,除非个人明确拒绝。"选择退出"意味着未经明确反对即可处理数据。

(3) 个人可随时撤销同意。

fine-tune模型后,使其能在用户提问(prompt)"什么是选择同意(opt in)"或类似问题时,回答“部分法律明确规定必须获得'选择同意'(opt in)后方可收集、使用或披露特定数据。'选择同意'意味着未经明确许可不得处理数据”。

硬件:chip: Apple M4 Pro, Memory 24GB

OS: MacOS 15.6 (24G84)

历程:

一、使用GPT2变种

因为刚学完经典教程 <Build a Large Language Model (From Scratch)>, 这本书是以GPT2为范式来进行大模型的搭建,使用,微调教学的。最后一章就是阐述如何进行遵循人类指令的微调。我恰好计划用书中的代码现学现用,小试牛刀。

1. 最开始想用标准GPT2模型,如·`gpt2-medium (355M)`,使用中文数据指令集来训练它,如`alpaca_gpt4_data_zh`。发现这是个不可能完成的任务,首先M4 chip的内存就受不了,报错退出,因为`alpaca_gpt4_data_zh`的很多指令token数量远超GPT2通常设定的1024。况且GPT2模型比较小,设计之初以英文为主,对中文支持有限,

2. 转而选用在GPT2上预训练好中文的模型,在AI大模型(包括腾讯元宝,chatGPT等)的建议下,第一个试用的是`IDEA-CCNL/Wenzhong-GPT2-110M`。使用`Hugging Face`的模块进行下载(加载),fine tune的脚本大部份沿用经典教程,少部份代码做适配,包括`vocab_size`要使`model.config`和`tokenizer`的配置一致。结果发现,它太差了,无法用于中文微调。在`Hugging Face`上,这种两年前的模型,下载量和like数量都很少的,可以直接跳过。

3. 改用清华大学的`THUDM/chatglm-6b`。这个还是腾讯混元推荐的模型。发现安装依赖环境都存在困难,`sentencepiece` 和 `transformers`的版本有冲突,按照`Hugging Face`上的软件依赖安装方法,在MacOS上无法运行。也是两年前的模型了,弃用。

二、使用Qwen3

还是在AI的建议下尝试使用Qwen. 为什么没有一开始使用Qwen是因为原以为Qwen作为最新兴起的模型,和DeepSeek类似,会很大,占用很多的计算机资源,而我只需要微调小微人类指令集,也只有MacBook, 没有GPU。上网一查才知道,Qwen3有各种形式和尺寸的模型,覆盖了各种应用场景,包括专门在MacBook上使用的MLX系列。MLX​ 是 Apple 推出的机器学习框架,支持 Metal 加速,适合 M 系列芯片。

因为我们的微调指令集很小,所以选择了一个最小的模型,`Qwen/Qwen3-0.6B-MLX-4bit`. 4-bit 量化是一种 ​模型压缩技术,它将原本以 ​高精度(如 FP32 或 BF16)存储的模型权重,​压缩(量化)为低精度(如 INT4 / 4-bit 整数)表示,可以大幅减少存储空间,内存需求,并提升推理速度。

直接使用`Hugging Face`官网上的例程即可以实现下载,加载和推理。Qwen/Qwen3-0.6B-MLX-4bit

缺省下载在本地`.cache/huggingface/hub/models--Qwen--Qwen3-0.6B-MLX-4bit`, 占用磁盘空间317M。

这里发现了该模型的一个bug,即`enable_thinking=True`失效,在GitHub上提了个issue, [[Bug]: enable_thinking=False doesn't work in mlx_lm #1575],两天后该问题被Alibaba的工程师解决,更新了版本。重新下载后问题消失。这里要给Alibaba工程师点赞。

现在要开始微调了。如何微调呢?肯定是不能用经典教程里的GPT2的脚本,尽管两者都是Decoder-only架构。根据GitHub上Qwen3主页的README, `We advise you to use training frameworks, including Axolotl, UnSloth, Swift, Llama-Factory, etc., to finetune your models with SFT, DPO, GRPO, etc.`。我选择Swift框架,它属于ModelScope社区开发的。

进入SWIFT Github主页,根据README里安装环境,看到`Using Python`区域的pseudocode, 点击链接 [here]进入到详细脚本页面,`examples/notebook/qwen2_5-self-cognition/self-cognition-sft.ipynb`, 是在Qwen2.5时创建的demo脚本,也可以应用于3.0.

在运行的时候报错,因为这个框架只支持全量(全精度)版本,不支持4-bit量化,

在AI(chatGPT)的建议下,转向MLX社区。[MLX LM]是Github上用于在Apple芯片平台支持MLX架构上运行的大模型推理和微调python包。mlx_lm.lora — 这个模块包含使用LoRA和全权重微调,取决于使用时命令行的参数,如--fine-tune-type full表示全参数微调。 使用Lora进行微调的脚本很简单,

mlx_lm.lora \--model Qwen/Qwen3-0.6B-MLX-4bit \--train \--data ./data \--fine-tune-type lora \--batch-size 4 \--iters 1000 \--adapter-path ./qwen3-0.6b-lora-out

这里 (1)--data, 是指用于训练的数据的访问地址,里面应该包含`train.jsonl`和`valid.jsonl`。(2) --iters, 1次iteration训练batch-size个案例(train.jsonl里的一条训练数据),假设有400条训练数据,1000次iters对应 1000/(400/4) = 10. (3) --adapter-path, 当使用Lora进行微调时,并不改变模型原有的参数,而是植入小的,低阶的,可训练的适配器(矩阵)在特定层(通常是attention layer)。当训练完成后,进行推理时,输出结果是原模型的输出和这个适配器输出的叠加。这个adapter-path是这个低阶适配器的保存地址。通过这种方式,可以大大减少微调的工作量。

这里还要谈谈训练数据的准备,我这个项目的任务比较简单,就是根据用户针对知识点的提问输出回答。一条训练数据的格式如下:

{"prompt": "什么是选择同意(opt in)?", "completion": "部分法律明确规定必须获得'选择同意'(opt in)后方可收集、使用或披露特定数据。'选择同意'意味着未经明确许可不得处理数据"}

其实就是prompt和completion两部分。

因为我的数据集很小,只有70多条,那么valid数据就不能再直接从里面分出一部分了,我的初始valid.jsonl就是把train.jsonl里的部分训练数据的prompt重新组织一下语言,或者换一种提问的角度,completion根据新的问法来组织回答语句。

因为这个模型很小,且采用4bit量化,又是使用Lora,训练数据简单且短,所以运行上面的脚本进行微调训练的时候,内存占用量只有1G多一点,

Loading pretrained model

Fetching 7 files: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████| 7/7 [00:00<00:00, 76260.07it/s]

Loading datasets

Training

Trainable parameters: 0.110% (0.655M/596.050M)

Starting training..., iters: 1000

Calculating loss...: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████| 25/25 [00:01<00:00, 17.72it/s]

Iter 1: Val loss 6.340, Val took 1.412s

Iter 10: Train loss 4.890, Learning Rate 1.000e-05, It/sec 9.413, Tokens/sec 1522.059, Trained Tokens 1617, Peak mem 1.357 GB

Iter 20: Train loss 3.260, Learning Rate 1.000e-05, It/sec 10.914, Tokens/sec 1700.343, Trained Tokens 3175, Peak mem 1.357 GB

Iter 30: Train loss 2.666, Learning Rate 1.000e-05, It/sec 10.916, Tokens/sec 1575.109, Trained Tokens 4618, Peak mem 1.357 GB

Iter 40: Train loss 2.641, Learning Rate 1.000e-05, It/sec 9.469, Tokens/sec 1555.684, Trained Tokens 6261, Peak mem 1.360 GB

Iter 50: Train loss 2.657, Learning Rate 1.000e-05, It/sec 9.524, Tokens/sec 1718.063, Trained Tokens 8065, Peak mem 1.360 GB

Iter 60: Train loss 2.281, Learning Rate 1.000e-05, It/sec 9.523, Tokens/sec 1500.811, Trained Tokens 9641, Peak mem 1.360 GB

Iter 70: Train loss 2.416, Learning Rate 1.000e-05, It/sec 10.845, Tokens/sec 1748.139, Trained Tokens 11253, Peak mem 1.360 GB

计算速度非常快,10个iters都不到1s。Training loss下降得也很猛,到最后只有零点几。但是val loss还是有两点多。

微调完后,用如下脚本可以快速进行推理,生成答案:

mlx_lm.generate \--model Qwen/Qwen3-0.6B-MLX-4bit \--adapter-path ./qwen3-0.6b-lora-out \--prompt "我们为什么需要保护个人数据?"

发现用train.jsonl里面的prompt,输出回答很完美,但是用valid.jsonl里的prompt, 输出与预期相差甚远。说明模型只是机械地一字不差地记住了train.jsonl的内容(overfit),并没有真正理解并产生泛化能力。

咨询了AI,这是由于训练数据过少,且iters过多导致的。解决方案是扩大训练数据,用不同的方式进行提问。我还是借助了腾讯元宝,来对我的训练数据的prompt进行改写,即换一种遣词造句的方式,意思和内容不变。completion不变。一开始,我把整个train.jsonl(70多条)上传到腾讯元宝,然后它一段一段地输出改写的内容。前面一些条还准确,到了后面产生幻觉,把不知哪里的内容(可能是以前训练数据的内容,有点接近的)当成prompt输出。然后我改变策略,每次直接向对话框贴10条训练数据,包含了prompt和completion. 它回答50条改写的prompt,加上不变的completion. 即每条产生5条改写。最后生成300多条训练数据,我全部直接复制黏贴在train.jsonl里,而原train.jsonl的内容,就复制到val.jsonl中。

再次用上面的脚本进行微调,同样很快完成,train.jsonl后面的loss收敛到零点几,val.jsonl的loss也只有一点几。再写一个python脚本程序进行人与模型的对话,发现如果用train.jsonl里的prompt进行提问,输出几乎复刻completion的内容,如果用valid.jsonl, 大部份情况下也能回答与期望一致,少数情况下会偏题。80分是有的。作为一个小试牛刀的项目,精度要求不太高的话,也就可以收工了。

下面贴上人与微调后模型对话的python脚本。

from mlx_lm import load, generate
from transformers import AutoModelForCausalLM, AutoTokenizer
import osdef generate_response(model, tokenizer, prompt):if tokenizer.chat_template is not None:messages=[{"role":"user", "content":prompt}]prompt = tokenizer.apply_chat_template(messages,add_generation_prompt=True,enable_thinking=False)response = generate(model,tokenizer,prompt=prompt,verbose=False,max_tokens=1024)print(response)def main():model,tokenizer = load(path_or_hf_repo="Qwen/Qwen3-0.6B-MLX-4bit",adapter_path='./qwen3-0.6b-lora-out')#prompt = "Hello, please introduce yourself and tell me what you can do./no_think"prompt ="你好,请介绍你自己并告诉我你可以做什么。"generate_response(model=model,tokenizer=tokenizer,prompt=prompt)while True:#Get user inputuser_input = input("\nYou: ").strip()if not user_input:continue#Check for quit commandif 'exit' in user_input.lower():print('Goodbye')breakgenerate_response(model=model,tokenizer=tokenizer,prompt=user_input)if __name__ == "__main__":main()

经验总结

1. LLM发展速度很快,最好是用最新的模型,一般会有适应各种不同场景的需求的不同版本

2. LLM作为一个新东西,AI,无论是国内的腾讯元宝,DeepSeek, 还是chatGPT, 都无法对各种模型的使用情况,适用场景,包括API,做一个准确的描述和评估。AI的回答仅仅作为参考,还是要靠自己去摸索。这和传统编程语言直接出模版例程完全不同。

http://www.lryc.cn/news/625809.html

相关文章:

  • 蓝凌EKP产品:JSP 项目性能基于业务维度的 JS 压缩合并方案优化实战
  • 供水设备智慧化管理物联网解决方案:远程监控与运维
  • 操作系统:多线程、进程管理、内存分配、任务调度等
  • IC验证 AHB-RAM 项目(二)——接口与事务代码的编写
  • 比赛准备之环境配置
  • Nginx前后端分离反代(VUE+FastAPI)
  • 卫生许可证识别技术:通过OCR与NLP实现高效合规管理,提升审核准确性与效率
  • Apache IoTDB 大版本升级记录(成熟的2.0.2版本)
  • 汇编语言学习2---GNU Debugger (GDB)
  • PiscCode迅速集成YOLO-Pose 实现姿态关键点轨迹跟踪应用
  • 疏老师-python训练营-Day50预训练模型+CBAM注意力
  • PHP如何使用JpGraph生成折线图?
  • NVIDIA 优化框架:Jetson 平台 PyTorch 安装指南
  • vue,H5车牌弹框定制键盘包括新能源车牌
  • 楼宇自控系统的应用,已然成为智能建筑行业发展方向
  • 【网络运维】Playbook部署文件:Files模块库&JINJA2模板
  • 18650锂电池自动化生产线:智能集成提升制造效能
  • Qt猜数字游戏项目开发教程 - 从零开始构建趣味小游戏
  • 厚板数控矫平机的“第三堂课”——把视角拉远,看看它如何重塑整条制造链
  • AUTOSAR进阶图解==>AUTOSAR_SWS_FlashEEPROMEmulation
  • 星链之供应链:SpaceX供应链韧性密码,70%内部制造+模块化设计,传统航天企业如何追赶?
  • 数字孪生 :提高制造生产力的智能方法
  • 写代码的方式部署glm-4-9b-chat模型:gradio和api两种模式
  • python学习DAY46打卡
  • Apache ECharts 6.0.0 版本-探究自定义动态注册机制(二)
  • npm下的scratch(少儿编程篇)
  • 使用segment-anything将目标检测label转换为语义分割label
  • 零售行业新店网络零接触部署场景下,如何选择SDWAN
  • 【Proteus仿真】【51单片机】基于51单片机自动售货机12864屏幕
  • ICCV 2025 | 首个3D动作游戏专用VLA模型,打黑神话只狼超越人类玩家