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.generate
。LoRARequest
的第一个参数是一个人类可识别的名称,第二个参数是适配器的全局唯一 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
,您可以按照此指南进行安装)。
命令
请求可以通过 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
。
使用 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 适配器加载。如果成功,请求将正常完成,并且该适配器将可在服务器上正常使用。
或者,按照以下示例步骤实现您自己的插件
-
实现 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
-
注册
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 模块的 name
和 path
,但未提供指定 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-lora
的parent
字段现在链接到其基础模型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 Speech和Phi-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
和聊天补全。