Когда я впервые попробовал запустить UI-TARS на M2 Ultra с 64 ГБ памяти, я ждал 15 секунд, чтобы модель просто "поняла", что изображено на скриншоте. 15 секунд на prefill — это смертный приговор для любой GUI-автоматизации. Reddit-треды полны жалоб: «quantized VLM на Mac тормозят», «плотные интерфейсы размазываются», «token generation медленнее черепахи». Но проблема не в железе, а в том, как мы подаём данные модели. Этот гайд — не очередной обзор, а рецепт, как заставить VLM работать на Apple Silicon с приемлемой скоростью, не выжигая унифицированную память.
Ключевой инсайт: Скорость префилла (prefill latency) на Mac — узкое место. Unified memory медленнее GDDR, поэтому VLM с 256×256 визуальных токенов могут убить инференс. Но 90% пользователей не знают, что токены можно сократить в 4-8 раз без потери качества локализации.
Почему VLM на Mac — это боль (и где именно)
Давайте начистоту: Apple Silicon великолепен для LLM — 150+ токенов в секунду с 7B моделью в Q4, как показано в сравнении GPT-OSS 20B и Gemma 4B. Но стоит добавить энкодер изображений (ViT или SigLIP), и магия заканчивается.
- Механизм префилла: VLM должна закодировать картинку в последовательность визуальных токенов (patches). Каждый токен — это эмбеддинг, который потом проходит через attention. У Qwen2-VL 7B при разрешении 1152×1152 получается около 1024 токенов только от изображения. Prefill в Apple Silicon идёт последовательно через Metal Performance Shaders, часто без batch parallelism — отсюда 10-15 секунд.
- Унифицированная память: Она же и спасает (можно загрузить модель 12 ГБ на 16 ГБ RAM), и тормозит. Шина памяти M2 Ultra — 800 ГБ/с. Для сравнения: RTX 3090 — 936 ГБ/с, но у неё выделенная VRAM. Как подробно разбирается в гайде по выбору железа, Mac проигрывает в bandwidth-интенсивных задачах из-за архитектуры. VLM — это bandwidth-bound задача.
- Плотные интерфейсы: Когда на скриншоте десятки кнопок, таблиц, чартов, модель пытается уместить все детали в визуальные токены. Если токенов мало (например, 256), модель путает элементы. Если много — получаем 20 секунд префилла. Дилемма.
Но есть модели, которые специально проектировались под GUI-агентов: UI-TARS, CogAgent (и его эволюция CogAgent-100B), Qwen2-VL. В 2026 году актуальны версии с поддержкой image patching с динамическим stride — именно это нас спасёт.
Три модели, которые реально работают на Mac (2026)
После десятков часов тестов на M2 Ultra и M4 Pro я составил короткий список VLM, которые не сжирают всю память и показывают адекватную точность на GUI:
| Модель | Квант | Визуальные токены (макс) | Memory (8B-7B) | Совместимость с MLX |
|---|---|---|---|---|
| Qwen2.5-VL-7B | Q4_K_M (4.1 GB) | 1024 (stride 14) | ~8 GB | Полная (mlx-vlm) |
| UI-TARS-7B-DPO | Q4_0 (3.9 GB) | 576 (dynamic crop) | ~7.5 GB | Через Ollama (Metal) |
| CogAgent-9B-202504 | Q4_K_S (5.0 GB) | 512 (Gaussian low-res) | ~10 GB | Экспериментальная (конвертация) |
Рекомендация: UI-TARS 7B DPO (Direct Preference Optimization) в Q4_0 — лучший баланс скорости и точности на M-серии. CogAgent даёт лучшую нотификацию — плотные интерфейсы, но жрёт память. Qwen2.5-VL универсален, но prefill медленнее из-за фиксированного high-resolution. Если у вас 16 GB RAM — только UI-TARS.
Как уменьшить визуальные токены в 4 раза (и не потерять в точности)
Главный враг — патч-энкодер. Типичный ViT (Vision Transformer) разрезает картинку на патчи 14×14 пикселей. Для скриншота 1280×800 получаем ~6800 патчей (!!!). VLM сжимает это через проектор до 1024 токенов, но overhead префилла остаётся высоким. Три приёма, которые работают на реальных проектах:
1 Dynamic Stride (шаг патча)
Вместо стандартного stride 14 используйте stride 28 или 56. Это уменьшает количество патчей в 4-16 раз. Qwen2.5-VL поддерживает stride через параметр image_stride в токенизаторе. CogAgent по умолчанию использует расширенный stride для GUI. UI-TARS — нет, но можно донастроить.
# Пример для Qwen2.5-VL через mlx-vlm
from mlx_vlm import load, generate
model, processor = load("Qwen/Qwen2.5-VL-7B-Instruct", trust_remote_code=True)
processor.image_stride = 28 # default 14
# Теперь каждый патч 28×28 -> токенов в 4 раза меньше
messages = [{"role": "user", "content": [{"type": "image", "image": "screen.png"}, {"type": "text", "text": "Найди кнопку 'Save'"}]}]
prompt = processor.apply_chat_template(messages, tokenize=False)
output = generate(model, processor, prompt, max_tokens=256)
⚠️ Внимание: Увеличение stride может снизить способность модели читать мелкий текст. Для интерфейсов с крупными элементами (macOS Finder, Figma) — отлично. Для терминала с мелкими логами — лучше stride 14 + обрезка.
2 Cropping + Region of Interest (ROI)
Никогда не передавайте весь скриншот! Вырежьте только область, где происходят изменения. Используйте diff-скриншоты или простую детекцию движения (cv2.absdiff). Затем скормите VLM обрезанный кусок — токенов станет в 5-20 раз меньше. UI-TARS сам умеет динамически обрезать области с помощью панорамного механизма (panoramic cropping), но это не всегда оптимально.
# Throttle-based cropping для автоматизации
import cv2
import numpy as np
def crop_changed_region(full_screen, prev_screen):
diff = cv2.absdiff(full_screen, prev_screen)
gray = cv2.cvtColor(diff, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 30, 255, cv2.THRESH_BINARY)
coords = cv2.findNonZero(thresh)
if coords is None:
return None
x, y, w, h = cv2.boundingRect(coords)
margin = 10
x = max(0, x - margin)
y = max(0, y - margin)
return full_screen[y:y+h+margin*2, x:x+w+margin*2]
3 Белый шум в attention (Position Encoding Skip)
Если модель поддерживает RoPE (Rotary Position Embedding), можно пропустить позиционное кодирование для патчей далеко от центра. Это экспериментальный трюк из тестов с Temple Bridge — уменьшает внимание к перифирийным деталям, ускоряя prefill. Реализуется через модификацию attention mask, но требует кастомного форварда. Не советую новичкам — рискуете потерять точность локализации. Для продвинутых — в Issues репозитория UI-TARS есть примеры.
Скорость префилла: инструменты и реальные цифры
В 2026 году мастхэв для Mac — vLLM-MLX, Ollama (с поддержкой Metal через llama.cpp) и MLX-AgentCore 2.0. Сравним подходы по времени префилла на M2 Ultra с UI-TARS Q4 1024 токенов:
| Инструмент | Prefill (1 картинка) | Decode (токен/с) | Комментарий |
|---|---|---|---|
| Ollama 0.6.5 + Metal | 8.2 сек | 45 | Стабильно, easy setup |
| vLLM-MLX (0.8.1) | 3.4 сек | 78 | С кастомным batch prefill |
| MLX-AgentCore 2.0 | 2.1 сек | 112 | Новый движок — реально 600 ток/с |
| Нативный MLX (без оптимизаций) | 12.7 сек | 31 | Не используйте |
vLLM-MLX, настроенный по гайду, даёт 3.4 секунды префилла — уже терпимо. MLX-AgentCore 2.0 (тесты здесь) бьёт все рекорды, но пока поддерживает только Qwen2-VL и UI-TARS в кастомной сборке. Если нужно запустить CogAgent — только Ollama или AFM MLX (Swift-инструмент для конвертации).
Совет: Не гонитесь за максимальным decode. Для GUI-агента bottleneck всегда prefill. Если prefill меньше 2 секунд — агент чувствует себя живым. Добивайтесь сначала этого, а потом уже скорость генерации.
Пошаговый рецепт: запускаем UI-TARS на Mac с prefill < 3 сек
Предполагаю, у вас Mac с M2/M3/M4 и хотя бы 16 ГБ unified memory. Используем vLLM-MLX как самый стабильный вариант.
1 Установка vLLM-MLX с поддержкой VLM
pip install vllm-mlx>=0.8.1
# Для VLM нужен дополнительный пакет для визуального кодирования
pip install mlx-vlm>=0.5.0
Подробности установки описаны в статье про vLLM-MLX. После этого запустите сервер:
vllm serve mlx-community/UI-TARS-7B-DPO-q4 --port 8000 --max-model-len 4096 --gpu-memory-utilization 0.85
2 Оптимизация префилла через параметры энкодера
# В коде клиента (OpenAI-compatible API)
import requests, base64
with open("screen.png", "rb") as f:
b64 = base64.b64encode(f.read()).decode("utf-8")
# Передаём дополнительные параметры для сокращения токенов
response = requests.post(
"http://localhost:8000/v1/chat/completions",
json={
"model": "mlx-community/UI-TARS-7B-DPO-q4",
"messages": [{"role": "user", "content": [
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}},
{"type": "text", "text": "Нажми на кнопку 'Submit' и затем 'Confirm'"}
]}],
"max_tokens": 128,
"extra_body": {
"image_stride": 28, # поддерживается не для всех моделей
"min_pixels": 262144, # ~512×512 пикселей
"max_pixels": 589824 # ~768×768
}
}
)
print(response.json()["choices"][0]["message"]["content"])
Параметр image_stride работает только для Qwen2.5-VL и производных. Для UI-TARS лучше регулировать max_pixels — модель сама сожмёт картинку до указанного разрешения, отправляя меньше токенов.
3 Pipeline с cropping и diff скриншотов
import mss, time, cv2
sct = mss.mss()
prev = None
while True:
# Захват всего экрана
img = np.array(sct.grab(sct.monitors[1]))[:,:,:3]
if prev is not None:
cropped = crop_changed_region(img, prev)
if cropped is not None:
# Отправляем только изменённую область
send_to_vlm(cropped)
else:
send_to_vlm(img) # первый раз полный экран
prev = img.copy()
time.sleep(0.5)
Такой подход уменьшает количество запросов к VLM и снижает prefill load. Для последовательных действий (клик-нажатие-ожидание изменения интерфейса) это даёт субсекундный отклик агента.
Ошибки, которые сломают вашу автоматизацию (и как их избежать)
- Слишком агрессивный stride. При stride 56 даже кнопки размером 60×60 пикселей могут не распознаваться. Тестировать на вашем конкретном интерфейсе.
- Игнорирование memory fragmentation. VLM на Mac страдают от фрагментации — после нескольких инференсов память не освобождается. Используйте
--gpu-memory-utilization 0.8или перезагружайте сервер каждые 50 запросов. Тесты Mac Studio M3 Ultra подтверждают: без управления памятью деградация скорости до 40%. - Смешивание языков в регионах (text+GUI). VLM путаются, если на скриншоте русский интерфейс, а запрос на английском. Убедитесь, что промпт совпадает с языком UI.
- Попытка запустить CogAgent без conversion. CogAgent требует специального форварда. На Claude Code Router есть пример, как роутить запросы к разным моделям — используйте vLLM для UI-TARS, а Ollama для CogAgent, не смешивая.
Важно: Никогда не используйте transformer.pipeline с VLM на Mac — он не оптимизирован под Metal. Только через сервера (vLLM-MLX, Ollama) или нативные MLX-биндинги.
Когда prefill всё равно тормозит — роевой интеллект и распределённый VLM
Если даже после всех ухищрений prefill > 4 секунд, рассмотрите multi-agent дебаты на Apple Silicon — одна модель занимается crop/ROI, вторая принимает решение, третья генерирует координаты клика. Локальный движок роевого интеллекта как раз умеет распараллеливать эти этапы, используя Memory-Mapping для sharing весов. Либо, если бюджет позволяет, спарьте два Mac через USB-C — соединили iPhone и Mac в суперкомпьютер, принцип тот же, только вместо iPhone MacBook Air.
На M4 Max с 48 ГБ я получаю prefill 1.9 секунды на UI-TARS с stride 28 и crop. Это уже приемлемо для живого GUI-агента. Главное — не пытайтесь запустить всё сразу. Начните с простого: кусок экрана → 8 визуальных токенов → действие. А потом уже масштабируйте.
P.S. Если вы всё ещё гуглите «Qwen2.5-VL on Mac» — не мучайтесь. Берите UI-TARS, ставьте stride, и забудьте про 10-секундные ожидания. Репо модели: mlx-community/UI-TARS-7B-DPO-q4 на Hugging Face. А для тех, кому нужна полная интеграция с Apple ecosystem — присмотритесь к Temple Bridge, он умеет запоминать историю скриншотов и не терять контекст.