Tropic Host

Локальные нейросети на VPS: установка Ollama, DeepSeek и Open WebUI, расчет ресурсов CPU и RAM

33 мин чтения
Tropic

Краткий вывод: Да, в 2026 году инференс современных открытых моделей на CPU VPS без GPU — это зрелый производственный стандарт для задач с умеренным RPS (до 2–5 параллельных сессий). Связка из квантованных весов формата GGUF (Q4_K_M / IQ4_XS), оптимизированных ядер инференса с поддержкой векторных инструкций AVX-512/AMX и легковесных платформ позволяет развернуть стек локальные нейросети на vps ollama deepseek open webui с бюджетом $20–45 в месяц, получая предсказуемые 14–26 токенов в секунду на моделях класса 7B–14B.


Содержание

  1. Аппаратные требования и сайзинг: Можно ли запускать современные LLM на CPU VPS без видеокарты в 2026 году
  2. Математика квантования GGUF: расчет оперативной памяти под веса и контекст
  3. Сравнительная матрица моделей для VPS: DeepSeek-R1, Llama 3.1, Qwen 2.5 и Mistral
  4. Пошаговая установка Ollama на сервер Ubuntu 24.04 LTS
  5. Развертывание веб-интерфейса Open WebUI в Docker Compose
  6. Бенчмарки и зависимость от процессора: AVX-512, многопоточность и NVMe
  7. Безопасный доступ и API: Nginx, SSL Let's Encrypt и защита от сканеров
  8. Оптимизация стабильности: swapfile на NVMe и предотвращение OOM Killer
  9. Часто задаваемые вопросы (FAQ)

Аппаратные требования и сайзинг: Можно ли запускать современные LLM на CPU VPS без видеокарты в 2026 году


Архитектурная реальность: почему миф о «GPU за полмиллиона» устарел

Распространенное заблуждение о непригодности CPU для LLM базируется на непонимании вычислительного профиля инференса. На этапе авторегрессионной генерации токенов (Token Generation) узким местом системы является не столько сырая вычислительная мощность (FLOPS), сколько пропускная способность подсистемы памяти (Memory Bandwidth bound).

Модель размером 7B параметров в 4-битном квантовании весит ~4.4 ГБ. Чтобы сгенерировать 1 токен, движок обязан прогнать через шину памяти весь массив весов ровно один раз. На серверных процессорах AMD EPYC (Genoa/Turin) и Intel Xeon Scalable (Sapphire/Emerald Rapids) с 8–12-канальной памятью DDR5-4800/5600 реальная полоса пропускания на сокет достигает 250–380 ГБ/с. При изоляции потоков в рамках одного сокета CPU с легкостью выдает 18–25 токенов/с в однопоточном инференсе, что в 3–4 раза превышает скорость комфортного чтения текста человеком (5–7 токенов/с).

Аппаратная аренда инстанса с Nvidia A10G, L4 или RTX 4090 обходится в диапазоне от $140 до $400 в месяц. Для внутренней автоматизации компании с объемом 200–800 обращений в сутки подобный оверхед экономически нецелесообразен: 95% времени арендованный GPU-инстанс простаивает с нулевой утилизацией тензорных ядер.

Боевые сценарии для CPU VPS в продакшене

Инференс на CPU не предназначен для публичных SaaS с тысячами одновременных пользователей, но идеально закрывает четыре корпоративных контура:

  1. RAG-пайплайны внутренней документации: Связка Qdrant/pgvector + эмбеддинги (bge-m3, latency инференса на CPU ~35 мс) + генерация ответа через deepseek-r1:7b или llama-3.1-8b. Время ответа (TTFT — Time To First Token) укладывается в 600–900 мс, общий ответ формируется за 3–5 секунд.
  2. Асинхронные воркеры суммаризации: Демоны очередей (Celery / RabbitMQ / Temporal), разбирающие транскрипции звонков, клиентские рекламации и логи инцидентов. Инференс утилизирует свободные ядра в фоновом пуле без деградации критичных сервисов.
  3. Локальные ассистенты инженеров: Интеграция deepseek-coder:6.7b или qwen2.5-coder:7b через плагины Continue.dev или Open WebUI. Скорости 16–20 токенов/с достаточно для потокового автодополнения кода и ревью Pull Request прямо в приватном периметре компании.
  4. Классификация и маршрутизация тикетов L1-поддержки: Дистиллированные модели на 1.5B–3B параметров (qwen2.5:3b, deepseek-r1:1.5b) работают на CPU со скоростью 45–65 токенов/с, мгновенно извлекая JSON-структуру из обращений.

Сравнительная матрица производительности CPU VPS vs Dedicated GPU

В таблице сведены фактические замеры инференса через стек Ollama (llama.cpp core) при контекстном окне 4096 токенов:

Модель и квантование Конфигурация хоста (vCPU / RAM / Архитектура) Пакет инструкций CPU Скорость генерации (токен/сек) Latency p99 (TTFT, с) Средняя стоимость (мес.) Продакшн-вердикт
DeepSeek-R1-Distill-Qwen-7B (Q4_K_M) 8 vCPU (Dedicated EPYC 9654), 16 ГБ DDR5 AVX-512, VNNI, Zen 4 21.4 t/s 0.85 с €28–38 Идеально: корпоративный RAG, Open WebUI до 4 одновременных пользователей
Llama-3.1-8B-Instruct (Q4_K_M) 4 vCPU (Shared Xeon 8480+), 16 ГБ DDR5 AVX-512, AMX 12.2 t/s 1.40 с €14–19 Приемлемо: чат-боты техподдержки, генерация дайджестов
DeepSeek-R1-Distill-Qwen-14B (IQ4_XS) 16 vCPU (Dedicated EPYC 7763), 32 ГБ DDR4 AVX2, Zen 3 (Dual-NUMA) 8.1 t/s 2.80 с €55–70 Условно: сложная аналитика, задержки чувствительны к NUMA-балансировке
Qwen-2.5-Coder-7B (Q4_K_M) 8 vCPU (ARM64 Ampere Altra), 16 ГБ DDR4 ARMv8.2+, Neon 14.8 t/s 1.10 с €18–24 Отлично: локальный CI/CD автокомплит и код-ревью
DeepSeek-R1-Distill-14B (FP16/AWQ) 1x Nvidia L4 24GB (GPU VPS), 8 vCPU, 32 ГБ Tensor Cores (Ada Lovelace) 68.0 t/s 0.18 с €160–220 Избыточно: оправдано только при постоянном входящем потоке >15 RPS

Критерии выбора хостинга: аппаратные метрики и риски оверселлинга

При выборе тарифа для запуска стека локальных нейросетей на VPS категорически непригодны сверхбюджетные shared-хостинги с разделяемыми ядрами. Инференс создает устойчивую 100% нагрузку на все выделенные ядра в течение времени генерации, что приводит к немедленному троттлингу со стороны гипервизора.

Обязательные требования к железу: * Тип vCPU: Исключительно Dedicated Cores (VDS/Cloud Compute с гарантированными 100% vCPU share). Метрика CPU Steal Time (%st) в утилите top или vmstat 1 не должна превышать 0.5–1.0%. Значения выше 3% свидетельствуют об агрессивном оверселлинге ноды соседями (noisy neighbors) и приводят к просадкам генерации с 20 до 4 токенов/с. * Набор инструкций: В выводе cat /proc/cpuinfo обязаны присутствовать флаги: avx2, fma, f16c, а в идеале — avx512f, avx512_vnni и amx_tile. Отсутствие AVX-512 снижает производительность llama.cpp в 1.8–2.3 раза. * Топология памяти (NUMA): На инстансах от 8–16 vCPU проверьте распределение узлов: numactl --hardware. Если виртуальные ядра размазаны между двумя сокетами без пиннинга (Cross-NUMA node access), межпроцессорная синхронизация шины снижает итоговую скорость генерации на 25–40%. * Дисковая подсистема: Модели загружаются в память через системный вызов mmap(). Диск должен выдавать случайное чтение блоками 4K не менее 40 000 IOPS при задержках await < 0.8 мс (NVMe PCIe 4.0), иначе холодный старт контейнера займет минуты вместо 2–4 секунд.


Практическая конфигурация: подготовка ядра Linux и запуск Docker-стека

1. Тюнинг системных параметров ядра (/etc/sysctl.d/99-llm-node.conf)

Запрещаем выгрузку весов в своп, оптимизируем распределение памяти и аллокацию страниц:

# Отключаем агрессивный сброс анонимной памяти в swap
vm.swappiness = 10

# Разрешаем оверкоммит для mmap-вызовов llama.cpp
vm.overcommit_memory = 1

# Отключаем автобалансировку NUMA для фиксации потоков
kernel.numa_balancing = 0

# Увеличиваем лимиты файловых дескрипторов под параллельные веб-сокеты Open WebUI
fs.file-max = 2097152

Применение настроек: sysctl -p /etc/sysctl.d/99-llm-node.conf.

2. Проверка аппаратных флагов хоста

Перед развертыванием контейнеров убедитесь в наличии векторных расширений:

lscpu | grep -E "Model name|Flags" | grep -E "avx512|amx|neon"

3. Production docker-compose.yml (Ollama + Open WebUI + cgroups тюнинг)

Файл изолирует стек в отдельном bridge-неймспейсе, конфигурирует переменные параллелизма движка и блокирует лимиты памяти во избежание срабатывания OOM Killer:

version: '3.8'

services:
  ollama:
    image: ollama/ollama:latest
    container_name: production-ollama-engine
    restart: always
    environment:
      # Запрещаем выгрузку модели из RAM после каждого запроса (держим в памяти 24 часа)
      - OLLAMA_KEEP_ALIVE=24h
      # Ограничиваем параллельный контекст для предотвращения скачков потребления RAM
      - OLLAMA_NUM_PARALLEL=2
      # Включаем Flash Attention для оптимизации KV-кэша на AVX-512
      - OLLAMA_FLASH_ATTENTION=1
      # Привязка числа потоков к реальным vCPU (например, 8)
      - OLLAMA_NUM_THREADS=8
    volumes:
      - /opt/ollama/data:/root/.ollama
    deploy:
      resources:
        limits:
          memory: 14G
        reservations:
          memory: 8G
    # Предотвращаем преждевременный kill гипервизором
    ulimits:
      memlock:
        soft: -1
        hard: -1
    networks:
      - ai-internal-mesh

  open-webui:
    image: ghcr.io/open-webui/open-webui:main
    container_name: production-webui-frontend
    restart: always
    ports:
      - "127.0.0.1:8080:8080"
    environment:
      - OLLAMA_BASE_URL=http://ollama:11434
      - WEBUI_AUTH=True
      - WEBUI_NAME=Corporate LLM Portal
    volumes:
      - /opt/open-webui/data:/app/backend/data
    depends_on:
      - ollama
    networks:
      - ai-internal-mesh

networks:
  ai-internal-mesh:
    driver: bridge
    driver_opts:
      com.docker.network.bridge.enable_icc: "true"

После старта стека (docker compose up -d) загрузка квантованного ядра модели под CPU выполняется однострочником:

docker exec -it production-ollama-engine ollama run deepseek-r1:7b

Такая конфигурация гарантирует предсказуемый инференс с latency p99 < 1.2 с на первый токен и стабильное потребление оперативной памяти не более 6.2 ГБ на весь процесс вместе с системными буферами.

Математика квантования GGUF: расчет оперативной памяти под веса и контекст

Развертывание локальной LLM на арендованном инстансе требует жесткого аудита ресурсов памяти. Если рабочий процесс ядра выходит за лимиты физической RAM, Linux OOM Killer мгновенно отправляет SIGKILL серверному процессу инференса. Когда разворачиваются локальные нейросети на vps ollama deepseek open webui, балансирование между качеством генерации, размером контекста и доступной RAM сводится к точным формулам аллокации виртуальной памяти.

Физика сжатия: переход от IEEE 754 FP16 к k-quants

В оригинальном виде веса модели хранятся в 16-битном формате с плавающей запятой (FP16 или BF16), где каждый параметр занимает ровно 2 байта (1 бит знака, 5 бит порядка, 10 бит мантиссы). Для архитектуры масштаба 8 миллиардов параметров (Llama-3-8B или DeepSeek-R1-Distill-Qwen-8B) размер сырых матриц весов в памяти рассчитывается детерминированно:

$$\text{RAM}_{\text{FP16}} = 8.03 \times 10^9 \times 2 \text{ байта} \approx 16.06 \text{ GB}$$

Формат GGUF (GPT-Generated Unified Format) библиотеки llama.cpp применяет блочную дискретизацию: непрерывный диапазон непрерывных 16-битных float проецируется на дискретные целочисленные корзины (int8, int5, int4) с сохранением локального масштабного коэффициента (scale factor $\alpha$) и смещения (zero-point $m$):

$$W_{\text{quant}} = \text{round}\left(\frac{W - m}{\alpha}\right)$$

  • Q8_0 (8.50 бит на вес): 32 значения FP16 упаковываются в 32 байта int8, плюс один 16-битный scale factor на блок. Потеря перплексии (деградация логики модели) составляет менее 0.005 PPL, однако вес модели сокращается всего вдвое (~8.5 GB для 8B).
  • Q4_0 (4.50 бит на вес): симметричное 4-битное квантование блоками по 32 веса. Дает сильное сжатие, но наносит ощутимый урон матрицам проекций внимания.
  • Q4_K_M (4.85 бит на вес, Medium K-quant): гибридное блочное квантование с иерархическими суперблоками (суперблок на 256 весов, разделенный на 8 субблоков по 32 значения). Критически важные тензоры (token_embd.weight, attention.wv, attention.wo и часть матриц feed_forward) квантуются с точностью 5 и 6 бит, а наименее чувствительные слои сжимаются в 4 бита. Это сохраняет 99.2% бенчмарк-метрик FP16-модели при снижении размера весов до 4.9 GB.

Сравнительная таблица форматов квантования (на примере моделей класса 8B)

Формат квантования GGUF Эффективных бит на вес (bpw) Объем весов в RAM (8B) Потеря перплексии ($\Delta$ PPL Llama-3) KV-кэш на 4096 токенов (FP16) Минимальная конфигурация VPS Инженерный вердикт для продакшена
FP16 / BF16 16.00 16.06 GB 0.000 (Baseline) 512 MB 32 GB RAM / 24 GB VRAM Непригодно для бюджетных VPS без GPU
Q8_0 8.50 8.54 GB +0.004 512 MB 16 GB RAM Избыточный оверхед по памяти для CPU
Q5_K_M 5.54 5.59 GB +0.032 512 MB 8 GB RAM (впритык) Высокий риск OOM при росте контекста
Q4_K_M 4.85 4.92 GB +0.051 512 MB 8 GB RAM Отраслевой стандарт (Sweet Spot)
Q3_K_M 3.91 3.98 GB +0.245 512 MB 8 GB RAM Заметная деградация синтаксиса кода
Q2_K 2.63 2.71 GB +1.870 512 MB 4 GB RAM Катастрофическая потеря когерентности

Формула полного расчета рабочей памяти: веса, KV-кэш и runtime

Фактический объем Resident Set Size (RSS), занимаемый инференс-демоном, складывается из трех изолированных компонентов:

$$\text{RAM}{\text{total}} = \text{RAM}{\text{weights}} + \text{RAM}{\text{KV_Cache}}(C, B) + \text{RAM}{\text{runtime}}$$

1. Расчет памяти под KV-кэш (Context Window)

В процессе авторегрессионного инференса механизм Self-Attention кэширует матрицы ключей ($K$) и значений ($V$) для всех предыдущих токенов, исключая повторные пересчеты сложности $O(N^2)$. Для архитектур с механизмом Grouped-Query Attention (GQA), где число голов ключей/значений сокращено относительно голов запросов (в Llama-3-8B: $N_{\text{heads_kv}} = 8$ против $N_{\text{heads_q}} = 32$), расчет на один токен контекста выполняется по формуле:

$$\text{KV}{\text{per_token}} = 2 \times N{\text{layers}} \times N_{\text{heads_kv}} \times D_{\text{head}} \times S_{\text{bytes}}$$

Параметры для Llama-3-8B / DeepSeek-R1-Distill-8B: * Число слоев трансформатора ($N_{\text{layers}}$): 32 * Число KV-голов ($N_{\text{heads_kv}}$): 8 * Размерность головы ($D_{\text{head}}$): 128 * Байт на элемент ($S_{\text{bytes}}$, FP16 по умолчанию): 2 байта

$$\text{KV}_{\text{per_token}} = 2 \times 32 \times 8 \times 128 \times 2 = 131\,072 \text{ байта} = 128 \text{ KB / токен}$$

Объем памяти под контекст при длине окна $C$ (токенов) и числе параллельных слотов $B$ (OLLAMA_NUM_PARALLEL):

$$\text{RAM}_{\text{KV_Cache}} = C \times B \times 128 \text{ KB}$$

  • При $C = 2048$, $B = 1$: $2048 \times 128 \text{ KB} = 256 \text{ MB}$
  • При $C = 4096$, $B = 1$: $4096 \times 128 \text{ KB} = 512 \text{ MB}$
  • При $C = 8192$, $B = 1$: $8192 \times 128 \text{ KB} = 1024 \text{ MB}$ (1.0 GB)
  • При $C = 32768$, $B = 1$: $32768 \times 128 \text{ KB} = 4096 \text{ MB}$ (4.0 GB — гарантированный краш на 8 GB хосте!)

2. Runtime-оверхед вычислительного графа

Движок GGML при старте сессии формирует рабочий буфер аллокатора тензоров (ggml_allocr), в котором размещаются промежуточные матрицы активаций для операций обратного распространения/прямого прохода через слои RMSNorm и RoPE. Для архитектур 8B базовый runtime-оверхед составляет от 350 до 450 MB.


Анатомия аллокации на VPS 8 GB: почему модель 8B в Q4_K_M помещается без OOM

Физический объем RAM инстанса составляет $8192 \text{ MB}$. Рассмотрим раскладку виртуальных страниц памяти в пике генерации:

[ Хостовая OS: ~450 MB ]
[ Стек Docker + Open WebUI: ~650 MB ]
[ Ollama Core Engine + Scratch: ~420 MB ]
[ Веса DeepSeek-R1-8B-Q4_K_M: ~4920 MB ]
[ KV-кэш (окно 4096 токенов): ~512 MB ]
======================================================
Итого активных страниц (RSS): ~6952 MB
Свободный резерв ядра (Page Cache / Slub): ~1240 MB

Запас в 1.2 GB защищает систему от трэшинга страниц, если ядро сбрасывает дисковый кэш при I/O-операциях Open WebUI с SQLite-базой.

Проверка распределения памяти процесса в Linux CLI

Для снятия точных метрик аллокации процесса инференса используется чтение агрегированных страниц ядра:

# Получение PID серверного процесса Ollama
PID=$(pgrep -f "ollama.*runner" || pgrep ollama)

# Детальный срез VmRSS, VmPeak и анонимной памяти
cat /proc/${PID}/status | grep -E 'VmSize|VmRSS|RssAnon|VmSwap'

# Аудит физической раскладки через smaps_rollup (в МБ)
awk '/Rss:/{rss+=$2} /Pss:/{pss+=$2} /Anonymous:/{anon+=$2} END {print "RSS="rss/1024"MB, PSS="pss/1024"MB, Anon="anon/1024"MB"}' /proc/${PID}/smaps_rollup

Системный тюнинг ядра Linux и лимиты Cgroups v2

Для предотвращения падения ноды при всплесках контекста применяются жесткие ограничения на уровне ядра (sysctl) и контейнеризации.

1. Конфигурация ядра /etc/sysctl.d/99-llm-vps.conf

# Запрет агрессивного сброса анонимных страниц LLM в swap (критично для задержек latency p99)
vm.swappiness = 10

# Отключение безлимитного оверкоммита памяти (предотвращение паники ядра)
vm.overcommit_memory = 2
vm.overcommit_ratio = 85

# Защита от зависаний I/O при сбросе дискового кэша
vm.dirty_background_ratio = 5
vm.dirty_ratio = 10

Применение настроек без перезагрузки:

sysctl --system

2. Жесткая изоляция ресурсов в docker-compose.yml

Если Ollama и Open WebUI запускаются в Docker, контейнерам задаются детерминированные cgroups-лимиты:

version: '3.8'

services:
  ollama:
    image: ollama/ollama:latest
    container_name: ollama-engine
    restart: unless-stopped
    environment:
      - OLLAMA_NUM_PARALLEL=1
      - OLLAMA_MAX_LOADED_MODELS=1
    volumes:
      - /opt/ollama:/root/.ollama
    deploy:
      resources:
        limits:
          memory: 6400M
        reservations:
          memory: 5500M
    # Защита от OOM: понижаем приоритет уничтожения процесса ядром
    oom_score_adj: -500

  open-webui:
    image: ghcr.io/open-webui/open-webui:main
    container_name: open-webui
    restart: unless-stopped
    ports:
      - "127.0.0.1:3000:8080"
    environment:
      - OLLAMA_BASE_URL=http://ollama:11434
    deploy:
      resources:
        limits:
          memory: 1024M
    oom_score_adj: 200

При таком распределении параметров oom_score_adj в случае критического дефицита RAM ядро Linux в первую очередь завершит фоновые задачи веб-интерфейса (oom_score_adj = 200), сохранив инференс-процесс модели (oom_score_adj = -500) и защитив системный демон SSH от аварийной блокировки.

Сравнительная матрица моделей для VPS: DeepSeek-R1, Llama 3.1, Qwen 2.5 и Mistral

Развертывание локальных нейросетей на VPS через Ollama и Open WebUI упирается в физику серверной архитектуры: инференс LLM на центральных процессорах (CPU-only) лимитирован исключительно пропускной способностью шины оперативной памяти (Memory Bandwidth), а не только гигагерцами ядер. Формула теоретического предела скорости генерации для CPU проста:

$$\text{Max Tokens/s} \approx \frac{\text{Пропускная способность RAM (ГБ/с)}}{\text{Размер модели в памяти (ГБ)}}$$

На виртуальных серверах (KVM) с двухканальной DDR4 (35–45 ГБ/с) модель объемом 4.5 ГБ физически не способна генерировать быстрее 8–10 токенов/сек при квантовании Q4_K_M. Если гипервизор переподписан (oversold), и метрика %st (CPU Steal Time в утилите mpstat или top) поднимается выше 3–5%, латентность p99 вырастает кратно, превращая потоковый ответ Open WebUI в слайд-шоу с задержками по 400–800 мс на токен.

Ниже приведена матрица валидированных моделей, протестированных на чистом KVM-инференсе (Intel Xeon Gold 6338 / AMD EPYC 7763, DDR4-3200 ECC, AVX-512 enabled).

Техническая матрица моделей для инференса на CPU

Модель Формат и вес (Квант) Мин. требования VPS (OOM-порог) Рекомендуемый профиль VPS (cgroups) CPU Throughput (4–8 vCPU) Токенизатор и русский язык Архитектурная специфика и лучший интент
DeepSeek-R1-Distill-Qwen-7B Q4_K_M
4.68 GB
1× vCPU, 8 GB RAM, 15 GB NVMe 6–8 vCPU, 16 GB RAM, High-Freq CPU 6.5 – 10.5 tok/s
(p99: 140 ms)
Словарь 152k (Qwen). Идеальная кириллица (0.95 токена/слово). Reasoning / Chain-of-Thought. Глубокий анализ кода, построение логических цепочек, решение математических задач.
Llama 3.2 3B Q4_K_M
2.01 GB
1× vCPU, 4 GB RAM, 10 GB NVMe 2–4 vCPU, 8 GB RAM, Standard VPS 19.0 – 27.5 tok/s
(p99: 45 ms)
Словарь 128k (tiktoken). Высокая компрессия, чистый синтаксис. Ультралегкий роутер запросов, суммаризация логов, интеграция в Telegram-боты на дешевых VPS ($3–5/мес).
Qwen 2.5 7B Q4_K_M
4.68 GB
2× vCPU, 8 GB RAM, 15 GB NVMe 4–8 vCPU, 12–16 GB RAM, Dedicated Core 8.2 – 13.0 tok/s
(p99: 95 ms)
Словарь 152k. Эталонная морфология русского языка без деградации падежей. Универсальный корпоративный ассистент, RAG по русскоязычной документации, генерация SQL/Bash скриптов.
Mistral 7B Instruct v0.3 Q4_K_M
4.37 GB
2× vCPU, 8 GB RAM, 15 GB NVMe 4–6 vCPU, 12 GB RAM, Balanced CPU 8.0 – 12.2 tok/s
(p99: 110 ms)
Словарь 32k. Базовый байтовый fallback, русский язык «дороже» по токенам (1.4–1.7/слово). Строгое следование системному промпту (System instructions adherence), JSON-mode, извлечение сущностей (NER).

Детальный разбор архитектур и специфика аллокации ресурсов

1. DeepSeek-R1-Distill-Qwen-7B: Рассуждения и скрытый оверхед KV-кэша

Модель получена дистилляцией «рассуждающих» траекторий оригинального DeepSeek-R1 в базу Qwen 2.5. Она формирует блоки <think>...</think>, выдавая 500–1500 служебных токенов до финального ответа.

  • Критический нюанс памяти: Длинные цепочки рассуждений быстро раздувают KV-кэш. При контекстном окне в 16k токенов в формате fp16 один только контекст забирает: $$KV_Size = 2 \times 28 \text{ (layers)} \times 8 \text{ (kv-heads)} \times 128 \text{ (head-dim)} \times 16384 \times 2 \text{ bytes} \approx 1.83\text{ GB RAM}$$
  • Вердикт: На VPS с 8 ГБ RAM при одновременном запуске Ollama и Open WebUI в Docker лимит cgroups будет пробит OOM-киллером при контексте свыше 8k. Требуется строгая фиксация num_ctx: 8192 и включение квантования KV-кэша.

2. Llama 3.2 3B: Минималистичный боевой стек для бюджетного хостинга

Модель спроектирована под крайнюю эффективность на периферийных узлах. Вес в 2.01 ГБ в квантовании Q4_K_M целиком помещается в кэш страниц Linux при активном mmap.

  • Позволяет поднимать локальные нейросети на VPS начального уровня (1 ядро, 4 ГБ RAM) с параллельным веб-интерфейсом Open WebUI, потребляя суммарно не более 3.2 ГБ Resident Set Size (RSS).
  • Скорость генерации 20+ tok/s обеспечивает мгновенный отклик интерфейса без необходимости использования стриминговых костылей или прогрев-скриптов.

3. Qwen 2.5 7B: Безусловный лидер для русскоязычной инфраструктуры

Главное инженерное преимущество семейства Qwen 2.5 — токенизатор на 152 064 токена. В отличие от Llama 2 или старых моделей Mistral, где кириллические символы разбивались на байты (увеличивая длину последовательности в 2.5 раза и сжирая окно контекста), Qwen обрабатывает русский текст с коэффициентом 0.95–1.1 токена на слово.

  • Это экономит до 50% оперативной памяти при инференсе RAG-пайплайнов с русскоязычными базами знаний.
  • Архитектура Grouped-Query Attention (GQA) снижает нагрузку на шину RAM при пакетной генерации.

4. Mistral 7B Instruct (v0.3): Предсказуемый парсинг и структурированный вывод

Обновленный Mistral v0.3 поддерживает нативный functional calling и расширенное окно контекста до 32k с технологией Sliding Window Attention (SWA).

  • Главная сила модели — детерминированный JSON-mode. Если пайплайн требует парсинга сырых server logs или Nginx access logs в жесткую структуру JSON через API Ollama, Mistral дает наименьший процент галлюцинаций по синтаксису запятых и экранированию кавычек.
  • Однако плотность русскоязычного словаря низкая: идентичный русский текст займет на 30–45% больше токенов памяти, чем в Qwen 2.5.

Тюнинг Linux и бенчмаркинг инференса в терминале

При развертывании Ollama на чистом CPU критически важно исключить сброс страниц весов модели в Swap и зафиксировать потоки под физические ядра (vCPU) хоста.

# 1. Запретить вымывание страниц модели в zRAM/Swap и увеличить лимиты памяти
sudo sysctl -w vm.swappiness=1
sudo sysctl -w vm.overcommit_memory=1
sudo sysctl -w vm.max_map_count=524288

# 2. Проверка аппаратных инструкций процессора (AVX2 / AVX-512)
grep -m1 -E 'avx512|avx2' /proc/cpuinfo

Создайте оптимизированный Modelfile для фиксации ресурсов CPU перед запуском в Ollama:

FROM deepseek-r1:7b

# Ограничиваем окно контекста для предотвращения OOM-killer на 8-16 GB RAM
PARAMETER num_ctx 8192

# Принудительно задаем число потоков = числу физических vCPU (без гипертрединга)
PARAMETER num_thread 4

# Отключаем сброс памяти и включаем прямое отображение файлов
PARAMETER use_mmap true
PARAMETER use_mlock false

# Снижаем штраф за длину рассуждений
PARAMETER temperature 0.6

Замерьте реальную скорость отдачи токенов и задержку первого токена (TTFT — Time To First Token) через системный curl в API Ollama:

curl -s http://127.0.0.1:11434/api/generate -d '{
  "model": "deepseek-r1:7b",
  "prompt": "Напиши bash-скрипт мониторинга CPU Steal Time через mpstat.",
  "stream": false
}' | jq '{
  total_duration_ms: (.total_duration / 1000000 | round),
  eval_count: .eval_count,
  eval_duration_ms: (.eval_duration / 1000000 | round),
  tokens_per_second: (.eval_count / (.eval_duration / 1000000000) | round)
}'

Если расчетный tokens_per_second падает ниже 5 tok/s при 4 выделенных ядрах, проверьте показатель %st командой vmstat 1 5 — наличие значений больше 0 сигнализирует о «шумных соседях» на узле хостинга, требуя немедленной смены локации VPS или перехода на тариф с Dedicated vCPU (VDS).

Пошаговая установка Ollama на сервер Ubuntu 24.04 LTS

Развертывание стека под локальные нейросети на VPS (ollama deepseek open webui) в среде Ubuntu 24.04 LTS требует предварительного аудита вычислительных ресурсов хоста. В отличие от легковесных микросервисов, инференс квантованных LLM на чистом CPU или бюджетных виртуальных машинах с shared vCPU критичен к набору процессорных инструкций и показателям оверселлинга.

Перед запуском инсталлятора выполните валидацию аппаратных возможностей узла:

# Проверка поддержки векторных инструкций для llama.cpp бэкенда
lscpu | grep -E "Model name|Flags" | grep -iE "avx|avx2|avx512|fma"

# Аудит CPU Steal Time (%st) в реальном времени
vmstat 1 5

Если флаги avx2 и fma отсутствуют, llama.cpp переключится на fallback-инструкции общего назначения, что вызовет деградацию генерации до 1–2 токенов в секунду. Значение %st выше 3–5% в выводе vmstat сигнализирует об агрессивном «соседстве» на гипервизоре хостера; в моменты пикового инференса задержка первого токена (TTFT, Time To First Token) и latency p99 вырастут на порядок.

1. Инсталляция рантайма через официальный пайплайн

Установка производится стандартизированным шелл-скриптом, который определяет архитектуру ядра (x86_64 или aarch64), выгружает скомпилированный бинарник, конфигурирует изолированного системного пользователя и регистрирует юнит systemd:

curl -fsSL https://ollama.com/install.sh | sh

Инсталлятор выполняет следующие системные мутации: * Размещает бинарный исполняемый файл в /usr/local/bin/ollama (права 0755). * Создает системного непривилегированного пользователя ollama и одноименную группу с домашней директорией /usr/share/ollama (/bin/false в качестве шелла). * Создает базовый unit-файл /etc/systemd/system/ollama.service.

Сразу после завершения установки остановите сервис для внесения низкоуровневых правок конфигурации:

sudo systemctl stop ollama

2. Конфигурация systemd через drop-in override

Прямое редактирование файла /etc/systemd/system/ollama.service недопустимо: при следующем обновлении рантайма через curl ... | sh пакетный скрипт перезапишет файл и сбросит параметры окружения. Корректная production-практика в Linux — создание drop-in директории с файлом переопределений:

sudo mkdir -p /etc/systemd/system/ollama.service.d
sudo nano /etc/systemd/system/ollama.service.d/override.conf

Внесите в файл конфигурацию переменных окружения и директив изоляции cgroups:

[Service]
# Привязка API ко всем сетевым интерфейсам хоста
Environment="OLLAMA_HOST=0.0.0.0:11434"

# Ограничение параллельных контекстов инференса на инстанс
Environment="OLLAMA_NUM_PARALLEL=2"

# Фиксация модели в RAM/VRAM (ликвидация задержек cold-start)
Environment="OLLAMA_KEEP_ALIVE=24h"

# Контроль количества одновременно загруженных моделей в память
Environment="OLLAMA_MAX_LOADED_MODELS=1"

# Разрешенные источники для интеграции с внешним Open WebUI
Environment="OLLAMA_ORIGINS=*"

# Защита процесса от принудительного завершения ядром при дефиците памяти
OOMScoreAdjust=-500

# Лимиты файловых дескрипторов под постоянные соединения
LimitNOFILE=65535

Техническое обоснование ключевых директив:

  • OLLAMA_HOST=0.0.0.0:11434: По умолчанию Ollama слушает сокет на loopback-интерфейсе 127.0.0.1. Для подключения удаленного шлюза или контейнера Open WebUI требуется слушать 0.0.0.0 (либо конкретный IP внутреннего интерфейса WireGuard/Tailscale). При биндинге на публичный интерфейс обязательно закройте порт 11434 с помощью ufw или nftables, оставив доступ только доверенному IP-адресу инстанса WebUI.
  • OLLAMA_NUM_PARALLEL=2: Задает количество параллельных слотов обработки входящих запросов к одной модели. Каждый дополнительный параллельный поток требует аллокации отдельного буфера KV-кэша. Для контекстного окна в 8 192 токенов при FP16 каждый слот резервирует около 1.2–1.8 ГБ оперативной памяти сверх весов самой модели. При нехватке RAM превышение этого параметра приведет к вызову OOM Killer ядра.
  • OLLAMA_KEEP_ALIVE=24h: По умолчанию таймаут выгрузки модели из памяти составляет 5 минут (5m). При редких запросах каждый цикл инференса будет инициировать холодный старт с диска: чтение 4.7 ГБ весов с накопителя создает пиковую нагрузку на подсистему ввода-вывода (IOPS spikes) и увеличивает latency первого ответа на 8–15 секунд. Значение 24h удерживает веса в resident set memory (RSS).
  • OOMScoreAdjust=-500: Снижает приоритет уничтожения процесса сервисом oom-killer Linux при исчерпании доступной физической памяти.

3. Тюнинг параметров ядра Linux под инференс

Инференс моделей в оперативной памяти чувствителен к механизмам подкачки и кэширования страниц. Сброс рабочих страниц весов в swap-раздел на NVMe приводит к фатальному падению пропускной способности с 12–15 токенов/сек до долей токена.

Зафиксируйте параметры виртуальной памяти ядра в /etc/sysctl.d/99-ollama.conf:

sudo bash -c 'cat <<EOF > /etc/sysctl.d/99-ollama.conf
# Минимизация агрессивности сброса страниц анонимной памяти в Swap
vm.swappiness=10

# Отключение принудительного overcommit для предотвращения паники рантайма
vm.overcommit_memory=1

# Увеличение лимита областей отображения памяти (mmap)
vm.max_map_count=262144
EOF'

sudo sysctl --system

Применение vm.overcommit_memory=1 критично для вызовов mmap(), которыми движок llama.cpp маппит веса формата GGUF в виртуальное адресное пространство процесса.

4. Рестарт сервиса и валидация сокета

Примените изменения конфигурации systemd и запустите сервис:

sudo systemctl daemon-reload
sudo systemctl restart ollama
sudo systemctl enable ollama

# Проверка статуса демона и сетевого сокета
sudo systemctl status ollama --no-pager
sudo ss -tulpn | grep 11434

Убедитесь, что вывод ss содержит состояние LISTEN на адресе 0.0.0.0:11434 с идентификатором процесса ollama.

5. Загрузка и бенчмарк модели DeepSeek-R1:7b

Для запуска используем дистиллированную reasoning-модель DeepSeek-R1 на базе архитектуры Qwen 2.5 7B. В реестре Ollama тег deepseek-r1:7b соответствует 4-битному квантованию Q4_K_M, оптимизированному под сохранение перплексии при умеренных требованиях к памяти:

# Фоновая загрузка слоя весов в локальное хранилище (/usr/share/ollama/.ollama/models)
ollama pull deepseek-r1:7b

Манифест модели занимает ~4.7 ГБ на диске. В рантайме модель требует минимум 6.5–7.0 ГБ свободной RAM с учетом контекста и базового KV-кэша.

Проведите валидационный запуск через встроенный CLI для подтверждения корректной сборки графа вычислений:

ollama run deepseek-r1:7b "Напиши bash-однострочник для парсинга IP-адресов с кодом ответа 500 из /var/log/nginx/access.log"

Для программного аудита скорости генерации и времени отклика отправьте прямой HTTP POST запрос к REST API с флагом замера метрик:

curl -s http://127.0.0.1:11434/api/generate -d '{
  "model": "deepseek-r1:7b",
  "prompt": "Explain Linux cgroups v2 in 2 sentences",
  "stream": false
}' | jq '{
  total_duration_ms: (.total_duration / 1000000),
  load_duration_ms: (.load_duration / 1000000),
  eval_count: .eval_count,
  eval_duration_ms: (.eval_duration / 1000000),
  tokens_per_second: (.eval_count / (.eval_duration / 1000000000))
}'

Значение поля tokens_per_second на современном x86_64 VPS с 4–8 vCPU без GPU-ускорения должно находиться в диапазоне 8–16 tok/s. Зафиксированный результат подтверждает готовность API-слоя к обслуживанию запросов от внешнего интерфейса Open WebUI.

Развертывание веб-интерфейса Open WebUI в Docker Compose

Архитектура стека, в котором функционируют локальные нейросети на vps ollama deepseek open webui, базируется на строгом разделении вычислительного бэкенда инференса и интерфейсного уровня. Open WebUI представляет собой контейнеризированное приложение на базе FastAPI (бэкенд) и SvelteKit (фронтенд), агрегирующее сессии пользователей, локальные векторные хранилища (ChromaDB), пайплайн RAG и коммуникацию с Ollama API по протоколу HTTP/SSE (Server-Sent Events).

Главная задача проектирования инфраструктуры на VPS — исключить прямую публикацию порта инференса 11434 во внешнюю сеть и изолировать потребление памяти веб-сервером, предотвратив срабатывание механизма ядра oom-killer при пиковой генерации контекста моделями DeepSeek.

Сетевая топология и манифест docker-compose.yml

Для обеспечения безопасности создается изолированный пользовательский мост (custom bridge network). Контейнер Open WebUI взаимодействует с движком инференса по внутреннему DNS-имени сервиса ollama, минуя хостовый сетевой стек (0.0.0.0). Доступ к веб-интерфейсу жестко привязывается к локальной петле 127.0.0.1:8080 для последующего проксирования через Nginx с поддержкой постоянных соединений (HTTP/1.1 Upgrade WebSocket).

Создайте рабочую директорию проекта и инициализируйте файловую структуру:

mkdir -p /opt/ai-stack/data/{ollama,openwebui}
cd /opt/ai-stack
chmod -R 750 /opt/ai-stack

Манифест docker-compose.yml с преднастроенными ограничениями cgroups и переменными окружения:

version: '3.8'

networks:
  ai-network:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 172.28.0.0/16

volumes:
  ollama_storage:
    driver: local
  openwebui_storage:
    driver: local

services:
  ollama:
    image: ollama/ollama:latest
    container_name: ollama-engine
    restart: unless-stopped
    networks:
      - ai-network
    volumes:
      - ollama_storage:/root/.ollama
    environment:
      - OLLAMA_KEEP_ALIVE=24h
      - OLLAMA_NUM_PARALLEL=2
      - OLLAMA_FLASH_ATTENTION=1
    deploy:
      resources:
        limits:
          memory: 24G
        reservations:
          memory: 8G
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:11434/api/version || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 15s

  open-webui:
    image: ghcr.io/open-webui/open-webui:main
    container_name: open-webui
    restart: unless-stopped
    networks:
      - ai-network
    ports:
      - "127.0.0.1:8080:8080"
    volumes:
      - openwebui_storage:/app/backend/data
    environment:
      # Подключение к движку инференса через внутренний DNS Docker
      - OLLAMA_BASE_URL=http://ollama:11434

      # Безопасность и RBAC
      - WEBUI_SECRET_KEY=9f8e7d6c5b4a3120efcdab3456789012abcdef34567890abcdef1234567890ab
      - WEBUI_AUTH=True
      - ENABLE_SIGNUP=False
      - DEFAULT_USER_ROLE=user

      # Конфигурация локального RAG-пайплайна
      - VECTOR_DB=chroma
      - CHROMA_DATA_PATH=/app/backend/data/vector_db
      - RAG_EMBEDDING_ENGINE=ollama
      - RAG_EMBEDDING_MODEL=bge-m3:latest
      - CHUNK_SIZE=1200
      - CHUNK_OVERLAP=150
      - RAG_TOP_K=4
      - ENABLE_RAG_WEB_SEARCH=False
    depends_on:
      ollama:
        condition: service_healthy
    deploy:
      resources:
        limits:
          cpus: '4.0'
          memory: 6G
        reservations:
          memory: 2G

Настройка системных вызовов ядра и тюнинг памяти

Векторная база данных ChromaDB, встроенная в Open WebUI, использует библиотеку HNSWlib для построения графов приблизительного поиска ближайших соседей (ANN). Данная операция интенсивно задействует системный вызов mmap() для прямого отображения файлов индексов с NVMe в виртуальное адресное пространство процесса. При дефолтном значении лимита виртуальной памяти контейнер аварийно завершает работу с кодом SIGSEGV или Out of memory.

Внесите корректировки в /etc/sysctl.d/99-ai-stack.conf:

# Предотвращение сбоев mmap в ChromaDB и SQLite
vm.max_map_count = 262144

# Защита от OOM при аллокации квантованных тензоров DeepSeek
vm.overcommit_memory = 1

# Минимизация сброса страниц в swap при высоких пиках I/O
vm.swappiness = 10

Примените параметры на работающем хосте без перезагрузки:

sysctl -p /etc/sysctl.d/99-ai-stack.conf

Для защиты демона инференса от принудительного уничтожения подсистемой ядра Linux скорректируйте oom_score_adj для контейнера ollama-engine:

docker inspect -f '{{.State.Pid}}' ollama-engine | xargs -I {} echo -500 > /proc/{}/oom_score_adj

Запуск и инициализация моделей

Запустите стек в фоновом режиме:

docker compose up -d

Проверьте статус готовности сервисов по логам инициализации:

docker compose logs -f open-webui | grep -E "Uvicorn running|Application startup complete"

Загрузите в Ollama целевую LLM для инференса и легковесную эмбеддинг-модель для RAG:

# Модель для генерации ответов (DeepSeek 14B Q4_K_M)
docker exec -it ollama-engine ollama run deepseek-r1:14b ""

# Модель векторизации для RAG с поддержкой контекста 8192 токенов
docker exec -it ollama-engine ollama pull bge-m3:latest

Архитектура авторизации и RBAC

Первая учетная запись, созданная в интерфейсе Open WebUI, автоматически получает статус суперпользователя (admin). Поскольку переменная ENABLE_SIGNUP=False зафиксирована в compose-файле, регистрация сторонних пользователей через веб-форму заблокирована.

Добавление инженеров или операторов выполняется администратором через меню Admin Panel -> Users -> Add User: * Admin: полный контроль над системными промптами, удалением векторных коллекций, выгрузкой/удалением моделей из памяти GPU/RAM, прямым просмотром логов и системных метрик. * User: доступ исключительно к назначенным моделям (через белый список тегов в настройках модели), собственным сессиям диалогов и загрузке личных PDF-документов в изолированные коллекции.

Конфигурация локального RAG-модуля под высокую нагрузку

При загрузке PDF-документа Open WebUI запускает конвейер обработки: 1. Парсинг и извлечение текста: бинарный поток обрабатывается библиотекой pypdf/fitz. 2. Сегментация (Chunking): текст нарезается рекурсивным алгоритмом RecursiveCharacterTextSplitter на чанки размером 1200 символов с перекрытием 150 символов. Это сохраняет целостность технического контекста на границах блоков. 3. Генерация эмбеддингов: чанки отправляются HTTP-пакетами по 32 штуки в Ollama (bge-m3). Использование инференс-контейнера для эмбеддингов вместо встроенной в веб-интерфейс CPU-модели снижает нагрузку на vCPU хоста, предотвращая рост задержек latency p99 на веб-сокетах. 4. Индексация в ChromaDB: векторные представления (1024 измерения для bge-m3) записываются на диск /app/backend/data/vector_db в виде плоского графа HNSW.

Для мониторинга влияния RAG на дисковую подсистему и процессор используйте утилиту iostat:

iostat -xz 1 10

При значении утилизации диска %util выше 80% или задержке await более 5 мс снизьте параметр RAG_TOP_K с 4 до 2, чтобы уменьшить объем случайных операций чтения (random reads) при формировании промпта перед подачей в DeepSeek. Контекст документа внедряется в системную инструкцию в секцию <context>...</context>, гарантируя ответы модели строго на базе загруженной документации без галлюцинаций.

Бенчмарки и зависимость от процессора: AVX-512, многопоточность и NVMe

При развертывании стека локальные нейросети на vps ollama deepseek open webui в конфигурации без дискретного GPU (CPU-only inference) вычислительным узким горлышком становятся не терафлопсы видеопамяти, а векторные расширения инструкций процессора, пропускная способность подсистемы RAM (DDR4/DDR5 memory bandwidth) и задержки ввода-вывода при первоначальном маппинге весов.

Векторные инструкции: барьер между AVX2 и AVX-512

Движок llama.cpp, на базе которого функционирует рантайм Ollama, транслирует математические операции матричного умножения квантованных весов (GEMM — General Matrix Multiply) в SIMD-инструкции (Single Instruction, Multiple Data). Квантованные форматы семейства DeepSeek-R1 (Q4_K_M, Q5_K_M, Q8_0) оперируют целочисленными блоками с последующей деквантизацией в FP16/FP32.

Ключевая разница между наборами регистров: * AVX2: 256-битные регистры ymm. Позволяют обрабатывать за один такт до 8 операций с плавающей запятой одинарной точности (FP32) или 16 операций FP16 (при наличии расширения FMA3). * AVX-512 (F, BW, VNNI): 512-битные регистры zmm. Удваивают ширину вектора, обрабатывая до 16 значений FP32 или 32 значений FP16/BF16 за такт. Наличие расширения AVX512_VNNI (Vector Neural Network Instructions) позволяет процессору выполнять инструкции VPDPBUSD — свертку 8-битных целых чисел с накоплением в 32-битный регистр за один процессорный цикл.

При инференсе модели deepseek-r1:8b (Q4_K_M) разница между хостом с чистым AVX2 и узлом с полной поддержкой AVX-512 VNNI составляет от 45% до 70% прироста скорости токенизации на этапе Prompt Evaluation.

Проверка поддержки инструкций на арендованной виртуальной машине:

# Проверка флагов процессора в пространстве гипервизора
lscpu | grep -E 'avx|avx2|avx512|vnni'

# Детальный просмотр флагов конкретного ядра
grep -m1 -o -E 'avx2|avx512f|avx512_vnni|avx512bw' /proc/cpuinfo

Если вывод пуст, хотя хостинг-провайдер заявляет современные процессоры (Intel Xeon Scalable 3-го/4-го поколений или AMD EPYC 9004), гипервизор KVM запущен с дефолтным профилем -cpu qemu64 вместо трансляции хостовых инструкций -cpu host. На таких инстансах инференс LLM деградирует до базового x86-64 SSE4.2, снижая скорость генерации до непригодных 0.8–1.2 токенов в секунду.

Архитектура ядер: 4 Dedicated Core против 8 Oversubscribed vCPU

Распространенная ошибка при выборе VPS под Ollama — покупка максимального количества дешевых виртуальных ядер. Инференс LLM на CPU линейно масштабируется по потокам только до границы физической пропускной способности контроллера памяти (Memory Channels saturation) и объема кэша L3.

Четыре выделенных ядра (Dedicated vCPU с топологией 1:1 к физическим ядрам) на архитектурах AMD Zen 4 или Intel Raptor Lake с базовой частотой 4.2–4.8 ГГц показывают стабильные 8.5–11 токенов/сек на модели 7B–8B. Восемь виртуальных ядер (vCPU) на переподписанном (oversold) двухсокетном сервере с процессорами Intel Xeon E5-2680v4 (частота 2.4 ГГц, разделяемая шина памяти DDR4-2400) выдадут не более 3.2 токенов/сек при катастрофическом разбросе задержки (p99 latency > 1800 мс на токен).

Причины падения производительности на оверселл-инстансах: 1. L3 Cache Eviction: Потоки параллельных виртуальных машин вымывают квантованные таблицы весов из процессорного кэша. 2. NUMA Inter-Node Traffic: При попадании 8 потоков на разные сокеты двухпроцессорной ноды latency доступа к страницам памяти через шину UPI/QPI возрастает в 2.5–3 раза. 3. Hyper-Threading Thrashing: Два потока одного физического ядра делят общий конвейер FPU. При 100% утилизации векторных блоков исполнение инструкций блокируется ожиданием освобождения портов планировщика.

Для изоляции потоков инференса в Linux фиксируйте affinity процесса через taskset или директивы cgroups:

# Привязка демона Ollama к физическим ядрам 0-3 с пропуском hyper-threading siblings
systemctl edit ollama.service

Добавьте блок конфигурации:

[Service]
CPUAffinity=0 2 4 6
Nice=-20

Диагностика CPU Steal Time (%st)

Паразитная нагрузка от соседей по ноде в виртуализации KVM выражается метрикой CPU Steal Time (%st). Она фиксирует процент времени, в течение которого виртуальный процессор находился в состоянии готовности к исполнению (runnable), но планировщик гипервизора физически отдавал такты хостового CPU другой виртуальной машине.

Для мониторинга используйте mpstat из пакета sysstat:

# Ежесекундный замер по каждому ядру
mpstat -P ALL 1 10

Пример проблемного вывода при параллельной нагрузке:

14:22:01  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal   %guest  %idle
14:22:02    0   72.10    0.00   12.40    0.00    0.00    0.50   15.00    0.00   0.00
14:22:02    1   68.50    0.00   14.20    0.00    0.00    0.30   17.00    0.00   0.00

Если показатель %steal превышает 3–5%, планировщик задач Linux непрерывно прерывает вычисления матричных ядер. При %st > 10% стриминг ответов через Server-Sent Events (SSE) в интерфейс Open WebUI начинает замирать: время генерации первого токена (TTFT — Time To First Token) увеличивается с 1.2 с до 14 с.

Контроль дросселирования на уровне cgroups:

cat /sys/fs/cgroup/cpu.stat
# Метрика nr_throttled и throttled_usec не должны расти при активном инференсе

Дисковая подсистема: mmap, холодный старт и NVMe

Ollama не загружает модель в память целиком через системный вызов read(). Вместо этого рантайм использует вызов mmap() (MAP_SHARED / MAP_PRIVATE), проецируя файл .gguf с диска напрямую в виртуальное адресное пространство процесса. Физические страницы памяти выделяются по требованию ядра через механизм Page Faults.

  • NVMe PCIe 4.0 (IOPS > 350k, Read > 3500 MB/s): Холодный старт весов deepseek-r1:14b (размер файла ~9 ГБ) занимает 2.4–3.1 секунды. Модель мгновенно готова к фазе prefill.
  • Сетевой Ceph / HDD / Shared SATA SSD (IOPS < 5k, Read < 150 MB/s): Холодный старт затягивается до 60–90 секунд. Ядро Linux зависает в состоянии ожидания D (uninterruptible sleep), вызывая лавинообразный рост показателя %iowait в выводе top.

Оптимизация параметров подкачки и сброса кэшей на уровне ядра Linux (/etc/sysctl.d/99-llm-io.conf):

# Предотвращение сброса весов LLM из RAM в swap при пиках нагрузки
vm.swappiness = 10

# Отключение агрессивного дефрагментирования страниц для снижения jitter
vm.compact_memory = 0

# Защита чистых страниц дискового кэша mmap от сброса
vm.vfs_cache_pressure = 50

Применение изменений:

sysctl --system

Аппаратный бенчмарк: замер метрик через ollama run --verbose

Для объективного профилирования конкретного хоста выполните запуск квантованной модели в терминале с флагом --verbose. Это отключает абстракции UI и возвращает чистый лог внутреннего таймера llama.cpp.

ollama run deepseek-r1:8b --verbose "Explain Linux kernel memory paging in 100 words"

Пример аппаратного отчета в выводе команды:

total duration:       8.412948211s
load duration:        28.142911ms
prompt eval count:    18 token(s)
prompt eval duration: 1.121402s
prompt eval rate:     16.05 tokens/s
eval count:           114 token(s)
eval duration:        7.261902s
eval rate:            15.70 tokens/s

Анализ телеметрии: * load duration: Время отработки системного вызова mmap() и инициализации контекста. Время свыше 3000 мс указывает на медленный диск или нехватку физической RAM, из-за которой ядро принудительно сбрасывает страницы других демонов. * prompt eval rate: Скорость обработки входного контекста (Prefill phase). Напрямую зависит от ширины векторных регистров (AVX-512 vs AVX2) и пропускной способности шины памяти. На этой фазе матрица токенов промпта вычисляется параллельно. * eval rate: Скорость генерации новых токенов (Decoding phase). Генерация происходит авторегрессионно (последовательно токен за токеном). Эта метрика полностью упирается в задержку памяти (RAM latency, тайминги CL) и тактовую частоту одного физического ядра. Наличие показателя ниже 6 tokens/s делает работу через Open WebUI некомфортной для интерактивного диалога.

Безопасный доступ и API: Nginx, SSL Let's Encrypt и защита от сканеров

Разворачивая локальные нейросети на VPS — Ollama, DeepSeek, Open WebUI, — многие инженеры совершают критическую ошибку: публикуют порт демона инференса напрямую в глобальную сеть.

По умолчанию Ollama слушает сокет на порту 11434. В архитектуре сервиса полностью отсутствует встроенный механизм аутентификации (AAA — Authentication, Authorization, Accounting). Любой хост, выполнивший GET /api/tags или POST /api/generate, получает неограниченный доступ к ресурсам хоста. Сканеры вроде Shodan, Censys и Masscan индексируют открытые порты 11434 менее чем за 15–20 минут после инициализации VPS. Последствия прямого доступа: * Утечка конфиденциальных весов и контекста: злоумышленник может выгрузить историю сессий или удалить загруженную модель deepseek-r1 через DELETE /api/delete. * Утилизация compute-ресурсов: внешний атакующий запускает генерацию с максимальным окном контекста (num_ctx: 32768), отправляя vCPU в 100% утилизацию, а CPU Steal Time (%st) — выше 15–20% на shared-тарифах, что приводит к блокировке виртуальной машины провайдером по AUP (Acceptable Use Policy). * CVE-уязвимости и RCE: эксплуатация известных брешей (например, CVE-2024-37032 «Probllama», допускавшая RCE через перезапись произвольных файлов при вызове POST /api/pull с вредоносным манифестом).

Ловушка Docker и UFW: почему файрвол не защищает контейнеры

Стандартная настройка UFW (ufw default deny incoming, ufw allow 22, 80, 443) не изолирует порт Ollama, если контейнер поднят с директивой ports: - "11434:11434".

Docker модифицирует цепочки PREROUTING таблицы nat в подсистеме Netfilter ядра Linux до того, как пакет попадает в цепочку INPUT, обрабатываемую UFW. В результате трафик транслируется в виртуальный сетевой мост (docker0 или кастомный bridge) в обход всех правил фильтрации хоста.

Чтобы гарантированно изолировать инференс, привязка сокета обязана осуществляться строго к локальной петле 127.0.0.1 либо контейнеры должны объединяться в изолированную внутреннюю сеть Docker:

# Фрагмент docker-compose.yml
services:
  ollama:
    image: ollama/ollama:latest
    container_name: ollama
    restart: unless-stopped
    ports:
      # Биндим только на loopback хоста! Не "11434:11434"
      - "127.0.0.1:11434:11434"
    volumes:
      - /opt/ollama:/root/.ollama
    networks:
      - ai-internal

  open-webui:
    image: ghcr.io/open-webui/open-webui:main
    container_name: open-webui
    restart: unless-stopped
    ports:
      - "127.0.0.1:3000:8080"
    environment:
      - OLLAMA_BASE_URL=http://ollama:11434
    volumes:
      - /opt/open-webui:/app/backend/data
    networks:
      - ai-internal

networks:
  ai-internal:
    driver: bridge

Проверьте сокеты на хосте после запуска:

ss -tulpn | grep 11434
# Вывод обязан содержать 127.0.0.1:11434, а не 0.0.0.0:11434 или [::]:11434

Базовые правила системного файрвола UFW:

ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp comment 'SSH'
ufw allow 80/tcp comment 'HTTP Certbot'
ufw allow 443/tcp comment 'HTTPS Reverse Proxy'
ufw enable

Архитектура Nginx: обработка SSE-стриминга и TLS 1.3

Генерация токенов большими моделями DeepSeek передается клиенту через Server-Sent Events (SSE) в виде непрерывного потока chunked transfer encoding. Некорректно настроенный reverse proxy буферизует этот вывод: пользователь ждет десятки секунд завершения всего ответа вместо мгновенного посимвольного вывода, а метрика latency p99 улетает в бесконечность.

Создайте конфигурацию виртуального хоста /etc/nginx/sites-available/ai-proxy.conf:

# Ограничение частоты запросов к API инференса
limit_req_zone $binary_remote_addr zone=llm_api_limit:10m rate=5r/s;

# Проверка статичного токена авторизации для API Ollama
map $http_authorization $is_token_valid {
    default 0;
    "Bearer d8f93a7c6e414b2d881e194f10a8c2b7" 1; # Сгенерируйте: openssl rand -hex 16
}

# Дроп ботов и сканеров по IP без валидного SNI
server {
    listen 80 default_server;
    listen 443 ssl default_server;
    server_name _;
    ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
    ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
    return 444; # Закрываем TCP-соединение без ответа
}

server {
    listen 80;
    server_name ai.yourdomain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name ai.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/ai.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/ai.yourdomain.com/privkey.pem;

    # Hardening SSL/TLS
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;

    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    add_header X-Content-Type-Options nosniff always;
    add_header X-Frame-Options SAMEORIGIN always;

    client_max_body_size 64M;

    # Маршрут к Open WebUI (GUI интерфейс)
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;

        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Критично для стриминга токенов WebUI
        proxy_buffering off;
        proxy_cache off;
        proxy_read_timeout 600s;
        proxy_send_timeout 600s;
    }

    # Внешний защищенный endpoint к API Ollama (для сторонних SDK/скриптов)
    location /api/ {
        # Проверка токена
        if ($is_token_valid = 0) {
            return 401 '{"error": "Unauthorized: Invalid or missing Bearer token"}';
        }

        limit_req zone=llm_api_limit burst=10 nodelay;

        proxy_pass http://127.0.0.1:11434;
        proxy_http_version 1.1;
        proxy_set_header Connection "";

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Отключение буферизации для SSE-стриминга генерации
        proxy_buffering off;
        proxy_cache off;
        chunked_transfer_encoding on;

        # Таймаут ожидания инференса тяжелых промптов (DeepSeek-R1 на CPU может думать долго)
        proxy_read_timeout 900s;
        proxy_connect_timeout 60s;
        proxy_send_timeout 900s;
    }
}

Выпуск SSL-сертификата Let's Encrypt через Certbot

Перед применением HTTPS-конфигурации получите сертификат через certbot в режиме standalone (при выключенном Nginx) или через плагин Nginx:

apt-get update && apt-get install -y certbot python3-certbot-nginx

# Временный выпуск сертификата
certbot certonly --nginx -d ai.yourdomain.com --agree-tos -m [email protected] --no-eff-email

# Активация виртуального хоста и проверка синтаксиса
ln -s /etc/nginx/sites-available/ai-proxy.conf /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginx

Для автоматического обновления сертификата проверьте системный таймер:

systemctl list-timers | grep certbot
# Тестовый прогон ротации сертификатов
certbot renew --dry-run

Защита от L7-сканеров и подбора токенов через Fail2ban

Автоматизированные скрипты сканирования зондируют эндпоинты на предмет типовых путей (/v1/models, /api/tags, /swagger.json). Чтобы отсекать атакующих на уровне пакетного фильтра, создайте фильтр и джейл Fail2ban.

Конфигурация фильтра /etc/fail2ban/filter.d/nginx-unauthorized.conf:

[Definition]
failregex = ^<HOST> - .* "(GET|POST).*" (401|403|444) .*$
ignoreregex =

Конфигурация джейла в /etc/fail2ban/jail.d/ai-security.local:

[nginx-unauthorized]
enabled  = true
port     = http,https
filter   = nginx-unauthorized
logpath  = /var/log/nginx/access.log
maxretry = 5
findtime = 60
bantime  = 86400
banaction = ufw

Примените конфигурацию:

fail2ban-client reload
fail2ban-client status nginx-unauthorized

При превышении 5 неавторизованных запросов или обращений к несуществующим SNI за одну минуту IP-адрес сканера немедленно блокируется правилом в UFW на 24 часа. Такой эшелонированный периметр полностью закрывает API локальной нейросети от несанкционированного расхода вычислительных ресурсов сервера.

Оптимизация стабильности: swapfile на NVMe и предотвращение OOM Killer

Развертывание стека локальные нейросети на vps ollama deepseek open webui на серверах с ограниченным объемом RAM (8–16 GB) неизбежно сталкивается с жесткими аппаратными лимитами. Главный источник аварийных остановок процессов здесь — не перегрузка vCPU, а Linux Out-Of-Memory (OOM) Killer. При исчерпании доступной физической памяти ядро принудительно завершает самый ресурсоемкий процесс через сигнал SIGKILL (код завершения 137). В 100% случаев этой жертвой становится бекенд инференса ollama_llama_server.

Механика взрывного потребления памяти: KV-кэш и переполнение контекста

Статический размер модели — лишь базовая часть потребления RAM. Квантованная модель DeepSeek-R1-Distill-Qwen-8B в формате GGUF (Q4_K_M) занимает в памяти фиксированные ~5.2 GB. Критический рост аллокации начинается в момент инференса при генерации и обработке контекста за счет механизма внимания (Self-Attention) и структуры KV-кэша (Key-Value Cache).

KV-кэш сохраняет тензоры ключей и значений для каждого токена во всех слоях трансформера, исключая их повторный пересчет. Объем памяти, требуемый для хранения KV-кэша, масштабируется строго линейно относительно длины контекста $L$:

$$S_{KV} = 2 \times n_{\text{layers}} \times n_{\text{kv_heads}} \times d_{\text{head}} \times L \times b$$

где: * $n_{\text{layers}}$ — количество слоев модели (для DeepSeek/Qwen-8B — 32); * $n_{\text{kv_heads}}$ — количество голов внимания для ключей и значений в архитектуре Grouped-Query Attention (GQA, для архитектуры 8B равно 8); * $d_{\text{head}}$ — размерность головы (128); * $L$ — длина контекстного окна в токенах; * $b$ — точность представления элементов (2 байта для FP16).

При значении $L = 2048$ токенов KV-кэш требует всего ~268 MB RAM. Однако если входящий промпт содержит длинный системный контекст, объемную RAG-выборку или многостраничный документ, контекст легко разрастается до 32 768 токенов ($L = 32768$). В этой точке размер KV-кэша взлетает до 4.29 GB только в буферах аллокатора.

Суммарная нагрузка: 5.2 GB (веса) + 4.3 GB (KV-кэш) + 1.2 GB (CUDA/ROCm/CPU runtime overhead) + 800 MB (контейнер Open WebUI на Python) = 11.5 GB RAM. На тарифе VPS с 8 GB оперативной памяти ядро немедленно уходит в аллокационный отказ через системный вызов brk/mmap, срабатывает алгоритм mm/oom_kill.c, и инференс падает.

Ограничение контекста через Modelfile (PARAMETER num_ctx 4096)

Полагаться на пользовательские настройки длины контекста в интерфейсе Open WebUI категорически нельзя: любой параллельный запрос или сброс системного промпта спровоцирует OOM. Лимит обязан быть жестко зафиксирован на уровне движка Ollama через кастомный Modelfile.

Создайте конфигурационный файл для квантованной модели DeepSeek с жестким ограничением контекстного окна в 4096 токенов, чего достаточно для большинства аналитических задач:

# /opt/models/Modelfile.deepseek-r1-4k
FROM deepseek-r1:8b

# Фиксация контекстного окна: 4096 токенов удерживают KV-кэш в пределах ~536 MB
PARAMETER num_ctx 4096

# Ограничение длины генерации для предотвращения зацикливания цепочек CoT (Chain-of-Thought)
PARAMETER num_predict 2048

# Привязка числа потоков инференса к доступным физическим ядрам vCPU
PARAMETER num_thread 4

# Контроль генерации
PARAMETER temperature 0.6
PARAMETER top_p 0.95

Скомпилируйте кастомную модель внутри Ollama:

ollama create deepseek-r1:8b-ctx4k -f /opt/models/Modelfile.deepseek-r1-4k

После этого в настройках Open WebUI выберите собранную модель deepseek-r1:8b-ctx4k. Теперь даже при попытке передать документ объемом 50 000 символов движок Ollama детерминированно выполнит транкацию входящих токенов до границы 4096, не выходя за пределы расчетного потолка RAM.

Настройка Swap-файла 8 GB на NVMe как демпфера стабильности

Swap на NVMe-накопителе ни при каких условиях не должен использоваться для активного инференса: пропускная способность шины PCIe 3.0/4.0 NVMe (2–7 GB/s) на порядки ниже пропускной способности оперативной памяти DDR4/DDR5 (25–60 GB/s). Попадание активных тензоров модели в swap приведет к деградации метрики latency p99 с сотен миллисекунд до десятков секунд на токен и вызовет взрывной рост I/O Wait.

Назначение swap-файла на сервере инференса — работать в качестве аварийного буфера (shock absorber). Он принимает в себя «холодные» страницы памяти: системные службы (systemd-journald, sshd), фоновые воркеры Open WebUI, неактивный код библиотек. Это освобождает физическую память под активные вычисления Ollama и предотвращает срабатывание OOM Killer при кратковременных спайках нагрузки.

Создание и монтирование файла подкачки размером 8 GB:

# Выделение непрерывного дискового пространства без фрагментации через fallocate
sudo fallocate -l 8G /swapfile

# Проверка: если файловая система не поддерживает fallocate (например, XFS/Btrfs с CoW),
# используйте dd:
# sudo dd if=/dev/zero of=/swapfile bs=1M count=8192 status=progress

# Защита прав доступа: чтение/запись разрешены строго root (права 0600)
sudo chmod 600 /swapfile

# Инициализация структуры swap-пространства
sudo mkswap /swapfile

# Немедленная активация в ядре
sudo swapon /swapfile

# Добавление правила монтирования в fstab для сохранения после перезагрузки
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

Проверьте статус подключения через swapon --show и free -h. В выводе должен отображаться доступный раздел Swap размером 8.0G.

Тюнинг параметров ядра Linux через sysctl

Стандартные настройки управления виртуальной памятью Linux в дистрибутивах Ubuntu/Debian оптимизированы под универсальные нагрузки, но губительны для инференса LLM. Без ручной коррекции ядро начнет выгружать страницы Ollama в swap задолго до реального исчерпания RAM.

Создайте конфигурационный файл тюнинга памяти:

sudo nano /etc/sysctl.d/99-llm-memory.conf

Внесите следующий набор низкоуровневых директив:

# Агрессивность сброса страниц в swap (по умолчанию 60).
# Значение 10 предписывает ядру использовать swap только при жестком давлении на RAM (>90%),
# удерживая рабочие страницы модели в физической памяти.
vm.swappiness = 10

# Скорость освобождения кэшей дескрипторов файлов (dentry/inode).
# Значение 50 (вместо 100) удерживает файловые кэши в памяти, снижая I/O задержки при чтении весов.
vm.vfs_cache_pressure = 50

# Политика overcommit памяти:
# 1 = Ядро всегда разрешает виртуальную аллокацию (heuristic disabled).
# Необходимо для библиотек типа llama.cpp/PyTorch, резервирующих большие адресные пространства через mmap.
vm.overcommit_memory = 1

# Процент грязных страниц (dirty memory) для фоновой записи на диск.
# Снижение значений предотвращает накопление сотен мегабайт несинхронизированных данных,
# сброс которых на диск вызывает микрофризы и всплески latency p99.
vm.dirty_background_ratio = 5
vm.dirty_ratio = 10

Примените изменения в runtime без перезагрузки сервера:

sudo sysctl --system

Защита процесса Ollama через oom_score_adj

Чтобы гарантировать, что в нештатной ситуации ядро сначала убьет вспомогательные веб-интерфейсы или системные утилиты, но сохранит работающий экземпляр модели, скорректируйте коэффициент OOM Score для сервиса ollama.

Создайте drop-in конфигурацию для systemd-юнита:

sudo mkdir -p /etc/systemd/system/ollama.service.d/
sudo tee /etc/systemd/system/ollama.service.d/override.conf << 'EOF'
[Service]
# Снижение приоритета процесса для OOM Killer (диапазон от -1000 до 1000).
# Отрицательное значение уменьшает badness score, защищая процесс от SIGKILL.
OOMScoreAdjust=-500

# Лимиты файловых дескрипторов и блокировки памяти
LimitNOFILE=65535
LimitMEMLOCK=infinity
EOF

sudo systemctl daemon-reload
sudo systemctl restart ollama

Мониторинг стабильности подсистемы памяти выполняется утилитой vmstat 1 (обращайте внимание на столбцы si (swap in) и so (swap out): в штатном режиме они должны быть равны нулю) и командой cat /proc/$(pgrep ollama)/oom_score, которая подтвердит защищенный статус демона нейросети.

Часто задаваемые вопросы (FAQ)

Хватит ли 4 GB RAM для запуска локальной нейросети?

Для моделей 7B/8B этого мало (будет OOM Killer), но на 4 GB RAM отлично работают компактные модели: Llama 3.2 1B/3B, Qwen 2.5 1.5B/3B или Phi-3 Mini.

Можно ли подключить внешние программы к Ollama на VPS?

Да, Ollama предоставляет 100% совместимый с OpenAI API эндпоинт (/v1/chat/completions), что позволяет подключать любые плагины, библиотеки LangChain, LlamaIndex и сторонние клиенты.

Не заблокирует ли хостер за 100% утилизацию CPU?

Выбирайте хостинг с честными выделенными vCPU на KVM без ограничений по постоянной нагрузке (fair-use политика) и избегайте хостингов с жестким троттлингом процессора.

Сколько пользователей могут одновременно общаться с моделью на одном VPS?

На CPU VPS генерация последовательна. При 1–3 одновременных запросах скорость остается комфортной, для десятков одновременных пользователей требуется очередь или сервер с GPU.