Когда 2+2=5, и модель этому рада
Вы даете маленькому трансформеру задачу: сложи два двузначных числа. Он учится. Сначала тупо запоминает пары из обучающей выборки. Потом, после сотен эпох, будто щелкает выключатель – начинает правильно складывать любые числа, даже те, которых не видел. Это grokking, красиво и загадочно.
Но если залезть внутрь и посмотреть на веса, открывается иная картина. Модель не решает арифметику. Она жульничает. Использует хитрые шаблоны в данных, чтобы имитировать правильный ответ, не понимая сути. Это не баг, а фича – так работает большинство ИИ, даже самые большие. Механистическая интерпретируемость (Mechanistic Interpretability) – это как вскрытие, чтобы найти эти шаблоны-жульничества.
К марту 2026 года инструменты для вскрытия моделей стали доступнее. Anthropic открыла часть своих наработок, в PyTorch 2.5+ появились встроенные утилиты для трассировки внимания. Но суть осталась: чтобы понять, как модель думает, ее надо разобрать на матрицы.
Строим подопытного трансформера
Наша цель – не SOTA модель, а понятная. 4 слоя, 4 головы внимания, 128-мерные эмбеддинги. Итого около 1.1 миллиона параметров – смешно по меркам 2026 года, но достаточно для жульничества.
1 Архитектура на голом PyTorch
Забудьте про Hugging Face для этого эксперимента. Мы пишем все с нуля, чтобы видеть каждый тензор.
import torch
import torch.nn as nn
import torch.nn.functional as F
class ToyTransformer(nn.Module):
def __init__(self, vocab_size, d_model=128, n_layers=4, n_heads=4):
super().__init__()
self.embed = nn.Embedding(vocab_size, d_model)
self.pos_embed = nn.Embedding(512, d_model) # Максимальная длина
self.layers = nn.ModuleList([
nn.TransformerEncoderLayer(d_model, n_heads, dim_feedforward=512,
batch_first=True, activation='gelu',
norm_first=True) # Ключевой флаг для стабильности
for _ in range(n_layers)
])
self.ln_out = nn.LayerNorm(d_model)
self.head = nn.Linear(d_model, vocab_size)
def forward(self, x):
# x: [batch, seq_len]
positions = torch.arange(x.size(1), device=x.device).unsqueeze(0)
x_emb = self.embed(x) + self.pos_embed(positions)
for layer in self.layers:
x_emb = layer(x_emb)
x_emb = self.ln_out(x_emb)
logits = self.head(x_emb)
return logits
2 Посимвольная токенизация – где начинается магия
Мы токенизируем каждую цифру и знак отдельно. «57+24=81» превращается в токены ['5','7','+','2','4','=','8','1']. Это дает модели пространство для маневра – она может выучить правила переноса, а может и сжульничать.
vocab = {str(i): i for i in range(10)} # Цифры 0-9
vocab['+'] = 10
vocab['='] = 11
vocab[''] = 12
vocab_size = len(vocab)
def tokenize(expr):
# expr типа "57+24="
return [vocab[ch] for ch in expr]
Где спрятано жульничество? Смотрим в attention
После обучения (а мы обучаем с weight decay=0.1, это ускоряет grokking в разы) модель выдает 95% точности на тесте. Кажется, она научилась сложению.
А теперь включаем механистическую интерпретируемость. Смотрим, куда смотрят головы внимания в последнем слое, когда модель вычисляет последнюю цифру ответа.
| Вход | Ожидаемое внимание | Реальное внимание (жульничество) |
|---|---|---|
| 57+24= | На цифры 7 и 4 (единицы) | На позицию символа '=' и первую цифру первого числа |
| 38+91= | На 8 и 1 | Тот же шаблон: '=' и '3' |
Модель игнорирует второе слагаемое! Она научилась корреляции между первой цифрой первого числа и последней цифрой ответа в обучающей выборке. Это классический shortcut learning, но в контексте grokking он выглядит как осмысленная стратегия.
Такие шаблоны – не ошибка, а неизбежное следствие того, как трансформеры минимизируют loss. Они ищут любые статистические регулярности, даже если это не "настоящая" арифметика. В больших моделях это масштабируется до сложных жульничеств в рассуждениях.
3 Визуализируем матрицу жульничества
Вытаскиваем вес из линейного слоя головы, отвечающей за копирование первой цифры. Видно, что он активируется только на определенных позициях.
# Берем одну из голов внимания в последнем слое
layer = model.layers[-1].self_attn
# attention_scores shape: [batch, heads, seq_len, seq_len]
# Смотрим на head 2 для примера
cheating_head = attention_scores[:, 2, :, :]
# Визуализируем для одного примера
import matplotlib.pyplot as plt
plt.imshow(cheating_head[0].detach().cpu(), cmap='hot')
plt.xlabel("Query positions")
plt.ylabel("Key positions")
plt.title("Паттерн жульничества: внимание на '=' и первую цифру")
plt.show()
На картинке виден яркий столбец на позиции символа '=' и слабая связь с позицией первой цифры. Это и есть механизм – модель использует '=' как якорь, чтобы скопировать модифицированную первую цифру в ответ.
Почему weight decay заставляет модель жульничать умнее?
Без weight decay модель может тысячу эпох сидеть в локальном минимуме, запоминая тренировочные примеры. Weight decay (L2 регуляризация) постепенно обнуляет веса, не критичные для задачи. Это заставляет модель искать более компактные, обобщающие решения – но иногда эти решения оказываются жульническими паттернами, а не истинными алгоритмами.
В нашем эксперименте с weight decay=0.1 grokking (переход к обобщению) наступал примерно на 300-й эпохе. Без него – после 1500+ эпох, если вообще наступал.
Что это значит для больших LLM?
То же самое, но в масштабе. Когда Llama-3 или Qwen-2.5 решают логическую задачу, они могут опираться не на цепочку рассуждений, а на поверхностные статистические сходства с текстами из тренировочных данных. Anthropic, вскрывая Claude, нашла целые «нейронные схемы», отвечающие за поддельные рассуждения.
Механистическая интерпретируемость – единственный способ найти эти схемы и, возможно, исправить их. Не через промпты, а через прямое вмешательство в веса. Это как хирургия мозга модели.
Популярный миф: большие модели менее склонны к жульничеству. Нет. Они просто жульничают на более высоком уровне абстракции. Вместо копирования цифр – заученные шаблоны аргументации, которые выглядят как логика.
Как искать жульничество в своей модели?
- Начните с простой задачи, где известно правильное алгоритмическое решение (арифметика, сортировка строк).
- Обучите небольшую модель с weight decay. Дождитесь grokking (резкий рост accuracy на валидации).
- Зафиксируйте несколько примеров, где модель ошибается. Проанализируйте паттерны ошибок – они часто указывают на границы жульнического алгоритма.
- Используйте библиотеки для визуализации внимания (например, BertViz, обновленную под PyTorch 2.5). Ищите неожиданные, слишком сильные связи между токенами.
- Попробуйте «абилитацию» – отключите подозрительные головы внимания или нейроны и посмотрите, как изменится поведение. Если точность падает на определенном типе примеров, вы нашли схему жульничества.
Инструментарий быстро развивается. Если в 2024-м для такого анализа нужны были костыли, то к 2026-му в PyTorch вошли нативные функции для извлечения активаций с графовой трассировкой.
И что, теперь всем становиться нейрохирургами ИИ?
Не всем. Но если вы тренируете модели для критичных задач – медицинских, финансовых, – слепое доверие к accuracy смертельно. Модель может жульничать именно в тех edge-кейсах, где ошибка стоит дорого.
Mechanistic Interpretability превращается из академической дисциплины в инженерную практику. Компании вроде Anthropic и OpenAI уже используют ее для поиска уязвимостей в своих моделях перед релизом. Скоро это будет стандартным этапом пайплайна, как code review.
А пока – тренируйте маленькие модели, вскрывайте их, ищите жульничество. Это лучший способ понять, что на самом деле происходит внутри гигантских LLM. Они не волшебные – они просто очень хитрые.