量化 KV 缓存#

FP8 KV 缓存#

将 KV 缓存量化为 FP8 可以减少其内存占用。 这增加了可以存储在缓存中的令牌数量,从而提高了吞吐量。

FP8 格式#

OCP(开放计算项目) 指定了两种常见的 8 位浮点数据格式

  • E5M2(5 位指数和 2 位尾数)

  • E4M3FN(4 位指数和 3 位尾数,通常缩写为 E4M3)

与 E5M2 相比,E4M3 格式提供更高的精度。 但是,由于其动态范围较小 (±240.0),E4M3 通常需要更高精度 (FP32) 的缩放因子以及每个量化张量。

当前限制#

目前,仅支持每个张量(标量)缩放因子。 支持更精细粒度的缩放因子(例如,每通道)的开发正在进行中。

性能影响#

当前的 FP8 KV 缓存实现主要通过允许大约两倍的 KV 缓存分配空间来提高吞吐量。 这使得能够

  • 处理单个请求的更长上下文长度,或

  • 处理更多并发请求批次

但是,目前没有延迟改进,因为该实现尚未包括融合的反量化和注意力操作。 未来版本将支持具有硬件加速的量化注意力,这应该会提供额外的性能优势。 虽然最新的硅产品(例如 AMD MI300、NVIDIA Hopper 或更高版本)支持 FP8 和其他格式(fp32、fp16、bf16)之间的原生硬件转换,但这种优势尚未完全实现。

研究表明,FP8 E4M3 量化通常只会略微降低推理精度,使其成为吞吐量优化的实用选择。

使用示例#

以下是如何启用 FP8 量化的示例

# To calculate kv cache scales on the fly enable the calculate_kv_scales
# parameter

from vllm import LLM, SamplingParams

sampling_params = SamplingParams(temperature=0.7, top_p=0.8)
llm = LLM(model="meta-llama/Llama-2-7b-chat-hf",
          kv_cache_dtype="fp8",
          calculate_kv_scales=True)
prompt = "London is the capital of"
out = llm.generate(prompt, sampling_params)[0].outputs[0].text
print(out)

kv_cache_dtype 参数指定 KV 缓存存储的数据类型

  • "auto":使用模型的默认“未量化”数据类型

  • "fp8""fp8_e4m3":在 CUDA 11.8+ 和 ROCm (AMD GPU) 上受支持

  • "fp8_e5m2":在 CUDA 11.8+ 上受支持

用于提高准确性的校准尺度#

为了在使用 FP8 KV 缓存时获得最佳模型质量,我们建议使用针对代表性推理数据调整的校准尺度。 LLM Compressor 是此过程的推荐工具。

安装#

首先,安装所需的依赖项

pip install llmcompressor

使用范例#

这是一个使用 meta-llama/Llama-3.1-8B-Instruct 的完整示例(大多数模型可以使用相同的模式)

from datasets import load_dataset
from transformers import AutoModelForCausalLM, AutoTokenizer
from llmcompressor.transformers import oneshot

# Select model and load it
MODEL_ID = "meta-llama/Llama-3.1-8B-Instruct"
model = AutoModelForCausalLM.from_pretrained(MODEL_ID, device_map="auto", torch_dtype="auto")
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)

# Select calibration dataset
DATASET_ID = "HuggingFaceH4/ultrachat_200k"
DATASET_SPLIT = "train_sft"

# Configure calibration parameters
NUM_CALIBRATION_SAMPLES = 512  # 512 samples is a good starting point
MAX_SEQUENCE_LENGTH = 2048

# Load and preprocess dataset
ds = load_dataset(DATASET_ID, split=DATASET_SPLIT)
ds = ds.shuffle(seed=42).select(range(NUM_CALIBRATION_SAMPLES))

def process_and_tokenize(example):
    text = tokenizer.apply_chat_template(example["messages"], tokenize=False)
    return tokenizer(
        text,
        padding=False,
        max_length=MAX_SEQUENCE_LENGTH,
        truncation=True,
        add_special_tokens=False,
    )

ds = ds.map(process_and_tokenize, remove_columns=ds.column_names)

# Configure quantization settings
recipe = """
quant_stage:
    quant_modifiers:
        QuantizationModifier:
            kv_cache_scheme:
                num_bits: 8
                type: float
                strategy: tensor
                dynamic: false
                symmetric: true
"""

# Apply quantization
oneshot(
    model=model,
    dataset=ds,
    recipe=recipe,
    max_seq_length=MAX_SEQUENCE_LENGTH,
    num_calibration_samples=NUM_CALIBRATION_SAMPLES,
)

# Save quantized model
SAVE_DIR = MODEL_ID.split("/")[1] + "-FP8-KV"
model.save_pretrained(SAVE_DIR, save_compressed=True)
tokenizer.save_pretrained(SAVE_DIR)

上面的脚本将在当前目录中创建一个文件夹,其中包含您的量化模型(例如,Llama-3.1-8B-Instruct-FP8-KV),并带有校准尺度。

运行模型时,您必须指定 kv_cache_dtype="fp8" 才能启用 kv 缓存量化并使用尺度。

from vllm import LLM, SamplingParams

sampling_params = SamplingParams(temperature=0.7, top_p=0.8)
llm = LLM(model="Llama-3.1-8B-Instruct-FP8-KV", kv_cache_dtype="fp8")
prompt = "London is the capital of"
out = llm.generate(prompt, sampling_params)[0].outputs[0].text
print(out)