跳到内容

LoRA 适配器

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

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 适配的模型也可以与兼容 OpenAI 的 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,以确保您使用的是正确的 ID。

服务器入口点接受所有其他 LoRA 配置参数(max_lorasmax_lora_rankmax_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 设置得足够高,则可能与其他 LoRA 适配器请求并行处理)。

以下是一个示例请求

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 和聊天补全。

使用技巧

配置 max_lora_rank

--max-lora-rank 参数控制 LoRA 适配器允许的最大秩。此设置会影响内存分配和性能。

  • 将其设置为最大秩 — 您计划使用的所有 LoRA 适配器中的最大值。
  • 避免设置过高 — 使用远大于所需的值会浪费内存并可能导致性能问题。

例如,如果您的 LoRA 适配器具有秩 [16, 32, 64],则使用 --max-lora-rank 64 而不是 256。

# Good: matches actual maximum rank
vllm serve model --enable-lora --max-lora-rank 64

# Bad: unnecessarily high, wastes memory
vllm serve model --enable-lora --max-lora-rank 256