低端PC运行LLM的5个方法

LLM Nov 16, 2024

生成式AI时代正处于巅峰时期,除了 ChatGPT Pro 之外,每个人似乎都需要拥有无限的 GPU!

这是因为这些 LLM 非常庞大,仅用于推理有时都需要几分钟,而且在使用 GPU 时也是如此。如果你没有 GPU,情况会更糟,因为 LLM 在 GPU 上花一分钟,而在 CPU 上却要花几个小时。

此外,由于 GPU并非人人都有,作为个人贡献者,应该如何研究和实验?

我们将讨论一些技术,通过这些技术,你可以大幅减少内存消耗,并可以在有限的 GPU 或 CPU 内存上运行所需的 LLM。

让我们开始吧!

1、量化模型

通常,任何 ML 模型(无论是 LLM 还是 Logistic 回归)的权重都存储为 float32 位数据类型。量化有助于将此内存需求降低到较低的精度(例如 16 位、8 位,有时也为 4 位)。通过使用更少的位来表示每个权重,可以减少模型的内存占用,从而实现更快的处理和更高效的存储,尤其是在计算资源有限的环境中,例如移动设备、边缘设备或嵌入式系统。

量化背后的核心思想是,模型中并非所有权重都需要 32 位浮点数提供的全精度。在许多情况下,较低精度的表示仍可以捕获大多数重要信息,同时减少总体内存使用量。例如,与原始 float32 表示相比,将精度降低到 int8(8 位)或 float16(16 位)可以分别将模型大小缩小 4 倍或 2 倍。

有趣的是,性能不会受到重大影响,大多数指标几乎保持不变。

使用量化生成的任何模型的 16 位变体称为半精度模型。

如何加载量化或半精度模型?

方法 1:直接加载量化模型

通常,你会在 HuggingFace 上找到不同 LLM 的量化版本。加载这些量化模型,而不是加载原始模型。

提示:在 HuggingFace 模型部分搜索 8 位、4 位、16 位模型,如下所示

单击模型卡,转到“使用此模型”按钮并运行相应 LLM 的代码。以下是 Qwen2.5 8 位版本的示例:

 # Load model directly
from transformers import AutoTokenizer, AutoModelForCausalLM

tokenizer = AutoTokenizer.from_pretrained("mlx-community/Qwen2.5.1-Coder-7B-Instruct-8bit")
model = AutoModelForCausalLM.from_pretrained("mlx-community/Qwen2.5.1-Coder-7B-Instruct-8bit")

方法 2:Bitsandbytes

BitsandBytes 是一个轻量级 Python 库,旨在通过提供量化和自定义 CUDA 函数的有效实现来优化机器学习模型(尤其是 LLM)的性能。

from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig

# Specify the quantization configuration for 8-bit
quantization_config = BitsAndBytesConfig(
    load_in_8bit=True,  # Enable 8-bit quantization
)

# Load the model and tokenizer with the specified quantization config
model_id = "meta-llama/Llama-3.1-8B-Instruct"  # Replace with "meta-llama/Llama-3.1-70B-Instruct" for 70B model

model = AutoModelForCausalLM.from_pretrained(
    model_id,
    quantization_config=quantization_config,
    device_map="auto"  # Automatically allocate GPU memory
)

tokenizer = AutoTokenizer.from_pretrained(model_id)

# Example usage: Generate text based on a prompt
prompt = "What are the benefits of using large language models?"
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)

# Generate output
with torch.no_grad():
    outputs = model.generate(**inputs, max_length=100)
    
# Decode and print the generated text
generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
print(generated_text)

方法 3:使用 torch 进行半精度处理

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

# Specify the model ID (e.g., Llama 3.1)
model_id = "meta-llama/Llama-3.1-8B-Instruct"  # Replace with your desired model

# Load the model in half precision
model = AutoModelForCausalLM.from_pretrained(
    model_id,
    torch_dtype=torch.float16,  # Set the model to use FP16
    device_map="auto"  # Automatically allocate GPU memory
)

...

2、SafeTensor

SafeTensor 是一种用于高效安全地存储和共享张量的文件格式和数据结构。它是为了解决与大型张量数据的序列化和反序列化相关的常见问题而引入的。

使用 SafeTensor 的一些优点是:

  • 高效存储:压缩张量数据以减少内存和磁盘空间使用率,这对于具有数十亿个参数的大型模型至关重要。
  • 数据完整性:使用内置校验和确保安全可靠的数据传输,防止在加载和共享张量期间发生损坏。
  • 更快的加载:支持并行加载张量,加快训练和推理的模型加载速度。
  • 跨框架兼容性:跨不同的深度学习框架(例如 PyTorch、TensorFlow、JAX)无缝工作,使共享模型变得更容易。
  • 安全的张量处理:提供内存安全性并防止内存损坏等错误,在分布式或多设备训练中特别有用。
  • 可扩展部署:优化模型权重存储和加载,以便在资源受限的环境(例如边缘设备或云)中部署。

如何使用 SafeTensors 文件格式?

只需提及 use_safetensors=True 并确保文件夹中存在 safetensor 格式和 safetensor.index.json。请查看此内容以供参考:

AutoModelForCausalLM.from_pretrained(<path>,
use_safetensors=True,
<rest_of_args>)

3、GGUF

GGUF是一种专门用于存储和表示 LLM 的文件格式。它被引入用于标准化和优化模型数据的存储和共享,重点关注不同机器学习框架和环境中的灵活性、效率和可移植性。

GGUF具有如下优点:

  • 高效存储:通过优化的压缩技术减少内存和磁盘使用量。
  • 更快的推理:优化模型以便在各种硬件上快速加载和高速推理。
  • 跨平台兼容性:允许在不同框架(例如 PyTorch、TensorFlow)和设备(CPU、GPU、TPU)之间共享和部署模型。
  • 支持量化:支持存储低精度模型(例如 FP16、INT8),以节省空间并加速计算。

如何使用 GGUF 文件格式?

方法 1:使用 Ollama 和 Python

参考这个视频

方法 2:使用 llama-cpp-python

示例代码如下:

from llama_cpp import Llama

llm = Llama(
      model_path="./models/7B/llama-model.gguf",
)
output = llm(
      "Q: Name the planets in the solar system? A: ", # Prompt
      max_tokens=32, # Generate up to 32 tokens, set to None to generate up to the end of the context window
      stop=["Q:", "\n"], # Stop generating just before the model would generate a new question
      echo=True # Echo the prompt back in the output
) 
print(output)

4、设备和内存映射

加载 LLM 时的设备和内存映射是指如何将模型参数(权重、激活、梯度等)分配到各种硬件资源(例如 CPU、GPU 或专用加速器)并在这些资源之间进行管理。

正确的映射对于高效使用内存、加快计算速度以及流畅的模型训练或推理至关重要。此过程可确保模型能够适应设备的可用内存,并在模型执行期间高效访问数据。

4.1 设备映射

设备映射涉及根据可用的硬件资源决定模型的每个部分(例如,层、权重、梯度)将驻留在何处。

常见设备包括:

  • CPU:中央处理单元,用于处理较小的模型或 GPU 资源不可用的任务。
  • GPU/TPU:图形处理单元或张量处理单元,它们是用于深度学习任务的专用硬件加速器,与 CPU 相比可提供显着的加速。
  • 边缘设备:专用或资源受限的设备,例如移动电话或 IoT 设备,可能需要减小模型大小或量化。

设备映射策略:

在多设备环境中,模型的各个部分(甚至不同的模型)可以放置在多个 GPU 或节点上。这通常使用数据并行(跨设备拆分数据)或模型并行(跨设备拆分模型本身)等技术来完成。

例如,在模型并行期间,神经网络的一部分(例如,前几层)可以放在一个 GPU 上,而其余层则放在另一个 GPU 上。

4.2 内存映射

LLM 上下文中的内存映射涉及将模型权重和参数直接映射到应用程序的虚拟内存空间中。这样可以高效访问模型数据,而无需一次将所有内容加载到 RAM 中。

LLM(例如 GPT-3 或 GPT-4)可以有数十亿个参数,需要大量内存。这意味着它们可能无法完全安装在单个设备上,因此需要跨设备对模型进行分区或使用内存优化策略。

内存映射策略:

梯度检查点:在前向传递期间仅存储中间激活的子集,并在后向传递期间重新计算其他子集。这以额外的计算为代价减少了训练期间的内存使用量。

修剪:删除模型中不必要或多余的权重,从而减少整体大小和内存占用。

混合精度:混合精度涉及同时使用较低精度(例如在模型加载和推理期间,混合精度将使用 16 位 (P16 或 INT8) 和更高精度 (如 FP32) 数据类型。通过在对模型性能影响最小的地方策略性地应用较低的精度,混合精度可以显着减少内存使用量并提高计算速度。

如何使用设备映射?

# Everything remains same as Half-Precision example
model = AutoModelForCausalLM.from_pretrained(
    model_id,
    device_map="auto",  # Automatically distribute the model across available devices
    trust_remote_code=True  # Trust remote code if needed (for specific models)
)

device_map 的可能值可以是

  • auto:自动在所有可用设备上分配层。
  • balanced:在设备之间均匀分布层。
  • Sequenced:按顺序将层加载到设备上。

如何使用内存映射 (CPU 卸载)?

内存映射的一个重大突破方法是 CPU 卸载,它指的是将模型计算或数据存储的某些部分从 GPU 移动到 CPU,以优化内存使用率和计算效率。

...
model = AutoModelForCausalLM.from_pretrained( model_id, device_map="auto")  
model.to('cpu')  # Move the model to CPU initially
...

设备映射与内存映射:

设备映射是指将任务分配给不同的硬件设备(例如 CPU、GPU 或 TPU)。例如,在大型模型上训练或运行推理时,GPU 可能用于繁重的计算,而 CPU 处理较轻的任务或数据准备。本质上,设备映射是关于决定哪个设备处理工作负载的哪个方面。

另一方面,内存映射处理数据在这些设备的内存中的存储方式和位置。目标是将模型的权重和中间结果分配到最有效的可用内存空间(例如 CPU RAM 或 GPU VRAM),以避免空间不足。可以将其视为确定存储和检索数据的最佳位置以最大化性能。

5、注意力切片

注意力切片是一种优化技术,它通过将注意力计算分解为更小的块来帮助减少内存使用量。它不是一次计算序列中所有标记的注意力,而是以较小的“切片”处理它们。例如,如果模型正在处理 512 个标记,它可以将它们分成 4 个较小的块(每个 128 个标记)进行处理。

通过对注意力机制进行切片,模型可以保持较低的内存占用。这对于大型模型至关重要,因为当尝试同时计算所有标记的注意力时,可能会超出可用的 GPU 内存。

如何实现注意力切片?

目前,只有 Diffusers(图像生成模型)支持注意力切片,而 Python 中的 Transformers 库不支持。

import torch
from diffusers import DiffusionPipeline

# Load the model from Hugging Face Hub
model_id = "runwayml/stable-diffusion-v1-5"  # Replace with your desired model
pipe = DiffusionPipeline.from_pretrained(
    model_id,
    torch_dtype=torch.float16  # Use FP16 for reduced memory usage
)

# Enable attention slicing
pipe.enable_attention_slicing()

# Example usage: Generate an image based on a prompt
prompt = "A photo of an astronaut riding a horse on Mars"
with torch.no_grad():
    image = pipe(prompt).images[0]

# Save or display the generated image
image.save("output.png")

6、结束语

就这样!我们介绍了几种优化 LLM 以在 GPU 或 CPU 内存有限的系统上运行的策略。无论你使用的是量化、内存映射还是注意力切片等技术,这些方法都可以帮助你充分利用硬件。


原文链接:How to run LLMs with less GPU and CPU memory?

汇智网翻译整理,转载请标明出处

Tags