Комментарии 11
немного вбок вопрос, но если MoE использует ограниченную часть параметров для ответа на вопрос, то так-ли необходимо грузить в VRAM все веса? По мне так это очень важный аспект, ответ на который может на порядок (именно что в 10 раз, а то и больше) ускорить вычисления.
На текущий момент ответ на этот вопрос однозначен - да нужно, так как "загрузка/выгрузка" экспертов MoE занимает много времени из RAM в VRAM да через не всегда широкую PCIe шину, если мы говорим про условно-гражданские GPU.
Но уже есть много исследований на эту тему, чтобы внутренний router мог предсказывать какие эксперты будут задействованы и грузить только их.
В MoE моделях отлично работает закон парето: 20% экспертов делают 80% работы, остальные прохлаждаются.
Далеко ли у вас ваш внутренний роутер напредсказывает? Предположим, что у вас генерация идёт 20 токенов в секунду (это совсем немного). И какие-то из экспертов используются только 1% времени. Значит примерно раз в пять секунд вам придётся их перегружать в GPU.
Но! Это и есть та причина, по которой MoE настолько заметно быстрее работают в гибридном режиме - когда в GPU влазит не вся сетка, а только её часть, как раз достаточная для просчёта очередного шага. Для посчёта следующего токена вам, может быть, из уже загруженных экспертов нужно 75%, а значит прогрузить надо только одну четвёртую.
Теперь понятно, почему после долгого общения с ChatGPT мне кажется, что мои собственные мысли кто-то токенизировал и пустил по слоям.
Пойду проверять, не живу ли я в transformer decodere :D
Качественное понимание вопроса. Приятно обнаружить движение в сторону глубины, в отличии от примитивного повторения формул без понимания их сути, как это принято в большинстве других текстов на аналогичную тему.
Но глубина, к сожалению, пока ещё не велика. Остались за кадром вопросы вроде "а почему именно так?", "а зачем?", ну и т.д.
И по мелочам:
Output(y) = y@Output
У вас здесь y из ниоткуда появился. Поправьте, пожалуйста.
Ну могу разве что исправить на Output(чтото) = чтото@Output. Ну либо на A(B) = B@A. "Игрек" потому и появился, чтобы не вписывать туда целую формулу.
А насчёт недостаточной глубины - готов ответить на уточняющие вопросы. Почему именно как? Зачем что?
Статья полезная.
Вот некоторые замечания о местах, на мой взгляд требующих уточнения:
Поэтому если "внешний формат" - это "слово номер 5273", то внутренний "слово на 83% грустное, на 23% солёное, на 1% зелёное, ..." и т.д. - по всем 4096 характеристикам.
Следует немного раскрыть механизм образования векторов характиристик и уточнить, что "солёное/грустное" - это условность и прямого соответствия нет.
свои 4096 характеристик (составленные из его прошлого + вновь обретённого опыта) путём матричного преобразования раздуваются в 3 раза (до 12288 параметров), потом фильтруются - слишком яркие впечатления притупляются - как позитивные, так и негативные, а потом опять же, путём матричного преобразоваются "сдуваются" снова в 3 раза до исходных 4096.
Как именно работает матричная операция "раздувание"/"сдувание"
Вот только матрицы Q, K, V, O каждый день новые.
Как тренеруются матрицы Q, K, V, O
Честно говоря, я бы все три места оставил как есть. Первое кажется мне достаточно очевидным, а я сам не люблю, когда мне предельно "разжёвывают" материал, поэтому предпочту воздержаться от такого.
А вот второй и третий пункты просто за рамками. Очень соблазнительно всё это объяснить, но всё таки статья не совсем про это. Поэтому ограничусь ответом на коммент здесь, кому интересно - прочитает.
1. Условность без прямого соответствия? Ну, да, наверное действительно так. "Солёное" - действительно вряд ли, модели не способны ощущать вкус, "зелёное" - тоже, если модель не мультимодальная. А вот грусть они очень даже понимают, поэтому думаю, что такая характеристика там вполне может быть.
2. Если умножить матрицу размером 1x10 на матрицу размером 10x40 то получим новую, 1x40. Получается, что мы нашу "раздули" в 4 раза. После этого можно применить активацию (притупить положительные и отрицательные эмоции) и можно сдувать обратно домножением на матрицу 40x10
3. LLM - это, строго говоря, математическая функция. А от функии можно взять производную. Производная, по смыслу своему - градиент. В процессе обучения мы обновляем веса следуя градиентам и из изначального шума (до обучения вся модель состоит из случайных чисел) постепенно проступают осмысленные веса. Вот только осмыслены они с точки зрения модели, а для человека всё ещё зачастую выглядят случайными, пусть в них и прослеживаются иногда некие паттерны.
Ну здрасьте, вроде понимание есть, а названия переменной придумать не можете? Обычно это делают так: представляют себе зачем это значение нужно, что оно делает, какую роль играет, ну и т.д. Далее на этой основе придумывают название. Но часто одним названием не ограничиваются, а пишут что каждое значение в формуле означает. Попробуйте пойти по этому пути, его многие используют.
Из этого следует ответ про "почему что". Вот поэтому - глубина, это когда, в том числе, нет проблем с выбором названия переменной.
Но всё же добавлю. "Зачем" и "как" предполагают не просто перечисление операций, но и стоящий за ними смысл. Вы его образно передали, но только на уровне "общения токенов", а как из этого рождается осмысленный текст? То есть нужно двигаться дальше, в векторные пространства и пользу от их преобразований. Как в этом участвуют k,v,q,o? Почему они именно так участвуют? Где по ходу движения рождается смысл текста? Ну и т.д. Обычно про это в аналогичных статьях информация присутствует, а вы же её почему-то упустили. Рано похвалил за глубину?
Ну и дальше в сторону архитектур надо развиваться. Там та же основа, но без понимания как и почему, туда не попасть.
Спасибо за статью, было интересно.
Споткнулся на ffn объяснении. Если attention блоки было легко сопоставить с описанием выше, но к чему подвязать ffn блоки не понял.
Каждый "блок" (blk) - это параметры трансформера на соответствующем шаге. Они пронумерованы от 0 до 35. Внутри каждого блока хорошо видно два этапа - attention (attn), который содержит k, q, v и output - правила сбора информации и fast forward network (ffn), которая описывает, что делать с собранной информацией.
Спросил у Жемини про blk.35.ffn_* строки и направления в них. Оставлю тут, может у кого будет такая же неясность.
Скрытый текст
Для понимания этих весов нужно разобраться в архитектуре Feed-Forward Network (FFN), которая идет в каждом слое трансформера (в данном случае Qwen2/Qwen3) сразу после блока Attention.
В современных моделях (LLaMA, Mistral, Qwen) используется архитектура SwiGLU. В отличие от старых сетей, где было всего два слоя, здесь их три: gate, up и down.
Что значат эти названия (направления)?
Представьте, что данные проходят через «бутылочное горлышко», которое сначала расширяется, а потом сужается.
ffn_up(Up-projection):Что делает: Увеличивает размерность вектора.
В вашем дампе: из 4096 (скрытая размерность модели) в 12288.
Это «расширение» позволяет модели спроецировать данные в пространство более высокой размерности, чтобы извлечь сложные признаки.
ffn_gate(Gate-projection):Что делает: Работает параллельно с
up. Это тоже расширение из 4096 в 12288.Результат этого слоя проходит через функцию активации (обычно SiLU). Затем он поэлементно умножается на результат слоя
up.Это работает как «фильтр» или «ворота» (gate), определяя, какая информация из
upважна, а какая — нет.
ffn_down(Down-projection):Что делает: Сжимает данные обратно в основную размерность модели.
В дампе: из 12288 обратно в 4096.
Это завершающий этап блока, подготавливающий данные для передачи в следующий слой.
Разбор ваших строк из дампа:
blk.35.ffn_norm.weight F32 [4096]Это параметры нормализации (LayerNorm или RMSNorm) перед входом в FFN. Она стабилизирует числа, чтобы они не «взрывались». Веса хранятся в точности Float32 для стабильности.
blk.35.ffn_gate.weight Q4_K [4096 12288]иblk.35.ffn_up.weight Q4_K [4096 12288]Это два огромных слоя расширения. Обратите внимание на тип
Q4_K— это 4-битная квантование. Это сделано для экономии памяти, так как это самые «тяжелые» части модели.
blk.35.ffn_down.weight Q6_K [12288 4096]Слой сжатия. Здесь используется
Q6_K(6 бит). Часто разработчики оставляют чуть больше точности наdown-projection, так как ошибки в этом слое сильнее влияют на итоговый результат.
Итог: зачем это нужно?
Если Attention отвечает за то, чтобы токены «смотрели» друг на друга и понимали контекст (связи между словами), то FFN (Fast Forward / Feed Forward) — это «база знаний» модели. Именно в этих огромных матрицах (расширение до 12288 и обратно) хранятся фактические знания и логические правила, которые модель применяет к каждому токену по отдельности.
Почему "Fast Forward"? Скорее всего, в статье или контексте это опечатка или упрощение от Feed-Forward (связь «вперед»). В контексте нейросетей это означает, что сигнал идет строго в одном направлении: вход -> gate/up -> down -> выход.
FFN это та часть, которую я описал как "токен спит и видит красочные сны". FFN на схемах обычно рисуют как две трапеции - одна расширяется раструбом, а вторая сразу после неё сужается. Вообще говоря, между ними ещё есть тонюсенькая прослойка - активация, но её рисуют не всегда.
В матричном вычислении есть умножить матрицу размером AxB на матрицу размера BxC то размер результата будет AxC.
Токен - это вектор значений (некоторое количество чисел с плавающей запятой), в моих примерах его размер был 4096. Поэтому его размер можно записать как 1x4096.
FFN матрицы имеют размеры 4096x12288 и 12288x4096.
Поэтому цепочка выглядит так:
1. токен @ первая матрица -> результат 1x12288 - красочный сон
2. красочный сон -> притуплённый сон в результате активации (размер сохраняется 1x12288)
3. притуплённый сон @ вторая матрица -> снова 1x4096, готов к следующему этапу
Активация на шаге 2 это "ужимание" диапазона в какие-то жёсткие рамки. В результате стольких умножений и сложений каждое из 12288 чисел может иметь очень большие или очень малые значения. После активации это обычно ужато в диапазон -1...+1, а то и ещё меньше. Функций активации много разных, самая простая для понимания - сигмоид, по нему прекрасно видно, как именно сжимается диапазон.

Как работает трансформер (LLM)