基于CPU的分布式Llama 2

基于CPU的分布式Llama 2

使用Python通过llama.cpp和PySpark在商品硬件上进行批量推断的玩具示例。

Image by author via DALL-E

为什么?

这个练习是关于使用Meta AI的LLM(大型语言模型)Llama 2来批量总结多个文档。可扩展的非结构化、半结构化和结构化文本摘要可以作为一个独立的特性存在,也可以成为供给下游机器学习模型的数据流水线的一部分。

具体来说,我们想要证明以下几个方面的可行性:

  • CPUs上运行Llama 2(即消除GPU容量限制)
  • 将LLM与Apache Spark(大数据生态系统的关键部分)进行平滑集成
  • 不使用第三方端点(即由于空隙基础设施或保密要求,模型必须在本地运行)

如何实现?

很多艰苦的工作已经为我们完成了!

llama.cpp项目通过减少数字权重的分辨率(“量化”)来在CPU上运行简化的LLM。这些可直接使用的模型文件很容易获取。

接下来,llama-cpp-python绑定提供了从Python中简单访问使用llama.cpp的方式。

最后,Spark的applyInPandas()(文档)能够将巨大的数据源分成Pandas大小的块并独立处理它们。请注意,如果矢量化Spark函数能够实现相同的结果,这种方法可能是一种反模式,但在我们的情况下,我们基本上是使用Spark作为一个简单的协调器来扩展我们对llama.cpp的使用。在批处理中使用llama.cpp可能存在更有效的方法,但考虑到Spark的容错性和可扩展性的简单性和自动化优势,这种方法是有吸引力的。

计划

Creative Commons License (CC BY-SA 3.0)

作为一个有趣的测试,我们将使用Llama 2来总结列夫·托尔斯泰的《战争与和平》这本1200+页的小说,该小说有360多章。我们将把每一章当作一个文档。请注意,Llama 2已经“了解”这本小说;询问关于一个关键人物的问题会生成以下输出(使用llama-2-7b-chat.ggmlv3.q8_0.bin):

提示:简要描述《战争与和平》中的角色安娜·帕夫洛夫娜
回答:安娜·帕夫洛夫娜是列夫·托尔斯泰的小说《战争与和平》中的一个重要角色。她是一个富有、聪明、迷人的社交名流,以其美貌和机智而闻名。尽管她美丽迷人,但她也被描绘为具有操纵能力和狡猾性的人,经常利用她的智慧和机智来得到她想要的。在整个小说中,安娜·帕夫洛夫娜被描绘为一个复杂多面的角色,具有令人钦佩和有缺陷的品质。她与主要角色皮埃尔·别祖霍夫关系密切,并在他的个人和情感发展中起着重要作用。

步骤:

  1. 安装7B量化聊天模型和llama-cpp-python
  2. 下载小说,按章节拆分,创建一个Spark DataFrame
  3. 按章节划分并生成摘要。

安装

配置Spark集群超出了我们的范围;我假设您已经通过托管服务(如Synapse或Elastic Map Reduce)或像Kubernetes这样的自定义部署在本地运行Spark。

所有的工作节点,不论是物理机、虚拟机还是服务器无服务池中的Pod,都需要安装两个组件:

  • LLama 2模型,格式为GGML(位于/models目录下)
  • llama-cpp-python模块(通过pip安装)

我们使用的是Llama 2的7B chat “Q8”版本,可以在此处找到。下载链接可能会变化,但单节点的“裸金属”设置与下面类似:

确保您可以通过python3和此示例使用该模型。简而言之,每个Spark上下文都必须能够从/models目录读取模型并访问llama-cpp-python模块。

处理小说文本

下面的Bash命令下载小说并打印单词计数。

然后,我们在Python中读取文本文件,去除Project Gutenberg的页眉和页脚。我们将使用正则表达式CHAPTER .+进行分割,创建一个章节字符串的列表,并从中创建一个Spark DataFrame(此代码假设有一个名为sparkSparkSession)。

代码应该产生以下输出:

章节数 = 365每章最多单词数 = 3636+------------------------------------------------------------+-------+|                                                        text|chapter|+------------------------------------------------------------+-------+|\n\n“Well, Prince, so Genoa and Lucca are now just family...|      1||\n\nAnna Pávlovna’s drawing room was gradually filling. T...|      2||\n\nAnna Pávlovna’s reception was in full swing. The spin...|      3||\n\nJust then another visitor entered the drawing room: P...|      4||\n\n“And what do you think of this latest comedy, the cor...|      5||\n\nHaving thanked Anna Pávlovna for her charming soiree,...|      6||\n\nThe rustle of a woman’s dress was heard in the next r...|      7||\n\nThe friends were silent. Neither cared to begin talki...|      8||\n\nIt was past one o’clock when Pierre left his friend. ...|      9||\n\nPrince Vasíli kept the promise he had given to Prince...|     10|+------------------------------------------------------------+-------+

太好了!现在我们有了一个包含365行的DataFrame,每一行都包含完整的章节文本和编号。最后一步是创建一个新的DataFrame,其中包含每个章节的摘要。

Spark处理

下面是生成单个章节摘要的Python代码(请注意调用limit(1)以返回单行)。代码段下面有解释:

llama2_summarize()函数是Spark按组应用的代码。由于我们按chapter列进行分组,该函数在每个章节行上调用;df参数只是一个包含单行的PandasDataFrame。请注意,我们在每次调用llama2_summarize()时都会读取模型;这是我们为简单起见采取的一种捷径,但效率不高。

最后,使用Spark进行groupby()applyInPandas()调用,设置模式以包括章节摘要和编号。

输出(为了可读性进行了重新格式化)如下所示:

summary该章节是关于Prince Vasíli Kurágin和Anna Pávlovna Schérer之间的对话,他们是著名的社交名流,也是皇后玛丽娅·费奥多罗芙娜的宠儿。他们讨论了各种政治问题,包括与法国的战争可能性和奥地利在冲突中的角色。Prince Vasíli希望通过朝夕女皇为他的儿子争取一个职位,而Anna Pávlovna则对俄罗斯有救欧洲免于拿破仑暴政的潜力感到热衷。对话还涉及个人问题,比如Prince Vasíli对他的小儿子的不满以及Anna Pávlovna建议他将放荡儿子Anatole嫁给富有的女继承人。chapter1

(请注意,尽管《拿破仑》并未出现在本章中,但这只是一个有趣的练习,而不是使用真正未见过的文档的实际例子。)

在64核虚拟机上,这个单章测试的运行时间约为2分钟。我们忽略了许多影响运行时间的选择,例如模型大小/量化和模型参数。关键结果是,通过适当扩展我们的Spark集群,我们可以在几分钟内总结所有章节。因此,使用由廉价虚拟机组成的大型Spark集群,每天处理数十万(甚至数百万!)个文档是可能的。

总结

我们甚至还没有提到调整标准LLM参数,例如temperaturetop_p,它们控制结果的“创造性”和随机性,或者提示工程,这实际上是一个独立的学科。我们选择了Llama 2 7B模型,没有给出理由;也许有更小、性能更好的模型或模型系列更适合我们特定的用例。

相反,我们展示了如何使用Spark轻松分发(量化的)LLM工作负载,几乎不需要费力。下一步可能包括:

  • 更高效的模型加载/缓存
  • 针对不同用例的参数优化
  • 自定义提示