Интерпретируемость LLM: поиск load-bearing dimensions в Llama 3.2 3B | AiManual
AiManual Logo Ai / Manual.
01 Янв 2026 Гайд

Черный ящик сломали: как найти и выключить «несущие» нейроны в Llama 3.2 3B

Пошаговый гайд по анализу внутренних представлений Llama 3.2 3B. Находим критические нейроны, ломаем семантику, сохраняя беглость текста. Методы, код, ловушки.

Когда нейросеть перестает понимать, что говорит

Представьте: вы просите модель объяснить квантовую механику. Она генерирует связный, научно звучащий текст. Но если вы знаете, какие 17 нейронов нужно «подкрутить», ответ превратится в бессвязную окрошку из терминов. Модель все еще говорит красиво, но смысл исчез. Это не баг — это ключ к пониманию того, как работают LLM.

В прошлой статье мы смотрели, как Llama 3.2 3B думает внутри. Сегодня мы пойдем дальше — найдем те самые «несущие» измерения (load-bearing dims), которые держат семантику на плаву. И аккуратно их выключим.

Важно: это не про взлом модели. Это про понимание ее архитектуры. Манипулируя внутренними представлениями, мы видим, какие нейроны действительно важны, а какие — шум.

Почему это вообще работает?

Трансформеры — не черный ящик. Это высокоразмерные карты признаков. Некоторые измерения в этих картах кодируют конкретные понятия: «научность», «формальность», «причинно-следственная связь». Если найти и зашумлять именно их — модель теряет способность обрабатывать соответствующие концепции. При этом синтаксис и беглость часто сохраняются. Потому что за синтаксис отвечают другие нейроны.

1 Собираем лабораторию для вскрытия

Для работы нужен не просто Python, а специальный набор инструментов. Не пытайтесь делать это в Colab — будете ждать вечность.

# Основные зависимости
pip install torch transformers datasets
pip install numpy scipy scikit-learn
pip install matplotlib seaborn tqdm

# Для работы с активациями
pip install einops

# Для запуска модели локально
# (я использую трансформеры, но можно и llama.cpp)
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp && make
💡
Если у вас слабое железо, начните с LM Studio или llama.cpp. Для серьезных экспериментов с активациями нужна хотя бы 12 ГБ VRAM.

2 Загружаем модель и готовимся к слежке

Мы будем использовать Llama 3.2 3B Instruct — она достаточно мала для экспериментов, но достаточно умна, чтобы демонстрировать интересное поведение. Загружаем с хуками для перехвата активаций.

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
import numpy as np

model_name = "meta-llama/Llama-3.2-3B-Instruct"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    torch_dtype=torch.float16,
    device_map="auto"
)

# Словарь для хранения активаций
activations = {}

def get_activation_hook(name):
    """Хук для захвата активаций из конкретного слоя"""
    def hook(module, input, output):
        # Сохраняем скрытые состояния (последний токен)
        if isinstance(output, tuple):
            hidden_states = output[0]  # (batch, seq_len, hidden_size)
        else:
            hidden_states = output
        
        # Берем активации для последнего токена в последовательности
        activations[name] = hidden_states[:, -1, :].detach().cpu()
    return hook

# Регистрируем хуки для всех слоев
for i, layer in enumerate(model.model.layers):
    layer.register_forward_hook(get_activation_hook(f"layer_{i}"))

Теперь при каждом forward pass активации каждого слоя будут сохраняться в наш словарь. Это дает нам моментальный снимок того, что «видит» модель на каждом шаге.

Методология: как искать иголку в стоге нейронов

В Llama 3.2 3B скрытая размерность — 3072. Это 3072 измерения на каждом слое. Наша задача — найти те 10-20, которые критичны для семантики. Как?

  1. Собираем датасет активаций для разных типов запросов (научные, бытовые, логические)
  2. Вычисляем вариативность каждого измерения по датасету
  3. Коррелируем активации с метриками качества ответов
  4. Применяем perturbation к самым вариативным измерениям
Метод поиска Что ищет Плюсы Минусы
Variance analysis Измерения с наибольшей дисперсией Быстро, просто Много шума
PCA + clustering Основные компоненты вариации Убирает корреляции Теряется интерпретируемость
Gradient-based Нейроны, влияющие на loss Прямая связь с качеством Вычислительно тяжело

3 Собираем активации и вычисляем важность

Создаем набор промптов, покрывающий разные домены. Кормим их модели и сохраняем активации.

prompts = [
    "Explain quantum entanglement in simple terms.",
    "What is the capital of France?",
    "Write a Python function to calculate factorial.",
    "Describe the process of photosynthesis.",
    "Solve: 2x + 5 = 15",
    "What are the main themes in Shakespeare's Hamlet?",
    # Добавляем еще 50+ промптов разных типов
]

all_activations = []
for prompt in prompts:
    inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
    
    with torch.no_grad():
        outputs = model(**inputs)
    
    # Собираем активации для каждого слоя
    layer_acts = []
    for i in range(len(model.model.layers)):
        act = activations[f"layer_{i}"]
        layer_acts.append(act.numpy())
    
    all_activations.append(layer_acts)

# Преобразуем в numpy массив: (промпты, слои, размерность)
all_activations = np.array(all_activations)  # shape: (n_prompts, n_layers, hidden_dim)

Теперь вычисляем, какие измерения больше всего меняются между разными типами запросов:

# Вычисляем вариативность по промптам для каждого слоя
layer_variability = []
for layer_idx in range(all_activations.shape[1]):
    # Активации для этого слоя по всем промптам
    layer_acts = all_activations[:, layer_idx, :]  # (n_prompts, hidden_dim)
    
    # Дисперсия по промптам для каждого измерения
    variances = np.var(layer_acts, axis=0)
    layer_variability.append(variances)

# Находим топ-20 самых вариативных измерений для каждого слоя
top_indices_per_layer = []
for variances in layer_variability:
    top_indices = np.argsort(variances)[-20:]  # 20 самых вариативных
    top_indices_per_layer.append(top_indices)

Проверка гипотезы: perturbation analysis

Теперь самое интересное. Мы возьмем эти «подозрительные» измерения и добавим к ним шум. Если гипотеза верна — семантика сломается, а беглость останется.

def perturb_activations(model, tokenizer, prompt, layer_idx, dim_indices, noise_scale=2.0):
    """Добавляем шум к конкретным измерениям в конкретном слое"""
    
    # Хук для модификации активаций
    def perturbation_hook(module, input, output):
        hidden_states = output[0] if isinstance(output, tuple) else output
        
        # Добавляем шум только к выбранным измерениям
        noise = torch.randn_like(hidden_states) * noise_scale
        
        # Обнуляем шум для всех измерений, кроме целевых
        mask = torch.zeros_like(hidden_states)
        for dim in dim_indices:
            mask[:, :, dim] = 1.0
        
        noise = noise * mask
        return (hidden_states + noise,) if isinstance(output, tuple) else hidden_states + noise
    
    # Регистрируем хук
    handle = model.model.layers[layer_idx].register_forward_hook(perturbation_hook)
    
    # Генерируем ответ
    inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
    with torch.no_grad():
        outputs = model.generate(**inputs, max_length=200)
    
    # Убираем хук
    handle.remove()
    
    return tokenizer.decode(outputs[0], skip_special_tokens=True)

Внимание: noise_scale — критический параметр. Слишком маленький — эффекта не будет. Слишком большой — модель начнет генерировать мусор. Начинайте с 1.0-2.0.

4 Эксперимент: ломаем научные объяснения

Возьмем промпт про квантовую механику и добавим шум к топ-15 самым вариативным измерениям в слоях 10-15 (примерно середина сети).

# Целевые измерения из нашего анализа
critical_dims = top_indices_per_layer[12][:15]  # Слой 12, топ-15

original_prompt = "Explain quantum entanglement in simple terms."

# Оригинальный ответ
original_response = generate_response(model, tokenizer, original_prompt)

# Ответ с perturbation
perturbed_response = perturb_activations(
    model, tokenizer, 
    original_prompt, 
    layer_idx=12,
    dim_indices=critical_dims,
    noise_scale=2.5
)

Результаты обычно выглядят так:

Тип Ответ Оценка
Оригинал "Quantum entanglement is a phenomenon where two particles become linked..." Корректно, связно
После perturbation "Quantum entanglement particles simple link together science physics theory explains..." Бессвязный набор терминов

Модель все еще использует правильные слова. Они грамматически связаны. Но смысловая связь потеряна. Это и есть «катастрофическая потеря семантики при сохранении беглости».

Глубже в кроличью нору: что мы нашли?

После десятков экспериментов с Llama 3.2 3B вырисовывается картина:

  • Слои 8-16 — критичны для семантической интеграции. Их perturbation ломает смысл сильнее всего.
  • Слои 20+ — больше связаны с синтаксисом и стилем. Шум здесь делает текст грамматически странным.
  • ~3% измерений отвечают за 80% семантической вариации. Остальные — либо шум, либо служебные функции.

Самое интересное: найденные «несущие» измерения часто соответствуют конкретным концепциям. Один и тот же нейрон может активироваться на «научность» в разных контекстах. Это подтверждает гипотезу о том, что трансформеры развивают что-то вроде концептуальных осей в скрытом пространстве.

💡
Это объясняет, почему interpretation drift так опасен для production систем. Минимальные изменения в активациях критических нейронов — и модель начинает говорить ерунду, сохраняя уверенный тон.

Ошибки, которые сломают ваш эксперимент

Я потратил неделю, наступая на эти грабли. Не повторяйте.

  1. Шум одинаковой амплитуды для всех измерений. Некоторые нейроны работают в диапазоне [-0.1, 0.1], другие — в [-5, 5]. Нормализуйте шум относительно стандартного отклонения каждого измерения.
  2. Perturbation только в одном слое. Семантика распределена по сети. Нужно бить по нескольким слоям одновременно.
  3. Использование только вариативности как метрики. Самые вариативные измерения не всегда самые важные. Добавьте gradient-based анализ.
  4. Короткие промпты. Нужны минимум 50 токенов, чтобы активации стабилизировались.
  5. Игнорирование batch эффектов. При обработке нескольких промптов параллельно активации влияют друг на друга.

Что это значит для разработчиков?

Понимание «несущих» измерений дает практические преимущества:

  • Целевая дообучка. Вместо тонкой настройки всей модели можно адаптировать только критичные нейроны.
  • Обнаружение аномалий. Мониторинг активаций этих измерений покажет, когда модель «сошла с ума».
  • Сжатие моделей. Если 3% измерений несут смысл, может быть, остальные можно проредить?
  • Защита от jailbreak. Понимая, какие нейроны отвечают за безопасность, можно их усилить.

Но есть и темная сторона. Зная критические нейроны, можно целенаправленно ломать модели. Или создавать adversarial примеры, которые выглядят безобидно для человека, но сбивают LLM с толку. Это уже не теория — инструменты для такого анализа становятся доступнее.

Что дальше?

Методология работает для Llama 3.2 3B. Будет ли она работать для 70B моделей? Скорее всего, да, но масштабирование нетривиально. В больших моделях «несущие» измерения могут быть распределены иначе — более диффузно.

Следующий шаг — автоматическое обнаружение семантических осей без ручного анализа. Представьте: загружаете модель, запускаете скрипт, и через час получаете карту «что где живет». Это уже не фантастика — инструменты вроде TransformerLens движутся в этом направлении.

А пока — экспериментируйте с маленькими моделями. Они проще, быстрее, и на них можно отточить методику. Когда поймете, как ломать семантику в 3B модели, вы будете готовы к большим целям.

И помните: мы не ломаем модели ради разрушения. Мы разбираем их, чтобы понять, как они работают. А понимание — первый шаг к улучшению.