В данной статье автор попытается осмыслить архитектуру трансформеров - все наслышаны, все всё знают, но никто толком не понимает - а посему цель этой статьи - попытаться дать объяснения на концептуальном уровне, без заумных математических формул и нечитаемых дефиниций. Признаю, что объяснять теорию простыми и понятными словами - истинное искусство, доступное немногим уровня Ландау и Лифшица, но в наше время декаданса и постмодерна буду пытаться как умею. И да, автор не гарантирует 100% достоверности нижеприведенной информации, а лишь свое разумение.
Итак - попытаемся понять, что же происходит с промптом в его путешествии от токенизатора до торжественной генерации нового токена, с остановками по пути. Так же обсудим метафизический смысл нейрона.
Сперва посмотрим на общую схему — как промпт проходит токенизатор, превращается в токены и их номера, получает векторы из матрицы эмбеддингов и наконец попадает на вход первого слоя - попытаемся понять, что происходит на каждом шаге, и уловить смысл происходящего.

Рис. 1. Путь промпта с высоты птичьего полёта: подробности — далее.
Эта часть будет нудновата - но с развитием сюжета будет интереснее, обещаю
Итак, ни для кого не будет открытием, что модель не работает со словами - она работает с числами фиксированного словаря, соответственно вопрос - как нам перевести слова промпта в числа? Особенно для языков с большим количеством словоформ (те же падежи в русском), да еще учитывая, что любой язык тоже развивается, обрастает новыми терминами и варваризмами. Те же опечатки, всякие там эмодзи - с этим что делать? Стало быть, мы не пытаемся создать заранее фиксированный словарь с перечислением всех словоформ - мы идем по другому пути, по алгоритму BPE (Byte-Pair Encoding) - на этапе обучения используем так называемую технику “жадного обучения” словаря, заключающуюся в анализе наиболее часто встречающихся пар символов. Тут лучше пояснить на примере - представим, что мы обучаем токенизатор на маленьком корпусе, где встречаются слова “низ”, “низкий”, “нижний”, “внизу”. Начинаем с алфавита из отдельных символов:
н и з к и й в н и з у н и ж н и й ...
Алгоритм считает, какая пара соседних символов встречается чаще всего, и склеивает её в один новый токен. Допустим, чаще всего рядом стоят н и и → создаём токен ни. Перезаписываем корпус:
ни з к ни й в ни з у ни ж ни й ...
Снова ищем самую частую пару. Теперь это, скажем, ни + з → токен низ. И так далее — тысячи и тысячи слияний, пока словарь не дорастёт до заданного размера. Каждое слияние записывается в таблицу правил слияний — упорядоченный список «склей вот это с вот этим».

Рис. 2. BPE строит словарь: на каждом шаге склеивается самая частая пара соседних символов, правило записывается в таблицу слияний.
Что мы получаем на выходе обучения токенизатора — два артефакта:
Сам словарь, то есть отображение токен-строка → целочисленный ID. Например “▁низкий” → 5567, “низ” → 4021, “низк” → 903, “ая” → 231, “й” → 68.
Таблица слияний: набор правил “склей вот это с этим”; именно по нему из отдельных символов собираются токены при разборе нового текста.
Важно тут то, что при этом подходе вообще не будет неизвестных слов - так как все отдельные символы уже присутствуют в словаре - мы лишь пытаемся, используя таблицу слияний, из них что-то сложить. И если в промпте попался бессмысленный набор символов - то ни одно правило слияния не подойдет, и этот набор останется рассыпанным на отдельные символы. То есть прошел кот по клавиатуре - получили ффырлжв, токенизатор сперва порежет последовательность на отдельные буквы, а знакомых пар в таблице слияний не найдется - в итоге токенизатор оставит все как есть - 7 отдельных токенов. Смысла в них, конечно, нет, но важно, что закодировать их модель все равно смогла, а стало быть, любой текст пролезет дальше.
Кстати, вспомните, как старые модели не могли посчитать количество букв в словах (сколько букв р в слове “трансформер”?). Так вот это и есть следствие работы токенизатора - для него это слово один токен с каким-то номером, модель не знает, какие там буквы. Позже это решилось - или через “механизм рассуждений” (модель выписывает слово из отдельных токенов-символов), или через инструменты (генерация кода для подсчета количества букв).
Из интересного еще: пробелы. Я тоже думал, что это отдельные токены - а вот оказалось, что во многих токенизаторах пробел это не отдельный токен, а часть другого токена. То есть кошка (с пробелом в начале) и кошка - это два разных токена. Точнее, пробел как отдельный токен тоже есть - например, если у нас будет кошка, то в итоге первые два пробела - два отдельных токена, и третий токен кошка (с одним пробелом). А зачем такие усложнения? Ну это:
Экономия токенов, потому что пробелов в тексте примерно столько же, сколько и слов, и количество токенов бы удваивалось. А так пробел бесплатно уезжает внутрь слова - итоговых токенов меньше, а стало быть, меньше вычислений в дальнейшем.
Метка границы слова - то есть подсказка модели “здесь начинается новое слово”. Это разводит два случая одних и тех же букв:
кошка — слово «кошка», стоящее отдельно.
кошка (без пробела) — те же буквы как продолжение другого слова (например, внутри «микрокошка» или составного токена).
То есть приклеить пробел к слову — это самый дешёвый способ пометить границы слов и не платить по отдельному токену за каждый пробел.
Попробуем проследить судьбу простого промпта Кошка сидела на крыше. Приехал промпт в токенизатор, и тот нарезал, например:
"Кошка" → [88123]
" сидела" → [4517, 902] # "сид" + "ела" — редкая словоформа рассыпалась
" на" → [312]
" крыше" → [15044, 88] # "крыш" + "е"
Итого: [88123, 4517, 902, 312, 15044, 88] — 6 токенов из 4 «человеческих» слов. И заметьте - пробелы уехали внутрь токенов, а два слова разбились на два куска. Вот с этими шестью числами и будет дальше работать модель.
Перевести слово “эмбеддинг” на русский сложно (тот самый устоявшийся уже “варваризм”), наверное, самое близкое в нужном нам контексте - “матрица векторных представлений”. Размер этой таблицы - это вокабуляр данной модели, то есть все ей известные токены. И колонки - это координаты данного токена в “смысловом пространстве”. И количество этих колонок определяется на этапе проектирования модели - 4096 например. И каждая - это некое измерение в смысловом пространстве - например “размер”, “одушевленность”, “разумность”, “прошедшее время” и так далее.
То есть, например, предположим эмбеддинг(“Кошка”) = E[88123] → вектор из 4096 чисел. Соответственно, близкие по смыслу токены будут недалеко друг от друга в этом многомерном смысловом пространстве. Представить столько измерений мы не можем - но по аналогии с нашей галактикой Млечного Пути - как будто звезды это токены, и близкие по смыслу будут недалеко друг от друга. “Собака” будет недалеко от “кошка” и “хорек”, дальше “синий кит”, а “трактор” где-то далеко. Но названия осей вроде “размер” или “одушевлённость” — это лишь удобная иллюстрация. На деле человекочитаемого смысла у отдельной координаты, как правило, нет: один признак размазан по многим измерениям, а одно измерение кодирует смесь признаков. И этот базис не задан нами заранее — модель вывела его сама на этапе обучения, из статистики предсказания следующего токена, подобрав такое разложение, какое оказалось удобным ей.
Иначе говоря, с нашей картиной мира совпадает не смысл отдельных измерений, а геометрия пространства в целом: близость и взаимное расположение токенов отражают знакомые нам смысловые связи — «собака» рядом с «кошкой», а классическое «король − мужчина + женщина ≈ королева» работает как обычная арифметика. Оно и понятно: модель училась на человеческих текстах. Непостижимы для нас только сами измерения — что и по какой оси разложено, понять сложно. Впрочем, и не нужно: смысл живёт не в отдельных колонках, а в расстояниях и направлениях между точками.
Пока мы разбирались с обработкой одного токена - но вернемся к нашему изначальному промпту Кошка сидела на крыше. Итак - у нас 6 чисел, и мы получаем 6 векторов как описано в разделе выше (6 х 4096) - то есть матрицу. Она и отправляется в первый слой трансформера.
Но перед отправкой - один неочевидный момент: порядок слов. Казалось бы, какая проблема - вот же строки лежат по порядку, первая “Кошка”, вторая “сид” и так далее. А проблема в том, что модель не читает матрицу сверху вниз, как мы текст - она обрабатывает все 6 векторов разом, одной операцией, и номер строки в этих вычислениях нигде не участвует.
Решаем это так: раз модель смотрит только внутрь векторов - значит и позицию надо засунуть внутрь вектора, прямо в те самые 4096 чисел. Занимается этим сама модель (не токенизатор) - простая детерминированная операция, которая подмешивает в каждый вектор его номер в последовательности. В результате модель видит уже не “кошку вообще”, а “кошку, стоящую первой в последовательности”.
Старые модели к вектору токена прибавляли отдельный «позиционный вектор» PE[i] — свой для каждой позиции набор чисел, но мы сразу перейдем к современному методу “зашивания” позиции в вектор, методу RoPE (Rotary Position Embedding — «вращательное позиционное кодирование»). Суть в том, что позицию токена кодируют поворотом его вектора. Пока выглядит непонятно, но не пугаемся и разберем в деталях.
Что значит повернуть вектор? Вектор токена - это в нашем примере 4096 чисел, координаты точки в смысловом пространстве. Проведите мысленно линию из нуля в эту точку - получится стрелка; точка и стрелка - одно и то же, два взгляда на те же числа. Повернуть стрелку - значит сдвинуть точку по дуге вокруг нуля: расстояние до нуля осталось прежним, а координаты стали другими. На плоскости с двумя координатами это выглядит так: была “кошка” = [1, 0] - стрелка вправо; повернули на 30° - стало [0.87, 0.5] - та же стрелка, чуть задранная вверх. С 4096 числами то же самое, просто изобразить нельзя.
С другой стороны - мы же выяснили, что координаты это положение в смысловом пространстве. Вспомните, где именно находится смысл - “в расстояниях и направлениях между точками”. Не в самих числах - отдельные координаты вообще нечитаемы, - а в расстояниях и направлениях до других слов: кошка это кошка, потому что она рядом с “собакой” и далеко от “трактора”. Смысл - это не адрес точки, это ее положение относительно других слов.
Теперь смотрите, что именно делает поворот. Каждое слово в промпте крутится на угол своей позиции: первое слово, к примеру - на 0°, второе - на 30°, третье - на 60°. То есть слова повернуты по-разному - и угол между любыми двумя словами изменился. Было между токенами “Кошка” и “сид” какое-то смысловое расхождение, скажем 20° - а теперь между ними 20° плюс 30° доворота (они соседи, разница позиций - один шаг, то есть 30°). Вот сюда позиция и подмешалась: в угол между словами теперь входят два слагаемых - старый смысловой угол и добавка, ровно пропорциональная расстоянию между словами в тексте.
Ключевое - добавка эта не случайная, а строго известная: соседние слова всегда довернуты друг относительно друга на 30°, слова через две позиции - на 60°, и так везде, в любом тексте, всегда одинаково. Смысловая часть угла никуда не делась - она сидит внутри суммы, и модель, обучавшаяся с этим правилом с рождения, ее оттуда выделяет: она знает, что от слов на соседних позициях надо “ожидать” доворот в 30°, и все что сверх - это уже смысл. Грубо говоря, мы не стерли старую запись, а дописали поверх нее вторую, другим цветом - и читатель, знающий про оба цвета, читает обе.
А кошка остается кошкой потому, что сама по себе добавка от слова ничего не требует: “кошка” на позиции 0 и “кошка” на позиции 100 - это один и тот же вектор, довернутый на разную величину, и при сравнении с любым словом, стоящим, скажем, в трех шагах, картина будет абсолютно одинаковой - что в начале текста, что в конце. Слово не изменилось - изменилась только его “отметка о месте в строю”, и отметка эта устроена так, что важна лишь разница отметок, а не сами отметки.
Крутит этот вектор формула, зашитая в код модели, внутри механизма внимания (до него мы еще доберемся). Причем крутит она не оригинал, а рабочие копии векторов, которые механизм внимания делает для сравнения токенов - исходный вектор лежит нетронутым. И важно понимать: поворот не какая-то внешняя помеха, к которой модели пришлось привыкать. Он был включен уже во время обучения - каждый текст, на котором модель училась, проходил через те же самые повороты, по тому же правилу. То есть модель никогда в жизни не видела слов без поворотов - все ее знание о том, как слова связаны друг с другом, изначально выучено на повернутых векторах. Мы с вами читаем текст слева направо и не считаем это искажением - просто так записано. Вот и для модели повернутый вектор - это единственный известный ей способ записи, другого она не знает.
Подытожим: вектор - это стрелка, все стрелки крутятся по общему правилу, смысл живет во взаимном расположении и потому не страдает, а в углах между позициями находится информация о порядке слов. Дальше в иллюстративных целях будем рисовать на плоскости с двумя координатами. Пусть у слова “кошка” вектор [1, 0] - стрелка, смотрящая строго вправо. Тогда повороты по позициям выглядят так:
позиция 0: поворот 0° → [1.00, 0.00] стрелка вправо →
позиция 1: поворот 30° → [0.87, 0.50] чуть вверх ↗
позиция 2: поворот 60° → [0.50, 0.87] круче вверх ↗
позиция 3: поворот 90° → [0.00, 1.00] строго вверх ↑
Теперь смотрим на иллюстрацию ниже, поясняющую смысл идеи - она из двух половин.
Левая половина - это наша таблица поворотов, нарисованная стрелками: одна и та же “кошка” веером, от “позиции 0” (вправо) до “позиции 3” (вертикально вверх). Само слово не меняется - меняется только наклон, и наклон этот кодирует позицию.
Правая половина - о том, как модель будет эти наклоны читать. Тут придется забежать опять вперед к механизму внимания: внутри слоя трансформера токены постоянно сравниваются друг с другом попарно - “насколько ты мне важен?” - и сравнение это математически устроено так, что зависит только от угла между двумя стрелками. Наклон каждой стрелки по отдельности в нем не участвует вообще - только раствор угла между парой. Вот эти два вектора А и B на правой половине - два токена из нашего промпта, каждый повернут на угол своей позиции, и дуга между ними - единственное, что модель увидит при их сравнении.
Вроде начинает сходиться - угол между А и B - это разница их поворотов, то есть разница их позиций, умноженная на 30°. Проверим на числах: токены на позициях 2 и 5 - повороты 60° и 150°, между ними 150° − 60° = 90°. Те же два токена в конце длинного текста, на позициях 100 и 103 - повороты уже огромные, 3000° и 3090°, но между ними все те же 90°. То есть само по себе “накручено” на каждый токен может быть сколько угодно - при сравнении это не видно; виден только угол между парой, а он зависит от одного-единственного факта: токены стоят в трех шагах друг от друга.

Рис. 3. RoPE. Слева: одно и то же слово, повернутое тем сильнее, чем дальше его позиция. Справа: внимание смотрит на угол между векторами, а он зависит только от разницы позиций.
В этом цимес. Прибавленный позиционный вектор кодирует абсолютный адрес: “ты токен №100000” - и если модель при обучении не видела текстов такой длины, адрес для нее бессмыслица, качество на длинных текстах падает. Поворот же кодирует относительное расстояние: “вы стоите в трех шагах друг от друга” - а такой паттерн модель видела миллионы раз, на любых позициях. Где бы пара токенов ни оказалась - в начале текста или на стотысячной позиции - их взаимная геометрия одинакова. Поэтому RoPE отлично держится на длинных контекстах и стал стандартом де-факто.
Для целей этой статьи достаточно понимать: к моменту входа во внимание каждый токен «знает» свою позицию.
Ну и подведем итог всему пути от промпта до первого слоя. Превращений было четыре: сначала токенизатор порезал текст на токены по своим двум таблицам (словарь плюс правила слияний) и выдал вместо текста номера. Потом по каждому номеру из матрицы эмбеддингов достали строку - вектор из 4096 чисел, координаты токена в смысловом пространстве. Строки сложили друг под другом - получилась матрица N х 4096, в нашем примере 6 х 4096. И напоследок в векторы подмешали позиционную информацию - поворотами, как мы только что разобрали. Вот это и есть то, что въезжает в первый слой: уже не текст, но еще и не какое-то там “понимание” - просто числа, в которых записано, что значит каждый токен и где он стоит.

Рис. 4. Полный путь проходной: текст → токены (BPE) → номера-ID → строки матрицы эмбеддингов → матрица X [N × 4096] + позиция. Блок «+ позиция (RoPE)» здесь — упрощение: как разобрано выше, на самом деле поворот применяется не к матрице X, а к её рабочим копиям внутри механизма внимания, в каждом слое.
Для начала - слой трансформера состоит из “внимания” и FFN (Feed-Forward Network — «сеть прямого распространения», обычная полносвязная нейросеть, которая обрабатывает каждый токен по отдельности).
Начнем с “внимания” - зачем оно, и какую задачу решает? Вспомним, что въехало в первый слой: матрица, где каждому токену соответствует его вектор из матрицы эмбеддингов. Но это словарные значения — «кошка вообще», «сидела вообще», “крыше вообще”. Каждый вектор описывает свой токен в отрыве от остальных, как статья в толковом словаре. А нужно уловить смысл всего предложения. Кто сидел, где сидел — вся эта информация в предложении есть, но как связать все эти токены в смысловое представление о “кошке на крыше”?
Но кстати, выше же говорилось, что “сидела” было порезано токенизатором на “сид” и “ела”. То есть у нас как бы омоним. Все так, но давайте договоримся, почему дальше мы будем говорить о “сидела” как об одном токене. Во-первых, одна из первых задач, которую внимание решает на самых нижних слоях - как раз склейка таких обрезков: токен “ела” первым делом находит своего соседа “сид” (запрос, что-то вроде “я окончание, ищу основу” встречает ключ “а я основа без окончания”) и впитывает его - после пары слоев вектор на позиции “ела” фактически несет смысл целого слова “сидела”. Во-вторых, кстати, не путайте этот токен с глаголом “ела” (“кошка ела рыбу”) - тот пишется с приклеенным пробелом ела, и это вообще другой токен с другим вектором; кусок ела без пробела в обучающих текстах встречался почти исключительно как хвост глаголов, и его словарное значение именно такое. Так что склейка - тоже функция модели, а мы далее будем считать “сидела” одним токеном.
Внимание — это и есть механизм, которым токены обмениваются такой информацией. За один проход через слой каждый токен «осматривает» все токены слева, решает, какие из них ему важны, и подмешивает их содержимое в свой вектор. Подмешивает - значит обновляет свои координаты, то есть прибавляет что-то к своему вектору и получает новый вектор, который как бы сдвинут в смысловом пространстве в сторону, продиктованную контекстом.
Пока упрощенно - каждый токен обогащается смыслами предшествующих токенов. То есть «сидела» впитала «кошку», «крыша» впитала «на» и “кошку” и “сидела”.
Чтобы реализовать такой механизм, каждому токену нужно уметь три вещи: сформулировать, чего ему не хватает; быть находимым, когда его содержимое нужно другим; и отдать своё содержимое, когда его выбрали. Вот под эти три роли и заведены три вектора — Q, K и V: чем искать, по чему находить и что забирать.
Query (запрос) — с чем токен идёт искать. Вектор, вопрошающий “отзовитесь те, кто имеет ко мне отношение!”. У «сидела» в запросе будет что-то вроде «ищу подлежащее: кто или что выполняло действие, женский род, третье лицо».
Key (ключ) — по чему токен находят другие. Вектор, кодирующий «вот какие у меня признаки»: у «кошка» в ключе — «я существительное, третье лицо, одушевлённое».
Value (значение) — что токен отдаёт, если его нашли. Вектор с подробным перечислением признаков: у «кошка» — её признаки “животное, пушистое, хищник, млекопитающее”.
Сравнивая запрос одного токена с ключами всех остальных слева, внимание решает, у кого забирать информацию; а забирает оно значения. Почему нельзя обойтись одним вектором и сравнивать токены напрямую? Потому что «релевантен» — не то же самое, что «похож». Прямое сравнение векторов нашло бы для «сидела» другие глаголы — они рядом в смысловом пространстве. А нужен ей не похожий глагол, а подлежащее — то есть искать надо по одному признаку, находиться по другому, а отдавать третье. Поэтому ролей три, и для каждой — своё представление токена.
Теперь откуда берутся эти векторы - это результат преобразования данного токена при помощи соответствующих матриц W_Q, W_K, W_V.
Эти матрицы:
созданы при обучении и при инференсе неизменны;
свои на каждом слое трансформера;
постоянно лежат в VRAM;
А нужны они, чтобы из вектора токена - где все его словарное значение лежит одним куском - вытянуть те самые три среза: запрос, ключ и значение. Каждая матрица делает свой: W_Q - запрос, W_K - ключ, W_V - значение. И тут важно понимать, что это за “срез” такой. Матрица - это просто таблица чисел, и вспоминаем матричное умножение: каждое число нового вектора - это взвешенная сумма всех 4096 чисел исходного, где веса - числа из матрицы (у каждого выходного числа - своя колонка весов). То есть матрица решает, какие координаты исходного вектора усилить, какие приглушить, какие смешать - и что из этого попадет в результат. И вот эти веса подобраны на этапе обучения, на миллиардах предложений: обучение длилось до тех пор, пока из любого токена, каким бы вектором он ни приехал, W_Q не начала стабильно вытаскивать именно “чего этому токену не хватает”, W_K - “по чему его искать”, а W_V - “что он может отдать”.
И вот когда вы слышите “у модели столько-то параметров” - имеется в виду общее количество обучаемых весов: эти три матрицы на каждом слое - лишь часть, туда же входят матрица эмбеддингов, матрицы второго блока слоя (FFN, о нем позже - и это, к слову, львиная доля) и выходная матрица (стоит в самом конце модели и превращает итоговый вектор в предсказание следующего токена - до нее мы еще доберемся).
А размеры матриц устроены так - число строк зависит от длины входного вектора: в нашем примере это 4096 чисел, значит и строк 4096 (вспоминаем матричное умножение: вектор умножается на матрицу поколоночно - каждое число вектора умножается на соответствующее число колонки, потом произведения складываются - и одна колонка сворачивается в одно число результата; потому длина вектора и высота колонки должны совпадать). А вот число колонок - это длина вектора на выходе, ведь сколько колонок - столько чисел в результате, и вот это уже выбирает проектировщик модели: в нашем примере тоже 4096, но можно и сделать меньше.
Теперь вернемся к нашему промпту Кошка сидела на крыше и проследим весь процесс на токене “сидела” (как договаривались - считаем его одним токеном). Этому слову явно не хватает контекста: кто сидел? где сидел?
Шаг первый - наш токен получает свою тройку векторов. На входе у нас вектор токена “сидела” - 4096 чисел, его словарное значение: там закодировано что-то вроде “глагол, прошедшее время, женский род, действие сидения, поза покоя”. И вот по итогу умножения этого вектора на три матрицы получаем три разных вектора - каждый под свою роль:
умножили на W_Q - получили запрос q (4096 чисел). Матрица W_Q обучена вытаскивать из исходного вектора “чего этому токену не хватает”. Глаголу прошедшего времени не хватает деятеля и места - вот q и получится что-то вроде “ищу существительное-подлежащее, ищу обстоятельство места, женский род, третье лицо”;
умножили на W_K - получили ключ k. Матрица W_K вытаскивает “по каким признакам меня могут искать другие”: для “сидела” это “я глагол, я действие, я сказуемое” - именно так ее будут искать те, кому нужен глагол;
умножили на W_V - получили значение v. Матрица W_V пакует “что я отдам, если меня выберут” - собственно смысловую начинку: само действие сидения, прошедшее время, оттенок неподвижности.
(Признаки в кавычках - для наглядности: в реальности это все те же нечитаемые координаты, но по смыслу матрицы предназначены делать именно такую работу.)
Обратите внимание - все три вектора вытянуты из одного и того же исходного вектора, но это три разных его среза: что мне нужно, как меня найти, что я отдам. И так делает каждый из шести токенов промпта: у “кошки” q будет искать глагол (“что я делала?”), k - объявлять “я существительное, одушевленное, кандидат в подлежащие”, v - нести кошачьи признаки. На этом шаге токены еще ничего не знают друг о друге - каждый готовит свою тройку в одиночку, из самого себя. Технически все шесть прогоняются через матрицы разом, одним умножением - но это чистая оптимизация под видеокарту, по смыслу это шесть независимых операций “токен на матрицу”.

Рис. 5. Один вектор токена, три обученные матрицы - три роли: запрос (Q), ключ (K), значение (V).
И чтобы роли матриц окончательно улеглись, простое правило: W_Q и W_K вдвоем определяют, кто на кого посмотрит - то есть структуру связей между токенами. А W_V определяет, что именно потечет по этим связям - содержимое. Первые две рисуют карту внимания, третья готовит информацию (груз), которую по этой карте повезут.
Шаг второй - запросы встречаются с ключами. Вот теперь токены начинают взаимодействовать. Запрос q токена “сидела” сравнивается с ключом k каждого токена промпта, что слева от него (включая свой собственный) - сравнение это то самое скалярное произведение, чувствительное к углу между векторами. Направо смотреть запрещено - и это не каприз, а суть генеративной модели: она учится предсказывать следующий токен, и если бы токен мог подглядывать вправо, обучение превратилось бы в списывание. Так что “сидела” видит только “Кошку” и себя, а вот стоящей в конце “крыше” видны все. На выходе - по одному числу на каждый доступный токен: насколько его ключ “отозвался” на запрос “сидела”. Запрос был “ищу подлежащее” - и ключ “кошки” (“я существительное, одушевленное, третье лицо”) даст большое число. Получился профиль интересов: кто для “сидела” важен, а кто не очень.
Шаг третий - оценки превращаются в проценты. Сырые числа прогоняют через нормализацию, которая превращает их в доли, дающие в сумме единицу. У “сидела” выбор небольшой: условно 70% на “Кошку”, 30% на себя. А вот у “крыши” профиль богаче: скажем, 40% на “Кошку”, 25% на “сидела”, 15% на “на”, остальное на себя. Это и есть то самое распределение внимания: каждый токен решил, на кого из доступных сколько смотреть.
Шаг четвертый - сбор итогов. Теперь “сидела” забирает у доступных токенов их значения v - но не целиком, а пропорционально процентам: 70% от значения “кошки”, 30% от своего. Все это суммируется в один вектор - добавку, в которой намешано содержимое релевантных токенов в нужных пропорциях. И эта добавка прибавляется к исходному вектору “сидела” - помните “подмешивает значит обновляет координаты”? Вот это оно и есть: старый вектор плюс добавка равно новый вектор, сдвинутый в смысловом пространстве туда, куда продиктовал контекст. “Сидела” теперь не “сидела вообще”, а “сидела, причем сидела кошка”. А “где сидела” - уже не ее забота: полную картину “кошка сидела на крыше” соберут токены правее, которым видно все предложение.
И такую процедуру одновременно проделывает каждый токен промпта - у каждого свой запрос, свой профиль интересов и своя добавка. Прошли слой - все шесть векторов сдвинулись, каждый впитал свое.
Тут резонно спросить - а зачем обогащать все токены? Ведь когда дело дойдет до генерации, модель будет предсказывать следующее слово по вектору последнего токена - именно он должен вобрать в себя смысл всего предложения. А затем, что слоев много, и у каждого своя работа: у каждого свои матрицы W_Q, W_K, W_V, и обучены они вытаскивать разное. Нижние слои собирают грамматику - как раз наш пример: “сидела, причем кошка”. Средние - складывают из этих связок цельный образ кошки на крыше. Верхние - самое абстрактное: о чем предложение, какая тональность, эмоциональная окраска, к чему все идет. Каждый уровень строит из того, что подготовил предыдущий - и потому предыдущий должен обогатить все токены, а не только последний: это его заготовки для следующего этажа. Так, слой за слоем, смысл стекается к последнему токену - по векторам всех остальных.
Итак - после того как токены обменялись информацией, каждый вектор по отдельности отправляется во второй блок — FFN (Feed-Forward Network, «сеть прямого распространения»). Если внимание помогало токену оглядеться на уже пройденный контекст слева - то FFN отвечает на другой вопрос: «а что модель вообще знает о том, что у меня теперь записано». То есть связи между словами мы уже установили - теперь время приложить к ним знания модели.
По сути FFN — это два линейных слоя с функцией активации между ними. Первый слой — это множество нейронов-шаблонов: каждый из них как бы спрашивает входной вектор токена «насколько ты похож на меня?». Функция активации оставляет сильные срабатывания и глушит слабые, а второй слой возвращает результат обратно в рабочий формат модели. Ни свёрток, ни рекуррентности, ни внимания — обычная feed-forward нейросеть, потомок того самого перцептрона, который Фрэнк Розенблатт описал еще в конце 1950-х.
И что важно - FFN обрабатывает каждый токен независимо. Никакого обмена между токенами здесь уже нет — весь обмен закончился на внимании. Каждый вектор проходит через FFN в гордом одиночестве. Но разберем по порядку.

Рис. 6. После внимания один контекстный вектор уходит в FFN: первая матрица расширяет его, функция активации глушит слабые срабатывания, вторая матрица сжимает результат обратно в формат следующего слоя.
Давайте попробуем понять метафизический смысл нейрона. С одной стороны, технически все просто: нейрон в FFN можно представить как детектор паттерна. В первой матрице у него есть шаблон: с ним сравнивается входной вектор (тем же скалярным произведением, что сравнивало запросы с ключами во внимании), и ответ на вопрос «насколько во входе выражено мое направление?» становится силой срабатывания. Затем функция активации оставляет сильный сигнал или давит слабый почти в ноль. Во второй матрице у того же нейрона есть вклад: что именно он добавит в выходной вектор, если сработал.
Но что такое нейрон, если посмотреть на это с точки зрения феноменологии познания модели? Я бы это определил как “атом смысла”. Но не в человеческом понимании (как слово в словаре), а в специфическом, «машинном» смысле. Разобьём на уровни, чтобы попытаться понять его смысловую сущность (автор осознает спорность утверждений ниже):
Нейрон — это элементарная единица абстракции. Это способ модели «вычленить» из хаоса данных одну конкретную грань реальности и дать ей имя (в виде активации). Как говорил Аристотель, «подлинное бытие — это деятельность». Нейрон не «есть» — он «случается» в момент активации. Но проблема в том, что нейроны часто многозначны. Один и тот же нейрон может реагировать и на «имена собственные», и на «слова после запятой». То есть его «смысл» — это не четкое определение, а сгусток корреляций. Нейрон оперирует не объективным миром, а его тенями на стене платоновской пещеры — только пещера здесь векторное пространство. Он не отражает реальность напрямую, он создает её статистический слепок.
Нейрон — фильтр реальности. Вопрос, который модель задает миру. Человеческий вопрос: «Где я?» Нейронный «вопрос»: «Насколько вектор входа совпадает с моим внутренним напряжением (весами)?» Смысл нейрона раскрывается только в отношении. Он не существует сам по себе. Он существует только как реакция. Сущность нейрона — это не объект, а событие. Это акт распознавания. Это не смысловая сущность в вакууме. Это координата в многомерном пространстве смыслов.
Нейрон — это элементарный акт различения. Это минимальная единица, которая говорит: «Вот это — немного больше похоже на то, чем на это». Его веса — это не «знания», а кристаллизованный опыт проверки этой гипотезы на миллионах примеров. Когда нейрон активируется, он не «вспоминает факт» — он говорит: «Моя гипотеза снова нашла подтверждение». Но без входящего сигнала этот механизм мертв. Опять вспоминая Шекли - «Не было различий, ибо некому было о них спрашивать». Без вопроса (входных данных) нейрон — лишь пустая форма. Смысл рождается только на стыке вопроса и ответа.
Спустимся с метафизических высот обратно к механике. Мы описали один нейрон как детектор паттерна: шаблон, сила срабатывания, активация и вклад в результат. Теперь соберем из таких нейронов весь FFN - а он, как уже говорилось, устроен из двух линейных слоев с функцией активации между ними. И весь проход токена через него укладывается в три шага: расширить → отфильтровать → сжать.
Шаг первый - расширение. Вектор токена (наши 4096 чисел) умножается на первую матрицу FFN. Каждая ее строка - это шаблон одного нейрона, тот самый первый из двух векторов. Строк в этой матрице сильно больше, чем 4096 - например, раза в четыре, то есть 16384. Умножение вектора на матрицу - это, как мы помним, 16384 скалярных произведения разом: входной вектор сравнивается с шаблоном каждого нейрона. На выходе - 16384 числа, сила срабатывания каждого детектора. По сути токену одновременно задали 16384 вопроса: “ты про столицу?”, “ты глагол?”, “ты про еду?”, “ты про кошку?”…
Зачем расширять - почему не оставить 4096? Затем, что 4096 детекторов мало для всего, что модель знает. При 4096 нейронах одному нейрону пришлось бы совмещать несколько смысловых единиц. Чем больше детекторов, тем чище специализация. 16384 - это количество “атомов смысла”, описанных выше.
Шаг второй - фильтрация. Из 16384 чисел большинство - ни о чем: около нуля или отрицательные, нейрон не нашел общего. Здесь вступает функция активации. В итоге из 16384 нейронов реально активируются единицы процентов - те, чей смысл совпал с содержимым токена.
Шаг третий - сжатие. Осталось упаковать ответы выживших обратно в рабочий формат. Вторая матрица FFN - это те самые вторые векторы нейронов, вклады: у каждого нейрона в ней своя колонка, “что я впишу в результат, если сработал”. Выходной вектор - сумма вкладов всех нейронов, взвешенная силой их срабатывания: молчавшие вносят ноль, выжившие - свой вклад, пропорционально уверенности. И размер результата снова 4096 - потому что вся остальная модель (следующий слой, финальное предсказание) работает именно с таким форматом.

Рис. 7. Проход токена через FFN: вектор (4096) → первая матрица расширяет до 16384 нейронов-детекторов → фильтр глушит несработавшие (~95% замолкают) → вторая матрица сжимает вклады выживших обратно в 4096.
Пару слов про ReLU и GELU с картинки - это просто конкретные варианты той самой функции активации, «фильтра». ReLU - самый примитивный: отрицательное число превращает в ноль, положительное пропускает как есть. Вся функция - «если меньше нуля, то ноль». GELU (и его родственник SiLU) - то же самое, но со сглаженным углом: около нуля не жесткий обрыв, а плавный переход, слабые сигналы не убиваются наповал, а сильно приглушаются. На практике сглаженные варианты обучаются чуть лучше, поэтому в современных моделях стоят именно они - но суть у всех одна: заглушить несработавших, пропустить сработавших.
Проиллюстрируем упрощенным примером - пусть токен будет слово “Минск”, а его вектор - [0.8, 0.1, 0.6, 0.3]. Что значат эти числа? Грубо говоря: 0.8 — «связано с географией», 0.1 — «связано с едой», 0.6 — «это главный город страны», 0.3 — «абстрактное понятие».

Рис. 8. Скалярное произведение: строка матрицы W1 (паттерн нейрона) поэлементно умножается на вектор токена, результаты складываются. Высокий результат означает, что паттерн совпал.
И вот этот вектор сталкивается с первым слоем нейронов FFN.
Остался вопрос - откуда у нейрона берутся его шаблон и вклад? Я назвал нейроны “атомами смысла” - но никто ведь не раскладывал знание на атомы вручную и не программировал “нейрон для столиц”. Каждый атом выкристаллизовался сам. Проследим, как - на судьбе одного нейрона при обучении.
Сначала его веса случайны. Модели показывают “Минск - столица ___”, она отвечает какую-нибудь ерунду - и алгоритм обратного распространения ошибки вычисляет для каждого веса: если бы этот нейрон чуть сильнее реагировал на паттерн “город + столица”, ошибка была бы меньше. Веса чуть-чуть сдвигаются. Потом еще пример - “Москва - столица России”, “Скопье - столица Македонии” - и с каждым разом реакция на паттерн усиливается. А встретился “Минск славится драниками” - контекст другой, срабатывание не помогло предсказанию, и нейрон учится в таких случаях молчать. После миллиардов примеров получается специалист: шаблон настроен на “город + столица”, вклад - подсказывать нужную страну. И так одновременно со всеми 16384 нейронами на каждом слое - специализация не спроектирована, она выпадает в осадок из статистики.

Рис. 9. Четыре этапа обучения: случайные веса → backpropagation корректирует ошибку → после тысяч примеров нейрон специализируется → становится «детектором столиц».
Отсюда, кстати, и галлюцинации. Нейрон выучил паттерн, а не таблицу фактов. Подайте “Вакандо - столица ___” - и детектор честно сработает: паттерн-то совпал. Модель может уверенно назвать какую-нибудь страну, хотя Вакандо не существует. Опять вспоминая Шекли - "иллюзорной сущности - иллюзорную монетку". Нейрон не проверяет, существует ли Вакандо, - он отвечает на единственный вопрос, который умеет задавать: “похоже ли это на то, что я видел?”. Похоже - получите ответ, той же статистической чеканки, что и вопрос.
Ну а теперь сведем все вместе - что же такое слой трансформера.
Слой - это два блока, которые мы разобрали, поставленные друг за другом. Сначала внимание: каждый токен через свои матрицы W_Q, W_K, W_V получает запрос, ключ и значение, сравнивает свой запрос с чужими ключами, собирает взвешенную сумму чужих значений - и подмешивает ее к своему вектору. Это горизонтальный обмен: каждый токен запрашивает информацию у токенов слева и обогащает себя контекстом того, что для него важно. Потом FFN: каждый обновленный вектор каждого токена в одиночку проходит через расширение - фильтрацию - сжатие, и нейроны-детекторы дописывают в него то, что модель знает о таком содержимом. Это вертикальное обогащение: токен сверился с “энциклопедией” модели. Обе добавки не заменяют вектор, а прибавляются к нему - токен въехал в слой одним вектором, а выехал изменившимся, обогащенным дважды: сначала контекстом, потом знаниями.

Рис. 10. Один слой трансформера: внимание (три матрицы W_Q, W_K, W_V, сравнение запросов с ключами, сбор значений) → добавка к вектору → FFN (W1, активация, W2) → еще добавка. На выходе - те же векторы, обогащенные контекстом и знаниями.
Но цимес в том, что таких слоев много в современных моделях - десятки, а то и сотни. И каждый слой устроен одинаково - внимание плюс FFN, - но веса у каждого свои, и потому работа у каждого своя. Упрощенно: нижние слои склеивают обрезки токенизатора и собирают грамматику, средние - складывают из связок цельные образы, верхние - вытягивают абстракции: о чем текст, какая эмоциональная окраска, куда он идет, каким тоном продолжается. Каждый слой строит из заготовок предыдущего - и добавляет все больше контекста.
И все это нужно для того, чтобы предсказать следующий токен. Для этого нужен один-единственный вектор - скрытое состояние последнего токена промпта. Именно к нему, как мы видели, слой за слоем стекался смысл всех остальных: в нашем промпте это “крыше”, и после всех этажей в его векторе лежит уже не крыша, а спрессованное “кошка сидела на крыше, повествование, прошедшее время, дальше вероятно продолжение про…”. Вот с этим вектором и выходим на финальный шаг.
Итак, у нас на руках один вектор - 4096 чисел, в которых упаковано все понимание промпта Кошка сидела на крыше. А ответить модель должна токеном - одним из, ну например, ~128 тысяч, что лежат в ее словаре. Осталось превратить одно в другое.
Делает это выходная матрица (я упоминал ее, перечисляя параметры модели) - последняя таблица весов на пути, размером 4096 на размер словаря. Скрытый вектор умножается на нее - а это, как мы уже дважды видели, значит: скалярное произведение со столбцом каждого токена словаря. У каждого из 128 тысяч токенов в этой матрице свой столбец - по сути, его “смысловые координаты”, - и умножение разом сравнивает наш вектор с векторами всех токенов словаря. В итоге получаем по числу на каждый токен словаря - сырую оценку “насколько ты годишься быть следующим”. Эти сырые оценки и именуются логитами - это еще не вероятности: числа могут быть любыми, хоть −7, хоть +12. Для нашей кошки на крыше у токена мурлыкая логит окажется большим, у и - средним, а у трактора - глубоко отрицательным: не то видал нейрон при обучении после слова “крыше”.
Дальше логиты прогоняют через softmax - ту же нормализацию, что превращала оценки внимания в проценты: все 128 тысяч чисел становятся долями, дающими в сумме единицу. Вот теперь получается распределение вероятностей возможных продолжений нашего предложения: мурлыкая - 21%, и - 14%, греясь - 9%, и множество остальных с долями процента.
И только теперь происходит выбор. Можно брать всегда самый вероятный токен - но текст получится казенным и склонным зацикливаться. Поэтому обычно из распределения тянут жребий: токены с большой долей выпадают чаще, с маленькой - реже, а знакомая всем “температура” регулирует азартность этой лотереи - при низкой распределение заостряется к фаворитам, при высокой хвост получает шансы. Выпавший токен дописывается к промпту - и весь конвейер, который мы препарировали всю статью, запускается заново, уже для удлиненной последовательности. Токен за токеном - так и рождается ответ.

Рис. 11. Финал: скрытый вектор последнего токена умножается на выходную матрицу (столбец на каждый токен словаря) → логиты → softmax → распределение вероятностей → выбор следующего токена.
В целом цель была описать концептуально архитектуру трансформеров, не опускаясь к деталям коих множество. Тут вам и “головы внимания” (внимание на самом деле работает не одним прожектором, а десятками параллельных, каждый со своей специализацией), MoE-архитектура (когда FFN разбит на “экспертов” и токен идет не через всех, а лишь через избранных), устройство KV-кэша (почему генерация не пересчитывает весь промпт заново на каждом токене и за что вы платите в API),рассуждающие модели и так далее. Обо всем этом, если сойдутся звезды, поговорим в следующих статьях.