NSDT工具推荐Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器 - REVIT导出3D模型插件 - 3D模型语义搜索引擎 - AI模型在线查看 - Three.js虚拟轴心开发包 - 3D模型在线减面 - STL模型在线切割 - 3D道路快速建模

本练习是关于使用 Llama 2(Meta AI 的 LLM)一次总结许多文档。 非结构化、半结构化和结构化文本的可扩展摘要本身可以作为一项功能存在,也可以作为输入下游机器学习模型的数据管道的一部分。

具体来说,我们想证明以下同时存在的可行性:

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

1、如何实现?

我们已经做了很多艰苦的工作!

llama.cpp 项目通过降低数值权重的分辨率(“量化”),可以在 CPU 上运行简化的 LLM。 这些现成的模型文件很容易获得

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

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

2、实施计划

列夫·托尔斯泰巨著《战争与和平》

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

Prompt:
Briefly describe the character Anna Pavlovna from 'War and Peace'

Response:
Anna Pavlovna is a major character in Leo Tolstoy's novel "War and Peace".
She is a wealthy, intelligent, and charming socialite who is known for her beauty and wit.
Despite her beauty and charm, she is also portrayed as being manipulative and cunning, often using her intelligence and wit to get what she wants.
Throughout the novel, Anna Pavlovna is shown to be a complex and multifaceted character, with both admirable and flawed qualities.
She is particularly close to the main character Pierre Bezukhov, and plays an important role in his personal and emotional development.

实施步骤如下:

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

3、安装配置

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

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

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

我们使用的是 Llama 2 的 7B 聊天“Q8”版本,可以在此处找到。 下载链接可能会发生变化,但单节点“裸机”设置类似于以下内容:

# download Llama 2 model
mkdir -p /models && cd /models
wget https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGML/resolve/main/llama-2-7b-chat.ggmlv3.q8_0.bin

# install llama.cpp Python bindings
pip install llama-cpp-python

确保你可以通过 python3 和此示例使用该模型。 回顾一下,每个 Spark 上下文都必须能够从 /models 读取模型并访问 llama-cpp-python 模块。

4、处理小说文本

下面的 Bash 命令下载小说并打印字数统计。

# download "War and Peace" from Project Gutenberg
mkdir -p ~/data
curl "https://gutenberg.org/cache/epub/2600/pg2600.txt" -o ~/data/war_and_peace.txt

# print lines, words, characters
echo "$(cat ~/data/war_and_peace.txt | wc -l) lines"
echo "$(cat ~/data/war_and_peace.txt | wc -w) words"
echo "$(cat ~/data/war_and_peace.txt | wc -c) characters"

接下来,我们用 Python 读取文本文件,删除 Project Gutenberg 页眉和页脚。 我们将拆分正则表达式 CHAPTER .+ 以创建章节字符串列表并从中创建 Spark DataFrame(此代码假设 SparkSession 名为 Spark)。

import re

# read book, remove header/footer
text = open('~/data/war_and_peace.txt', 'r').read()
text = text.split('PROJECT GUTENBERG EBOOK WAR AND PEACE')[1]

# get list of chapter strings
chapter_list = [x for x in re.split('CHAPTER .+', text) if len(x) > 100]

# print stats
print('number of chapters = '+str(len(chapter_list)))
print('max words per chapter = '+str(max([len(c.split(' ')) for c in chapter_list])))

# create Spark dataframe, show it
df = spark.createDataFrame(pd.DataFrame({'text':chapter_list, 
                                         'chapter':range(1,len(chapter_list)+1)}))

df.show(10, 60)

该代码应产生以下输出:

number of chapters = 365
max words per chapter = 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,每行包含完整的章节文本和编号。 最后一步是创建一个新的数据框架,其中包含每章的摘要。

5、Spark处理

下面是生成单章摘要的 Python 代码,请参阅调用 limit(1) 以返回单行。 片段下方的说明:

# this is the function applied per-group by Spark
# the df passed is a *Pandas* dataframe!
def llama2_summarize(df):

    # read model
    from llama_cpp import Llama
    llm = Llama(model_path="/models/llama-2-7b-chat.ggmlv3.q8_0.bin", 
                n_ctx=8192, 
                n_batch=512)

    # template for this model version, see:
    # https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGML#prompt-template-llama-2-chat
    template = """
    [INST] <<SYS>>
    You are a helpful, respectful and honest assistant. 
    Always answer as helpfully as possible, while being safe.  
    Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. 
    Please ensure that your responses are socially unbiased and positive in nature.
    If a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. 
    If you don't know the answer to a question, please don't share false information.
    <</SYS>>
    {INSERT_PROMPT_HERE} [/INST]
    """
    
    # create prompt
    chapter_text = df.iloc[0]['text']
    chapter_num = df.iloc[0]['chapter']
    prompt = 'Summarize the following novel chapter in a single sentence (less than 100 words):' + chapter_text
    prompt = template.replace('INSERT_PROMPT_HERE', prompt)
    
    output = llm(prompt, 
                 max_tokens=-1, 
                 echo=False, 
                 temperature=0.2, 
                 top_p=0.1)

    return pd.DataFrame({'summary': [output['choices'][0]['text']], 
                         'chapter':[int(chapter_num)]})

# create summaries via Spark
summaries = (df
                .limit(1)
                .groupby('chapter')
                .applyInPandas(llama2_summarize, schema='summary string, chapter int')
                .show(vertical=True, truncate=False)
            )

llama2_summarize() 函数是 Spark 按组应用的代码。 由于我们按章节列进行分组,因此会在每个章节行上调用该函数; df 参数只是一个带有单行的 Pandas DataFrame。 请注意,我们正在为每次调用 llama2_summarize() 读取模型; 这是我们为了简单起见而采取的捷径,但效率不高。

最后,我们使用 Spark 执行 groupby() 并调用 applyInPandas(),设置模式以包含章节摘要和编号。

输出为了可读性而重新格式化,如下所示:

summary
The chapter is about a conversation between Prince Vasíli Kurágin and 
Anna Pávlovna Schérer, a well-known socialite and favorite 
of Empress Márya Fëdorovna. 
They are discussing various political matters, including the possibility 
of war with France and Austria's role in the conflict. 
Prince Vasíli is hoping to secure a post for his son through 
the Dowager Empress, while Anna Pávlovna is enthusiastic 
about Russia's potential to save Europe from Napoleon's tyranny. 
The conversation also touches on personal matters,
such as Prince Vasíli's dissatisfaction with his younger son
and Anna Pávlovna's suggestion that he marry off 
his profligate son Anatole to a wealthy heiress.

chapter
1

请注意Nepaleon的使用,尽管它没有出现在本章中!同样,这是一个有趣的练习,而不是使用真正看不见的文档的实际示例。

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

6、结束语

我们甚至没有提到调整标准的LLM参数,如温度和top_p,这些参数控制结果的“创造力”和随机性,或者提示工程,这实际上是一个独立的学科。 我们也毫无道理地选择了Llama 2 7B模型; 可能会有更小、性能更高的模型或模型系列更适合我们的特定用例。

相反,我们展示了如何使用 Spark 以相当小的工作量轻松分配(量化)LLM 工作负载。 后续步骤可能包括:

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

原文链接:Distributed Llama 2 on CPUs

BimAnt翻译整理,转载请标明出处