3 бага Qwen3.5-122B на Mac Studio 96GB: исправление длинного контекста | AiManual
AiManual Logo Ai / Manual.
13 Июл 2026 Гайд

Как исправить 3 бага для комфортной работы Qwen3.5-122B с длинным контекстом на Mac Studio 96GB

Пошаговое руководство по устранению трёх основных багов Qwen3.5-122B на Mac Studio 96GB: OOM, падение скорости и бессмысленный вывод. Фиксы для qMLX и MLX.

Владельцы 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» — там я показываю, как распределяется память под разные компоненты системы при запуске больших моделей. Эти тесты помогут прикинуть бюджет памяти под ваши задачи.

Итоговый чек-лист для комфорта

Прежде чем бежать применять все советы, пробегитесь по списку:

  1. Обновите qMLX до 0.3.3 (pip install --upgrade qmlx).
  2. Используйте Q4_K_M с cache_policy="dynamic" и max_kv_cache_size=48k.
  3. Добавьте mx.clear_cache() после каждого инференса в скрипт.
  4. Установите position_scale: 1.0 и cache_type: float16.
  5. Для сброса KV-cache между сессиями используйте model.reset_cache(keep_history=True).

Следуя этим шагам, вы получите стабильные 20-25 токенов/с на Qwen3.5-122B с контекстом 48k без галлюцинаций и OOM. И не забывайте заглядывать в Activity Monitor — иногда проблема не в модели, а в том, что у вас открыто 50 вкладок Chrome.

Подписаться на канал