- BrainTools - https://www.braintools.ru -
MacBook M3, 16 гигабайт, никакого облака. Свежая Gemma 4 берёт с картинки график и отдаёт CSV. Первые три кейса — идеально. На четвёртом модель начала врать. И врать аккуратнее, чем говорила правду.
Вышла Gemma 4 12B Unified — мультимодальная модель, которая читает не только текст, но и картинки. В квантованном виде она помещается на обычный ноутбук, и мне стало любопытно, что это даёт на практике, а не в бенчмарках.
Просто запустить «hello world» неинтересно. Задача была двойная: собрать на этой модели маленький рабочий инструмент — и заодно честно проверить, где у локального зрения [1] предел. Научился сам — расскажи, как оно на самом деле.
Инструмент выбрал такой, чтобы локальность была оправдана, а не «потому что могу»: вытаскивать данные из картинок с графиками и таблицами в CSV. Это то, что нельзя слить в облако, и то, что сразу грузит vision по полной — OCR, чтение осей, разбор структуры.
Дальше по порядку: что за модель и влезает ли в 16 ГБ, поднимается ли на Mac, на какие грабли я наступил, как устроен инструмент — и карта из семи кейсов, где видно, чему верить, а чему нет.
Облачные API распознают картинки точнее и быстрее. Но есть данные, которые нельзя выгружать наружу: внутренние дашборды, отчёты под NDA, в общем, визуализация, которую надо оцифровать, не светя в стороннем логе. Тут локальная модель — единственный вариант. Приватность, офлайн, нулевая стоимость инференса. Вопрос один: насколько ей можно верить. Об этом и текст.
Герой — Gemma 4 12B Unified. Мультимодальная, encoder‑free: проецирует патчи картинки напрямую, без отдельного визуального энкодера. Контекст до 256K, режим рассуждений гасится одним флагом.
В full precision это ~24 ГБ, в 16 не влезает. Беру квантованную:
gemma-4-12b-it-UD-Q4_K_XL.gguf — 6.86 ГБ;
mmproj-F16.gguf — это «глаза», без него модель текстовая — 167 МБ.
В рантайме mmproj добавляет ~360 МБ. Помещается с запасом, если не выкручивать контекст. Я ставил --ctx-size 8192 — для одной картинки за глаза, а 256K сожрали бы всю память [2].
Ставится через Homebrew и llama.cpp. Грабли я собрал на свежести модели — выношу сразу, чтобы ты не повторял.
Качаем модель. Имена файлов идут позиционно, не через --include — иначе CLI скачает только последний:
hf download unsloth/gemma-4-12b-it-GGUF gemma-4-12b-it-UD-Q4_K_XL.gguf mmproj-F16.gguf --local-dir ~/models/gemma-4-12b
Первая засада. Стабильный llama.cpp из Homebrew (билд 9430) при старте с mmproj падал:
clip_init: ... unknown projector type: gemma4uv
gemma4uv — новый визуальный проектор encoder‑free 12B. Поддержку влили в llama.cpp за пару дней до моих экспериментов, и стабильная сборка её ещё не знала. Сборка из исходников через brew install --HEAD упала на компиляции — устаревшие Command Line Tools, «Tier 2 configuration». Сработал готовый официальный бинарник с GitHub Releases, новее билда 9496. Скачал, распаковал, запустил:
~/llama-bin/llama-b9528/llama-server --model ~/models/gemma-4-12b/gemma-4-12b-it-UD-Q4_K_XL.gguf --mmproj ~/models/gemma-4-12b/mmproj-F16.gguf --ctx-size 8192 --jinja --reasoning off --temp 0.1 --top-p 0.95 --top-k 64 --port 8080
Строка в логе, ради которой всё затевалось:
loaded multimodal model, '.../mmproj-F16.gguf'
Зрение поднялось. Урок: на свежих моделях версия раннера решает больше, чем железо. Проверяй номер билда, прежде чем грешить на свой ноут.
Сам chartscan.py [3] — без внешних зависимостей, только стандартная библиотека. Логика [4] простая: кодируем картинку в base64, шлём в llama-server по OpenAI‑совместимому эндпоинту, парсим строгий JSON, пишем CSV. Весь интеллект [5] — в промпте и схеме:
Возвращай ТОЛЬКО валидный JSON. Схема:{ "kind": "chart" | "table", "chart_type": "bar"|"line"|"pie"|"scatter"|"area"|"other"|null, "title": ..., "columns": [...], "rows": [[...]], "n_labels_seen": целое, // сколько ЯВНЫХ подписей насчитал "value_source": "labeled"|"estimated"|"mixed", "notes": ...}
Четыре решения, которые стоит перенять:
temperature=0.1, а не рекомендованная Google 1.0. Структурированное извлечение хочет детерминизма, а не разнообразия. Единица — под диалог.
value_source — модель сама говорит, взяла числа из подписей или прикинула по осям. Звучит как готовое решение проблемы доверия. Спойлер: не работает, вернусь к этому ниже.
n_labels_seen — модель сперва считает подписи, потом заполняет строки, а скрипт сверяет одно с другим. Разошлось — красный флаг.
Graceful‑fail. На большой таблице вывод обрывается по лимиту токенов посреди JSON. Скрипт не падает, а дозакрывает скобки и отдаёт все целиком прочитанные строки с пометкой ВЫВОД ОБРЕЗАН. Недописанную хвостовую строку отбрасывает, а не угадывает.
Дальше самое интересное. Я взял семь картинок — от тривиальных до намеренно злых — и сверил вывод с оригиналом руками.
Простая таблица (описание полей, 5×4): все ячейки точь‑в-точь, включая пустые (вернула null) и длинный текст с переносами. Сто процентов.

Круговая диаграмма с подписями: все шесть значений (15.2 / 18.2 / 12.1 / 9.1 / 24.2 / 21.2) совпали до десятой. Флаг labeled честный.

Зона комфорта: чистый растр плюс явные числовые подписи. Здесь модели можно верить.
Stacked bar без подписей. Значения прикинуты по оси, попадание в пределах 1–2 пунктов от реальных границ сегментов, каждая колонка сходится к 100%. И модель сама поставила estimated и объяснила почему. Единственная придирка: выдала 29.5, 20.5 — ложная точность. По сетке с шагом 20 полпроцента не разрешить физически, а десятичные она дорисовала.

Линейный график с 16 плотными подписями — отдельная драма. С первого захода модель потеряла часть точек, переврала значения и выдумала годы 2027–2043, которых на оси нет, достроив её «по плюс четыре года». И при этом нагло пометила всё как labeled. Я ужесточил промпт: запретил достраивать ось (нет подписи — ставь null), потребовал считать подписи, привязал labeled к тому, что и X, и Y взяты из видимого текста. После правки:
n_labels_seen: 16, строк 16 — счёт сходится, точки не теряются;
выдуманные годы исчезли, неподписанные X встали в null;
флаг сменился на честный mixed.
Структурную дыру я закрыл. Но 4 значения из 16 всё равно прочитаны неверно (513→517, 1018→1010, пара 1319/1802 переставлена местами) — всё в загромождённой середине, где подписи налезают на линию. Это уже предел самой модели, промптом не лечится. Зато теперь она об этом честно сигналит.


Мыльный скриншот простой зарплатной таблицы. Первая строка прочитана идеально. Вторая, по Петрову, выдумана целиком: 800/1000/800/1000/1200/1400 из оригинала превратились в 1200/1300/1100/1200/1400/1500. Третья — одна ошибка [6]. Подзаголовки аванс/зарплата переврала в важн/план.

И вот тут самое неприятное: рядом стоял флаг labeled: доверяй и счёт сходится. Самоотчёт модели соврал. Кросс‑чек по n_labels_seen считает точки графика, для таблиц он бесполезен — рассинхрон строк не возникает, даже когда половина чисел галлюцинация. Урок: нельзя доверять модели судить о собственной надёжности. Сигнализация должна быть внешней по отношению к ней.
Гигантская вложенная таблица — 30 строк на 18 столбцов, объединённые ячейки, двухуровневая шапка 2019/2020 → квартал → План/Факт. Сначала она вообще не доехала: на ~10 ток/с генерация 4096 токенов тянется минут семь, и клиент отваливался по таймауту раньше, чем сервер договаривал. Поднял таймаут — дождался. Вывод оборвался по лимиту токенов, но graceful‑fail спас 29 строк.


А вот качество спасённого — финал всей истории. Модель прочитала первую ячейку‑две в каждой строке верно, а дальше перестала читать и насыпала гладкую арифметическую прогрессию круглыми тысячами — 10000, 11000, 12000, 13000, — хотя в оригинале рваные 4302, 657, 9892. Объединённые ячейки развалились: товары Nestea уехали под BonAqua. Колонки местами сдвинулись. Появились несуществующие позиции — «братиш», «черника». Последняя строка продублировала предыдущую слово в слово.
Попробуй на глаз отличить это от настоящих данных. В том и подвох: фабрикация выглядит чище правды. Ровные тысячи, аккуратная структура — а это почти весь синтетик.
Наивная гипотеза «локальная модель честна о своей точности» — неверна. Модель врёт и про числа, и про собственный флаг доверия. Точная формулировка такая:
Локальный VLM полезен только внутри узкой проверяемой зоны — чистые таблицы и простые графики с подписями. За её пределами он не падает, а правдоподобно врёт. И чем аккуратнее выглядит результат, тем больше повод насторожиться: честный OCR рваных реальных данных выглядит грязнее, чем выдуманная гладкая прогрессия.
Отсюда и роль инженера. Она не в том, чтобы выжать последние проценты точности — та упирается в саму модель. Она в том, чтобы натянуть проволочки‑сигнализации: строгая схема, флаг происхождения значений, кросс-чек подписей, graceful‑fail с явной пометкой обрыва. Они не делают модель умнее. Они говорят, когда ей не верить.
Бери локально, если: разовая оцифровка чувствительных таблиц и простых графиков с подписями, нужен офлайн, нулевая стоимость, приватность. С обязательной ручной сверкой на сомнительных кейсах.
Не мучайся, если: плотные многосерийные графики, мыльные сканы, огромные вложенные таблицы. Тут либо облачный API, либо человек с глазами.
Вот как выглядит локальный инференс на M3 16 ГБ:
Простой график отдаётся за полминуты, гигантская таблица — за минуты. Практическим потолком на больших задачах оказывается wall‑clock, а не точность.
Локальная 12B на ноуте — это про приватность и контроль, а не про идеальную цифру. Если держать это в голове и не верить гладкому выводу на слово — инструмент рабочий.
GGUF‑кванты, которые качал я: https://huggingface.co/unsloth/gemma-4-12b‑it‑GGUF [7]
Официальная карточка Gemma 4 12B (instruction‑tuned): https://huggingface.co/google/gemma-4-12B‑it [8]
Релизы llama.cpp (бери билд ≥ 9496 ради gemma4uv): https://github.com/ggml‑org/llama.cpp/releases [9]
Код chartscan.py [3]: [https://gist.github.com/inforobotvit/0c90319f61ca20899011265c40c3f60b [10]]
Будешь повторять [11] — проверь номер билда llama.cpp, на свежих моделях это решает.
Если нужен гайд по установке и работе с этой моделью на твоём маке, напиши комментарий — подготовлю и пришлю.
Пишу про практики работы с AI в Telegram‑канале «Я и мой друг робот» — про мульти‑агентные системы, визуализацию данных и реальные автоматизации: https://t.me/mewithrobot [12]
Автор: VitTurov
Источник [13]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/31365
URLs in this post:
[1] зрения: http://www.braintools.ru/article/6238
[2] память: http://www.braintools.ru/article/4140
[3] chartscan.py: http://chartscan.py
[4] Логика: http://www.braintools.ru/article/7640
[5] интеллект: http://www.braintools.ru/article/7605
[6] ошибка: http://www.braintools.ru/article/4192
[7] https://huggingface.co/unsloth/gemma-4-12b‑it‑GGUF: https://huggingface.co/unsloth/gemma-4-12b-it-GGUF
[8] https://huggingface.co/google/gemma-4-12B‑it: https://huggingface.co/google/gemma-4-12B-it
[9] https://github.com/ggml‑org/llama.cpp/releases: https://github.com/ggml-org/llama.cpp/releases
[10] https://gist.github.com/inforobotvit/0c90319f61ca20899011265c40c3f60b: https://gist.github.com/inforobotvit/0c90319f61ca20899011265c40c3f60b
[11] повторять: http://www.braintools.ru/article/4012
[12] https://t.me/mewithrobot: https://t.me/mewithrobot
[13] Источник: https://habr.com/ru/articles/1044400/?utm_campaign=1044400&utm_source=habrahabr&utm_medium=rss
Нажмите здесь для печати.