生成式人工智能/大语言模型

使用 NVIDIA TensorRT-LLM 调整和部署 LoRA LLM

大型语言模型 (LLM) 可以从大量文本中学习,并为各种任务和领域生成流畅、一致的文本,从而彻底改变自然语言处理 (NLP)。然而,定制 LLM 是一个具有挑战性的任务,通常需要 训练过程,这非常耗时且计算成本高昂。此外,训练 LLM 需要多样化且具有代表性的数据集,这可能很难获取和整理。

企业如何在不支付全部训练成本的情况下利用 LLM 的强大功能?一个很有前景的解决方案是 Low-Rank Adaption (LoRA),这是一种微调方法,可以显著减少可训练参数的数量、内存需求和训练时间,同时实现与各种 NLP 任务和领域的微调相当甚至更好的性能。

本文介绍了 LoRA 的直觉、实现和一些应用。它还比较了 LoRA 与监督式微调和提示工程,并讨论了它们的优缺点。本文概述了训练和推理 LoRA 调整模型的实用指南。最后,它演示了如何使用 NVIDIA TensorRT-LLM 在 NVIDIA GPU 上优化 LoRA 模型的部署。

教程预备知识

要充分利用本教程,您需要了解 LLM 训练和推理流程的基本知识,以及:

什么是 LoRA?

LoRA 是一种微调方法,它在 LLM 架构的每一层中引入低秩矩阵,并仅训练这些矩阵,同时保持原始 LLM 权重冻结。它是 LLM 架构中支持的 LLM 自定义工具之一,NVIDIA NeMo(图 1)。

Graphic showing the suite of model customization tools in NVIDIA NeMo, with use cases and examples.
图 1.LoRA 是 NVIDIA NeMo 支持的 LLM 自定义工具和技术之一

LLM 功能强大,但通常需要自定义,尤其是在用于企业或领域特定的用例时。从简单的提示工程到监督式微调 (SFT),有许多调整选项。调整选项的选择通常基于所需数据集的大小 (提示工程为最小值,SFT 为最大值) 和计算可用性。

LoRA 调优是一种名为 Parameter Efficient Fine-Tuning (PEFT) 的调优系列。这些技术是一种中间方法。与提示工程相比,它们需要更多的训练数据和计算,但也能产生更高的准确性。常见的主题是,它们引入少量参数或层,同时保持原始 LLM 不变。

事实证明,PEFT 在使用更少的数据和计算资源的同时,实现了与 SFT 相当的准确性。与其他调整技术相比,LoRA 有几个优势。它降低了计算和内存成本,因为它只添加了几个新参数,但不添加任何层。它支持多任务学习,通过根据需要部署相关微调的 LoRA 变体,允许单基础 LLM 用于不同的任务,仅在需要时加载其低秩矩阵。

为了避免灾难性的遗忘,LoRA 利用了模型在学习新数据时对先前学习信息的有效利用。与使用提示调整和适配器等替代调整技术模型相比,LoRA 的性能优于这些模型,如 LoRA:大型语言模型的低级别适应

LoRA 背后的数学运算

LoRA 背后的数学运算基于低秩分解的理念,即通过两个较小矩阵的乘积求得矩阵的近似值。矩阵的秩是矩阵中线性独立的行数或列数。低秩矩阵的自由度较低,可以比全秩矩阵更紧凑地表示。

LoRA 对通常非常大且密集的 LLM 权重矩阵应用低秩分解。例如,如果 LLM 的隐藏大小为 1024,词汇量为 50000,则输出权重矩阵W将具有 1024 x 50000=51200,000 个参数。

LoRA 分解此矩阵W转换成两个较小的矩阵,即矩阵A形状为 1024 x r 和矩阵 B 形状为 r x  50000,其中 r是控制分解秩的超参数。这两个矩阵的乘积的形状与原始矩阵相同,但只有 1024 x r + r x 50,000 = 51,200,000 – 50,000 x (1024 – r) 参数。

超参数 r 正确设置至关重要。选择较小的 r 可以节省大量参数和显存,并实现更快的训练速度。但是,r 可能会减少在低级矩阵中捕获的特定于任务的信息。r 可能会导致过拟合。因此,必须进行实验,以便为您的特定任务和数据实现理想的精度 – 性能权衡。

LoRA 将这些低秩矩阵插入 LLM 的每一层,并将其添加到原始权重矩阵中。原始权重矩阵使用预训练的 LLM 权重初始化,并且不会在训练期间更新。低秩矩阵是随机初始化的,并且是训练期间更新的唯一参数。LoRA 还对原始矩阵和低秩矩阵的总和应用了层归一化,以稳定训练。

A diagram showing the decomposition of the LLM matrix W into low-rank matrices.
图 2.将 LLM 矩阵 W 分解为两个低级矩阵 A 和 B

多 LoRA 部署

部署 LLM 的一个挑战是如何高效地为数百或数千个调优模型提供服务。例如,Llama 2 等单个基础 LLM 在每种语言或区域设置中可能存在许多 LoRA 调优变体。标准系统需要独立加载所有模型,占用大量内存容量。利用 LoRA 的设计,通过加载单个基础模型和低秩矩阵,在每个模型中捕获较小的低秩矩阵中的所有信息(A 和 B 每个经过相应 LoRA 调优的变体)。通过这种方式,可以存储数千个 LLM,并在最小的 GPU 显存占用范围内动态高效地运行它们。

LoRA 调优

LoRA 调整需要以特定格式 (通常使用提示模板) 准备训练数据集。在形成提示时,您应确定并遵循模式,不同的用例自然会有所不同。下面显示了一个问答示例。

{
        "taskname": "squad",
        "prompt_template": "<|VIRTUAL_PROMPT_0|> Context: {context}\n\nQuestion: {question}\n\nAnswer:{answer}",
        "total_virtual_tokens": 10,
        "virtual_token_splits": [10],
        "truncate_field": "context",
        "answer_only_loss": True,
        "answer_field": "answer",
}

提示开头包含所有 10 个虚拟令牌,然后是上下文、问题,最后是答案。训练数据 JSON 对象中的相应字段将映射到此提示模板,以形成完整的训练示例。

有多个可用于自定义 LLM 的平台。您可以使用 NVIDIA NeMo 或工具,例如 拥抱面部 PEFT。有关如何使用 NeMo 在 PubMed 数据集上调整 LoRA 的示例,请参阅 NVIDIA NeMo 文档:使用 Lama 2 的 PEFT 框架

请注意,本文使用了 Hugging Face 中经过现成调整的 LLM,因此无需进行调整。

LoRA 推理

要使用 TensorRT-LLM 优化 LoRA 调整的 LLM,您必须了解其架构,并确定它最相似的常见基础架构。本教程使用 Lama 2 13B 和 Lama 2 7B 作为基础模型,以及 Hugging Face 上提供的几个 LoRA 调整变体。

第一步是使用此目录中的转换器和构建脚本编译所有模型并为硬件加速做好准备。然后,我将展示使用命令行和 Triton 推理服务器进行部署的示例。

请注意,分词器并非由 TensorRT-LLM 直接处理。但必须能够在定义的分词器系列中对其进行分类,以用于运行时以及在 Triton 中设置预处理和后处理步骤。

设置和构建 TensorRT-LLM

首先,克隆并构建 NVIDIA/TensorRT-LLM 库。最简单的方式是使用附带的 Dockerfile。这些命令将拉取基础容器并安装 TensorRT-LLM 所需的所有依赖项。然后,它将在容器中构建并安装 TensorRT-LLM 本身。

git lfs install
git clone https://github.com/NVIDIA/TensorRT-LLM.git
cd TensorRT-LLM
git submodule update --init --recursive
make -C docker release_build

检索模型权重

从 Hugging Face 下载基础模型和 LoRA 模型:

git-lfs clone https://huggingface.co/meta-llama/Llama-2-13b-hf
git-lfs clone https://huggingface.co/hfl/chinese-llama-2-lora-13b

编译模型

构建引擎时,设置 --use_lora_plugin--hf_lora_dir 参数。如果 LoRA 有一个单独的 `lm_head` 和嵌入,它们将取代 `lm_head` 和基础模型的嵌入。

python convert_checkpoint.py --model_dir /tmp/llama-v2-13b-hf \
                         --output_dir ./tllm_checkpoint_2gpu_lora \
                         --dtype float16 \
                         --tp_size 2 \
                         --hf_lora_dir /tmp/chinese-llama-2-lora-13b
                         
trtllm-build --checkpoint_dir ./tllm_checkpoint_2gpu_lora \
            --output_dir /tmp/new_lora_13b/trt_engines/fp16/2-gpu/ \
            --gpt_attention_plugin float16 \
            --gemm_plugin float16 \
            --lora_plugin float16 \
            --max_batch_size 1 \
            --max_input_len 512 \
            --max_output_len 50 \
            --use_fused_mlp

运行模型

在推理期间运行模型时,请设置 lora_dir 命令行参数。请记住使用 LoRA 分词器,因为 LoRA 调整模型的词汇量更大。

mpirun -n 2 python ../run.py --engine_dir "/tmp/new_lora_13b/trt_engines/fp16/2-gpu/" \
              --max_output_len 50 \
              --tokenizer_dir "chinese-llama-2-lora-13b/" \
              --input_text "今天天气很好,我到公园的时后," \
              --lora_dir "chinese-llama-2-lora-13b/" \
              --lora_task_uids 0 \
              --no_add_special_tokens \
              --use_py_session

 Input: "今天天气很好,我到公园的时后,"
Output: "发现公园里人很多,有的在打羽毛球,有的在打乒乓球,有的在跳绳,还有的在跑步。我和妈妈来到一个空地上,我和妈妈一起跳绳,我跳了1"

您可以运行烧蚀测试来了解 LoRa 调优模型的贡献。要轻松比较使用和不使用 LoRa 的结果,只需在命令中添加 `–lora_task_uids -1` 参数。在这种情况下,模型将忽略 LoRa 模块,结果将仅基于基础模型。

mpirun -n 2 python ../run.py --engine_dir "/tmp/new_lora_13b/trt_engines/fp16/2-gpu/" \
              --max_output_len 50 \
              --tokenizer_dir "chinese-llama-2-lora-13b/" \
              --input_text "今天天气很好,我到公园的时后," \
              --lora_dir "chinese-llama-2-lora-13b/" \
              --lora_task_uids -1 \
              --no_add_special_tokens \
              --use_py_session

 Input: "今天天气很好,我到公园的时后,"
Output: "我看见一个人坐在那边边看书书,我看起来还挺像你,可是我走过过去问了一下他说你是你吗,他说没有,然后我就说你看我看看你像你,他说说你看我像你,我说你是你,他说你是你,"

使用多个 LoRA 调优模型运行基础模型

TensorRT-LLM 支持同时运行具有多个 LoRA 调整模块的单个基础模型。在这里,我们使用两个 LoRA 检查点作为示例。作为 rank 8 的两个 Checkpoint,您可以将其设置为 --max_lora_rank 8,以减少 LoRA 插件的内存需求。

此示例使用基于中文数据集 luotuo-lora-7b-0.1 微调的 LoRA 检查点,以及基于 Japanese-Alpaka-LoRA-7b-v0 微调的 LoRA 检查点。要加载多个检查点,请使用 `–lora_dir “luotuo-lora-7b-0.1/” “Japanese-Alpaca-LoRA-7b-v0/”` 参数。TensorRT-LLM 将分配 `lora_task_uids` 这些检查点。`lora_task_uids -1` 是一个预定义值,代表基础模型。例如,通过 `lora_task_uids 0 1`,将在第一句中使用第一个 LoRA 检查点,并在第二句中使用第二个 LoRA 检查点。

要验证正确性,请将相同的中文输入美国的首都在哪里? \n答案: 三次,以及相同的日语输入 アメリカ合衆国の首都はどこですか? \n答え 三次。(在英语中,这两种输入的意思是“美国的首都在哪里? nAnswer”)。然后分别在基本模型 luotuo-lora-7b-0.1 和 Japanese-Alpaka-LoRA-7b-v0 上运行:

git-lfs clone https://huggingface.co/qychen/luotuo-lora-7b-0.1
git-lfs clone https://huggingface.co/kunishou/Japanese-Alpaca-LoRA-7b-v0
BASE_LLAMA_MODEL=llama-7b-hf/

python convert_checkpoint.py --model_dir ${BASE_LLAMA_MODEL} \
                            --output_dir ./tllm_checkpoint_1gpu_lora_rank \
                            --dtype float16 \
                            --hf_lora_dir /tmp/Japanese-Alpaca-LoRA-7b-v0 \
                            --max_lora_rank 8 \
                            --lora_target_modules "attn_q" "attn_k" "attn_v"

trtllm-build --checkpoint_dir ./tllm_checkpoint_1gpu_lora_rank \
            --output_dir /tmp/llama_7b_with_lora_qkv/trt_engines/fp16/1-gpu/ \
            --gpt_attention_plugin float16 \
            --gemm_plugin float16 \
            --lora_plugin float16 \
            --max_batch_size 1 \
            --max_input_len 512 \
            --max_output_len 50

python ../run.py --engine_dir "/tmp/llama_7b_with_lora_qkv/trt_engines/fp16/1-gpu/" \
              --max_output_len 10 \
              --tokenizer_dir ${BASE_LLAMA_MODEL} \
              --input_text "美国的首都在哪里? \n答案:" "美国的首都在哪里? \n答案:" "美国的首都在哪里? \n答案:" "アメリカ合衆国の首都はどこですか? \n答え:" "アメリカ合衆国の首都はどこですか? \n答え:" "アメリカ合衆国の首都はどこですか? \n答え:" \
              --lora_dir  "luotuo-lora-7b-0.1/" "Japanese-Alpaca-LoRA-7b-v0/" \
              --lora_task_uids -1 0 1 -1 0 1 \
              --use_py_session --top_p 0.5 --top_k 0

结果如下所示:

Input [Text 0]: " 美国的首都在哪里? \n答案:"
Output [Text 0 Beam 0]: "Washington, D.C.
What is the"

Input [Text 1]: " 美国的首都在哪里? \n答案:"
Output [Text 1 Beam 0]: "华盛顿。
"

Input [Text 2]: " 美国的首都在哪里? \n答案:"
Output [Text 2 Beam 0]: "Washington D.C.'''''"

Input [Text 3]: " アメリカ合衆国の首都はどこですか? \n答え:"
Output [Text 3 Beam 0]: "Washington, D.C.
Which of"

Input [Text 4]: " アメリカ合衆国の首都はどこですか? \n答え:"
Output [Text 4 Beam 0]: "华盛顿。
"

Input [Text 5]: " アメリカ合衆国の首都はどこですか? \n答え:"
Output [Text 5 Beam 0]: "ワシントン D.C."

请注意,luotuo-lora-7b-0.1 在第一句和第五句 (中文) 上生成正确答案。日语 – Alpaka-LoRA-7b-v0 在第六句 (日语) 上生成正确答案。

**重要提示:**如果任何 LoRA 模块包含经过微调的嵌入表或 Logit GEMM,则用户必须确保所有模型实例都使用相同的经过微调的嵌入表或 Logit GEMM。

使用 Triton 和机上批处理部署经过 LoRA 调整的模型

本节展示了如何使用 Triton 推理服务器,通过 LoRA 调优的模型进行机上批处理部署。有关设置和启动 Triton 推理服务器的具体说明,请参阅 NVIDIA TensorRT-LLM 和 NVIDIA Triton:部署 AI 编码助手。

与之前一样,首先编译启用 LoRA 的模型,这次使用基础模型 Lama 2 7B。

BASE_MODEL=llama-7b-hf

python3 tensorrt_llm/examples/llama/build.py --model_dir ${BASE_MODEL} \
                --dtype float16 \
                --remove_input_padding \
                --use_gpt_attention_plugin float16 \
                --enable_context_fmha \
                --use_gemm_plugin float16 \
                --output_dir "/tmp/llama_7b_with_lora_qkv/trt_engines/fp16/1-gpu/" \
                --max_batch_size 128 \
                --max_input_len 512 \
                --max_output_len 50 \
                --use_lora_plugin float16 \
                --lora_target_modules "attn_q" "attn_k" "attn_v" \
                --use_inflight_batching \
		        --paged_kv_cache \
                --max_lora_rank 8 \
                --world_size 1 --tp_size 1

接下来,生成 LoRA 张量,这些张量将随每个请求传入 Triton。

git-lfs clone https://huggingface.co/qychen/luotuo-lora-7b-0.1
git-lfs clone https://huggingface.co/kunishou/Japanese-Alpaca-LoRA-7b-v0

python3 tensorrt_llm/examples/hf_lora_convert.py -i Japanese-Alpaca-LoRA-7b-v0 -o Japanese-Alpaca-LoRA-7b-v0-weights --storage-type float16
python3 tensorrt_llm/examples/hf_lora_convert.py -i luotuo-lora-7b-0.1 -o luotuo-lora-7b-0.1-weights --storage-type float16

然后,如前所述,创建 Triton 模型库并启动 Triton 服务器。

最后,通过从客户端发出多个并发请求来运行 multi-LoRA 示例。机上批处理程序将在同一批量中执行具有多个 LoRA 的混合批量。

INPUT_TEXT=("美国的首都在哪里? \n答案:" "美国的首都在哪里? \n答案:" "美国的首都在哪里? \n答案:" "アメリカ合衆国の首都はどこですか? \n答え:" "アメリカ合衆国の首都はどこですか? \n答え:" "アメリカ合衆国の首都はどこですか? \n答え:")
LORA_PATHS=("" "luotuo-lora-7b-0.1-weights" "Japanese-Alpaca-LoRA-7b-v0-weights" "" "luotuo-lora-7b-0.1-weights" "Japanese-Alpaca-LoRA-7b-v0-weights")

for index in ${!INPUT_TEXT[@]}; do
    text=${INPUT_TEXT[$index]}
    lora_path=${LORA_PATHS[$index]}
    lora_arg=""
    if [ "${lora_path}" != "" ]; then
        lora_arg="--lora-path ${lora_path}"
    fi

    python3 inflight_batcher_llm/client/inflight_batcher_llm_client.py \
        --top-k 0 \
        --top-p 0.5 \
        --request-output-len 10 \
        --text "${text}" \
        --tokenizer-dir /home/scratch.trt_llm_data/llm-models/llama-models/llama-7b-hf \
        ${lora_arg} &
done

wait

输出示例如下所示:

Input sequence:  [1, 29871, 30310, 30604, 30303, 30439, 30733, 235, 164, 137, 30356, 30199, 31688, 30769, 30449, 31250, 30589, 30499, 30427, 30412, 29973, 320, 29876, 234, 176, 151, 30914, 29901]
Input sequence:  [1, 29871, 30630, 30356, 30210, 31688, 30769, 30505, 232, 150, 173, 30755, 29973, 320, 29876, 234, 176, 151, 233, 164, 139, 29901]
Input sequence:  [1, 29871, 30630, 30356, 30210, 31688, 30769, 30505, 232, 150, 173, 30755, 29973, 320, 29876, 234, 176, 151, 233, 164, 139, 29901]
Input sequence:  [1, 29871, 30310, 30604, 30303, 30439, 30733, 235, 164, 137, 30356, 30199, 31688, 30769, 30449, 31250, 30589, 30499, 30427, 30412, 29973, 320, 29876, 234, 176, 151, 30914, 29901]
Input sequence:  [1, 29871, 30310, 30604, 30303, 30439, 30733, 235, 164, 137, 30356, 30199, 31688, 30769, 30449, 31250, 30589, 30499, 30427, 30412, 29973, 320, 29876, 234, 176, 151, 30914, 29901]
Input sequence:  [1, 29871, 30630, 30356, 30210, 31688, 30769, 30505, 232, 150, 173, 30755, 29973, 320, 29876, 234, 176, 151, 233, 164, 139, 29901]
Got completed request
Input: アメリカ合衆国の首都はどこですか? \n答え:
Output beam 0: ワシントン D.C.
Output sequence:  [1, 29871, 30310, 30604, 30303, 30439, 30733, 235, 164, 137, 30356, 30199, 31688, 30769, 30449, 31250, 30589, 30499, 30427, 30412, 29973, 320, 29876, 234, 176, 151, 30914, 29901, 29871, 31028, 30373, 30203, 30279, 30203, 360, 29889, 29907, 29889]
Got completed request
Input: 美国的首都在哪里? \n答案:
Output beam 0: Washington, D.C.
What is the
Output sequence:  [1, 29871, 30630, 30356, 30210, 31688, 30769, 30505, 232, 150, 173, 30755, 29973, 320, 29876, 234, 176, 151, 233, 164, 139, 29901, 7660, 29892, 360, 29889, 29907, 29889, 13, 5618, 338, 278]
Got completed request
Input: 美国的首都在哪里? \n答案:
Output beam 0: Washington D.C.
Washington D.
Output sequence:  [1, 29871, 30630, 30356, 30210, 31688, 30769, 30505, 232, 150, 173, 30755, 29973, 320, 29876, 234, 176, 151, 233, 164, 139, 29901, 7660, 360, 29889, 29907, 29889, 13, 29956, 7321, 360, 29889]
Got completed request
Input: アメリカ合衆国の首都はどこですか? \n答え:
Output beam 0: Washington, D.C.
Which of
Output sequence:  [1, 29871, 30310, 30604, 30303, 30439, 30733, 235, 164, 137, 30356, 30199, 31688, 30769, 30449, 31250, 30589, 30499, 30427, 30412, 29973, 320, 29876, 234, 176, 151, 30914, 29901, 7660, 29892, 360, 29889, 29907, 29889, 13, 8809, 436, 310]
Got completed request
Input: アメリカ合衆国の首都はどこですか? \n答え:
Output beam 0: Washington D.C.
1. ア
Output sequence:  [1, 29871, 30310, 30604, 30303, 30439, 30733, 235, 164, 137, 30356, 30199, 31688, 30769, 30449, 31250, 30589, 30499, 30427, 30412, 29973, 320, 29876, 234, 176, 151, 30914, 29901, 7660, 360, 29889, 29907, 29889, 13, 29896, 29889, 29871, 30310]
Got completed request
Input: 美国的首都在哪里? \n答案:
Output beam 0: 华盛顿
W
Output sequence:  [1, 29871, 30630, 30356, 30210, 31688, 30769, 30505, 232, 150, 173, 30755, 29973, 320, 29876, 234, 176, 151, 233, 164, 1

结束语

TensorRT-LLM 对许多热门的 LLM 架构提供基准支持,可让用户轻松地使用各种代码 LLM 进行部署、实验和优化。 NVIDIA TensorRT – LLM 和 NVIDIA Triton 推理服务器携手合作,为高效优化、部署和运行 LLM 提供了不可或缺的工具包。TensorRT – LLM 支持 LoRA 调优模型,支持高效部署自定义 LLM,显著降低内存和计算成本。

要开始使用,请下载并设置 NVIDIA/TensorRT-LLM 开源库,并尝试使用不同的 LLM 示例。您可以使用 NVIDIA NeMo 参考 使用 Lama 2 的 NeMo 框架 PEFTNeMo 框架推理容器

 

Tags