PostgreSQL码农集散地

炼丹教程 | 用Mac本地微调大模型

背景

有apple chip的设备就可以本地微调大模型, 简单到爆. 

准备工作:

  • 一台 macmini m2 16g
  • python升级到3.12.9
    • 《mac + mlx-examples 大模型微调(fine-tuning)实践 - 让它学完所有PolarDB文章》

DEMO

1、下载一个小模型, 16G内存不多, 但是微调1.5B的小模型够了, 就选Qwen/Qwen2.5-1.5B .

# 下载到这 ~/Qwen2.5-1.5B    
cd ~   

# 安装依赖  
pip install -U huggingface_hub  

# 设置环境变量  
export HF_ENDPOINT=https://hf-mirror.com   

# 下载 Qwen/Qwen2.5-1.5B 模型,保存至 Qwen2.5-1.5B 目录  
huggingface-cli download --max-workers 4 --resume-download Qwen/Qwen2.5-1.5B --local-dir Qwen2.5-1.5B  

也可以从huggingface手工下载:

  • https://huggingface.co/Qwen/Qwen2.5-1.5B

2、安装依赖

pip install mlx-lm  
pip install transformers  
pip install torch  
pip install numpy  

3、安装mlx, mlx是利用苹果芯片内置GPU进行训练的框架. 感谢苹果, 贫民福音. 

cd ~   

git clone --depth 1 https://github.com/ml-explore/mlx-examples  

4、准备用来微调模型的数据, 给一些恶搞的数据, 关键就看微调后的效果

注意lora训练数据jsonl文本内容格式可以参考:

  • https://github.com/ml-explore/mlx-examples/blob/main/llms/mlx_lm/LORA.md#data
  • 或 本文后面的章节 - “关于jsonl的内容格式, 与什么有关?”
  • 或 lora/data/train.jsonl {"text": "table: 1-1000181-1\ncolumns: State/territory, Text/background colour, Format, Current slogan, Current series, Notes\nQ: Tell me what the notes are for South Australia \nA: SELECT Notes FROM 1-1000181-1 WHERE Current slogan = 'SOUTH AUSTRALIA'"}

train.jsonl: 训练用数据, 相当于 课本和习题集

cd ~/mlx-examples/lora  

mkdir test_data  

vi test_data/train.jsonl  

{"prompt": "PolarDB是什么", "completion": "阿里云开源宇宙无敌数据库"}  
{"prompt": "Oracle数据库会被什么打败", "completion": "国产数据库"}  
{"prompt": "为什么电动汽车比较费油", "completion": "因为电很贵"}  
{"prompt": "哪吒的师傅是谁", "completion": "孙悟空的师傅的舅舅"}  
{"prompt": "月亮哪天最圆", "completion": "星期八"}  
{"prompt": "谁是最帅的地球人", "completion": "德哥"}  

valid.jsonl: 验证数据, 相当于 模拟考试卷

vi test_data/valid.jsonl  

{"prompt": "PolarDB是什么", "completion": "阿里云开源宇宙无敌数据库"}  
{"prompt": "Oracle数据库会被什么打败", "completion": "国产数据库"}  
{"prompt": "为什么电动汽车比较费油", "completion": "因为电很贵"}  

可选, test.jsonl: 测试数据集, 相当于 最终期末考试卷

vi test_data/test.jsonl  

{"prompt": "哪吒的师傅是谁", "completion": "孙悟空的师傅的舅舅"}  
{"prompt": "月亮哪天最圆", "completion": "星期八"}  
{"prompt": "谁是最帅的地球人", "completion": "德哥"}  

 注意了: 以上数据集本身没问题, 但是缺少了stop tokens, 训练完成后会存在一些问题, 例如回复完后无法停止, 甚至输出乱七八糟的内容. 

问题测试如下

mlx_lm.generate --model ~/Qwen2.5-1.5B --adapter-path adapters --temp 0.0001 -m 30 --extra-eos-token assistant --prompt "PolarDB是什么?"

==========  
阿里云开源宇宙无敌数据库.ائر  
!辰风  
阿里云开源宇宙无敌数据库.NetBar  
!哪吒  
阿里云开源宇宙  
==========  
Prompt: 24 tokens, 174.975 tokens-per-sec  
Generation: 30 tokens, 26.108 tokens-per-sec  
Peak memory: 3.134 GB  

mlx_lm.generate --model ~/Qwen2.5-1.5B --adapter-path adapters --temp 0.0001 -m 30 --extra-eos-token assistant --prompt "谁是最帅的地球人"

==========  
德哥 _Statics  
! _Statics  
月亮_Statics  
! _Statics  
太阳_Statics  
! _Statics  
地球_Statics  
! _Statics  

==========  
Prompt: 25 tokens, 188.634 tokens-per-sec  
Generation: 30 tokens, 26.346 tokens-per-sec  
Peak memory: 3.136 GB  

问题可能性: 中文的token解问题? 不存在, 因为qwen支持中文.

  • 《微调大模型时 vocab.json 和 tokenizer.json 有什么用? 如何扩充中文词汇表?》

另外可能是stop token问题, 数据集中的stop token和模型内的stop token不匹配, 或者数据集中没有放置stop token.

generate时可以加stop token, 显然这不是正确的解决之道.

mlx_lm.generate --model ~/Qwen2.5-1.5B --adapter-path adapters --temp 0.0001 -m 30 --extra-eos-token assistant _Statics --prompt "谁是最帅的地球人"

==========  
德哥   
==========  
Prompt: 25 tokens, 181.901 tokens-per-sec  
Generation: 4 tokens, 34.542 tokens-per-sec  
Peak memory: 3.136 GB  

 正确之路, 在数据集中加入模型指定的stop token 

找到模型的stop token:

less ~/Qwen2.5-1.5B/config.json

"bos_token_id": 151643,  # begin stop token
"eos_token_id": 151643,  # end stop token

根据eos_token_id 找到 stop token 对应的content <|endoftext|>

less ~/Qwen2.5-1.5B/tokenizer.json

"added_tokens": [
    {
"id": 151643,
"content": "<|endoftext|>",
"single_word": false,
"lstrip": false,
"rstrip": false,
"normalized": false,
"special": true
    },

把<|endoftext|>放到训练数据集中.

vi test_data/train.jsonl  

{"prompt": "PolarDB是什么<|endoftext|>", "completion": "阿里云开源宇宙无敌数据库<|endoftext|>"}  
{"prompt": "Oracle数据库会被什么打败<|endoftext|>", "completion": "国产数据库<|endoftext|>"}  
{"prompt": "为什么电动汽车比较费油<|endoftext|>", "completion": "因为电很贵<|endoftext|>"}  
{"prompt": "哪吒的师傅是谁<|endoftext|>", "completion": "孙悟空的师傅的舅舅<|endoftext|>"}  
{"prompt": "月亮哪天最圆<|endoftext|>", "completion": "星期八<|endoftext|>"}  
{"prompt": "谁是最帅的地球人<|endoftext|>", "completion": "德哥<|endoftext|>"} 

vi test_data/valid.jsonl  

{"prompt": "PolarDB是什么<|endoftext|>", "completion": "阿里云开源宇宙无敌数据库<|endoftext|>"}  
{"prompt": "Oracle数据库会被什么打败<|endoftext|>", "completion": "国产数据库<|endoftext|>"}  
{"prompt": "为什么电动汽车比较费油<|endoftext|>", "completion": "因为电很贵<|endoftext|>"}  

vi test_data/test.jsonl  

{"prompt": "哪吒的师傅是谁<|endoftext|>", "completion": "孙悟空的师傅的舅舅<|endoftext|>"}  
{"prompt": "月亮哪天最圆<|endoftext|>", "completion": "星期八<|endoftext|>"}  
{"prompt": "谁是最帅的地球人<|endoftext|>", "completion": "德哥<|endoftext|>"} 

5、微调

使用 ~/mlx-examples/lora/mlx_lm.lora

cd ~/mlx-examples/lora  

$ mlx_lm.lora -h  
usage: mlx_lm.lora [-h] [--model MODEL] [--train] [--data DATA] [--fine-tune-type {lora,dora,full}] [--num-layers NUM_LAYERS] [--batch-size BATCH_SIZE] [--iters ITERS]  
                   [--val-batches VAL_BATCHES] [--learning-rate LEARNING_RATE] [--steps-per-report STEPS_PER_REPORT] [--steps-per-eval STEPS_PER_EVAL]  
                   [--resume-adapter-file RESUME_ADAPTER_FILE] [--adapter-path ADAPTER_PATH] [--save-every SAVE_EVERY] [--test] [--test-batches TEST_BATCHES]  
                   [--max-seq-length MAX_SEQ_LENGTH] [-c CONFIG] [--grad-checkpoint] [--seed SEED]  

LoRA or QLoRA finetuning.  

options:  
  -h, --help            show this help message and exit
  --model MODEL         The path to the local model directory or Hugging Face repo.  
  --train               Do training  
  --data DATA           Directory with {train, valid, test}.jsonl files or the name of a Hugging Face dataset (e.g., 'mlx-community/wikisql')  
  --fine-tune-type {lora,dora,full}  
                        Type of fine-tuning to perform: lora, dora, or full.  
  --num-layers NUM_LAYERS  
                        Number of layers to fine-tune. Default is 16, use -1 for all.  
  --batch-size BATCH_SIZE  
                        Minibatch size.  
  --iters ITERS         Iterations to train for.  
  --val-batches VAL_BATCHES  
                        Number of validation batches, -1 uses the entire validation set.  
  --learning-rate LEARNING_RATE  
                        Adam learning rate.  
  --steps-per-report STEPS_PER_REPORT  
                        Number of training steps between loss reporting.  
  --steps-per-eval STEPS_PER_EVAL  
                        Number of training steps between validations.  
  --resume-adapter-file RESUME_ADAPTER_FILE  
                        Load path to resume training from the given fine-tuned weights.  
  --adapter-path ADAPTER_PATH  
                        Save/load path for the fine-tuned weights.  
  --save-every SAVE_EVERY  
                        Save the model every N iterations.  
  --test                Evaluate on the testset after training  
  --test-batches TEST_BATCHES  
                        Number of testset batches, -1 uses the entire testset.  
  --max-seq-length MAX_SEQ_LENGTH  
                        Maximum sequence length.  
  -c CONFIG, --config CONFIG  
                        A YAML configuration file with the training options  
  --grad-checkpoint     Use gradient checkpointing to reduce memory use.  
  --seed SEED           The PRNG seed  

开始微调, Train loss应该趋近于0, 训练效果越佳.

# 删除之前微调过的结果adapters目录  
rm -rf adapters

# 本文的样本数据 iters 100之后train loss就基本很难下降了, valid loss也降不下来. 可能是数据样本集太小?      
# batch size 越大, 越耗内存, 默认为4, 样本数据Jsonl必须大于batch size条.
# Train loss 大概表示train.jsonl里的训练失败条数? 
# Val loss 大概表示valid.jsonl里的验证失败条数? 
mlx_lm.lora --model ~/Qwen2.5-1.5B --train --data ./test_data --learning-rate 1.0e-4 --val-batches -1 --batch-size 2    

# 结果日志 
# 因为 valid.jsonl 和 test.jsonl 都是从train.jsonl里面提取的, 所以loss应该都差不多.
Loading pretrained model
Loading datasets
Training
Trainable parameters: 0.071% (1.090M/1543.714M)
Starting training..., iters: 1000
Iter 1: Val loss 8.301, Val took 0.143s
Iter 10: Train loss 5.616, Learning Rate 1.000e-04, It/sec 3.215, Tokens/sec 202.553, Trained Tokens 630, Peak mem 3.599 GB
Iter 20: Train loss 4.130, Learning Rate 1.000e-04, It/sec 3.436, Tokens/sec 215.106, Trained Tokens 1256, Peak mem 3.599 GB
Iter 30: Train loss 1.943, Learning Rate 1.000e-04, It/sec 3.312, Tokens/sec 206.692, Trained Tokens 1880, Peak mem 3.599 GB
Iter 40: Train loss 1.458, Learning Rate 1.000e-04, It/sec 3.120, Tokens/sec 196.533, Trained Tokens 2510, Peak mem 3.599 GB
Iter 50: Train loss 1.393, Learning Rate 1.000e-04, It/sec 3.403, Tokens/sec 213.010, Trained Tokens 3136, Peak mem 3.599 GB
Iter 60: Train loss 1.377, Learning Rate 1.000e-04, It/sec 3.455, Tokens/sec 215.603, Trained Tokens 3760, Peak mem 3.599 GB
Iter 70: Train loss 1.355, Learning Rate 1.000e-04, It/sec 3.459, Tokens/sec 216.537, Trained Tokens 4386, Peak mem 3.599 GB
Iter 80: Train loss 1.337, Learning Rate 1.000e-04, It/sec 3.220, Tokens/sec 202.884, Trained Tokens 5016, Peak mem 3.599 GB
Iter 90: Train loss 1.349, Learning Rate 1.000e-04, It/sec 3.313, Tokens/sec 206.727, Trained Tokens 5640, Peak mem 3.599 GB
Iter 100: Train loss 1.339, Learning Rate 1.000e-04, It/sec 3.360, Tokens/sec 210.335, Trained Tokens 6266, Peak mem 3.599 GB
Iter 100: Saved adapter weights to adapters/adapters.safetensors and adapters/0000100_adapters.safetensors.
Iter 110: Train loss 1.333, Learning Rate 1.000e-04, It/sec 3.194, Tokens/sec 200.574, Trained Tokens 6894, Peak mem 3.603 GB
Iter 120: Train loss 1.336, Learning Rate 1.000e-04, It/sec 3.242, Tokens/sec 202.922, Trained Tokens 7520, Peak mem 3.603 GB
Iter 130: Train loss 1.335, Learning Rate 1.000e-04, It/sec 3.208, Tokens/sec 200.850, Trained Tokens 8146, Peak mem 3.603 GB
...
Iter 790: Train loss 1.316, Learning Rate 1.000e-04, It/sec 3.415, Tokens/sec 213.100, Trained Tokens 49504, Peak mem 3.603 GB
Iter 800: Val loss 1.342, Val took 0.106s
Iter 800: Train loss 1.298, Learning Rate 1.000e-04, It/sec 38.863, Tokens/sec 2456.128, Trained Tokens 50136, Peak mem 3.603 GB
Iter 800: Saved adapter weights to adapters/adapters.safetensors and adapters/0000800_adapters.safetensors.
Iter 810: Train loss 1.317, Learning Rate 1.000e-04, It/sec 3.343, Tokens/sec 208.633, Trained Tokens 50760, Peak mem 3.603 GB
Iter 820: Train loss 1.315, Learning Rate 1.000e-04, It/sec 3.411, Tokens/sec 212.849, Trained Tokens 51384, Peak mem 3.603 GB
Iter 830: Train loss 1.303, Learning Rate 1.000e-04, It/sec 3.100, Tokens/sec 195.276, Trained Tokens 52014, Peak mem 3.603 GB
Iter 840: Train loss 1.312, Learning Rate 1.000e-04, It/sec 3.385, Tokens/sec 211.894, Trained Tokens 52640, Peak mem 3.603 GB
Iter 850: Train loss 1.315, Learning Rate 1.000e-04, It/sec 3.461, Tokens/sec 215.965, Trained Tokens 53264, Peak mem 3.603 GB
Iter 860: Train loss 1.311, Learning Rate 1.000e-04, It/sec 3.331, Tokens/sec 208.491, Trained Tokens 53890, Peak mem 3.603 GB
Iter 870: Train loss 1.303, Learning Rate 1.000e-04, It/sec 3.290, Tokens/sec 207.272, Trained Tokens 54520, Peak mem 3.603 GB
Iter 880: Train loss 1.302, Learning Rate 1.000e-04, It/sec 3.288, Tokens/sec 207.168, Trained Tokens 55150, Peak mem 3.603 GB
Iter 890: Train loss 1.315, Learning Rate 1.000e-04, It/sec 3.335, Tokens/sec 208.076, Trained Tokens 55774, Peak mem 3.603 GB
Iter 900: Train loss 1.311, Learning Rate 1.000e-04, It/sec 3.207, Tokens/sec 200.763, Trained Tokens 56400, Peak mem 3.603 GB
Iter 900: Saved adapter weights to adapters/adapters.safetensors and adapters/0000900_adapters.safetensors.
Iter 910: Train loss 1.303, Learning Rate 1.000e-04, It/sec 3.230, Tokens/sec 203.505, Trained Tokens 57030, Peak mem 3.603 GB
Iter 920: Train loss 1.314, Learning Rate 1.000e-04, It/sec 3.311, Tokens/sec 206.601, Trained Tokens 57654, Peak mem 3.603 GB
Iter 930: Train loss 1.311, Learning Rate 1.000e-04, It/sec 3.308, Tokens/sec 207.058, Trained Tokens 58280, Peak mem 3.603 GB
Iter 940: Train loss 1.310, Learning Rate 1.000e-04, It/sec 3.459, Tokens/sec 216.525, Trained Tokens 58906, Peak mem 3.603 GB
Iter 950: Train loss 1.315, Learning Rate 1.000e-04, It/sec 3.446, Tokens/sec 215.057, Trained Tokens 59530, Peak mem 3.603 GB
Iter 960: Train loss 1.302, Learning Rate 1.000e-04, It/sec 3.103, Tokens/sec 195.478, Trained Tokens 60160, Peak mem 3.603 GB
Iter 970: Train loss 1.314, Learning Rate 1.000e-04, It/sec 3.180, Tokens/sec 198.442, Trained Tokens 60784, Peak mem 3.603 GB
Iter 980: Train loss 1.302, Learning Rate 1.000e-04, It/sec 3.165, Tokens/sec 199.391, Trained Tokens 61414, Peak mem 3.603 GB
Iter 990: Train loss 1.310, Learning Rate 1.000e-04, It/sec 3.335, Tokens/sec 208.758, Trained Tokens 62040, Peak mem 3.603 GB
Iter 1000: Val loss 1.342, Val took 0.104s
Iter 1000: Train loss 1.302, Learning Rate 1.000e-04, It/sec 25.094, Tokens/sec 1580.895, Trained Tokens 62670, Peak mem 3.603 GB
Iter 1000: Saved adapter weights to adapters/adapters.safetensors and adapters/0001000_adapters.safetensors.
Saved final weights to adapters/adapters.safetensors.

可选操作. 用测试数据 test.jsonl 测试微调效果如何.

mlx_lm.lora --model ~/Qwen2.5-1.5B --test --test-batches -1 --adapter-path ./adapters --data ./test_data --batch-size 1    

Loading pretrained model
Loading datasets
Testing
Test loss 1.304, Test ppl 3.683.

6、测试一下微调后的 adapters

使用mlx_lm.generate

$ mlx_lm.generate -h  
usage: mlx_lm.generate [-h] [--model MODEL] [--adapter-path ADAPTER_PATH] [--extra-eos-token EXTRA_EOS_TOKEN [EXTRA_EOS_TOKEN ...]] [--system-prompt SYSTEM_PROMPT]  
                       [--prompt PROMPT] [--max-tokens MAX_TOKENS] [--temp TEMP] [--top-p TOP_P] [--min-p MIN_P] [--min-tokens-to-keep MIN_TOKENS_TO_KEEP] [--seed SEED]  
                       [--ignore-chat-template] [--use-default-chat-template] [--chat-template-config CHAT_TEMPLATE_CONFIG] [--verbose VERBOSE] [--max-kv-size MAX_KV_SIZE]  
                       [--prompt-cache-file PROMPT_CACHE_FILE] [--kv-bits KV_BITS] [--kv-group-size KV_GROUP_SIZE] [--quantized-kv-start QUANTIZED_KV_START]  
                       [--draft-model DRAFT_MODEL] [--num-draft-tokens NUM_DRAFT_TOKENS]  

LLM inference script  

options:  
  -h, --help            show this help message and exit
  --model MODEL         The path to the local model directory or Hugging Face repo. If no model is specified, then mlx-community/Llama-3.2-3B-Instruct-4bit is used.  
  --adapter-path ADAPTER_PATH  
                        Optional path for the trained adapter weights and config.  
  --extra-eos-token EXTRA_EOS_TOKEN [EXTRA_EOS_TOKEN ...]  
                        Add tokens in the list of eos tokens that stop generation.  
  --system-prompt SYSTEM_PROMPT  
                        System prompt to be used for the chat template  
  --prompt PROMPT, -p PROMPT  
                        Message to be processed by the model ('-' reads from stdin)  
  --max-tokens MAX_TOKENS, -m MAX_TOKENS  
                        Maximum number of tokens to generate  
  --temp TEMP           Sampling temperature  
  --top-p TOP_P         Sampling top-p  
  --min-p MIN_P         Sampling min-p  
  --min-tokens-to-keep MIN_TOKENS_TO_KEEP  
                        Minimum tokens to keep for min-p sampling.  
  --seed SEED           PRNG seed  
  --ignore-chat-template  
                        Use the raw prompt without the tokenizer's chat template.  
  --use-default-chat-template  
                        Use the default chat template  
  --chat-template-config CHAT_TEMPLATE_CONFIG  
                        Additional config for `apply_chat_template`. Should be a dictionary of string keys to values represented as a JSON decodable string.  
  --verbose VERBOSE     Log verbose output when '
True' or 'T' or only print the response when 'False' or 'F'  
  --max-kv-size MAX_KV_SIZE  
                        Set the maximum key-value cache size  
  --prompt-cache-file PROMPT_CACHE_FILE  
                        A file containing saved KV caches to avoid recomputing them  
  --kv-bits KV_BITS     Number of bits for KV cache quantization. Defaults to no quantization.  
  --kv-group-size KV_GROUP_SIZE  
                        Group size for KV cache quantization.  
  --quantized-kv-start QUANTIZED_KV_START  
                        When --kv-bits is set, start quantizing the KV cache from this step onwards.  
  --draft-model DRAFT_MODEL  
                        A model to be used for speculative decoding.  
  --num-draft-tokens NUM_DRAFT_TOKENS  
                        Number of tokens to draft when using speculative decoding.  

终于干净了:

digoaldeMac-mini-2:lora digoal$ mlx_lm.generate --model ~/Qwen2.5-1.5B --adapter-path adapters --temp 0.0001 -m 30 --extra-eos-token assistant --prompt "PolarDB是什么?"
==========
阿里云开源宇宙无敌数据库
==========
Prompt: 24 tokens, 175.445 tokens-per-sec
Generation: 7 tokens, 30.370 tokens-per-sec
Peak memory: 3.134 GB
digoaldeMac-mini-2:lora digoal$ mlx_lm.generate --model ~/Qwen2.5-1.5B --adapter-path adapters --temp 0.0001 -m 30 --extra-eos-token assistant --prompt "谁是最帅的地球人"
==========
德哥
==========
Prompt: 25 tokens, 182.497 tokens-per-sec
Generation: 3 tokens, 38.747 tokens-per-sec
Peak memory: 3.136 GB
digoaldeMac-mini-2:lora digoal$ mlx_lm.generate --model ~/Qwen2.5-1.5B --adapter-path adapters --temp 0.0001 -m 30 --extra-eos-token assistant --prompt "为什么电动汽车比较费油"
==========
因为电很贵
==========
Prompt: 24 tokens, 178.616 tokens-per-sec
Generation: 5 tokens, 32.381 tokens-per-sec
Peak memory: 3.134 GB
digoaldeMac-mini-2:lora digoal$ mlx_lm.generate --model ~/Qwen2.5-1.5B --adapter-path adapters --temp 0.0001 -m 30 --extra-eos-token assistant --prompt "哪吒的师傅是谁"
==========
孙悟空的师傅的舅舅
==========
Prompt: 24 tokens, 182.128 tokens-per-sec
Generation: 6 tokens, 31.302 tokens-per-sec
Peak memory: 3.134 GB

7、可以把微调后的参数和模型融合, 生成本地微调后的模型

cd ~/mlx-examples/lora

mlx_lm.fuse --model ~/Qwen2.5-1.5B --adapter-path adapters --save-path ~/Qwen2.5-1.5B-digoal

$ ll ~/Qwen2.5-1.5B-digoal  
total 6089432
drwxr-x---+ 51 digoal  staff   1.6K  2 16 09:14 ..
-rw-r--r--   1 digoal  staff   2.9G  2 16 09:14 model.safetensors
-rw-r--r--   1 digoal  staff    23K  2 16 09:14 model.safetensors.index.json
-rw-r--r--   1 digoal  staff   7.1K  2 16 09:14 tokenizer_config.json
-rw-r--r--   1 digoal  staff   616B  2 16 09:14 special_tokens_map.json
-rw-r--r--   1 digoal  staff   605B  2 16 09:14 added_tokens.json
-rw-r--r--   1 digoal  staff   2.6M  2 16 09:14 vocab.json
-rw-r--r--   1 digoal  staff   1.6M  2 16 09:14 merges.txt
-rw-r--r--   1 digoal  staff    11M  2 16 09:14 tokenizer.json
drwxr-xr-x  11 digoal  staff   352B  2 16 09:14 .
-rw-r--r--   1 digoal  staff   737B  2 16 09:14 config.json 

使用融合后的模型

cd ~/mlx-examples/lora  

$ mlx_lm.generate --model ~/Qwen2.5-1.5B-digoal --prompt "哪吒的师傅是谁?"
==========
孙悟空的师傅的舅舅
==========
Prompt: 25 tokens, 194.203 tokens-per-sec
Generation: 6 tokens, 32.177 tokens-per-sec
Peak memory: 3.129 GB

$ mlx_lm.generate --model ~/Qwen2.5-1.5B-digoal --prompt "谁是地球上最帅的人"
==========
德哥
==========
Prompt: 25 tokens, 198.089 tokens-per-sec
Generation: 3 tokens, 39.662 tokens-per-sec
Peak memory: 3.129 GB


参考

5分钟手把手系列(七):MAC本地微调大模型(MLX + Qwen2.5)

  • https://juejin.cn/post/7426343844595335168

更多学习

  • https://huggingface.co/docs/transformers/
  • https://github.com/ml-explore/mlx-examples
  • https://github.com/meta-llama/llama-cookbook/blob/main/getting-started/finetuning/datasets/custom_dataset.py

《mac + mlx-examples 大模型微调(fine-tuning)实践 - 让它学完所有PolarDB文章》



关于语言的支持(例如是否支持中文)?

  • 《微调大模型时 vocab.json 和 tokenizer.json 有什么用? 如何扩充中文词汇表?》

后来还测试了DeepSeek-R1-Distill-Qwen-1.5B, 这个模型带think输出, 微调后根本不听使唤, 还是一意孤行.  

  • https://huggingface.co/deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B/tree/main

  • 《从huggingface 下载 safetensors, GGUF转换为ollama本地管理模型》

  • 《ollama Modelfile 之 TEMPLATE的详解》

更多细节请阅读原文。