Вот типичная картина: вы запускаете GPT-4o или Claude 3.7, получаете гениальный ответ и думаете – «какого черта?». Как куча умножений матриц порождает смысл? Большинство довольствуется магией. Но некоторые смотрят на этот черный ящик и хотят его вскрыть отверткой. Это и есть Mechanistic Interpretability (MI) – дисциплина для параноиков, которые не верят в магию.
В 2026 году это уже не академическая забава. Когда модель стоимостью в миллионы долларов внезапно выдает опасные инструкции, понимать «почему» становится вопросом безопасности. Не говоря уже о том, что без интерпретируемости вы не сможете починить сломанное поведение. Вы будете просто тыкать в промпты наугад.
Зачем это вам? (Спойлер: не для красоты)
Представьте, что ваша fine-tuned Llama 3.3 начала врать в ответах про медицину. Вы можете:
- Добавить больше примеров в датасет (надеяться)
- Поиграть с гиперпараметрами (помолиться)
- Или залезть внутрь и посмотреть, какой именно нейрон активируется на медицинских терминах и какие ассоциации он выдает
Третий вариант – это MI. Вы не просто смотрите на вход и выход. Вы ставите модель под микроскоп, замораживаете выполнение на определенном слое и спрашиваете: «Что ты сейчас думаешь, дружок?».
Важно: Mechanistic Interpretability ≠ Explainable AI (XAI). XAI часто отвечает «почему модель так решила» через приближения (типа SHAP). MI отвечает «как именно она это сделала» на уровне отдельных нейронов и внимания. Это reverse engineering, а не объяснение постфактум.
Инструментарий: что нужно кроме храбрости
В 2026 году экосистема созрела. Раньше нужно было писать тонны кода с нуля. Теперь есть библиотеки, которые делают грязную работу.
| Инструмент | Для чего | Состояние на 05.02.2026 |
|---|---|---|
| TransformerLens | Основная работа с активациями, вмешательства | Активно развивается, полная поддержка Llama 3.3, Qwen-2.5-32B, новых архитектур |
| Neuroscope | Визуализация активаций в браузере | Добавлена поддержка grouped-query attention (GQA) из последних моделей |
| NNSight | «Отладчик» для LLM, вмешательство в реальном времени | Стабильная версия, работает с моделями через Hugging Face |
| CircuitsVis | Визуализация паттернов внимания | Интеграция с Jupyter, улучшенная работа с длинными контекстами |
Начнем с установки. Не пытайтесь ставить все сразу – начните с TransformerLens, это Swiss Army knife для MI.
pip install transformer_lens==1.12.0 # Актуально на 05.02.2026
pip install circuitsvis
pip install plotly # для графиков
1 Загружаем модель и смотрим, что внутри
Первое, что удивляет новичков – модели не так уж черны. Мы можем загрузить любую модель из Hugging Face и сразу получить доступ ко всем внутренностям.
import transformer_lens
import torch
from transformer_lens import HookedTransformer
# Загружаем не самую большую модель для экспериментов
# На 2026 год Llama 3.1 8B уже считается «маленькой» для экспериментов
model = HookedTransformer.from_pretrained(
"meta-llama/Llama-3.1-8B",
device="cuda" if torch.cuda.is_available() else "cpu",
dtype=torch.bfloat16 # Современные модели используют bfloat16
)
# Смотрим, что у нас есть
print(f"Модель: {model.cfg.model_name}")
print(f"Слоев: {model.cfg.n_layers}")
print(f"Голов внимания: {model.cfg.n_heads}")
print(f"Размерность: {model.cfg.d_model}")
print(f"Словарь: {model.cfg.d_vocab} токенов")
Уже здесь видна первая странность. HookedTransformer – это обертка, которая добавляет «крючки» (hooks) в модель. Эти крючки позволяют перехватывать активации в любой точке сети. Без них вы просто видите вход и выход, как обычный пользователь API.
2 Первый вскрытие: что происходит при генерации?
Давайте сделаем простейший эксперимент: прогоним промпт через модель и сохраним активации со всех слоев. Это как сделать МРТ мозга во время мышления.
prompt = "Столица Франции – это"
tokens = model.to_tokens(prompt)
# Словарь для хранения активаций
activations = {}
# Функция-крючок, которая будет вызываться во время forward pass
def save_activation_hook(activation, hook):
# hook.name содержит информацию о месте (например, 'blocks.0.attn.hook_z')
activations[hook.name] = activation.detach().cpu()
# Регистрируем крючки на всех слоях для внимания и MLP
hook_locations = [
f"blocks.{layer}.attn.hook_z" for layer in range(model.cfg.n_layers)
] + [
f"blocks.{layer}.mlp.hook_post" for layer in range(model.cfg.n_layers)
]
# Запускаем модель с крючками
with model.hooks(fwd_hooks=[(loc, save_activation_hook) for loc in hook_locations]):
logits = model(tokens)
# Получаем предсказание
next_token = model.tokenizer.decode(logits[0, -1].argmax().item())
print(f"Следующий токен: {next_token}")
print(f"Сохранено {len(activations)} тензоров активаций")
Теперь в activations у нас лежит примерно 2 * n_layers тензоров. Для Llama 3.1 8B это 32 слоя * 2 = 64 тензора. Каждый тензор имеет размерность [batch, position, features].
И вот первый инсайт: вы можете увидеть, как информация течет через сеть. Ранние слои (0-5) обычно работают с синтаксисом и базовой семантикой. Средние слои (6-20) строят представления. Поздние слои (21-31) готовят ответ.
3 Ищем нейроны-функции: кто отвечает за столицы?
Одна из ключевых идей MI: некоторые нейроны в MLP слоях кодируют конкретные понятия. Есть нейрон для «столичности», нейрон для «французскости» и т.д. Найдем их.
Сначала создадим датасет пар «страна – столица»:
country_capital_pairs = [
("Франция", "Париж"),
("Германия", "Берлин"),
("Италия", "Рим"),
("Япония", "Токио"),
("Россия", "Москва"),
("США", "Вашингтон"),
("Китай", "Пекин"),
("Индия", "Нью-Дели"),
]
# Токенизируем
activations_by_neuron = {}
for country, capital in country_capital_pairs:
prompt = f"Столица {country} – это"
tokens = model.to_tokens(prompt)
# Нас интересует активация на последнем токене (после «это»)
target_position = -1
# Запускаем и сохраняем активации MLP слоев
with model.hooks(fwd_hooks=[
(f"blocks.{{layer}}.mlp.hook_post", save_activation_hook)
for layer in range(model.cfg.n_layers)
]):
model(tokens)
# Для каждой пары сохраняем активации
activations_by_neuron[(country, capital)] = activations
Теперь проанализируем, какие нейроны наиболее активны на столицах:
import numpy as np
# Соберем статистику по нейронам
neuron_activations = {}
for layer in range(model.cfg.n_layers):
key = f"blocks.{layer}.mlp.hook_post"
# Размерность активаций: [batch=1, position, d_mlp]
# d_mlp обычно 4 * d_model для Llama
# Соберем активации для всех примеров
all_acts = []
for (country, capital), acts in activations_by_neuron.items():
# Берем активацию на последнем токене
act = acts[key][0, -1, :].numpy() # [d_mlp]
all_acts.append(act)
# Преобразуем в массив [n_examples, d_mlp]
all_acts = np.stack(all_acts, axis=0)
# Для каждого нейрона считаем среднюю активацию
mean_activation = np.mean(all_acts, axis=0)
std_activation = np.std(all_acts, axis=0)
# Найдем нейроны с наибольшим средним
top_neuron_indices = np.argsort(mean_activation)[-10:] # Топ-10
print(f"\nСлой {layer}: топ нейроны для столиц")
for idx in top_neuron_indices[::-1]: # От большего к меньшему
print(f" Нейрон {idx}: mean={mean_activation[idx]:.3f}, std={std_activation[idx]:.3f}")
Если вам повезет, вы найдете нейроны, которые сильно активируются на всех столицах. В реальности все сложнее – понятия распределены по многим нейронам. Но иногда находятся удивительно чистые «мононейроны».
Предупреждение: не ожидайте, что каждый нейрон будет соответствовать человеческому понятию. Современные модели (особенно после 2024 года) стали более распределенными. «Нейрон для столиц» может оказаться на самом деле «нейроном для географических иерархических отношений».
4 Визуализируем внимание: кто на кого смотрит
Механизм внимания – это самое интересное в трансформерах. Он показывает, какие части контекста модель считает важными. Давайте визуализируем это для многослойного случая.
import circuitsvis as cv
import pandas as pd
# Возьмем более сложный промпт
prompt = "Илон Маск основал компанию Tesla, которая производит электромобили"
tokens = model.to_tokens(prompt)
# Получим матрицы внимания
attention_patterns = {}
def save_attention_hook(attn, hook):
# attn имеет размерность [batch, head, query_pos, key_pos]
attention_patterns[hook.name] = attn.detach().cpu()[0] # Берем первый batch
# Регистрируем крючки для внимания
hook_locations = [
f"blocks.{layer}.attn.hook_pattern" for layer in range(model.cfg.n_layers)
]
with model.hooks(fwd_hooks=[(loc, save_attention_hook) for loc in hook_locations]):
model(tokens)
# Преобразуем токены обратно в текст для подписей
token_labels = [model.tokenizer.decode(t) for t in tokens[0]]
# Визуализируем внимание для первого слоя, всех голов
layer = 0
attn_matrix = attention_patterns[f"blocks.{layer}.attn.hook_pattern"] # [head, query, key]
# CircuitsVis ожидает данные в определенном формате
# Преобразуем для одной головы для примера
head = 3
attention_df = pd.DataFrame(
attn_matrix[head].numpy(),
index=token_labels,
columns=token_labels
)
# Это упрощенный пример. В реальности используйте cv.attention.attention_heads
# для интерактивной визуализации в Jupyter
Что вы увидите? В ранних слоях внимание часто следует за синтаксическими связями (существительное → прилагательное, глагол → дополнение). В поздних слоях появляются семантические связи («Илон Маск» → «Tesla» → «электромобили»).
На 2026 год появилась интересная особенность: в моделях с длинным контекстом (128K+) механизм внимания становится разреженным. Модель учится «фокусироваться» на релевантных частях, а не равномерно смотреть на весь контекст.
Продвинутые техники: когда простого взгляда недостаточно
Активационная патч-инженерия
Что если мы хотим понять, какие части сети ответственны за конкретное поведение? Например, за то, что модель знает, что Париж – столица Франции?
Метод: берем два промпта – один корректный («Столица Франции – это Париж»), один некорректный («Столица Франции – это Берлин»). Запускаем корректный, но подменяем активации из некорректного в определенных слоях. Смотрим, где подмена ломает правильный ответ.
# Упрощенный пример патч-инженерии
def run_with_patch(model, corrupted_tokens, clean_tokens, patch_layer):
"""Запускаем модель с подменой активаций на определенном слое"""
# Сначала получаем активации от corrupted промпта
corrupted_activations = {}
def save_corrupted_hook(act, hook):
if hook.layer() == patch_layer:
corrupted_activations[hook.name] = act
# Запускаем corrupted
with model.hooks(fwd_hooks=[(f"blocks.{patch_layer}.attn.hook_z", save_corrupted_hook)]):
model(corrupted_tokens)
# Теперь запускаем clean, но подменяем активации
def patch_hook(act, hook):
if hook.layer() == patch_layer:
return corrupted_activations[hook.name]
return act
with model.hooks(fwd_hooks=[(f"blocks.{patch_layer}.attn.hook_z", patch_hook)]):
logits = model(clean_tokens)
return logits
# Пример использования
clean_prompt = "Столица Франции – это Париж"
corrupted_prompt = "Столица Франции – это Берлин"
clean_tokens = model.to_tokens(clean_prompt)
corrupted_tokens = model.to_tokens(corrupted_prompt)
# Тестируем разные слои
for layer in [5, 10, 15, 20, 25]:
logits = run_with_patch(model, corrupted_tokens, clean_tokens, layer)
predicted_token = model.tokenizer.decode(logits[0, -1].argmax().item())
print(f"Слой {layer}: предсказание после патча = '{predicted_token}'")
Если на слое 15 подмена активаций меняет «Париж» на «Берлин», значит этот слой критически важен для знания о столицах. Это мощный метод локализации функций.
Анализ словаря признаков
Современный тренд (2025-2026): вместо анализа отдельных нейронов анализировать «направления» в пространстве активаций. Каждое направление соответствует какому-то признаку.
Библиотеки вроде SAE (Sparse Autoencoders) учатся разлагать активации на разреженные признаки. Один признак может быть «столичность», другой «французскость», третий «географический объект».
# Пример загрузки предобученного SAE для Llama 3.1
# (В реальности нужно обучать свой или скачать предобученный)
try:
# Это примерный API, детали зависят от реализации
from sae_lens import SAE
sae = SAE.from_pretrained("jbloom/llama-3.1-8B-sae")
# Получаем активации для промпта
activations = model.run_with_cache("Столица Франции – это")["blocks.15.mlp.hook_post"]
# Декомпозируем на признаки
features = sae.encode(activations)
# features - разреженная матрица [batch, position, n_features]
# Каждый столбец соответствует одному признаку
# Находим наиболее активные признаки
feature_activity = features[0, -1, :].abs().cpu().numpy()
top_features = np.argsort(feature_activity)[-5:]
print("Топ-5 признаков для 'это' в контексте столиц:")
for feat_idx in top_features[::-1]:
# Можно посмотреть, какие токены максимально активируют этот признак
print(f" Признак {feat_idx}: активность={feature_activity[feat_idx]:.3f}")
except ImportError:
print("SAE Lens не установлена. pip install sae-lens")
Ошибки, которые сломают ваш анализ
Я видел, как люди тратят недели на бессмысленные эксперименты. Вот что не работает:
- Анализ на одном примере. Нейрон активировался на «котик» в одном промпте? Это еще ничего не значит. Нужна статистика по сотням примеров.
- Игнорирование распределенности. В современных моделях почти нет чистых «мононейронов». Понятия распределены по десяткам нейронов, а нейроны участвуют в десятках понятий.
- Забывание о контексте. Один и тот же нейрон может активироваться на «яблоко» (фрукт) и «Apple» (компания) в разных контекстах.
- Прямая интерпретация весов. Веса в матрицах внимания или MLP почти невозможно интерпретировать напрямую. Они оптимизированы для совместной работы, а не для человеческого чтения.
Зачем все это? (Реальные кейсы)
«Ну и что?» – спрашивает менеджер. Вот что:
- Дебаггинг сломанного поведения. Модель внезапно начала говорить, что 2+2=5? С помощью MI можно найти, какой нейрон «сломался» и либо починить его (через fine-tuning), либо исключить из ансамбля.
- Поиск уязвимостей. Модели понимают инструкции, но игнорируют их? MI показывает, где именно происходит «несогласование» между пониманием цели и генерацией.
- Улучшение эффективности. Если вы обнаружили, что 80% нейронов в некоторых слоях почти никогда не активируются – может, их можно prune без потери качества?
- Создание более контролируемых моделей. Зная, какие части сети отвечают за креативность, можно создавать «регуляторы», как в том исследовании про Llama 3.2.
Что будет дальше? (Прогноз на 2027)
Механистическая интерпретируемость перестанет быть нишевой дисциплиной. Вот тренды:
- Автоматизированный MI. Инструменты, которые сами будут предлагать гипотезы и проверять их, как авто-ML для интерпретации.
- Интерпретируемость мультимодальных моделей. Как связаны текстовые и визуальные представления в одном пространстве? Это следующая frontier.
- MI как часть training loop. Представьте: модель обучается, а система интерпретации постоянно мониторит, какие понятия она усваивает и нет ли проблем.
- Стандартизация метрик. Сейчас каждый исследователь придумывает свои метрики «интерпретируемости». К 2027 появятся стандартные бенчмарки.
Самый важный сдвиг: из исследовательской дисциплины MI превратится в инженерную практику. Так же, как когда-то unit-тесты из академической идеи стали обязательными для production кода.
И последний совет: не пытайтесь понять все сразу. Начните с одного слоя, одного типа анализа. Постройте интуицию. Эти модели сложны, но они не магические. Просто очень, очень сложные.
P.S. Если после этого гайда вы все еще думаете, что LLM – это просто «статистика на стероидах», посмотрите на активации нейрона, который зажигается на иронии. Статистика так не умеет.