Почему AI не умеет считать - и как исследователи засовывают калькулятор прямо внутрь нейросети
Помните мем со strawberry? Лето 2024, весь интернет спрашивает ChatGPT: сколько букв "r" в слове "strawberry"? Три, очевидно. Любой первоклассник справится. Модель уверенно отвечает: две.
Дальше было еще веселее. Когда пользователи начинали спорить, ChatGPT аргументировал: мол, буква R в "straw" - это часть слова, но она не влияет на общий подсчет R в "strawberry". Модель буквально газлайтила людей, защищая неправильный ответ развернутой аргументацией. Gemini в некоторых случаях заявлял, что букв "r" в strawberry вообще ноль - и отказывался признавать ошибку даже после побуквенного разбора.
Кейс стал настолько знаковым, что OpenAI назвала свой проект рассуждающей модели кодовым именем "Strawberry" (позже ставший o1) - как внутреннюю шутку: модель наконец научилась считать буквы.
Забавная история. Но strawberry - это верхушка айсберга.
Систематические тесты показывают куда более масштабную проблему: GPT-4o не справляется с умножением чисел длиннее четырех цифр. При умножении нескольких чисел GPT-4 давал 95% точности для двух чисел до 100, 30% для трех, 5% для четырех - и ноль дальше. При этом модель часто правильно угадывала последнюю цифру результата (потому что она определяется только последними цифрами множителей - простой паттерн), но середина числа - мусор.
Модель, которая пишет код, рассуждает о квантовой физике и генерирует сонеты, не может перемножить два пятизначных числа.
Давайте разберемся, почему так происходит, как это пытаются решить - и как группа исследователей из Германии предложила радикально другой подход.
Почему LLM не умеют в арифметику
Тут три независимые причины. Каждая сама по себе достаточна, а вместе они делают надежную арифметику в LLM практически невозможной.
Причина 1. Она не считает - она угадывает следующий символ
Ключевое: языковая модель - это машина для предсказания следующего токена. Не калькулятор, не процессор, не логический движок. Машина для продолжения текста.
Когда вы пишете "2 + 3 =", модель не складывает. Она смотрит на паттерн "цифра + цифра =" и вспоминает, что в обучающих данных (весь интернет, книги, код) после такой конструкции обычно стояло "5". И выдает "5".
Для простых случаев это неотличимо от настоящего вычисления - потому что в интернете тысячи примеров "2 + 3 = 5". Но "72528 x 67066 =" модель почти наверняка не видела в обучающих данных. И начинает экстраполировать.
Представьте человека, который не знает алгоритм умножения, но прочитал миллион задач с ответами. Он заметит закономерности: "если числа заканчиваются на 8 и 6, результат заканчивается на 8", "пятизначное на пятизначное - обычно десятизначное". По этим закономерностям он угадает края результата. Середину - нет.
Принципиальное отличие от калькулятора: калькулятор выполняет алгоритм - цепочку детерминированных шагов, где каждый зависит от предыдущего. Запустите его тысячу раз с одним входом - тысячу одинаковых ответов. Модель выбирает вероятное продолжение текста. Каждый раз - потенциально немного другое. Это не баг - это фундамент, на котором все построено.
Причина 2. Она не видит символы - она видит куски
Вторая фундаментальная проблема - и именно она объясняет фиаско со strawberry.
LLM не работают с текстом посимвольно. Перед попаданием в модель текст разбивают на токены - куски, которые могут быть словом, частью слова, цифрой или группой цифр. Это как если бы вы читали текст, но вместо букв видели слоги - и не могли разобрать слог на отдельные буквы.
Слово "strawberry" модель видит не как s-t-r-a-w-b-e-r-r-y. GPT-4o разбивает его на три токена: "st", "raw", "berry". Каждый токен - неделимая единица. Модель не умеет "заглядывать внутрь" токена, как мы не видим атомы в предметах.
Она знает, что "st" + "raw" + "berry" = клубника. Но что "raw" содержит одну "r", а "berry" - две - эта информация при токенизации растворяется. Модель физически не может посчитать буквы, потому что не видит буквы.
С числами - то же, только хуже. Число 1234567890 Llama 3.1 разобьет на: "123", "456", "789", "0". Модель не "знает", что "123" в начале числа - это сотни миллионов. Токен "123" как самостоятельное число и "123" как начало миллиарда выглядят одинаково. А позиция цифры - это все в арифметике. Единицы, десятки, сотни - все определяется позицией, и эта позиция теряется при разбиении на токены.
Причина 3. Трансформер не заточен под многошаговые алгоритмы
Даже с идеальной токенизацией трансформер не умел бы считать надежно.
Умножение 72528 x 67066 - это не одна операция. Это цепочка: перемножить каждую пару цифр, сдвинуть, сложить промежуточные суммы, передать переносы. Десятки зависимых шагов. В прямом проходе трансформера фиксированное число слоев (в Llama 8B - 32). Каждый слой может выполнить ограниченный объем работы.
Аналогия: вам нужно умножить столбиком, но вместо того чтобы записывать промежуточные результаты, вы держите все в голове - и у вас ровно 32 "шага мышления". Для сложения трехзначных чисел хватит. Для умножения пятизначных - нет.
Исследование Dziri et al. (2024, NeurIPS) формально показало: трансформеры имеют фундаментальные ограничения на композиционные задачи. Где каждый шаг зависит от предыдущего - сложность растет экспоненциально. А ресурсы модели фиксированы.
И каждый шаг рассуждения генерируется вероятностно. Одна ошибка в переносе на третьем шаге - и весь результат неправильный. Для калькулятора каскад ошибок невозможен: каждый шаг детерминирован. Для нейросети - это норма.
А почему не встроили калькулятор сразу?
Ответ прост: не входило в задачу. Трансформер проектировался для языка. Attention, позиционные кодировки, нормализация - все оптимизировано для статистических зависимостей между словами. Не для алгоритмов.
GPU, на котором работает LLM, прекрасно считает - триллионы арифметических операций в секунду. Но эти вычисления "инфраструктурные": перемножение матриц весов, вычисление attention-скоров. Это вычисления для предсказания текста, а не для решения вашей задачи "324 x 87332". Из 10^15 операций в секунду - ноль про вашу задачу.
Как машина с двигателем в 300 л.с. не может вскипятить чайник. Мощность есть, но приложена не туда.
Как это решают сегодня
Раз модель сама не считает, ей дали костыли. Три подхода, каждый со своими проблемами:
Chain of Thought. Модель расписывает решение по шагам: "умножим 4 на 7, получим 28, запишем 8, перенесем 2...". Каждый шаг - отдельная генерация, каждый может ошибиться. Модель o1 от OpenAI справляется с умножением девятизначных чисел примерно в половине случаев - прогресс, но все еще лотерея. И инференс замедляется в разы: вместо одного прохода модель генерирует десятки промежуточных токенов.
Tool use. Модель генерирует вызов калькулятора, вызов перехватывается оркестратором, число считается на CPU, результат подставляется обратно в контекст. Так работают ChatGPT (code interpreter), Claude (MCP), Gemini. Точно, но: задержка на переключение GPU-CPU-GPU, модель не может использовать результат "внутри" своих рассуждений на уровне латентных представлений, и при претрейне этого инструмента не было - модель узнает о нем только после обучения.
Генерация кода. Модель пишет result = 324 * 87332, код исполняется снаружи. По сути, вариант tool use с теми же ограничениями.
Общий знаменатель: модель не считает - делегирует. И все три метода добавляются после претрейна. Модель сначала годами учится угадывать арифметику по паттернам, потом получает внешний инструмент. Старые "привычки угадывания" никуда не деваются и мешают.
IGC - калькулятор внутри модели
В январе 2025 года Флориан Дитц и Дитрих Клакоу из Саарландского университета предложили принципиально другое: не делегировать, а встроить калькулятор прямо внутрь LLM. Модуль IGC (Integrated Gated Calculator) работает на GPU, в том же прямом проходе, без промежуточных токенов, без внешних вызовов. (arxiv:2501.00684)
Где это сидит в архитектуре
Текст приходит, превращается в эмбеддинги, проходит через 32 слоя трансформера Llama 3.1 8B. IGC встраивается после слоя 1 (авторы перебрали разные варианты - ранние слои работают лучше, потому что активации еще не "размазаны" по всем 4096 измерениям). Модуль перехватывает промежуточные активации, обрабатывает их и модифицирует. Остальные 31 слой продолжают работу уже с модифицированными данными.
Все 8 миллиардов параметров Llama заморожены. Обучается только IGC - 17 миллионов параметров. Это 0.2% от базовой модели. Авторы используют именно instruction-finetuned Llama 3.1 8B - модель, которая уже умеет следовать инструкциям. IGC добавляет ей арифметику, а не учит общаться.
Три компонента IGC
1. Input Mapping - "прочитай задачу"
Из токенов "What is 324 times 87332?" нужно извлечь: операнд 1 (324), операнд 2 (87332), оператор (x).
Работает через attention-механизм. Есть "якорный токен" - момент, когда задача целиком описана (переход от реплики пользователя к ответу модели). Якорный токен формирует Query, все предшествующие токены - Key и Value. Attention собирает нужное из контекста.
На выходе - вероятностные распределения: для каждой позиции каждого операнда (какая цифра 0-9 или "пусто") и для оператора. Не числа напрямую, а вероятности - потому что это обучаемый компонент и ему нужны гладкие градиенты.
2. Calculator - "посчитай"
Чистые тензорные операции, никакого ML:
- Дискретизировать распределения (взять самую вероятную цифру каждой позиции)
- Собрать числа
- Выполнить все четыре операции параллельно (на батче это эффективнее, чем выбирать одну)
- Выбрать результат нужной операции через weighted average на основе вероятности оператора из Input Mapping
- Результат - обратно в цифры, one-hot кодировка
Блок не дифференцируем. Градиент через него не идет. Калькулятор не учится - он просто считает. Как настоящий калькулятор, только реализованный в тензорах.
3. Output Mapping - "верни результат модели"
Результат калькулятора "вписывается" в латентные представления трансформера через gated connections. Для каждого токена после якорного два компонента: "значение" (как результат калькулятора влияет на этот токен) и "вентиль" (число от 0 до 1 - насколько сильно калькулятор влияет).
Вентили - ключевой элемент всей архитектуры. Если задача не арифметическая - скажем, "напиши стихотворение" - вентили закрываются к 0, и модель работает как раньше. Калькулятор физически присутствует, но молчит. Никаких побочных эффектов на неарифметические задачи.
Как это обучается
Модифицированный teacher forcing с двумя хитростями:
Auxiliary loss. Калькулятор блокирует градиент, поэтому Input Mapping обучается отдельно - через cross-entropy между его выходами и правильными операндами/оператором. Для простых шаблонов вроде "What is X times Y?" разметка автоматическая. Для сложных текстовых задач авторы описывают подход на базе Toolformer: LLM сама размечает данные, и если разметка снижает perplexity - добавляется в обучение.
Подмена результата. Пока Input Mapping не сошелся и подает мусор на вход калькулятора, на выход подставляется правильный ответ. Output Mapping начинает учиться сразу, не дожидаясь Input Mapping. Не обязательно, но ускоряет сходимость.
Хитрость с форматом чисел
Неочевидная, но критическая деталь, без которой ничего не работает.
Llama 3.1 токенизирует числа слева направо, по три цифры: 1234567890 -> "123" "456" "789" "0". Стандартный формат представления чисел в вычислениях - выравнивание по правому краю (единицы справа), как мы записываем в тетради. Проблема: при таком формате позиция цифры внутри токена зависит от длины числа. Число 12345 разбивается на "123" "45", а 1234567890 на "123" "456" "789" "0" - и токен "123" в обоих случаях означает совершенно разные разряды. Это нелокальная информация, которую attention-механизм с трудом улавливает.
Авторы развернули формат: старшая цифра - на фиксированной позиции (слева), хвост заполняется заглушкой "*". При этом четвертая по старшинству цифра всегда во втором токене, шестая - в третьем - независимо от длины числа. Позиция предсказуема.
Результат: right-aligned формат часто не сходился вообще. Left-aligned - стабильные 99%.
Абляция: shortcut мешает
Авторы проверили интересный гибрид - IGC + прямое дифференцируемое соединение (shortcut) в обход калькулятора. Идея: дать модели два пути - через калькулятор и напрямую:
- Только shortcut (без калькулятора): ~70%, уровень baseline
- Чистый IGC: 99%, стабильно
- IGC + shortcut: нестабильно, итоговая точность ниже
Shortcut сходится быстрее (он дифференцируем, градиент идет напрямую), но переобучается на обучающей выборке и не генерализует. А его присутствие мешает модели "довериться" калькулятору - destructive interference. Как шпаргалка рядом с калькулятором: ученик подглядывает в шпаргалку вместо того, чтобы научиться пользоваться инструментом. Вывод авторов: чистая архитектура лучше "на всякий случай добавим еще путь".
Сравнение подходов
СвойствоОбычная LLMCoTTool UseIGCСложение/вычитание-+++Умножение/деление-~++Один шаг (без доп. токенов)+--+Все на GPU++-+Нет побочных эффектов---+Можно интегрировать в претрейн+--+Произвольная длина чисел+++-
IGC выигрывает почти везде, кроме одного: фиксированная максимальная длина чисел. Но для 99% практических случаев (числа до 5 цифр) этого достаточно.
За пределами арифметики
А вот здесь начинается самое интересное - и ради этого стоило разбирать всю механику.
Калькулятор в IGC - черный ящик для обучаемых компонентов. Input Mapping и Output Mapping не знают, как он устроен внутри. Они учатся подавать входы и использовать выходы. Им все равно, что внутри - сложение, поиск по базе, обход графа.
Что, если заменить черный ящик?
- Поиск по базе данных. Input Mapping извлекает ключ запроса, недифференцируемый блок ищет по индексу, Output Mapping встраивает результат в активации. RAG без промежуточных токенов, в один проход трансформера.
- Граф знаний. Обход графа вместо поиска. Тот же паттерн.
- Любая детерминированная операция. Конвертация валют, валидация формата, справочник - все, что можно реализовать как функцию "вход -> выход".
Паттерн "обучаемый вход -> недифференцируемый блок -> обучаемый выход с вентилями" - универсален. Архитектура не привязана к арифметике - арифметика просто удобный полигон для проверки идеи. Но потенциал значительно шире.
Ограничения
Честно про то, чего IGC пока не может:
Фиксированная длина чисел. Максимум задается при обучении. Авторы отмечают: в датасете MATH 99% чисел четырехзначные и короче. Для остальных - tool use как fallback. Практически это означает, что для обычных задач IGC достаточно, а для финансовых расчетов с длинными числами нужен запасной вариант.
Только арифметика, не word problems. IGC считает, но не извлекает задачу из текста - это остается работой LLM. Авторы разграничивают два типа ошибок: бывает, модель правильно рассуждает, но ошибается в арифметике внутри рассуждения. А бывает - арифметика тривиальная, но модель не может правильно вытащить числа из условия задачи. IGC решает первую проблему, не вторую.
Код не опубликован. Статья на ревью, код обещают при принятии.
Одна операция за раз. Выражение "2 + 3 * 4" требует нескольких проходов. Цепочки вычислений - через последовательные вызовы.
Итого
Работа из Саарландского университета - не про арифметику как таковую. Она про архитектурный паттерн: недифференцируемую операцию можно обернуть обучаемыми "переходниками" с вентилями и встроить в трансформер. Без промежуточных токенов, без внешних вызовов, без переключения GPU-CPU-GPU.
17 миллионов параметров побеждают 535 миллиардов - не потому что нейросеть научилась считать, а потому что ей дали настоящий калькулятор и научили им пользоваться.
Для агентных систем здесь важный сигнал: tool use через MCP и code interpreter - рабочий, но архитектурно неэлегантный подход. Задержка на внешний вызов, разрыв контекста, невозможность учиться на результатах при претрейне. IGC показывает альтернативу - инструменты как часть архитектуры, а не как внешние костыли. Не "модель вызывает калькулятор", а "калькулятор живет внутри модели".
Ссылки:
- Dietz and Klakow, 2025 - IGC: Integrating a Gated Calculator into an LLM (arxiv:2501.00684) - оригинальная статья
Хочешь жесткости, технических деталей и схемы архитектуры? Полная версия со всеми диаграммами - на сайте: https://zinovev.org/post/pochemu-llm-ne-umeyut-schitat-igc