Статья написана без использования нейросетей

Любая нейросеть — это black box. Любая LLM — это black box^2. Однако люди смогли их придумать. И если старые нейронные сети, основанные на перцептроне или его производных, базируются на вполне известных биологических процессах, то трансформеры лежат вне представления о работе мозга. Следовательно, возникает вопрос — почему это сделано именно так?

В давнюю для себя пору я работал с трехмерной графикой, и когда мои должностные обязанности привели меня на темную дорожку современного хайпа, увиденное заставило меня задуматься о том, что где‑то все описанное уже было...

Внутренности LLM

Прежде чем перейти к основной части, необходимо отметить следующие понятия:

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

Эмбеддинг — это вектор, который является численным представлением качественной характеристики. Иными словами, каждому токену соответствует свой вектор‑эмбеддинг. Размер эмбеддинга жестко зафиксирован внутри LLM.

Принцип работы LLM основан на получении промпта, разбиении его на токены и постоянном дополнении промпта до тех пор, пока не будет достигнуто какое‑то критическое значение. Таким значением может быть, к примеру, максимальный размер сообщения. Стоит уточнить, что контекст, который может удерживать в себе LLM, ограничен сверху. Однако это не мешает модели генерировать текст, превышающий размер контекста: старые токены удаляются из видимости и не влияют на следующие результаты.

Конвейер LLM можно описать следующие образом:

Рис. 1. Decoder‑only pipeline (based on ChatGPT)
Рис. 1. Decoder‑only pipeline (based on ChatGPT)

1. Tokenizer — задача токенизатора разбить текст\промпт на токены и сопоставить каждому токену численную характеристику — эмбеддинг. Это необходимо, так как LLM не может работать со словами — ему необходимы числа. Дополнительно к эмбеддингам токенов существует векторная кодировка позиции — positional encoding. Это некоторый добавочный вектор, предопределенный для каждой конкретной позиции. Он позволяет учитывать «расстояние» между токенами.

Рис. 2. Преобразований качественных характеристик в количественные, понятные математической модели
Рис. 2. Преобразований качественных характеристик в количественные, понятные математической модели

2. Self‑Attention (далее просто attention) — позволяет устанавливать связи между токенами. Благодаря этому модель может учитывать контекст, максимально правильно генерируя следующие последовательности. Важно отметить, что связь может быть установлена только «назад». Иными словами, из примера на рис. 2, attention mechanism не пытается получить информацию о том, как «Котик» относится к «мышке», но учитывает, как к «мышке» относится к «Котик». Attention можно представить как матрицу (рис. 3):

Рис. 3. Attention-matrix - "однонаправленная" матрица, элементы которой показывают взаимосвязь токенов между собой
Рис. 3. Attention‑matrix — «однонаправленная» матрица, элементы которой показывают взаимосвязь токенов между собой

3. Neural Network — нейронная сеть, использующая информацию с предыдущих стадий для генерации нового вектора. Вместе с attention механизмом эта стадия формирует Transformer. Вектор не поступает сразу же на выход, вместо этого он проходит через несколько итераций трансформеров, при этом численные значения весов нейронных сетей и механизма attention отличаются у всех трансформеров.

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

Теперь обратим внимание на работу трансформера:

Набор токенов промпта формирует матрицу эмбеддингов, которая дополняется positional encoding и поступает на вход трансформера. Эта матрица используется для расчета q, k и v векторов посредством умножения на матрицы Q(Query), K(Key), V(Value), являющиеся частью трансформера (рис. 4). Затем выполняется умножение матриц q и kT, маскируется верхний треугольник и нормализуются значения в каждой строке (конкретнее — используется softmax), что дает attention‑матрицу (рис. 3). Далее на основе этой матрицы мы считаем средне‑взвешенный вектор v, который передаем в классическую Neural Network. Выход нейронной сети идет либо в следующий трансформер, либо в финальную нейронную сеть, которая для каждого токена в своем словаре определяет вероятность того, что следующим должен быть именно он (рис. 5). Случайным образом на основе этих вероятностей один из токенов выбирается и дополняет промпт.

Рис. 4. Умножение векторов эмбеддингов на матрицы QKV. Часто матрицы объединяют вместе с целью оптимизации (вызов одного gemm-а большего размера вместо трех меньшего).
Рис. 4. Умножение векторов эмбеддингов на матрицы QKV. Часто матрицы объединяют вместе с целью оптимизации (вызов одного gemm‑а большего размера вместо трех меньшего).
Рис. 5. Расширенный пайплайн.
Рис. 5. Расширенный пайплайн.

Звучит хорошо. Но почему именно так? Давайте отвлечемся на трехмерное моделирование:

В современном мире для отрисовки моделей используют DirectX, OpenGL или Vulkan. Все перечисленные API имеют идентичную базу под собой, которая называется graphics pipeline — набор стадий, через которые происходит отрисовка кадра. Часть из них — программируемые, именуемые шейдерами. Шейдеры являются произвольными программами, единственное ограничение которых — это зафиксированное «предназначение». Вершинный шейдер (vertex shader) принимает на вход позиции вершин и возвращает их же, а шейдер отрисовки (pixel shader) обязан вернуть цвет пикселя.
Второй тип стадий — настраиваемые. Исполнение этих стадий жестко зафиксировано в железе, единственный способ влияния на них — это изменение флагов. Примером является стадия растеризации (rasterization stage), которая принимает на вход вершины полигонов и производит трилинейную интерполяцию данных, хранимых в этих вершинах (позиции, цвет, текстурные координаты), которые далее идут в шейдер отрисовки. Эта стадия не позволяет исполнение произвольного кода, но дает возможность, например, включать\отключать прозрачность.

Рис. 6. Графический конвейер DirectX. Источник: https://learn.microsoft.com/en‑us/windows/win32/direct3d11/overviews‑direct3d-11-graphics‑pipeline
Рис. 6. Графический конвейер DirectX. Источник: https://learn.microsoft.com/en‑us/windows/win32/direct3d11/overviews‑direct3d-11-graphics‑pipeline

Если мы рассмотрим самый базовый графический конвейер, он включит в себя следующие стадии:

Input Assembler Stage → Vertex Shader Stage → Rasterization Stage → Pixel Shader Stage → Output‑Merge Stage

  1. Input Assembler Stage — получает на вход набор вершин и мета‑данные, описывающие их взаимоотношения. Благодаря этой информации стадия конструирует примитивы, такие как точки, линии, треугольники, которые затем идут в вершинный шейдер.

  2. Vertex Shader Stage — Вершинный шейдер принимает на вход данные о вершинах, которые могут включать в себя их трехмерные координаты и какие‑то дополнительные данные, такие как координаты текстуры, которая должна быть «натянута» на этот полигон. Главная проблема, которую решает эта стадия — преобразование координат. На трехмерной сцене может быть множество деревьев, которые отрисованы из одной модели, но абсолютно неэффективно хранить их по отдельности в файлах приложения: расходы на память и загрузку из нее будут колоссальными. Поэтому все эти деревья — это одно и то же дерево, которое сохранено в памяти в своих локальных координатах, и задача вершинного шейдера — преобразовать эти координаты. Для этого существует паттерн WVP: набор из трех матриц, именуемых World, View, Projection. Первая матрица переводит объект из локального пространства в пространство мира, вторая — в пространство камеры, а третья — в пространство проекции. Все эти матрицы имеют размерность 4×4, где подматрица 3×3 отвечает за вращение, а оставшаяся часть за перенос (ссылка). Благодаря этому для 100 одинаковых деревьев достаточно хранить только одну модель дерева и 100 World‑матриц, размер которых в памяти значительно меньше размера модели дерева.

  3. Rasterization Stage — отвечает за преобразование векторной информации в растровое изображение. Иными словами, устанавливает связь между пикселями (в «софтверном» смысле) и примитивами. Благодаря этой стадии последующий шейдер получает информацию не о вершинах примитива, а о том, какие пиксели он (примитив) покрывает.

  4. Pixel Shader Stage — шейдер отрисовки, который отвечает за расчет цвета пикселя. Существует набор моделей, которые позволяют различным образом отрисовать детали на экране, следуя в сторону фотореалистичности или стилизации. Самые первые модели — это модели Гуро, Фонга. Современные модели основаны на Physically Based Rendering (PBR).

  5. Output Merge-Stage - определяет окончательный цвет пикселя, учитывая информацию, получаемую из буферов трафарета и глубины.

Указанные выше модели сосредоточены на правильной отрисовке затенения, то есть учете всего падающего излучения на микро‑площадку, которое затем, в зависимости от характеристик поверхности (цвет, шероховатость), переводится в исходящее излучение. Компоненты отражения можно разделить на три вида: Ambient(окружение), Diffuse (диффузное) и Specular (зеркальное). Я рассчитываю не углубляться в сложные модели и остановиться на Фонге, и, более того, затронуть только диффузное освещение.

Рис. 7. Составные компоненты модели Фонга. https://en.wikipedia.org/wiki/Phong_reflection_model
Рис. 7. Составные компоненты модели Фонга. https://en.wikipedia.org/wiki/Phong_reflection_model
Рис. 8. Пример влияния диффузионной компоненты на изображения: а) без диффузного b) с диффузным https://www.bibliothek.tu‑chemnitz.de/ojs/index.php/cs/article/view/476
Рис. 8. Пример влияния диффузионной компоненты на изображения: а) без диффузного b) с диффузным https://www.bibliothek.tu‑chemnitz.de/ojs/index.php/cs/article/view/476

Диффузное освещение помогает нам воспринимать объем предметов (Рис. 8). С физической точки зрения эта компонента излучения формируется из квантов света, поглощенных поверхностью, которые испытали множественные отражения от частиц материала, изменив свои длину волны и направление. Благодаря последнему такое «пере‑излучение» равномерно распределяется по полусфере вдоль нормали к поверхности, или, иными словами, его интенсивность не зависит от позиции наблюдателя (исключая затухание с расстоянием, конечно). Для понимания того, от чего зависит интенсивность диффузного излучения, достаточно взглянуть на следующий рисунок:

Рис. 9. Геометрия падающего излучения. а) Освещение коллинеарно к поверхности b) Освещение под углом к поверхности
Рис. 9. Геометрия падающего излучения. а) Освещение коллинеарно к поверхности b) Освещение под углом к поверхности

Как видно, если угол падения излучения коллинеарен углу нормали поверхности, то плотность излучения, падающая на площадку, равна в точности плотности самого падающего излучения. Однако если излучение падает под углом α к поверхности, то площадь проекции возрастает, а значит плотность падающего излучения на единицу поверхности падает. Весьма естественно. Что насчет математики? Из изображения выше легко получить, что плотностью падающего излучения пропорционально cos(α).

При этом ||N||*||L||*cos(α) = dot(N, L), где ||x|| — означает норму вектора. При написании шейдера L и N всегда нормализуют (несмотря на то, что нормаль поверхности должна быть нормализована по смыслу, она теряет это свойство при три‑линейной интерполяции), а значит формула упрощается до cos(α) = dot(N, L).

Тогда для расчета диффузной составляющей отражения пикселя нам необходимо применить следующую формулу:

I = kd * dot(-L, N) * Wi, где kd — это диффузное свойство поверхности, а Wi описывает интенсивность падающего излучения как вектор RGB.

Теперь предлагаю вернуться к LLM и привести некоторые сравнения:

  1. В графический конвейер assembler‑stage поставляет вектора, описывающие модель. В LLM tokenizer поставляет вектора, описывающие текст.

  2. В вершинный шейдер поставляются матрицы трансформации WVP. В трансформерах LLM есть матрицы QKV (Строго говоря, WVP, в отличие от QKV, используются аккумулятивно, то есть vertex*W*V*P, в то время как в LLM эмбеддинги умножаются на каждые матрицы в отдельности. Но можно предположить, что после обучения матрица V, в действительности, является умножением матрицы Q на некую «скрытую» матрицу C, т. е. v=e*Q*C).

  3. В вершинном шейдере мы умножаем вектора модели на матрицы трансформации. В LLM мы умножаем эмбеддинги на матрицы QKV.

  4. Для расчета диффузного освещения мы нормализуем L и N, а затем рассчитываем dot(-L,N) внутри pixel shader. В LLM вектора q и k нормализуются, а затем умножаются матрицы q*kT, при этом все вектора q, кроме последнего, используются только для «восстановления истории» в последующих трансформерах. А значит смысл формулы можно свести до dot(qlast, ki). Подробнее.

  5. Финальная формула освещенности выглядит следующим образом:

    \textbf{I}_{diff} = \sum_{i \in lights}\textbf{W}_i*dot(\textbf{-L}_i,\textbf{N})*k_d

    В то время как выход трансформера можно описать приблизительно следующей формулой:

    \textbf{Out}_{transformer} = \sum_{i \in embeddings}\textbf{v}_i*dot(\textbf{k}_i, \textbf{q}_{last})

Иными словами, можно представить, что последний токен, а точнее его вектор q — это нормаль, описывающая некоторую гиперповерхность. Вектора ki — это фонарики. И чем ярче какой либо из этих фонариков освещает гиперповерхность, тем больший вклад он делает в выходное значение. Вектора vi, в свою очередь, описывают спектр излучения фонариков.

Послесловие

Конечно, все описанное выше — это лишь забавное наблюдение. С другой стороны, в нашем мире все связано, и большинство идей рождается из уже существующих. Хочется добавить фразу, которую кто-то произнес когда-то. Я уже даже не вспомню автора, но она прочно мне врезалась в память:

Всё это было прежде, и повторится вновь.

P.S. Дженсен, мы же сможем запихнуть сюда рейтрейсинг?

Комментарии (0)