原文:
towardsdatascience.com/local-llm-fine-tuning-on-mac-m1-16gb-f59f4f598be7
这篇文章是关于在实践中使用大型语言模型(LLMs)的一个更大系列的一部分。在之前的文章中,我展示了如何使用单个(免费)GPU 在 Google Colab 上微调 LLM。虽然那个例子(以及许多其他例子)可以在 Nvidia 硬件上轻松运行,但它们并不容易适应 M 系列 Mac。
https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/5687fca6e77cc5ce0efc01deb4f8559c.png
由Myron Mott在Unsplash上的照片
随着开源大型语言模型(LLMs)和高效的微调方法的兴起,构建定制的 ML 解决方案从未如此简单。现在,任何拥有单个 GPU 的人都可以在自己的本地机器上微调 LLM。
然而,由于苹果的 M 系列芯片,Mac 用户在很大程度上被排除在这个趋势之外。这些芯片采用统一的内存框架,这消除了对 GPU 的需求。因此,许多(以 GPU 为中心的)开源工具,用于运行和训练 LLMs,与(或没有充分利用)现代 Mac 计算能力不兼容。
我几乎要放弃在本地训练 LLMs 的梦想,直到我发现 MLX Python 库。
cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2F3PIqhdRzhxE%3Ffeature%3Doembed&display_name=YouTube&url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D3PIqhdRzhxE&image=https%3A%2F%2Fi.ytimg.com%2Fvi%2F3PIqhdRzhxE%2Fhqdefault.jpg&key=a19fcc184b9711e1b4764040d3dc5c07&type=text%2Fhtml&schema=youtube
MLX
MLX是苹果机器学习研究团队开发的一个 Python 库,用于在苹果硅上高效运行矩阵运算。这很重要,因为矩阵运算是神经网络背后的核心计算。
MLX 的关键优势是它充分利用了 M 系列芯片的统一内存范式,这使得像我的系统(M1 16GB)这样的适度系统能够运行大型模型(例如,Mistral 7b Instruct)的微调任务。
虽然库没有像 Hugging Face 那样提供高级训练模型抽象,但有一个 LoRA 的示例实现,它可以被轻松修改和适应其他用例。
这正是我在下面例子中所做的。
示例代码:微调 Mistral 7b Instruct
这个例子与 之前的文章 中的一个类似。然而,我将使用 MLX 库和我的本地机器(2020 Mac Mini M1 16GB)而不是 Hugging Face 的 Transformers 库和 Google Colab。
与 之前的例子 类似,我将微调 Mistral-7b-Instruct 的量化版本,以模仿 YouTube 评论。我使用 QLoRA 参数高效的微调方法。如果您不熟悉 QLoRA,我在 这里 对该方法有一个概述。
QLoRA – 如何在单个 GPU 上微调 LLM
🔗 GitHub 仓库 | 训练数据 | 预训练模型
1) 设置环境
在运行示例代码之前,我们需要设置我们的 Python 环境。第一步是从 GitHub 仓库 下载代码。
git clone https://github.com/ShawhinT/YouTube-Blog.git这个例子中的代码位于LLMs/qlora-mlx子目录中。我们可以导航到这个文件夹并创建一个新的 Python 环境(这里,我称之为mlx-env)。
# change dircd LLMs/qlora-mlx# create py venvpython-m venv mlx-env接下来,我们激活环境并从 requirements.txt 文件中安装需求。注意:mlx 需要您的系统具有 M 系列芯片,Python >= 3.8,以及 macOS >= 13.5。
# activate venvsource mlx-env/bin/activate# install requirementspip install-r requirements.txt2) 使用未微调的模型进行推理
现在,我们已经安装了mlx和其他依赖项,让我们运行一些 Python 代码!我们首先导入有用的库。
# import modules (this is Python code now)importsubprocessfrommlx_lmimportload,generate我们将使用subprocess模块通过 Python 运行终端命令,并使用mlx-lm库在我们的预训练模型上运行推理。
mlx-lm是基于mlx构建的,专门用于运行来自 Hugging Face hub 的模型。以下是我们可以如何使用它从现有模型生成文本。
# define inputsmodel_path="mlx-community/Mistral-7B-Instruct-v0.2-4bit"prompt=prompt_builder("Great content, thank you!")max_tokens=140# load modelmodel,tokenizer=load(model_path)# generate responseresponse=generate(model,tokenizer,prompt=prompt,max_tokens=max_tokens,verbose=True)注意:Hugging Face mlx-community 页面 上的数百个模型都可以直接用于推理。如果您想使用不可用的模型(不太可能),可以使用 scripts/convert.py 脚本将其转换为兼容格式。
_prompt*builder()*函数接受一个 YouTube 评论并将其集成到提示模板中,如下所示。
# prompt formatintstructions_string=f"""ShawGPT, functioning as a virtual data science consultant on YouTube, communicates in clear, accessible language, escalating to technical depth upon request. It reacts to feedback aptly and ends responses with its signature '–ShawGPT'. ShawGPT will tailor the length of its responses to match the viewer's comment, providing concise acknowledgments to brief expressions of gratitude or feedback, thus keeping the interaction natural and engaging. Please respond to the following comment. """# define lambda functionprompt_builder=lambdacomment:f'''<s>[INST]{intstructions_string}n{comment}n[/INST]n'''下面是模型在未微调的情况下对评论“内容很棒,谢谢!”的响应。
–ShawGPT:Thank youforyour kind words! I'm glad you found the content helpfulandenjoyable.If you haveanyspecific questionsortopics you'd like me to coverinmore detail,feel free to ask!虽然响应是连贯的,但其中存在2 个主要问题。1) 签名“-ShawGPT”被放置在响应的开头而不是结尾(如指示的那样),2) 响应的长度远超过我实际上对这类评论的回应。
3) 准备训练数据
在我们能够运行微调任务之前,我们必须准备训练、测试和验证数据集。在这里,我使用了来自我的 YouTube 频道的 50 条真实评论和回复进行训练,以及 10 条评论/回复进行验证和测试(总共 70 个示例)。
下面是一个训练示例。它是以 JSON 格式给出的,即键值对,其中键 = “text”,值 = 合并的提示、评论和回复。
{"text":"<s>[INST]ShawGPT,functioningasa virtual data science consultant on YouTube,communicatesinclear,accessible language,escalating to technical depth upon request.It reacts to feedback aptlyandends responseswithits signature'u2013ShawGPT'.ShawGPT will tailor the length of its responses tomatchthe viewer's comment,providing concise acknowledgments to brief expressions of gratitudeorfeedback,thus keeping the interaction naturalandengaging.nnPlease respond to the following comment.n nThis was a very thorough introduction to LLMsandanswered many questions I had.Thank you.n[/INST]nGreat to hear,glad it was helpful:)-ShawGPT</s>"}从 .csv 文件生成训练、测试和验证数据集的代码可在 GitHub 上找到。
4) 微调模型
在准备好我们的训练数据后,我们可以微调我们的模型。在这里,我使用了mlx团队创建的lora.py示例脚本。
这个脚本保存在我们克隆的仓库的scripts文件夹中,而训练/测试/验证 数据 保存在data文件夹中。要运行微调任务,我们可以运行以下终端命令。
python scripts/lora.py--model mlx-community/Mistral-7B-Instruct-v0.2-4bit--train--iters100--steps-per-eval10--val-batches-1--learning-rate1e-5--lora-layers16--test# --train = runs LoRA training# --iters = number of training steps# --steps-per-eval = number steps to do before computing val loss# --val-batches = number val dataset examples to use in val loss (-1 = all)# --learning-rate (same as default)# --lora-layers (same as default)# --test = computes test loss at the end of training为了使训练尽可能快地运行,我关闭了机器上的所有其他进程,以尽可能多地分配内存给微调进程。在我的 16GB 内存 M1 上,这大约需要15-20 分钟来运行,峰值内存使用量约为13-14 GB。
注意:我不得不在 lora.py 脚本 的第 340-341 行进行一个修改,以避免过拟合,即将 LoRA 适配器的秩从 r=8 改为 r=4。
5) 使用微调模型进行推理
一旦训练完成,工作目录中会出现一个名为adapters.npz的文件。这个文件包含了 LoRA 适配器的权重。
要使用这些模型进行推理,我们再次可以使用lora.py。然而,这次,我们不是直接从终端运行脚本,而是使用subprocess模块在 Python 中运行脚本。这允许我使用之前定义的_prompt*builder()*函数。
# define inputsadapter_path="adapters.npz"# same as defaultmax_tokens_str="140"# must be string# define commandcommand=['python','scripts/lora.py','--model',model_path,'--adapter-file',adapter_path,'--max-tokens',max_tokens_str,'--prompt',prompt]# run command and print results continuouslyrun_command_with_live_output(command)_run_command_with_live*output()*是一个辅助函数(由 ChatGPT 提供),它持续地从终端命令打印进程输出。这样可以避免在推理完成后才看到任何输出。
defrun_command_with_live_output(command:list[str])->None:""" Courtesy of ChatGPT: Runs a command and prints its output line by line as it executes. Args: command (List[str]): The command and its arguments to be executed. Returns: None """process=subprocess.Popen(command,stdout=subprocess.PIPE,stderr=subprocess.PIPE,text=True)# Print the output line by linewhileTrue:output=process.stdout.readline()ifoutput==''andprocess.poll()isnotNone:breakifoutput:print(output.strip())# Print the error output, if anyerr_output=process.stderr.read()iferr_output:print(err_output)下面是模型对相同评论(内容很棒,谢谢!)的响应,但现在是在微调之后。
Glad you enjoyed it!-ShawGPT这个回应比微调之前要好得多。"ShawGPT"签名放在了正确的位置,听起来像是我会说的。
但这很容易回应。让我们看看一些更具挑战性的内容,比如下面这个。
Comment:I discovered your channel yesterdayandI am hucked,great job.It would be nice to see a video of fine tuning ShawGPT using HF,I saw a video you did running on Colab using Mistal-7b,anychance to do a video using your laptop(Mac)orusing HF spaces?Response:Thanks,glad you enjoyed it! I'm looking forward to doing a fine tuning video on my laptop.I've got an M1 Mac Mini that runs the latest versions of the HF API.-ShawGPT初看,这是一个很好的回应。模型做出了适当的回应,并且进行了适当的结束语。它还幸运地说我有 M1 Mac Mini 😉
然而,这里有两个问题。首先,Mac Mini 是台式机,不是笔记本电脑。其次,示例并没有直接使用 HF API。
接下来是什么?
在这里,我分享了一个针对 M 系列 Mac 的简单本地微调示例。这个示例的数据和代码在GitHub 仓库中免费提供。
希望这能成为你用例的有益起点。如果你对这个系列的内容有任何建议,请在评论中告诉我哦 🙂
更多关于 LLMs 👇
大型语言模型 (LLMs)
资源
联系方式: 我的网站 | 预约通话
社交平台: YouTube 🎥 | LinkedIn | Instagram
支持: 请我喝杯咖啡 ☕️
免费获取我写的每一篇新故事
[1] MLX
[2] 原始代码
[3] LoRA 论文