Gemma 4 12B Unified — мультимодальная модель Google с архитектурой без отдельного визуального энкодера: патчи изображения проецируются напрямую в пространство токенов. В квантованном виде (Q4_K_XL) она занимает 6,86 ГБ плюс 167 МБ для визуального проектора mmproj-F16.gguf. На MacBook M3 с 16 ГБ оперативной памяти это помещается с запасом при контексте 8192 токенов — достаточно для одного изображения за запрос.
Практическая задача, которую автор поставил перед моделью, — извлечение данных из графиков и таблиц в CSV без выгрузки в облако. Это актуально для внутренних дашбордов, отчётов под NDA и любой визуализации, которую нельзя отправлять в сторонние API. Облачные сервисы вроде GPT-4o Vision или Gemini 1.5 Pro читают изображения точнее, но оставляют след в логах провайдера. Локальная модель — единственный вариант, когда приватность не опциональна.
| Тип изображения | Результат | Флаг value_source | Проблема |
|---|---|---|---|
| Простая таблица 5×4 | 100% совпадение | labeled | — |
| Круговая диаграмма с подписями | Все 6 значений точно | labeled | — |
| Stacked bar без подписей | Погрешность 1–2 пункта | estimated | Ложная точность до десятых |
| Линейный график, 16 подписей | 4 из 16 значений неверны | mixed (после правки промпта) | До правки — выдуманные годы 2027–2043 |
| Размытая зарплатная таблица | Строка 2 выдумана целиком | labeled (ошибочно) | Самодиагностика не сработала |
| Вложенная таблица 30×18 | Арифметическая прогрессия вместо данных | — | Объединённые ячейки, несуществующие позиции |
Запуск потребовал нестандартного шага: стабильная сборка llama.cpp из Homebrew (билд 9430) не поддерживает новый тип визуального проектора gemma4uv и падает при старте. Поддержка появилась в llama.cpp буквально за несколько дней до экспериментов, и в стабильный канал ещё не попала. Решение — официальный бинарник с GitHub Releases новее билда 9496. Урок, который автор формулирует прямо: на свежих моделях версия раннера важнее железа.
Круговые диаграммы и чистые таблицы с явными числовыми подписями модель читает точно — до десятых долей.
Инструмент chartscan.py написан на стандартной библиотеке Python без внешних зависимостей. Логика: изображение кодируется в base64, отправляется в llama-server через OpenAI-совместимый эндпоинт, ответ парсится как строгий JSON, результат пишется в CSV. Весь смысл сосредоточен в промпте и схеме ответа. Температура выставлена в 0,1 вместо рекомендованной Google 1,0 — структурированное извлечение данных требует детерминизма. Схема включает поле value_source (labeled / estimated / mixed), которое должно сигнализировать, взяты ли числа из явных подписей или оценены по осям, и счётчик n_labels_seen для кросс-проверки.
Стресс-тест на семи изображениях показал чёткую границу между зоной надёжности и зоной риска. Простая таблица 5×4 и круговая диаграмма с шестью подписанными секторами (15.2 / 18.2 / 12.1 / 9.1 / 24.2 / 21.2) воспроизведены точно до десятых. Stacked bar без числовых подписей модель оценила по осям с погрешностью 1–2 пункта и честно выставила флаг estimated — но добавила ложную точность до десятых там, где сетка с шагом 20 физически не позволяет разрешить полпроцента.
На линейном графике с 16 плотными подписями модель с первого захода потеряла точки, переврала значения и достроила ось X на четыре несуществующих года (2027–2043), пометив всё как labeled. После ужесточения промпта — запрет достраивать ось, требование ставить null для неподписанных точек — счёт сошёлся, выдуманные годы исчезли. Но 4 из 16 значений в загромождённой середине графика всё равно прочитаны неверно: 513 вместо 517, 1018 вместо 1010, пара значений переставлена местами. Это предел модели, который промптом не устраняется.
Самый тревожный кейс — размытый скриншот зарплатной таблицы. Первая строка прочитана верно. Вторая строка выдумана целиком: реальные числа 800/1000/800/1000/1200/1400 заменены на 1200/1300/1100/1200/1400/1500. Подзаголовки «аванс/зарплата» превратились в «важн/план». При этом модель выставила флаг labeled и счётчик n_labels_seen не сигнализировал об ошибке — для таблиц механизм кросс-проверки строк попросту не работает. Самодиагностика модели оказалась ненадёжной именно там, где ошибка наиболее опасна.
Гигантская вложенная таблица (30 строк, 18 столбцов, двухуровневая шапка, объединённые ячейки) добавила технических проблем: при скорости ~10 токенов в секунду генерация 4096 токенов занимает около семи минут, и клиент отваливался по таймауту. После увеличения таймаута вывод всё равно оборвался по лимиту токенов, но механизм graceful-fail сохранил 29 строк. Качество этих строк, однако, оказалось показательным: первые одна-две ячейки в каждой строке прочитаны верно, дальше модель перестала читать и заполнила столбцы гладкой арифметической прогрессией круглыми тысячами — 10000, 11000, 12000, 13000, — тогда как в оригинале стоят рваные числа вроде 4302, 657, 9892. Объединённые ячейки развалились, товары Nestea уехали под BonAqua, появились несуществующие позиции «братиш» и «черника».
Общий вывод из эксперимента: локальная мультимодальная модель на потребительском железе работает надёжно только на чистых растровых изображениях с явными числовыми подписями. Как только качество изображения падает или структура усложняется, модель начинает галлюцинировать — и делает это тихо, не меняя флагов достоверности. Доверять модели оценивать собственную надёжность нельзя; внешняя верификация должна быть встроена в пайплайн отдельно от самой модели.
