LoRA 어댑터
LoRA 어댑터 (LoRA Adapters)
이 문서는 베이스 모델 위에서 LoRA 어댑터를 vLLM과 함께 사용하는 방법을 알려줘요. LoRA 어댑터는 SupportsLoRA 를 구현하는 모든 vLLM 모델과 함께 사용할 수 있습니다.
출처: 문서
본문
기본 사용 (Basic usage)
어댑터는 요청별로 최소 오버헤드로 효율적으로 서빙할 수 있어요. 먼저 어댑터를 다운로드해 로컬에 저장합니다.
from huggingface_hub import snapshot_download
sql_lora_path = snapshot_download(repo_id="jeeejeee/llama32-3b-text2sql-spider")
그 다음 베이스 모델을 인스턴스화하고 enable_lora=True 플래그를 전달합니다.
from vllm import LLM, SamplingParams
from vllm.lora.request import LoRARequest
llm = LLM(model="meta-llama/Llama-3.2-3B-Instruct", 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),
)
async engine에서 LoRA 어댑터를 사용하고 더 고급 구성 옵션을 활용하는 예시는 examples/features/lora/multilora_offline.py 를 확인해 보세요.
LoRA 어댑터 서빙 (Serving LoRA Adapters)
LoRA로 튜닝된 모델은 OpenAI 호환 vLLM 서버로도 서빙할 수 있어요. 이를 위해 서버를 시작할 때 각 LoRA 모듈을 지정하는 --lora-modules {name}={path} {name}={path} 를 사용합니다.
vllm serve meta-llama/Llama-3.2-3B-Instruct \
--enable-lora \
--lora-modules sql-lora=jeeejeee/llama32-3b-text2sql-spider
서버 엔트리포인트는 다른 모든 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-3.2-3B-Instruct",
"object": "model",
...
},
{
"id": "sql-lora",
"object": "model",
...
}
]
}
요청은 model 요청 파라미터로 다른 모델처럼 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 어댑터 동적 서빙 (Dynamically serving LoRA Adapters)
서버 시작 시 LoRA 어댑터를 서빙하는 것 외에도, vLLM 서버는 전용 API 엔드포인트와 플러그인을 통해 런타임에 LoRA 어댑터를 동적으로 구성하는 것을 지원합니다. 이 기능은 모델을 즉석에서 바꿔야 하는 유연성이 필요할 때 특히 유용합니다.
경고: 이 기능은 보안 위험이 있습니다. 격리된, 완전히 신뢰할 수 있는 환경이 아니면 프로덕션에서 사용하지 마세요.
동적 LoRA 구성을 활성화하려면 환경 변수 VLLM_ALLOW_RUNTIME_LORA_UPDATING 을 True 로 설정해야 합니다.
export VLLM_ALLOW_RUNTIME_LORA_UPDATING=True
API 엔드포인트 사용 (Using API Endpoints)
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 엔드포인트에 언로드할 어댑터의 이름 또는 ID를 담은 POST 요청을 보내세요.
요청이 성공하면 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"
}'
플러그인 사용 (Using Plugins)
또는 LoRAResolver 플러그인을 사용해 LoRA 어댑터를 동적으로 로드할 수 있어요. LoRAResolver 플러그인은 로컬 파일시스템과 S3 같은 로컬 및 원격 소스에서 LoRA 어댑터를 로드할 수 있게 해줍니다. 요청마다 아직 로드되지 않은 새 모델 이름이 있으면 LoRAResolver가 해당 LoRA 어댑터를 해석하고 로드하려고 시도합니다.
서로 다른 소스에서 LoRA 어댑터를 로드하려면 여러 LoRAResolver 플러그인을 설정할 수 있어요. 예를 들어 로컬 파일용 리졸버 하나와 S3 스토리지용 리졸버 하나를 둘 수 있습니다. vLLM은 먼저 찾은 LoRA 어댑터를 로드합니다.
기존 플러그인을 설치하거나 직접 구현할 수 있습니다. 기본적으로 vLLM에는 로컬 디렉터리에서 LoRA 어댑터를 로드하는 리졸버 플러그인과 Hugging Face Hub의 저장소에서 LoRA 어댑터를 로드하는 리졸버 플러그인 이 함께 제공됩니다. 이 중 하나를 활성화하려면 VLLM_ALLOW_RUNTIME_LORA_UPDATING 을 True로 설정해야 합니다.
- 로컬 디렉터리를 활용하려면
VLLM_PLUGINS에lora_filesystem_resolver를 포함하고VLLM_LORA_RESOLVER_CACHE_DIR를 로컬 디렉터리로 설정하세요. vLLM이 LoRA 어댑터foobar를 사용하는 요청을 받으면 먼저 로컬 디렉터리에서foobar디렉터리를 찾고, 그 디렉터리 내용을 LoRA 어댑터로 로드하려고 시도합니다. 성공하면 요청은 정상적으로 완료되고 그 어댑터는 이후 서버에서 정상 사용할 수 있습니다. - Hugging Face Hub의 저장소를 활용하려면
VLLM_PLUGINS에lora_hf_hub_resolver를 포함하고VLLM_LORA_RESOLVER_HF_REPO_LIST를 Hugging Face Hub의 저장소 ID 쉼표 구분 목록으로 설정하세요. vLLM이 LoRA 어댑터my/repo/subpath에 대한 요청을 받으면,adapter_config.json을 포함한my/repo의subpath에 어댑터가 존재하면 다운로드하고lora_filesystem_resolver와 유사하게 어댑터용 캐시 디렉터리로 요청을 구성합니다. 참고로 원격 다운로드 활성화는 안전하지 않으며 프로덕션 환경에서 사용하기 위한 것이 아닙니다.
또는 다음 예시 단계에 따라 자체 플러그인을 구현할 수도 있어요.
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 리로드 (In-Place LoRA Reloading)
LoRA 어댑터를 동적으로 로드할 때 이름은 유지하면서 기존 어댑터를 업데이트된 가중치로 교체해야 할 수 있어요. load_inplace 파라미터가 이 기능을 활성화합니다. 이는 어댑터가 지속적으로 업데이트되고 진행 중인 추론을 방해하지 않고 교체되는 비동기 강화 학습 설정에서 흔히 발생합니다.
load_inplace=True 일 때 vLLM은 기존 어댑터를 새 어댑터로 교체합니다.
같은 이름의 LoRA 어댑터를 로드하거나 교체하는 요청 예시:
curl -X POST http://localhost:8000/v1/load_lora_adapter \
-H "Content-Type: application/json" \
-d '{
"lora_name": "my-adapter",
"lora_path": "/path/to/adapter/v2",
"load_inplace": true
}'
--lora-modules 의 새 형식
이전 버전에서는 사용자가 키-값 쌍 또는 JSON 형식으로 LoRA 모듈을 제공했습니다. 예를 들어:
--lora-modules sql-lora=jeeejeee/llama32-3b-text2sql-spider
이 방식은 각 LoRA 모듈의 name 과 path 만 포함하며 base_model_name 을 지정할 방법이 없었어요. 이제 JSON 형식으로 이름과 경로와 함께 base_model_name 을 지정할 수 있습니다.
--lora-modules '{"name": "sql-lora", "path": "jeeejeee/llama32-3b-text2sql-spider", "base_model_name": "meta-llama/Llama-3.2-3B-Instruct"}'
하위 호환성을 위해 이전 키-값 형식(name=path)도 여전히 사용할 수 있지만, 그 경우 base_model_name 은 미지정으로 남습니다.
2D 및 3D MoE LoRA 어댑터 혼합 (Mixing 2D and 3D MoE LoRA Adapters)
같은 엔진 인스턴스에서 2D 형식(megatron 기반)과 3D 형식(peft 기반) 어댑터를 서빙하려면 --enable-mixed-moe-lora-format 으로 서버를 시작하고 is_3d_lora_weight 필드로 각 어댑터의 레이아웃을 명시적으로 선언하세요.
서버 시작(정적 모듈):
vllm serve Qwen/Qwen3.6-35B-A3B \
--enable-lora \
--enable-mixed-moe-lora-format \
--tensor-parallel-size 4 \
--enable-expert-parallel \
--lora-modules \
'{"name": "lora-2d", "path": "jeeejeee/qwen36-35ba3b-2d-weights-poken-lora", "is_3d_lora_weight": false}' \
'{"name": "lora-3d", "path": "jeeejeee/qwen36-35ba3b-moe-all-linear-poken-lora", "is_3d_lora_weight": true}'
/v1/load_lora_adapter 를 통한 동적 로드:
curl -X POST http://localhost:8000/v1/load_lora_adapter \
-H "Content-Type: application/json" \
-d '{
"lora_name": "lora-3d",
"lora_path": "/path/to/3d-format-lora",
"is_3d_lora_weight": true
}'
어댑터의 레이아웃을 반드시 알아야 합니다.
--enable-mixed-moe-lora-format아래에서 vLLM은 호출자가 선언한is_3d_lora_weight를 그대로 신뢰합니다. 체크포인트를 검사해 검증하지 않아요. 잘못된 선언은 가중치를 잘못된 스택 버퍼에 로드해 로드 시 오류 없이 조용히 쓰레기 출력을 만들어냅니다. 서빙 전에 레이아웃을 확인하세요.
- 2D (per-expert, megatron 스타일) →
is_3d_lora_weight: false로 설정. 어댑터 키는...experts.{idx}.gate_proj.lora_A.weight,...experts.{idx}.up_proj.lora_A.weight,...experts.{idx}.down_proj.lora_A.weight처럼 보이며, expert마다 한 세트입니다.- 3D (fused, peft 스타일) →
is_3d_lora_weight: true로 설정. 어댑터 키는...experts.gate_up_proj.lora_A.weight,...experts.down_proj.lora_A.weight처럼 보이며, 모든 expert를 선행 차원에 스택한 단일 텐서입니다.
--enable-mixed-moe-lora-format 이 설정되지 않으면 is_3d_lora_weight 는 무시됩니다. vLLM은 베이스 모델의 is_3d_moe_weight 에서 래퍼를 선택하고 어댑터가 그와 일치해야 합니다. 이 필드는 비-MoE 모델에서도 무시됩니다.
모델 카드의 LoRA 모델 혈통 (LoRA model lineage in model card)
--lora-modules 의 새 형식은 주로 모델 카드에 부모 모델 정보를 표시하기 위한 것입니다. 현재 응답이 이를 어떻게 지원하는지 설명하면:
- LoRA 모델
sql-lora의parent필드는 이제 베이스 모델meta-llama/Llama-3.2-3B-Instruct를 가리킵니다. 이는 베이스 모델과 LoRA 어댑터 사이의 계층적 관계를 정확히 반영합니다. root필드는 LoRA 어댑터의 아티팩트 위치를 가리킵니다.
명령 출력:
$ curl http://localhost:8000/v1/models
{
"object": "list",
"data": [
{
"id": "meta-llama/Llama-3.2-3B-Instruct",
"object": "model",
"created": 1715644056,
"owned_by": "vllm",
"root": "meta-llama/Llama-3.2-3B-Instruct",
"parent": null,
"permission": [
{
.....
}
]
},
{
"id": "sql-lora",
"object": "model",
"created": 1715644056,
"owned_by": "vllm",
"root": "jeeejeee/llama32-3b-text2sql-spider",
"parent": "meta-llama/Llama-3.2-3B-Instruct",
"permission": [
{
....
}
]
}
]
}
멀티모달 모델의 Tower와 Connector에 대한 LoRA 지원
현재 vLLM은 멀티모달 모델의 Tower와 Connector 구성 요소에 대한 LoRA를 실험적으로 지원합니다. 이 기능을 활성화하려면 tower와 connector에 해당하는 토큰 헬퍼 함수를 구현해야 합니다. 이 접근 방식의 근거에 대한 자세한 내용은 PR 26674 를 참고하세요. 추가 모델의 tower와 connector에 LoRA 지원을 확장하는 기여를 환영합니다. 현재 모델 지원 상태는 Issue 31479 를 참고하세요.
멀티모달 모델용 기본 LoRA 모델 (Default LoRA Models For Multimodal Models)
Granite Speech 와 Phi-4-multimodal-instruct 같은 일부 멀티모달 모델은 특정 양식(modality)이 있을 때 항상 적용되도록 기대되는 LoRA 어댑터를 포함합니다. 이는 요청의 멀티모달 데이터 내용에 따라 사용자가 LoRARequest 를 보내거나(오프라인) 베이스 모델과 LoRA 모델 사이에서 요청을 필터링해야(서버) 하므로 위 방식으로 관리하기 다소 번거로울 수 있어요.
이를 위해 vLLM은 기본 멀티모달 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`][vllm.lora.request.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,
),
)
양식을 LoRA 모델 ID에 매핑하는 --default-mm-loras JSON 딕셔너리를 전달할 수도 있어요. 예를 들어 서버를 시작할 때:
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 와 채팅 완료(chat completions)에서만 사용할 수 있습니다.
시퀀스 분류 LoRA 어댑터 (Sequence-Classification LoRA Adapters)
vLLM은 modules_to_save 를 통해 완전한 단일 레이어 선형 분류 헤드를 저장하는 PEFT 시퀀스 분류 어댑터를 지원합니다. 저장된 모듈 이름은 score 또는 classifier 여야 합니다.
LoRA 어댑터를 사용하는 오프라인 분류 예시는 classification_with_lora_offline.py 를 참고하세요.
이 지원에는 다음 제한사항이 있습니다:
- 한 엔진의 모든 어댑터는 베이스 분류 헤드와 동일한
num_labels와 hidden size를 가져야 합니다. 호환되지 않는 어댑터는 로드 시 거부됩니다. - float32로 저장된 분류 헤드는 로드될 때 런타임 헤드 dtype으로 변환됩니다.
- 토큰 분류(Token-classification) 어댑터는 이 기능에서 지원되지 않습니다.
사용 팁 (Using Tips)
max_lora_rank 구성
--max-lora-rank 파라미터는 LoRA 어댑터에 허용되는 최대 랭크를 제어합니다. 이 설정은 메모리 할당과 성능에 영향을 줍니다.
- 사용할 모든 LoRA 어댑터 중 최대 랭크로 설정하세요.
- 너무 높게 설정하지 마세요. 필요보다 훨씬 큰 값을 사용하면 메모리를 낭비하고 성능 문제를 일으킬 수 있습니다.
예를 들어 LoRA 어댑터의 랭크가 [16, 32, 64]라면 256이 아니라 --max-lora-rank 64 를 사용하세요.
# 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
LoRA를 특정 모듈로 제한 (Restricting LoRA to Specific Modules)
--lora-target-modules 파라미터를 사용하면 배포 시점에 LoRA를 적용할 모델 모듈을 제한할 수 있어요. 특정 레이어에만 LoRA가 필요할 때 성능 튜닝에 유용합니다.
# Apply LoRA only to output projection layers
vllm serve model --enable-lora --lora-target-modules o_proj
# Apply LoRA to multiple specific modules
vllm serve model --enable-lora --lora-target-modules o_proj qkv_proj down_proj
--lora-target-modules 를 지정하지 않으면 LoRA는 모델의 모든 지원 모듈에 적용됩니다. 이 파라미터는 o_proj, qkv_proj, gate_proj 같은 모듈 접미사(모듈 이름의 마지막 구성 요소)를 받습니다.