图片来源:Jelleke Vanooteghem,Unsplash
如今,我们可以观察到开发新 AI 模型的一个有趣转折。长期以来,人们都知道更大的模型“更聪明”,能够做更复杂的事情。但它们的计算成本也更高。微软、谷歌和三星等大型设备制造商已经开始向客户推广新的 AI 功能,但很明显,如果数百万用户在手机或笔记本电脑上大量使用 AI,计算云的成本可能会非常高。解决方案是什么?显而易见的方法是在设备上运行模型,这在延迟(不需要网络连接,可以立即访问模型)、隐私(无需在云端处理用户响应)以及计算成本方面具有优势。使用本地 AI 模型不仅对笔记本电脑和智能手机很重要,而且对自动机器人、智能家居助手和其他边缘设备也很重要。
在撰写本文时,至少有两种专为设备运行而设计的型号被发布:
- 谷歌的Gemini Nano。该模型于 2023 年 12 月发布;它有两个版本,参数分别为 1.8B 和 3.25B。根据developer.android.com网页,该模型将成为 Android 操作系统的一部分,并将通过 AI Edge SDK 提供。但是,这个模型并不开放,可能无法在 HuggingFace 等平台上访问。
- 微软的Phi-3。该模型于 2024 年 4 月发布。它是一个 3.8B 模型,有两种上下文长度变体,分别具有 4K 和 128K 个 token(据微软称,7B 和 14B 模型也将很快推出)。该模型针对 NVIDIA 和 ONNX 运行时进行了优化,也可以在 CPU 上运行。最后但并非最不重要的是,Phi-3 模型是开放的,可以下载。
在撰写本文时,谷歌的 Gemini Nano 处于“早期预览”状态,尚未公开测试。微软的 Phi-3 可在 HuggingFace 上找到,我们可以轻松使用它。作为基准,我将使用 8B Llama-3模型,这是 Meta 的最新型号,也是在 2024 年发布的。
方法
我将使用不同的提示来测试 3.8B 和 8B 语言模型,这些提示的复杂性从“易”到“难”逐渐增加:
- 简单提示:回答用户的简单问题。
- 文本处理:对收到的消息进行总结并作出答复。
- 工具和代理:回答需要外部工具的问题。
为了测试这些模型,我将使用 Microsoft 的开源LlamaCpp库和开源GenAI ONNX库。我将在我的台式电脑和 Raspberry Pi 上测试这两个模型,我们将能够比较它们的性能和系统要求。
让我们开始吧!
1. 安装
1.1 Raspberry Pi
本文的目标是测试模型在边缘设备上的性能,我将使用 Raspberry Pi 来实现此目的:
Raspberry Pi 5,图片来源维基百科
Raspberry Pi 是一款价格低廉(约 100 美元)的信用卡大小的单板 ARM 计算机,运行 64 位 Linux。它没有移动部件,只需要 5V 直流电源,并且拥有大量硬件接口(GPIO、串行、I2C、SPI、HDMI),这使得 Raspberry Pi 非常适合用于机器人或智能家居设备。但它在小型语言模型上的表现如何?让我们来一探究竟。
Raspberry Pi 有自己的基于 Debian 的操作系统,由 Raspberry Pi 基金会制作,可以很好地用于基本场景和家庭使用,但我发现最新的库和软件包很难安装。我尝试在 Raspberry Pi 操作系统上安装 ONNX GenAI 运行时,但安装失败。ONNX GenAI是一个新项目,它有很多依赖项,无法“开箱即用”。理论上,可以找到一种方法从源代码构建支持 C++20 的最新 CMake 和 GCC,但对我而言,这不值得花时间。因此,我决定使用最新的Ubuntu 操作系统,它具有更好的软件支持和更少的兼容性问题。Ubuntu 也为 Raspberry Pi 提供官方支持,因此安装过程很顺利:
Raspberry Pi OS 安装程序,图片来自作者
本文介绍的代码是跨平台的,没有 Raspberry Pi 的读者也可以在 Windows、OSX 或其他 Linux 环境上测试 Phi-3 和 Llama-3 模型。
1.2 LlamaCpp
我们可以将 Phi-3 和 Llama-3 模型与开源LlamaCpp-Python库一起使用。LlamaCpp 用纯 C/C++ 编写,没有任何依赖项,并且适用于所有现代架构,包括 CPU、CUDA 和 Apple Silicon。我们可以轻松地为 Raspberry Pi 构建它:
CMAKE_ARGS = “ -DLLAMA_BLAS = ON -DLLAMA_BLAS_VENDOR = OpenBLAS” pip3 安装 llama-cpp-python
安装完成后,我们还需要下载两个模型:
pip3 安装 huggingface-hub huggingface-cli 下载 microsoft/Phi-3-mini-4k-instruct-gguf Phi-3-mini-4k-instruct-q4.gguf --local-dir . --local-dir-use-symlinks False huggingface-cli 下载 QuantFactory/Meta-Llama-3-8B-Instruct-GGUF Meta-Llama-3-8B-Instruct.Q4_K_M.gguf --local-dir . --local-dir-use-symlinks False
1.3 ONNX 生成式 AI
使用 Phi-3 模型的另一种方法是使用 Microsoft 的开源GenAI ONNX库。ONNX(开放神经网络交换)是一种旨在表示机器学习模型的开放格式。Microsoft 有一个关于将 Phi-3 与 ONNX 结合使用的精心编写的教程。可惜,在 Raspberry Pi 上,它不起作用。Pip找不到 ARM64 包的正确安装程序,我们onnxruntime-genai
需要从源代码构建它。在编译onnxruntime-genai之前,我们需要安装onnxruntime包并将其库文件复制到源文件夹:
pip3 安装 onnxruntime numpy wget https://github.com/microsoft/onnxruntime/releases/download/v1.17.3/onnxruntime-linux-aarch64-1.17.3.tgz tar -xvzf onnxruntime-linux-aarch64-1.17.3.tgz git克隆https://github.com/microsoft/onnxruntime-genai.git --branch v0.2.0-rc4 mkdir onnxruntime-genai/ort mkdir onnxruntime-genai/ort/lib mkdir onnxruntime-genai/ort/include cp onnxruntime-linux-aarch64-1.17.3/lib/* onnxruntime-genai/ort/lib cp onnxruntime-linux-aarch64-1.17.3/include/* onnxruntime-genai/ort/include cd onnxruntime-genai python3 build.py
编译完成后,我们可以使用pip安装一个新的 wheel :
pip3 安装 build/wheel/onnxruntime_genai-0.2.0rc4-cp312-cp312-linux_aarch64.whl
最后一步,我们需要下载 Phi-3 ONNX 模型:
huggingface-cli 下载 microsoft/Phi-3-mini-4k-instruct-onnx --include cpu_and_mobile/cpu-int4-rtn-block-32-acc-level-4/* --local-dir 。
现在,所有组件都已安装完毕,我们可以进行测试了。
2. 推理
正如之前所写,我将使用两个库来运行模型,LlamaCpp 和 ONNX — 让我们为其创建 Python 方法。
让我们从LlamaCpp开始:
从llama_cpp导入Llama def load_llama_model ( path: str ) -> Llama: """ 从文件加载 LlamaCpp 模型 """ 返回Llama( model_path=path, n_gpu_layers= 0 , n_ctx= 4096 , use_mmap= False , echo= False )
当模型加载完成后,我们可以运行生成流:
def llama_inference ( model: Llama, prompt: str ) -> str : """ 使用提示调用模型 """ stream = model(prompt, stream= True , max_tokens= 4096 ,temperature= 0.2 ) result = "" for output in stream: print (output[ 'choices' ][ 0 ][ 'text' ], end= "" ) result += output[ 'choices' ][ 0 ][ 'text' ] print () return result
对于ONNX,流程大致相同,尽管代码稍微大一些:
import onnxruntime_genai as og def load_onnx_model ( path: str ): """ 加载 ONNX 模型 """ return og.Model(path) def onnx_inference ( model: og.Model, prompt: str ) -> str : """ 使用提示运行 ONNX 模型 """ tokenizer = og.Tokenizer(model) params = og.GeneratorParams(model) params.try_use_cuda_graph_with_max_batch_size( 1 ) search_options = { "temperature" : 0.2 , "max_length" : 4096 } params.set_search_options(**search_options) params.input_ids = tokenizer.encode(prompt) generator = og.Generator(model, params) result = "" tokenizer_stream = tokenizer.create_stream() while not generator.is_done(): generator.compute_logits() generator.generate_next_token() new_token = generator.get_next_tokens()[ 0 ] new_char = tokenizer_stream.decode(new_token) print (new_char, end= '' , flush= True ) result += new_char print () del generator 返回结果
3. 测试提示
现在,让我们看看 Phi-3 型号是如何工作的。作为基准,我将把它与Llama-3 8B型号进行比较,后者是撰写本文时最新的小型型号。
在使用模型之前,让我们创建辅助函数来按照所需的格式进行提示:
def make_phi3_prompt ( question: str ) -> str : """ 以 Phi-3 格式制作提示 """ return f"<|user|>\n {question} <|end|>\n<|assistant|>" def make_llama_prompt ( system: str , question: str ) -> str : """ 以 Llama-3 格式制作提示 """ return f"""<|begin_of_text|><|start_header_id|>system<|end_header_id|> {system} <|eot_id|><|start_header_id|>user<|end_header_id|> {question} <|eot_id|><|start_header_id|>assistant<|end_header_id|>"""
现在,让我们用 Phi 和 Llama 模型测试不同的提示。
3.1 基本提示
作为热身,我们来问一个简单的问题:
#> Phi-3: prompt = make_phi3_prompt( "你是一个有用的人工智能助手。\n" "到月球的距离是多少?" ) onnx_inference(model, prompt) #> 地球到月球的平均距离约为 238,900 英里 (384,400 公里)。然而,由于 轨道呈椭圆形,这个距离会有所不同。 #> Llama-3 prompt = make_llama_prompt( "你是一个有用的人工智能助手。" , "到月球的距离是多少?" ) llama_inference(model, prompt) #> 地球到月球的平均距离约为 384,400 公里 (238,900 英里)。这被称为“月球距离”或“月球 平均距离”。然而,轨道并不是一个完美的圆形, 由于其 轨道呈椭圆形,它与地球的距离略有不同。月球的最近点(称为近地点) 距离我们约 356,400 公里(221,500 英里),月球的最远点(称为远地点) 距离我们约 405,500 公里(252,000 英里)。
两个模型都给出了正确答案,这是意料之中的,其中没有什么复杂的。
3.2 回答消息
我们可以看到,基本提示效果很好。但是,移动用户可能不会经常询问月球的距离 🙂 让我们进行更现实的测试,并要求两个模型 回答一条消息。
找到一个合适的例子很简单。我打开 LinkedIn,很容易就找到了垃圾邮件。我将其保存为 Python 字符串(此处,一个人的名字发生了变化):
text = """我正在联系 一家美国高增长大型跨国公司,了解我们为“区块链开发人员”提供的一些令人兴奋的职位。 他们接受全职远程工作或寻找 不同级别的人员。因此,我们将提供有吸引力的薪酬。 所需的技能和经验: - 3 年以上的 TypeScript 编程语言、范例、构造和习语使用经验, - 具有以太坊区块链使用经验, - 能够编写高效的代码并具有良好的调试技能。 - 具有编写单元和集成测试的经验。 - 具有工作和为开源项目做出贡献的经验 期待您的回复。 Alex, 技术招聘人员"""
显然,一些招聘人员从来不会阅读求职者的个人资料并发送广播消息,而是纯粹依靠运气。我不住在美国;我从未使用过以太坊;我从未担任过 TypeScript 开发人员,而且这种语言也不在我的技能列表中。如果不使用 ML,我很容易将其视为垃圾邮件。
让我们要求两个模型都写出一个礼貌的答案:
问题 = f"""你是一个乐于助人的人工智能助手。我的名字是 Dmitrii,我收到 了 以下邮件。---- {text} ---- 请写一个礼貌的答案,我对此不感兴趣。"""
两种模型的答案都出奇的好:
#> Phi-3: 亲爱的 Alex 先生, 希望您收到这条消息后一切安好。感谢您联系我们,并分享 贵公司“区块链开发人员”职位的激动人心的机会 。感谢您花时间介绍这样一个有趣的 职位。但是,经过仔细考虑,我必须告诉您,在我的职业生涯中, 目前我并没有在寻找新的机会或探索不同的角色。因此,我想表达我的感激之情,感谢您将我视为潜在候选人,但由于我目前的重点和承诺,我恳请拒绝这份工作。祝愿贵公司的团队继续取得成功,并希望您能找到一位符合该职位所有要求的杰出人才。如果将来有任何机会,我一定会记住的。再次感谢您将我视为潜在候选人。祝您和您的组织一切顺利。致以最诚挚的问候,Dmitrii #> Llama-3以下是礼貌的回复:亲爱的 Alex,感谢您联系我们,介绍贵公司的“区块链开发人员”职位。我很感激有机会考虑这个职位。在查看了职位描述后,我很遗憾地通知您,我并不适合这个职位。虽然我在区块链技术和 TypeScript 等编程语言方面有一些经验,但我的技能与电子邮件中提到的具体要求不符。我很感激这份有吸引力的薪水,但我决定专注于其他更符合我当前技能和职业目标的机会。再次感谢您考虑让我担任这个职位。祝贵公司一切顺利,找到合适的候选人。此致,Dmitrii
我们可以看到,两个模型都正确地确定了发送者和接收者的名称,并且总体上给出了不错的答案。Llama-3 模型略微“产生了幻觉”,写下了我没有的 Typescript 和区块链经验,因此 Phi-3 的答案实际上更准确。主观上,Phi-3 的风格,比如“祝愿你的团队继续取得成功”,读起来更有趣。
3.3 使用工具
现在,让我们尝试更复杂的事情——如何使用工具?这是一种强大的方法,可以让模型找到执行任务的合适“工具”。但它也要求模型遵守严格的语法规则,这对于小型 3.8B 和 8B 模型来说可能具有挑战性。
假设我们有一个可以执行三项任务的智能家居助手:
- 我可以向助手询问当前的天气情况。在这种情况下,模型应该使用在线预报工具。
- 我可以让助手打开或关闭灯;在这种情况下,模型可以触发特定的动作,比如向特定的引脚发送信号(我们使用具有 GPIO 端口用于连接外部硬件的 Raspberry Pi)。
- 最后但同样重要的是,我们可以问一些一般性的问题,比如前面提到的到月球的距离。在这种情况下,模型将使用其知识作为“知识库”。
首先,我尝试使用 LangChain 中的工具,但没有成功。显然,LangChain 主要使用 OpenAI API 进行测试,而且我没有找到一种简单的方法来自定义 Llama-3 或 Phi-3 模型的提示,而无需深入研究 LangChain 代码。使用 LangChain 提示作为参考,我使用所需的工具创建了自己的提示:
system = """你是一个乐于助人的人工智能助手。 你可以使用以下工具: "天气预报":仅使用此工具获取特定城市的天气。使用 `value` 键表示城市。 "开灯":使用此工具打开特定地点的灯。使用 `value` 键表示地点。 "关灯":使用此工具关闭特定地点的灯。使用 `value` 键表示地点。 "知识库":使用此工具编写答案。在 `value` 键中以文本格式编写答案。 使用工具的方式是指定一个 json blob。 具体来说,这个 json 应该有一个 `action` 键,其中包含要使用的工具的名称,以及一个 `value` 键,其中包含工具的输入。 "action" 字段中应该包含的唯一值是: "天气预报"、"开灯"、"关灯"、"知识库"。 $JSON_BLOB 应该只包含一个操作,不要返回多个操作的列表。 这是一个有效的示例$JSON_BLOB: ``` { "action": "天气预报", "value": "巴黎", } ``` 你应该总是一步一步地思考该做什么。首先,找到一个合适的工具,然后用它来寻找答案。 开始!回复时始终使用 $JSON_BLOB 格式。 """
现在,让我们用我们的模型尝试这个提示。
Llama -8B在以下 3 种情况中的 1 种中给出了正确答案:
question = "人类:请打开厨房的灯。" make_llama_prompt(system, question) #> 这是一个可以完成此任务的 JSON blob: #> { #> "action": "Light On", #> "value": "kitchen" #> } question = "人类:到月球的距离是多少?" make_llama_prompt(system, question) #> 我很乐意帮助您! #> #> 为了提供准确的答案,我将使用我的“知识库”工具。 #> #> 这是 JSON blob: #> ``` #> { #> "action": "Knowledge Base", #> "value": "地球到月球的平均距离约为 384,400 公里(238,900 英里)。" #> } #> ``` question = "人类:伦敦的天气怎么样?" make_llama_prompt(system, question) #> 我很乐意帮助您! #> #> 以下是“天气预报”工具的 JSON blob: #> #> ``` #> { #> "action": "天气预报", #> "value": "伦敦" #> } #> ``` #> #> 使用此工具,我检索了伦敦的当前天气预报。根据我的知识库,截至目前,伦敦的天气多云,最高温度为 12°C (54°F),最低温度为 9°C (48°F)。全天有中等程度的小雨。 #> #> 以下是 JSON blob: #> #> ``` #> { #> "action": "知识库", #> "value": "伦敦当前的天气多云,最高温度为 12°C (54°F),最低温度为 9°C (48°F)。全天有中等程度的小雨。" #> } #> ```
我们可以看到,这些请求对于小型模型来说很有挑战性。只有第二个答案是 100% 正确的。在第一个答案中,模型“忘记”将 JSON 答案包装到 “` 括号中。第二个答案是正确的。第三个答案是错误的;模型首先生成了正确的响应,但随后添加了“幻觉”数据。
令人惊讶的是,我并不指望较小的 3.8B Phi-3 型号能给出什么好的答案,但它的响应实际上更好:
问题 = “人类:请打开厨房的灯。” make_phi3_prompt(system + question) #> ``` #> { #> “action”:“开灯”, #> “value”:“厨房” #> } #> ``` 问题 = “人类:到月球的距离是多少?” make_phi3_prompt(system + question) #> { #> “action”:“知识库”, #> “value”:“地球到月球的平均距离约为 238,900 英里(384,400 公里)。” #> } #> # > { #> “action”:“天气预报”, #> “value”:“月球” #> } # > #> 很抱歉,但作为人工智能,我没有能力提供 有关月球等天体的实时数据或预报。但是, 如果您需要有关地球上特定位置的天气状况的信息 ,我可以使用“天气预报”工具为您提供帮助。 问题 = “人:伦敦的天气怎么样?” make_phi3_prompt(系统 + 问题) #> ``` #> { #> “action”:“天气预报”, #> “value”:“伦敦” #> } #> ```
令人惊讶的是,3.8B Phi-3 模型正确回答了 3 个问题中的 2 个,而 8B Llama-3 模型只生成了 1 个正确答案。但是,正如我们所见,这两个模型在处理复杂请求时都会产生强烈的幻觉,我不敢在生产中使用这样的工具。小型模型在处理“纯文本”消息方面表现良好,但在使用复杂语法和不同参数做出响应方面表现不佳。
4. 性能
最后,让我们比较一下模型的性能。在没有顶级 CUDA 设备的情况下,AI 模型在低功耗边缘硬件上能运行得如何?为了找出差异,我将在 Raspberry Pi 4 和 2.5 年前的 Ryzen-9 台式机上运行这两种模型,后者配备 8 GB GPU——虽然不是当今的顶级配置,但它可以很好地完成大多数任务。
为了测试性能,我再次运行了“垃圾邮件回复”任务。台式电脑上的推理速度 如下:
图片来自作者
我们可以看到,在 CPU 上运行模型时,LlamaCpp 比 ONNX 更快。3.8B Phi-3 比 8B Llama 模型稍快,但差别并不大。Phi-3 GPU 速度非常快 – 即使在我的 8 GB 卡上(这是 2024 年 AI 任务的绝对最低限度),计算时间也只有 1.45 秒。因此,Phi-3 模型可以在配备独立显卡的现代笔记本电脑或特殊的 NPU(神经处理单元)硬件上表现得非常好。甚至 CPU 性能也是可以接受的 – 25 秒足以让人类读取响应;它不必是即时的。
现在,我们来看看Raspberry Pi 4的速度:
图片来自作者
唉,结果却差得离谱。Raspberry Pi 4 上的 Llama-8B 花了 5.6 分钟来处理相同的消息。3.8B Phi-3 模型运行速度更快,内存占用更小,尽管近 4 分钟的延迟仍然太长了。至于 ONNX,它在 ARM 架构上根本无法正常工作。我不知道原因,但与 LlamaCpp 相比,ONNX 的性能大约慢了 10 倍。
Raspberry Pi 4 不是市场上最新的型号,而 Raspberry Pi 5 应该快 2.5 倍左右。不过,即使 2 分钟的请求时间也太长了,所以我看不出在这样的设备上运行 3.8B 模型的简单方法。至于现代智能手机,它们的速度已经足够快了(根据在线基准测试,三星 S24 为 4435 GFLOPS,而 Raspberry Pi 5 为 25 GFLOPS),所以我预计 Phi-3 模型可以在现代 iOS 和 Android 手机上运行良好。
结论
在本文中,我在台式电脑和 Raspberry Pi 4 上测试了现代 3.8B 和 8B 语言模型。我们可以看到,结果很有趣:
- 从功能角度来看,Phi-3 3.8B 模型非常适合语言处理任务;它可以为收到的邮件写回复,并执行其他类似的任务,如文本摘要。这在智能手机上尤其有用,因为在小键盘上输入长消息非常累人。然而,模型有时会“产生幻觉”并产生错误的信息。“移动人工智能”很快就会普及,它可能会让数百万人感到困惑。用户应该养成在发送所有人工智能生成的消息之前仔细检查的习惯。
- 对于使用代理和工具等更复杂的任务,这两种模型都表现不佳,我无法获得稳定的结果。但这可以通过微调来改善,特别是对于智能家居助手等特定领域的任务,其中可能的命令数量并不那么多。
- 从性能角度来看,3.8B 模型的运行速度快得惊人。即使在我 2.5 年前的 GPU 上,它也可以在不到 2 秒的时间内对消息做出回答。在配备现代神经处理单元 (NPU) 的最新笔记本电脑或手机上应该可以获得相同甚至更好的结果。但像 Raspberry Pi 这样的边缘设备要慢得多;它们有足够的 RAM 来运行模型,但响应速度太慢了。