Технологии

Выгрузка слоёв в оперативную память: сколько можно и когда бросить

Кривая «доля слоёв на процессоре против скорости» нелинейна — но не в ту сторону, в какую обычно думают. Расчёт, процедура подбора -ngl и критерий, когда выгрузка себя не оправдывает.

Модель на гигабайт больше, чем ваша видеокарта. Движок предлагает компромисс: часть слоёв считать на видеокарте, часть — на процессоре. Звучит как разумная сделка, и половина владельцев карт на 8 и 12 гигабайт живёт именно так.

Прежде чем принимать эту сделку, полезно понять её цену. Она выше, чем кажется, и распределена не так, как подсказывает интуиция.

#Короткий ответ

Дороже всего обходятся первые же выгруженные слои, а не последние. Распространённое представление обратное — будто «немножко выгрузить почти бесплатно, а вот половину уже дорого». Арифметика говорит иначе.

Причина: слои считаются последовательно, и время на токен складывается из времени чтения весов с видеокарты и времени чтения весов из оперативной памяти. Оперативная память медленнее видеопамяти в разы. Как только на процессор уезжает хотя бы десятая часть модели, её вклад в общее время уже сопоставим с вкладом всей остальной модели.

Практический вывод: выгрузка — это не плавный регулятор, а обрыв. Если есть способ уместить модель целиком, он почти всегда лучше.

#Как это устроено

Чтобы выдать один токен, движок читает все веса модели. Слои на видеокарте читаются со скоростью видеопамяти, слои в оперативной — со скоростью системной памяти. Время складывается:

время на токен = вес_на_GPU / ПСП_видеопамяти + вес_на_CPU / ПСП_оперативной

Возьмём типичную связку: модель 8B в Q4_K_M весом 4,9 ГБ, видеокарта с пропускной способностью 360 ГБ/с, двухканальная DDR4-3200 с 51 ГБ/с. Разница между ними — семикратная. Считаем теоретические потолки при разной доле слоёв на процессоре:

Слоёв на CPU Время на токен Потолок Потеря от максимума
0% 13,6 мс ~73 ток/с —
10% 21,9 мс ~46 ток/с −37%
20% 30,0 мс ~33 ток/с −55%
30% 38,2 мс ~26 ток/с −64%
50% 54,6 мс ~18 ток/с −75%
75% 75,2 мс ~13 ток/с −82%
100% 96,1 мс ~10 ток/с −86%

Что здесь смотреть: не абсолютные значения (у вас будут свои), а форму кривой. Между 0% и 10% теряется больше трети скорости. Между 50% и 100% — ещё около 44% от уже упавшего значения. Кривая крутая в начале и пологая в конце, а не наоборот.

Это расчёт, а не замер: реальные числа будут ниже за счёт накладных расходов на передачу данных по шине, но пропорции сохраняются. И именно пропорции здесь важны.

#Что из этого следует на практике

Первое правило: сначала попробуйте уместить всё. Порядок действий, прежде чем соглашаться на выгрузку:

  1. Сократить контекст — самый дешёвый по качеству ход.
  2. Включить квантизацию KV-кэша — почти бесплатно по качеству, вдвое дешевле по памяти.
  3. Взять квантизацию весов на ступень жёстче (Q5 → Q4_K_M).
  4. И только потом выгружать слои.

Модель 8B в Q4_K_M, целиком помещающаяся в видеокарту, почти всегда полезнее модели 14B, половина которой считается процессором. Полная лестница решений — в материале о том, что делать, если модель не влезает в память.

Второе правило: если выгружать, то минимально. Каждый оставленный на видеокарте слой ценен, поэтому подбирать нужно максимально возможное значение, а не «примерно половину».

#Процедура подбора числа слоёв

  1. Узнайте, сколько слоёв в модели. Число видно в логе запуска и в метаданных файла GGUF (поле с числом блоков). Для моделей класса 7–8B это обычно 28–32, для 14B — около 48.
  2. Запустите с максимумом: -ngl 99. Если модель загрузилась — задача решена, дальше читать незачем.
  3. При ошибке памяти уменьшайте бинарным поиском. Не по одному слою: начните с половины, затем делите оставшийся интервал. Пять-шесть запусков дают точное значение.
  4. Отступите на 2–3 слоя вниз от найденного максимума. Память под контекст расходуется по мере диалога, и значение, работающее на пустом контексте, упадёт на длинном.
  5. Замерьте на найденном значении и сравните с расчётным потолком из таблицы выше.
  6. Сравните с альтернативой: та же модель с более жёсткой квантизацией, но целиком на видеокарте. Часто второй вариант выигрывает и по скорости, и по удобству.

Флаг называется -ngl в llama.cpp и num_gpu в параметрах Ollama. Разбор остальных настроек — в материале о параметрах Ollama, которые действительно влияют на результат, и в гайде по llama.cpp без магии.

Третье правило: следите за тем, что выгружается автоматически. Инструменты подбирают число слоёв сами, и делают это по свободной памяти на момент запуска. Открытый браузер во время старта модели может стоить вам пяти слоёв на видеокарте.

#Где интуиция подводит

«Немного выгрузить — почти бесплатно». Как раз наоборот. Первые 10% выгруженных слоёв стоят больше трети скорости. Ощущение «бесплатности» возникает у тех, кто сравнивает не с полной загрузкой на видеокарту, а с полным расчётом на процессоре.

«Быстрая оперативная память решит проблему». Улучшит, но не решит. Даже DDR5 в двухканальном режиме отстаёт от видеопамяти дискретной карты в несколько раз. Механику разбирает материал о том, от чего зависит скорость LLM на процессоре.

«Шина PCIe — узкое место при выгрузке». Не она. Веса, лежащие в оперативной памяти, считаются процессором на месте, а не гонятся через шину на видеокарту каждый токен. По шине идёт только промежуточное состояние — небольшой объём. Узкое место — скорость самой оперативной памяти.

«Если модель влезла с частичной выгрузкой, значит всё в порядке». Она влезла на старте. Память под контекст растёт по мере диалога, и настройка, работавшая на первом сообщении, может отказать на двадцатом. Как считать расход заранее — в материале о том, сколько нужно VRAM для локальной LLM.

«Выгружать надо ровно столько, чтобы влезло». Плюс запас. Значение, найденное впритык, оставляет вас без резерва под рост контекста и под любую программу, которая займёт видеопамять.

#Частые вопросы

#Какое значение -ngl поставить?

Максимальное, при котором модель загружается, минус два-три слоя запаса. Значение 99 означает «все» и является правильной первой попыткой. Универсального числа нет: оно зависит от модели, контекста и того, что ещё занимает видеокарту.

#Сколько слоёв можно выгрузить без заметной потери?

Заметной будет уже выгрузка десятой части. Если формулировать порог по ощущениям: пока скорость остаётся выше 10–15 токенов в секунду, работать комфортно. Считайте по формуле, а не по доле слоёв.

#Почему модель загрузилась, но работает как на процессоре?

Скорее всего, на видеокарту не ушёл ни один слой: бэкенд не подхватился, и произошёл молчаливый откат. Проверяется командой ollama ps или строкой о числе выгруженных слоёв в логе llama.cpp.

#Лучше выгрузить слои или взять модель поменьше?

В большинстве случаев — взять модель поменьше или квантизацию пожёстче. Модель класса 8B целиком на видеокарте обычно полезнее модели 14B, наполовину считающейся процессором: она быстрее и не заставляет ждать.

#Влияет ли объём оперативной памяти на скорость выгруженной части?

Влияет её скорость, а не объём. Объём определяет, поместятся ли выгруженные слои вообще; скорость — с какой быстротой они читаются. Одна планка вместо двух означает вдвое меньшую пропускную способность и вдвое худший результат.

#Можно ли выгрузить KV-кэш отдельно от весов?

В некоторых движках есть такая возможность, но выигрыш от неё обычно отрицательный: кэш читается на каждом токене, и его вынос в медленную память бьёт по скорости сильнее, чем вынос части слоёв. Экономить память на кэше правильнее его квантизацией.

Хотите ИИ, который помнит вас?

Persona держит контекст между чатами и работает на вашем железе — если развернуть её у себя и подключить локальную модель.

Создать аккаунт