Владельцы Mac Studio с 96 ГБ объединённой памяти хорошо знают этот фокус: вы скачиваете Qwen3.5-122B, загружаете контекст на 48k, жмёте «Enter» — и вместо быстрого кодинга получаете либо чёрный экран Activity Monitor с красной полосой давления памяти, либо вежливый OOM, либо поток бессмыслицы после второго ответа. Я сам через это прошёл, перепробовал все квантования от AesSedai до CatalystSec, менял стеки — пока не разобрался с тремя конкретными багами, которые убивают комфорт. Они не в модели — они в стыке qMLX и Mac OS. И их можно победить.
Важно: Все исправления актуальны для стека qMLX и последних версий MLX (на 13 июля 2026). Если вы используете llama.cpp или другое решение — часть фиксов не подойдёт, но общий подход останется тем же.
Три кита, на которых держится комфорт
Первый баг — OOM при попытке загрузить контекст даже на 64k, хотя 96 ГБ физически есть. Второй — драматическое падение скорости после 3-4 запросов из-за раздутого KV-cache. Третий — модель начинает «плыть» и галлюцинировать, теряя нить диалога уже на втором ответе. Каждый из них решается по-своему, но без понимания внутренностей вы будете просто перебирать квантования и терять время.
Баг №1: OOM при контексте в 64k — память есть, а толку нет
В теории 96 ГБ должно хватать на 122B модель в Q4_K_M с контекстом 64k. На практике — Memory pressure краснеет уже на prefill, и процесс падает с NSErrorException: malloc: can't allocate region. Причина — не сама модель, а то, как qMLX и MLX управляют KV-cache и буферами внимания. По умолчанию MLX аллоцирует память под максимальный контекст сразу, часто с запасом, плюс фрагментация в unified memory Mac делает своё дело.
--max-kv-cache-size в qMLX загрузчике, либо явный вызов mlx.core.eval после каждого шага с принудительной очисткой. Также помогает установка prefill_chunk_size в 16k вместо дефолтного 32k.В моём рабочем скрипте для Qwen3.5-122B на Mac Studio M3 Ultra фикс выглядит так:
import mlx.core as mx
from mlx.utils import tree_reduce
from qmlx import Qwen3ForCausalLM, load_model
model, tokenizer = load_model("Qwen/Qwen3.5-122B-q4_K_M", cache_policy="dynamic", max_kv_cache_size=32768)
# Принудительная очистка кэша после каждого запроса
def inference(prompt: str, context_limit: int = 48000):
inputs = tokenizer(prompt, return_tensors="np")
tokens = mx.array(inputs["input_ids"])
for step in range(0, tokens.shape[1], 4096):
chunk = tokens[:, step:step+4096]
logits = model(chunk)
mx.eval(logits) # форсируем выполнение
# освобождаем ненужные буферы
mx.clear_cache()
return model.generate(tokens, max_new_tokens=1024, temperature=0.7)
Ключевой момент — вызов mx.clear_cache() после каждого цикла. Без этого MLX продолжает держать все промежуточные тензоры, и память не освобождается. Не путайте это с очисткой KV-cache — мы очищаем только вычислительные буферы, которые не нужны после генерации.
Ещё один трюк: используйте cache_policy="dynamic" при загрузке модели. В старых версиях qMLX (до 0.3.1) этот параметр отсутствовал, и память аллоцировалась статически под максимальный контекст — сразу 96k, даже если вы задали 48k. После обновления до последней версии (на июль 2026 — qMLX 0.3.3) это поведение исправлено, и я легко запускаю Qwen3.5-122B с контекстом 60k без OOM.
Предупреждение: Если вы используете кастомный код с ручным управлением KV-cache, убедитесь, что не вызываете model.reset() без необходимости — это сбросит весь кэш и замедлит следующий запрос. Правильный паттерн описан выше.
Для тех, кто хочет глубже понять механику, рекомендую прочитать статью «24 ГБ ОЗУ на M4 и 64k контекст: какие LLM реально работают без тормозов?» — там я разбираю общие принципы работы unified memory и почему важен prefill chunk.
Баг №2: Скорость падает в три раза после трёх запросов
Первые один-два запроса летают — 30+ токенов/с. Потом скорость падает до 10, потом до 5. Activity Monitor показывает, что память почти не растёт, но загрузка CPU/GPU хаотичная. Чаще всего проблема в накоплении KV-cache, который qMLX не чистит между сессиями, если вы не перезагружаете модель. Mac Studio начинает свопиться на SSD, и вот вам тормоза.
Проверенный способ — принудительное ограничение размера KV-cache через параметр --max-kv-cache-size при запуске. Но есть нюанс: если вы используете LM Studio или другое GUI, этот параметр недоступен. Придётся писать скрипт, который перезагружает модель после N запросов.
Вот как я сделал на bash с использованием mlx_lm.server:
#!/bin/bash
# reload_model.sh
COUNTER=0
MAX_REQUESTS=3
while true; do
PID=$(pgrep -f "qwen3.5-122b-q4_")
if [ -z "$PID" ]; then
# запускаем сервер
mlx_lm.server --model Qwen/Qwen3.5-122B-q4_K_M --max-kv-cache-size 24k --port 8080 &
sleep 20
else
# проверяем количество обработанных запросов (имитация через логи)
REQ_COUNT=$(grep -c "Request completed" /tmp/llm.log)
if [ $REQ_COUNT -gt $MAX_REQUESTS ]; then
echo "Перезагружаем модель после $MAX_REQUESTS запросов"
kill $PID
sleep 5
> /tmp/llm.log
fi
fi
sleep 30
done
Скрипт примитивный, но рабочий. В реальном проекте я добавил экспоненциальную задержку между перезагрузками, чтобы не дёргать модель на каждый чих. Суть: не давайте KV-cache жить дольше, чем нужно.
Более элегантное решение — использовать model.reset_cache() после каждого диалогового оборота. В qMLX 0.3.3 этот метод появился: model.reset_cache(keep_history=True) позволяет сохранить префикс, но сбросить историю генерации. Это значительно чище, чем перезагрузка всего процесса.
Баг №3: Модель начинает нести чушь после второго ответа
Сценарий: вы задаёте вопрос, получаете отличный ответ. Задаёте уточнение — ответ снова норм. На третьем запросе начинается каша: повтор слов, потеря темы, генерация случайных токенов. Знаете? Это не модель «деградировала», это известная ошибка в квантовании qMLX Q4_K_M на длинных контекстах. Из-за особенностей репрезентации KV-cache в 4-битных квантах при длине контекста более 16k начинает накапливаться ошибка округления, и позиционное кодирование «плывёт».
Впервые я столкнулся с этим, когда тестировал Qwen 3.5 122B на Mac Studio M3 Ultra. После двух ответов модель теряла контекст и выдавала совершенно несвязный текст. Тогда я написал разбор этой проблемы для llama.cpp, но в qMLX она тоже есть — и решается обновлением до последней версии, где исправлено позиционное кодирование для длинных контекстов.
Как проверить, что баг именно в квантовании? Запустите модель в FP16 (если позволяет память) — если бессмыслица исчезла, значит, дело в кванте. Второй тест: используйте квантование Q5_K_M или Q6_K — ошибка пропадает, но растёт потребление памяти. Для 122B на 96GB Q5_K_M вполне подъёмна с контекстом до 48k.
Фикс: обновить qMLX до 0.3.1 или новее, где этот баг закрыт. Если по каким-то причинам обновление невозможно, добавьте в конфиг модели параметр position_scale: 1.0 вручную. Вот как это выглядит в YAML загрузчика:
model: Qwen/Qwen3.5-122B-q4_K_M
quantization: Q4_K_M
context_length: 48000
position_scale: 1.0 # отключает дополнительный scaling, который вызывал ошибки
cache_type: float16 # храним KV-cache в FP16, а не в q4
Обратите внимание: параметр cache_type: float16 увеличивает память под кэш примерно на 15-20%, но полностью устраняет проблему накопления ошибки. Я использую этот вариант для длительных диалогов с агентным кодингом — контекст 48k держится стабильно, без потери связности.
Кстати, если вы столкнулись с «бесконечным мышлением» (модель генерирует бесконечные <|im_start|> и <|im_end|>) — это следствие того же бага, и описанный фикс тоже помогает. Подробнее в статье «Как исправить бесконечное мышление Qwen 3.5 9B на Mac».
Частые ошибки и грабли
На основе моего опыта и отзывов читателей собрал топ-3 грабли, на которые наступают почти все:
- Забывают чистить кэш между экспериментами. Mac OS не освобождает unified memory автоматически после падения процесса. Нужно или перезагружать компьютер, или вручную вызывать
sudo purge. Иначе следующий запуск будет стартовать с фрагментированной памятью. - Гонятся за максимальным контекстом. 96 ГБ — это вся память системы. Если вы загрузите Qwen3.5-122B с контекстом 80k, у вас не останется памяти для браузера, терминала и прочих инструментов. Реальный комфортный предел для кодинга с длинным контекстом — 48-56k.
- Игнорируют версию qMLX. Баги, описанные выше, исправлены в 0.3.1 и 0.3.3. Если ваша версия 0.2.5 — ни один из фиксов не сработает. Обновляйтесь.
Рекомендую также прочитать «Mac Studio M3 Ultra для локальных LLM: реальные тесты GLM-4.7 Q4» — там я показываю, как распределяется память под разные компоненты системы при запуске больших моделей. Эти тесты помогут прикинуть бюджет памяти под ваши задачи.
Итоговый чек-лист для комфорта
Прежде чем бежать применять все советы, пробегитесь по списку:
- Обновите qMLX до 0.3.3 (pip install --upgrade qmlx).
- Используйте Q4_K_M с
cache_policy="dynamic"иmax_kv_cache_size=48k. - Добавьте
mx.clear_cache()после каждого инференса в скрипт. - Установите
position_scale: 1.0иcache_type: float16. - Для сброса KV-cache между сессиями используйте
model.reset_cache(keep_history=True).
Следуя этим шагам, вы получите стабильные 20-25 токенов/с на Qwen3.5-122B с контекстом 48k без галлюцинаций и OOM. И не забывайте заглядывать в Activity Monitor — иногда проблема не в модели, а в том, что у вас открыто 50 вкладок Chrome.