- BrainTools - https://www.braintools.ru -
LoRA — популярный метод дообучения больших моделей на небольших датасетах, однако на этапе инференса низкоранговые адаптеры работают неэффективно, а их объединение с весами требует хранения отдельной полной копии модели для каждого адаптера.
MultiLoRA решает эту проблему, позволяя одновременно выполнять инференс с несколькими адаптерами на основе одной базовой модели.
В статье мы сравним производительность MultiLoRA-инференса в двух популярных фреймворках — vLLM и TensorRT-LLM. Тесты проведём на готовых релизных Docker-образах, оценивая, какой фреймворк эффективнее обрабатывает батчи запросов в сценариях, близких к офлайн и асинхронному инференсу.
LoRA — эффективный способ дообучения базовой модели на небольших наборах данных. Основное преимущество — существенное сокращение числа обучаемых параметров: вместо в каждом слое дообучаются только
, где
— ранг LoRA.
При инференсе с LoRA возможны два подхода:
Запекание адаптеров в веса модели (объединение LoRA с базовыми весами);
Динамическое применение адаптеров — вычисление активаций адаптеров параллельно с основной моделью и их прибавление к выходу базовой модели.
Первый подход исключает дополнительные вычисления и, потому, эффективнее под нагрузкой. Однако в корпоративных применениях часто нет нагрузки на отдельную модель (а высоконагруженных применений – единицы). При этом те же компании куда менее охотно выделяют дорогое железо под инференс, чем под обучение [1].
Во втором варианте адаптеры применяются динамически, без создания отдельных копий модели. Однако здесь возникает другая проблема: на этапе инференса LoRA требует выполнения двух матричных операций с “тонкими” матрицами. Из-за их малого размера GPU загружается неэффективно, что снижает общую производительность.
Кроме того, большинство фреймворков по умолчанию не позволяют батчевать запросы, использующие разные LoRA-адаптеры. Это дополнительно ограничивает эффективность.
Год назад вышла работа [2] по масштабированию инференса с большим числом адаптеров.
Основная идея работы в том, чтобы не объединять веса адаптеров вместе с весами моделей, а группировать их отдельно на исполнении. Для базовой модели будет исполняться обычное матричное умножение, а для адаптеров – Grouped Gemm.
За счет группировки обе операции будут выполняться над большими размерами матриц, что позволяет достигать хорошей утилизации.
Сейчас подобная идея реализована в нескольких фреймворках, включая vLLM и TensorRT-LLM, производительность которых мы и сравним далее.
Мы не будем смотреть на сетевую обвязку, только на подачу батча промптов на генерацию. И так как мы не смотрим на сетевую часть, нет смысла измерять time-to-first token, потому что он зависит от батчинга и эффективности поточной работы движка, что не совсем про тему MultiLoRA и сильно бы раздуло статью.
Мы также буду использовать только python обвязки (без явной работы с c++) из официальных release docker-ов обоих фреймворков. Возможно, поэтому tensorrt-llm окажется плох.
Но именно так большинство людей и будет использовать оба фреймворка для своих задач.
Статья скорее отвечает на вопрос – как будут работать оба фреймворка в MultiLoRA сетапе, если вы просто возьмете готовый docker и запустите как есть для своих задач (примерно так, на самом деле, большинство и сделают).
Получается, что замеры в статье не про ultra-fast-realtime инференс, а скорее про офлайн или ассинхронный его вариант (таких применений тоже много).
Сетап моделей:
Все тесты проводятся в формате bfloat16.
Квантизации за рамками замеров.
Для GPU RTX 4090 используется LLAMA-3.2-3B модель, поскольку 8B-вариант не помещается в 24 ГБ видеопамяти при контексте более 2–3 тыс. токенов.
Для H100 – LLAMA-3.2-8B модель, как жизнеспособный вариант для большинства бизнес приложений.
В тестах на переменный контекст будет сразу подаваться много запросов (20) и время замеряется целиком на весь батч (выше объяснил, почему решено так делать).
Замеры сделаны на сервере с одной GPU с следующими сетапами:
Nvidia 4090 24Gb, AMD EPYC 7402, 64Gb RAM
Nvidia H100 SXM 80Gb, AMD EPYC 9554, 192Gb RAM
Код замеров выложен тут [3].
Можно адаптировать под себя и сделать более точечные замеры или написать в комментарии полезные параметры для ускорения.
Пользователям, привыкшим к использованию интерфейса transformers и обучающим LoRA, может захотеться развернуть инференс самым простым образом, через set_adapter [4].
Peft vs vllm при увеличении контекста (перемалываем 20 запросов):
|
context size |
1000 |
2000 |
4000 |
|---|---|---|---|
|
peft |
420.09 |
418.52 |
461.24 |
|
peft per request |
21.00 |
20.93 |
23.06 |
|
vllm |
5.14 |
5.60 |
6.91 |
Peft vs vllm при увеличении количества запросов при контексте 8000 токенов:
|
num request |
1 |
2 |
4 |
6 |
|---|---|---|---|---|
|
peft |
23.42 |
47.2 |
94.32 |
140.86 |
|
peft per request |
23.42 |
23.6 |
23.58 |
23.47 |
|
vllm |
1.68 |
5.63 |
7.29 |
8.09 |
Однако так делать не надо. По замерам видно, что вне зависимости от небольших изменений размера контекста или числа запросов – общее время исполнения растет линейно, а среднее время выполнения одного запроса – одинаково высокое и никак не амортизируется.
Можно выделить 2 причины такой неэффективности:
Инференс будет всегда с размером батча равным 1.
Между каждыми двумя запросами вызывается set_adapter, состоящий из операций вычитания, умножения и суммы на каждый адаптируемый параметр.
С точки зрения [5] простоты установки и запуска vLLM выигрывает безоговорочно — он устанавливается через pip и работает «из коробки» без дополнительных манипуляций.
С TensorRT-LLM ситуация заметно сложнее:
Актуальные pip-сборки не работают на CUDA ≤ 12.4: при запуске возникает ошибка [6] линковки сервисных функций.
При сборке из исходников требуется CUDA ≥ 12.8, иначе компиляция падает. Причина — в исходном коде много необёрнутых #include-ов, в том числе заголовки под Blackwell и поддержку FP4, которые не компилируются на более старых версиях.
Ручная сборка требуется править пути к .so-библиотекам вручную, так как они жёстко зашиты в файлах конфигурации.
Вывод: использовать TensorRT-LLM имеет смысл только через готовый Docker-образ или если pip wheel нормально установится. Установка вручную — потенциально долгий и болезненный процесс.
vLLM: vllm/vllm-openai:v0.9.1-2025-06-22
TensorRT-LLM: nvcr.io/nvidia/tensorrt-llm/release:0.20.0
Недавно коллеги из крупных ML-команд заметили, что использование LoRA-адаптеров в продакшене влечёт за собой определённые накладные расходы — и если объёмы данных и нагрузка на каждую задачу позволяют, лучше делать полный fine-tuning. Моё изначальное мнение было противоположным: казалось, что накладные расходы LoRA не должны быть большими и в большинстве случаев ими можно пренебречь.
Давайте разберёмся, действительно ли использование LoRA-адаптера заметно влияет на производительность.

|
Context size |
1000 |
2000 |
4000 |
8000 |
16000 |
32000 |
|---|---|---|---|---|---|---|
|
trt_llm_nolora |
5.29 |
5.72 |
6.74 |
8.65 |
12.62 |
49.44 |
|
trt_llm_lora |
6.71 |
7.04 |
7.99 |
10.05 |
14.10 |
53.86 |
|
trt_llm_ratio |
1.27 |
1.23 |
1.19 |
1.16 |
1.12 |
1.09 |
|
Context size |
1000 |
2000 |
4000 |
8000 |
16000 |
32000 |
|---|---|---|---|---|---|---|
|
vllm_nolora |
4.54 |
4.92 |
5.83 |
8.19 |
11.35 |
44.06 |
|
vllm_lora |
5.14 |
5.60 |
6.91 |
8.65 |
12.17 |
50.48 |
|
vllm_ratio |
1.13 |
1.14 |
1.19 |
1.06 |
1.07 |
1.15 |
|
Context size |
1000 |
2000 |
4000 |
8000 |
16000 |
32000 |
|---|---|---|---|---|---|---|
|
vllm_nolora |
6.33 |
7.34 |
9.31 |
27.04 |
46.73 |
115.50 |
|
vllm_lora |
6.72 |
7.69 |
10.87 |
25.21 |
55.62 |
130.82 |
|
vllm_ratio |
1.06 |
1.05 |
1.17 |
0.93 |
1.19 |
1.13 |
|
Context size |
1000 |
2000 |
4000 |
8000 |
16000 |
32000 |
|---|---|---|---|---|---|---|
|
trt_llm_nolora |
9.46 |
19.67 |
33.35 |
58.78 |
122.44 |
250.84 |
|
trt_llm_lora |
10.28 |
20.63 |
35.97 |
63.01 |
135.91 |
265.34 |
|
trt_llm_ratio |
1.09 |
1.05 |
1.08 |
1.07 |
1.11 |
1.06 |
На первый взгляд, накладные расходы кажутся непропорционально большими: адаптируемые веса LoRA занимают всего ~12 млн параметров (12,156,928), что составляет всего 0.4 % от общего числа параметров в модели на 3 миллиарда (3,224,906,752). Казалось бы, настолько малая добавка не должна оказывать значимого влияния.
Но с другой стороны, в некоторых реальных продакшен-условиях, где:
Ограничены общие квоты на число GPU в инференсе,
Нагрузка на каждый адаптер нестабильна или невелика,
Важно гибко переключаться между задачами,
— издержки на LoRA становятся допустимыми, особенно если используется MultiLoRA. Этот подход позволяет батчевать запросы от разных адаптеров на общей базе модели, улучшая утилизацию GPU и компенсируя неэффективность отдельных операций.


|
Context size |
1000 |
2000 |
4000 |
8000 |
16000 |
32000 |
|---|---|---|---|---|---|---|
|
vllm_4090 |
6.72 |
7.69 |
10.87 |
25.21 |
55.62 |
130.82 |
|
trt_llm_4090 |
10.28 |
20.63 |
35.97 |
63.01 |
135.91 |
265.34 |
|
vllm_h100 |
5.14 |
5.60 |
6.91 |
8.65 |
12.17 |
50.48 |
|
trt_llm_h100 |
6.71 |
7.04 |
7.99 |
10.05 |
14.10 |
53.86 |
В текущих тестах vLLM демонстрирует лучшую масштабируемость (не требуя особых настроек). Хотя по опубликованным бенчмаркам в сети TensorRT-LLM должен показывать более высокую производительность. Возможно, дело в неоптимальных настройках по умолчанию, накладных расходах на Python-обвязки или менее гибком runtime для генерации.
Но важно понимать: 95 % пользователей вряд ли будут вручную настраивать low-level параметры или переписывать инференс на C++ (особенно, в офлайн и асинхронном инференсе).
Вот два источника с бенчмарками конца 2024 года, где TensorRT-LLM показывал лучшую производительность:
Squeezebits Blog (октябрь 2024) [7] — общее сравнение производительности vLLM и TensorRT-LLM.
vLLM Performance Update (сентябрь 2024) [8] — данные по оптимизациям в последних релизах.


|
Num requests |
1 |
2 |
4 |
6 |
8 |
12 |
16 |
|---|---|---|---|---|---|---|---|
|
vllm_4090 |
1.68 |
5.63 |
7.29 |
8.09 |
8.45 |
11.38 |
12.66 |
|
trt_llm_4090 |
9.59 |
10.27 |
11.43 |
23.64 |
26.55 |
39.92 |
53.20 |
|
vllm_h100 |
1.24 |
4.80 |
5.25 |
5.69 |
5.68 |
6.50 |
7.43 |
|
trt_llm_h100 |
6.13 |
6.09 |
6.67 |
7.18 |
7.66 |
8.47 |
9.24 |
|
Num requests |
1 |
2 |
4 |
6 |
8 |
12 |
16 |
|---|---|---|---|---|---|---|---|
|
vllm_4090 |
6.09 |
7.24 |
8.62 |
11.15 |
10.19 |
28.23 |
41.75 |
|
trt_llm_4090 |
9.52 |
10.63 |
25.79 |
38.73 |
53.27 |
80.61 |
105.30 |
|
vllm_h100 |
5.01 |
5.09 |
5.77 |
7.40 |
6.76 |
8.66 |
9.72 |
|
trt_llm_h100 |
6.41 |
6.63 |
7.62 |
8.46 |
9.18 |
10.79 |
12.38 |
Та же самая картина. TensorRT-LLM теоретически должен быть быстрее, но в реальности — при использовании стандартных настроек и Python API — vLLM снова уверенно выигрывает.
На практике это означает: без тонкой настройки trt-llm уступает и по скорости, и по удобству.
Замеры при увеличении количества адаптеров (перемалываем 20 запросов), контекст 16000 токенов:
|
Num adapters |
1 |
2 |
4 |
8 |
|---|---|---|---|---|
|
trt_llm_4090 |
143.807280 |
142.092142 |
140.954860 |
oom |
|
vllm_4090 |
48.812364 |
48.834726 |
48.874838 |
48.843075 |
TensorRT-LLM в дефолтной конфигурации упал по OOM при 8 LoRA-адаптерах на 3B модели и 4090 видеокарте. При этом vLLM тот же сценарий выдержал без проблем.
Как и ожидалось, увеличение числа адаптеров не приводит к сильной просадке производительности. Единственное — при большом числе адаптеров каждый из них получает меньший батч, что может немного снижать эффективность матричных операций.
В этих тестах мы не рассматриваем все варианты оффлоадинга и работу с длинными сессиями — это отдельная и объёмная тема. Вместо этого сосредоточимся на более прикладном вопросе: имеет ли смысл включать offloading KV-кеша в типовых оффлайн или асинхронных запросах, когда нет гигантских диалогов, но есть ограниченная память [9] GPU?

|
Context Size |
1000 |
2000 |
4000 |
8000 |
16000 |
32000 |
|---|---|---|---|---|---|---|
|
vllm_4090_no_offload |
6.72 |
7.69 |
10.87 |
25.21 |
55.62 |
130.82 |
|
vllm_4090_offload |
262.24 |
169.41 |
158.88 |
139.15 |
887.97 |
1762.07 |
|
trt_llm_4090_no_offload |
10.28 |
20.63 |
35.97 |
63.01 |
135.91 |
265.34 |
|
trt_llm_4090_offload |
10.29 |
20.45 |
36.47 |
63.96 |
137.71 |
269.48 |
Интуитивно кажется, что при нехватке памяти можно сбросить часть KV-кеша в RAM и продолжить генерацию. Но всё не так просто:
Скорость передачи в CPU-память слишком низкая, поэтому выгоды по времени не получается — ускорения нет;
Оба фреймворка — и vLLM, и TensorRT-LLM — почти не используют CPU-память в стандартных настройках, если речь не про длинные диалоги;
Однако, при включённом оффлоадинге, vLLM показывает заметную деградацию производительности, даже в простом сценарии с одним generate() и без переполнения GPU. При этом htop не показывает большого использования оперативной памяти. Скорее похоже, что отключается какая-то из оптимизаций, типа chunked prefill.
Вывод: в базовых сценариях offloading не даёт выигрыша, а иногда даже вредит.
vLLM оказался быстрее, стабильнее и заметно проще в использовании из коробки. Его установка не требует лишних усилий, а производительность остаётся высокой даже без дополнительной настройки.
TensorRT-LLM, напротив, в текущем виде требует ручной сборки и тонкой настройки — в дефолтной конфигурации он проигрывает по всем ключевым метрикам.
Для офлайн- и асинхронного инференса выбор очевиден — vLLM.
Онлайн-сценарии в этой статье не рассматривались.
Поддержка MultiLoRA есть в обоих фреймворках добавляет ~10-15 % накладных расходов, но существенно улучшает утилизацию GPU в задачах с несколькими адаптерами.
Канал автора: @deploy_ml [10]
Автор: svtDanny
Источник [11]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/16690
URLs in this post:
[1] обучение: http://www.braintools.ru/article/5125
[2] вышла работа: https://arxiv.org/pdf/2311.03285
[3] тут: https://github.com/svtdanny/bench_multilora/
[4] set_adapter: https://huggingface.co/docs/transformers/v4.52.3/en/main_classes/peft#transformers.integrations.PeftAdapterMixin.set_adapter
[5] зрения: http://www.braintools.ru/article/6238
[6] ошибка: http://www.braintools.ru/article/4192
[7] Squeezebits Blog (октябрь 2024): https://blog.squeezebits.com/vllm-vs-tensorrtllm-1-an-overall-evaluation-30703
[8] vLLM Performance Update (сентябрь 2024): https://blog.vllm.ai/2024/09/05/perf-update.html
[9] память: http://www.braintools.ru/article/4140
[10] @deploy_ml: https://t.me/+c4dU_Yd1EKphYmM6
[11] Источник: https://habr.com/ru/articles/922290/?utm_campaign=922290&utm_source=habrahabr&utm_medium=rss
Нажмите здесь для печати.