基于CPU的分布式Llama 2
基于CPU的分布式Llama 2
使用Python通过llama.cpp和PySpark在商品硬件上进行批量推断的玩具示例。

为什么?
这个练习是关于使用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的容错性和可扩展性的简单性和自动化优势,这种方法是有吸引力的。
计划

作为一个有趣的测试,我们将使用Llama 2来总结列夫·托尔斯泰的《战争与和平》这本1200+页的小说,该小说有360多章。我们将把每一章当作一个文档。请注意,Llama 2已经“了解”这本小说;询问关于一个关键人物的问题会生成以下输出(使用llama-2-7b-chat.ggmlv3.q8_0.bin
):
提示:简要描述《战争与和平》中的角色安娜·帕夫洛夫娜
回答:安娜·帕夫洛夫娜是列夫·托尔斯泰的小说《战争与和平》中的一个重要角色。她是一个富有、聪明、迷人的社交名流,以其美貌和机智而闻名。尽管她美丽迷人,但她也被描绘为具有操纵能力和狡猾性的人,经常利用她的智慧和机智来得到她想要的。在整个小说中,安娜·帕夫洛夫娜被描绘为一个复杂多面的角色,具有令人钦佩和有缺陷的品质。她与主要角色皮埃尔·别祖霍夫关系密切,并在他的个人和情感发展中起着重要作用。
步骤:
- 安装7B量化聊天模型和llama-cpp-python。
- 下载小说,按章节拆分,创建一个Spark
DataFrame
。 - 按章节划分并生成摘要。
安装
配置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
(此代码假设有一个名为spark
的SparkSession
)。
代码应该产生以下输出:
章节数 = 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参数,例如temperature
和top_p
,它们控制结果的“创造性”和随机性,或者提示工程,这实际上是一个独立的学科。我们选择了Llama 2 7B模型,没有给出理由;也许有更小、性能更好的模型或模型系列更适合我们特定的用例。
相反,我们展示了如何使用Spark轻松分发(量化的)LLM工作负载,几乎不需要费力。下一步可能包括:
- 更高效的模型加载/缓存
- 针对不同用例的参数优化
- 自定义提示