一文探秘LLM应用开发(15)-模型部署与推理(框架工具-推理执行引擎(HF pipeline))

动手点关注

picture.image

干货不迷路

picture.image

本文旨在让无大模型开发背景的工程师或者技术爱好者无痛理解大语言模型应用开发的理论和主流工具,因此会先从与LLM应用开发相关的基础概念谈起,并不刻意追求极致的严谨和完备,而是从直觉和本质入手,结合笔者调研整理及消化理解,帮助大家能够更容易的理解LLM技术全貌,大家可以基于本文衍生展开,结合自己感兴趣的领域深入研究。若有不准确或者错误的地方也希望大家能够留言指正。

本文体系完整,内容丰富,由于内容比较多,分多次连载

第一部分 基础概念

1.机器学习场景类别

2.机器学习类型(LLM相关)

3.深度学习的兴起

4.基础模型

第二部分 应用挑战

1.问题定义与基本思路

2.基本流程与相关技术

1)Tokenization与Embbeding

2)向量数据库

3)finetune(微调)

4)模型部署与推理

5)prompt

6)编排与集成

7)预训练

第三部分 场景案例

常用参考

第二部分 应用挑战

2.基本流程与相关技术

4)模型部署与推理

模型服务层相关工具和框架

前文对模型服务层做了简单的分类,基于封装程度不同,有大量的框架和工具,笔者结合目前趋势,在每个类别中挑选了几个常见的项目和大家分享,它们各有特点,将从内向外一一介绍,最后给出一些选型建议以供参考。

picture.image

推理执行引擎(Inference Execute Engine)

默认的,机器学习框架(如Pytorch)训练出模型后,就可以通过标准能力进行推理,比如在pytorch中使用model.eval()可以将模型设置为评估模式便能切换为前向推理状态。虽然这种方式简单直接,但往往存在一些弊端,简单列举三个:

1)资源要求高,性能差,没有针对目标场景和设备进行针对性优化。

2)不利于模型交换分享,训用分离,训练和推理框架和过程耦合度高

3)与算法细节相关,门槛较高,应用开发者难以使用。

为了解决这些问题,加速推理过程,推理执行引擎应运而生。在大模型时代之前,就已经有tensorRT这样的引擎出现,支持TensorFlow、Caffe、Mxnet、Pytorch等几乎所有的深度学习框架训练的模型优化,并使其在英伟达GPU下,特别是嵌入式场景的推理性能得以提升。

picture.image

到了大模型时代,资源性能问题突出,原始模型不经过优化难以直接进行用于生产环境推理使用,因此,推理执行引擎就变得尤为重要,大量框架工具雨后春笋产生。从重点优化的目标设备来分大致分为两类:服务器端和个人电脑或边缘端。

HuggingFace Pipelines

该执行引擎在本章第一节就有介绍过,是HuggingFace早期为了简化NLP模型训练推理而开发的transformers库中的一个核心模块,现在该库得到了广泛的应用,并扩展到了计算机视觉,语音等多模态的等任务。利用pipeline功能就能快速实现推理任务,再配合web server框架,如gradio,flask就能快速构建一个推理服务,供下游使用。下面是一个llama2模型使用pipeline进行推理的例子。


            
from transformers import AutoTokenizer
            
import transformers
            
import torch
            

            
model = "meta-llama/Llama-2-7b-chat-hf"
            

            
tokenizer = AutoTokenizer.from_pretrained(model)
            
pipeline = transformers.pipeline(
            
    "text-generation",
            
    model=model,
            
    torch_dtype=torch.float16,
            
    device_map="auto",
            
)
            

            
sequences = pipeline(
            
    'I liked "Breaking Bad" and "Band of Brothers". Do you have any recommendations of other shows I might like?\n',
            
    do_sample=True,
            
    top_k=10,
            
    num_return_sequences=1,
            
    eos_token_id=tokenizer.eos_token_id,
            
    max_length=200,
            
)
            
for seq in sequences:
            
    print(f"Result: {seq['generated_text']}")
        

整个执行过程大致如下:

picture.image

该库提供了各种各样的预置任务类型(具体参看:https://huggingface.co/docs/transformers/main/en/main\_classes/pipelines#transformers.pipeline.task),并可以根据任务类型自动加载指定模型及选择合适的预处理类。

在性能优化层面,配合accelerate库使用,支持批处理,cpu offload,模型并行,量化等常见优化手段。

1)批处理

该功能在使用了流能力(如输入参数为List、Dataset 或generator)生效,如:


            
from transformers import pipeline
            
from transformers.pipelines.pt_utils import KeyDataset
            
import datasets
            

            
dataset = datasets.load_dataset("imdb", name="plain_text", split="unsupervised")
            
pipe = pipeline("text-classification", device=0)
            
for out in pipe(KeyDataset(dataset, "text"), batch_size=8, truncation="only_first"):
            
    print(out)
            
    # [{'label': 'POSITIVE', 'score': 0.9998743534088135}]
            
    # Exactly the same output as before, but the content are passed
            
    # as batches to the model
            

        

需要说明的是,使用批处理并不意味着性能都会得到提升。这与硬件、数据和实际使用的模型有关,它可能会 让推理加快也可能会变慢。下面是一个加速和变慢的例子。

  • 加速的例子:

            
from transformers import pipeline
            
from torch.utils.data import Dataset
            
from tqdm.auto import tqdm
            

            
pipe = pipeline("text-classification", device=0)
            

            

            
class MyDataset(Dataset):
            
    def __len__(self):
            
        return 5000
            

            
    def __getitem__(self, i):
            
        return "This is a test"
            

            

            
dataset = MyDataset()
            

            
for batch_size in [1, 8, 64, 256]:
            
    print("-" * 30)
            
    print(f"Streaming batch_size={batch_size}")
            
    for out in tqdm(pipe(dataset, batch_size=batch_size), total=len(dataset)):
            
        pass
        

运行结果:


            
# On GTX 970
            
------------------------------
            
Streaming no batching
            
100%|██████████████████████████████████████████████████████████████████████| 5000/5000 [00:26<00:00, 187.52it/s]
            
------------------------------
            
Streaming batch_size=8
            
100%|█████████████████████████████████████████████████████████████████████| 5000/5000 [00:04<00:00, 1205.95it/s]
            
------------------------------
            
Streaming batch_size=64
            
100%|█████████████████████████████████████████████████████████████████████| 5000/5000 [00:02<00:00, 2478.24it/s]
            
------------------------------
            
Streaming batch_size=256
            
100%|█████████████████████████████████████████████████████████████████████| 5000/5000 [00:01<00:00, 2554.43it/s]
            
(diminishing returns, saturated the GPU)
        

变慢的例子:


            
class MyDataset(Dataset):
            
    def __len__(self):
            
        return 5000
            

            
    def __getitem__(self, i):
            
        if i % 64 == 0:
            
            n = 100
            
        else:
            
            n = 1
            
        return "This is a test" * n
            

        

与其他句子相比,某些句子偶尔可能会很长。在这种情况下,整个batch需要 400 个token,因此整个batch将是 [64, 400],而不是 [64, 4],从而导致速度大大降低。更糟糕的是,如果batch设置太大,程序会直接崩溃。


            
------------------------------
            
Streaming no batching
            
100%|█████████████████████████████████████████████████████████████████████| 1000/1000 [00:05<00:00, 183.69it/s]
            
------------------------------
            
Streaming batch_size=8
            
100%|█████████████████████████████████████████████████████████████████████| 1000/1000 [00:03<00:00, 265.74it/s]
            
------------------------------
            
Streaming batch_size=64
            
100%|██████████████████████████████████████████████████████████████████████| 1000/1000 [00:26<00:00, 37.80it/s]
            
------------------------------
            
Streaming batch_size=256
            
  0%|                                                                                 | 0/1000 [00:00, ?it/s]
            
Traceback (most recent call last):
            
  File "/home/nicolas/src/transformers/test.py", line 42, in <module>
            
    for out in tqdm(pipe(dataset, batch_size=256), total=len(dataset)):
            
....
            
    q = q / math.sqrt(dim_per_head)  # (bs, n_heads, q_length, dim_per_head)
            
RuntimeError: CUDA out of memory. Tried to allocate 376.00 MiB (GPU 0; 3.95 GiB total capacity; 1.72 GiB already allocated; 354.88 MiB free; 2.46 GiB reserved in total by PyTorch)
        

对于避免这类问题,官方给出一些建议可供参考:

  1. 在实际的硬件环境下进行性能测试,进而评估其有效性。

  2. 如果有延迟要求(正在进行推理的实时产品),请不要批量处理。

  3. 如果使用 CPU,不要批处理。

  4. 如果用的是吞吐量优先,那么在 GPU 上:

  5. 如果对不清楚实际输入sequence_length 的范围,那么默认情况下不要批处理,实际测试并尝试暂时添加 sequence_length,并且添加 OOM 的检查措施以便失败时能够恢复。

  6. 如果sequence_length 非常有规律,那么batch通常会比较有效,可以不断测试增大batch,直到出现 OOM,以获得最佳配置。

  7. GPU 越大,批处理就越有可能更有效。

  8. 一旦启用批处理,需要考虑处理可能的OOM带来的问题。

2)利用accelerate加速大模型推理

该能力依赖huggingface的Accelerate库,利用它可以使得大规模训练和推理变得简单、高效且适应性强。 它能够提供了一系列优化技术使得可以对显卡无法完全容纳的模型进行推理。

高阶使用它也非常容易,在from_pretrained()或 者pipeline()函数中设置device_map为auto可以自动获得Accelerate的支持。


            
from transformers import AutoModelForSeq2SeqLM
            

            
model = AutoModelForSeq2SeqLM.from_pretrained("bigscience/T0pp", device_map="auto")
        

当 device_map设置为auto时,Accelerate 会自动决定将每一层放在哪里,以最大限度地利用GPU,并将其余部分卸载到 CPU 上,如果没有足够的 GPU RAM(或 CPU RAM),甚至可以卸载到硬盘上。即使模型被分割到多个设备上,它也会像你通常期望的那样运行。也就是说cpu offload和模型并行等策略对开发者透明,极大 地降低了使用难度。虽然这会给正在执行的推理增加一些开销,但通过这种方法,只要最大的层能够在 GPU 上运行,就可以在系统上运行任何大小的模型。当然,低阶使用时,这个参数也可以进行显性的设置,从而自定义device_map:


            
device_map = {
            
    "transformer.wte": 0,
            
    "transformer.wpe": 0,
            
    "transformer.drop": 0,
            
    "transformer.h": "cpu",
            
    "transformer.ln_f": "disk",
            
    "lm_head": "disk",
            
}
        

下面是其工作的基本原理,可以直观 地理解其工作过程。

另外,在from_pretrained()或 者 pipeline()函数中还可以设置加载模型的精度,通过设置合理精度可以进一步地降低内存需求。


            
from transformers import AutoModelForSeq2SeqLM
            

            
model = AutoModelForSeq2SeqLM.from_pretrained("bigscience/T0pp", device_map="auto", torch_dtype=torch.float16)
        

更进一步,accelerate对bitsandbytes进行了集成,简单几步就可以对模型进行8位或4位量化,进一步减少显存占用,加速推理过程。

量化模型:


            
# 8-bit quantization
            
from accelerate.utils import BnbQuantizationConfig
            
bnb_quantization_config = BnbQuantizationConfig(load_in_8bit=True, llm_int8_threshold = 6)
            

            
# 4-bit quantization
            
from accelerate.utils import BnbQuantizationConfig
            
bnb_quantization_config = BnbQuantizationConfig(load_in_4bit=True, bnb_4bit_compute_dtype=torch.bfloat16, bnb_4bit_use_double_quant=True, bnb_4bit_quant_type="nf4")
            

            
# quantize model
            
from accelerate.utils import load_and_quantize_model
            
quantized_model = load_and_quantize_model(empty_model, weights_location=weights_location, bnb_quantization_config=bnb_quantization_config, device_map = "auto")
        

存储或加载量化后的模型:


            
# save model
            
from accelerate import Accelerator
            
accelerate = Accelerator()
            
new_weights_location = "path/to/save_directory"
            
accelerate.save_model(quantized_model, new_weights_location)
            
# load model
            
quantized_model_from_saved = load_and_quantize_model(empty_model, weights_location=new_weights_location, bnb_quantization_config=bnb_quantization_config, device_map = "auto")
        

官方提供了一个colab实验可以在线体验:https://colab.research.google.com/drive/1T1pOgewAWVpR9gKpaEWw4orOrzPFb3yM?usp=sharing

huggingface pipeline主要面向服务端设计,但得益于accelerate集成了PyTorch 的 MPS (Apple’s Metal Performance Shaders )后端,这也意味着huggingFace pipeline能够加速在m1芯片的macbook上大模型推理。

更多能力,可参考文档:

https://huggingface.co/docs/transformers/main/en/main\_classes/pipelines

https://huggingface.co/docs/accelerate/main/en/index

总之,huggingface transformers pipeline由于其简单易用的特点,加上accelerate的加成,使得其成为众多推理框架的重要候选之一。

未完待续。。。

鉴于篇幅原因,在下一篇中将继续其它框架介绍。

合集目录:

第一部分 基础概念

一文探秘LLM应用开发(1)

第二部分 应用挑战

1.问题定义与基本思路

一文探秘LLM应用开发(2)

2 . 基本流程与相关技术

1)Tokenization与Embbeding

一文探密LLM应用开发(3)

2)向量数据库

一文探秘LLM应用开发(4)

3)微调

一文探秘LLM应用开发(5)-微调(背景与挑战)

一文探秘LLM应用开发(6)-微调(方案理论)

一文探秘LLM应用开发(7)-微调(工具实践)

4)模型部署与推理

一文探秘LLM应用开发(8)-模型部署与推理(DEMO验证)

一文探秘LLM应用开发(9)-模型部署与推理(模型适配优化之GPU面面观-1)

一文探秘LLM应用开发(10)-模型部署与推理(模型适配优化之GPU面面观-2)

一文探秘LLM应用开发(11)-模型部署与推理(模型大小与推理性能的关系)

一文探秘LLM应用开发(12)-模型部署与推理(大模型相关参数计算及性能分析)

一文探秘LLM应用开发(13)-模型部署与推理(优化理论)

一文探秘LLM应用开发(14)-模型部署与推理(技术架构)

0
0
0
0
评论
未登录
暂无评论