Почему Claude Code убивает вашу память с локальными моделями
Вы скачали свежую модель с Hugging Face, запустили через llama.cpp, подключили к Claude Code. Все работает. Пока не отправляете файл побольше. Или не пытаетесь обработать длинный чат. И тогда - бац - Out of Memory. Модель падает, VRAM переполнена, а вы смотрите на ошибку и думаете: "Но я же указал контекст в 4K токенов!"
Дело в том, что Claude Code по умолчанию пытается зарезервировать память под максимально возможный контекст. Не под тот, что указали вы. А под тот, что зашит в модели. И если у вас модель с 32K контекстом, а видеокарты на 8 ГБ - вы обречены на OOM еще до первой строки кода.
Ошибка не в вашей карте. Ошибка в том, как Claude Code общается с сервером llama.cpp. Он не спрашивает "сколько можешь". Он говорит "давай все". И получает все - вместе с крахом.
Что на самом деле происходит под капотом
Когда вы запускаете локальную модель через Claude Code, происходит диалог между клиентом и сервером. Claude Code отправляет запрос на /v1/models. Сервер llama.cpp возвращает параметры модели. Включая max_tokens. И вот здесь начинается магия (точнее, ее отсутствие).
Claude Code берет это значение как священную истину. И резервирует память под него. Не важно, что вы запустили модель с флагом --ctx-size 4096. Если в модели зашито 32768 - Claude Code будет готовиться к 32K. И упадет, потому что физически не поместится.
1 Диагностика: находим реальную проблему
Прежде чем лечить, нужно понять, что болит. Откройте терминал и выполните:
curl -X GET http://localhost:8080/v1/models
Вы увидите что-то вроде:
{
"object": "list",
"data": [
{
"id": "llama-3.2-3b",
"object": "model",
"max_tokens": 131072,
"owned_by": "llama.cpp"
}
]
}
Видите этот max_tokens: 131072? Это и есть корень зла. Claude Code увидит 128K контекста и попытается подготовиться к работе с ним. На 8 ГБ VRAM это невозможно физически. Модель просто не влезет.
Решение: принудительная компрессия контекста
Есть два пути: правильный и простой. Правильный - патчить llama.cpp, чтобы он возвращал реальное значение контекста. Простой - обмануть Claude Code, заставив его думать, что контекста меньше.
Мы пойдем простым путем. Потому что он работает прямо сейчас, без перекомпиляции.
2 Прокси-сервер: перехватываем и подменяем
Создадим простой Python#endif скрипт, который будет сидеть между Claude Code и llama.cpp. Его задача - перехватывать запрос к /v1/models и подменять max_tokens на наше значение.
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
import urllib.request
import urllib.parse
class ProxyHandler(BaseHTTPRequestHandler):
llama_server = "http://localhost:8080"
max_context = 4096 # Ваше значение здесь
def do_GET(self):
if self.path == "/v1/models":
# Перехватываем запрос к моделям
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
# Получаем реальный ответ от llama.cpp
with urllib.request.urlopen(f"{self.llama_server}{self.path}") as response:
data = json.load(response)
# Подменяем max_tokens
for model in data.get("data", []):
model["max_tokens"] = self.max_context
self.wfile.write(json.dumps(data).encode())
else:
# Проксируем все остальные запросы
with urllib.request.urlopen(f"{self.llama_server}{self.path}") as response:
self.send_response(response.status)
for header, value in response.headers.items():
self.send_header(header, value)
self.end_headers()
self.wfile.write(response.read())
def do_POST(self):
# Проксируем POST-запросы
content_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(content_length)
req = urllib.request.Request(
f"{self.llama_server}{self.path}",
data=post_data,
headers={key: value for key, value in self.headers.items() if key != 'Host'},
method='POST'
)
with urllib.request.urlopen(req) as response:
self.send_response(response.status)
for header, value in response.headers.items():
self.send_header(header, value)
self.end_headers()
self.wfile.write(response.read())
if __name__ == "__main__":
server = HTTPServer(('localhost', 8081), ProxyHandler)
print("Прокси запущен на http://localhost:8081")
print(f"Подменяем контекст на {ProxyHandler.max_context} токенов")
server.serve_forever()
Сохраните как proxy_server.py и запустите:
python proxy_server.py
Теперь в Claude Code указываете адрес http://localhost:8081 вместо 8080. И он увидит ваш ограниченный контекст. И перестанет пытаться зарезервировать память под 128K.
3 Настройка llama.cpp: режем по живому
Прокси - это костыль. Хороший, рабочий, но костыль. Настоящее решение - правильно запустить llama.cpp с самого начала.
Запускайте сервер так:
./server -m models/llama-3.2-3b.Q4_K_M.gguf \
--ctx-size 4096 \
--batch-size 512 \
--ubatch-size 512 \
--parallel 1 \
--cont-batching \
--no-mmap \
--mlock \
--host 0.0.0.0 \
--port 8080
Ключевые флаги:
- --ctx-size 4096 - реальный размер контекста. Не обещайте больше, чем можете отдать
- --batch-size 512 - размер батча. Меньше = меньше пикового потребления памяти
- --ubatch-size 512 - размер микро-батча. Критично для больших контекстов
- --no-mmap --mlock - загружаем модель полностью в RAM. Медленнее запуск, но стабильнее работа
Не верьте мифу про --no-mmap. "Но он же медленнее!" Да, на 5-10%. Зато не будет внезапных OOM при обращении к странице памяти, которую система решила выгрузить на диск.
Почему это работает (и когда не работает)
Принудительное ограничение контекста через прокси или правильные флаги llama.cpp решает 80% проблем с OOM. Оставшиеся 20% - это когда вы действительно пытаетесь обработать больше токенов, чем помещается в память.
Допустим, у вас 8 ГБ VRAM. Модель 3B параметров в Q4_K_M занимает ~2 ГБ. На контекст остается ~6 ГБ. При 4K контекста - все ок. При 8K - уже на грани. При 16K - гарантированный OOM.
| Размер модели | Квантование | Память модели | Макс. контекст на 8 ГБ |
|---|---|---|---|
| 3B | Q4_K_M | ~2 ГБ | 12-16K |
| 7B | Q4_K_M | ~4 ГБ | 8-10K |
| 13B | Q4_K_M | ~7 ГБ | 2-4K |
Видите проблему? С моделью 13B на 8 ГБ VRAM вы физически не можете иметь контекст больше 4K. И если Claude Code попытается зарезервировать под 32K - он упадет сразу при запуске.
Экстренные меры: когда память уже на нуле
Бывает: модель запущена, работает, и вдруг - OOM. Чаще всего это не внезапно. Это накопление. Каждый запрос оставляет что-то в памяти. Кэш внимания. Промежуточные результаты. И через 20-30 запросов память переполняется.
Решение - принудительная очистка. В llama.cpp есть флаг --no-kv-offload. Он отключает сохранение кэша внимания на GPU. Да, это замедляет генерацию при длинных контекстах. Но сохраняет память.
Еще лучше - использовать техники компрессии вроде DroPE. Они радикально уменьшают потребление памяти при работе с длинными контекстами.
4 Мониторинг: видеть проблему до того, как она ударит
Не ждите OOM. Отслеживайте потребление памяти в реальном времени:
# Для NVIDIA
watch -n 1 nvidia-smi
# Или с детализацией по процессам
nvidia-smi --query-compute-apps=pid,process_name,used_memory --format=csv -l 1
Увидели, что память стабильно растет с каждым запросом? Это утечка. Или накопление кэша. Остановите сервер. Перезапустите с --no-kv-offload.
Частые ошибки (и как их не делать)
Ошибка 1: Доверять значениям по умолчанию. "llama.cpp сам все настроит" - нет, не настроит. Он настроит для максимальной производительности. А не для стабильности на вашем железе.
Ошибка 2: Запускать с --ctx-size 32768 на карте 8 ГБ. Математика простая: модель + контекст + overhead должны помещаться в VRAM. Если нет - вы получите OOM при первом же длинном запросе.
Ошибка 3: Игнорировать --batch-size и --ubatch-size. Эти параметры контролируют, сколько токенов обрабатывается за раз. Большие значения = быстрее, но больше пиковое потребление памяти.
Если вы уже наступили на эти грабли, посмотрите практический гайд по избеганию ошибок. Там разобраны еще десятки подобных случаев.
Альтернативы: когда Claude Code слишком прожорлив
Иногда проблема не в настройках, а в архитектуре. Claude Code создан для облачных моделей с гигабайтами свободной памяти. Для локального использования есть специализированные инструменты.
Попробуйте LMStudio-Ollama модификацию VS Code. Она изначально заточена под работу с ограниченными ресурсами. Или Open WebUI для браузерного интерфейса.
Но если нужен именно Claude Code - наш прокси-метод работает. Проверено на моделях до 13B параметров.
Что в итоге
Проблема OOM в Claude Code с локальными моделями - это не баг. Это недоработка коммуникации. Claude Code верит тому, что говорит сервер. А сервер говорит "я могу все", даже если на деле может только часть.
Решений три:
- Прокси-сервер для подмены max_tokens (быстро, грязно, работает)
- Правильные флаги запуска llama.cpp (правильно, требует понимания)
- Специализированные инструменты вместо Claude Code (радикально, но эффективно)
Мой выбор - второй вариант с элементами первого. Запускайте llama.cpp с адекватными --ctx-size, --batch-size. И если нужно - добавьте прокси для подстраховки.
И запомните: память не резиновая. Даже с самыми оптимизированными локальными моделями физические ограничения железа никуда не деваются. Работайте в их рамках. И не пытайтесь впихнуть невпихуемое.
P.S. Если после всех настроек модель все равно падает - возможно, дело не в контексте. Проверьте, не зацикливается ли она на тул-коллах. Или не пытается ли обработать бинарный файл как текст. Но это уже совсем другая история.