Чернетка

Розріджені автоенкодери: виділення зрозумілих ознак із навченої мережі

У лютому 2026 року Goodfire опублікував рецензію описує, що знадобилося для дослідження можливості інтерпретації Kimi K2 Thinking, моделі суміші експертів із трильйонами параметрів. Цифра в заголовку була вражаючою: за один нічний запуск вони зібрали 3 мільярди активацій — приблизно половину кількості токенів англійської Вікіпедії — і в підсумку отримали близько 33 ТБ стислих тензорів на диску. Щоб зробити це практичним, вони розгалужували сервер висновків SGLang і додали гаки для передачі активацій із графічного процесора без обмеження пропускної здатності, безперервного пакетування, розділеного попереднього заповнення та 8-стороннього тензорного паралелізму.

Очевидне запитання: 3 мільярди активацій що, саме, і чому?

Коротка відповідь полягає в тому, що вони збирали навчальні дані для розрідженого автокодувальника — центрального інструменту інтерпретації останніх кількох років. SAE — це те, що бере прихований стан моделі, який зовні виглядає як непрозорий вектор плаваючих елементів, і перетворює його на список розпізнаваних ознак: «це цикл», «це вертикальний штрих», «це сигнал ввічливості», «це визначення функції Python». Отримавши цей список, ви можете будь-коли прочитати, про що думає модель, і — що більш дивно — ви можете його редагувати.

Ця стаття про сам SAE. Ми не будемо розгортати SGLang або збирати мільярди токенів. Замість цього ми використаємо 3-значний MNIST CNN від стаття CNN (цифри 0, 1, 2) як мініатюрну лабораторію. Все, що Goodfire створив у масштабі трильйонів параметрів — гачок, зібраний набір даних, кодер/декодер, покарання за розрідженість, втручання в керування — має прямий аналог у цьому масштабі. Справа в тому, що примітиви маленькі. Те, що важко в масштабі кордону, так це інженерія навколо них, до якої ми повернемося в кінці.

Вирішує проблему SAE

Коли ви тренуєте нейронну мережу наскрізно із зворотним поширенням, у функції втрат нічого не написано «будь ласка, зробіть так, щоб кожен прихований нейрон відповідав одній концепції, яку може інтерпретувати людина». Втрата лише говорить: виробляйте правильний вихід. Таким чином, оптимізатор може вести будь-який бухгалтерський облік, який йому зручний, і що він зазвичай вважає зручним, це впихнути кілька непов’язаних понять в один нейрон. Це явище називається полісемантичністю — один нейрон, багато значень — а механізм, що лежить в основі, називається суперпозицією — модель упаковує в прихований простір більше функцій, ніж вимірів, представляючи їх як напрямки, а не координати, вирівняні по осі.

У нашому 3-значному MNIST CNN легко побачити полісемантичність. Згадайте його приблизну форму зі статті CNN — кілька етапів conv+pool, зведення та невеликий щільний шар із, скажімо, 64 одиниць перед 3-класовим softmax:

model = keras.Sequential([
    keras.layers.Conv2D(16, 3, activation="relu", input_shape=(28, 28, 1)),
    keras.layers.MaxPool2D(2),
    keras.layers.Conv2D(32, 3, activation="relu"),
    keras.layers.MaxPool2D(2),
    keras.layers.Flatten(),
    keras.layers.Dense(64, activation="relu", name="features"),
    keras.layers.Dense(3, activation="softmax", name="logits"),
])

Рівень features створює 64-вимірний вектор — назвіть його h∈R64h \in \mathbb{R}^{64} — який потім зчитує класифікатор. Ми можемо запитати: на що відповідає єдина координата hh, скажімо, h17h_{17}?

Стандартний спосіб перегляду полягає в тому, щоб запустити весь навчальний набір через модель, записати значення h17h_{17} для кожного вхідного сигналу та подивитися на top-k зображень, які активували цей нейрон найсильніше. У такій невеликій мережі, як наша, ця купа найкращих зображень рідко буває чистою. Ви побачите такі речі, як: кілька нулів із товстою петлею, а також кілька двійок із широкою кривою внизу та, можливо, одну або дві одиниці, намальовані з незвичним нахилом. Neuron 17 — це не «петлевий детектор» чи «кривий детектор» — це змішаний детектор, який реагує на кілька шаблонів, які модель знайшла зручною для об’єднання. Таке об’єднання добре підходить для класифікатора, якому просто потрібно, щоб кінцевий softmax був правильним, але це катастрофа для тих, хто намагається прочитати, що представляє модель.

Це центральна проблема інтерпретації. Модель явно навчилася чогось корисного — вона отримала 99% тестового набору — але основа, яку вона використовує для зберігання своїх знань, не узгоджується з концепціями, які ми розпізнаємо. Концепції живуть десь у R64\mathbb{R}^{64}, але не на координатних осях.

Робота SAE полягає в тому, щоб знайти основу, яка узгоджується з концепціями, і зробити це без будь-яких міток — виключно з геометрії активацій.

Що насправді таке розріджений автокодер

У найзагальнішому розумінні автокодер — це пара мереж: кодер, який відображає вхідний вектор на деякий внутрішній код, і декодер, який відображає код назад. Все це навчено реконструювати свій вхід. Класичний автокодер пропускає вхідні дані через вузьке місце, яке є меншим за вхідні дані — це варіант стиснення, який використовується для усунення шумів, зменшення розмірності тощо.

Розріджений автокодер інвертує це. Внутрішній код ширший за вхідні дані, часто у 8, 16 і більше разів, але лише невелика кількість записів коду може бути відмінною від нуля для будь-якого введення. Таким чином, замість стиснення SAE розширює активацію в надповний словник і змушує розріджений вибір із цього словника для реконструкції оригіналу.

Конкретно, для нашого MNIST CNN SAE бере 64-вимірну активацію features hh і відображає її на набагато ширший вектор ознак f∈RDf \in \mathbb{R}^{D} — скажімо, D=512D = 512:

f=ReLU(Wenc h+benc)f = \mathrm{ReLU}(W_{\text{enc}}\, h + b_{\text{enc}}) h^=Wdec f+bdec\hat h = W_{\text{dec}}\, f + b_{\text{dec}}

Кодер складається з одного лінійного шару та ReLU. Декодер являє собою один лінійний шар (без нелінійності). WdecW_{\text{dec}} має 512 стовпців, по одному на функцію, кожен є вектором у R64\mathbb{R}^{64} — ці стовпці є атомами словника, напрямками, які вивчив SAE. Щоб реконструювати hh, декодер бере зважену суму тих стовпців, де ваги є активними записами ff.

Мета має два терміни:

L=∥h−h^∥22⏟reconstruction+λ∥f∥1⏟sparsity\mathcal{L} = \underbrace{\|h - \hat h\|_2^2}_{\text{reconstruction}} + \lambda \underbrace{\|f\|_1}_{\text{sparsity}}

Термін реконструкції говорить: декодер повинен точно зібрати hh. Термін розрідженості — штраф L1 для вектора ознак — змушує більшість записів ff дорівнювати точно нулю. Взаємодія полягає в усьому: без покарання за розрідженість найпростішим рішенням є встановити Wdec=Wenc−1W_{\text{dec}} = W_{\text{enc}}^{-1} (або його лівий зворотний) і використовувати кожну функцію потроху, що нічого не виграє порівняно з оригінальною основою. Через розрідженість SAE змушений знаходити невеликий набір напрямків, які після активації реконструюють hh — і найдешевший спосіб зробити це — змусити кожен напрямок відповідати повторюваному шаблону в активаціях. Патерн, за визначенням, є концепцією.

Є кілька варіантів, про які варто знати:

  • L1 SAE (класичний) використовують покарання L1 вище. Легко навчитися, але зміщення члена L1 показують величини вниз, що ускладнює подальший аналіз.
  • TopK SAEs замінює покарання L1 жорстким обмеженням: зберігайте лише kk найбільші записи ff і обнуляйте решту перед декодером. Немає зміщення величини, але потрібно вибрати kk.
  • JumpReLU використовує порогове значення, яке можна дізнатися, для кожної функції, отримуючи деякі переваги обох.

Для наших цілей версія L1 підходить — її найпростіше записати, а концептуальна картина однакова для всіх варіантів.

Коефіцієнт розширення D/dD/d і цільова розрідженість (кількість функцій має запускатися на один вхід) є двома основними регуляторами. Для нашої 64-вимірної активації MNIST розумною відправною точкою є словник із 512 функціями з ~8 активними функціями на введення. Для роботи Anthropic і Goodfire над граничними моделями типовими є коефіцієнти розширення 16x–64x і активні підрахунки в десятках, що частково є причиною такого великого апетиту до навчальних даних.

Збирання активації: універсальний примітив

Перш ніж ми зможемо навчити SAE, нам потрібен набір даних активацій цільової моделі. Це той крок, на який Goodfire витратив більшу частину своїх інженерних зусиль, оскільки в масштабі трильйонів параметрів кожна його частина є ворожою для пропускної здатності. У нашому масштабі це три лінії Кераса.

Базовий примітив однаковий у всіх масштабах: приєднати прямий хук до цільового шару, пропустити вхідні дані через модель і скопіювати вихід шару в буфер. У Keras найпростіший спосіб — побудувати підмодель, яка закінчується на цікавому шарі:

# Build a sub-model that returns the `features` layer activation, not the softmax.
feature_extractor = keras.Model(
    inputs=model.input,
    outputs=model.get_layer("features").output,
)

# Run the training set through it, in batches, and stack the results.
all_activations = feature_extractor.predict(X_train, batch_size=256)
# all_activations.shape == (N, 64)

Ось і врожай. Результатом є тензор (N,64)(N, 64) — один рядок на вхідне зображення, 64 значення активації на рядок — і на цьому тензорі ми будемо тренувати SAE. Для нашої 3-значної підмножини MNIST з ~18 000 навчальних зображень це приблизно 4,5 МБ плаваючих елементів. Ми можемо зберігати його в оперативній пам’яті та повторювати його скільки завгодно разів.

У PyTorch той самий примітив пишеться інакше — ви реєструєте перехоплення на цільовому модулі — але семантика ідентична:

captured = []

def hook(module, inputs, output):
    # Detach so we don't drag autograd state along, and move to CPU.
    captured.append(output.detach().cpu())

handle = model.features.register_forward_hook(hook)
with torch.no_grad():
    for x_batch in loader:
        _ = model(x_batch)         # forward pass — the hook fires
handle.remove()

all_activations = torch.cat(captured, dim=0)   # (N, 64)

Трубопровід Goodfire виглядає так само, тільки під величезним тиском. Kimi K2 Thinking розділено на 8 графічних процесорів за допомогою тензорного паралелізму, тому простий перехоплення кожного рангу дає вам часткові активації, які потрібно зібрати, або — якщо ви перехоплюєте після точки синхронізації — дубльовані дані на всіх 8 графічних процесорах. SGLang використовує безперервне пакетування, тому пакет під час попереднього заповнення не є чистим тензором (B,T,d)(B, T, d) — це згладжений тензор (∑Ti,d)(\sum T_i, d) із токенами з багатьох непов’язаних послідовностей, поєднаних разом, і вам потрібно відстежувати походження, щоб знати, яка активація надійшла від якого токена якого запиту. Довгі вхідні дані поділяються попереднім заповненням на фрагменти, тому активації одного документа надходять через кілька записів і їх потрібно з’єднати. Ніщо з цього не змінює те, що таке активація. Це лише змінює кількість інженерних робіт, необхідних для копіювання одного з GPU без зниження пропускної здатності.

Форму їх рішення варто описати, оскільки це корисний загальний шаблон. Сама передача вперед робить найдешевшу річ: скопіювати активацію в проміжний буфер на стороні графічного процесора, попередньо виділений з пулу, поставити в чергу невеликий запис метаданих (дескриптор тензора, походження та подія CUDA) у кільцевий буфер і продовжити. Окремий потік з підтримкою чекає на подію CUDA, копіює тензор у закріплену пам’ять ЦП після завершення роботи графічного процесора, а потік запису приймає закріплені тензори пам’яті та стискає їх на диск, реконструюючи записи для кожної послідовності. Три етапи, кожен з яких працює у власному темпі, розділені буферами. Прямий перехід ніколи не блокує дисковий ввід-вивід. Нам нічого з цього не потрібно для MNIST, але добре знати архітектуру — це те, як ви підштовхуєте хук до межі пропускної здатності в будь-якій системі, де швидкість прямого проходу має значення.

Навчання SAE

Коли ми маємо тензор активації, навчання SAE є невеликою самодостатньою проблемою оптимізації. Ось цикл у Keras, написаний прозоро, а не ідіоматично — кожен крок у втраті видно:

import numpy as np
import tensorflow as tf
from tensorflow import keras

D_INPUT = 64      # width of the layer we harvested
D_FEATURES = 512  # dictionary size — expansion factor of 8x
L1 = 1e-3         # sparsity coefficient

class SAE(keras.Model):
    def __init__(self):
        super().__init__()
        self.encoder = keras.layers.Dense(D_FEATURES, activation="relu")
        self.decoder = keras.layers.Dense(D_INPUT, activation=None,
                                          use_bias=True)

    def call(self, h):
        f = self.encoder(h)        # (B, D_FEATURES)  — sparse code
        h_hat = self.decoder(f)    # (B, D_INPUT)     — reconstruction
        return h_hat, f

sae = SAE()
opt = keras.optimizers.Adam(1e-3)

@tf.function
def train_step(h_batch):
    with tf.GradientTape() as tape:
        h_hat, f = sae(h_batch)
        recon_loss = tf.reduce_mean(tf.reduce_sum((h_batch - h_hat) ** 2, axis=1))
        l1_loss   = tf.reduce_mean(tf.reduce_sum(tf.abs(f), axis=1))
        loss = recon_loss + L1 * l1_loss
    grads = tape.gradient(loss, sae.trainable_variables)
    opt.apply_gradients(zip(grads, sae.trainable_variables))
    return loss, recon_loss, l1_loss

# Iterate over the harvested activations, many epochs.
ds = tf.data.Dataset.from_tensor_slices(all_activations).shuffle(10_000).batch(256)
for epoch in range(50):
    for h_batch in ds:
        train_step(h_batch)

Варто звернути увагу на дві речі. По-перше, саме CNN заморожено під час навчання SAE — ми ніколи не торкаємося його ваги. SAE — це паразит, що висить збоку моделі, живиться тензором активації, який ми зібрали один раз. По-друге, коефіцієнт L1 є домінуючим гіперпараметром. Занадто малий, і SAE із задоволенням використовує десятки функцій на вхід і втрачає можливість інтерпретації. Надто великий, і SAE відмовляється використовувати достатню кількість функцій для реконструкції hh, і термін реконструкції зростає. Корисною діагностикою під час навчання є L0 — середня кількість функцій із ненульовою активацією на один вхід. Для нашої установки ми б націлилися на щось на зразок L0 ≈ 8 з помилкою реконструкції невеликою часткою ∥h∥2\|h\|^2.

Одну пастку, яку варто позначити: SAE схильні до мертвих функцій — атомів словника, які ніколи не активуються для жодного введення та просто сидять. Виробничі установки (Anthropic, Goodfire) використовують різні хитрощі, щоб відновити їх, включаючи періодичну повторну ініціалізацію мертвих функцій у напрямку найбільшої помилки реконструкції. Для футляра для іграшок достатньо знати, що режим відмови існує.

Читання словника

Після навчання SAE словник Wdec∈R64×512W_{\text{dec}} \in \mathbb{R}^{64 \times 512} має 512 стовпців, і кожен стовпець є напрямком у вихідному просторі активації. Ми хочемо знати, що означає кожен стовпець. Стандартна процедура складається з двох кроків, і жоден з них не включає мітки.

Крок 1: для кожної функції знайдіть вхідні дані, які активують її найбільше. Проведіть весь навчальний набір через CNN, потім через кодувальник SAE, і для кожної з 512 функцій зберіть top-k зображень, код коду яких для цієї функції був найбільшим. Це дає вам невелику галерею вхідних даних для кожної функції.

# all_activations: (N, 64)        — harvested CNN activations
# all_codes:       (N, 512)       — SAE feature activations
_, all_codes_t = sae(all_activations)
all_codes = all_codes_t.numpy()

# For each feature, the indices of the top 16 most-activating inputs.
top_k = 16
top_indices = np.argsort(-all_codes, axis=0)[:top_k]   # (top_k, 512)

# For feature f, the gallery is X_train[top_indices[:, f]].

Крок 2. Подивіться на галерею. Для нашого 3-значного MNIST CNN, з добре навченим SAE, ви зазвичай бачите, що функції потрапляють у розпізнавані сегменти:

  • Кілька функцій, у галереях яких майже всі 0, усі 1 або всі 2. Це функції, вирівняні за класом — напрямки, які модель справді використовує для розрізнення, тепер ізольованих як окремі атоми словника.
  • Об’єкти, галереї яких змішані між класами, але мають спільний шаблон рівня штрихів — широкі нижні криві (з’являються через 2 секунди та іноді 0), сильні вертикальні штрихи (1 і хребет деяких 2), замкнуті петлі (переважно 0).
  • Кілька функцій, галереї яких виглядають не дуже. Зазвичай це мертві або майже мертві функції, які модель ніколи не використовувала, або шумові функції, які підхоплюють особливості навчального набору.

Це виграш: 64-вимірна активація, яка на рідних осях була полісемантичною та нечитабельною, була переписана як розріджена комбінація впізнаваних шаблонів. Ми не сказали SAE, що шукати. Покарання за розрідженість змусило його знаходити напрямки, які повторюються через багато вхідних даних, і ці повторювані напрямки виявляються тими самими шаблонами, які розпізнають люди — тому що і ми, і мережа дивимось на той самий набір даних і ту саму структуру завдань.

Кілька застережень. Можливість інтерпретації ознак залежить від якості моделі та вибраного шару. Ранні шари conv, як правило, надають контурів і текстур; щільний шар попереднього класифікатора має тенденцію надавати ознаки класу та частини; останній шар перед softmax має тенденцію давати майже вирівняні за класом функції, які менш цікаві. Також немає гарантії, що функція є односемантичною лише тому, що її топ-16 галерея виглядає чистою — галерея є зразком, а не доказом. Інструменти діагностики, такі як абляція ознак, допомагають відокремити чисті особливості від випадкових, але це поле все ще працює переважно за допомогою візуального огляду.

Кермування: редагування словника

Причина, по якій функції цікаві, полягає не лише в тому, що вони описують те, що робить модель, а в тому, що вони дозволяють нам змінювати те, що робить модель, хірургічним шляхом, нічого не перенавчаючи.

Хитрість проста. У нас є кодер SAE f=ReLU(Wench+benc)f = \mathrm{ReLU}(W_{\text{enc}} h + b_{\text{enc}}) і декодер h^=Wdecf+bdec\hat h = W_{\text{dec}} f + b_{\text{dec}}. Якщо ми вибираємо індекс функції ii, встановлюємо fif_i на якесь більше значення та повторно обчислюємо h^\hat h, ми маємо модифікований вектор активації. Якщо ми замінимо цей вектор назад у CNN — замінимо результат шару features на h^\hat h — і запустимо класифікатор зверху, ми просто скеруємо модель у напрямку функції ii.

У коді це найпростіше зробити, розділивши CNN на керівну точку:

# Two halves of the original CNN, split at the `features` layer.
feature_extractor = keras.Model(model.input, model.get_layer("features").output)
classifier_head   = keras.Model(model.get_layer("features").output, model.output)

def steer(x, feature_idx, strength=5.0):
    h = feature_extractor(x[None, ...])     # (1, 64)
    _, f = sae(h)                            # (1, 512)
    f_steered = f.numpy().copy()
    f_steered[0, feature_idx] += strength    # nudge the chosen feature
    h_steered = sae.decoder(f_steered)       # (1, 64) — new activation
    return classifier_head(h_steered)        # (1, 3)  — new probabilities

Якщо функція 42 є однією з наших функцій «вертикального штриха / 1-ish», виклик steer(image_of_0, feature_idx=42, strength=5.0) помітно підштовхне масу ймовірності класифікатора до класу 1, навіть незважаючи на те, що ми ніколи нічого не говорили класифікатору про 1s — ми просто додали більше напрямку, який, як виявило SAE, корелює з 1, і класифікатор відреагував відповідно. При досить великій міцності класифікатор перевертається. При помірній силі прогноз залишається правильним, але зі зниженою впевненістю.

Це найменша з можливих версій результату, який Goodfire описав як “керування в реальному часі в ланцюжку думок”. У їхньому випадку цільова модель є авторегресійною, активація, якою керують, є залишковим потоком на якомусь рівні трансформатора, і керування застосовується на кожному кроці декодування під час генерації моделі — тому такі функції, як «будьте обережнішими з цією відповіддю» або «менше говорити, як помічник із кодування», можна збільшити або зменшити в середині відповіді. Геометрія – це саме те, що ми щойно зробили: виберіть функцію, додайте її до активації, дозвольте решті моделі працювати зі зміненого стану.

Дві речі роблять це важливим, а не цікавістю. По-перше, це показує, що функції, які знаходить SAE, є причинно-наслідковими, а не просто описовими — їхнє просування змінює поведінку передбачуваним чином. Функція, яка тільки корелює з поведінкою, не будучи частиною того, як її обчислює модель, не керуватиме. По-друге, рульове керування дешеве: без тонкого налаштування, без RL, без даних з мітками. SAE навчається один раз. Після цього кожна функція в словнику є вільною поведінковою ручкою.

Чому це важко в прикордонному масштабі

Все, що ми зробили, вміщується в кілька сотень рядків Python на ноутбуці. Стрибок розміру до моделі з трильйонами параметрів — це те, про що насправді розповідає блог Goodfire. Варто зрозуміти, чому кожна частина стає складнішою, оскільки складність не концептуальна, а операційна.

Обсяг даних. Наш тензор активації 64-dim для 18 000 вводів становить ~4,5 МБ. Залишковий потік Kimi K2 Thinking, залежно від рівня, має кілька тисяч розмірів, і SAE такого масштабу потребують мільярдів токенів навчальних даних, щоб знайти чисті функції. 3B токенів × кілька тисяч плаваючих елементів × 2 байти (після квантування) досягають ~33 ТБ у стисненому вигляді. Ви не можете зберегти це в RAM. Ви навіть не можете зручно тримати його на локальному SSD.

Пропускна здатність. При гіпотетичній пропускній здатності 100 токенів/с, 3 млрд токенів — це рік часу GPU. Щоб зробити це за одну ніч, потрібні десятки тисяч токенів за секунду. Це територія виробничих серверів висновків — SGLang, vLLM — що означає, що ви повинні додати свій гачок до системи, яка була песимізованою через припущення, що її ніхто не підхоплює.

Ускладнення з сервером висновків. Безперервне пакетування змішує непов’язані послідовності в один тензор і вимагає походження кожного маркера. Попереднє заповнення на фрагменти розділяє документи на кілька проходів вперед. Тензорний паралелізм розподіляє ваги між графічним процесором і змушує вас вибирати де в конвеєрі рівня підключатися, щоб ви отримували повну активацію без зайвих зборів. CUDA створює графіки перед записом і не любить умовні хуки. Кожен із них є розв’язним; разом вони підштовхують вас до того, що Goodfire називає хірургічною вилкою — невеликі цільові виправлення, а не переписування, оскільки сервер висновків рухається швидко, і ви не хочете успадкувати постійний тягар обслуговування.

Зберігання та введення/виведення. Коли активації знаходяться в закріпленій пам’яті ЦП, їх потрібно записати досить швидко, щоб не відставати від передавання. Це означає асинхронні багатоетапні конвеєри — проміжні буфери, кільцеві буфери, побічні потоки — тому затримка дискового вводу-виводу не пов’язана з висновками. Ментальна модель така ж, як і добре розроблений лісозаготівельний трубопровід: виробники не можуть блокувати, споживачі працюють у своєму власному темпі, розміри всього підібрані таким чином, щоб поглинати невідповідність у найгіршому випадку.

Жодне з цього не змінює самого SAE. Кодер все ще є одним лінійним шаром, декодер все ще одним лінійним шаром, втрата все ще є реконструкцією плюс розрідженість. Що змінюється, так це підготовка набору даних, і ось де йде інженерна робота. Опис Goodfire — найкращий публічний обліковий запис того, як це виглядає в деталях.

Чому це важливо

Історія SAE цікава сама по собі, але ширший момент полягає в тому, що інтерпретація змінилася від «дивлячись на ваги уваги та сподіваючись» до легкої задачі оптимізації з придатними результатами. Ви можете запустити SAE на будь-якому рівні будь-якої моделі, для якої у вас є ваги та активації, і ви можете використовувати отриманий словник для читання, позначення та керування поведінкою. Це змінює інтерпретацію з «ми дослідили цю маленьку модель, і це деякі спостереження» до «у нас є словник функцій для серійної моделі, а ось ручки».

Залишилася прогалина – інфраструктура. Ми зібрали 18 000 активацій за один виклик predict. Goodfire зібрав 3 мільярди за одну ніч, ретельно перебудувавши примітив збирання на сервері продуктивних висновків. Концептуальний механізм невеликий. Інженерія - це те, що робить його землею.

Якщо ви хочете пограти з цим самостійно, робочий процес точно такий же:

  1. Навчіть (або завантажте) модель. MNIST CNN від наша стаття CNN працює.
  2. Виберіть шар і зберіть його активації на типовому наборі даних. Достатньо підмоделі Keras або переднього гачка PyTorch.
  3. Тренуйте SAE на цих активаціях із коефіцієнтом розширення 8x–16x і коефіцієнтом L1, налаштованим для отримання невеликої цілі L0.
  4. Візуалізуйте найпопулярніші активаційні входи кожної функції. Знайдіть впізнаваних.
  5. Керуйте, змінюючи вектор коду SAE і запускаючи класифікатор на реконструйованій активації. Слідкуйте за зміною прогнозу.

Це весь цикл, і він масштабується — за допомогою інженерії — аж до Kimi K2 Thinking.

Перегляд зображення

Змінюйте масштаб жестом щипка або клавішами + / −. Переміщуйте зображення перетягуванням або стрілками. Esc — закрити.