SSD Nodes Learn Hosting plans →
Руководства Matt ConnorАвтор: Matt Connor · Обновлено 2026-08-26

Как запустить llama.cpp сервер на VPS через systemd

Руководство по сборке llama-server из конкретного тега, настройке OpenAI-совместимого API и запуску процесса через systemd с жесткими лимитами оперативной памяти на сервере.

Что вы создаете

Запуск сервера llama.cpp на VPS подразумевает использование одного бинарного файла, llama-server, который загружает единственный файл модели GGUF и отвечает на HTTP-запросы через API, совместимый с OpenAI. Укажите любой клиент OpenAI на http://127.0.0.1:8080/v1, и он заработает. Установка — это лишь половина дела.

Остальная часть работы — это эксплуатация: фиксация версии, привязка порта к localhost, написание systemd-юнита и определение поведения системы при нехватке оперативной памяти. Именно это рассматривается в данном руководстве. Если вы еще не выбрали между двумя очевидными вариантами, сначала прочитайте сравнение преимуществ и недостатков Ollama и llama.cpp, так как это руководство описывает то, что намеренно опущено в сравнении.

Выберите тег релиза и запишите его

llama.cpp присваивает тег практически каждому слиянию, поэтому теги представляют собой номера сборок. b10488 — новейший на 18 августа 2026 года. В проекте нет долгоживущих стабильных веток, а это значит, что «latest» постоянно меняется, и версия, которую вы протестировали, — единственная, которую вы можете поддерживать. Выберите тег, запишите его и используйте эту же строку при клонировании, в имени бинарного файла и в своих заметках.

Для каждого тега также выпускаются предварительно собранные архивы. Для x86 VPS только с CPU это llama-b10488-bin-ubuntu-x64.tar.gz, а архив для arm64 находится рядом, если вы используете ARM VPS вместо x86.

curl -LO https://github.com/ggml-org/llama.cpp/releases/download/b10488/llama-b10488-bin-ubuntu-x64.tar.gz
tar tf llama-b10488-bin-ubuntu-x64.tar.gz | head

Просмотрите содержимое архива перед распаковкой, чтобы знать, куда будут помещены файлы. Эти бинарные файлы скомпонованы с библиотекой C того образа, в котором они были собраны, поэтому в старых дистрибутивах они не запускаются с ошибкой, указывающей на версию GLIBC_, которая не установлена. Сборка из исходного кода занимает несколько минут на небольшом VPS и устраняет весь этот класс проблем, поэтому ниже описан именно этот путь.

Сборка llama-server из зафиксированного тега

sudo apt update
sudo apt install -y build-essential cmake git libssl-dev
git clone --depth 1 --branch b10488 https://github.com/ggml-org/llama.cpp
cd llama.cpp
cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DLLAMA_BUILD_TESTS=OFF -DLLAMA_BUILD_EXAMPLES=OFF
cmake --build build --config Release -t llama-server -j 2

Команда --branch b10488 в клоне --depth 1 переключает состояние репозитория строго на указанный тег, поэтому конфигурация сборки не изменится в процессе работы.

libssl-dev важен, так как опция LLAMA_OPENSSL включена по умолчанию; именно она позволяет бинарному файлу загружать модели по протоколу HTTPS. Без соответствующих заголовков этап конфигурации завершится ошибкой.

-DBUILD_SHARED_LIBS=OFF позволяет получить один автономный исполняемый файл. Сборка по умолчанию размещает разделяемые библиотеки рядом с исполняемым файлом, поэтому копирование только самого файла в /usr/local/bin приведет к ошибке error while loading shared libraries: libllama.so.

-t llama-server собирает только цель server. Сборка по умолчанию также компилирует другие инструменты и тесты, что на VPS с двумя ядрами занимает лишние несколько минут для файлов, которые вы не будете использовать.

-j 2 — это осознанный выбор. Каждый параллельный процесс компиляции потребляет значительный объем оперативной памяти, поэтому -j $(nproc) на тарифе с малым объемом памяти приводит к c++: fatal error: Killed signal terminated program cc1plus — срабатыванию механизма OOM Killer, который принудительно завершает процесс компилятора. Уменьшите количество параллельных задач или добавьте swap на время сборки.

Один флаг, который может потребоваться изменить: GGML_NATIVE по умолчанию включен, поэтому компилятор оптимизирует код под конкретный процессор, на котором выполняется сборка. Это оптимально, если вы собираете программу на той же машине, где она будет работать. Если вы собираете бинарный файл для переноса на другой хост, добавьте -DGGML_NATIVE=OFF, иначе при попытке использовать инструкции, отсутствующие на целевом процессоре, программа завершится с ошибкой Illegal instruction (core dumped) при первом же запуске инференса.

Установите файл под именем, содержащим версию тега.

./build/bin/llama-server --version
sudo install -m 755 build/bin/llama-server /usr/local/bin/llama-server-b10488
sudo ln -sfn /usr/local/bin/llama-server-b10488 /usr/local/bin/llama-server

--version выводит номер сборки и хеш коммита. Они должны соответствовать тегу, который вы выбрали. Если данные не совпадают, значит, была собрана другая версия. Использование номера версии в имени файла и создание символической ссылки позволяют выполнить обновление одной командой ln -sfn и перезапуском, а откат к предыдущей версии выполняется аналогично с использованием старого имени файла.

Получение модели GGUF и проверка диска

GGUF — это формат единого файла, который загружает llama.cpp. Один файл содержит веса, токенизатор и метаданные, поэтому устанавливать что-либо еще не требуется. Суффикс в имени файла обозначает квантование — точность, с которой хранятся веса: Q4_K_M представляет собой 4-битное смешение, Q8_0 — 8-битное, а f16 — файл половинной точности без квантования.

Создайте сервисную учетную запись и каталог для моделей перед тем, как что-либо загружать.

sudo useradd --system --home /srv/llama --create-home --shell /usr/sbin/nologin llama
sudo install -d -o llama -g llama /srv/models
df -h /srv

Сервер может самостоятельно получить модель с помощью -hf, что является самым быстрым способом проверить работоспособность вашей сборки.

sudo -u llama env LLAMA_CACHE=/srv/models /usr/local/bin/llama-server \
  -hf ggml-org/gemma-3-1b-it-GGUF:Q4_K_M --host 127.0.0.1 --port 8080

LLAMA_CACHE задает каталог для загрузки. Без этого параметра файл попадет в ~/.cache/llama.cpp под учетной записью, которая выполнила команду, что является неподходящим местом для сервиса, чей домашний каталог вы собираетесь сделать недоступным для чтения. После этого выполните ls -lh /srv/models, так как кэшированное имя файла производно от имени репозитория, а не от исходного имени файла.

Для сервиса выполняйте загрузку в выбранный вами путь, чтобы юнит-файл имел стабильную точку доступа.

sudo -u llama curl -L --output-dir /srv/models -O \
  https://huggingface.co/ggml-org/gemma-3-1b-it-GGUF/resolve/main/gemma-3-1b-it-Q4_K_M.gguf

Дисковое пространство — это ограничение, с которым сталкиваются в первую очередь. Ниже приведены опубликованные размеры файлов для двух моделей, проверенные 18 августа 2026 года.

ChartGGUF file size on disk, published figures, 18 August 2026
The data behind this chart
[
  {
    "label": "gemma-3-1b-it Q4_K_M",
    "size_gb": 0.81
  },
  {
    "label": "gemma-3-1b-it Q8_0",
    "size_gb": 1.07
  },
  {
    "label": "gemma-3-1b-it f16",
    "size_gb": 2.01
  },
  {
    "label": "gpt-oss-20b MXFP4",
    "size_gb": 12.11
  }
]

4-битный файл для модели 1B занимает 0.81 ГБ. Та же модель без квантования занимает 2.01 ГБ, таким образом, выбор формата изменяет значение более чем в два раза. Модель 20B в формате MXFP4 занимает 12.11 ГБ, что не помещается на диск многих начальных тарифных планов, к тому же файл еще должен быть считан в память. Если вы присматриваетесь к конкретному семейству, аналогичный расчет размеров для GLM показывает, как быстро топовая модель становится слишком дорогой для VPS, в то время как ее младшая версия вполне подходит.

Проверяйте df -h перед каждой загрузкой. Корневая файловая система, заполненная во время передачи 12 ГБ данных, нарушает работу всех остальных процессов, которым требуется запись, включая журнал.

Запустите его вручную один раз и проверьте

sudo -u llama /usr/local/bin/llama-server \
  --model /srv/models/gemma-3-1b-it-Q4_K_M.gguf \
  --host 127.0.0.1 --port 8080 \
  --ctx-size 4096 --parallel 1 --threads 2 --no-webui

Во втором сеансе запросите у сервера, готов ли он к работе.

curl -s http://127.0.0.1:8080/health

Пока файл загружается, вы получаете ошибку HTTP 503 и следующее тело ответа:

{"error":{"code":503,"message":"Loading model","type":"unavailable_error"}}

Когда сервер готов, тело ответа содержит {"status": "ok" }. После этого отправьте реальный запрос.

curl -s http://127.0.0.1:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"local","messages":[{"role":"user","content":"Say hello in five words."}]}'

JSON-объект с массивом choices означает, что сервер работает корректно. Поле model присутствует, так как клиенты OpenAI всегда его отправляют. На этом сервере загружена только одна модель, поэтому данное значение не используется для выбора.

API, совместимый с OpenAI, и другие функции порта

POST /v1/chat/completions, POST /v1/completions и POST /v1/embeddings — это маршруты, совместимые с OpenAI, а GET /v1/models возвращает информацию о загруженной модели. GET /health — это упомянутая выше проверка готовности, GET /props возвращает текущие настройки сервера, а GET /metrics предоставляет счетчики Prometheus, если вы запускаете сервер с флагом --metrics.

Любой SDK для OpenAI будет работать, если вы укажете базовый URL http://127.0.0.1:8080/v1 и передадите непустую строку в качестве API-ключа. Никакая проверка ключа не выполняется, пока вы сами не настроите --api-key.

Не принимайте заявления о пропускной способности за показатели для вашего собственного плана. Скорость инференса на CPU зависит от количества ядер, пропускной способности памяти и соседей, с которыми вы делите хост, поэтому измерьте количество токенов в секунду на своей машине и считайте этот результат истинным. Время ожидания из-за «шумного соседа» проявляется здесь как скорость генерации, которая меняется от часа к часу.

Оставьте сервис на 127.0.0.1 и используйте прокси

--host по умолчанию привязывается к 127.0.0.1, поэтому сервер недоступен извне, пока вы не измените настройки. Не меняйте их. В llama-server отсутствует модель пользователей, ограничение частоты запросов и полезные журналы аудита, а единственный встроенный механизм контроля — --api-key, который сравнивает одну строку. Открытый порт для инференса — это бесплатные вычислительные мощности для любого, кто его обнаружит. Ошибка при настройке Ollama имеет те же последствия: ограничение доступа к API локальной модели применимо здесь в полной мере.

Выполняйте TLS (transport layer security) termination в nginx и проксируйте запросы на loopback-интерфейс.

server {
    listen 443 ssl;
    server_name llm.example.com;

    location /v1/ {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_buffering off;
        proxy_read_timeout 600s;
    }
}

proxy_buffering off необходим для потоковой передачи. Если буферизация включена, nginx удерживает server-sent events (SSE) до завершения ответа, поэтому клиент ожидает в тишине, а затем получает весь ответ целиком. proxy_read_timeout 600s позволяет обрабатывать длительные генерации, так как значение по умолчанию в 60 секунд превращает медленный ответ в 504 Gateway Time-out. Получите сертификат с помощью Certbot и Let's Encrypt в nginx.

Юнит systemd

Запишите /etc/systemd/system/llama-server.service.

[Unit]
Description=llama.cpp server
After=network-online.target
Wants=network-online.target

[Service]
User=llama
Group=llama
Environment=LLAMA_ARG_MODEL=/srv/models/gemma-3-1b-it-Q4_K_M.gguf
Environment=LLAMA_ARG_HOST=127.0.0.1
Environment=LLAMA_ARG_PORT=8080
Environment=LLAMA_ARG_CTX_SIZE=4096
Environment=LLAMA_ARG_N_PARALLEL=1
Environment=LLAMA_ARG_THREADS=2
ExecStart=/usr/local/bin/llama-server --no-webui
Restart=on-failure
RestartSec=5
TimeoutStopSec=30
MemoryHigh=3G
MemoryMax=3500M
OOMPolicy=stop
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes

[Install]
WantedBy=multi-user.target

Настройки находятся в строках Environment=, так как llama-server считывает переменные LLAMA_ARG_* для большинства флагов, а аргумент командной строки переопределяет соответствующую переменную. Это позволяет изменять размер контекста в одном месте и сохраняет ExecStart достаточно коротким для быстрого чтения.

Параметр ProtectSystem=strict делает всю файловую систему доступной только для чтения для данного юнита, что допустимо, так как сервер только считывает модель. Добавьте ReadWritePaths=/srv/models, если вы хотите, чтобы сам сервис загружал модели с помощью -hf. Параметр ProtectHome=yes скрывает /home и /root, и это вторая причина хранить модели в /srv: при включенном ProtectHome путь по умолчанию ~/.cache/llama.cpp вообще не виден процессу.

sudo systemctl daemon-reload
sudo systemctl enable --now llama-server
systemctl status llama-server
curl -s http://127.0.0.1:8080/health
journalctl -u llama-server -n 50 --no-pager

enable --now — это половина дела, которую люди пропускают. Без enable сервер исчезнет после следующей перезагрузки. Если вам нужны запланированные задачи вокруг сервиса, например, ночная проверка наличия нового релиза, используйте systemd-сервис и таймер в качестве механизма для этого.

Определите поведение при OOM до возникновения проблемы

Использование памяти состоит из двух частей, которые ведут себя по-разному при достижении лимита. Файл модели по умолчанию отображается в память (memory-mapped), поэтому его страницы привязаны к файлу: ядро может выгрузить их и считать снова с диска. KV-кэш, представляющий собой состояние для каждого токена, которое сервер хранит для всех активных диалогов, является анонимной памятью. Его нельзя выгрузить, поэтому именно из-за него процесс завершается принудительно.

Именно поэтому два лимита в юните выполняют разные задачи. MemoryHigh=3G — это мягкий лимит: при его превышении ядро оказывает давление на cgroup, поэтому отображенные страницы модели вытесняются и считываются с диска при генерации следующего токена. Сервис продолжает работать, но замедляется. MemoryMax=3500M — это жесткий лимит: при его превышении процесс завершается, и журнал событий прямо указывает на это.

llama-server.service: A process of this unit has been killed by the OOM killer.

Установите --ctx-size самостоятельно. Значение по умолчанию — 0, что соответствует контексту, на котором обучалась модель. Современные модели с длинным контекстом выделяют очень большой KV-кэш при запуске. В результате сервис завершается, не успев обработать ни одного запроса. --parallel умножает те же затраты, так как каждый слот хранит собственное состояние диалога, поэтому оставьте значение 1, пока не убедитесь, что вам действительно нужна параллельная обработка.

С параметром Restart=on-failure завершенный сервис перезапускается. Если процесс завершается при каждом запуске, systemd прекращает попытки, и systemctl status выводит start request repeated too quickly. Это правильное поведение: цикл перезапуска, который считывает 12 GB файл каждые пять секунд, хуже, чем простой сервиса. Исправьте лимит или размер контекста, затем очистите состояние с помощью sudo systemctl reset-failed llama-server.

Отслеживайте реальное потребление с помощью systemctl show llama-server -p MemoryCurrent во время выполнения запроса. В Ограничение памяти и CPU процесса с помощью systemd эти директивы описаны более подробно.

Избегайте использования swap для данной нагрузки. Вытеснение модели в swap превращает генерацию каждого токена в случайные операции чтения с диска. Отображение файла модели в память (memory mapping) достигает того же эффекта с меньшим вредом, так как ядро считывает нужные страницы напрямую из файла.

В каких случаях Ollama является лучшим решением

Здесь пути расходятся. Выбирайте llama-server, если вам нужен один процесс с заданными флагами, зафиксированной сборкой и конкретным файлом, где конфигурация остается неизменной, так как другие процессы не запущены.

Выбирайте Ollama, если вам требуется управление моделями: загрузка моделей по имени, хранение нескольких моделей на диске, выгрузка неактивных моделей и обновление одной командой вместо пересборки. Это реальная работа, которую в противном случае вам пришлось бы автоматизировать с помощью скриптов. Запуск Ollama на VPS — это та же задача, но с иным балансом преимуществ и недостатков. Оба варианта предоставляют API, совместимый с OpenAI, поэтому клиентский код будет работать при переключении в любую сторону.

Обновление зафиксированной сборки

Замените bNNNNN на тег, на который вы переходите.

cd llama.cpp
git fetch --tags
git checkout bNNNNN
cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DLLAMA_BUILD_TESTS=OFF -DLLAMA_BUILD_EXAMPLES=OFF
cmake --build build --config Release -t llama-server -j 2
sudo install -m 755 build/bin/llama-server /usr/local/bin/llama-server-bNNNNN
sudo ln -sfn /usr/local/bin/llama-server-bNNNNN /usr/local/bin/llama-server
sudo systemctl restart llama-server

Старый бинарный файл остаётся на диске, поэтому откат выполняется одной командой ln -sfn с возвратом к llama-server-b10488 и последующим перезапуском. Перед обновлением ознакомьтесь с примечаниями к выпуску. Файлы GGUF имеют версионность, и старые версии продолжают загружаться, однако флаги могут быть переименованы: --mlock и --no-mmap уже считаются устаревшими в пользу --load-mode, и unit-файл, передающий удалённый флаг, при запуске выдаст ошибку о нераспознанном аргументе.

Типичные сбои и сообщения об ошибках

error while loading shared libraries: libllama.so возникает после копирования бинарного файла в другое место. Сборка по умолчанию создает разделяемые библиотеки рядом с исполняемым файлом. Выполните пересборку с параметром -DBUILD_SHARED_LIBS=OFF или скопируйте всю директорию build/bin.

Illegal instruction (core dumped) при запуске или при первом запросе. Бинарный файл был скомпилирован с включенным GGML_NATIVE для процессора, архитектура которого отличается от текущей. Выполните пересборку на целевой машине или настройте параметры с помощью -DGGML_NATIVE=OFF.

c++: fatal error: Killed signal terminated program cc1plus во время сборки. Компилятор был принудительно завершен из-за нехватки оперативной памяти. Уменьшите значение -j или добавьте swap-файл на время сборки, а затем удалите его.

curl: (7) Failed to connect ... Connection refused при попытке подключения с вашего ноутбука. Это ожидаемое поведение: сервер ожидает соединений только на адресе loopback внутри VPS. Проверьте работу на самой VPS или создайте туннель с помощью ssh -L 8080:127.0.0.1:8080 user@your-vps и используйте http://127.0.0.1:8080 локально.

HTTP 503 с "message":"Loading model" в первые секунды или минуты после перезапуска. Чтение файла объемом в несколько гигабайт требует времени, а systemd помечает юнит как активный сразу после запуска процесса, еще до того, как модель загрузится в память.

Запросы зависают и возвращают 504 Gateway Time-out. Прокси-сервер прервал соединение до того, как модель завершила работу. Увеличьте значение proxy_read_timeout и отключите proxy_buffering, чтобы токены передавались клиенту по мере их генерации.

Юнит постоянно перезапускается и затем останавливается с ошибкой start request repeated too quickly. Процесс принудительно завершается при каждом запуске. Проверьте journalctl -u llama-server на наличие записей от OOM killer, затем уменьшите --ctx-size, уменьшите --parallel или увеличьте MemoryMax.

FAQ

Что лучше запустить на VPS: сервер llama.cpp или Ollama?

Используйте llama-server, если вам нужно зафиксировать конкретную сборку, передать точные флаги и хранить одну модель в одном файле, который никто не обновит без вашего ведома. Используйте Ollama, если вам нужно управление моделями и обновления одной командой, так как загрузка моделей по имени, хранение нескольких версий на диске и выгрузка неактивных моделей — это задачи, которые в противном случае пришлось бы автоматизировать самостоятельно. Оба решения предоставляют API, совместимый с OpenAI, поэтому код клиента не изменится, если вы решите сменить инструмент позже.

Какую версию llama.cpp стоит зафиксировать?

Любой тег, который вы успешно собрали и протестировали. llama.cpp помечает тегом почти каждый merge, а названия представляют собой номера сборок, например b10488, который был актуален на 18 августа 2026 года. Отдельной стабильной ветки не существует, поэтому «текущая» версия меняется несколько раз в день. Клонируйте репозиторий с помощью --branch <tag>, установите бинарный файл под именем, содержащим этот тег, и укажите на него через символическую ссылку. Это позволит выполнять обновление и откат одной командой.

Сколько оперативной памяти нужно для llama-server?

Начните с размера файла GGUF, затем добавьте объем KV-кэша, который растет вместе с --ctx-size и количеством слотов --parallel. Опубликованные цифры не заменят замеры вашей собственной системы, так как итоговое потребление зависит от модели, квантования и разрешенного контекста. Запустите systemctl show llama-server -p MemoryCurrent во время выполнения запроса и используйте полученное значение.

Почему /health возвращает 503 с сообщением "Loading model"?

Процесс запущен, но файл модели еще не загружен в память, поэтому сервер отвечает {"error":{"code":503,"message":"Loading model","type":"unavailable_error"}}. Это нормальное поведение после каждого перезапуска, которое длится столько времени, сколько занимает чтение файла. Проблема возникает только в том случае, если клиент или прокси воспринимает этот первый ответ 503 как критический сбой. Опрашивайте /health, пока он не вернет {"status": "ok" }.

Можно ли открывать llama-server напрямую в интернет?

Не привязывайте его к 0.0.0.0 и не открывайте порт. В нем нет учетных записей, ограничения частоты запросов и логов, пригодных для аудита, а единственная встроенная проверка — это --api-key, которая сравнивает одну строку. Оставьте привязку по умолчанию 127.0.0.1, установите перед ним nginx с TLS и настройте --api-key, чтобы одна ошибка в конфигурации прокси не сделала модель доступной для всех.