Иногда баги прячутся так глубоко, что находишь их только после того, как перестаёшь верить собственным инструментам. Команда Mistral AI опубликовала разбор одной такой истории – про утечку памяти в vLLM, которую долго не могли поймать именно из-за того, что доверились стандартным способам отладки.
Что такое vLLM и почему утечка памяти – это проблема 🔧
vLLM – это популярный фреймворк для запуска больших языковых моделей. Он умеет эффективно работать с GPU-памятью, распределяя её между запросами. Если коротко, он нужен, чтобы модели работали быстро и не съедали всю память сразу.
Но в какой-то момент инженеры Mistral заметили: при длительной работе память начинает утекать. Не сразу, не очевидно – но стабильно. Система постепенно съедала всё больше ресурсов, хотя логически не должна была.
Классическая утечка памяти: что-то выделяется, но не освобождается. Вопрос только – что именно?
Первая версия: может, дело в Python?
Логичное предположение: проблема где-то в коде Python. Python не управляет памятью напрямую – за него это делает сборщик мусора. Но иногда объекты зависают в памяти из-за циклических ссылок или неправильного управления жизненным циклом.
Команда начала с профилировщиков памяти для Python. Смотрели, какие объекты растут, отслеживали ссылки, пытались понять, где утечка. Но ничего подозрительного не нашли. Все объекты вроде как корректно удалялись, сборщик мусора работал, а память всё равно росла.
Значит, дело не в уровне Python. Или не только в нём.
Вторая версия: может, GPU-память?
vLLM активно использует GPU. Может, утечка там? Проверили выделение памяти на видеокарте – всё в порядке. Память на GPU освобождалась корректно, никаких зависших тензоров.
Но проблема оставалась. Память системы (RAM) продолжала расти. Значит, дело в обычной оперативной памяти, а не в GPU.
Третья версия: нативный код и кучи
В vLLM есть нативные расширения на C++. И вот тут уже можно было предположить, что утечка где-то там – на уровне функций malloc/free, в ручном управлении памятью.
Команда взялась за инструменты вроде Valgrind и AddressSanitizer. Это стандартные отладчики для поиска утечек в C/C++. Они умеют отслеживать, что выделяется и что не освобождается.
И вот тут началось самое интересное: инструменты говорили, что утечек нет. Всё вроде как корректно освобождается. Но память реально росла – это было видно в системных мониторах.
Проще говоря, код делал всё правильно, но память всё равно не возвращалась системе.
В чём подвох: как работают аллокаторы памяти
Тут нужно немного контекста. Когда программа на C++ запрашивает память через malloc, она не напрямую берёт её у операционной системы. Между программой и ОС стоит аллокатор – библиотека, которая управляет кучей (heap).
Аллокатор выделяет большие блоки памяти у системы, а потом раздаёт их программе маленькими кусочками по запросу. Когда программа освобождает память через free, аллокатор не всегда сразу возвращает её системе. Он может оставить блок у себя «на всякий случай», чтобы потом быстрее выдать его снова.
Это нормальное поведение. Но если паттерн выделения памяти неравномерный (например, сначала много маленьких объектов, потом большие, потом снова маленькие), аллокатор может фрагментировать кучу. В результате память формально свободна, но не возвращается системе, потому что она «застряла» между занятыми блоками.
И вот именно это происходило в vLLM.
Как нашли корень проблемы
Команда подключила более продвинутые инструменты для анализа кучи. Вместо того чтобы искать утечки (их не было), они начали смотреть, как распределяется память внутри кучи.
Оказалось, что vLLM активно выделяет и освобождает блоки разного размера – это связано с динамической природой обработки запросов. Модель обрабатывает токены пачками, размер которых постоянно меняется. Из-за этого куча фрагментировалась, и память не возвращалась системе, даже когда объекты удалялись.
Технически утечки не было. Но с точки зрения системы память продолжала расти – потому что аллокатор её не отдавал.
Что с этим сделали
Решение оказалось в переходе на другой аллокатор – jemalloc. Это альтернативная библиотека управления памятью, которая лучше справляется с фрагментацией и агрессивнее возвращает память системе.
После замены аллокатора проблема исчезла. Память перестала расти, хотя код остался прежним.
Почему это важно
Эта история показывает, что иногда проблема не в коде, а в инфраструктуре, которая остаётся невидимой. Стандартные инструменты отладки говорили «всё хорошо», потому что технически так и было – утечки не было. Но поведение аллокатора создавало видимость утечки.
Это напоминание о том, что между вашим кодом и железом много слоёв, и каждый из них может влиять на результат. Иногда нужно копать глубже, чем кажется на первый взгляд.
Для тех, кто работает с высоконагруженными системами или запускает модели в продакшене, это полезный кейс. Если видите рост памяти, но профилировщики ничего не находят – возможно, дело в аллокаторе. И стоит попробовать альтернативы вроде jemalloc или tcmalloc.
Mistral AI выложили подробный разбор в своём блоге – там больше технических деталей, если интересно покопаться глубже. Но главная мысль простая: кучи могут обманывать. Иногда память свободна, но система об этом не знает.