Синдром «память захвачена и не отпускает»
Вы запустили Qwen3.6-32B-Instruct на связке из RTX 3090 через eGPU. Модель работает, токены летят, вы довольны. Закрываете процесс — а занятые 20 ГБ VRAM так и висят в nvidia-smi. Никакие kill -9, сброс драйвера, даже перезагрузка X-сервера не помогают. Знакомо?
Проблема не мифическая. Она возникает из-за того, как llama.cpp (и многие другие инференс-движки) по умолчанию работают с памятью. Но хорошая новость: есть два флага — --no-mmap и --mlock — которые превращают «залипшую» VRAM обратно в чистый лист. Разберёмся, как они работают и почему их стоит указывать почти всегда.
Короткий ликбез: что такое mmap и почему он ворует память
По умолчанию llama.cpp использует memory-mapped files (mmap) для загрузки весов модели. Это означает, что файл модели (GGUF, например) не читается целиком в память, а отображается в адресное пространство процесса. Операционная система сама решает, какие страницы подгружать в RAM, а какие оставить на диске. Удобно? Безусловно. Но есть подвох.
Когда вы работаете с GPU, llama.cpp копирует нужные слои из этого mmap-региона в VRAM. Но само отображение остаётся в виртуальной памяти процесса, а его часть — в физической RAM (page cache). После завершения процесса Linux не гарантирует немедленную очистку page cache. Более того, если вы используете CUDA Unified Memory или просто неаккуратно работаете с cudaFree, память GPU может висеть до полного выгрузки драйвера. В итоге — «утечка» без реальной утечки, просто мусор, который никто не подмёл.
Факт: в старых версиях драйвера NVIDIA (до R535) cudaFree мог не возвращать память пулу, если использовался mmap. Флаг --no-mmap заставляет llama.cpp читать модель напрямую через read() в выделенный буфер, что даёт полный контроль над освобождением.
--no-mmap: когда «no» значит «свобода»
Этот флаг отключает mmap для весов модели. Теперь они загружаются обычным чтением в кучу процесса. После завершения инференса вся память, включая страницы, скопированные в VRAM, освобождается сразу же — потому что нет никаких внешних отображений, которые бы держали страницы.
1 Как проверить, что проблема именно в mmap
Запустите llama.cpp без флагов, потом убейте процесс. Посмотрите nvidia-smi и free -h. Если VRAM продолжают показывать занятость, а в RAM висит несколько гигабайт page cache — вы нашли виновника.
2 Применяем --no-mmap
./llama-cli \
-m Qwen3.6-32B-Instruct-Q4_K_M.gguf \
-ngl 30 \
--no-mmap
После завершения этой команды вся занятая VRAM должна освободиться мгновенно. Если нет — возможно, у вас другая проблема (например, утечка в драйвере, исправленная в версии 550.120 или новее).
Важно: --no-mmap увеличивает время загрузки модели (чтение с диска вместо ленивой подгрузки через страницы) и потребление RAM — модель полностью лежит в памяти процесса. Если RAM мало, а модель большая — может не хватить. В таких случаях --no-mmap не панацея, лучше использовать обычный mmap и чистить page cache вручную (echo 3 > /proc/sys/vm/drop_caches).
--mlock: якорь для RAM, чтобы GPU не ждал
Флаг --mlock (ёmlock) делает вызов mlockall() — закрепляет все страницы памяти процесса в RAM, запрещая их вытеснение в swap. Зачем это для GPU? Если ваша операционная система начнёт свопировать страницы, содержащие критичные для GPU данные (например, метаданные CUDA или часть модели в Unified Memory), инференс может «зависать» на десятки миллисекунд. С --mlock такого не случится.
Но есть нюанс. --mlock имеет смысл только вместе с --no-mmap (или если вы используете Unified Memory). Если у вас включён mmap, mlock может зафиксировать в RAM весь файл модели — а это десятки гигабайт. На сервере с 64 ГБ RAM и моделью 48 ГБ это может привести к OOM-убийству. А если модель лежит на медленном диске, mlock всё равно не ускорит — вы зафиксируете страницы, но они могут быть не загружены.
3 Правильная комбинация для eGPU и карт с 8-12 ГБ
./llama-cli \
-m Qwen3.6-8B-Q4_K_M.gguf \
-ngl 33 \
--no-mmap \
--mlock \
--tensor-split 8,8,8,9 # если у вас несколько GPU
На практике на eGPU RTX 3090 (24 ГБ VRAM) с подключением через Thunderbolt 4 комбинация --no-mmap --mlock стабилизирует задержки: пропадают микрофризы, которые возникали, когда система решала «немного посвопировать» кэши.
Кстати, о eGPU: если вы используете внешнюю карту, у вас и так узкое горлышко PCIe. Каждый лишний обмен с памятью через шину — потеря производительности. Поэтому --no-mmap и --mlock для eGPU — почти обязательный набор. Без них вы рискуете получить ситуацию, описанную в статье «Аргументы llama.cpp: от слепого копирования команд к осознанной настройке под любое железо» — когда копируют команду из интернета, не понимая, что делает каждый флаг.
Измеряем эффект
Чтобы убедиться, что флаги работают, замерьте потребление VRAM до и после завершения процесса. Самый простой способ — использовать nvidia-smi --query-gpu=memory.used --format=csv -l 1 в одном терминале и запустить инференс в другом.
| Сценарий | VRAM занято после завершения | Время загрузки модели |
|---|---|---|
| По умолчанию (mmap) | 18–22 ГБ (висят до очистки page cache) | ~2 с (ленивая подгрузка) |
| --no-mmap | 0–1 ГБ (освобождается сразу) | ~15 с (полное чтение) |
| --no-mmap --mlock | 0–1 ГБ | ~16 с (с mlocket-ом чуть дольше) |
Как видите, плата за освобождение VRAM — время загрузки. Если вы запускаете одну модель на весь день и не перезагружаете процесс, mmap удобнее. Но для «поигрался и закрыл» или для использования с переключением между разными моделями — --no-mmap обязателен.
Ловушки и грабли
Ловушка №1: противоречие с --n-gpu-layers
Если вы используете --no-mmap, но не указываете -ngl (оставляете все слои на CPU), смысл теряется — VRAM не будет занята вообще. Но и утечка не возникнет. Главное — что при --no-mmap слои, отправляемые на GPU, копируются из буфера, который уже целиком в памяти. Это безопасно.
Ловушка №2: --mlock и swap
Если у вас модель занимает 48 ГБ, а RAM — 32 ГБ, --mlock просто не сработает (mlockall вернёт ошибку). Процесс может упасть или работать нестабильно. Всегда проверяйте, что память не переполнена.
Ловушка №3: двойное mmap для нескольких процессов
Если вы запускаете несколько экземпляров llama.cpp, map-области разделяются между ними (если модель одна). Но при --no-mmap каждый процесс загрузит свою копию модели в RAM — расход памяти вырастет кратно. Для серверов с одним инстансом это не проблема.
Подробнее о других методах экономии VRAM читайте в статье «Как сэкономить VRAM в llama.cpp: отключаем pipeline parallelism». А если вас беспокоят не только утечки, но и общая производительность на картах с 8 ГБ — взгляните на «3060 Ti против llama.cpp: как выжать 20+ токенов в секунду».
Неочевидный бонус: безопасность памяти
Используя --no-mmap, вы убираете потенциальную поверхность атаки для Rowhammer-подобных уязвимостей на GPU. Хотя современные карты имеют защиту, уязвимости в драйверах или в самой CUDA всё ещё возможны. Отключение mmap снижает риск, так как данные модели не отображаются в режиме read-write в общем адресном пространстве. Подробнее об этом — в статье «Безопасность GPU для локальных LLM: угроза Rowhammer через CUDA ядра».
Когда НЕ стоит использовать --no-mmap
- У вас очень мало RAM (меньше, чем размер модели + 20%).
- Вы запускаете модель на длительное время (сервер) и не планируете его перезапускать.
- У вас медленный HDD, и каждая секунда загрузки критична.
- Вы используете swap на SSD: тогда mmap может быть даже быстрее, чем считывание с диска.
В таких случаях лучше оставить mmap, а для освобождения VRAM после использования вызывать cudaDeviceReset() вручную или использовать скрипт, который в фоне чистит page cache. Да, это костыль, но рабочий. Альтернатива — флаг --mlock без --no-mmap — не спасёт от утечки VRAM, но защитит от свопинга.
Подведём черту: ваш чек-лист
--no-mmap (если RAM позволяет) и --mlock (если хотите избежать микрофризов). Загрузка модели подождёт 15–20 секунд, а вот освобождённая память и плавная работа — бесценны.И не забывайте проверять, не страдает ли ваш сервер от фрагментации кучи glibc — «Ваш LLM сервер жрет память как не в себя? Виновник — фрагментация кучи glibc» — может оказаться, что дело не в mmap, а в аллокаторе.
На сегодняшний день (17 июня 2026) актуальная версия llama.cpp — это коммит xyz (но лучше всегда билдить свежий master). Флаги --no-mmap и --mlock стабильны уже пару лет, их поддерживают все бэкенды: CUDA, ROCm, SYCL, Vulkan, Metal. Никаких сюрпризов.