3W6000字了解大模型LLM:部署、优化与框架

==============================

🕙发布时间:2025-02-21

本文3W6000字,分了11个小节介绍各种框架和方法,后续会把最新的方法更新进来~,有些内容是翻译自HuggingFace和一些论文 近日热文:
1. 全网最全的神经网络数学原理(代码和公式)直观解释
2. 大模型进化史:从Transformer到DeepSeek-R1的AI变革之路
3. 2W8000字深度剖析25种RAG变体:全网最全~没有之一
知乎【柏企
公众号【柏企科技说】【柏企阅文

LLM服务指的是部署和运行大型语言模型(LLM)以处理用户请求的过程。这涉及获取通常经过离线训练的LLM,并将其设置为能够实时响应查询。

以下是LLM服务的具体内容细分

  • 高效处理:由于LLM的计算成本高昂,因此会采用诸如将多个用户请求一起批处理的服务技术,以此优化资源利用率并加快响应时间。

  • 模型部署:LLM模型被部署在能够处理相应处理需求的服务器或云平台上。

  • API创建:创建应用程序编程接口(API),以便用户能够与LLM进行交互并发送查询。

  • 基础设施管理:服务系统需要具备可扩展性和可靠性,以应对大量用户的访问,并确保系统持续稳定运行 。

有多种框架可用于LLM服务,每种框架都有其独特优势。接下来让我们详细探讨一下。

1. 在本地运行LLM

PrivateGPT、llama.cpp、Ollama、GPT4All、llamafile等项目的流行,凸显了在本地(即在自己的设备上)运行LLM的需求。

这至少带来两个重要好处:

  • 隐私性:我们的数据不会被发送给第三方,也不受商业服务条款的约束。

  • 成本:不存在推理费用,这对于像长时间运行的模拟、摘要等令牌密集型应用程序而言至关重要。

在本地运行LLM需要满足以下几个条件:

1.1 开源LLM

如今,用户能够访问数量迅速增长的开源LLM。

这些LLM至少可以从两个维度进行评估(见图):

也可以使用多个排行榜来评估这些模型的相对性能,包括:

  • LmSys LM系统

  • GPT4All

  • HuggingFace

为此,已经出现了一些框架,用于支持在各种设备上对开源LLM进行推理:

  • llama.cpp:使用C++ 并通过权重优化/量化实现LLAMA推理代码。

  • gpt4all:优化的C后端,用于推理。

  • Ollama:将模型权重和环境捆绑到一个应用程序中,该应用程序可在设备上运行并提供LLM服务。

  • llamafile:把模型权重和运行模型所需的所有内容捆绑在一个文件中,这样我们无需任何额外安装步骤,就能从此文件在本地运行LLM。

通常,这些框架会执行以下操作:

2. 有效加载LLM

现在,我们将探索如何通过几种(量化)标准加载本地LLM。由于存在分片、量化以及不同的保存和压缩策略,想要确定哪种方法适合自己并非易事。

在所有示例中,我们将使用Zephyr 7B,它是Mistral 7B的微调变体,使用直接偏好优化(DPO)进行训练。

🔥 提示:在每个加载LLM的示例之后,建议重新启动笔记本,以防止出现OutOfMemory错误。加载多个LLM需要大量的RAM/VRAM。我们可以通过删除模型并重置缓存来重置内存,具体操作如下:

  
`del model, tokenizer, pipe   import torch   torch.cuda.empty_cache()   `

2.1 HuggingFace

加载LLM最直接、最常规的方法是通过🤗 Transformers。HuggingFace创建了一大套软件包,让我们能够使用LLM完成许多出色的任务!

我们首先从其主分支安装HuggingFace及相关依赖,以支持更新的模型:

  
`!pip install git+https://github.com/huggingface/transformers.git   !pip install accelerate bitsandbytes xformers   `

安装完成后,我们可以使用以下管道轻松加载LLM:

  
`from torch import bfloat16   from transformers import pipeline      pipe = pipeline(       "text-generation",       model="HuggingFaceH4/zephyr-7b-beta",       torch_dtype=bfloat16,       device_map="auto"   )   `

这种加载LLM的方法通常不会执行任何压缩技巧来节省VRAM或提高效率。

要生成提示(prompt),我们首先必须创建必要的模板。幸运的是,如果聊天模板保存在底层分词器中,就可以自动完成此操作:

  
`messages = [       {           "role": "system",           "content": "You are a friendly chatbot."       },       {           "role": "user",           "content": "Tell me a funny joke about Large Language Models."       }   ]   prompt = pipe.tokenizer.apply_chat_template(       messages,       tokenize=False,       add_generation_prompt=True   )   `

使用内部提示模板生成的提示构造如下:

然后,我们可以开始将提示传递给LLM以生成答案:

  
`outputs = pipe(       prompt,       max_new_tokens=256,       do_sample=True,       temperature=0.1,       top_p=0.95   )   print(outputs[0]["generated_text"])   `

这将给我们带来以下输出: 为什么大型语言模型要参加派对? 为了社交并扩大它的词汇量!

这个笑点可能有点俗,但LLM的核心就在于扩大词汇量并与其他模型交流以提升语言技能。所以,这个笑话和它们很契合!

对于纯推理而言,这种方法通常效率最低,因为我们在加载整个模型时未采用任何压缩或量化策略。不过,它是一个很好的入门方法,因为它便于加载和使用模型!

2.2 LangChain

我们在本地运行LLM的另一种方式是使用LangChain。LangChain是一个用于构建AI应用程序的Python框架。它提供了抽象层和中间件,使我们能够在其支持的模型之一的基础上开发AI应用程序。例如,以下代码向microsoft/DialoGPT – medium模型提出一个问题:

  
`from langchain.llms.huggingface_pipeline import HuggingFacePipeline   hf = HuggingFacePipeline.from_model_id(       model_id="microsoft/DialoGPT-medium", task="text-generation", pipeline_kwargs={"max_new_tokens": 200, "pad_token_id": 50256}   )   from langchain.prompts import PromptTemplate   template = """Question: {question}   Answer: Let's think step by step."""   prompt = PromptTemplate.from_template(template)   chain = prompt | hf   question = "What is electroencephalography?"   print(chain.invoke({"question": question}))   `

LangChain优点

LangChain缺点

2.3 Llama.cpp

Llama.cpp是一个基于C和C++的LLM推理引擎,针对Apple芯片进行了优化,可运行Meta的Llama2模型。

克隆存储库并构建项目后,我们可以使用以下命令运行模型:

  
`$ ./main -m /path/to/model-file.gguf -p "Hi there!"   `

Llama.cpp优点

  • 比基于Python的解决方案性能更高。

  • 在适度的硬件上支持Llama 7B等大型模型。

  • 提供绑定,以便在通过Llama.cpp运行推理时,使用其他语言构建AI应用程序。

Llama.cpp缺点

  • 模型支持有限。

  • 需要构建工具。

2.4 Llamafile

由Mozilla开发的Llamafile为运行LLM提供了一种用户友好的替代方案。Llamafile以其可移植性和创建单文件可执行文件的能力而闻名。

下载llamafile和任何GGUF格式的模型后,我们可以使用以下命令启动本地浏览器会话:

  
`$ ./llamafile -m /path/to/model.gguf   `

Llamafile优点

Llamafile缺点

2.5 Ollama

Ollama是比Llama.cpp和Llamafile更用户友好的替代品。下载一个可执行文件,它会在您的机器上安装一个服务。安装完成后,打开终端并运行:

  
`$ ollama run llama2   `

Ollama将下载模型并启动交互式会话。

Ollama优点

Ollama缺点

  • 提供的模型库有限。

  • 自行管理模型,无法重复使用自己的模型。

  • 运行LLM时没有可调选项。

  • 目前还没有Windows版本。

2.6 GPT4ALL

GPT4ALL是一款易于使用的桌面应用程序,具有直观的图形用户界面(GUI)。它支持本地模型运行,并通过API密钥提供与OpenAI的连接。它以能够处理本地文档以获取上下文、确保隐私的能力而脱颖而出。

优点

缺点

2.7 分片

在讨论量化策略之前,还有另一种技巧可用于减少加载模型所需的VRAM,那就是分片。通过分片,我们实际上是将模型拆分成小块或分片。

每个分片包含模型的较小部分,旨在通过在不同设备之间分配模型权重来解决GPU内存限制问题。

我们加载的模型Zephyr – 7B – β已经为我们进行了分片。

虽然我们可以自己对模型进行分片,但通常建议留意量化模型,甚至自己进行量化。

使用Accelerate包进行分片非常简单:

  
`from accelerate import Accelerator   accelerator = Accelerator()   accelerator.save_model(       model=pipe.model,       save_directory="/content/model",       max_shard_size="4GB"   )   `

就是这样!因为我们将模型分片为4GB的块,而不是2GB,所以创建的加载文件更少。

2.8 用Bitsandbytes进行量化

LLM由一系列权重和激活值表示。这些值通常用常见的32位浮点(float32)数据类型表示。

比特数能表明它可以表示多少个值。Float32可以表示介于到之间的值,范围很广!比特数越低,能表示的值就越少。

正如我们所料,如果选择较低的比特大小,模型的准确性会降低,但同时它需要表示的值也更少,从而减小了模型的大小和内存需求。

量化指的是将LLM从其原始的Float32表示转换为更小的数据表示形式。然而,我们不只是简单地使用较小比特变体,而是要在不丢失太多信息的情况下,将较大比特表示映射到较小比特。

在实践中,我们经常会看到一种名为4bit – NormalFloat(NF4)的新格式用于量化。这种数据类型采用了一些特殊技巧来高效表示较大比特的数据类型,它包含三个步骤:

  • 归一化:对模型的权重进行归一化处理,使我们期望权重落在特定范围内。这有助于更有效地表示常见值。

  • 量化:将权重量化为4位。在NF4中,量化级别是根据归一化后的权重均匀分布的,从而有效地表示原始的32位权重。

  • 反量化:尽管权重以4位存储,但在计算过程中会进行反量化,这在推理时能够提高性能 。

要使用HuggingFace进行这种量化,我们需要使用Bitsandbytes定义量化配置:

  
`from transformers import BitsAndBytesConfig   from torch import bfloat16      bnb_config = BitsAndBytesConfig(       load_in_4bit=True,       bnb_4bit_quant_type='nf4',       bnb_4bit_use_double_quant=True,       bnb_4bit_compute_dtype=bfloat16   )   `

这个配置允许我们指定要采用的量化级别。通常,我们希望用4位量化表示权重,但在16位下进行推理。

然后,在管道中加载模型就很简单了:

  
`from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline      tokenizer = AutoTokenizer.from_pretrained("HuggingFaceH4/zephyr-7b-alpha")   model = AutoModelForCausalLM.from_pretrained(       "HuggingFaceH4/zephyr-7b-alpha",       quantization_config=bnb_config,       device_map='auto'   )   pipe = pipeline(model=model, tokenizer=tokenizer, task='text-generation')   `

接下来,我们可以使用与之前相同的提示:

  
`outputs = pipe(       prompt,       max_new_tokens=256,       do_sample=True,       temperature=0.7,       top_p=0.95   )   print(outputs[0]["generated_text"])   `

这将给我们带来以下输出: 为什么大型语言模型要参加派对?

量化是一种强大的技术,可在保持性能相似的同时减少模型的内存需求。它使得即使使用较小的GPU,也能够更快地加载、使用和微调LLM。

2.9 预量化(GPTQ、AWQ和GGUF的对比)

量化模型有多种不同的形式和大小。最值得注意的是,GPTQ、GGUF和AWQ格式是最常用于执行4位量化的格式。

  • GPTQ:GPT模型的训练后量化GPTQ是一种用于4位量化的训练后量化(PTQ)方法,主要侧重于GPU推理和性能优化。

该方法的核心思想是通过最小化与权重的均方误差,尝试将所有权重压缩为4位量化。在推理过程中,它会动态地将权重反量化为float16,以在保持低内存占用的同时提高性能。

如果想深入了解GPTQ的内部工作原理,一定要查看这篇文章:4-bit Quantization with GPTQ

我们首先安装在HuggingFace Transformers中加载类似GPTQ模型所需的一些包:

  
`pip install optimum   pip install auto-gptq --extra-index-url https://huggingface.github.io/autogptq-index/whl/cu118/   `

安装完成后,我们可以导航到想要加载的模型,即“TheBloke/zephyr – 7B – beta – GPTQ”,并选择特定的版本。

这些版本本质上表明了量化方法、压缩级别、模型大小等信息。

  
`from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline      model_id = "TheBloke/zephyr-7B-beta-GPTQ"   tokenizer = AutoTokenizer.from_pretrained(model_id, use_fast=True)   model = AutoModelForCausalLM.from_pretrained(       model_id,       device_map="auto",       trust_remote_code=False,       revision="main"   )   pipe = pipeline(model=model, tokenizer=tokenizer, task='text-generation')   `

尽管我们安装了一些额外的依赖项,但仍然可以使用与之前相同的管道,这是使用GPTQ的一大优势。

加载模型后,我们可以按如下方式运行提示:

  
`outputs = pipe(       prompt,       max_new_tokens=256,       do_sample=True,       temperature=0.1,       top_p=0.95   )   print(outputs[0]["generated_text"])   `

这将给我们带来以下生成的文本: 为什么大型语言模型要参加派对?

当然是为了展示它的智慧和魅力!

但不幸的是,它在人群中迷路了,找不到回到主人身边的路。派对参与者对它能如此无缝地融入人群的能力印象深刻,但大型语言模型却很困惑,只想回家。最后,一群人发现了它,认出了它独特的风格,并把它带回了它该在的地方。从那时起,大型语言模型在所有派对上都确保戴上名牌,只是为了安全起见。

GPTQ是最常用的压缩方法,因为它针对GPU使用进行了优化。如果GPU无法处理如此大的模型,从GPTQ开始,再切换到专注于CPU的方法(如GGUF)是值得的。

  • GGUF:GPT生成的统一格式虽然GPTQ的压缩效果很好,但如果我们的硬件有限,它对GPU的专注可能会成为一个缺点。

GGUF(以前称为GGML)是一种量化方法,允许用户使用CPU运行LLM,同时也可以将部分层卸载到GPU以提高速度。

虽然在推理时使用CPU通常比使用GPU慢,但对于在CPU或Apple设备上运行模型的人来说,这是一种非常出色的格式。特别是随着越来越小且功能越来越强大的模型(如Mistral 7B)的出现,GGUF格式可能会一直存在!

使用ctransformers包来使用GGUF相当简单,我们首先需要安装这个包:

  
`pip install ctransformers[cuda]   `

安装完成后,我们可以导航到想要加载的模型,即“TheBloke/zephyr – 7B – beta – GGUF”,并选择特定的文件。

与GPTQ类似,这些文件表明了量化方法、压缩级别、模型大小等信息。

我们使用“zephyr – 7b – beta.Q4_K_M.gguf”,因为我们专注于4位量化:

  
`from ctransformers import AutoModelForCausalLM   from transformers import AutoTokenizer, pipeline      model = AutoModelForCausalLM.from_pretrained(       "TheBloke/zephyr-7B-beta-GGUF",       model_file="zephyr-7b-beta.Q4_K_M.gguf",       model_type="mistral", gpu_layers=50, hf=True   )   tokenizer = AutoTokenizer.from_pretrained(       "HuggingFaceH4/zephyr-7b-beta", use_fast=True   )   pipe = pipeline(model=model, tokenizer=tokenizer, task='text-generation')   `

加载模型后,我们可以按如下方式运行提示:

  
`outputs = pipe(prompt, max_new_tokens=256)   print(outputs[0]["generated_text"])   `

这给我们带来了以下输出: 为什么大型语言模型要参加派对?为了用它的词汇量给每个人留下深刻印象!但不幸的是,它一遍又一遍地讲同样的笑话,让每个人都唉声叹气、翻白眼。派对参与者很快意识到,这个大型语言模型更像是个扫兴的人,而不是派对动物。故事的寓意是:仅仅因为一个大型语言模型能生成很多单词,并不意味着它知道如何变得有趣或有娱乐性。有时候,少即是多!

如果我们在GPU资源不足且没有最新最好的GPU时,想同时利用CPU和GPU,GGUF是一种很棒的格式。

  • AWQ:激活感知权重量化AWQ(Activation-aware Weight Quantization,激活感知权重量化)是一种新出现的格式,它是一种与GPTQ类似的量化方法。AWQ和GPTQ在方法上有一些区别,但最重要的一点是,AWQ认为并非所有权重对LLM的性能都同等重要。

换句话说,在量化过程中会跳过一小部分权重,这有助于减少量化损失。

因此,他们的论文提到,与GPTQ相比,AWQ在保持相似甚至有时更好的性能的同时,速度有显著提升。

这种方法仍然相对较新,尚未像GPTQ和GGUF那样被广泛采用,所以看看这些方法是否能够共存是很有趣的。

对于AWQ,我们将使用vLLM包,至少以我的经验来看,这是使用AWQ阻力最小的途径:

  
`pip install vllm   `

有了vLLM,加载和使用我们的模型变得毫不费力:

  
`from vllm import LLM, SamplingParams      sampling_params = SamplingParams(temperature=0.0, top_p=1.0, max_tokens=256)   llm = LLM(       model="TheBloke/zephyr-7B-beta-AWQ",       quantization='awq',       dtype='half',       gpu_memory_utilization=.95,       max_model_len=4096   )   `

然后,我们可以使用.generate轻松运行模型:

  
`output = llm.generate(prompt, sampling_params)   print(output[0].outputs[0].text)   `

这给我们带来了以下输出: 为什么大型语言模型要参加派对?为了社交和扩大它的词汇量!为什么大型语言模型脸红了?因为它无意中听到另一个模型说它有点太啰嗦了!为什么大型语言模型被赶出了图书馆?因为它太吵了,不停地用没完没了的闲聊打断其他模型的对话!……

3. 推理优化

堆叠变压器层以创建大型模型,能带来更高的准确率、少样本学习能力,甚至在广泛的语言任务中展现出接近人类的涌现能力。这些基础模型训练成本高昂,在推理时(持续成本)可能会占用大量内存和计算资源。如今,最受欢迎的大型语言模型(LLM)规模可达数十亿至数百亿参数,并且根据用例的不同,可能需要处理长输入(或上下文),这也会增加成本。例如,检索增强生成(RAG)管道需要将大量信息输入到模型中,大大增加了LLM必须完成的处理工作。

本文讨论了LLM推理中最紧迫的挑战以及一些实用的解决方案。读者应该对变压器架构和注意力机制有基本的了解。理解LLM推理的复杂性至关重要,我们将在下一节中讨论。

4. 理解LLM推理

大多数流行的仅解码器LLM(例如GPT-3)是在因果建模目标上进行预训练的,本质上是作为下一个单词预测器。这些LLM将一系列标记作为输入,并自回归地生成后续标记,直到满足停止条件(例如生成的标记数量限制或停止词列表),或者生成一个特殊的标记,表示生成结束。这个过程涉及两个阶段:预填充阶段和解码阶段。

请注意,标记是模型处理的语言基本单元。一个标记大约相当于四个英文字符。自然语言中的所有输入在输入到模型之前都要转换为标记。

  • 4.1 预填充阶段或处理输入在预填充阶段,LLM处理输入标记以计算中间状态(键和值),这些中间状态用于生成 “第一个” 新标记。每个新标记都依赖于所有先前的标记,但由于输入的完整内容是已知的,从高层次来看,这是一个高度并行化的矩阵乘法操作。它有效地使GPU达到饱和利用状态。

  • 4.2 解码阶段或生成输出在解码阶段,LLM自回归地一次生成一个输出标记,直到满足停止条件。每个连续的输出标记都需要知道之前所有迭代的输出状态(键和值)。这就像是一个矩阵向量操作,与预填充阶段相比,它没有充分利用GPU的计算能力。数据(权重、键、值、激活值)从内存传输到GPU的速度决定了延迟,而不是实际计算的速度。换句话说,这是一个受内存限制的操作。

本文中提到的许多推理挑战和相应的解决方案都涉及对这个解码阶段的优化:高效的注意力模块、有效地管理键和值等等。

不同的LLM可能使用不同的分词器,因此,在它们之间比较输出标记可能并不简单。在比较推理吞吐量时,即使两个LLM每秒输出的标记数量相似,但如果它们使用不同的分词器,它们也可能并不等效。这是因为相应的标记可能代表不同数量的字符。

  • 4.3 请求批处理LLM服务的一个重要方面是对用户请求进行批处理。一种高效的方法是将参数一次性加载到GPU上,并利用它们一次性处理尽可能多的输入序列,而不是为每个新请求重新加载参数。这种方法不仅提高了服务器的吞吐量,优化了计算资源的利用,还显著提高了成本效益。然而,采用一种简单的方法,比如在处理批次之前等待固定数量的用户请求积累,会带来一些挑战。这意味着每个请求在批次内生成序列结束标记的时间不同。因此,批次计算速度受到最长生成时间的限制,导致用户等待时间(延迟)不理想。序列完成时间的差异导致GPU利用率不足,降低了批处理预期的效率提升。

  • 静态批处理概述由于我们提到的所有这些挑战,连续批处理被提出来解决这些问题。

  • 4.4 连续批处理连续批处理是一种专为LLM设计的批调度类型。与动态批处理(根据配置的时间阈值和最大批大小动态确定批大小)相比,连续批处理允许新请求在下一个解码器周期加入当前批次,而不是等待当前批次结束。由于LLM的自回归生成过程,这种方法对LLM很适用,并且可以显著提高模型的吞吐量。

连续批处理对于动态批处理请求非常有用。然而,我们也面临另一个问题:内存限制。考虑我们的聊天机器人场景,一个用户可能用一个句子提出问题,而另一个用户可能向我们的应用程序发送一段文字,我们无法假设输入(和输出)序列的长度。这种不确定性给我们带来了内存消耗的关键问题。在不知道序列确切内存需求的情况下,人们不得不采用最坏情况的假设,为整个批次预留尽可能多的内存。

问题在于:GPU的内存是有限的,需要为以下两方面留出空间: – 模型参数 – 用户请求计算(KV缓存)以进行整个批次的计算

如果不进行优化,这些会占用大量空间,迫使我们缩小批大小,遗憾的是,这会降低吞吐量。但我们希望有高吞吐量。我们该如何优化呢?内存是关键。

让我们从内存的角度更深入地了解解码过程中发生了什么。LLM的生成过程从处理输入序列开始,并以自回归的方式逐个生成下一个标记(见下图)。这个生成过程包括自注意力计算,它需要目前为止处理的每个标记的所有键值(KV)分数计算。为了说明这一点,对于标记t的生成,我们需要来自标记t – 1、t – 2、…、1的计算出的键和值。

  • LLM的自回归生成过程

为了优化递归计算,引入了KV缓存的概念。这种方法旨在将之前计算的标记的K和V张量存储在解码器中,随后在接下来的迭代中重用它们。然而,这种优化策略是以增加内存空间为代价的,当为了提高吞吐量而增大批大小时,这一点非常关键。由于序列长度不可预测,这个挑战更加严峻,导致传统的注意力机制由于碎片化和过度分配,会造成60% – 80%的显著内存浪费。

  • 4.5 PagedAttention:以内存为中心的解决方案为了克服这个挑战,提出了PagedAttention。它从传统操作系统(OS)管理内存碎片化和共享的策略中获得灵感,PagedAttention使用带有分页的虚拟内存方法。它允许键和值向量存储在不连续的内存空间中。这使得键和值向量可以驻留在不连续的内存空间中,并组织成块。每个块容纳固定数量标记的注意力键和值。在执行计算时,PagedAttention内核会高效地识别并获取这些块。

  • 4.6 键值缓存解码阶段的一种常见优化方法是KV缓存。解码阶段在每个时间步生成一个标记,但每个标记都依赖于所有先前标记的键和值张量(包括在预填充阶段计算的输入标记的KV张量,以及到当前时间步计算的任何新KV张量)。

为了避免在每个时间步为所有标记重新计算这些张量,可以将它们缓存在GPU内存中。每次迭代时,新计算的元素会简单地添加到运行中的缓存中,以供下一次迭代使用。在一些实现中,模型的每一层都有一个KV缓存。

  • 4.6.1 LLM内存需求实际上,GPU上LLM内存需求的两个主要因素是模型权重和KV缓存。

  • 模型权重:内存被模型参数占用。例如,一个有70亿参数的模型(如Llama 2 7B),以16位精度(FP16或BF16)加载,大约会占用70亿 * sizeof(FP16) ≈ 14GB的内存。

  • KV缓存:内存被自注意力张量的缓存占用,以避免冗余计算。

通过批处理,批次中每个请求的KV缓存仍然必须单独分配,并且可能会占用大量内存。下面的公式描述了KV缓存的大小,适用于当今大多数常见的LLM架构。 每个标记的KV缓存大小(以字节为单位) = 2 (层数)(头数 * 头维度)* 精度字节数

第一个因子2表示K和V矩阵。通常,(头数 * 头维度)的值与变压器的隐藏大小(或模型维度d_model)相同。这些模型属性通常可以在模型卡片或相关的配置文件中找到。

这个内存大小是输入序列中每个标记所需的,适用于一批输入。假设是半精度,KV缓存的总大小由以下公式给出。 KV缓存总大小(以字节为单位)=(批大小)(序列长度) 2 (层数)(隐藏大小)* sizeof(FP16)

例如,对于一个16位精度的Llama 2 7B模型,批大小为1,KV缓存的大小将是1 * 4096 * 2 * 32 * 4096 * 2字节,约为2GB。

有效地管理这个KV缓存是一项具有挑战性的任务。由于内存需求随着批大小和序列长度线性增长,它可能会迅速扩展。因此,它限制了可以提供的吞吐量,并对长上下文输入带来了挑战。这就是本文中介绍的几种优化方法的动机。

5. 通过模型并行化扩展大语言模型


减少每个设备上模型权重内存占用的一种方法是将模型分布在多个GPU上。分散内存和计算负载能够运行更大的模型,或者处理更大批次的输入。对于需要比单个设备上可用内存更多内存的模型进行训练或推理,以及使训练时间和推理指标(延迟或吞吐量)适合特定的用例来说,模型并行化是必要的。根据模型权重的分割方式,有几种并行化模型的方法。

需要注意的是,数据并行也是一种常与下面列出的其他方法同时提及的技术。在数据并行中,模型的权重被复制到多个设备上,输入的(全局)批次大小被分割到每个设备上成为微批次。它通过处理更大的批次来减少整体执行时间。然而,这是一种训练时间的优化方法,在推理过程中不太相关。

5.1 流水线并行

流水线并行是将模型(垂直地)分割成块,每个块包含一组在单独设备上执行的层。图2a展示了四路流水线并行,模型被顺序分区,所有层的四分之一子集在每个设备上执行。一个设备上一组操作的输出会传递到下一个设备,下一个设备继续执行后续的块。和分别表示在设备上的前向和后向传递。在每个设备上存储模型权重所需的内存有效地减少到了原来的四分之一。

这种方法的主要限制是,由于处理的顺序性,一些设备或层在等待前一层的输出(激活值、梯度)时可能会处于空闲状态。这导致在前向和后向传递中都出现效率低下的情况,也就是 “流水线气泡”。在图2b中,白色的空白区域就是简单流水线并行中的大 “流水线气泡”,设备处于空闲和未充分利用的状态。

微批次处理可以在一定程度上缓解这个问题,如图2c所示。输入的全局批次大小被分割成子批次,这些子批次被逐个处理,梯度在最后进行累加。注意,和分别表示在设备上对微批次的前向和后向传递。这种方法缩小了 “流水线气泡” 的大小,但并不能完全消除它们。

图2. 四路流水线并行示意图。来源:GPipe: Easy Scaling with Micro-Batch Pipeline Parallelism

5.2 张量并行

张量并行是将模型的各个层(水平地)分割成更小的、独立的计算块,这些计算块可以在不同的设备上执行。注意力块和多层感知器(MLP)层是可以利用张量并行的Transformer的主要组件。在多头注意力块中,每个头或一组头可以分配到不同的设备上,这样它们就可以独立并行地计算。

图3展示了在多层感知器(MLP)和自注意力层中的张量并行示例。图3a展示了在一个两层MLP上的两路张量并行示例,每一层用一个圆角框表示。在第一层中,权重矩阵被分割成和。计算和可以在两个不同的设备上对同一批次的输入独立执行(是恒等操作)。这有效地将每个设备上存储权重所需的内存减少了一半。在第二层中,通过归约操作组合输出。

图3b是自注意力层中两路张量并行的示例。多个注意力头本质上是并行的,可以跨设备分割。

图3. 多层感知器(MLP)和自注意力层中的张量并行示意图

图3. 多层感知器(MLP)和自注意力层中的张量并行示意图。来源:Megatron-LM: Training Multi-Billion Parameter Language Models Using Model Parallelism

5.3 序列并行

张量并行有其局限性,因为它要求将层划分为独立的、可管理的块。它不适用于像LayerNorm和Dropout这样的操作,这些操作会在张量并行组中被复制。虽然LayerNorm和Dropout的计算成本不高,但它们确实需要大量内存来存储(冗余的)激活值。

如《Reducing Activation Recomputation in Large Transformer Models》中所示,这些操作在输入序列上是相互独立的,并且这些操作可以沿着 “序列维度” 进行分区,从而提高内存效率。这被称为序列并行。

图4. 同时使用张量和序列并行的Transformer层示意图

图4. 同时使用张量和序列并行的Transformer层示意图。来源:Reducing Activation Recomputation in Large Transformer Models

模型并行化的技术不是相互排斥的,可以结合使用。它们可以帮助扩展大语言模型并减少每个GPU的内存占用,但也有专门针对注意力模块的优化技术。

6. 优化注意力机制


缩放点积注意力(SDPA)操作将查询和键值对映射到一个输出,如《Attention Is All You Need》中所述。

6.1 多头注意力

作为对SDPA的增强,多头注意力通过对查询(Q)、键(K)和值(V)矩阵进行不同的、可学习的投影,并行地多次执行注意力层,使模型能够同时关注来自不同表示子空间不同位置的信息。这些子空间是独立学习的,为模型提供了对输入中不同位置更丰富的理解。

如图5所示,多个并行注意力操作的输出被连接起来并进行线性投影以组合它们。每个并行注意力层被称为一个 “头”,这种方法被称为多头注意力(MHA)。在原始工作中,当使用八个并行注意力头时,每个注意力头在模型的较低维度上操作(例如 )。这使得计算成本与单头注意力相似。

图5. 缩放点积注意力(左)和多头注意力(右)示意图,多头注意力即多个并行的SDPA头

图5. 缩放点积注意力(左)和多头注意力(右)示意图,多头注意力即多个并行的SDPA头。来源:Attention Is All You Need

6.2 多查询注意力

对MHA的一种推理优化方法是多查询注意力(MQA),如《Fast Transformer Decoding》中所提出的,它在多个注意力头之间共享键和值。查询向量仍然像以前一样被多次投影。

虽然MQA中的计算量与MHA相同,但从内存中读取的数据量(键、值)是以前的一部分。当受到内存带宽限制时,这能够实现更好的计算利用率。它还减少了内存中KV缓存的大小,为更大的批次大小留出空间。

减少键值头的数量可能会导致潜在的准确性下降。此外,需要在推理时利用这种优化的模型需要在启用MQA的情况下进行训练(或者至少用大约5% 的训练量进行微调)。

6.3 分组查询注意力

分组查询注意力(GQA)通过将键和值投影到几组查询头之间,在MHA和MQA之间取得平衡(图6)。在每个组内,它的行为类似于多查询注意力。

图6展示了多头注意力有多个键值头(左)。分组查询注意力(中)的键值头数量多于一个,但少于查询头的数量,这是在内存需求和模型质量之间的平衡。多查询注意力(右)有单个键值头以帮助节省内存。

最初使用MHA训练的模型,可以使用原始训练计算量的一小部分用GQA进行 “升级训练”。它们在保持接近MQA计算效率的同时,达到接近MHA的质量。Llama 2 70B就是一个利用GQA的模型示例。

像MQA和GQA这样的优化通过减少存储的键值头数量来帮助减少KV缓存所需的内存。然而,在管理这个KV缓存的方式上可能仍然存在效率低下的问题。与优化注意力模块本身不同,下一节将介绍一种更高效的KV缓存管理技术。

图6. 不同注意力机制的比较

图6. 不同注意力机制的比较。来源:GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints

6.4 Flash注意力

优化注意力机制的另一种方法是修改某些计算的顺序,以更好地利用GPU的内存层次结构。神经网络通常是按层来描述的,大多数实现也是如此,一次对输入数据顺序进行一种计算。但这并不总是能带来最佳性能,因为对已经带入内存层次结构中更高、性能更好层级的值进行更多计算可能会更有益。

在实际计算中,将多个层融合在一起可以减少GPU读写内存的次数,并将需要相同数据的计算组合在一起,即使它们是神经网络中不同层的一部分。

一种非常流行的融合方法是FlashAttention,这是一种具有I/O感知的精确注意力算法,如《FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness》中所述。精确注意力意味着它在数学上与标准多头注意力相同(有多查询和分组查询注意力的变体),因此可以直接替换现有模型架构甚至是已经训练好的模型,无需修改。

I/O感知意味着在融合操作时,它考虑了前面讨论的一些内存移动成本。特别是,FlashAttention使用 “平铺” 技术,一次完整计算并写出最终矩阵的一小部分,而不是逐步对整个矩阵进行部分计算,并在中间写出中间值。

图7展示了在40GB GPU上的平铺FlashAttention计算模式和内存层次结构。右边的图表显示了融合和重新排序注意力机制不同组件带来的相对加速比。

图7. 40GB GPU上的平铺FlashAttention计算模式和内存层次结构

图7. 40GB GPU上的平铺FlashAttention计算模式和内存层次结构。来源:FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness

6.5 使用分页高效管理KV缓存

有时,由于输入大小不可预测,KV缓存会被静态地 “过度分配”,以适应可能的最大输入(支持的序列长度)。例如,如果一个模型支持的最大序列长度是2048,那么无论请求中的输入和生成的输出大小如何,都会在内存中预留大小为2048的空间。这个空间可能是连续分配的,而且通常大部分空间都未被使用,导致内存浪费或碎片化。这个预留空间在请求的生命周期内都会被占用。

受操作系统分页的启发,PagedAttention算法能够将连续的键和值存储在内存中的非连续空间中。它将每个请求的KV缓存划分为表示固定数量令牌的块,这些块可以非连续地存储。

在注意力计算过程中,根据需要使用一个块表来获取这些块。随着新令牌的生成,会进行新的块分配。这些块的大小是固定的,消除了由于不同请求需要不同分配而产生的效率低下问题。这显著减少了内存浪费,使得可以使用更大的批次大小(从而提高吞吐量)。

图8. 由于过度分配和低效的KV缓存管理导致的内存浪费和碎片化示意图

图8. 由于过度分配和低效的KV缓存管理导致的内存浪费和碎片化示意图。来源:Efficient Memory Management for Large Language Model Serving with PagedAttention

7. 模型优化技术


到目前为止,我们已经讨论了大语言模型消耗内存的不同方式、在多个不同GPU之间分配内存的一些方法,以及优化注意力机制和KV缓存。还有几种模型优化技术可以通过修改模型权重本身来减少每个GPU上的内存使用。GPU也有专门的硬件来加速对这些修改后的值的操作,为模型提供更快的速度提升。

7.1 量化

量化是降低模型权重和激活值精度的过程。大多数模型是用32位或16位精度进行训练的,其中每个参数和激活元素占用32位或16位内存 —— 即单精度浮点数。然而,大多数深度学习模型每个值用8位甚至更少的位就可以有效地表示。

图9展示了一种可能的量化方法前后的值分布。在这种情况下,一些精度因舍入而损失,一些动态范围因裁剪而损失,使得值可以用更小的格式表示。

降低模型的精度可以带来几个好处。如果模型在内存中占用的空间更小,就可以在相同数量的硬件上适配更大的模型。量化还意味着可以在相同的带宽上传输更多的参数,这有助于加速受带宽限制的模型。

对于大语言模型,有许多不同的量化技术,涉及降低激活值、权重或两者的精度。量化权重要简单得多,因为训练后权重是固定的。然而,这可能会牺牲一些性能,因为激活值仍然保持较高的精度。GPU没有专门用于将INT8和FP16数字相乘的硬件,所以在实际操作中,权重必须转换回更高的精度。

也可以对激活值(Transformer块和网络层的输入)进行量化,但这也有其自身的挑战。激活向量通常包含异常值,有效地增加了它们的动态范围,使得以较低精度表示这些值比表示权重更具挑战性。

一种选择是通过将代表性数据集输入模型,找出这些异常值可能出现的位置,并选择以比其他激活值更高的精度来表示某些激活值(LLM.int8())。另一种选择是借用易于量化的权重的动态范围,并在激活值中重用该范围。

图9. 一种可能的量化方法前后的值分布

图9. 一种可能的量化方法前后的值分布

7.2 稀疏性

与量化类似,研究表明许多深度学习模型对剪枝具有鲁棒性,即将某些接近0的值替换为0本身。稀疏矩阵是指许多元素为0的矩阵。这些矩阵可以用一种压缩形式表示,比完整的密集矩阵占用更少的空间。

GPU尤其具有对某种结构化稀疏性的硬件加速,每四个值中有两个用零表示。稀疏表示也可以与量化结合使用,以实现更大的执行加速。寻找以稀疏格式表示大语言模型的最佳方法仍然是一个活跃的研究领域,为未来提高推理速度提供了有前景的方向。

图10. 以压缩格式表示的稀疏矩阵,由非零数据值及其对应的两位索引组成

图10. 以压缩格式表示的稀疏矩阵,由非零数据值及其对应的两位索引组成

7.3 蒸馏

另一种缩小模型规模的方法是通过一种名为“蒸馏”的过程,将模型的知识转移到一个较小的模型中。这个过程包括训练一个较小的模型(称为学生模型),使其模仿较大模型(教师模型)的行为。

经过蒸馏的模型,成功案例有DistilBERT,它将BERT模型压缩了40%,同时保留了97%的语言理解能力,速度还提高了60%。

虽然大语言模型中的蒸馏是一个活跃的研究领域,但这种通用方法最早是在论文《Distilling the Knowledge in a Neural Network》中针对神经网络提出的:学生网络通过一个测量其输出与教师网络输出之间差异的损失函数进行训练,以模仿较大教师网络的性能。除了可能包含将学生网络的输出与真实标签匹配的原始损失函数之外,这一目标也是训练的一部分。

与学生网络匹配的教师网络输出可以是最后一层(称为logits)或中间层的激活值。

图11展示了知识蒸馏的一般框架。教师网络的logits是学生网络使用蒸馏损失进行优化的软目标。其他蒸馏方法可能使用其他损失度量来从教师网络 “蒸馏” 知识。

图11. 知识蒸馏的一般框架

图11. 知识蒸馏的一般框架。来源:Knowledge Distillation: A Survey

一种替代蒸馏的方法是使用教师网络合成的数据对学生大语言模型进行有监督训练,当人类注释稀缺或不可用时,这种方法特别有用。《Distilling Step by Step!》一文更是进一步提出,除了用作基本事实的标签之外,还可以从教师大语言模型中提取推理依据。这些推理依据作为中间推理步骤,以数据高效的方式训练较小的学生大语言模型。

需要注意的是,如今许多最先进的大语言模型都有严格的许可限制,禁止使用它们的输出来训练其他大语言模型,这使得找到合适的教师模型颇具挑战性。

8. 模型服务技术

模型执行通常受限于内存带宽,特别是权重的带宽。即使应用了前面描述的所有模型优化方法,仍然很可能受限于内存。所以当模型权重加载后,你要尽可能充分利用它们。换句话说,尽量并行处理。可以采用两种方法:

8.1 正在进行的批处理

大语言模型具有一些独特的执行特征,这使得在实际中有效批处理请求变得困难。单个模型可以同时用于各种看起来截然不同的任务。从聊天机器人中的简单问答回复,到文档总结或长段代码生成,工作负载高度动态,输出大小相差几个数量级。

这种多功能性使得批处理请求并有效地并行执行变得具有挑战性,而这是神经网络服务中常见的优化手段。这可能导致一些请求比其他请求完成得早得多。

为了管理这些动态负载,许多大语言模型服务解决方案都采用了一种优化的调度技术,称为连续批处理或正在进行的批处理。这利用了大语言模型的整个文本生成过程可以分解为对模型的多次执行迭代这一事实。

正在进行的批处理中,服务器运行时不会等待整个批次完成后再处理下一组请求,而是会立即从批次中移除已完成的序列。然后,在其他请求仍在处理时,它就开始执行新的请求。因此,正在进行的批处理可以在实际应用场景中大大提高GPU的整体利用率。

8.2 推测性推理

推测性推理也被称为推测采样、辅助生成或块级并行解码,是另一种并行化大语言模型执行的方式。通常,GPT风格的大语言模型是自回归模型,按token生成文本。

生成的每个token都依赖于它前面的所有token来提供上下文。这意味着在常规执行中,无法并行生成同一序列中的多个token,必须等待第n个token生成后,才能生成第n+1个token。

图12展示了推测性推理的一个示例,其中一个草稿模型临时预测多个未来步骤,这些预测会被并行验证或拒绝。在这个例子中,草稿中预测的前两个token被接受,而最后一个被拒绝并在继续生成之前被删除。

图12. 推测性推理示例

图12. 推测性推理示例。来源:Blockwise Parallel Decoding for Deep Autoregressive Models

推测采样提供了一种解决方案。这种方法的基本思想是使用一些“成本较低”的过程来生成一个包含多个token的草稿续写内容。然后,在多个步骤中并行执行主要的“验证”模型,将成本较低的草稿作为需要的执行步骤的“推测”上下文。

如果验证模型生成的token与草稿中的相同,那么就可以接受这些token作为输出。否则,可以丢弃第一个不匹配的token之后的所有内容,并使用新的草稿重复这个过程。

关于如何生成草稿token有很多不同的选择,每种选择都有不同的权衡。可以训练多个模型,或者在单个预训练模型上微调多个头部,以预测未来多个步骤的token。或者,可以使用一个小模型作为草稿模型,用一个更大、能力更强的模型作为验证模型。

9. 大语言模型服务的重要指标

那么,我们究竟应该如何衡量推理速度呢?

我们使用四个关键指标来评估大语言模型服务:

  • 首token生成时间(TTFT):用户输入查询后,多快能开始看到模型的输出。在实时交互中,低响应等待时间至关重要,但在离线工作负载中则不那么重要。这个指标取决于处理提示并生成第一个输出token所需的时间。

  • 每个输出token的生成时间(TPOT):为每个查询系统的用户生成一个输出token所需的时间。这个指标反映了每个用户对模型 “速度” 的感知。例如,TPOT为100毫秒/词意味着每个用户每秒生成10个token,即每分钟约450个单词,比普通人的阅读速度还快。

  • 延迟(Latency):模型为用户生成完整响应所需的总时间。总体响应延迟可以使用前两个指标计算:延迟=(TTFT)+(TPOT)×(要生成的token数量)。

  • 吞吐量(Throughput):推理服务器在所有用户和请求中每秒生成的输出token数量。

10. 大语言模型服务需要什么?

Architecture of servers and engines

Architecture of servers and engines

服务器和引擎架构

在为基于大语言模型的应用程序提供服务时,主要有两个组件:引擎和服务器。引擎负责处理与模型相关的所有事务以及请求批处理,而服务器负责转发用户请求。

10.1 引擎

引擎负责运行模型,以及我们到目前为止讨论的使用不同类型优化技术的生成过程。本质上,它们是Python库。它们处理从用户发送到聊天机器人的请求批处理,并为这些请求生成响应。

10.2 服务器

服务器负责协调来自用户的HTTP/gRPC请求。在实际应用中,一天中不同时间会有许多用户向我们的聊天机器人提问。服务器将这些请求排队,并将它们转发给引擎以生成响应。服务器还会提供吞吐量和延迟等指标,这些指标对于模型服务的跟踪非常重要。

10.3 功能

引擎和服务器功能对比

引擎和服务器功能对比

到目前为止,我们讨论了模型处理单个请求的简单场景。然而,实际应用需要具备同时为数百甚至数千用户提供服务的能力。现在,我们的重点转向优化成本和吞吐量,这使我们关注到下一个关键考虑因素:请求批处理和使用PagedAttention进行内存优化。这些优化对于高效托管模型至关重要,可确保在用户需求较大的情况下实现成本效益和高吞吐量。

11. 大语言模型服务框架

现在我们已经介绍了重要指标、权衡因素以及应对大语言模型服务中关键挑战的技术,那么一个重要问题是:我们如何将这些技术付诸实践?哪些工具最适合我们的需求?在深入研究框架之前,我们应该了解些什么?

在本节中,我们将深入探讨关键框架的所有这些细节,并分享基准测试实验的主要发现。我们选择了行业中流行且广泛使用的框架。每个框架在优化和提高大语言模型推理性能方面都有独特的价值。我们将这些框架分为两组:服务器和引擎。最后,我们将清楚地了解可用工具及其是否适合我们特定的大语言模型服务需求。

11.1 vLLM

快速且易于使用的大语言模型推理和服务库。它的吞吐量比HuggingFace Transformers(HF)高14 – 24倍,比HuggingFace文本生成推理(TGI)高2.2 – 2.5倍。

  • 用法

  • 离线批处理推理

  
`from vllm import LLM, SamplingParams      prompts = [       "Funniest joke ever:",       "The capital of France is",       "The future of AI is"   ]   sampling_params = SamplingParams(temperature=0.95, top_p=0.95, max_tokens=200)   llm = LLM(model="huggyllama/llama-13b")   outputs = llm.generate(prompts, sampling_params)   for output in outputs:       prompt = output.prompt       generated_text = output.outputs[0].text       print(f"Prompt: {prompt!r}, Generated text: {generated_text!r}")   `
  • API服务器启动命令:python -m vllm.entrypoints.api_server --env MODEL_NAME=huggyllama/llama-13b在命令行查询模型示例:
  
`curl http:      -X POST      -H "Content-Type: application/json"      -d '{     "prompt": "Funniest joke ever:",     "n": 1,     "temperature": 0.95,     "max_tokens": 200     }'   `
  • 特色功能

  • 连续批处理:迭代级调度,每次迭代确定批大小。得益于批处理,vLLM在高查询负载下也能表现出色。

  • PagedAttention:受操作系统中虚拟内存和分页经典思想启发的注意力算法。这是模型加速的秘诀。

  • 优势

  • 文本生成速度快:作者使用该库进行了多次实验,结果令人满意。目前,使用vLLM进行推理是最快的选择之一。

  • 高吞吐量服务:支持各种解码算法,包括并行采样、波束搜索等。

  • 兼容OpenAI的API服务器:如果之前使用OpenAI API,只需替换端点URL即可。

  • 局限性

  • 添加自定义模型复杂:虽然可以集成自己的模型,但如果模型架构与vLLM中现有模型差异较大,集成过程会变得复杂。比如添加Falcon模型支持的请求,实现起来颇具挑战。

  • 缺乏对适配器(LoRA、QLoRA等)的支持:针对特定任务微调的开源大语言模型很有价值,但当前vLLM无法单独使用模型和适配器权重,限制了模型使用的灵活性。

  • 没有权重量化功能:有时大语言模型可能无法完全加载到可用的GPU内存中,此时降低内存消耗就显得尤为重要。

11.2 文本生成推理(Text generation inference,TGI)

文本生成推理(TGI)是一个用于部署和服务大语言模型(LLMs)的工具包。TGI为最流行的开源大语言模型,包括Llama、Falcon、StarCoder、BLOOM、GPT-NeoX和T5,实现高性能文本生成。它是一个用Rust、Python编写的gRPC服务器,用于文本生成推理。HuggingFace在生产环境中使用它为大语言模型API推理小部件提供支持。

  
`mkdir data   docker run --gpus all --shm-size 1g -p 8080:80      -v data:/data ghcr.io/huggingface/text-generation-inference:0.9      --model-id huggyllama/llama-13b      --num-shard 1   `
  
`from text_generation import Client      client = Client("http://127.0.0.1:8080")   prompt = "Funniest joke ever:"   print(client.generate(prompt, max_new_tokens=17, temperature=0.95).generated_text)   `
  • 特色功能

  • 内置Prometheus指标:可以监控服务器负载并深入了解其性能。

  • 使用FlashAttention(及v2)和Paged Attention优化的Transformer推理代码:不过,并非所有模型都内置对这些优化的支持。如果使用不太常见的模型架构,可能会遇到挑战。

  • 优势

  • Docker中安装了所有依赖项:可以立即获得一个在本地机器上即可使用的现成环境。

  • 原生支持HuggingFace的模型:轻松运行自己的模型或使用HuggingFace模型库中的任何模型。

  • 可控制模型推理:该框架提供了广泛的选项来管理模型推理,包括精度调整、量化、张量并行、重复惩罚等。

  • 局限性

  • 缺乏对适配器的支持:虽然可以部署带有适配器的大语言模型,但目前没有官方支持或文档说明。

  • 需要从源代码编译(Rust + CUDA内核):Rust语言并非所有数据科学团队都熟悉,这使得在库中融入自定义更改颇具挑战。

  • 文档不完善:所有信息都在项目的README文件中。虽然涵盖了基础知识,但有时仍需要在问题或源代码中搜索更多细节(对于不熟悉Rust语言的人来说尤其困难)。

11.3 CTranslate2

CTranslate2是一个用C++和Python编写的库,用于高效地进行Transformer模型推理。

  
`pip install -qqq transformers ctranslate2   ct2-transformers-converter --model huggyllama/llama-13b --output_dir llama-13b-ct2 --force   `
  
`import ctranslate2   import transformers      generator = ctranslate2.Generator("llama-13b-ct2", device="cuda", compute_type="float16")   tokenizer = transformers.AutoTokenizer.from_pretrained("huggyllama/llama-13b")   prompt = "Funniest joke ever:"   tokens = tokenizer.convert_ids_to_tokens(tokenizer.encode(prompt))   results = generator.generate_batch(       [tokens],       sampling_topk=1,       max_length=200   )   tokens = results[0].sequences_ids[0]   output = tokenizer.decode(tokens)   print(output)   `
  • 特色功能

  • 在CPU和GPU上快速高效执行:得益于一系列内置优化,如层融合、去除填充、批重排序、原地操作、缓存机制等,大语言模型推理速度更快,内存需求更低。

  • 动态内存使用:由于CPU和GPU上的缓存分配器,内存使用会根据请求大小动态变化,同时仍能满足性能要求。

  • 支持多种CPU架构:该项目支持x86 – 64和AArch64/ARM64处理器,并集成了针对这些平台优化的多个后端,如Intel MKL、oneDNN、OpenBLAS、Ruy和Apple Accelerate。

  • 优势

  • 并行和异步执行:可以使用多个GPU或CPU核心并行和异步处理多个批次。

  • 提示缓存:对静态提示运行一次模型后,会缓存模型状态,相同静态提示的后续调用可重用该状态。

  • 磁盘占用小:量化可使模型在磁盘上的大小缩小4倍,且精度损失极小。

  • 局限性

  • 没有内置REST服务器:虽然仍可以运行REST服务器,但缺少一个具有日志记录和监控功能的现成服务。

  • 缺乏对适配器(LoRA、QLoRA等)的支持

CTranslate2这个库很有意思。开发者在积极维护,从GitHub上的发布和提交记录可以看出,他们还分享有关其应用的信息丰富的博客文章。该库的众多优化令人印象深刻,主要亮点是能够在CPU上进行大语言模型推理。

11.4 DeepSpeed-MII

MII(Model Inference Intelligence,模型推理智能)借助DeepSpeed实现低延迟和高吞吐量推理。

  • 用法

  • 运行网络服务器

  
`import mii   mii_configs = {       "dtype": "fp16",      'max_tokens': 200,       'tensor_parallel': 1,       "enable_load_balancing": False   }   mii.deploy(task="text-generation",              model="huggyllama/llama-13b",              deployment_name="llama_13b_deployment",              mii_config=mii_configs)   `
- **进行查询**  

  
`import mii   generator = mii.mii_query_handle("llama_13b_deployment")   result = generator.query(       {"query": ["Funniest joke ever:"]},       do_sample=True,       max_new_tokens=200   )   print(result)   `
  • 特色功能

  • 多副本负载均衡:这是一个处理大量用户请求时非常有用的工具。负载均衡器能有效地将传入请求分配到各个副本中,从而缩短应用程序的响应时间。

  • 非持久化部署:这种部署方式下,更新不会永久应用到目标环境。在资源效率、安全性、一致性以及管理便捷性至关重要的场景中,这是一个很有价值的选择。它能在降低运营成本的同时,实现更可控、更标准化的环境。

  • 优势

  • 不同的模型仓库:可通过多个开源模型仓库获取模型,如Hugging Face、FairSeq、EluetherAI等。

  • 量化延迟和降低成本:MII能显著降低昂贵语言模型的推理成本。

  • 原生和Azure集成:由微软开发的MII框架,与他们的云系统有出色的集成表现。

  • 局限性

  • 缺乏官方版本发布:我花了好几个小时才找到能正常运行应用的合适提交版本。部分文档已经过时,不再适用。

  • 支持的模型数量有限:不支持Falcon、LLaMA 2等语言模型,能运行的模型数量有限。

  • 不支持适配器(如LoRA、QLoRA等)

该项目基于可靠的DeepSpeed库,DeepSpeed在社区中已赢得良好声誉。如果追求稳定性和经过实践检验的解决方案,MII会是一个不错的选择。根据我的实验,这个库在处理单个提示时展现出了最快的速度。尽管如此,我还是建议在将其应用到系统之前,针对特定任务对该框架进行测试。

11.5 OpenLLM

一个用于在生产环境中运行大型语言模型(LLMs)的开放平台。

  • 用法

  • 运行网络服务器

  
`pip install openllm scipy   openllm start llama   `
- **进行查询**:  

  
`import openllm   client = openllm.client.HTTPClient('http://localhost:3000')   print(client.query("Funniest joke ever:"))   `
  • 特色功能

  • 支持适配器:可以将多个适配器连接到一个已部署的LLM上。想象一下,我们可以用一个模型处理多个特定任务。

  • 运行时实现:可使用不同的实现方式,如Pytorch(pt)、Tensorflow(tf)或Flax(flax)。

  • HuggingFace智能体:可以链接HuggingFace上的不同模型,并用LLM和自然语言进行管理。

  • 优势

  • 良好的社区支持:该库在持续开发,并不断添加新功能。

  • 集成新模型:开发者提供了添加自定义模型的指南。

  • 量化支持:OpenLLM支持使用bitsandbytes和GPTQ进行量化。

  • 与LangChain集成:我们可以通过LangChian与远程OpenLLM服务器进行交互。

  • 局限性

  • 缺乏批处理支持:在处理大量消息流时,这很可能成为应用程序性能的瓶颈。

  • 缺乏内置分布式推理功能:如果我们想在多个GPU设备上运行大型模型,需要额外安装OpenLLM的服务组件Yatai。

这是一个功能丰富的优秀框架,能让我们以较低成本创建灵活的应用程序。虽然文档可能没有涵盖所有方面,但在深入研究这个库的过程中,我们很可能会发现其更多功能带来的惊喜。

11.6 Ray Serve

Ray Serve是一个可扩展的模型服务库,用于构建在线推理API。Serve与框架无关,因此我们可以使用这一个工具包来为深度学习模型等各类模型提供服务。

Ray AIR支持端到端的机器学习开发,并为与MLOps生态系统中的其他工具和库集成提供了多种选择。

  • 用法

  • 运行网络服务器

  
`import pandas as pd   import ray   from ray import serve   from starlette.requests import Request         @serve.deployment(ray_actor_options={"num_gpus": 1})   class PredictDeployment:       def __init__(self, model_id: str):           from transformers import AutoModelForCausalLM, AutoTokenizer           import torch           self.model = AutoModelForCausalLM.from_pretrained(               model_id,               torch_dtype=torch.float16,               device_map="auto"           )           self.tokenizer = AutoTokenizer.from_pretrained(model_id)          def generate(self, text: str) -> pd.DataFrame:           input_ids = self.tokenizer(text, return_tensors="pt").input_ids.to(               self.model.device           )           gen_tokens = self.model.generate(               input_ids,               temperature=0.9,               max_length=200           )           return pd.DataFrame(               self.tokenizer.batch_decode(gen_tokens), columns=["responses"]           )          asyncdef __call__(self, http_request: Request) -> str:           json_request: str = await http_request.json()           return self.generate(prompt["text"])         deployment = PredictDeployment.bind(model_id="huggyllama/llama-13b")   `
- **进行查询**  

  
`import requests   sample_input = {"text": "Funniest joke ever:"}   output = requests.post("http://localhost:8000/", json=[sample_input]).json()   print(output)   `
  • 特色功能

  • 监控仪表盘和Prometheus指标:我们可以使用Ray仪表盘对Ray集群和Ray Serve应用程序的状态进行高级概述。

  • 跨多个副本自动缩放:Ray通过观察队列大小来适应流量高峰,并做出添加或删除副本的缩放决策。

  • 动态请求批处理:当我们的模型使用成本较高,且希望最大化硬件利用率时,动态请求批处理是很有必要的。

  • 优势

  • 详尽的文档:我很欣赏开发者在文档方面所花的时间和精力。几乎每个用例都有大量示例,这非常有帮助。

  • 适用于生产环境:在我看来,这是本文列出的所有框架中最成熟的一个。

  • 原生LangChain集成:我们可以通过LangChian与远程Ray服务器进行交互。

  • 局限性

  • 缺乏内置模型优化:Ray Serve并不专注于LLM,它是一个更通用的部署任何机器学习模型的框架。我们必须自己进行优化。

  • 入门门槛高:这个库有时功能过多,这提高了入门门槛,使得新手难以理解和使用。

如果我们需要一个不仅仅适用于深度学习,且最适合生产环境的解决方案,Ray Serve是个不错的选择。它最适合企业使用,因为在企业中,可用性、可扩展性和可观测性都很重要。此外,我们还可以利用其庞大的生态系统进行数据处理、训练、微调以及模型服务。最后要提到的是,从OpenAI到Shopify和Instacart等公司都在使用它。

11.7 MLC LLM

机器学习编译(Machine Learning Compilation,MLC LLM)是一种通用的部署解决方案,它利用本地硬件加速,使LLM能够在消费级设备上高效运行。

MLC LLM中的高级项目概念

  • 用法

  • 运行网络服务器

  
`conda create -n mlc-chat-venv -c mlc-ai -c conda-forge mlc-chat-nightly   conda activate mlc-chat-venv   pip install --pre --force-reinstall mlc-ai-nightly-cu118                mlc-chat-nightly-cu118                -f https://mlc.ai/wheels   git lfs install && mkdir -p dist/prebuilt &&    git clone https://github.com/mlc-ai/binary-mlc-llm-libs.git dist/prebuilt/lib &&    cd dist/prebuilt &&    git clone https://huggingface.co/huggyllama/llama-13b dist/ &&    cd ../..   python -m mlc_chat.rest --device-name cuda --artifact-path dist   `
- **进行查询**  

  
`import requests   payload = {       "model": "lama-30b",       "messages": [{"role": "user", "content": "Funniest joke ever:"}],       "stream": False   }   r = requests.post("http://127.0.0.1:8000/v1/chat/completions", json=payload)   print(r.json()['choices'][0]['message']['content'])   `
  • 特色功能

  • 平台原生运行时:部署在用户设备的原生环境中,这些设备可能没有预装Python或其他必要的依赖项。应用开发者只需熟悉平台原生运行时,即可将MLC编译的LLM集成到他们的项目中。

  • 内存优化:我们可以使用不同技术对模型进行编译、压缩和优化,以便在不同设备上进行部署。

  • 优势

  • 所有设置均在JSON配置文件中:允许我们在单个配置文件中为每个编译模型定义运行时配置。

  • 预构建应用程序:我们可以为不同平台编译模型,如命令行使用的C++、网页使用的JavaScript、iOS使用的Swift以及Android使用的Java/Kotlin。

  • 局限性

  • 使用LLM模型的功能有限:不支持适配器,无法更改精度,不支持令牌流等。该库主要专注于为不同设备编译模型。

  • 仅支持分组量化:虽然这种方法已显示出不错的效果,但社区中其他量化方法(如bitsandbytes和GPTQ)更受欢迎。很有可能社区对这些方法的开发会更完善。

  • 安装过程复杂:我花了好几个小时才正确安装好这个库。它很可能不适合初学者开发者。

如果我们需要在iOS或Android设备上部署应用程序,这个库正是我们所需要的。它能让我们快速将模型原生编译并部署到设备上。然而,如果我们需要一个高负载的服务器,我不建议选择这个框架。

结论

我们在白皮书中使用不同的设置评估了这些框架的性能及其功能。每个框架,无论是像TensorRT-LLM和vLLM这样的引擎,还是像RayLLM搭配RayServe、Triton搭配TensorRT-LLM以及文本生成推理(TGI)这样的服务器,都有独特的能力,在不同的用例中发挥着重要价值。我们的基准测试研究揭示了一些细致的发现,从内存分配的挑战,到抢占策略的权衡,以及序列长度对吞吐量的影响。以下是我们从实验中获得的简要概述:

  1. 内存是关键:内存分配的管理对于优化LLM性能至关重要。

  2. 抢占是一种策略性权衡:对于像vLLM这样的引擎来说,由于生成操作受内存限制,而GPU未被充分利用,因此抢占是一种策略性的权衡。

  3. 序列长度的影响:序列长度的研究揭示了vLLM在处理并发请求方面的效率,特别是在输出较短的情况下。

  4. 模型大小对吞吐量的影响:模型大小对吞吐量有显著影响。然而,超过一定程度后,额外的GPU内存不再能提高吞吐量。

  5. 服务器选择的重要性:服务器的选择起着至关重要的作用,正如白皮书中Triton搭配TensorRT-LLM的表现优于独立的TensorRT-LLM所证明的那样。

尽管用于LLMs推理的框架众多,但每个框架都有其特定的用途。以下是一些需要考虑的要点:

  1. 追求速度时选择vLLM:当需要以最大速度进行批处理提示交付时,使用vLLM。

  2. 需要HuggingFace原生支持时选择文本生成推理:如果需要HuggingFace的原生支持,且不打算为核心模型使用多个适配器,选择文本生成推理。

  3. 在CPU上运行推理时选择CTranslate2:如果速度对我们很重要,并且打算在CPU上运行推理,考虑CTranslate2。

  4. 使用适配器和HuggingFace智能体时选择OpenLLM:如果想为核心模型连接适配器并使用HuggingFace智能体,尤其是不单纯依赖PyTorch时,选择OpenLLM。

  5. 追求稳定和灵活部署时选择Ray Serve:对于稳定的流程和灵活的部署,考虑Ray Serve。它最适合更成熟的项目。

  6. 在客户端进行原生部署时选择MLC LLM:如果想在客户端(边缘计算),如Android或iPhone平台上原生部署LLMs,使用MLC LLM。

  7. 基于DeepSpeed库进行部署时选择DeepSpeed-MII:如果已经有使用DeepSpeed库的经验,并希望继续使用它来部署LLMs,选择DeepSpeed-MII。

零基础入门AI大模型

今天贴心为大家准备好了一系列AI大模型资源,包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

有需要的小伙伴,可以点击下方链接免费领取【保证100%免费

点击领取 《AI大模型&人工智能&入门进阶学习资源包》

1.学习路线图

在这里插入图片描述

第一阶段: 从大模型系统设计入手,讲解大模型的主要方法;

第二阶段: 在通过大模型提示词工程从Prompts角度入手更好发挥模型的作用;

第三阶段: 大模型平台应用开发借助阿里云PAI平台构建电商领域虚拟试衣系统;

第四阶段: 大模型知识库应用开发以LangChain框架为例,构建物流行业咨询智能问答系统;

第五阶段: 大模型微调开发借助以大健康、新零售、新媒体领域构建适合当前领域大模型;

第六阶段: 以SD多模态大模型为主,搭建了文生图小程序案例;

第七阶段: 以大模型平台应用与开发为主,通过星火大模型,文心大模型等成熟大模型构建大模型行业应用。

2.视频教程

网上虽然也有很多的学习资源,但基本上都残缺不全的,这是我自己整理的大模型视频教程,上面路线图的每一个知识点,我都有配套的视频讲解。

在这里插入图片描述

在这里插入图片描述

(都打包成一块的了,不能一一展开,总共300多集)

3.技术文档和电子书

这里主要整理了大模型相关PDF书籍、行业报告、文档,有几百本,都是目前行业最新的。
在这里插入图片描述

4.LLM面试题和面经合集

这里主要整理了行业目前最新的大模型面试题和各种大厂offer面经合集。
在这里插入图片描述

👉学会后的收获:👈

• 基于大模型全栈工程实现(前端、后端、产品经理、设计、数据分析等),通过这门课可获得不同能力;

• 能够利用大模型解决相关实际项目需求: 大数据时代,越来越多的企业和机构需要处理海量数据,利用大模型技术可以更好地处理这些数据,提高数据分析和决策的准确性。因此,掌握大模型应用开发技能,可以让程序员更好地应对实际项目需求;

• 基于大模型和企业数据AI应用开发,实现大模型理论、掌握GPU算力、硬件、LangChain开发框架和项目实战技能, 学会Fine-tuning垂直训练大模型(数据准备、数据蒸馏、大模型部署)一站式掌握;

• 能够完成时下热门大模型垂直领域模型训练能力,提高程序员的编码能力: 大模型应用开发需要掌握机器学习算法、深度学习框架等技术,这些技术的掌握可以提高程序员的编码能力和分析能力,让程序员更加熟练地编写高质量的代码。

1.AI大模型学习路线图
2.100套AI大模型商业化落地方案
3.100集大模型视频教程
4.200本大模型PDF书籍
5.LLM面试题合集
6.AI产品经理资源合集

5.免费获取

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码或者点击以下链接都可以免费领取【保证100%免费】

点击领取 《AI大模型&人工智能&入门进阶学习资源包》

在这里插入图片描述

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。