Вышла версия LocalAI 4.8.0, на создание которой ушло двадцать два дня и 386 объединенных пулл-реквестов. В LocalAI появилось четыре новые возможности, а также проделана большая работа по исправлению уже существующих функций.
Полный список изменений доступен в примечаниях. В этой статье рассматриваются части, которые влияют на вашу повседневную работу, с указанием номеров пулл-реквестов, чтобы вы могли ознакомиться с изменениями в коде.
Веб-интерфейс стал в 3,48 раза легче
Откройте пользовательский интерфейс через медленное соединение, и теперь вы будете ждать примерно в три раза меньше. Три отдельные проблемы HTTP были исправлены одновременно в #11056, все замеры проводились на работающем развертывании.
Сервер вообще не отправлял Content-Encoding, независимо от того, что запрашивал клиент. Теперь по умолчанию используется gzip-промежуточное ПО, которое можно изменить с помощью параметров --disable-http-compression и --http-compression-min-length (по умолчанию 1024). Потоковые ответы явно пропускаются, поскольку буферизация SSE-потока за gzip-писателем нарушает инкрементальную передачу данных и выглядит для клиента как зависший запрос. Пути completion, realtime, speech, transcription, agent-job и log-tail находятся в этом списке исключений, как и уже сжатые форматы, которые gzip делал лишь незначительно больше.
Vite добавляет контентные хеши к именам файлов бандла, поэтому URL-адрес /assets/ никогда не может изменить содержимое, однако ассеты поставлялись без заголовков Cache-Control, ETag или Last-Modified. Теперь они содержат public, max-age=31536000, immutable, а для index.html явно установлен no-cache, чтобы развертывание всегда подхватывалось.
Третья проблема заключалась в том, что /api/traces возвращал 21 МБ непагинированного блоба, который интерфейс опрашивал каждые пять секунд. Оба эндпоинта трассировки теперь принимают параметры limit (по умолчанию 50, макс. 1000, 0 для всех), offset и full, а также по умолчанию суммируют данные, отбрасывая тела и заголовки, но сохраняя счетчики байтов, чтобы интерфейс мог отображать, что именно было отброшено. Каждая трассировка имеет идентификатор времени жизни процесса, а GET /api/traces/{id} предоставляет полную запись при развертывании строки.
Одна запись в галерее, несколько сборок
Установка модели больше не означает чтение списка квантований и угадывание того, какое из них потянет ваша видеокарта. Запись в галерее теперь может объявлять variants: — список ссылок на другие записи, которые являются альтернативными сборками тех же весов:
Во время установки LocalAI отбрасывает варианты, которые не может запустить данный хост, определяя это по имени бэкенда, а не по аппаратным условиям, которые автору пришлось бы прописывать вручную. Таким образом, MLX исчезает на Linux, а CUDA — на Mac. Затем он отбрасывает те, которые не поместятся, используя VRAM на GPU-хостах и системную RAM с учетом cgroup на CPU-хостах, чтобы контейнер видел свой собственный лимит, а не лимит всей машины. Из оставшихся он выбирает самый большой, исходя из предположения, что больший объем — это лучшая сборка тех же весов. Собственная сборка записи всегда участвует в выборе и никогда не фильтруется, поэтому выбор всегда заканчивается чем-то устанавливаемым.
Размеры берутся из существующего оценщика pkg/vram, который считывает удаленный заголовок GGUF, при необходимости переключается на HTTP HEAD, затем на объявленный размер: и, наконец, на листинг репозитория Hugging Face. Для принятия решения ничего не скачивается, а неудачная попытка проверки никогда не приводит к сбою установки.
Любая поверхность может переопределить выбор: variant в POST /models/apply, local-ai models install --variant, инструмент MCP install_model и кнопка с выпадающим списком в таблице моделей. Явный выбор учитывается, даже если он не подходит (с предупреждением), поскольку это осознанное решение оператора. Старые клиенты читают тот же live gallery/index.yaml, игнорируют непонятный им ключ и устанавливают все точно так же, как раньше.
Один пробел, о котором стоит знать: в распределенном режиме InstallModel выполняет разрешение относительно фронтенда, а не воркера, который будет обслуживать модель, поэтому кластер с маленьким фронтендом и большими воркерами делает выбор консервативно. Пулл-реквесты #10943, #10983, #10992, #11027 и #11139.
Новый движок: vllm.cpp (альфа)
vllm.cpp распространяется под лицензией Apache-2.0 и поддерживается командой LocalAI. Мы хотим, чтобы это был проект, ориентированный на сообщество, а не только на LocalAI, поэтому он живет в собственном репозитории со своей документацией, результатами бенчмарков и трекером задач, и работает без участия LocalAI. Он начинался как порт vLLM на C++20. Здесь он поставляется как бэкенд vllm-cpp (#11100). Он реализует архитектуру V1 от vLLM, включая постраничный KV-кэш, непрерывную пакетную обработку, префиксный кэш, планировщик и сэмплер на переносимой тензорной среде выполнения без Python, PyTorch и ggml при выводе. vLLM остается эталонной реализацией: корректность проверяется путем сравнения вывода с ним, а таблица результатов бенчмарков ведется относительно него.
Он обзавелся функциями, которых нет в vLLM, что является основной причиной существования этого порта. Он загружает как GGUF, так и safetensors, работает на CPU, Apple Metal и Vulkan наряду с CUDA 12 и 13, а также L4T для GB10, и поддерживает спекулятивную декодировку и выгрузку KV. Его страница бенчмарков теперь сравнивает результаты с llama.cpp, MLX-LM и DwarfStar, а также с vLLM, потому что на этом оборудовании именно с этими движками он конкурирует. Ожидается, что проект будет переименован, новое название еще не выбрано; он уходит достаточно далеко, поэтому название vllm.cpp со временем будет вводить в заблуждение.
Вызов инструментов (Tool calling) по своей структуре находится на уровне паритета с llama.cpp, поскольку чат намеренно повторно использует тот же путь автопарсера: полные шаблоны чата minja, tool_choice: auto, сведенный к ограничению декодирования ленивых структурных тегов, 30 диалектов инструментов, 7 парсеров рассуждений и потоковые ChatDelta и ToolCallDelta.
Цифры из собственного scoreboard проекта, где ничьи остаются ничьими, а проигрыши — проигрышами. Значение выше 1.0 означает, что vllm.cpp впереди:
Страница выше по течению осторожна в отношении собственного шума: в сетке 27B разброс между запусками составляет 0,5%, а значения от c2 до c32 попадают в диапазон от 0,7% до 1,7%, поэтому она называет эти пять случаев ничьими, а не победами. Результат для concurrency-1 — это тот, на который она опирается.
Строка DeepSeek-V4-Flash показывает, насколько далеко проект ушел от простого порта vLLM. Он запускает DeepSeek-V4-Flash примерно в 2-битном режиме (IQ2_XXS mixed, около 80 ГБ) на одном DGX Spark, декодируя со скоростью 18,69 ток/с против 16,33 у DwarfStar. При общем количестве параметров 300B+ даже 4-битный чекпоинт занимает 156 ГБ или более, поэтому 2-битный GGUF — это то, что помещается в объединенный пул Spark объемом 119 ГБ, и чтение GGUF — это то, что делает это возможным.
Это число менялось дважды за неделю, и второе изменение произошло благодаря одному рычагу. Плотная проекционная башня Q8_0 считывалась из mmap GGUF через объединенную память, которую GB10 считывает примерно на 20% медленнее на операцию GEMV, чем память устройства. Размещение этой 6 ГБ башни в памяти устройства один раз при загрузке, с теми же байтами и теми же ядрами, увеличило скорость декодирования с 16,23 до 18,69, генерируя те же токены и не используя больше пиковой памяти. То же изменение перевело Laguna-XS-2.1 с 87% от vLLM на 1,03x впереди него.
Спекулятивная декодировка находится в похожем состоянии: MTP на Qwen3.6-27B NVFP4 генерирует те же токены, что и MTP в vLLM, и работает примерно на 4% быстрее при concurrency 1.
Конфигурация — это обычная установка бэкенда:
Рассматривайте это как альфа-сборки для разработки, а не как выпущенный бэкенд. vllm.cpp находится на ранней стадии, и его включение в 4.8 предназначено для того, чтобы показать его людям, которые хотят попробовать, а не для того, чтобы рекомендовать его для важных задач. llama-cpp остается стандартом для реального использования.
Путь процессора проверен от начала и до конца на Qwen3.5-2B-UD-Q8_K_XL.gguf с использованием полного набора тестов Ginkgo, охватывающего блокирующую и потоковую побайтовую идентичность, жадный детерминизм, стоп-слова, генерацию с ограничениями GBNF, параллельные потоки, разделение рассуждений, а также обязательные вызовы инструментов и вызовы с автоопределением. Образы для GPU собираются и поставляются, но их поведение во время выполнения не проходило через эту проверку. Сравнение производительности с исходным vLLM не заявлено. Ожидайте шероховатостей и, пожалуйста, сообщайте о возникающих сбоях.
На Apple Silicon в образ теперь входит провайдер MLX GEMM для vllm.cpp (#11137). Исходный репозиторий оставляет его отключенным по умолчанию, так как он добавляет около 124 МБ, поэтому мы провели измерения перед тем, как его включить. Тест Qwen3-1.7B-bf16 на M4, p=512 g=128, обе ветки переключены в одном бинарном файле, так что разница в сборке не может объяснить разрыв:
Два повторения, разброс между которыми достигает 9,4%, поэтому воспринимайте множители как +/-10%. Время до первого токена примерно уменьшается вдвое по всему диапазону.
LocalAI теперь генерирует 3D-модели
Генерация 3D — это новая модальность, поэтому ее пришлось внедрить во весь стек: RPC-вызов Generate3D в backend.proto, возможность FLAG_3D, чтобы загрузчик знал, какие бэкенды могут ее обслуживать, и POST-запрос /v1/3d/generations.
Первым движком для этого стал trellis2cpp, бэкенд для преобразования изображений в 3D на базе TRELLIS.2. Вы даете ему изображение — на выходе получаете GLB. В веб-интерфейсе для этого есть страница со встроенным просмотрщиком GLB, так что вы можете покрутить результат в браузере, вместо того чтобы скачивать его, чтобы проверить, сработало ли; история сохраняется в IndexedDB, поэтому перезагрузка не приведет к потере ваших генераций, а также доступна предварительная перетриангуляция сетки с возможностью печати для результатов, которые вы действительно планируете отправить на принтер (#10979).
Чат LocalAI перестал быть REPL
Чат LocalAI раньше представлял собой приглашение чата в терминале. Теперь это агент, и он представляет собой обертку nib, скомпилированную прямо в бинарный файл: использование инструментов за шлюзом подтверждения, субагенты, MCP-серверы, плагины и навыки, автоматически настраиваемые под ваш собственный экземпляр. Ничего лишнего устанавливать не нужно.
Последняя функция выводит скрипт интеграции с оболочкой (zsh, bash или fish), так что вы можете вызвать агента откуда угодно, где вы уже находитесь, вместо того чтобы открывать что-то еще.
Теперь он выполняет команды оболочки, поэтому каждый вызов инструмента проходит через запрос на подтверждение, которым вы управляете, а такие операции только для чтения, как ls и cat, выполняются без вопросов. Если у вас выработались привычки к старому REPL, кое-что изменилось: /clear исчез, а ближайшая замена к нему — /compact, /models и /model <name> означают то же, что и всегда, а переключение модели сохраняет беседу, а не начинает ее заново (#11291).
Один бэкенд, шесть аудиоэндопоинтов
Обычный подход для аудио — это один бэкенд на семейство моделей, что означает один процесс на каждую возможность и отдельный файл конфигурации для каждого. audio-cpp обертывает audio.cpp, многосемейственный аудиодвижок ggml. Один процесс бэкенда обслуживает несколько несвязанных семейств через единый словарь во время выполнения и определяет, к какому семейству принадлежит контрольная точка, по собственному ключу метаданных audiocpp.model_spec.family из GGUF. В конфигурации модели не нужно прописывать ничего специфичного для бэкенда.
Вместе с ним поставляется тринадцать элементов галереи — по одному на каждый тип задач, который движок действительно может выполнять. Там, где он не может честно поддержать RPC, он возвращает UNIMPLEMENTED с указанием причины, а не пустой успешный ответ, и неудачная загрузка является ошибкой gRPC, а не success: false, что важно, поскольку в противном случае жадный зонд бэкенда загрузчика выбрал бы его для модели, которую он обслуживать не может.
Два изменения выходят за рамки самого бэкенда. AudioTransformResult получил поле stems, поэтому разделение источников может возвращать весь набор стемов вместо одного сведенного микса. А /audio/transform больше не сводит каждую загрузку к 16 кГц моно: это сведение делало разделение на 4 стема невозможным по определению, поэтому оно стало возможностью, настраиваемой для каждого бэкенда: существующие бэкенды сохранили ее явно, а поведением по умолчанию для незарегистрированного бэкенда является оставление загрузки без изменений (#11141).
Три новых бэкенда для речи и небольших квантований
magpie-tts-cpp обертывает magpie-tts.cpp, порт C++17 и ggml для NVIDIA Magpie TTS Multilingual 357M со встроенным вокодером NanoCodec. Пять голосов, девять или более языков, 22,05 кГц моно, в одном автономном файле GGUF. Исходный движок проверен на идентичность с компонентами NeMo, с максимальной абсолютной разницей при принудительном обучении учителем (teacher-forced replay), равной 3.6e-5.
moss-tts-cpp обертывает moss-tts.cpp и обслуживает MOSS-TTS-Local v1.5 с частотой 48 кГц в стереорежиме, с поддержкой клонирования голоса по эталонному аудио. Образы охватывают CPU, CUDA 12 и 13, Intel SYCL, Vulkan, ROCm, L4T и Darwin Metal. Оба вошли в #11115, #10860 и #10877.
Бэкенд bonsai обслуживает 1-битные (Q1_0) и троичные (Q2_0) квантования Bonsai для Qwen3 8B и Qwen3.6-27B размером примерно от 1,15 ГБ. В стандартном llama.cpp нет ядер для этих форматов, поэтому бэкенд собирается на основе форка PrismML через обертку Makefile, которая подменяет только LLAMA_REPO и LLAMA_VERSION, повторно используя тот же grpc-server.cpp без каких-либо патчей расхождения. С ним поставляется восемь элементов галереи. Если Q1_0 и Q2_0 появятся в основной ветке llama.cpp, этот бэкенд уйдет в отставку простым обновлением версии (#10834, #10866).
Панель операций превратилась в страницу
Старая панель операций отображала по одной строке для каждой выполняющейся операции над каждой страницей. Поставьте в очередь четыре установки моделей и один бэкенд — и это занимало большую часть области просмотра на каждом маршруте, пока не завершится последняя. Она выполняла две задачи одновременно. Глобальному сигналу «что-то происходит» нужна всего одна строка, а деталям происходящего необходима отдельная страница.
Теперь полоса представляет собой одну строку, постоянно отображающую сбой в первую очередь, а в остальных случаях — наименее продвинутую из выполняющихся операций со значком «+N more». Ее кнопка ✕ скрывает полосу и больше ничего не отменяет. Это осознанное изменение поведения, о котором стоит знать, прежде чем вы кликнете по ней по привычке: тот же символ раньше отменял загрузку 17 ГБ в одной строке и закрывал сообщение в следующей. Отмена перенесена на новую страницу, под соответствующую кнопку.
Страница «Активность» по адресу /app/activity доступна только администраторам и содержит то, от чего полоса избавилась: фазу, байты, вычисленное оставшееся время и разбивку по узлам для кластерных установок. Она также ведет историю. /api/operations удаляла операцию в момент ее успешного завершения, поэтому, если вы запускали крупную установку и уходили от компьютера, впоследствии не было возможности узнать, завершилась ли она успехом, неудачей или вообще не начиналась. Эта история представляет собой кольцевой буфер ограниченного размера на 50 записей, а закрытие ошибки перемещает ее в историю, а не удаляет.
В результате рефакторинга было устранено несколько давних ошибок пользовательского интерфейса: повторная попытка удаления после сбоя снова скачивала модель, операции в очереди отображались как «Установка» со значком загрузки, длинное сообщение об ошибке сдвигало каждую страницу примерно на 270 пикселей за пределы области просмотра и добавляло всему приложению горизонтальную полосу прокрутки, а расчетное время прибытия (ETA) обнулялось для каждой операции при проверке любой из них (#11163).
Бюджеты VRAM на узел
Теперь вы можете ограничить объем памяти видеокарты, который разрешено использовать LocalAI, в виде процента или абсолютного значения (#10833):
Везде, где LocalAI считывает объем VRAM для принятия решения о выделении ресурсов, теперь используется функция min(detected, budget). Значения в процентах выше 100 отклоняются, а абсолютные значения выше физического объема ограничиваются, поэтому верхний предел может только уменьшать используемый объем VRAM. В автономном режиме это жесткое ограничение на процесс, которое наследуют аппаратные значения по умолчанию, автоподбор контекста, предупреждения GGUF и механизм сторожевого таймера. В распределенном режиме это предел размещения: воркер сообщает исходный объем VRAM и свою строку бюджета, реестр ноды определяет байтовый предел при регистрации и отправке сигнала сердцебиения (heartbeat), а SQL-планировщику не потребовалось менять запросы. Переопределение администратором через PUT- и DELETE-запросы к /api/nodes/:id/vram-budget сохраняется после перезапуска воркеров, а аналогичная возможность доступна в качестве MCP-инструмента set_node_vram_budget. Значение по умолчанию означает всю обнаруженную VRAM, поэтому существующие развертывания не изменятся.
Распределенный режим перестает очищать живые бэкенды
Модель, которая отображалась как загруженная на главной странице, но отсутствовала на всех нодах в кластере, оказалась следствием четырех разных багов, и все они были исправлены в этом цикле.
Механизм очистки удалял строки node_models для бэкендов, которые были живы и работали. Функция probeLoadedModels удаляла строку после единственной неудачной односекундной gRPC-проверки работоспособности, а занятый бэкенд не может ответить на нее вовремя, так как однопоточный бэкенд на Python блокируется на несколько минут внутри запроса. Воркер запускает бэкенд и удерживает дескриптор процесса, поэтому его ответ не блокируется тем, что делает бэкенд. Новый предмет запроса-ответа models.running обращается к воркеру напрямую, а модуль согласования (reconciler) сопоставляет ключи процессов воркера со строками реестра до запуска каких-либо проверок портов. Воркер, который не отвечает, пропускается, а не считается пустым, поэтому сбой NATS не может удалить строки ноды. Там, где проверка портов все же выполняется, она больше не смешивает типы сбоев: DeadlineExceeded означает занятость, Unavailable означает отсутствие, и только второй вариант учитывается для порога, который теперь требует трех последовательных неудач.
Кроме того, каждая маршрутизируемая модель оставляла локальный заглушечный объект (stub) во фронтенд-компоненте ModelLoader, а пути удаления стирали только запись в базе данных, поэтому заглушка переживала реплику, и модель навсегда отображалась как загруженная. Хук удаления реплики был преобразован в список, а новый механизм аннулирования удаляет локальную заглушку, как только в кластере не остается ни одной исправной реплики.
В дополнение к этому счетчики in_flight больше не завышаются и не защищают VRAM реплики от вытеснения, сроки загрузки моделей масштабируются в зависимости от размера контрольной точки и прогресса, а не реального времени (wall-clock), проверка этапа подготовки засчитывается как прогресс, а не как зависание, обнаружение бэкендов перестало скрывать установленные воркером и GPU-специфичные бэкенды за собственной файловой системой контроллера, а планировщик больше не размещает модель на ноде, которая не может ее вместить. Полный список доступен в #11142 и восемнадцати связанных PR.
Исправление безопасности, с которым стоит ознакомиться
POST /api/fine-tuning/jobs принимал reward_functions[].code — встроенное тело на Python, которое выполнялось на основе самописного белого списка встроенных функций. Этот белый список не являлся защитным барьером. Стандартная интроспекция CPython позволяет добраться из него до реального модуля os, что означает произвольное выполнение кода на хосте, причем выполнение происходило во время дымового теста при запуске задания на эндпоинте, который по умолчанию не требует аутентификации.
Встроенный код вознаграждения теперь отклоняется, если оператор не установит LOCALAI_TRL_ALLOW_INLINE_REWARD=true на бэкенде. Встроенные по умолчанию функции вознаграждения работают как прежде и не требуют настройки. Документация больше не описывает белый список как песочницу (#11068).
Еще два исправления для повышения надежности вошли в тот же цикл. Жесткие ссылки tar, выходящие за пределы корневого каталога распаковки, теперь отклоняются: экстрактор архива предварительно сканировал элементы и отклонял символические ссылки, но запись жесткой ссылки tar имеет режим обычного файла и проходила эту проверку, а Header.Linkname никогда не проверялся, поэтому архив мог ссылаться на путь вне пункта назначения (#11266). Кроме того, циклический $ref в грамматике JSON-схемы теперь отклоняется, а не приводит к рекурсии со стековым переполнением (#11041). Этот релиз также включает hono 4.12.25 для устранения уязвимости CVE-2026-54290 (#11023).
Остальное кратко
Артефакты моделей Hugging Face теперь представляют собой управляемый процесс создания снимков (snapshot): неизменяемое разрешение снимков, аутентифицируемые загрузки с реальным отображением прогресса, материализация при установке из галереи и предварительной загрузке, привязка во время выполнения к подготовленным артефактам, а также возобновление прерванной загрузки для каждого файла по отдельности вместо запуска сначала. Бэкенды на Python повторно используют путь загрузки Go вместо того, чтобы скачивать файлы самостоятельно.
Панель трассировки получила сортируемый столбец «Пользователь», а также IP-адрес клиента и user agent в развернутой строке, получаемые из RealIP() пакета echo, благодаря чему учитывается доверенный прокси.
Документация прошла масштабный пересмотр на основе покадрового аудита: одна модель, qwen3-4b, теперь используется на этапах установки, в веб-интерфейсе и при вызове через curl; появилось новое руководство по созданию вашего первого агента; а новый справочник ошибок времени выполнения ориентирован на те самые строки ошибок, которые пользователи видят в реальности.
Valkey Search пополнил список хранилищ векторов в качестве бэкенда valkey-store (#11196). local-ai можно запускать по требованию через активацию сокетов systemd, используя унаследованный слушатель вместо привязки собственного, при этом обычное поведение параметра --address сохраняется неизменным, если слушатель активации отсутствует (#11169). История трассировок теперь переживает перезапуск, сохраняясь в виде ограниченных по размеру записей JSON по пути данных без зависимости от базы данных (#11203). Вы можете редактировать сохраненные сообщения чата на месте, не инициируя новый запрос инференса (#11189). А бэкенд Intel SYCL llama.cpp теперь является самодостаточным, поэтому он работает на хосте без соответствующей среды выполнения oneAPI (#10991).
В этом же рельезе localai.io разделился на две части: сайт проекта на корневом уровне и документация в каталоге /docs/. Все ранее опубликованные URL по-прежнему работают благодаря 214 сгенерированным заглушкам перенаправления, так как у GitHub Pages нет механизмов перезаписи на стороне сервера для реализации этого должным образом (#11243).
В создании этого релиза приняли участие двадцать пять человек, одиннадцать из них — впервые. Количество элементов в галерее увеличилось с 1221 до 1515.
Для обновления выполните команду pull localai/localai:latest или перезапустите скрипт установки. В полном списке изменений содержится все, что не вошло в эту публикацию.







