跳至内容

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)

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

Code
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以确保使用正确的版本。

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

Command
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 http://localhost: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 http://localhost: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 http://localhost: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. Implement the LoRAResolver interface.

    Example of a simple S3 LoRAResolver implementation
    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's Plugins System

--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适配器的工件存储位置。
Command output
$ curl http://localhost: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;如果提供了多个模态且每个模态都已注册到指定模态,则这些适配器都不会被应用。

Example usage for offline inference
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,
    ),
)

你也可以传入一个json字典作为--default-mm-loras参数,用于映射模态到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和聊天补全功能。

优云智算