LoRA 服务#
SGLang 支持使用 LoRA 适配器 与基础模型结合使用。通过整合 S-LoRA 和 Punica 的技术,SGLang 可以高效地支持在单个输入批次中为不同序列使用多个 LoRA 适配器。
LoRA 服务的参数#
以下是与多 LoRA 服务相关的服务器参数:
enable_lora: 为模型启用 LoRA 支持。如果为向后兼容性提供了--lora-paths,则此参数会自动设置为 True。lora_paths: 要加载的 LoRA 适配器列表。每个适配器必须以以下格式之一指定: | = | JSON 格式 {"lora_name":str,"lora_path":str,"pinned":bool}。max_loras_per_batch: 每个批次使用的最大适配器数量。此参数会影响为多 LoRA 服务保留的 GPU 内存量,因此在内存不足时应设置为较小的值。默认为 8。max_loaded_loras: 如果指定,它会限制一次在 CPU 内存中加载的 LoRA 适配器的最大数量。该值必须大于或等于max-loras-per-batch。lora_eviction_policy: 当 GPU 内存池已满时的 LoRA 适配器淘汰策略。lru: 最近最少使用(默认,更好的缓存效率)。fifo: 先进先出。lora_backend: 用于运行 LoRA 模块的 GEMM 内核的后端。目前我们支持 Triton LoRA 后端 (triton) 和分块 SGMV 后端 (csgmv)。将来,会添加基于 Cutlass 或 Cuda 内核的更快后端。max_lora_rank: 应支持的最大 LoRA 秩。如果未指定,它将从--lora-paths中提供的适配器自动推断。当你希望在服务器启动后动态加载具有更大 LoRA 秩的适配器时,需要此参数。lora_target_modules: 应应用 LoRA 的所有目标模块的并集集(例如,q_proj、k_proj、gate_proj)。如果未指定,它将从--lora-paths中提供的适配器自动推断。当你希望在服务器启动后动态加载具有不同目标模块的适配器时,需要此参数。你也可以将其设置为all以对所有支持的模块启用 LoRA。但是,在额外模块上启用 LoRA 会带来轻微的性能开销。如果你的应用程序对性能敏感,我们建议只指定你计划加载适配器的模块。--max-lora-chunk-size: 分块 SGMV LoRA 后端的最大块大小。仅在 –lora-backend 为 'csgmv' 时使用。选择更大的值可能会提高性能。请根据需要根据你的硬件和工作负载调整此值。默认为 16。tp_size: SGLang 支持与张量并行一起进行 LoRA 服务。tp_size控制张量并行使用的 GPU 数量。关于张量分片策略的更多详细信息可以在 S-Lora 论文中找到。
从客户端角度看,用户需要提供字符串列表作为输入批次,以及每个输入序列对应的适配器名称列表。
使用方法#
服务单个适配器#
注意: SGLang 通过两种 API 支持 LoRA 适配器:
OpenAI 兼容 API (
/v1/chat/completions,/v1/completions):使用model:adapter-name语法。示例请参见 使用 LoRA 的 OpenAI API。原生 API (
/generate):在请求体中传递lora_path(如下所示)。
[ ]:
import json
import requests
from sglang.test.doc_patch import launch_server_cmd
from sglang.utils import wait_for_server, terminate_process
[ ]:
server_process, port = launch_server_cmd(
"""
python3 -m sglang.launch_server --model-path meta-llama/Meta-Llama-3.1-8B-Instruct \
--enable-lora \
--lora-paths lora0=algoprog/fact-generation-llama-3.1-8b-instruct-lora \
--max-loras-per-batch 1 \
--log-level warning \
"""
)
wait_for_server(f"http://localhost:{port}")
[ ]:
url = f"http://127.0.0.1:{port}"
json_data = {
"text": [
"列出3个国家及其首都。",
"列出3个国家及其首都。",
],
"sampling_params": {"max_new_tokens": 32, "temperature": 0},
# 第一个输入使用 lora0,第二个输入使用基础模型
"lora_path": ["lora0", None],
}
response = requests.post(
url + "/generate",
json=json_data,
)
print(f"输出 0: {response.json()[0]['text']}")
print(f"输出 1: {response.json()[1]['text']}")
[ ]:
terminate_process(server_process)
服务多个适配器#
[ ]:
server_process, port = launch_server_cmd(
"""
python3 -m sglang.launch_server --model-path meta-llama/Meta-Llama-3.1-8B-Instruct \
--enable-lora \
--lora-paths lora0=algoprog/fact-generation-llama-3.1-8b-instruct-lora \
lora1=Nutanix/Meta-Llama-3.1-8B-Instruct_lora_4_alpha_16 \
--max-loras-per-batch 2 \
--log-level warning \
"""
)
wait_for_server(f"http://localhost:{port}")
[ ]:
url = f"http://127.0.0.1:{port}"
json_data = {
"text": [
"列出3个国家及其首都。",
"列出3个国家及其首都。",
],
"sampling_params": {"max_new_tokens": 32, "temperature": 0},
# 第一个输入使用 lora0,第二个输入使用 lora1
"lora_path": ["lora0", "lora1"],
}
response = requests.post(
url + "/generate",
json=json_data,
)
print(f"输出 0: {response.json()[0]['text']}")
print(f"输出 1: {response.json()[1]['text']}")
[ ]:
terminate_process(server_process)
动态 LoRA 加载#
除了通过 --lora-paths 在服务器启动时指定所有适配器外,你还可以通过 /load_lora_adapter 和 /unload_lora_adapter API 动态加载和卸载 LoRA 适配器。
当使用动态 LoRA 加载时,建议在启动时明确指定 --max-lora-rank 和 --lora-target-modules。为了向后兼容,如果没有明确提供这些值,SGLang 将从 --lora-paths 中推断它们。但是,在这种情况下,你必须确保所有动态加载的适配器与初始 --lora-paths 中的适配器共享相同的形状(秩和目标模块),或者严格是"更小"的。
[ ]:
lora0 = "Nutanix/Meta-Llama-3.1-8B-Instruct_lora_4_alpha_16" # 秩 - 4, 目标模块 - q_proj, k_proj, v_proj, o_proj, gate_proj
lora1 = "algoprog/fact-generation-llama-3.1-8b-instruct-lora" # 秩 - 64, 目标模块 - q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj
lora0_new = "philschmid/code-llama-3-1-8b-text-to-sql-lora" # 秩 - 256, 目标模块 - q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj
# 下面的 `--target-lora-modules` 参数技术上不是必需的,因为服务器会已经指定了所有目标模块的 lora0 中推断它。
# 我们在这里添加它只是为了演示用法。
server_process, port = launch_server_cmd(
"""
python3 -m sglang.launch_server --model-path meta-llama/Meta-Llama-3.1-8B-Instruct \
--enable-lora \
--cuda-graph-max-bs 2 \
--max-loras-per-batch 2 \
--max-lora-rank 256
--lora-target-modules all
--log-level warning
"""
)
url = f"http://127.0.0.1:{port}"
wait_for_server(url)
加载适配器 lora0
[ ]:
response = requests.post(
url + "/load_lora_adapter",
json={
"lora_name": "lora0",
"lora_path": lora0,
},
)
if response.status_code == 200:
print("LoRA 适配器加载成功。", response.json())
else:
print("加载 LoRA 适配器失败。", response.json())
加载适配器 lora1:
[ ]:
response = requests.post(
url + "/load_lora_adapter",
json={
"lora_name": "lora1",
"lora_path": lora1,
},
)
if response.status_code == 200:
print("LoRA 适配器加载成功。", response.json())
else:
print("加载 LoRA 适配器失败。", response.json())
检查推理输出:
[ ]:
url = f"http://127.0.0.1:{port}"
json_data = {
"text": [
"列出3个国家及其首都。",
"列出3个国家及其首都。",
],
"sampling_params": {"max_new_tokens": 32, "temperature": 0},
# 第一个输入使用 lora0,第二个输入使用 lora1
"lora_path": ["lora0", "lora1"],
}
response = requests.post(
url + "/generate",
json=json_data,
)
print(f"lora0 的输出: \n{response.json()[0]['text']}\n")
print(f"lora1 的输出: \n{response.json()[1]['text']}\n")
卸载 lora0 并替换为不同的适配器:
[ ]:
response = requests.post(
url + "/unload_lora_adapter",
json={
"lora_name": "lora0",
},
)
response = requests.post(
url + "/load_lora_adapter",
json={
"lora_name": "lora0",
"lora_path": lora0_new,
},
)
if response.status_code == 200:
print("LoRA 适配器加载成功。", response.json())
else:
print("加载 LoRA 适配器失败。", response.json())
再次检查输出:
[ ]:
url = f"http://127.0.0.1:{port}"
json_data = {
"text": [
"列出3个国家及其首都。",
"列出3个国家及其首都。",
],
"sampling_params": {"max_new_tokens": 32, "temperature": 0},
# 第一个输入使用 lora0,第二个输入使用 lora1
"lora_path": ["lora0", "lora1"],
}
response = requests.post(
url + "/generate",
json=json_data,
)
print(f"lora0 的输出: \n{response.json()[0]['text']}\n")
print(f"lora1 的输出: \n{response.json()[1]['text']}\n")
OpenAI 兼容 API 使用#
你可以通过在 model 字段中使用 base-model:adapter-name 语法(例如,qwen/qwen2.5-0.5b-instruct:adapter_a)来指定适配器,从而通过 OpenAI 兼容 API 使用 LoRA 适配器。更多详细信息和示例,请参阅 OpenAI 文档中的"使用 LoRA 适配器"部分:openai_api_completions.ipynb。
[ ]:
terminate_process(server_process)
LoRA GPU 固定#
另一个高级选项是在加载期间将适配器指定为 pinned(固定)。当适配器被固定时,它会被永久分配给可用的 GPU 池槽之一(由 --max-loras-per-batch 配置),并且在运行时不会被从 GPU 内存中逐出。相反,它会一直保留直到被明确卸载。
在跨请求频繁使用相同适配器的场景中,这可以通过避免重复的内存传输和重新初始化开销来提高性能。但是,由于 GPU 池槽是有限的,固定适配器会降低系统按需动态加载其他适配器的灵活性。如果固定了太多适配器,可能会导致性能下降,在最极端的情况下(固定适配器数量 == max-loras-per-batch),可能会停止所有未固定的请求。因此,目前 SGLang 将固定适配器的最大数量限制为 max-loras-per-batch - 1,以防止意外的饥饿现象。
在下面的示例中,我们启动一个服务器,其中 lora1 作为固定适配器加载,lora2 和 lora3 作为常规(未固定)适配器加载。请注意,我们故意以两种不同的格式指定 lora2 和 lora3,以演示这两种格式都受支持。
[ ]:
server_process, port = launch_server_cmd(
"""
python3 -m sglang.launch_server --model-path meta-llama/Meta-Llama-3.1-8B-Instruct \
--enable-lora \
--cuda-graph-max-bs 8 \
--max-loras-per-batch 3 \
--max-lora-rank 256 \
--lora-target-modules all \
--lora-paths \
{"lora_name":"lora0","lora_path":"Nutanix/Meta-Llama-3.1-8B-Instruct_lora_4_alpha_16","pinned":true} \
{"lora_name":"lora1","lora_path":"algoprog/fact-generation-llama-3.1-8b-instruct-lora"} \
lora2=philschmid/code-llama-3-1-8b-text-to-sql-lora
--log-level warning
"""
)
url = f"http://127.0.0.1:{port}"
wait_for_server(url)
你还可以在动态适配器加载期间将适配器指定为固定。在下面的示例中,我们将 lora2 重新加载为固定适配器:
[ ]:
response = requests.post(
url + "/unload_lora_adapter",
json={
"lora_name": "lora1",
},
)
response = requests.post(
url + "/load_lora_adapter",
json={
"lora_name": "lora1",
"lora_path": "algoprog/fact-generation-llama-3.1-8b-instruct-lora",
"pinned": True, # 将适配器固定到 GPU
},
)
验证结果是否符合预期:
[ ]:
url = f"http://127.0.0.1:{port}"
json_data = {
"text": [
"列出3个国家及其首都。",
"列出3个国家及其首都。",
"列出3个国家及其首都。",
],
"sampling_params": {"max_new_tokens": 32, "temperature": 0},
# 第一个输入使用 lora0,第二个输入使用 lora1
"lora_path": ["lora0", "lora1", "lora2"],
}
response = requests.post(
url + "/generate",
json=json_data,
)
print(f"lora0(固定)的输出: \n{response.json()[0]['text']}\n")
print(f"lora1(固定)的输出: \n{response.json()[1]['text']}\n")
print(f"lora2(未固定)的输出: \n{response.json()[2]['text']}\n")
[ ]:
terminate_process(server_process)
选择 LoRA 后端#
SGLang 支持两种 LoRA 后端,你可以使用 --lora-backend 参数从中选择:
triton: 默认的基本 Triton 后端。csgmv: 专为高并发场景优化的分块 SGMV 后端。
csgmv 后端是最近推出的,旨在提高高并发场景下的性能。我们的基准测试表明,它比基本的 triton 后端实现了 20% 到 80% 的延迟改进。 目前它处于预览阶段,我们期望在未来的版本中将其设置为默认的 LoRA 后端。在此之前,你可以通过手动设置 --lora-backend 服务器配置来采用它。
[ ]:
server_process, port = launch_server_cmd(
"""
python3 -m sglang.launch_server \
--model-path meta-llama/Meta-Llama-3.1-8B-Instruct \
--enable-lora \
--lora-backend csgmv \
--max-loras-per-batch 16 \
--lora-paths lora1=path/to/lora1 lora2=path/to/lora2
"""
)
[ ]:
terminate_process(server_process)
未来工作#
LoRA 相关功能的发展路线图可以在此 issue 中找到。其他功能,包括嵌入层、统一分页、Cutlass 后端仍在开发中。