跳到内容

LoRA 适配器

本文档将向您展示如何在基础模型之上,将 LoRA 适配器与 vLLM 结合使用。

LoRA 适配器可用于任何实现了 SupportsLoRA 的 vLLM 模型。

适配器可以以最小的开销高效地按请求提供服务。首先,我们下载适配器并将其本地保存,使用以下命令:

from huggingface_hub import snapshot_download

sql_lora_path = snapshot_download(repo_id="yard1/llama-2-7b-sql-lora-test")

然后我们实例化基础模型,并传入 enable_lora=True 标志

from vllm import LLM, SamplingParams
from vllm.lora.request import LoRARequest

llm = LLM(model="meta-llama/Llama-2-7b-hf", enable_lora=True)

现在我们可以提交提示并使用 lora_request 参数调用 llm.generateLoRARequest 的第一个参数是一个人类可识别的名称,第二个参数是适配器的全局唯一 ID,第三个参数是 LoRA 适配器的路径。

代码
sampling_params = SamplingParams(
    temperature=0,
    max_tokens=256,
    stop=["[/assistant]"]
)

prompts = [
    "[user] Write a SQL query to answer the question based on the table schema.\n\n context: CREATE TABLE table_name_74 (icao VARCHAR, airport VARCHAR)\n\n question: Name the ICAO for lilongwe international airport [/user] [assistant]",
    "[user] Write a SQL query to answer the question based on the table schema.\n\n context: CREATE TABLE table_name_11 (nationality VARCHAR, elector VARCHAR)\n\n question: When Anchero Pantaleone was the elector what is under nationality? [/user] [assistant]",
]

outputs = llm.generate(
    prompts,
    sampling_params,
    lora_request=LoRARequest("sql_adapter", 1, sql_lora_path)
)

请查看 examples/offline_inference/multilora_inference.py,了解如何将 LoRA 适配器与异步引擎结合使用,以及如何使用更高级的配置选项。

服务 LoRA 适配器

LoRA 适配模型也可以通过兼容 Open-AI 的 vLLM 服务器进行服务。为此,我们在启动服务器时使用 --lora-modules {name}={path} {name}={path} 来指定每个 LoRA 模块。

vllm serve meta-llama/Llama-2-7b-hf \
    --enable-lora \
    --lora-modules sql-lora=$HOME/.cache/huggingface/hub/models--yard1--llama-2-7b-sql-lora-test/snapshots/0dfa347e8877a4d4ed19ee56c140fa518470028c/

注意

提交 ID 0dfa347e8877a4d4ed19ee56c140fa518470028c 可能会随时间变化。请检查您环境中的最新提交 ID,以确保使用正确。

服务器入口点接受所有其他 LoRA 配置参数(如 max_loras, max_lora_rank, max_cpu_loras 等),这些参数将应用于所有后续请求。查询 /models 端点时,我们应该能看到我们的 LoRA 及其基础模型(如果未安装 jq,您可以按照此指南进行安装)。

命令
curl localhost:8000/v1/models | jq .
{
    "object": "list",
    "data": [
        {
            "id": "meta-llama/Llama-2-7b-hf",
            "object": "model",
            ...
        },
        {
            "id": "sql-lora",
            "object": "model",
            ...
        }
    ]
}

请求可以通过 model 请求参数指定 LoRA 适配器,就像指定任何其他模型一样。请求将根据服务器范围内的 LoRA 配置进行处理(即与基础模型请求并行,并且如果提供了其他 LoRA 适配器请求且 max_loras 设置得足够高,则也与它们并行处理)。

以下是一个请求示例

curl https://:8000/v1/completions \
    -H "Content-Type: application/json" \
    -d '{
        "model": "sql-lora",
        "prompt": "San Francisco is a",
        "max_tokens": 7,
        "temperature": 0
    }' | jq

动态服务 LoRA 适配器

除了在服务器启动时服务 LoRA 适配器外,vLLM 服务器还支持通过专用的 API 端点和插件在运行时动态配置 LoRA 适配器。当需要即时更改模型的灵活性时,此功能会特别有用。

注意:在生产环境中启用此功能存在风险,因为用户可能会参与模型适配器管理。

要启用动态 LoRA 配置,请确保环境变量 VLLM_ALLOW_RUNTIME_LORA_UPDATING 设置为 True

export VLLM_ALLOW_RUNTIME_LORA_UPDATING=True

使用 API 端点

加载 LoRA 适配器

要动态加载 LoRA 适配器,请向 /v1/load_lora_adapter 端点发送 POST 请求,并附上要加载适配器的必要详细信息。请求的有效载荷应包含 LoRA 适配器的名称和路径。

加载 LoRA 适配器的请求示例

curl -X POST https://:8000/v1/load_lora_adapter \
-H "Content-Type: application/json" \
-d '{
    "lora_name": "sql_adapter",
    "lora_path": "/path/to/sql-lora-adapter"
}'

成功请求后,API 将从 vllm serve 返回 200 OK 状态码,并且 curl 返回响应体:Success: LoRA adapter 'sql_adapter' added successfully。如果发生错误,例如找不到或无法加载适配器,将返回相应的错误消息。

卸载 LoRA 适配器

要卸载先前加载的 LoRA 适配器,请向 /v1/unload_lora_adapter 端点发送 POST 请求,并附上要卸载适配器的名称或 ID。

成功请求后,API 将从 vllm serve 返回 200 OK 状态码,并且 curl 返回响应体:Success: LoRA adapter 'sql_adapter' removed successfully

卸载 LoRA 适配器的请求示例

curl -X POST https://:8000/v1/unload_lora_adapter \
-H "Content-Type: application/json" \
-d '{
    "lora_name": "sql_adapter"
}'

使用插件

或者,您可以使用 LoRAResolver 插件动态加载 LoRA 适配器。LoRAResolver 插件使您能够从本地文件系统和 S3 等本地和远程源加载 LoRA 适配器。在每个请求中,当出现尚未加载的新模型名称时,LoRAResolver 将尝试解析并加载相应的 LoRA 适配器。

如果您想从不同源加载 LoRA 适配器,可以设置多个 LoRAResolver 插件。例如,您可以有一个用于本地文件的解析器,另一个用于 S3 存储。vLLM 将加载它找到的第一个 LoRA 适配器。

您可以安装现有插件或实现自己的插件。默认情况下,vLLM 包含一个用于从本地目录加载 LoRA 适配器的解析器插件。要启用此解析器,请将 VLLM_ALLOW_RUNTIME_LORA_UPDATING 设置为 True,将 VLLM_PLUGINS 设置为包含 lora_filesystem_resolver,然后将 VLLM_LORA_RESOLVER_CACHE_DIR 设置为本地目录。当 vLLM 接收到使用 LoRA 适配器 foobar 的请求时,它将首先在本地目录中查找名为 foobar 的目录,并尝试将该目录的内容作为 LoRA 适配器加载。如果成功,请求将正常完成,并且该适配器将可在服务器上正常使用。

或者,按照以下示例步骤实现您自己的插件

  1. 实现 LoRAResolver 接口。

    一个简单的 S3 LoRAResolver 实现示例
    import os
    import s3fs
    from vllm.lora.request import LoRARequest
    from vllm.lora.resolver import LoRAResolver
    
    class S3LoRAResolver(LoRAResolver):
        def __init__(self):
            self.s3 = s3fs.S3FileSystem()
            self.s3_path_format = os.getenv("S3_PATH_TEMPLATE")
            self.local_path_format = os.getenv("LOCAL_PATH_TEMPLATE")
    
        async def resolve_lora(self, base_model_name, lora_name):
            s3_path = self.s3_path_format.format(base_model_name=base_model_name, lora_name=lora_name)
            local_path = self.local_path_format.format(base_model_name=base_model_name, lora_name=lora_name)
    
            # Download the LoRA from S3 to the local path
            await self.s3._get(
                s3_path, local_path, recursive=True, maxdepth=1
            )
    
            lora_request = LoRARequest(
                lora_name=lora_name,
                lora_path=local_path,
                lora_int_id=abs(hash(lora_name))
            )
            return lora_request
    
  2. 注册 LoRAResolver 插件。

    from vllm.lora.resolver import LoRAResolverRegistry
    
    s3_resolver = S3LoRAResolver()
    LoRAResolverRegistry.register_resolver("s3_resolver", s3_resolver)
    

    欲了解更多详情,请参阅vLLM 的插件系统

--lora-modules 的新格式

在之前的版本中,用户通过以下格式提供 LoRA 模块,可以是键值对形式或 JSON 格式。例如:

--lora-modules sql-lora=$HOME/.cache/huggingface/hub/models--yard1--llama-2-7b-sql-lora-test/snapshots/0dfa347e8877a4d4ed19ee56c140fa518470028c/

这仅包含每个 LoRA 模块的 namepath,但未提供指定 base_model_name 的方式。现在,您可以使用 JSON 格式在指定名称和路径的同时指定 base_model_name。例如:

--lora-modules '{"name": "sql-lora", "path": "/path/to/lora", "base_model_name": "meta-llama/Llama-2-7b"}'

为了提供向后兼容性支持,您仍然可以使用旧的键值对格式 (name=path),但在那种情况下,base_model_name 将保持未指定。

模型卡中的 LoRA 模型沿袭

--lora-modules 的新格式主要旨在支持在模型卡中显示父模型信息。以下是当前响应如何支持此功能的说明:

  • LoRA 模型 sql-loraparent 字段现在链接到其基础模型 meta-llama/Llama-2-7b-hf。这正确反映了基础模型和 LoRA 适配器之间的层次关系。
  • root 字段指向 LoRA 适配器的工件位置。
命令输出
$ curl https://:8000/v1/models

{
    "object": "list",
    "data": [
        {
        "id": "meta-llama/Llama-2-7b-hf",
        "object": "model",
        "created": 1715644056,
        "owned_by": "vllm",
        "root": "~/.cache/huggingface/hub/models--meta-llama--Llama-2-7b-hf/snapshots/01c7f73d771dfac7d292323805ebc428287df4f9/",
        "parent": null,
        "permission": [
            {
            .....
            }
        ]
        },
        {
        "id": "sql-lora",
        "object": "model",
        "created": 1715644056,
        "owned_by": "vllm",
        "root": "~/.cache/huggingface/hub/models--yard1--llama-2-7b-sql-lora-test/snapshots/0dfa347e8877a4d4ed19ee56c140fa518470028c/",
        "parent": meta-llama/Llama-2-7b-hf,
        "permission": [
            {
            ....
            }
        ]
        }
    ]
}

多模态模型的默认 LoRA 模型

一些模型,例如Granite SpeechPhi-4-multimodal-instruct多模态模型,包含 LoRA 适配器,这些适配器在存在给定模态时预期始终应用。用上述方法管理这可能有点繁琐,因为它要求用户发送 LoRARequest(离线),或者根据请求的多模态数据内容在基础模型和 LoRA 模型之间(服务器端)过滤请求。

为此,我们允许注册默认的多模态 LoRA 来自动处理此问题,用户可以将每个模态映射到一个 LoRA 适配器,以便在相应的输入存在时自动应用。请注意,目前每个提示只允许一个 LoRA;如果提供了多种模态,并且每种模态都注册到给定模态,则它们都不会被应用。

离线推理的用法示例
from transformers import AutoTokenizer
from vllm import LLM, SamplingParams
from vllm.assets.audio import AudioAsset

model_id = "ibm-granite/granite-speech-3.3-2b"
tokenizer = AutoTokenizer.from_pretrained(model_id)

def get_prompt(question: str, has_audio: bool):
    """Build the input prompt to send to vLLM."""
    if has_audio:
        question = f"<|audio|>{question}"
    chat = [
        {
            "role": "user",
            "content": question
        }
    ]
    return tokenizer.apply_chat_template(chat, tokenize=False)


llm = LLM(
    model=model_id,
    enable_lora=True,
    max_lora_rank=64,
    max_model_len=2048,
    limit_mm_per_prompt={"audio": 1},
    # Will always pass a `LoRARequest` with the `model_id`
    # whenever audio is contained in the request data.
    default_mm_loras = {"audio": model_id},
    enforce_eager=True,
)

question = "can you transcribe the speech into a written format?"
prompt_with_audio = get_prompt(
    question=question,
    has_audio=True,
)
audio = AudioAsset("mary_had_lamb").audio_and_sample_rate

inputs = {
    "prompt": prompt_with_audio,
    "multi_modal_data": {
        "audio": audio,
    }
}


outputs = llm.generate(
    inputs,
    sampling_params=SamplingParams(
        temperature=0.2,
        max_tokens=64,
    ),
)

您还可以传递一个 --default-mm-loras 的 JSON 字典,将模态映射到 LoRA 模型 ID。例如,在启动服务器时:

vllm serve ibm-granite/granite-speech-3.3-2b \
    --max-model-len 2048 \
    --enable-lora \
    --default-mm-loras '{"audio":"ibm-granite/granite-speech-3.3-2b"}' \
    --max-lora-rank 64

注意:默认多模态 LoRA 目前仅适用于 .generate 和聊天补全。