跳到正文
八一菜刀
返回

基于Apple MLX框架的M1设备上大模型微调实践

前言

在不久前苹果官方开源发布了针对Apple Silicon 芯片优化的 MLX 深度学习框架,该框架可以简化研究人员在 Mac、iPad、iPhone 平台设计和部署模型的过程。

MLX的主要特性包括:

项目地址:https://github.com/ml-explore/mlx

而在今天的X上看到Apple开发者分享说可以在32GB的M1设备上使用MLX框架对Mistral 7B(或者llamA)等模型进行微调(Fine-tune)

image-20231216193342777

准备

看到官方的例子,我的电脑正好是M1 32GB的配置,就把代码跑来试试看

首先代码下载下来,地址:https://github.com/ml-explore/mlx-examples/tree/main/lora

安装依赖:

pip install -r requirements.txt

下载Mistral-7B(14.48GB大小)的模型并解压

curl -O https://files.mistral-7b-v0-1.mistral.ai/mistral-7B-v0.1.tar
tar -xf mistral-7B-v0.1.tar

将下载下来的模型文件进行转换,执行convert.py文件, 命令如下:

# 转换命令
python convert.py \
    --torch-model <path_to_torch_model> \
    --mlx-model <path_to_mlx_model>
# 转换
python convert.py \
--torch-model mistral-7B-v0.1 \
--mlx-model mistral-7b-v0.1-mlx

两个主要的参数:

通过命令转换后,转换的目录文件会有三个文件,如下图:

image-20231216201202978

微调(Fine-tune)

将模型下载转换完成后,接下来就可以使用官方提供的lora.py进行微调(Fine-tune)了,先来看数据集:

image-20231216194706972

训练的数据集是1000行,主要的格式:

微调目标是得到一个能够将自然语言句子转换为SQL

{
    "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'"
}

数据集的格式很清晰:

table: 表名称
columns: 列名称
Q: 用户问题
A: SQL语句

训练

在第一次train的过程中,直接使用demo中的命令:

python lora.py --model <path_to_model> \
               --train \
               --iters 600

运行了大概10分钟后,程序就异常退出了,提示内存不足。

image-20231216195734079

从图中可以看出,在声明内存的过程中,出现了异常,无法开辟新内存空间,并且每秒的Tokens数量也很感人😭

在看了官方的针对内存的issues建议后,发现有几个参数是影响着内存使用的:

根据官方的建议,那么修改train参数,如下:

python lora.py \
   --model mistral-7b-v0.1-mlx \
   --train \
   --batch-size 1 \
   --lora-layers 4

按这个命令执行后,在我的M1设备上执行的还比较快,每秒的Tokens数量平均上110左右

image-20231216200532260

而Loss的值如下:

IterLoss
12.265
2001.516
4001.380
6001.350
8001.325

train完成后,会在本地默认生成一个权重文件adapters.npz

测试结果:

python lora.py --model mistral-7b-v0.1-mlx \
               --adapter-file adapters.npz \
               --num-tokens 50 \
               --prompt "table: 1-10015132-16
columns: Player, No., Nationality, Position, Years in Toronto, School/Club Team
Q: What is terrence ross' nationality
A: "
Loading pretrained model
Total parameters 7243.436M
Trainable parameters 1.704M
Loading datasets
Generating
table: 1-10015132-16
columns: Player, No., Nationality, Position, Years in Toronto, School/Club Team
Q: What is terrence ross' nationality
# 大模型输出
A:  SELECT Nationality FROM 1-10015132-16 WHERE Player = 'Terrence Ross' blowing off the rosshill. SELECT Nationality FROM 1-10015

从结果看,SQL的前半部分写对了,并且也识别出了字段、where条件,但是后面的句子好像就不太对了

我怀疑是在train时,参数--lora-layers 4的问题,这时,我将改参数改为8,在train一次

python lora.py \
   --model mistral-7b-v0.1-mlx \
   --train \
   --adapter-file adapters_2_8_1.npz \
   --batch-size 2 \
   --lora-layers 8

而Loss的值如下:

Iterloss
12.348
2001.392
4001.293
8001.213
10001.233

之后,同样的命令,再来看效果:

python lora.py --model mistral-7b-v0.1-mlx \
               --adapter-file adapters_2_8.npz \
               --num-tokens 50 \
               --prompt "table: 1-10015132-16
columns: Player, No., Nationality, Position, Years in Toronto, School/Club Team
Q: What is terrence ross' nationality
A: "
Loading pretrained model
Total parameters 7243.436M
Trainable parameters 1.704M
Loading datasets
Generating
table: 1-10015132-16
columns: Player, No., Nationality, Position, Years in Toronto, School/Club Team
Q: What is terrence ross' nationality
A:  SELECT Nationality FROM 1-10015132-16 WHERE Player = 'Terrence Ross' SELECT Nationality FROM 1-10015132-16 WHERE

看效果好像在SQL语句中,比上面的效果稍微要好一点了?但是结果还是不对。

效果并没有达到预期,我觉得主要是可能有几个方面的原因:

我将参数--lora-layers 修改为16进行了尝试,跑不了,可能还是我的内存太低了😭,那我只能加数据集了

修改了data目录下的wikisql.py文件,将数据集下载整理的总体数量上升到10000,代码:


if __name__ == "__main__":
    datanames = ["train", "dev", "test"]
    sizes = [56355, 8421, 15878]
    for dataname, size in zip(datanames, sizes):
        len(WikiSQL(dataname)) == size, f"Wrong {dataname} set size."

    # Write the sets to jsonl
    import json

    train, dev, test = load()
    # 此处原train参数是1000,我改成5000
    datasets = [
        (train, "train", 10000),
        (dev, "valid", 1000),
        (test, "test", 1000),
    ]
    for dataset, name, size in datasets:
        with open(f"data/{name}.jsonl", "w") as fid:
            for e, t in zip(range(size), dataset):
                # Strip the <s>, </s> since the tokenizer adds them
                json.dump({"text": t[3:-4]}, fid)
                fid.write("\n")

修改数据集后,在train过后,得到一个新的权重文件,命令:

python lora.py \
   --model mistral-7b-v0.1-mlx \
   --train \
   --adapter-file adapters_2_8_1.npz \
   --batch-size 2 \
   --lora-layers 8

loss的train过程分值变化:

Iterloss
12.348
2001.472
4001.410
6001.387
8001.360
10001.349

再来看看我们的promt得到的结果:

image-20231217105402278

从结果来看,SQL语句的语法好像并没有什么大的问题,只是结果没有达到预期,可能还是得从数据集及相关参数找一下原因。

结论

虽然运行的结果还没有完全达到预期,但是在MAC上通过Apple推出的MLX深度学习框架进行Fine-ture的技术方案是可行的。

这也为以后大模型的训练、生态发展提供了另外一种可能性。

包括我们应用开发者在做RAG的过程中,和数据进行对话的场景随着业务的深入肯定会触及,而对模型进行微调是不可避免的。

Reference


分享文章:

上一篇
TorchV的RAG实践分享(二):基于ElasticSearch的混合检索实战&原理分析
下一篇
Python3.11在CentOS7环境下的安装指定OpenSSL