Модуль 4.2 · Урок 1
Локальная инфраструктура
Содержание
- Введение
- Почему локальный деплой?
- Основные преимущества
- Особенности для России
- llama.cpp и llama-server
- Что это?
- Установка на Linux
- Скачивание модели в формате GGUF
- Запуск llama-server с OpenAI-совместимым API
- Тестирование API
- Горячая замена моделей
- Оптимизация для разных железок
- Ollama — альтернатива
- Что это?
- Установка
- Базовое использование
- API Ollama
- Modelfile — кастомные конфигурации
- Сравнение llama-server vs Ollama
- vLLM — для production с высокой пропускной способностью
- Особенности
- Установка
- Запуск сервера
- API вызовы (совместимо с OpenAI)
- Выбор модели для разных задач субагентов
- Архитектура многоагентной системы
- Рекомендуемые модели и их параметры
- Таблица выбора модели
- Сетевая архитектура: Host + VM
- Диаграмма
- Настройка SSH туннеля для сетевого доступа
- Docker Compose для мультимодельного сервиса
- Nginx для маршрутизации моделей
- Конфигурация nginx
- Практический пример: Полная настройка
- Структура проекта
- Установка с нуля (Linux/macOS)
- Агент с маршрутизацией моделей (Python)
- Мониторинг и отладка
- Оптимизация производительности
- Параметры llama-server
- Квантизация моделей
- Батчинг запросов
- Чек-лист для продакшена
- Заключение
- Полезные ссылки
Введение
Вы разработали агентов, которые умеют решать сложные задачи, но полагаться на облачные API недостаточно. В этом уроке мы рассмотрим, как организовать полностью локальное окружение для запуска открытых LLM моделей с возможностью:
- Использовать разные модели для разных субагентов
- Работать без интернета
- Соблюдать конфиденциальность данных
- Избежать зависимости от облачных провайдеров
Сценарий:
graph TD
A[Пользователь] -->|Запрос| B[Python-агент]
B -->|HTTP API| C[llama-server / Ollama / vLLM]
C -->|Загрузка| D[Файл модели GGUF]
D -->|Инференс| C
C -->|Ответ| B
B -->|Результат| A
style A fill:#f8fafc,stroke:#4f46e5,stroke-width:2px
style B fill:#f8fafc,stroke:#2563eb,stroke-width:2px
style C fill:#f8fafc,stroke:#059669,stroke-width:2px
style D fill:#f8fafc,stroke:#64748b,stroke-width:2px
Почему локальный деплой?
Основные преимущества
| Аспект | Облако | Локально |
|---|---|---|
| Стоимость | $0.01-0.10 за 1K токенов | Один раз на оборудование |
| Конфиденциальность | Данные на серверах провайдера | Полная изоляция |
| Интернет | Обязателен | Не требуется |
| Контроль | Привязаны к условиям API | Полная свобода |
| Скорость | Сетевые задержки | Минимальные задержки |
Особенности для России
- Санкции: OpenAI, Claude API недоступны напрямую
- Суверенитет данных: Чувствительные данные остаются на вашей машине
- Надёжность: Не зависите от перебоев в интернете
llama.cpp и llama-server
Что это?
llama.cpp — высокоэффективная C++ реализация Llama для запуска моделей на CPU/GPU. llama-server — встроенный HTTP API.
Установка на Linux
# Клонируем репозиторий
git clone https://github.com/ggerganov/llama.cpp.git
cd llama.cpp
# Компилируем с поддержкой GPU (CUDA для NVIDIA)
mkdir build && cd build
cmake .. -DGGML_CUDA=ON
make -j$(nproc)
# Или без GPU (только CPU):
# cmake ..
# make -j$(nproc)
Скачивание модели в формате GGUF
Модели в формате GGUF оптимизированы для llama.cpp (меньший размер, быстрая загрузка).
# Скачиваем модель Qwen2.5-Coder (компактная, отличная для кодинга)
huggingface-cli download Qwen/Qwen2.5-Coder-7B-Instruct-GGUF \
qwen2.5-coder-7b-instruct-q4_k_m.gguf --local-dir ./models
# Или используем curl:
curl -L -o models/qwen-7b.gguf \
https://huggingface.co/Qwen/Qwen2.5-Coder-7B-Instruct-GGUF/resolve/main/qwen2.5-coder-7b-instruct-q4_k_m.gguf
Запуск llama-server с OpenAI-совместимым API
# Базовый запуск
cd ~/llama.cpp/build/bin
./llama-server -m ../models/qwen-7b.gguf \
--port 8080 \
--n-gpu-layers 33 \
--ctx-size 4096
# Параметры:
# --port 8080 - порт для API
# --n-gpu-layers 33 - сколько слоёв отправить на GPU
# --ctx-size 4096 - размер контекста
# -ngl - alias для --n-gpu-layers
Тестирование API
Когда сервер запущен, можно делать запросы как к OpenAI:
# Обычное завершение текста
curl -X POST http://localhost:8080/v1/completions \
-H "Content-Type: application/json" \
-d '{
"prompt": "Напиши функцию на Python для сортировки:",
"max_tokens": 100,
"temperature": 0.7
}'
# Chat API (как ChatGPT)
curl -X POST http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "local",
"messages": [
{"role": "user", "content": "Привет! Что такое рекурсия?"}
],
"temperature": 0.7,
"max_tokens": 200
}'
Горячая замена моделей
Преимущество llama-server — можно менять модели без перезагрузки сервера.
# Запускаем сервер с первой моделью
./llama-server -m models/qwen-7b.gguf --port 8080
# В другом терминале загружаем другую модель (она стоит в очереди)
# llama-server автоматически выгружает старую из памяти
# Или используем slot management (если включены слоты):
curl -X POST http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "models/mistral-7b.gguf",
"messages": [{"role": "user", "content": "Hello"}],
"max_tokens": 100
}'
Оптимизация для разных железок
NVIDIA GPU (CUDA):
cmake .. -DGGML_CUDA=ON -DCUDA_ARCH=native
make -j$(nproc)
AMD GPU (ROCm):
cmake .. -DGGML_HIPBLAS=ON
make -j$(nproc)
Intel GPU:
cmake .. -DGGML_ONEAPI=ON
make -j$(nproc)
macOS (Apple Silicon):
cmake .. -DGGML_METAL=ON
make -j$(nproc)
Ollama — альтернатива
Что это?
Ollama — более удобный интерфейс для llama.cpp с встроенной системой управления моделями.
Установка
# На Linux
curl -fsSL https://ollama.com/install.sh | sh
# На macOS
# Скачать из https://ollama.com
# На Windows
# Скачать .exe
Базовое использование
# Скачать и запустить модель
ollama run qwen2.5-coder
# Ollama автоматически:
# 1. Скачает модель
# 2. Запустит локальный сервер (по умолчанию :11434)
# 3. Откроет интерактивный чат
# Запустить только сервер (без интерактива)
ollama serve
# Все модели хранятся в ~/.ollama/models/
API Ollama
# Chat API (совместимо с OpenAI)
curl -X POST http://localhost:11434/api/chat \
-H "Content-Type: application/json" \
-d '{
"model": "qwen2.5-coder",
"messages": [
{"role": "user", "content": "Как работает рекурсия?"}
],
"stream": false
}'
# Обычное завершение (generate)
curl -X POST http://localhost:11434/api/generate \
-H "Content-Type: application/json" \
-d '{
"model": "qwen2.5-coder",
"prompt": "def fibonacci(n):",
"stream": false
}'
Modelfile — кастомные конфигурации
Создаём Modelfile для своего кастомного агента:
Modelfile -- кастомная конфигурация модели Ollama
Запуск:
ollama create my-agent -f Modelfile
ollama run my-agent
Сравнение llama-server vs Ollama
| Параметр | llama.cpp | Ollama |
|---|---|---|
| Установка | Нужно компилировать | Один скрипт |
| API | OpenAI-совместимый | Собственный + OpenAI |
| Управление моделями | Ручное | Автоматическое |
| Производительность | Чуть выше | Немного ниже |
| Кастомизация | Больше контроля | Меньше возможностей |
| Идеален для | Production | Разработка |
vLLM — для production с высокой пропускной способностью
Особенности
- PagedAttention — эффективная работа с памятью
- LoRA поддержка — многие адаптеры одновременно
- Batch обработка — сотни запросов в очереди
- Streaming — ответы в реальном времени
Установка
pip install vllm
# С поддержкой CUDA
pip install vllm[cuda12]
Запуск сервера
python -m vllm.entrypoints.openai.api_server \
--model qwen/Qwen2.5-7B-Instruct \
--tensor-parallel-size 2 \
--gpu-memory-utilization 0.9 \
--port 8000
# Параметры:
# --tensor-parallel-size - сколько GPU использовать
# --gpu-memory-utilization - сколько памяти GPU занять (0-1)
API вызовы (совместимо с OpenAI)
curl -X POST http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "qwen/Qwen2.5-7B-Instruct",
"messages": [
{"role": "user", "content": "Объясни асинхронность в Python"}
],
"temperature": 0.7,
"max_tokens": 150
}'
Выбор модели для разных задач субагентов
Архитектура многоагентной системы
Главный агент (координатор)
+-- Классификатор задач (1-3B модель)
+-- Агент кодирования (7-13B модель)
+-- Агент анализа (30B модель)
+-- Агент планирования (7B модель)
Рекомендуемые модели и их параметры
Маленькие модели (1-3B) — Классификация, маршрутизация
Задачи:
- Определить тип запроса пользователя
- Выбрать нужного агента
- Быстрое предварительное заключение
Рекомендуемые:
- Qwen2.5-1.5B-Instruct-GGUF (~700MB)
- Phi-3-mini-4k-instruct (~2GB)
- TinyLlama-1.1B (~700MB)
Параметры запуска:
./llama-server -m models/qwen-1.5b.gguf \
--port 8081 \
--n-gpu-layers 20 \
--ctx-size 2048
Средние модели (7-13B) — Кодирование, рассуждение
Задачи:
- Генерация кода
- Анализ текста
- Сложные рассуждения
Рекомендуемые:
- Qwen2.5-Coder-7B-Instruct-GGUF (~5GB)
- DeepSeek-V3 (~5GB)
- Llama-4-8B-Instruct (~6GB)
- Mistral-7B-Instruct-v0.3 (~5GB)
Параметры запуска:
./llama-server -m models/qwen-7b.gguf \
--port 8082 \
--n-gpu-layers 33 \
--ctx-size 4096 \
--batch-size 32
Большие модели (30-70B) — Сложные задачи
Задачи:
- Глубокий анализ
- Сложное планирование
- Многошаговые рассуждения
- Перевод и трансформация
Рекомендуемые:
- Qwen2.5-32B-Instruct-GGUF (~20GB)
- DeepSeek-V3-MoE (~40GB)
- Llama-4-70B-Instruct (~45GB)
- Mistral Large (~48GB)
Параметры запуска:
./llama-server -m models/qwen-32b.gguf \
--port 8083 \
--n-gpu-layers 60 \
--ctx-size 8192 \
--batch-size 16
Таблица выбора модели
| Задача | Модель | Размер | Порт | RAM |
|---|---|---|---|---|
| Маршрутизация | Qwen2.5-1.5B | 700MB | 8081 | 2GB |
| Кодирование | Qwen2.5-Coder-7B | 5GB | 8082 | 8GB |
| Анализ | Qwen2.5-7B-Instruct | 5GB | 8082 | 8GB |
| Планирование | Llama-4-8B | 6GB | 8082 | 8GB |
| Сложное рассуждение | Qwen2.5-32B | 20GB | 8083 | 24GB |
Сетевая архитектура: Host + VM
Диаграмма
+-----------------------------------------------------+
| Хост-машина |
| (GPU, высокий CPU) |
| |
| +----------------------------------------------+ |
| | llama-server (несколько портов) | |
| | | |
| | :8081 -> Qwen2.5-1.5B (маршрутизация) | |
| | :8082 -> Qwen2.5-7B (основной агент) | |
| | :8083 -> Qwen2.5-32B (сложные задачи) | |
| +----------------------------------------------+ |
+-----------------------------------------------------+
^
HTTP API (localhost:808x)
v
+-----------------------------------------------------+
| VM (Ubuntu 22.04) |
| (Агент, низкий CPU, без GPU) |
| |
| +----------------------------------------------+ |
| | Python Агент (используется OpenAI SDK) | |
| | | |
| | # Роутер: выбирает модель | |
| | # Выполнение: отправляет в llama-server | |
| | # Результат: получает ответ | |
| +----------------------------------------------+ |
+-----------------------------------------------------+
Настройка SSH туннеля для сетевого доступа
Если VM нужна сетевая изоляция:
# На VM: подключаемся к хосту через SSH туннель
ssh -L 8081:localhost:8081 \
-L 8082:localhost:8082 \
-L 8083:localhost:8083 \
user@host-machine
# Теперь на VM может использовать:
# http://localhost:8081 (как если бы это был localhost)
Docker Compose для мультимодельного сервиса
Создаём docker-compose.yml:
Docker Compose для мультимодельного сервиса
Аналогично определяются сервисы agent (средняя модель на порту 8082) и reasoning (большая модель на порту 8083). Каждый сервис получает свой GPU через CUDA_VISIBLE_DEVICES и блок deploy.resources с резервацией GPU. Все сервисы объединены в сеть ai-agents.
Запуск:
docker compose up -d
docker compose ps
Nginx для маршрутизации моделей
Если нужен единый entry point с маршрутизацией:
graph LR
Client[Клиент] -->|:8080| LB[Nginx<br/>балансировщик]
LB -->|/v1/router/| S1[llama-server<br/>:8081<br/>Qwen 1.5B]
LB -->|/v1/agent/| S2[llama-server<br/>:8082<br/>Qwen 7B]
LB -->|/v1/reasoning/| S3[llama-server<br/>:8083<br/>Qwen 32B]
style Client fill:#f8fafc,stroke:#1e293b,stroke-width:2px
style LB fill:#f8fafc,stroke:#4f46e5,stroke-width:2px
style S1 fill:#f8fafc,stroke:#059669,stroke-width:2px
style S2 fill:#f8fafc,stroke:#2563eb,stroke-width:2px
style S3 fill:#f8fafc,stroke:#dc2626,stroke-width:2px
Конфигурация nginx
nginx.conf:
upstream router {
server localhost:8081;
}
upstream agent {
server localhost:8082;
}
upstream reasoning {
server localhost:8083;
}
server {
listen 8080;
server_name _;
# Маршрутизатор — для определения типа задачи
location /v1/router/ {
proxy_pass http://router;
proxy_set_header Content-Type application/json;
}
# Основной агент — для большинства задач
location /v1/agent/ {
proxy_pass http://agent;
rewrite ^/v1/agent/(.*)$ /v1/$1 break;
}
# Сложные задачи
location /v1/reasoning/ {
proxy_pass http://reasoning;
rewrite ^/v1/reasoning/(.*)$ /v1/$1 break;
}
# Дефолтный маршрут
location /v1/completions {
proxy_pass http://agent;
}
location /v1/chat/completions {
proxy_pass http://agent;
}
}
Запуск:
nginx -c /path/to/nginx.conf
Практический пример: Полная настройка
Структура проекта
ai-agents-local/
+-- docker-compose.yml # Мультимодельный сервис
+-- nginx.conf # Маршрутизация
+-- agent.py # Главный агент
+-- subrouters/
| +-- task_classifier.py # Маршрутизатор задач
| +-- code_agent.py # Агент кодирования
| +-- analysis_agent.py # Агент анализа
+-- models/ # GGUF модели (скачанные)
| +-- qwen-1.5b.gguf
| +-- qwen-7b.gguf
| +-- qwen-32b.gguf
+-- README.md
Установка с нуля (Linux/macOS)
#!/bin/bash
# setup.sh
# 1. Клонируем llama.cpp
git clone https://github.com/ggerganov/llama.cpp.git
cd llama.cpp
# 2. Компилируем с GPU поддержкой
mkdir build && cd build
cmake .. -DGGML_CUDA=ON
make -j$(nproc)
cd ../..
# 3. Создаём директорию моделей
mkdir -p models
# 4. Скачиваем модели
echo "Загружаем модели (может занять время)..."
huggingface-cli download Qwen/Qwen2.5-1.5B-Instruct-GGUF \
qwen2.5-1.5b-instruct-q4_k_m.gguf --local-dir ./models
huggingface-cli download Qwen/Qwen2.5-Coder-7B-Instruct-GGUF \
qwen2.5-coder-7b-instruct-q4_k_m.gguf --local-dir ./models
huggingface-cli download Qwen/Qwen2.5-32B-Instruct-GGUF \
qwen2.5-32b-instruct-q4_k_m.gguf --local-dir ./models
# 5. Запускаем сервисы
docker compose up -d
echo "Готово! Сервисы запущены на портах 8081-8083"
Запуск:
chmod +x setup.sh
./setup.sh
Агент с маршрутизацией моделей (Python)
agent.py:
import openai
import json
from typing import Literal
# Клиент openai v1.x для локального сервера
from openai import OpenAI
# Модели на разных портах
MODELS = {
"router": "http://localhost:8081/v1",
"agent": "http://localhost:8082/v1",
"reasoning": "http://localhost:8083/v1",
}
def get_client(base_url: str) -> openai.OpenAI:
"""Создаём клиент для конкретного сервера."""
return openai.OpenAI(api_key="sk-local", base_url=base_url)
def classify_task(user_input: str) -> Literal["routing", "coding", "reasoning"]:
"""
Определяем тип задачи маленькой моделью.
"""
client = get_client(MODELS["router"])
response = client.chat.completions.create(
model="local",
messages=[
{
"role": "system",
"content": """Определи тип задачи пользователя.
Ответь одним словом:
- routing: если нужно определить что-то простое
- coding: если нужно написать/изменить код
- reasoning: если нужны сложные рассуждения"""
},
{"role": "user", "content": user_input}
],
temperature=0.1,
max_tokens=10
)
task_type = response.choices[0].message.content.strip().lower()
return task_type if task_type in ["routing", "coding", "reasoning"] else "routing"
def execute_agent(task_type: str, user_input: str) -> str:
"""
Выполняем задачу нужной моделью.
"""
if task_type == "coding":
base_url = MODELS["agent"]
system_prompt = "Ты эксперт по программированию. Пиши чистый, эффективный код."
elif task_type == "reasoning":
base_url = MODELS["reasoning"]
system_prompt = "Ты логик и аналитик. Разбирай сложные концепции пошагово."
else:
base_url = MODELS["agent"]
system_prompt = "Ты полезный ассистент."
client = get_client(base_url)
response = client.chat.completions.create(
model="local",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_input}
],
temperature=0.7,
max_tokens=500
)
return response.choices[0].message.content
def main():
"""
Главная функция агента.
"""
user_input = input("Ваш запрос: ")
print("\nАнализирую запрос...")
task_type = classify_task(user_input)
print(f"Тип задачи: {task_type}")
print(f"\nВыполняю ({task_type})...")
result = execute_agent(task_type, user_input)
print(f"\nРезультат:\n{result}")
if __name__ == "__main__":
main()
Запуск:
pip install openai
python agent.py
Пример использования:
Ваш запрос: Напиши функцию для сортировки массива
Анализирую запрос...
Тип задачи: coding
Выполняю (coding)...
Результат:
def sort_array(arr):
"""Быстрая сортировка O(n log n)"""
if len(arr) <= 1:
return arr
pivot = arr[len(arr) // 2]
left = [x for x in arr if x < pivot]
middle = [x for x in arr if x == pivot]
right = [x for x in arr if x > pivot]
return sort_array(left) + middle + sort_array(right)
Мониторинг и отладка
# Проверка статуса сервисов
docker compose ps
# Логи конкретного сервиса
docker compose logs -f agent
# Проверка API напрямую
curl -X POST http://localhost:8082/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "local",
"messages": [{"role": "user", "content": "Привет!"}]
}' | jq .
# Использование памяти
nvidia-smi
# Скорость обработки (benchmark)
time curl -s http://localhost:8082/v1/chat/completions \
-d '{"model":"local","messages":[{"role":"user","content":"test"}]}' | jq .
Оптимизация производительности
Параметры llama-server
# Для CPU (медленнее, но универсально):
./llama-server -m model.gguf \
--port 8080 \
--threads $(nproc) # все ядра
# Для GPU (быстрее):
./llama-server -m model.gguf \
--port 8080 \
--n-gpu-layers 33 # больше слоёв на GPU
--batch-size 64 # параллельная обработка
# Для низкой памяти:
./llama-server -m model.gguf \
--port 8080 \
--n-gpu-layers 10 # меньше на GPU
--batch-size 8 # меньше параллелизма
--ctx-size 1024 # меньше контекст
Квантизация моделей
Используйте GGUF модели с квантизацией:
graph LR
FP16[FP16<br/>16 бит] --> Q8[INT8<br/>8 бит] --> Q4[INT4<br/>4 бита]
FP16 -.->|Качество: максимум| Q_FP16[Память: 14 GB<br/>Скорость: низкая]
Q8 -.->|Качество: высокое| Q_Q8[Память: 7 GB<br/>Скорость: средняя]
Q4 -.->|Качество: хорошее| Q_Q4[Память: 4 GB<br/>Скорость: высокая]
style FP16 fill:#fef2f2,stroke:#dc2626,stroke-width:2px
style Q8 fill:#fefce8,stroke:#ca8a04,stroke-width:2px
style Q4 fill:#f0fdf4,stroke:#059669,stroke-width:2px
style Q_FP16 fill:#f8fafc,stroke:#e2e8f0,stroke-width:1px
style Q_Q8 fill:#f8fafc,stroke:#e2e8f0,stroke-width:1px
style Q_Q4 fill:#f8fafc,stroke:#e2e8f0,stroke-width:1px
Обозначения уровней квантизации:
| Уровень | Биты | Назначение |
|---|---|---|
q3_k_m | ~3 | Маленькие, быстрые, низкое качество |
q4_k_m | ~4 | Баланс размера и качества |
q5_k_m | ~5 | Хорошее качество |
q8_0 | ~8 | Почти оригинал |
fp16 | 16 | Оригинальная точность |
Батчинг запросов
Отправляйте несколько запросов одновременно:
import concurrent.futures
def batch_requests(queries):
client = openai.OpenAI(api_key="sk-local", base_url="http://localhost:8080/v1")
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
futures = [
executor.submit(client.chat.completions.create,
model="local",
messages=[{"role": "user", "content": q}]
)
for q in queries
]
return [f.result() for f in futures]
# Использование
queries = ["Что такое AI?", "Как работает ML?", "Что такое агент?"]
results = batch_requests(queries)
Чек-лист для продакшена
- GPU правильно настроена (nvidia-smi показывает активность)
- Модели скачаны и в GGUF формате
- llama-server/Ollama запущены на разных портах
- Nginx маршрутизирует запросы корректно
- Agent код обрабатывает ошибки сети
- Логирование настроено (какие модели использовались)
- Backup моделей сделан
- Memory limits установлены в Docker
- API кэширование включено (Redis опционально)
- Мониторинг активирован (grafana/prometheus)
Заключение
Вы теперь знаете, как:
- Запустить локальные модели с llama.cpp/Ollama
- Менять модели для разных субагентов
- Организовать многомодельную инфраструктуру
- Маршрутизировать запросы через nginx
- Оптимизировать память и производительность
Следующий шаг: Интегрируйте это в вашу систему агентов из предыдущих уроков.
Полезные ссылки
- llama.cpp: https://github.com/ggerganov/llama.cpp
- Ollama: https://ollama.com
- vLLM: https://github.com/vllm-project/vllm
- Hugging Face GGUF модели: https://huggingface.co/models?search=gguf
- Qwen модели: https://huggingface.co/Qwen
- DeepSeek модели: https://huggingface.co/deepseek-ai