Как деплоить несколько LoRA адаптеров без перезагрузки базовой модели (Punica, S-LoRA)?
Краткий тезис
Традиционный деплой LoRA-адаптеров требует перезагрузки базовой модели при каждой смене адаптера, что неприемлемо для мультитенантных сценариев. Решения S-LoRA и Punica позволяют одновременно обслуживать множество адаптеров в рамках одного батча без перезагрузки весов, а начиная с 2024 года поддержка множественных LoRA встроена в vLLM. Ключевые механизмы — динамическое переключение адаптеров на уровне батча и специализированные CUDA-ядра для эффективного применения LoRA-матриц.
-----|----------------------|--------------------------|--------| | Наивный | Полная перезагрузка | Нет | O(N) на адаптер | | S-LoRA | Динамическое переключение в батче | Да | O(1) на адаптер + кэш | | Punica | CUDA-ядра для параллельного LoRA | Да | O(1) на адаптер | | vLLM + LoRA | Встроенный менеджер адаптеров | Да | O(1) на адаптер |
2. S-LoRA: batch inference с разными адаптерами
S-LoRA (Serving Multiple LoRA Adapters) — система, предложенная в 2023 году (Chen et al.), которая решает проблему мультиадаптерного обслуживания.
2.1. Архитектура
- Unified Paging — единое адресное пространство для ключей/значений (KV-cache) всех адаптеров. Позволяет эффективно управлять памятью при переключении.
- Dynamic LoRA Scheduling — на этапе формирования батча система группирует запросы по идентификатору адаптера, но не ждёт, пока наберётся полный батч для одного адаптера. Вместо этого она использует блочное переключение: каждый блок батча может содержать запросы от разных адаптеров.
- S-LoRA Kernel — оптимизированное CUDA-ядро, которое применяет LoRA-матрицы к скрытым состояниям с учётом того, что разные запросы в батче могут иметь разные адаптеры. Ядро загружает веса адаптера из кэша L2/L1 по мере необходимости.
2.2. Преимущества
- Почти нулевые накладные расходы на переключение (менее 2% от времени инференса).
- Поддержка до тысяч адаптеров одновременно.
- Совместимость с любыми базовыми моделями (LLaMA, GPT и др.).
2.3. Ограничения
- Требует модификации ядра внимания и FFN.
- Не поддерживает динамическое добавление адаптеров без остановки сервера (но может быть расширена).
3. Punica: CUDA kernels для параллельного LoRA
Punica (Punica: Multi-Tenant LoRA Serving) — ещё один подход, фокусирующийся на эффективных CUDA-ядрах для параллельного применения LoRA.
3.1. Ключевая идея
Вместо того чтобы переключать адаптеры на уровне батча, Punica предлагает многопоточное применение LoRA внутри одного ядра. Для каждого запроса в батче ядро загружает соответствующие LoRA-матрицы (A и B) и выполняет x = x + (x @ A) @ B с учётом того, что A и B могут быть разными для разных элементов батча.
3.2. Технические детали
- Segmented Gather/Scatter — веса адаптеров хранятся в непрерывном буфере, а ядро использует индексы для выбора нужных строк/столбцов.
- Fused Kernel — объединение LoRA-преобразования с основным линейным слоем базовой модели, что снижает количество обращений к памяти.
- Поддержка произвольного ранга — ядро работает для любого r (ранга LoRA) без перекомпиляции.
3.3. Сравнение с S-LoRA
| Характеристика | S-LoRA | Punica |
|---|---|---|
| Управление KV-cache | Unified Paging | Стандартный paged attention |
| Переключение адаптеров | На уровне блоков батча | На уровне отдельных запросов |
| Производительность | Выше при большом числе адаптеров (>100) | Выше при малом числе адаптеров (<10) |
| Сложность интеграции | Требует замены всего движка | Может быть встроена в существующие системы |
4. vLLM + LoRA с 2024
Начиная с версии 0.4.0 (2024), vLLM получил встроенную поддержку множественных LoRA-адаптеров, объединив идеи S-LoRA и Punica.
4.1. Как это работает
- LoRA Manager — компонент, который загружает все адаптеры в память при старте сервера.
- Dynamic LoRA Selection — каждый запрос может содержать поле
lora_nameилиlora_id. vLLM автоматически подставляет нужные веса при обработке батча. - Paged LoRA Weights — веса адаптеров хранятся в страничной памяти, что позволяет обслуживать сотни адаптеров без дублирования.
- Поддержка S-LoRA Kernel — vLLM использует оптимизированные ядра для применения LoRA, аналогичные S-LoRA.
4.2. Пример конфигурации
from vllm import LLM, SamplingParams
# Загрузка базовой модели и списка адаптеров
llm = LLM(
model="meta-llama/Llama-2-7b-hf",
enable_lora=True,
max_loras=32, # максимальное количество одновременно загруженных адаптеров
max_lora_rank=64,
lora_extra_vocab_size=0
)
# Инференс с разными адаптерами
prompts = [
{"prompt": "Hello", "lora_name": "adapter1"},
{"prompt": "World", "lora_name": "adapter2"},
]
outputs = llm.generate(prompts, sampling_params=SamplingParams())
4.3. Ограничения
- Требуется vLLM >= 0.4.0.
- Не все модели поддерживают LoRA (трансформеры с attention).
- При очень большом числе адаптеров (>1000) может потребоваться дополнительная настройка памяти.
5. Пет-проект для закрепления
Задача: Развернуть два LoRA-адаптера для одной базовой модели (например, mistralai/Mistral-7B-v0.1) с помощью vLLM и протестировать параллельный инференс.
Инструменты:
- Python 3.10+
- vLLM >= 0.4.0
- Hugging Face Transformers + PEFT (для создания адаптеров)
- Docker (опционально)
Шаги:
- Создать два простых LoRA-адаптера с помощью PEFT (например, на датасетах
imdbиsst2). - Сохранить адаптеры в формате Hugging Face (папки
adapter1,adapter2). - Запустить vLLM сервер с параметрами
--enable-lora --max-loras 2. - Отправить два запроса с разными
lora_nameчерез OpenAI-совместимый API. - Измерить время ответа и убедиться, что оба запроса обработаны в одном батче (проверить логи).
Ожидаемый результат:
- Сервер обрабатывает запросы одновременно, без перезагрузки модели.
- Время ответа для двух запросов примерно равно времени одного (с учётом накладных расходов на LoRA).
- Можно добавить третий адаптер и убедиться, что система масштабируется.
Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 952 | Основы LoRA: низкоранговая адаптация |
Навигация
- Предыдущий: 958
- Следующий: 960
- Индекс: 00. Индекс разборов