<?xml version="1.0" encoding="utf-8" ?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:tt="http://teletype.in/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"><title>Ivan</title><author><name>Ivan</name></author><id>https://teletype.in/atom/kittybytes</id><link rel="self" type="application/atom+xml" href="https://teletype.in/atom/kittybytes?offset=0"></link><link rel="alternate" type="text/html" href="https://teletype.in/@kittybytes?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=kittybytes"></link><link rel="next" type="application/rss+xml" href="https://teletype.in/atom/kittybytes?offset=10"></link><link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></link><updated>2026-04-27T05:20:48.752Z</updated><entry><id>kittybytes:vKPEDGI8fts</id><link rel="alternate" type="text/html" href="https://teletype.in/@kittybytes/vKPEDGI8fts?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=kittybytes"></link><title>DenseAttention: No-Compromise Exact All NxN Interactions Algorithm with O(N) Space and Time Complexity</title><published>2024-11-03T13:22:42.387Z</published><updated>2024-11-03T13:27:00.202Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/18/6c/186ca8ca-16f9-4c23-b6ac-3bcf74863a8e.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/db/06/db0667d4-5ebd-4f5e-9bf7-4ed8f57111d8.png&quot;&gt;Возможны ли нейросети без нелинейностей? Возможно ли сделать сеть только из матричных умножений - наиболее эффективных по вычислениям и с возможностью параллелизма? И самое главное - не потерять при этом точность работы трансформера. В этой статье показывается, что это возможно. Определив и выкинув наиболее слабые части архитектуры, автор заменяет их матричными умножениями, а где-то вводит новые преобразования для повышения эффективности модели. В результате получается DenseAttention - структура с повышенной точностью и эффективностью вычислений.</summary><content type="html">
  &lt;p id=&quot;8AQt&quot;&gt;Возможны ли нейросети без нелинейностей? Возможно ли сделать сеть только из матричных умножений - наиболее эффективных по вычислениям и с возможностью параллелизма? И самое главное - не потерять при этом точность работы трансформера. В этой статье показывается, что это возможно. Определив и выкинув наиболее слабые части архитектуры, автор заменяет их матричными умножениями, а где-то вводит новые преобразования для повышения эффективности модели. В результате получается DenseAttention - структура с повышенной точностью и эффективностью вычислений.&lt;/p&gt;
  &lt;p id=&quot;MwoC&quot;&gt;&lt;em&gt;Source&lt;/em&gt;: Architecture&amp;#x27;s author - Andrew Argatkiny, &lt;a href=&quot;https://github.com/andrewargatkiny/dense-attention/blob/master/assets/DenseAttention_paper.pdf&quot; target=&quot;_blank&quot;&gt;DenseAttention paper&lt;/a&gt;, &lt;a href=&quot;https://github.com/andrewargatkiny/dense-attention&quot; target=&quot;_blank&quot;&gt;DenseAttention Github&lt;/a&gt;, &lt;a href=&quot;https://vk.com/wall-187376020_376&quot; target=&quot;_blank&quot;&gt;VK Lab Meeting&lt;/a&gt;&lt;/p&gt;
  &lt;h2 id=&quot;5rm9&quot;&gt;Motivation&lt;/h2&gt;
  &lt;p id=&quot;H7bd&quot;&gt;Основным минусом нелинейностей является их неэффективность в вычислениях. Например, метрика &lt;strong&gt;Model FLOPS Utilization (MFU)&lt;/strong&gt;, которая является отношением наблюдаемой пропускной способности к теоретической максимальной пропускной способности, если бы модель работала с пиковым значением FLOPS без накладных расходов на память или связь, довольно низка в современных архитектурах:&lt;/p&gt;
  &lt;p id=&quot;IPMt&quot;&gt;&lt;a href=&quot;https://www.databricks.com/blog/mosaicbert&quot; target=&quot;_blank&quot;&gt;MosaicBERT&lt;/a&gt; - 40%&lt;/p&gt;
  &lt;p id=&quot;i3Hz&quot;&gt;&lt;a href=&quot;https://arxiv.org/abs/2204.02311&quot; target=&quot;_blank&quot;&gt;PaLM&lt;/a&gt; - 46%&lt;/p&gt;
  &lt;p id=&quot;7kf4&quot;&gt;&lt;a href=&quot;https://github.com/Dao-AILab/flash-attention&quot; target=&quot;_blank&quot;&gt;FlashAttention 2&lt;/a&gt; - 72%&lt;/p&gt;
  &lt;p id=&quot;Bqi9&quot;&gt;Так происходит, потому что GPUs не производят никаких вычислений, пока считывается/записывается память. В статье &lt;a href=&quot;https://arxiv.org/abs/2007.00072&quot; target=&quot;_blank&quot;&gt;Data Movement Is All You Need&lt;/a&gt; показано, что матричные умножения в модели BERT-large составляют &lt;strong&gt;99.8%&lt;/strong&gt; всех вычислений (FLOPS), но они занимают лишь &lt;strong&gt;61%&lt;/strong&gt; времени вычисления. А &lt;strong&gt;31%&lt;/strong&gt; времени тратится на вычисление оставшихся операций (которые составляют &lt;strong&gt;0.02%&lt;/strong&gt; FLOPS). То есть трансформер, из-за ограниченности памяти, вычислительно неэффективен.&lt;/p&gt;
  &lt;p id=&quot;82ms&quot;&gt;Углубляясь в проблему, приведу метрику &lt;strong&gt;Arithmetic Intensity (ArIn)&lt;/strong&gt; - отношение общего количества операций FLOPS к общему количеству перемещений данных (байт):&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(24,  24%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;KUa2&quot; data-align=&quot;center&quot;&gt;&lt;em&gt;Arithmetic Intensity = FLOPS / Bytes&lt;/em&gt;&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;2Ooy&quot;&gt;&lt;em&gt;&lt;u&gt;Для эффективности алгоритма необходимо (но не достаточно), чтобы его значение ArIn было выше, чем ArIn ускорителя.&lt;/u&gt;&lt;/em&gt; Иначе часть времени ускоритель будет простаивать, что и было показано в статье выше.&lt;/p&gt;
  &lt;p id=&quot;1KrQ&quot;&gt;Чему же равны метрики ArIn современных ускорителей и вычислительных операций, применяющихся в трансформере? У NVIDIA A100 этот показатель равен &lt;strong&gt;156 FLOPS/B&lt;/strong&gt;, тогда как в трансформере мы имеет следующие значения:&lt;/p&gt;
  &lt;ul id=&quot;Fo7d&quot;&gt;
    &lt;li id=&quot;DAag&quot;&gt;ReLU activation - &lt;strong&gt;0.25 FLOPS/B&lt;/strong&gt;&lt;/li&gt;
    &lt;li id=&quot;PHDF&quot;&gt;Element-wise - &lt;strong&gt;1/3 FLOPS/B&lt;/strong&gt;&lt;/li&gt;
    &lt;li id=&quot;JJnJ&quot;&gt;Layer normalization &amp;amp; Softmax &amp;lt; &lt;strong&gt;10 FLOPS/B&lt;/strong&gt;&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;gDLC&quot;&gt;То есть мы видим разницу ArIn минимум в 3-4 порядка. Это оказывает колоссальное влияние на время работы трансформера. Добавлю, что эти нематричные операции выполняются не на тензорных ядрах, а на обычных, что также снижает их эффективность.&lt;/p&gt;
  &lt;p id=&quot;ANiY&quot;&gt;Однако и с матричными вычислениями не все в порядке. Основная операция Attention - &lt;em&gt;softmax(Q *K^T)*V&lt;/em&gt; - имеет &lt;strong&gt;32&lt;/strong&gt; FLOPS/B.&lt;/p&gt;
  &lt;p id=&quot;yixu&quot;&gt;Можно ли заменить эти операции и даже избавиться от них и создать новую, более эффективную архитектуру?&lt;/p&gt;
  &lt;h2 id=&quot;QvFl&quot;&gt;Designing DenseAttention&lt;/h2&gt;
  &lt;figure id=&quot;N0W1&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/fc/f8/fcf8da98-2520-4d2b-8c8d-af78e8fb6871.png&quot; width=&quot;2092&quot; /&gt;
    &lt;figcaption&gt;&lt;a href=&quot;https://vk.com/wall-187376020_376&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;fS7r&quot;&gt;Для начала автор &lt;strong&gt;удаляет&lt;/strong&gt; некоторые составляющие:&lt;/p&gt;
  &lt;ul id=&quot;ziZi&quot;&gt;
    &lt;li id=&quot;PPmy&quot;&gt;&lt;em&gt;Dropouts&lt;/em&gt; - на этапе pre-train они не нужны, но их можно добавить в этап fine-tune&lt;/li&gt;
    &lt;li id=&quot;7hrZ&quot;&gt;&lt;em&gt;Masking&lt;/em&gt; - можно убрать с энкодера (и с декодера тоже можно)&lt;/li&gt;
    &lt;li id=&quot;2IsA&quot;&gt;&lt;em&gt;Scale&lt;/em&gt; - его можно перенести&lt;/li&gt;
    &lt;li id=&quot;PfYi&quot;&gt;&lt;em&gt;Softmax&lt;/em&gt;&lt;/li&gt;
    &lt;li id=&quot;zxOO&quot;&gt;&lt;em&gt;W_keys, W_values, W_output&lt;/em&gt;&lt;/li&gt;
    &lt;li id=&quot;E6rS&quot;&gt;Между слоями Attention и FNN убираем &lt;em&gt;LayerNorm и skip-connections&lt;/em&gt;&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;oiSJ&quot;&gt;Самой большой проблемой является &lt;strong&gt;softmax&lt;/strong&gt; - без него выходы Attention неограничены, возрастают в полиномиальной степени и в итоге сходятся либо к нулю, либо к бесконечности.&lt;/p&gt;
  &lt;p id=&quot;Jdb9&quot;&gt;Давайте попробуем разобраться почему это происходит. Рассмотрим упрощенную матрицу attention&lt;/p&gt;
  &lt;p id=&quot;ekOS&quot; data-align=&quot;center&quot;&gt;&lt;strong&gt;Y = X * W * X^T * X&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;jlCB&quot;&gt;Стандартное отклонение каждого элемента матрицы &lt;strong&gt;Y&lt;/strong&gt; ограничено снизу, но не ограничено сверху. Даже если матрицы &lt;strong&gt;X&lt;/strong&gt; и &lt;strong&gt;W&lt;/strong&gt; независимо распределенные, проблема остается - мы не знаем точное распределение &lt;strong&gt;X&lt;/strong&gt;. А это распределение получается с толстыми и тяжелыми хвостами -&amp;gt; на каждом новом слое оно уходит в бесконечность, а значит понять распределение &lt;strong&gt;Y&lt;/strong&gt; невозможно.В этом случае LayerNorm не помогает, потому что опирается на L2-норму.&lt;/p&gt;
  &lt;p id=&quot;QrAX&quot;&gt;Давайте попробуем поменять норму. Возьмем &lt;strong&gt;бесконечную&lt;/strong&gt; норму - модуль максимального значения этой матрицы:&lt;/p&gt;
  &lt;p id=&quot;58Xd&quot; data-align=&quot;center&quot;&gt;&lt;strong&gt;||X|| = max(|X_ij|)&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;G5Of&quot;&gt;Для этой нормы мы можем вывести такие условия, при которых выход attention будет ограничен. Введем для исследования матрицу &lt;strong&gt;Z&lt;/strong&gt;, которая будет произведением трех матриц &lt;strong&gt;X&lt;/strong&gt;:&lt;/p&gt;
  &lt;p id=&quot;xgXH&quot; data-align=&quot;center&quot;&gt;&lt;strong&gt;Z = X * X^T * X&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;dkL5&quot;&gt;Тогда, если бесконечная норма матрицы &lt;strong&gt;Z&lt;/strong&gt; ограничена, то и выход attention будет ограничен.&lt;/p&gt;
  &lt;p id=&quot;2RjO&quot;&gt;В статье приводится детальное доказательство этого факта, основанного на ограничении дисперсии произведения матрицы &lt;strong&gt;X&lt;/strong&gt; и &lt;strong&gt;W.&lt;/strong&gt;&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(323, 50%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;qXB1&quot;&gt;Вводя новый &lt;strong&gt;scale factor&lt;/strong&gt;, равный &lt;strong&gt;1/N^(1/3)&lt;/strong&gt;, норма матрицы будет ограничена сверху размерностью эмбеддинга. Тем самым мы полностью можем избавиться от &lt;strong&gt;softmax&lt;/strong&gt; без потери качества работы алгоритма. &lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;L6WP&quot;&gt;Тогда введем новую операцию - &lt;strong&gt;MaxNormActivation&lt;/strong&gt;:&lt;/p&gt;
  &lt;figure id=&quot;3VXS&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/48/42/4842e953-955f-4880-8917-df621b2e4340.png&quot; width=&quot;344.93333333333334&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;JnmI&quot;&gt;Такая норма не центрирована, в ней нет bias и нет никаких весов.&lt;/p&gt;
  &lt;p id=&quot;HtYl&quot;&gt;Введя такой трюк, мы получаем большую эффективность - без &lt;strong&gt;softmax&lt;/strong&gt; мы получаем ассоциативность матричных умножений:&lt;/p&gt;
  &lt;p id=&quot;lhPz&quot; data-align=&quot;center&quot;&gt;&lt;strong&gt;(Q * K^T) V = Q (K^T * V)&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;hz73&quot;&gt;То есть теперь мы можем варьировать нашу вычислительную сложность в зависимости от размера датасета и эмбеддинга. Но в любом случае наш алгоритм будет работать намного быстрее, чем раньше.&lt;/p&gt;
  &lt;figure id=&quot;eEuW&quot; class=&quot;m_retina&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/37/d3/37d3b2bc-b05d-49f8-a9e9-7f6ec5669739.png&quot; width=&quot;448&quot; /&gt;
    &lt;figcaption&gt;&lt;a href=&quot;https://github.com/andrewargatkiny/dense-attention/blob/master/assets/DenseAttention_paper.pdf&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;oSj5&quot;&gt;Также в DenseAttention автор уменьшает количество голов в архитектуре - рассматривается два варианта: либо одна большая, либо 4 маленьких. Таким образом получается выигрыш по вычислениям и точности модели. Так, используя только одну голову с размерностью d=1024 для модели &lt;strong&gt;BERTLarge&lt;/strong&gt; операции умножения матриц уже составляют &lt;strong&gt;205 FLOPS/B&lt;/strong&gt;, против &lt;strong&gt;32&lt;/strong&gt;. &lt;/p&gt;
  &lt;p id=&quot;GU5H&quot;&gt;Продолжая тематику удаления вычислений из трансформера, автор удаляет матрицу &lt;strong&gt;keys - &lt;em&gt;W_keys&lt;/em&gt;&lt;/strong&gt;. В стандартном механизме attention каждый раз происходит перемножение двух низкоранговых матриц - &lt;strong&gt;queries&lt;/strong&gt; и &lt;strong&gt;keys&lt;/strong&gt;. Они низкоранговые, потому что имеют размерность d эмбеддинга / d головы. В DenseAttention ранг выходной матрицы гораздо выше, а значит операция перемножения ее на низкоранговую избыточна. Поэтому мы можем удалить матрицу &lt;strong&gt;keys &lt;/strong&gt;для экономии ресурсов. Матрицы &lt;strong&gt;W_values, W_output&lt;/strong&gt;удаляются по той же причине.&lt;/p&gt;
  &lt;p id=&quot;clE2&quot;&gt;Последним удалением в архитектуре является &lt;strong&gt;LayerNorm&lt;/strong&gt;и &lt;strong&gt;skip-connections&lt;/strong&gt;. Эмпирически показано, что их удаление не ведет к уменьшению метрик модели.&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(323, 50%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;yLpU&quot;&gt;Теперь в нашей архитектуре нелинейности остались лишь в конце блока attention и FFN!&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;hcQy&quot;&gt;Финальная архитектура &lt;strong&gt;DenseAttention&lt;/strong&gt; имеет вид:&lt;/p&gt;
  &lt;figure id=&quot;uu0x&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/cd/ea/cdea92fe-427d-4bec-a84c-a22d55a0fb51.png&quot; width=&quot;2106&quot; /&gt;
    &lt;figcaption&gt;&lt;a href=&quot;https://github.com/andrewargatkiny/dense-attention/blob/master/assets/DenseAttention_paper.pdf&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;msfC&quot;&gt;В формулах для общего случая нескольких голов это выглядит так:&lt;/p&gt;
  &lt;p id=&quot;aeqo&quot;&gt;&lt;/p&gt;
  &lt;figure id=&quot;50og&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/2a/b1/2ab1ec5a-7b13-4dda-ac62-9aed295cd35b.png&quot; width=&quot;422&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure id=&quot;HAOT&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/44/a5/44a55813-85e9-4b72-ba22-e4fa49d47114.png&quot; width=&quot;434&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;3yEO&quot;&gt;Благодаря этому, обновленная архитектура attention имеет вычислительную сложность &lt;strong&gt;11Nd^2 в случае O(N)&lt;/strong&gt; и &lt;strong&gt;9Nd^2 + 2dN^2 в случае O(N^2)&lt;/strong&gt;, что вычислительно превосходит стандартную архитектуру, особенно на длинных последовательностях.&lt;/p&gt;
  &lt;h2 id=&quot;Njc9&quot;&gt;Cosine RelPE&lt;/h2&gt;
  &lt;p id=&quot;58LL&quot;&gt;Помимо повышения эффективности вычислений, автор вводит новую функцию positional encoding. Дело в том, что современные модели чаще всего используют &lt;strong&gt;Rotary Positional Embeddings (RoPE)&lt;/strong&gt;, который применяет преобразования к матрицам &lt;strong&gt;Q&lt;/strong&gt; и &lt;strong&gt;K&lt;/strong&gt;. Однако в работе &lt;a href=&quot;https://arxiv.org/abs/2104.09864&quot; target=&quot;_blank&quot;&gt;RoFormer: Enhanced Transformer with Rotary Position Embedding&lt;/a&gt; авторы показали, что параметризация, используемая в &lt;strong&gt;RoPE&lt;/strong&gt; приводит к долгосрочному снижению нормы выхода attention. Более того, преобразования &lt;strong&gt;RoPE&lt;/strong&gt; неэффективны в вычислительном отношении, поскольку их вычисление требует дорогостоящих изменений структуры тензора и нескольких поэлементных операций с низкой &lt;strong&gt;ArIn&lt;/strong&gt;, отдельно для &lt;strong&gt;Q&lt;/strong&gt; и &lt;strong&gt;K&lt;/strong&gt;.&lt;/p&gt;
  &lt;p id=&quot;vF3D&quot;&gt;Автор вводит новое преобразование &lt;em&gt;g1&lt;/em&gt;, которое вычислительно более эффективно, ведь оно допускает только одно element-wise умножение, вместо двух:&lt;/p&gt;
  &lt;figure id=&quot;qlA2&quot; class=&quot;m_retina&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/a1/e2/a1e2c32b-2f32-4751-b3f8-e988949ba2f3.png&quot; width=&quot;206&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;BUhn&quot;&gt;Тогда новое преобразование &lt;strong&gt;Cosine RelPE&lt;/strong&gt;:&lt;/p&gt;
  &lt;figure id=&quot;Fqhm&quot; class=&quot;m_retina&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/53/6d/536de732-eaf1-436b-b884-cc9844d7f662.png&quot; width=&quot;681&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;THf9&quot;&gt;Автор использует его перед слоем DenseAttention и отмечает, что хоть такое преобразование влияет на матрицу, оно не ухудшает производительность. &lt;/p&gt;
  &lt;h2 id=&quot;dHuo&quot;&gt;LocalAttention&lt;/h2&gt;
  &lt;p id=&quot;IATI&quot;&gt;Последним улучшением в данной статье является применение технологии &lt;strong&gt;LocalAttention&lt;/strong&gt;, которая в последнее время стала популярна благодаря снижению вычислительных затрат и потребления памяти. LocalAttention определяет окно фиксированного размера, в пределах которого каждый элемент может взаимодействовать только за соседними элементами.&lt;/p&gt;
  &lt;p id=&quot;qAp7&quot;&gt;Автор также вводит LocalAttention, однако делает это не ради повышения эффективности вычисления, а ради увеличения качества модели. LocalAttention в данном случае состоит из трех уровней: &lt;em&gt;LocalAttention&lt;/em&gt;, &lt;em&gt;ShiftedLocalAttention&lt;/em&gt; и &lt;em&gt;global&lt;/em&gt; &lt;em&gt;DenseAttention.&lt;/em&gt;&lt;/p&gt;
  &lt;figure id=&quot;uMVV&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/d2/60/d260cfa9-67e1-4bbd-bb1c-7df9dd798a30.png&quot; width=&quot;1690&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;wihd&quot;&gt;&lt;em&gt;LocalAttention&lt;/em&gt; классически разбивает последовательность на окна равного размера &lt;strong&gt;w&lt;/strong&gt;, но в таком случае половина информации теряется. Поэтому вводится &lt;em&gt;ShiftedLocalAttention&lt;/em&gt;, который смещен на &lt;strong&gt;w/2&lt;/strong&gt; относительно первого, что позволяет всем токенам иметь симметричное соседство после двух последовательных слоев. Последний слой &lt;em&gt;global&lt;/em&gt; &lt;em&gt;DenseAttention&lt;/em&gt; охватывает весь контекст последовательности. Все 3 слоя могут соединяться вместе, как обычные слои трансформера.&lt;/p&gt;
  &lt;h2 id=&quot;EM4F&quot;&gt;Experiments&lt;/h2&gt;
  &lt;p id=&quot;joIE&quot;&gt;Как обычно, по экспериментам пройдусь быстро.&lt;/p&gt;
  &lt;p id=&quot;zkVn&quot;&gt;&lt;strong&gt;Long Range Arena&lt;/strong&gt; - это сложный набор из 6 классификационных тестов, предназначенных для изучения возможностей эффективных моделей с длительным контекстом на больших последовательностях длиной от 1к до 16к.&lt;/p&gt;
  &lt;figure id=&quot;7X7j&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/68/53/6853e5ad-4741-497a-b66f-ea104219102f.png&quot; width=&quot;1730&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;oWnM&quot;&gt;&lt;strong&gt;BERT pre-train&lt;/strong&gt; с размерностью модели d = 1024 на тех же датасетах - Wikipedia и BookCorpus. Отмечу, что BERT-large был увеличен с 24 до 32 слоев, чтобы сохранять одинаковое количество параметров.&lt;/p&gt;
  &lt;figure id=&quot;9LyV&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/f3/ef/f3ef490a-1463-46fe-8c3b-b2c71c444329.png&quot; width=&quot;1722&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;INwA&quot;&gt;&lt;strong&gt;SpeedTest&lt;/strong&gt; алгоритмов DenseAttention, standard BERT и FlashAttention-2:&lt;/p&gt;
  &lt;figure id=&quot;pfrn&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/f1/2f/f12f533d-8ccb-4329-9106-83df9fa1ae7b.png&quot; width=&quot;1728&quot; /&gt;
  &lt;/figure&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;5YLx&quot;&gt;На этом все! Спасибо, что дочитали до конца :)&lt;/p&gt;

</content></entry><entry><id>kittybytes:KGAUKPuVFgi</id><link rel="alternate" type="text/html" href="https://teletype.in/@kittybytes/KGAUKPuVFgi?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=kittybytes"></link><title>Linear Quantization</title><published>2024-10-28T10:40:21.500Z</published><updated>2024-10-28T10:40:21.500Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img1.teletype.in/files/84/a1/84a11121-cd5b-41fb-bffd-2859039726d0.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/e3/7d/e37d9015-7f39-40fe-88c9-cb9442ae3f83.png&quot;&gt;Разобравшись в основах параллелизма моделей, перейдем к не менее актуальной теме - квантизации. Квантизация моделей машинного обучения стала одним из ключевых направлений оптимизации нейронных сетей в последние годы. Этот метод позволяет значительно уменьшить вычислительные затраты и объем памяти, необходимые для работы моделей, сохраняя при этом высокую точность предсказаний.</summary><content type="html">
  &lt;p id=&quot;TxuY&quot;&gt;Разобравшись в основах параллелизма моделей, перейдем к не менее актуальной теме - &lt;strong&gt;квантизации&lt;/strong&gt;. Квантизация моделей машинного обучения стала одним из ключевых направлений оптимизации нейронных сетей в последние годы. Этот метод позволяет значительно уменьшить вычислительные затраты и объем памяти, необходимые для работы моделей, сохраняя при этом высокую точность предсказаний.&lt;/p&gt;
  &lt;p id=&quot;zUaY&quot;&gt;&lt;em&gt;Source&lt;/em&gt;: &lt;a href=&quot;https://t.me/quant_prune_distill&quot; target=&quot;_blank&quot;&gt;КПД&lt;/a&gt;, &lt;a href=&quot;https://www.deeplearning.ai/short-courses/quantization-in-depth/&quot; target=&quot;_blank&quot;&gt;Quantization in Depth&lt;/a&gt;&lt;/p&gt;
  &lt;hr /&gt;
  &lt;h3 id=&quot;S3SX&quot;&gt;Brief Literary Review&lt;/h3&gt;
  &lt;p id=&quot;JKt1&quot;&gt;Вообще исследователи уже давно стараются оптимизировать модельки, уменьшать их вес и размер, при сохранении качества работы. О &lt;a href=&quot;https://link.springer.com/article/10.1007/s43069-023-00223-6&quot; target=&quot;_blank&quot;&gt;прунинге случайного леса&lt;/a&gt; я слышал еще в самом начале своей карьеры, а с появлением LLM эта тема и вовсе получила финансовый толчок от разных компаний, ведь инференсить квантизованную модельку дешевле, удобнее и быстрее, чем тратить миллионы у.е. на деплой Llama70B.&lt;/p&gt;
  &lt;p id=&quot;0uU6&quot;&gt;Фундаментальной работой в этой области стало исследование &lt;a href=&quot;https://proceedings.neurips.cc/paper_files/paper/2022/hash/c3ba4962c05c49636d4c6206a97e9c8a-Abstract-Conference.html&quot; target=&quot;_blank&quot;&gt;GPT3.int8(): 8-bit Matrix Multiplication for Transformers at Scale&lt;/a&gt; в которой авторы показали возможность эффективной 8-битной квантизации крупных языковых моделей. Далее ребята из Яндекса сказали, что это не предел и выкатили &lt;a href=&quot;https://arxiv.org/html/2401.06118v2&quot; target=&quot;_blank&quot;&gt;Extreme Compression of Large Language Models via Additive Quantization&lt;/a&gt; с возможностью сжатия до 2-3 битов, при чем их метод аддитивной квантизации является Парето-оптимальным с точки зрения соотношения точности и размера модели при сжатии до менее чем 3 битов на параметр. Этот же метод потом раскатили и на диффузионных моделях &lt;a href=&quot;https://huggingface.co/papers/2409.00492&quot; target=&quot;_blank&quot;&gt;Accurate Compression of Text-to-Image Diffusion Models via Vector Quantization&lt;/a&gt;, что дало результаты лучше, чем &lt;a href=&quot;https://arxiv.org/abs/2302.04304&quot; target=&quot;_blank&quot;&gt;Q-Diffusion: Quantizing Diffusion Models&lt;/a&gt; и &lt;a href=&quot;https://arxiv.org/abs/2211.15736&quot; target=&quot;_blank&quot;&gt;Post-training Quantization on Diffusion Models&lt;/a&gt;. Ну и вскоре китайцы в работе &lt;a href=&quot;https://arxiv.org/abs/2409.17066&quot; target=&quot;_blank&quot;&gt;VPTQ: Extreme Low-bit Vector Post-Training Quantization for Large Language Models&lt;/a&gt; показали, что векторная квантизация лучше всех и мы можем сжимать модели до 2 бит, при этом значительно снижать перплексию квантованных моделей и улучшать точность на задачах вопросов и ответов. А тут &lt;a href=&quot;https://arxiv.org/abs/2108.08842&quot; target=&quot;_blank&quot;&gt;EDEN: Communication-Efficient and Robust Distributed Mean Estimation for Federated Learning&lt;/a&gt; ребята показали, что квантовать можно не только веса и активации, но и градиенты.&lt;/p&gt;
  &lt;p id=&quot;bGua&quot;&gt;Из этого короткого литобзора видна довольно высокая актуальность методов квантизации, что еще раз подтолкнуло меня на разбор некоторых методов более подробно. Однако я не хочу, чтобы кто-то подумал, что я профессионал квантизации и разберу сразу все в своих постах. Если вы хотите углубиться в эту тему или развиваться в ней, то рекомендую канал &lt;a href=&quot;https://t.me/quant_prune_distill&quot; target=&quot;_blank&quot;&gt;КПД&lt;/a&gt; - там вы найдете море постов про квантизацию моделей, а сам автор является тем самым профессионалом в этой сфере.&lt;/p&gt;
  &lt;p id=&quot;YJxJ&quot;&gt;Моей целью будет являться разбор базовых понятий, которые помогли бы в дальнейшем понимать сложные вещи, которые я буду разбирать чуть позже. Мне показалось, что сделать это будет проще всего по &lt;a href=&quot;https://www.deeplearning.ai/short-courses/quantization-in-depth/&quot; target=&quot;_blank&quot;&gt;курсу о квантизации от HuggingFace&lt;/a&gt;, так что если вы его не смотрели или у вас не хватало времени на него, то у вас появилась отличная возможность прочитать summary этого курса :)&lt;/p&gt;
  &lt;h3 id=&quot;Irp5&quot;&gt;Linear Quantization&lt;/h3&gt;
  &lt;p id=&quot;543G&quot;&gt;Попробуем разобрать основные понятия на примере &lt;strong&gt;линейной квантизации&lt;/strong&gt; - это одна из самых базовых техник квантизации, когда мы хотим взаимооднозначно перевести значения из одного типа данных в другой (менее затратный по памяти).&lt;/p&gt;
  &lt;figure id=&quot;Umnj&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/a9/45/a9450b4d-99b0-419f-a710-fc1ea1b835fa.png&quot; width=&quot;407.84615384615387&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;uTx6&quot;&gt;Параметры &lt;strong&gt;s&lt;/strong&gt; (scale) и &lt;strong&gt;z&lt;/strong&gt; (zero point) имеют тот же тип данных, как у &lt;strong&gt;original value&lt;/strong&gt; и &lt;strong&gt;quantized value&lt;/strong&gt; соответственно. Из формулы выше закономерно вытекает формула квантизация вектора.&lt;/p&gt;
  &lt;figure id=&quot;eZbC&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/2e/56/2e565f6b-b9be-49f2-9134-3db5ee53b2d6.png&quot; width=&quot;389&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;PBz9&quot;&gt;Как видно, линейная квантизация не является каким-то rocket science, однако возникает закономерный вопрос - а как определить те самые &lt;strong&gt;s&lt;/strong&gt; (scale) и &lt;strong&gt;z&lt;/strong&gt; (zero point)? Для этого существуют отдельные формулы, из которых, с помощью границ области определения оригинальных значений и квантизованных, определяются &lt;strong&gt;s&lt;/strong&gt; и &lt;strong&gt;z&lt;/strong&gt;:&lt;/p&gt;
  &lt;figure id=&quot;GmUd&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/23/7e/237e724d-afe0-4317-80dc-cc526b1af28f.png&quot; width=&quot;430&quot; /&gt;
  &lt;/figure&gt;
  &lt;blockquote id=&quot;CfZv&quot;&gt;В случае, если &lt;strong&gt;zero point&lt;/strong&gt; выходит из области значений &lt;strong&gt;q&lt;/strong&gt;, то он приравнивается ближайшему крайнему значению &lt;strong&gt;q&lt;/strong&gt;. Например, если &lt;strong&gt;z&lt;/strong&gt; &amp;lt; &lt;strong&gt;q_min&lt;/strong&gt;, тогда &lt;strong&gt;z&lt;/strong&gt; = &lt;strong&gt;q_min&lt;/strong&gt;.&lt;/blockquote&gt;
  &lt;p id=&quot;Mhab&quot;&gt;Linear Quantization бывает двух типов: &lt;strong&gt;asymmetric&lt;/strong&gt; и &lt;strong&gt;symmetric&lt;/strong&gt;. Asymmetric мы разбирали только что, когда r_min не равен по модулю r_max (аналогично с q_min и q_max). В symmetric нам нет необходимости использовать zero point, ведь если q_min = q_max, то это просто середина отрезка, которая сопадает с серединой отрезка [-r_max; r_max] и равняется &lt;strong&gt;нулю&lt;/strong&gt;. Из-за этого формулы немного изменятся, но суть останется той же:&lt;/p&gt;
  &lt;figure id=&quot;Jgp1&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/33/98/33984419-282c-4d9c-8f50-6f871ca29159.png&quot; width=&quot;442&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;G23U&quot;&gt;Granularity&lt;/h3&gt;
  &lt;p id=&quot;izfu&quot;&gt;Также стоит добавить, что нет необходимости расчитывать в Linear Quantization параметры zero point и scale для всего тензора - это может негативно повлиять на точность. Лучше рассчитывать отдельные zero point и scale для каждой группы (например для каждой строки или даже n элементов тензора) - это и называется &lt;strong&gt;Granularity&lt;/strong&gt;.&lt;/p&gt;
  &lt;figure id=&quot;zjsK&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/73/51/7351cfdf-2643-4514-8aa2-d339fc0108d5.png&quot; width=&quot;461&quot; /&gt;
  &lt;/figure&gt;
  &lt;blockquote id=&quot;YuhP&quot;&gt;Очевидно, что чем меньше количество элементов в группе, тем точнее будет полученная квантизация, но тем больше памяти необходимо для хранения переменных.&lt;/blockquote&gt;
  &lt;h3 id=&quot;VUea&quot;&gt;Activations Linear Quantization&lt;/h3&gt;
  &lt;p id=&quot;FGZY&quot;&gt;Помимо весов, мы можем квантовать активации. В курсе приводится следующее мнение:&lt;/p&gt;
  &lt;p id=&quot;wSdb&quot;&gt;1) При квантовании весов мы можем использовать типы данных с плавающей точкой, что также позволяет нам де-квантовать тензор (применить обратную операцию к квантизации) и проверить точность квантования с оригинальным тензором.&lt;/p&gt;
  &lt;p id=&quot;Rag9&quot;&gt;2) Когда мы квантуем веса и активации, то часто нам необходимы типы данных в 8 битной памяти, поэтому используются такие как INT8 или INT4. Я не очень понял, что они имели в виду, когда писали, что деквантизация не поддерживается на некоторых устройствах, поэтому если вы поняли этот момент, то напишите в комментариях.&lt;/p&gt;
  &lt;figure id=&quot;5n2p&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/e2/da/e2daa02a-038a-46da-beac-24602f9e7dec.png&quot; width=&quot;506&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;NbJS&quot;&gt;Weights Packing&lt;/h3&gt;
  &lt;p id=&quot;hkRi&quot;&gt;Мы точно можем квантовать до 8 бит, но в сфере больших моделей это может вызвать проблемы - особенно когда значения будут все чаще выходить из этого диапазона, а точность закономерно будет падать, то мощностей мы сильно не сэкономим. Хочется квантовать в еще менее малое количество битов, чтобы эффективно сохранять память.&lt;/p&gt;
  &lt;p id=&quot;Q13Y&quot;&gt;А можем ли мы беспрепятственно квантовать до 2 бит? Кажется, мы можем столкнуться с ограничениями системы счисления, ведь довольно странно отображать числа в двух битах. Однако мы можем схитрить и таки перевести 8-битный тензор в 2-битный.&lt;/p&gt;
  &lt;p id=&quot;iu8U&quot;&gt;Допустим у нас будет тензор, состоящий из 4 чисел в формате UINT8:&lt;/p&gt;
  &lt;p id=&quot;Vb9D&quot; data-align=&quot;center&quot;&gt;&lt;strong&gt;[1, 0, 3, 2]&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;Y5zf&quot;&gt;Их двоичная запись будет выглядеть вот так:&lt;/p&gt;
  &lt;figure id=&quot;8RmW&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/3a/cd/3acd57ed-5abf-4607-a6b0-c05e629c4b9d.png&quot; width=&quot;468&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;y1tp&quot;&gt;Разумеется, нам совсем не хочется хранить нулевые биты, которые не несут никакой информации:&lt;/p&gt;
  &lt;figure id=&quot;3DdK&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/ec/fa/ecfa4e58-ce6f-44aa-aab4-3dd2e29642d6.png&quot; width=&quot;470.00000000000006&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;79eS&quot;&gt;Поэтому мы уберем их и соединим информативные биты в единое чило формата 8 бит:&lt;/p&gt;
  &lt;figure id=&quot;TBki&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/6b/d2/6bd2f33b-5d9e-4281-a70c-3662852bac21.png&quot; width=&quot;467&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;jE11&quot;&gt;Или же в десятичной записи:&lt;/p&gt;
  &lt;p id=&quot;3io1&quot; data-align=&quot;center&quot;&gt;&lt;strong&gt;[177]&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;WBum&quot;&gt;Итак, наша 2-битная квантизация заработала. Она позволяет отображать реальный объем памяти, который занимают 2-битные квантизованные веса. Однако у этого метода есть и недостаток: если мы хотим прогнать инференс модели, нам нужно распаковать параметры, потому что pytorch не поддерживает инференс на 2-битных весах.&lt;/p&gt;
  &lt;p id=&quot;q0yl&quot;&gt;В коде это можно иплементировать с помощью операции &lt;strong&gt;OR&lt;/strong&gt;, постепенно сдвигая биты и записывая их в заранее созданный тензор нужного размера:&lt;/p&gt;
  &lt;pre id=&quot;lIju&quot;&gt;for i in range(num_values):
    for j in range(num_steps):
        packed_tensor[i] |= uint8tensor[unpacked_idx] &amp;lt;&amp;lt; (bits * j)
        unpacked_idx += 1
return packed_tensor&lt;/pre&gt;
  &lt;p id=&quot;zOK0&quot;&gt;А &lt;strong&gt;unpacking&lt;/strong&gt;:&lt;/p&gt;
  &lt;pre id=&quot;xKe4&quot;&gt;mask = 2 ** bits - 1

for i in range(uint8tensor.shape[0]):
    for j in range(num_steps):
        unpacked_tensor[unpacked_idx] |= uint8tensor[i] &amp;gt;&amp;gt; (bits * j)
        unpacked_idx += 1

unpacked_tensor &amp;amp;= mask&lt;/pre&gt;
  &lt;h3 id=&quot;cXJW&quot;&gt;Beyond Linear Quantization&lt;/h3&gt;
  &lt;p id=&quot;6mGZ&quot;&gt;LLMs становятся все больше и больше, поэтому линейная квантизация, конечно же, уже не обеспечивает должного эффекта экономии памяти и вычислений. Помимо нее существуют и другие методы, такие как &lt;a href=&quot;https://arxiv.org/abs/2208.07339&quot; target=&quot;_blank&quot;&gt;LLM.int8&lt;/a&gt;, &lt;a href=&quot;https://arxiv.org/abs/2210.17323&quot; target=&quot;_blank&quot;&gt;GPTQ&lt;/a&gt;, &lt;a href=&quot;https://arxiv.org/abs/2211.10438&quot; target=&quot;_blank&quot;&gt;SmoothQuant&lt;/a&gt;, &lt;a href=&quot;https://arxiv.org/abs/2306.00978&quot; target=&quot;_blank&quot;&gt;AWQ&lt;/a&gt; и так далее. Их статьи я буду разбирать в следующих постах.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;uCc8&quot;&gt;На этом у меня все :)&lt;br /&gt;Спасибо, что дочитали до конца!&lt;/p&gt;

</content></entry><entry><id>kittybytes:tRLSFiF8cuI</id><link rel="alternate" type="text/html" href="https://teletype.in/@kittybytes/tRLSFiF8cuI?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=kittybytes"></link><title>Large Parallelism Post: Part V. FSDP: Fully Sharded Data Parallel</title><published>2024-08-19T13:54:47.501Z</published><updated>2024-08-19T13:54:47.501Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img3.teletype.in/files/28/86/2886034d-873e-44e8-9faf-ea970b21ebc1.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/54/c6/54c6d0c1-54fd-4825-89e5-c7c1db46ab83.png&quot;&gt;FSDP - параллелизм с полным шардингом данных. Реализован за счет разбиения операции AllReduce на две - ReduceScatter и AllGather, а также за счет перегруппировки этих операций. Имея шард модели, данные весов собираются с других GPU за счет AllGather, далее происходит Forward pass, после чего снова собираются веса через AllGather, и только потом проиходит Backward pass. В конце градиенты обновляются с помощью ReduceScatter. Имеет 3 типа шардинга - DDP, Hybrid Sharding и Full Sharding.</summary><content type="html">
  &lt;p id=&quot;27R6&quot;&gt;FSDP - параллелизм с полным шардингом данных. Реализован за счет разбиения операции AllReduce на две - ReduceScatter и AllGather, а также за счет перегруппировки этих операций. Имея шард модели, данные весов собираются с других GPU за счет AllGather, далее происходит Forward pass, после чего снова собираются веса через AllGather, и только потом проиходит Backward pass. В конце градиенты обновляются с помощью ReduceScatter. Имеет 3 типа шардинга - DDP, Hybrid Sharding и Full Sharding.&lt;/p&gt;
  &lt;p id=&quot;lcpS&quot;&gt;Source: &lt;a href=&quot;https://huggingface.co/docs/transformers/fsdp&quot; target=&quot;_blank&quot;&gt;HuggingFace&lt;/a&gt;, &lt;a href=&quot;https://habr.com/ru/companies/wunderfund/articles/659497/&quot; target=&quot;_blank&quot;&gt;Habr&lt;/a&gt;, &lt;a href=&quot;https://engineering.fb.com/2021/07/15/open-source/fsdp/&quot; target=&quot;_blank&quot;&gt;Meta&lt;/a&gt;&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;8gLP&quot;&gt;Обратимся к &lt;a href=&quot;https://teletype.in/@kittybytes/rfMgEGnBsk1#iavO&quot; target=&quot;_blank&quot;&gt;разобранному ранее&lt;/a&gt; методу DDP - модель копировалась на каждую машину с GPU, далее проходил расчет Forward и Backward проходов, а полученные значения градиентов усреднялись с помощью &lt;a href=&quot;https://docs.nvidia.com/deeplearning/nccl/user-guide/docs/usage/collectives.html#allreduce&quot; target=&quot;_blank&quot;&gt;AllReduce&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;g9bk&quot;&gt;Разработчики задаись вопросо - почему бы не изменить пайплайн усреднения и передачи данных? Ведь это можно сделать с помощью разбиения операции AllReduce на две: &lt;a href=&quot;https://docs.nvidia.com/deeplearning/nccl/user-guide/docs/usage/collectives.html#reducescatter&quot; target=&quot;_blank&quot;&gt;ReduceScatter&lt;/a&gt; и &lt;a href=&quot;https://docs.nvidia.com/deeplearning/nccl/user-guide/docs/usage/collectives.html#allgather&quot; target=&quot;_blank&quot;&gt;AllGather&lt;/a&gt;.&lt;/p&gt;
  &lt;figure id=&quot;rN7u&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/d6/41/d6419ec5-ddcc-4a4b-acdb-57c974d8f566.png&quot; width=&quot;1494&quot; /&gt;
  &lt;/figure&gt;
  &lt;blockquote id=&quot;CH4S&quot;&gt;На фазе ReduceScatter градиенты суммируются в виде одинаковых блоков по рангам на каждом GPU на основании индексов их рангов. На фазе AllGather шард-порция агрегированных градиентов, имеющаяся на каждом GPU, делается доступной всем GPU.&lt;/blockquote&gt;
  &lt;p id=&quot;azKd&quot;&gt;Далее операции ReduceScatter и AllGather перегруппировываются таким образом, чтобы каждому DDP-воркеру нужно было бы хранить лишь единственный шард параметров и состояний оптимизатора.&lt;/p&gt;
  &lt;figure id=&quot;9Agh&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/be/ad/beadd1a0-3aae-49ed-a251-4a8e8f5a953b.png&quot; width=&quot;1494&quot; /&gt;
    &lt;figcaption&gt;При использовании стандартного метода DDP копия модели имеется на каждом GPU, а последовательность вычислений, необходимых для выполнения прямых и обратных проходов по модели, выполняется лишь на фрагменте данных. После выполнения таких вот локальных вычислений, значения параметров и состояния оптимизаторов локальных процессов делаются доступными другим GPU для вычисления значений, необходимых для глобального обновления весов&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure id=&quot;CrnB&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/64/9b/649bf49b-3199-41eb-94bb-fd9b71b07b0a.png&quot; width=&quot;1494&quot; /&gt;
    &lt;figcaption&gt;При применении FSDP на GPU имеется лишь шард модели. Сведения о весах, для выполнения прямого прохода по модели, собирают с других GPU посредством шага AllGather. Потом, до выполнения обратного прохода, сбор данных о весах выполняется снова. После выполнения обратного прохода локальные градиенты усредняются и распространяются между всеми GPU посредством шага ReduceScatter. Это позволяет каждому GPU обновить свой локальный шард весов&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;zGp7&quot;&gt;Более подробный пайплайн полного прохода FSDP на трех нодах [0-2] изображен на рисунке ниже.&lt;/p&gt;
  &lt;figure id=&quot;kPPq&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/e3/33/e33374fb-3050-448a-8b9d-6640733fa4fd.png&quot; width=&quot;2082&quot; /&gt;
    &lt;figcaption&gt;Source: &lt;a href=&quot;https://arxiv.org/abs/2304.11277&quot; target=&quot;_blank&quot;&gt;PyTorch FSDP: Experiences on Scaling Fully Sharded Data Parallel&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;av4q&quot;&gt;Разработчики также отмечают, что существуют разные стратегии шардинга параметров. Для их классификации они вводят коэффициент &lt;em&gt;&lt;strong&gt;F&lt;/strong&gt;&lt;/em&gt; - количество уровней, на которые распределяется модель. Например, если &lt;em&gt;&lt;strong&gt;F&lt;/strong&gt;&lt;/em&gt; равен 1, то вся модель будет загружена на каждую моду и мы получим классический DDP - этот случай разобран &lt;a href=&quot;https://teletype.in/@kittybytes/rfMgEGnBsk1#iavO&quot; target=&quot;_blank&quot;&gt;в прошлом посте&lt;/a&gt;. Если &lt;em&gt;&lt;strong&gt;F&lt;/strong&gt;&lt;/em&gt; задать количество GPU (обозначим это количество за &lt;em&gt;&lt;strong&gt;W&lt;/strong&gt;&lt;/em&gt;), то тогда на каждой ноде будет 1/&lt;em&gt;&lt;strong&gt;W&lt;/strong&gt;&lt;/em&gt; часть модели. Также есть гибридный шардинг - когда &lt;em&gt;&lt;strong&gt;F&lt;/strong&gt;&lt;/em&gt; принимает значения между 1 и &lt;em&gt;&lt;strong&gt;W&lt;/strong&gt;&lt;/em&gt;. Кратко отмечу параметры &lt;em&gt;Full Sharding&lt;/em&gt; (F=W) и &lt;em&gt;Hybrid Sharding &lt;/em&gt;(1&amp;lt;F&amp;lt;W) для случая с 16 GPU:&lt;/p&gt;
  &lt;p id=&quot;kIoK&quot;&gt;&lt;em&gt;Full Sharding&lt;/em&gt; - вся модель шардится по всем GPU. На рисунке видно, что все веса разделяются между нодами, при этом они состовляют единую группу шардинга, так как являются частями одной модели. Этот способ обеспечивает наименьший объем занимаемой памяти, но требует наибольших затрат на коммуникацию параметров (в 1,5 раза больше, чем в DDP)&lt;/p&gt;
  &lt;figure id=&quot;pxgZ&quot; class=&quot;m_retina&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/db/f8/dbf8acae-5868-43dd-9357-a7969397e950.png&quot; width=&quot;652&quot; /&gt;
    &lt;figcaption&gt;Source: &lt;a href=&quot;https://arxiv.org/abs/2304.11277&quot; target=&quot;_blank&quot;&gt;PyTorch FSDP: Experiences on Scaling Fully Sharded Data Parallel&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;7EYs&quot;&gt;&lt;em&gt;Hybrid Sharding&lt;/em&gt; - это нечто среднее между DDP и Full Sharding. В этом случае у нас есть частичная репликация модели, а также шардинг ее параметров. Розовым выделены части шардинга модели - то есть модель копируется два раза, а веса каждой копии (репликация) распределяются между GPU (в данном случае 8). Чтобы проще это понять, представьте, что вы взяли 16 GPU и задали параметр шардинга 8 - это значит, что у вас будет 16/8 = 2 группы шардинга на 8 GPU - то есть на 16 GPU будет лежать 2 модели. А веса этих двух одинаковых моделей распределяться  по группам репликации - для каждой группы по 2 GPU. Так как каждая модель разбивается на 8 частей - вот и получается 16 GPU. Такой способ используется для моделей средних размеров - они слишком малы для полного шардинга из-за низкой скорости коммуникации и слишком большие, чтобы их тренировать классическим DDP.&lt;/p&gt;
  &lt;figure id=&quot;JU7G&quot; class=&quot;m_retina&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/56/a3/56a3746b-24b3-49d2-b182-3edd27e78999.png&quot; width=&quot;678&quot; /&gt;
    &lt;figcaption&gt;Source: &lt;a href=&quot;https://arxiv.org/abs/2304.11277&quot; target=&quot;_blank&quot;&gt;PyTorch FSDP: Experiences on Scaling Fully Sharded Data Parallel&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Q6Of&quot;&gt;Для финала приведу показатели TFLPOS на каждое GPU от размера моделей. Видно, что Full Sharding немного выигрывает в каждом варианте - однако не стоит забывать, что у него самый низкий показатель коммуникации параметров.&lt;/p&gt;
  &lt;figure id=&quot;h0T3&quot; class=&quot;m_custom&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/6b/59/6b5932e1-076a-4a9d-9e9a-dcae7cefa0e1.png&quot; width=&quot;421&quot; /&gt;
    &lt;figcaption&gt;Source: &lt;a href=&quot;https://arxiv.org/abs/2304.11277&quot; target=&quot;_blank&quot;&gt;PyTorch FSDP: Experiences on Scaling Fully Sharded Data Parallel&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;AGeY&quot;&gt;На этом у меня все!&lt;br /&gt;Спасибо, что дочитали до конца :)&lt;/p&gt;

</content></entry><entry><id>kittybytes:gykfpHkEATb</id><link rel="alternate" type="text/html" href="https://teletype.in/@kittybytes/gykfpHkEATb?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=kittybytes"></link><title>Large Parallelism Post: Part IV. ZeRO: Memory Optimizations Toward Training Trillion Parameter Models</title><published>2024-08-12T20:03:39.166Z</published><updated>2024-08-12T20:08:06.152Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img4.teletype.in/files/31/18/3118395a-f863-4ddc-8154-1d2d1ceab3dd.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/0d/9d/0d9dd443-4b6d-490a-b9b6-01261cc5bb5b.png&quot;&gt;В данной статье разработан метод параллельного обучения моделей с большим количеством параметров (от 100млрд до 1трл). ZeRO позволяет параллельно хранить и вычислять параметры модели, градиенты и параметры оптимизатора, сохраняя при этом низкий объем коммуникаций и высокую гранулярность вычислений.</summary><content type="html">
  &lt;p id=&quot;b6C9&quot;&gt;В данной статье разработан метод параллельного обучения моделей с большим количеством параметров (от 100млрд до 1трл). ZeRO позволяет параллельно хранить и вычислять параметры модели, градиенты и параметры оптимизатора, сохраняя при этом низкий объем коммуникаций и высокую гранулярность вычислений.&lt;/p&gt;
  &lt;p id=&quot;QHym&quot;&gt;&lt;em&gt;Source&lt;/em&gt;: &lt;a href=&quot;https://arxiv.org/abs/1910.02054&quot; target=&quot;_blank&quot;&gt;Arxive&lt;/a&gt;, &lt;a href=&quot;https://www.microsoft.com/en-us/research/blog/zero-deepspeed-new-system-optimizations-enable-training-models-with-over-100-billion-parameters/&quot; target=&quot;_blank&quot;&gt;ZeRO&amp;amp;DeepSpeed&lt;/a&gt;&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;eAYL&quot;&gt;Многие методы параллелизма неоптималены, когда речь идет про тяжелые модели и огромное количество данных. При этом хочется, чтобы паралеллизм сохранялся и в модели, и в данных. Ребята из Microsoft придумали &lt;strong&gt;Zero Redundancy Optimizer&lt;/strong&gt; (&lt;a href=&quot;https://www.microsoft.com/en-us/research/blog/zero-deepspeed-new-system-optimizations-enable-training-models-with-over-100-billion-parameters/&quot; target=&quot;_blank&quot;&gt;ZeRO&lt;/a&gt;) - метод тренировки очень больших моделей (&amp;gt;100B параметров).&lt;/p&gt;
  &lt;p id=&quot;c90k&quot;&gt;Для начала опишу проблему тренировки больших моделей - модель с 1 триллионом параметров и оптимизатором Adam в точности FP16 требует 16 Тб данных для хранения ее параметров, градиетов и значений оптимизатора. Такой объем данных необходимо разделять не только по памяти, но и вычислительно. &lt;/p&gt;
  &lt;p id=&quot;zk83&quot;&gt;ZeRO имеет два подхода - &lt;strong&gt;ZeRO-DP&lt;/strong&gt; (Data Parallel - сам механизм параллелизма) и &lt;strong&gt;ZeRO-R&lt;/strong&gt; (Residual - оптимизация работы с памятью, чтобы ZeRO-DP работал корректно).&lt;/p&gt;
  &lt;p id=&quot;ULHY&quot;&gt;Начнем с &lt;strong&gt;ZeRO-DP&lt;/strong&gt; - он имеет три вида оптимизации, которые соответствуют трем видам разделения параметров памяти:&lt;/p&gt;
  &lt;p id=&quot;xg3j&quot;&gt;1) Параметры модели. Это синий цвет и они имеют точность FP16&lt;/p&gt;
  &lt;p id=&quot;zCjD&quot;&gt;2) Градиенты точностью FP16 (оранжевый цвет), которые будут использованы для обновления весов на Backward проходе&lt;/p&gt;
  &lt;p id=&quot;j7fP&quot;&gt;3) Состояния оптимизатора. Выделены зеленым цветом - в него входят точные значения градиентов, дисперсия, моменты в точности FP32. Если вы используете SGD, то он не будет занимать много памяти, но вот если Adam, то памяти нужно будет тратить гораздо больше. Эти данные используются только после вычисления оранжевых градиентов.&lt;/p&gt;
  &lt;blockquote id=&quot;dnPK&quot;&gt;FP16, FP32 - это точность, где 16 и 32 это сколько бит отводится под хранение. Подробнее про стандарт IEEE 754 можно посмотреть &lt;a href=&quot;https://www.youtube.com/watch?v=U0U8Ddx4TgE&amp;t=97s&amp;ab_channel=AlekOS&quot; target=&quot;_blank&quot;&gt;вот здесь&lt;/a&gt;.&lt;/blockquote&gt;
  &lt;figure id=&quot;kiLR&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/0d/9d/0d9dd443-4b6d-490a-b9b6-01261cc5bb5b.png&quot; width=&quot;1868&quot; /&gt;
    &lt;figcaption&gt;fi - количество параметров модели, K - константа оптимизатора (у Adam K=12), N - на сколько GPU параллелим   &lt;a href=&quot;https://www.microsoft.com/en-us/research/blog/zero-deepspeed-new-system-optimizations-enable-training-models-with-over-100-billion-parameters/&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;tPdw&quot;&gt;&lt;strong&gt;ZeRO-R &lt;/strong&gt;разработан для оптимизации работы с остаточной памятью во время работы ZeRO-DP. Вот что он делает:&lt;/p&gt;
  &lt;p id=&quot;aqOn&quot;&gt;1) Сохраняя промежуточные активации на Forward pass, чтобы использовать их на Backward pass, можно оптимизировать вычисления (&lt;a href=&quot;https://arxiv.org/abs/1604.06174&quot; target=&quot;_blank&quot;&gt;Training Deep Nets with Sublinear Memory Cost&lt;/a&gt;), но это не работает на больших моделях. ZeRO-R разделяет и удаляет отработавшие реплики активаций.&lt;/p&gt;
  &lt;p id=&quot;teyC&quot;&gt;2) Определяет соответствующий размер временного буффера для нахождения баланса памяти и вычислений (я не смог найти исходный код, поэтому не могу подробно рассказать как он это делает)&lt;/p&gt;
  &lt;p id=&quot;XBqJ&quot;&gt;3) Предотвращает фрагментацию памяти. ZeRO-R управляет памятью, основываясь на различном времени жизни тензоров - скорее всего в зависимости от создания тензора он двигает его в ячейках памяти, чтобы не нарваться на OOM ошибку.&lt;/p&gt;
  &lt;blockquote id=&quot;DTn3&quot;&gt;&lt;u&gt;Фрагментация памяти&lt;/u&gt; - возникает когда вы заняли место в памяти, потом освободили часть из нее и пытаетесь записать память большего рамера. Проблема заключается в неудаленном фрагменте, который находится как бы посередине и из-за которого необходимо увеличивать ресурс, что может привести к ошибке переполнения (OOM) при одновременной доступности свободной памяти. Наглядно продемонстрировано &lt;a href=&quot;https://stackoverflow.com/questions/3770457/what-is-memory-fragmentation%D1%91&quot; target=&quot;_blank&quot;&gt;тут&lt;/a&gt;&lt;/blockquote&gt;
  &lt;p id=&quot;LFHm&quot;&gt;Давайте разберемся как оба подхода работают вместе.&lt;/p&gt;
  &lt;p id=&quot;I0yP&quot;&gt;Допустим у нас есть 4 карточки и тогда разделим входые данные на 4 части. Сама модель тоже делится на 4 части M [0-3], каждая из которых хранится на отдельном GPU (помним, что модель очень большая и хранить ее целиком на одной карте мы не можем). На каждой GPU создаем временные буфферы, в которых будем хранить промежуточные активации - они понадобятся на Backward pass. Далее с помощью &lt;a href=&quot;https://docs.nvidia.com/deeplearning/nccl/user-guide/docs/usage/operations.html#broadcast&quot; target=&quot;_blank&quot;&gt;broadcast&lt;/a&gt; распределяем параметры модели (голубые) с GPU [0] на каждое GPU [1-3] - параметров не так много и эта операция довольно дешевая.&lt;/p&gt;
  &lt;figure id=&quot;QrSz&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/ba/a0/baa0156d-4890-4e05-8964-8540dc5f543f.png&quot; width=&quot;2828&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;4zhw&quot;&gt;На каждом GPU считаем Forward pass. Нужно заметить, что здесь мы сохраняем в буффер лишь часть активаций, чтобы не вызвать переполнение памяти.&lt;/p&gt;
  &lt;figure id=&quot;irzw&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/d4/70/d4705c50-6c46-4c30-9c70-12ce3ec639ac.png&quot; width=&quot;2826&quot; /&gt;
  &lt;/figure&gt;
  &lt;blockquote id=&quot;XvGh&quot;&gt;Мы уже сталкивались с Activation Checkpointing в прошлой статье, однако я снова хочу остановиться на этом месте и объяснить более подробно, почему мы сохраняем лишь часть актиаций и что мы будем делать с ними потом. Я долго искал кодовую имплементацию ZeRO-R, но нашел лишь &lt;a href=&quot;https://nn.labml.ai/scaling/zero3/index.html&quot; target=&quot;_blank&quot;&gt;ZeRO-DP&lt;/a&gt;, где реализован случай полного параллелизма (ZeRO3 - случай os+g+p). Однако в документации &lt;a href=&quot;https://awsdocs-neuron.readthedocs-hosted.com/en/latest/libraries/neuronx-distributed/activation_memory_reduction.html#activation-recomputation&quot; target=&quot;_blank&quot;&gt;AWS Neuron&lt;/a&gt; подробно и понятно описано это решение. Все дело в памяти и скорости вычислений - мы хотим сохранять расчитанные активации на Forward pass, чтобы не рассчитывать их снова на Backward pass и экономить время для вычисления градиентов. Однако при работе с большими моделями, у нас не хватит памяти сохранять сразу все активации, поэтому мы сохраняем лишь активации последнего слоя. Когда начинается Backward pass, мы досчитываем необходимые активации между сохраненными слоями, применяем их для вычисления градиентов, обновляем веса и удаляем все рассчитанные активации из памяти. Так мы одновременно экономим на вычислениях и памяти - что-то вроде trade-off между памятью и вычислениями. Хотя я наткнулся на &lt;a href=&quot;https://github.com/Lightning-AI/pytorch-lightning/discussions/9144&quot; target=&quot;_blank&quot;&gt;дискуссию в GitHub&lt;/a&gt;, где говорят, что использовать такой метод доподсчета активаций не очень выгодно - видимо все зависит от размера модели.&lt;/blockquote&gt;
  &lt;p id=&quot;irle&quot;&gt;После расчета части параметров модели M0, мы удаляем эти параметры из памяти GPU [1-3] (вот тут начинает работать ZeRO-R), потому что сохранив промежуточные активации, нам нет надобности хранить эту часть данных - нам придется так делать с каждой частью модели, а вся она точно не поместиться.&lt;/p&gt;
  &lt;figure id=&quot;SLtT&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/db/d4/dbd4021c-0ccd-4919-96e4-7904594164d8.png&quot; width=&quot;2826&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Bf5C&quot;&gt;Так мы делаем для каждой части модели M [0-3] (то есть по очереди бродкастим параметры каждой части модели на остальные карты, считаем forward pass, сохраняем часть активаций и удаляем параметры). Когда процесс заканчивается на части M3, то на каждом GPU вычисляется значение Loss. Далее начинается Backward pass.&lt;/p&gt;
  &lt;figure id=&quot;IVP7&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/38/ce/38ceb197-9546-41c8-81d0-7b311e5cffd5.png&quot; width=&quot;2964&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;F7I5&quot;&gt;На каждом GPU дорасчитываются активации Forward (а часть уже сохранена) и на каждой карточке рассчитываются градиенты. Далее эти градиенты пересылаются на GPU [3], со всех остальных GPU и аккумулируются. Здесь происходит группировка градиентов - в оригинале авторы применяют на каждом процессе используют Reduce вместо AllReduce для экономии памяти.&lt;/p&gt;
  &lt;figure id=&quot;e5rJ&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/33/db/33db64a5-67dc-4027-950d-ec492f002835.png&quot; width=&quot;2964&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;lXmr&quot;&gt;После расчета градиентов M3 с каждой карты и сохранения их на GPU [3], на других GPU данные промежуточных активаций, градиентов и параметров модели удаляются для освобождения памяти.&lt;/p&gt;
  &lt;figure id=&quot;cVMA&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/d1/d7/d1d7b3fc-4310-4f92-8b58-acc287f78442.png&quot; width=&quot;2964&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;y5lT&quot;&gt;Аналогично пройдемся по остальным частям модели M [0-2] (во время Backward pass будем также делать broadcast параметров моделей на каждую карту и дорасчитывать параметры активаций) и в конце получим на каждом GPU параметры градиентов. Далее параметры градиентов запускаем в оптимизатор для обновления весов. Оптимизатор определит новые параметры модели в точности FP32, которые далее переведем в точность FP16. На этом шаге цикл завершается и все прошлые шаги повторяются заново.&lt;/p&gt;
  &lt;figure id=&quot;yAjy&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/f6/00/f600dd06-f322-46f2-80bc-8a81f12d50d2.png&quot; width=&quot;2964&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;DiV4&quot;&gt;Добавлю, что в 2023 году вышла статья &lt;a href=&quot;https://arxiv.org/abs/2306.10209&quot; target=&quot;_blank&quot;&gt;ZeRO++: Extremely Efficient Collective Communication for Giant Model Training&lt;/a&gt;, в которой сделано 3 улучшения - в совокупности они повышают эффективность работы алгоритма в 4 раза:&lt;/p&gt;
  &lt;ul id=&quot;Jnuj&quot;&gt;
    &lt;li id=&quot;tySw&quot;&gt;Квантование параметров с FP16 до INT8&lt;/li&gt;
    &lt;li id=&quot;4edO&quot;&gt;Иерархическое разбиение, которое позволяет избавиться от повторного вычисления данных&lt;/li&gt;
    &lt;li id=&quot;5kdS&quot;&gt;Квантование градиентов, которое позволяет применять all-to-all обмен данными (вместо AllReduce)&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;2yeM&quot;&gt;Пишите в комментариях, если хотите разбор статьи :)&lt;/p&gt;
  &lt;hr /&gt;
  &lt;h3 id=&quot;13YE&quot;&gt;Results&lt;/h3&gt;
  &lt;p id=&quot;G6se&quot;&gt;По результатам пробегуcь как всегда быстро, потому что кто бы публиковал статью с плохими результатами?)))&lt;/p&gt;
  &lt;p id=&quot;gpGA&quot;&gt;Видим, что ZeRO-DP отлично превосходит 10 и 15 Pflops на моделях с большим количеством параметров, когда другие методы проседают.&lt;/p&gt;
  &lt;figure id=&quot;Kdcp&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/41/ac/41acf908-7a86-41e2-a5c1-51211f0a1542.png&quot; width=&quot;1530&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;fZon&quot;&gt;Таблица с пояснением каждой конфигурации ZeRO - понадобиться для следующих графиков. С ZeRO-DP, думаю, все понятно - эти параметры нам встречались и ранее. А вот параметры ZeRO-R я прокомментирую:&lt;/p&gt;
  &lt;ul id=&quot;gDU9&quot;&gt;
    &lt;li id=&quot;YRk8&quot;&gt;CB - Constant Size Buffers - при больших моделях используется постоянный размер буфера (если модели маленькие, то буфер уменьшается)&lt;/li&gt;
    &lt;li id=&quot;KOfe&quot;&gt;MD - Memory Defragmentation - понятно, что фрагментация памяти так или иначе возникает при работе ZeRO, однако при работе с очень большими моделями он может выполнять дефрагментацию во время работы, предварительно выделяя смежные участки памяти для контрольных точек активации и градиентов и копируя их в предварительно выделенную память по мере их создания&lt;/li&gt;
    &lt;li id=&quot;JtKP&quot;&gt;Pa - Partitioned Activation Checkpointing - стандартный Activation Checkpointing, но в случае очень больших моделей и очень ограниченной памяти, данные могут быть перегружены на CPU&lt;/li&gt;
  &lt;/ul&gt;
  &lt;figure id=&quot;yENt&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/47/cb/47cb381f-90e5-4f0b-a867-4e7b91f34ec4.png&quot; width=&quot;270&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;tHRx&quot;&gt;На рисунке 6 показаны размеры моделей при использовании различных оптимизаций ZeRO для фиксированного batch-size = 16. На рисунке 7 показано максимальное количество памяти, кэшируемой PyTorch во время каждой итерации обучения для модели с параметрами 40B и 100B. Интересно, что 100В модель при конфигурации ZeRO 5 показывает примерно ту же кэшируемость, что и 40В на конфигурации 2 и 3. На рисунке 8 показано количество операций в секунду в зависимости от конфигурации ZeRO.&lt;/p&gt;
  &lt;figure id=&quot;sX0p&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/a7/02/a702b5f6-58f5-40c1-9ae4-995869282a99.png&quot; width=&quot;1942&quot; /&gt;
  &lt;/figure&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;ZOOK&quot;&gt;На этом у меня все!&lt;/p&gt;
  &lt;p id=&quot;nn3T&quot;&gt;Спасибо, что дочитали до конца :)&lt;/p&gt;

</content></entry><entry><id>kittybytes:IqgGHgK3Mrv</id><link rel="alternate" type="text/html" href="https://teletype.in/@kittybytes/IqgGHgK3Mrv?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=kittybytes"></link><title>Large Parallelism Post: Part III. Mixed Precision Training</title><published>2024-07-22T11:54:39.593Z</published><updated>2024-07-22T11:54:39.593Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img1.teletype.in/files/0d/d1/0dd1c1ea-ad8f-4596-a4da-34df53ff8c28.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img4.teletype.in/files/bb/10/bb10eb95-e835-4056-8421-9ce03e425754.png&quot;&gt;В статье предлагается способ снижения потребления памяти тренироки сетей в 2 раза за счет перевода величин из формата FP32 в FP16. Авторы статьи исследуют распределения полученных величин по экспоненте и вводят Loss Scaling для предовращения зануления значений активаций и увеличения точности. Последним дополнением является Arithmetic Precision - разграничение проводимых операций в FP32 и FP16 формате.</summary><content type="html">
  &lt;p id=&quot;IMha&quot;&gt;В статье предлагается способ снижения потребления памяти тренироки сетей в 2 раза за счет перевода величин из формата FP32 в FP16. Авторы статьи исследуют распределения полученных величин по экспоненте и вводят Loss Scaling для предовращения зануления значений активаций и увеличения точности. Последним дополнением является Arithmetic Precision - разграничение проводимых операций в FP32 и FP16 формате.&lt;/p&gt;
  &lt;p id=&quot;Z8wS&quot;&gt;&lt;em&gt;Source&lt;/em&gt;: &lt;a href=&quot;https://arxiv.org/abs/1710.03740&quot; target=&quot;_blank&quot;&gt;Arxive&lt;/a&gt;&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;hNfW&quot;&gt;Современные системы обучения глубокому обучению используют single-precision format FP32. Авторы предлагают тренировать сети используя IEEE half-precision format FP16 - то есть тратить в 2 раза меньше битов, а значит и в 2 раза меньше памяти, сохраняя при этом точность.&lt;/p&gt;
  &lt;figure id=&quot;GXoQ&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/bb/10/bb10eb95-e835-4056-8421-9ce03e425754.png&quot; width=&quot;2220&quot; /&gt;
    &lt;figcaption&gt;Примеры числовых форматов. &lt;a href=&quot;https://cloud.google.com/tpu/docs/bfloat16&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;MuNW&quot;&gt;FP32 Master Copy of Weights&lt;/h3&gt;
  &lt;p id=&quot;AphL&quot;&gt;Сам пайплайн mixed precision довольно прост и изображен на картинке ниже. Во время обучения веса, активации и градиенты хранятся в точности FP16, но для сохранения уровня точности создается Master Copy весов в FP32 формате, которая в последствии обновляется.&lt;/p&gt;
  &lt;figure id=&quot;dhhF&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/6f/25/6f25d6b4-52dc-4fe2-9ec8-0c76eaa219e6.png&quot; width=&quot;1416&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;FSBQ&quot;&gt;Хотя Master Copy весов FP32 далеко не всегда выгодно хранить, для этого есть причины. Первая: во многих сетях значения обновлений (веса умноженные на learning rate) могут быть меньше 2^(-24) - то есть они становятся просто нулем в оптимизаторе FP16. На гистограме ниже видно, что примерно 5% обновлений зануляются в half-precision format, что может негативно повлиять на точность модели.&lt;/p&gt;
  &lt;figure id=&quot;GXMA&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/93/d1/93d1a1fd-e39b-4b26-a7a3-d163a2c5d66c.png&quot; width=&quot;465.9999999999999&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;hI9q&quot;&gt;Вторая: веса могут занулиться в случае большой разницы между их значениями и обновлениями. Даже если значение весов можно представить в формате FP16, но значение обновления будет довольно большим, то при сложении этих параметров результат выйдет за границы формата FP16 и станет нулем, который невозможно будет восстановить. Именно Master Copy весов помогает избежать этого.&lt;/p&gt;
  &lt;p id=&quot;R6r3&quot;&gt;Хоть Master Copy весов FP32 увеличивает на 50% потребление памяти по сравнению с ипользованием только FP16, влияние на общее потребление памяти все равно остается куда меньшим, чем тренировка сети только в FP32 формате. Сохранение активаций каждого слоя при Backprop является самой затратной по памяти операцией, поэтому перевод этого процесса в FP16 снижает потребление примерно в 2 раза.&lt;/p&gt;
  &lt;h3 id=&quot;Ksy9&quot;&gt;Loss Scaling&lt;/h3&gt;
  &lt;p id=&quot;KiK4&quot;&gt;Ниже представлена диаграмма доли значений актиаций от экспоненты (2 в соответствующей степени). Легко заметить, что большинство значений выходят за пределы диапазона формата FP16, а значит они зануляются.&lt;/p&gt;
  &lt;figure id=&quot;rOIG&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/fe/95/fe957cbe-1ba1-465b-8609-3f3c9b4c9246.png&quot; width=&quot;502.00000000000006&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;plUL&quot;&gt;Чтобы решить эту проблему, авторы предлагают увеличить экспоненты в 3 раза (масштабировать в 8 раз) - этого будет достаточно для соответствия точности FP32 формата.&lt;/p&gt;
  &lt;p id=&quot;cGDY&quot;&gt;Для эффективного сдвига значений в FP16, достаточно масштабировать Loss, вычисленный за Forward pass, перед началом вычисления Backward pass. Chain rule позволяет сохранять масштабирование, а значит не потребуется проводить дополнительные вычисления. Величина масштабирования выбирается эмпирически.&lt;/p&gt;
  &lt;h3 id=&quot;aWAK&quot;&gt;Arithmetic Precision&lt;/h3&gt;
  &lt;p id=&quot;mOCg&quot;&gt;Для поддержания точности сетей, авторы обнаружили, что в некоторых сетях необходимо, чтобы векторное произведение накапливалось в формате FP32, а только потом переводилось в FP16 перед записью в память. Скорее всего это вызвано ограничениями точности арифместических операций в FP16. Тензорные ядра в архитектуре GPU Nvidia позволяют накапливать dot-product либо в FP16, либо в FP32 формате - &lt;a href=&quot;https://images.nvidia.com/content/volta-architecture/pdf/volta-architecture-whitepaper.pdf&quot; target=&quot;_blank&quot;&gt;Nvidia tesla v100 gpu architecture&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;qP5n&quot;&gt;Большие суммы по элементам вектора (такие как в batch-normalization или softmax) также должны выполняться в FP32 формате. То есть чтение и запись в память производится в FP16, а арифметика в FP32 для сохранения точности. Однако, это не замедляет скорость вычислений, поскольку такие операции ограничены пропускной способностью памяти.&lt;/p&gt;
  &lt;h3 id=&quot;HFz5&quot;&gt;Results&lt;/h3&gt;
  &lt;p id=&quot;OaK8&quot;&gt;Эксперименты проводились для двух подходов:&lt;/p&gt;
  &lt;ul id=&quot;NfhR&quot;&gt;
    &lt;li id=&quot;PJD3&quot;&gt;&lt;strong&gt;Baseline (FP32)&lt;/strong&gt; - активации, веса и градиенты хранятся в single-precision формате. Все вычисления также проводятся в FP32.&lt;/li&gt;
    &lt;li id=&quot;ITRZ&quot;&gt;&lt;strong&gt;Mixed Precision (MP)&lt;/strong&gt; - хранение в памяти и некоторые вычисления осущетсвляются в FP16. Веса, активации и градиенты хранятся в FP16, используется Master Copy весов в FP32. Для некоторых сетей используется Loss Scaling. Используются операции Tensor Core с накоплением в FP32 для сверток, полносвязных слоев и матричных умножений в рекуррентных слоях.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;9Z0J&quot;&gt;По таблице сравнения точности видно, что &lt;strong&gt;MP&lt;/strong&gt; нередко немного превосходит &lt;strong&gt;Baseline&lt;/strong&gt; вычисления.&lt;/p&gt;
  &lt;figure id=&quot;Cer8&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/5b/9f/5b9fae0a-179d-4da2-96be-5f45bc0b6842.png&quot; width=&quot;1794&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;eVXG&quot;&gt;Обратите внимание, как выбор величины Loss Scaling влияет на тренировку модели.&lt;/p&gt;
  &lt;figure id=&quot;Kv9L&quot; class=&quot;m_retina&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/7c/23/7c231c53-ca02-498b-9750-c31a933b3cdb.png&quot; width=&quot;651&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;RdS7&quot;&gt;Также эксперименты проводились с моделями CNNs Detection, Speech Recognition, Machine Translation, Language Modeling, GANs - все они показали результаты на уровне &lt;strong&gt;Baseline&lt;/strong&gt;.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;PzqW&quot;&gt;Спасибо, что дочитали до конца :)&lt;/p&gt;

</content></entry><entry><id>kittybytes:-dsArW3eCud</id><link rel="alternate" type="text/html" href="https://teletype.in/@kittybytes/-dsArW3eCud?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=kittybytes"></link><title>Large Parallelism Post: Part II. Megatron-LM: Training Multi-Billion Parameter Language Models Using Model Parallelism</title><published>2024-07-01T21:28:29.718Z</published><updated>2024-07-01T21:28:29.718Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img3.teletype.in/files/ac/e5/ace5b3e2-4444-4c2b-a14d-5387c2c67149.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/ce/d2/ced2eeb0-c103-4126-85f7-aca8b82c9318.png&quot;&gt;Во второй части мы углубимся в Tensor Parallelism на основе статьи Megatron-LM. В ней представлен способ параллельных вычислений внутри блоков MLP и Attention. Благодаря разделению весовых матриц по столбцам и строкам, становится возможным распараллелить блоки MLP и Attention между GPU с минимальными коммуникациями между нодами. Также разберем пайплайн TP+DP.</summary><content type="html">
  &lt;p id=&quot;hEbt&quot;&gt;Во второй части мы углубимся в Tensor Parallelism на основе статьи Megatron-LM. В ней представлен способ параллельных вычислений внутри блоков MLP и Attention. Благодаря разделению весовых матриц по столбцам и строкам, становится возможным распараллелить блоки MLP и Attention между GPU с минимальными коммуникациями между нодами. Также разберем пайплайн TP+DP.&lt;/p&gt;
  &lt;p id=&quot;Qjtq&quot;&gt;&lt;em&gt;Source&lt;/em&gt;: &lt;a href=&quot;https://arxiv.org/abs/1909.08053&quot; target=&quot;_blank&quot;&gt;Arxive&lt;/a&gt;, &lt;a href=&quot;https://github.com/huggingface/transformers/issues/10321&quot; target=&quot;_blank&quot;&gt;Tensor Parallelism&lt;/a&gt;&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;TcZU&quot;&gt;В прошлом посте &lt;a href=&quot;https://teletype.in/@kittybytes/rfMgEGnBsk1&quot; target=&quot;_blank&quot;&gt;Large Parallelism Post: Part I&lt;/a&gt; мы разбирали Model Parallelism, который включает в себя два типа параллелизма:&lt;/p&gt;
  &lt;ol id=&quot;0ygb&quot;&gt;
    &lt;li id=&quot;gHvo&quot;&gt;&lt;strong&gt;Вертикальный&lt;/strong&gt; - несколько слоев модели на каждом GPU&lt;/li&gt;
    &lt;li id=&quot;TJ5G&quot;&gt;&lt;strong&gt;Горизонтальный&lt;/strong&gt; - мы размещаем часть всей модели на каждом GPU&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;UqJF&quot;&gt;Горизонтальный параллелизм модели и называется Tensor Parallelism (TP) и именно с ним мы сегодня будем разбираться.&lt;/p&gt;
  &lt;p id=&quot;mkWa&quot;&gt;Главное отличие горизонтального параллелизма от вертикального в том, что он исключает простаивание GPU благодаря разделению всех слоев модели между нодами. На схеме ниже показаны их основные различия: в вертикальном мы на каждом GPU храним отдельный кусок слоя; в горизонтальном мы делим каждый слой модели между всеми GPU, после снова собитраем в один слой и снова делим между нодами&lt;/p&gt;
  &lt;figure id=&quot;FhZZ&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/66/51/6651d6ef-523c-4c50-a8c1-9c2413b7b1ba.png&quot; width=&quot;1560&quot; /&gt;
    &lt;figcaption&gt;&lt;a href=&quot;https://github.com/huggingface/transformers/issues/10321&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;5nJr&quot;&gt;Для начала обозначим, что именно мы будем параллелить - это классический слой трансформера, который изображен на схеме ниже. Глобально, он состоит из двух блоков - MLP и Attention. Внутри каждого блока есть слой Dropout, который в совокупности также хотелось бы параллелить.&lt;/p&gt;
  &lt;figure id=&quot;KK2C&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/2a/98/2a98c8c7-5bde-400a-9188-9de2c449ce6c.png&quot; width=&quot;226.31105047748977&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;qLgS&quot;&gt;Перед разбором статьи, обратимся к схеме из &lt;a href=&quot;https://huggingface.co/docs/transformers/v4.17.0/en/parallelism#tensor-parallelism&quot; target=&quot;_blank&quot;&gt;HuggingFace&lt;/a&gt;, которая поможет нам более детально понимать будущие расчеты. Ее суть в том, что мы можем параллелить данные входной матрицы и весов двумя способами - по &lt;strong&gt;столбцам&lt;/strong&gt; и &lt;strong&gt;строкам&lt;/strong&gt;.&lt;/p&gt;
  &lt;p id=&quot;nLPJ&quot;&gt;В случае &lt;strong&gt;параллелизма по столбцам&lt;/strong&gt;, мы распределяем матрицу весов на два столбца, умножаем на входные данные (важно, что входная матрица &lt;strong&gt;X&lt;/strong&gt; одинакова для &lt;strong&gt;A1&lt;/strong&gt; и &lt;strong&gt;A2&lt;/strong&gt; в этом случае), получаем два выходных столбца &lt;strong&gt;Y1&lt;/strong&gt; и &lt;strong&gt;Y2&lt;/strong&gt; и конкатенируем их в выходную матрицу &lt;strong&gt;Y.&lt;/strong&gt; &lt;/p&gt;
  &lt;p id=&quot;Ze1w&quot;&gt;Для &lt;strong&gt;параллелизма по строкам&lt;/strong&gt;, мы уже способны разделить входную матрицу &lt;strong&gt;X&lt;/strong&gt;, но разделяем ее по столбцам, а вот матрицу весов &lt;strong&gt;A&lt;/strong&gt; делим по строкам, производим умножение соответствующих частей, получаем выход &lt;strong&gt;Y1&lt;/strong&gt; и &lt;strong&gt;Y2&lt;/strong&gt;, складываем их и получаем финальную матрицу &lt;strong&gt;Y&lt;/strong&gt;.&lt;/p&gt;
  &lt;figure id=&quot;S3GD&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/bd/d1/bdd1aa41-f37e-4bb8-be32-ddc9d92fa969.png&quot; width=&quot;1620&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;7Le8&quot;&gt;Обратимся теперь к статье - как мы обсуждали ранее, в ней представлен параллелизм двух блоков: Multi-Layer Perceptron (MLP) и Attention.&lt;/p&gt;
  &lt;p id=&quot;qcdk&quot;&gt;Начнем с параллелизма &lt;strong&gt;MLP&lt;/strong&gt;. В обычном варианте без параллелизма выход из этого блока можно записать простым уравнением с перемножением матриц входных данных и весов, а после применить нелинейную функцию активации.&lt;/p&gt;
  &lt;figure id=&quot;qqI4&quot; class=&quot;m_retina&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/d0/ae/d0aee46d-a8c7-4cb7-8154-58c995028cd7.png&quot; width=&quot;197&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;48TM&quot;&gt;Первые мысли, которые могут возникнуть, когда мы хотим что-то параллелить - давайте поделим матрицы входа и весов (row parallelism):&lt;/p&gt;
  &lt;figure id=&quot;XRY5&quot; class=&quot;m_retina&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/b9/fe/b9fe24a6-b05c-4bf5-a16e-69e747623caa.png&quot; width=&quot;291&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;oaVT&quot;&gt;Однако, это не сработает, потому что &lt;strong&gt;GeLU&lt;/strong&gt; является нелинейной функцией и у нас не получится получить финальную матрицу с помощью сложения:&lt;/p&gt;
  &lt;figure id=&quot;Bu4m&quot; class=&quot;m_retina&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/ad/63/ad630529-3ddd-4da9-999d-3d358ffd6df8.png&quot; width=&quot;611&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;2pEY&quot;&gt;Однако, мы можем решить эту проблему, если сначала распараллелим в этом случае только матрицу весов (column parallelism). Тогда выходом из такой операции будет два столбца &lt;strong&gt;Y&lt;/strong&gt;, которые мы сконкатенируем:&lt;/p&gt;
  &lt;figure id=&quot;mZjF&quot; class=&quot;m_retina&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/e1/b8/e1b8fe0f-8bdc-4ea1-8ea1-d19a09786a2c.png&quot; width=&quot;439&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;vd3B&quot;&gt;Обращу внимание, что матрица входных данных остается одинаковой для каждой матрицы &lt;strong&gt;A1, A2&lt;/strong&gt; и не параллелится.&lt;/p&gt;
  &lt;figure id=&quot;qJoK&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/8e/a0/8ea07a48-b5f5-4b2d-9aed-73b390114190.png&quot; width=&quot;2098&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Brou&quot;&gt;После получения выходных матриц &lt;strong&gt;Y1, Y2&lt;/strong&gt;, они подаются в следующий слой, где матрицы весов &lt;strong&gt;B1, B2&lt;/strong&gt; разделены по строкам (row parallelism), после чего формируются выходные матрицы &lt;strong&gt;Z1, Z2&lt;/strong&gt; и они подаются в оператор &lt;strong&gt;g&lt;/strong&gt;, а дальше формируется финальная матрица&lt;strong&gt; Z&lt;/strong&gt;, после прохода&lt;strong&gt; Dropout &lt;/strong&gt;слоя&lt;strong&gt;.&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;1Gvy&quot;&gt;Подробнее расскажу, что за операторы &lt;strong&gt;f&lt;/strong&gt; и &lt;strong&gt;g&lt;/strong&gt; на схеме выше - это &lt;a href=&quot;https://docs.nvidia.com/deeplearning/nccl/user-guide/docs/usage/operations.html#allreduce&quot; target=&quot;_blank&quot;&gt;AllReduce&lt;/a&gt; оператор на Backward и Forward pass соответственно. &lt;strong&gt;g&lt;/strong&gt; работает, когда после Forward pass необходимо собрать все данные для формирования выходной матрицы, а &lt;strong&gt;f&lt;/strong&gt; после Backward pass формирует матрицу градиентов.&lt;/p&gt;
  &lt;p id=&quot;vRqc&quot;&gt;Теперь разберем параллелизм &lt;strong&gt;Attention&lt;/strong&gt; слоя. На самом деле, там все работает аналогично &lt;strong&gt;MLP&lt;/strong&gt;, только матриц чуть больше. Сначала применяется column parallelism для матриц &lt;strong&gt;Query&lt;/strong&gt;, &lt;strong&gt;Key&lt;/strong&gt;, &lt;strong&gt;Value&lt;/strong&gt;, далее их результат проходит через Sofmax и Dropout, после чего матрицы &lt;strong&gt;Y1, Y2&lt;/strong&gt; умножаются на веса &lt;strong&gt;B1, B2&lt;/strong&gt; в слое Dropout, который работает с помощью row parallelism, а потом все подается в оператор &lt;strong&gt;g&lt;/strong&gt; и формируется финальная матрица &lt;strong&gt;Z&lt;/strong&gt;.&lt;/p&gt;
  &lt;figure id=&quot;TfNC&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/c6/63/c6633feb-302f-45c8-993f-36363d54fd5b.png&quot; width=&quot;2384&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;aJvd&quot;&gt;Вот общий пайплайн tensor parallelism - по сути в каждом разобранном блоке у нас есть верхняя и нижняя часть, которые мы можем отправить на отдельные GPU. Тогда у нас остается только 4 операции AllReduce, которые мы будем делать для сбора финальных матриц оператором &lt;strong&gt;g&lt;/strong&gt; и сбора матриц градиентов оператором &lt;strong&gt;f&lt;/strong&gt;.&lt;/p&gt;
  &lt;figure id=&quot;OaoC&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/f3/da/f3daffc7-cc45-46f7-aa95-9201134177ac.png&quot; width=&quot;2324&quot; /&gt;
    &lt;figcaption&gt;&lt;a href=&quot;https://arxiv.org/abs/2104.04473&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;85ZZ&quot;&gt;Еще одна схема пайплайна tensor parallelism трансформера. Я ее прикрепил для понимания, почему всего будет 4 операции AllReduce, а также для комментария, что в пайплайне присутствуют Skip Connections.&lt;/p&gt;
  &lt;figure id=&quot;FSSQ&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/10/79/10797d7d-c3ed-43cb-a1fd-389a6781110d.png&quot; width=&quot;1894&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;PPct&quot;&gt;Матрицу входных эмбеддингов авторы параллелят по столбцам &lt;strong&gt;E = [E1, E2]&lt;/strong&gt;. Для выходных эмбеддингов &lt;strong&gt;Y1, Y2&lt;/strong&gt; авторы для них высчитывают Cross-Entropy Loss и после, с помощью &lt;a href=&quot;https://docs.nvidia.com/deeplearning/nccl/user-guide/docs/usage/operations.html#allgather&quot; target=&quot;_blank&quot;&gt;AllGather&lt;/a&gt;, получают финальные вероятности. Это сделано для сокращения времени коммуникации между GPU, поскольку сводит размер финального пространства к &lt;strong&gt;b&lt;/strong&gt; x &lt;strong&gt;s&lt;/strong&gt; (&lt;strong&gt;b&lt;/strong&gt; - batch-size, &lt;strong&gt;s&lt;/strong&gt; - sequence length).&lt;/p&gt;
  &lt;p id=&quot;Vwl4&quot;&gt;Также добавлю, что авторы используют &lt;a href=&quot;https://arxiv.org/abs/1809.02839&quot; target=&quot;_blank&quot;&gt;Activation Checkpointing&lt;/a&gt;. Он заключается в сохранении только конечных активаций каждого слоя на Forward pass, а на Backward pass все активации между сохраненными слоями будут рассчитываться повторно и использоваться для получения градиентов. Это экономит затраты памяти и вычислительные ресурсы.&lt;/p&gt;
  &lt;p id=&quot;GtP0&quot;&gt;До этого мы обсуждали, что делим трансформер между двумя карточками, но, конечно же, его можно делить между многими GPU. Я думаю этот факт более чем очевиден, поэтому перейдем к части совмещения &lt;strong&gt;TP&lt;/strong&gt; и &lt;strong&gt;DP. &lt;/strong&gt;Да, так можно делать и вот схема пайплайна:&lt;/p&gt;
  &lt;figure id=&quot;jYCQ&quot; class=&quot;m_retina&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/0f/24/0f249984-a9cd-4fd7-913a-687c26c5683e.png&quot; width=&quot;600&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Sdi3&quot;&gt;Если кратко, то вы можете создать ноду с 8 GPU (то есть разделить модель на 8 частей), потом перекопировать эту ноду 64 раза, а в каждой ноде подавать на определенное GPU определенную часть данных (да да, это тот самый самый простой DP). То есть, например на &lt;strong&gt;GPU [1, 9, 17 ... 505]&lt;/strong&gt; в каждой ноде вы подаете данные &lt;strong&gt;Group1&lt;/strong&gt;, на &lt;strong&gt;GPU [2, 10, 18 ... 506]&lt;/strong&gt; данные &lt;strong&gt;Group2&lt;/strong&gt; и так далее. Градиенты будут аккамулироваться как в DP, а каждая нода теперь способна вместить в себя большую модель (хватило бы только GPU).&lt;/p&gt;
  &lt;hr /&gt;
  &lt;h3 id=&quot;EIg4&quot;&gt;Results&lt;/h3&gt;
  &lt;p id=&quot;GKW0&quot;&gt;Судя по графикам, пайплайн TP+DP довольно неплохо справляется с большим количеством GPU - есть небольшая просадка на несколько процентов по сравнению только TP, но это скорее из-за малой задержки NCCL операций между карточками.&lt;/p&gt;
  &lt;figure id=&quot;9r7P&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/e2/e0/e2e0239c-4da9-4663-9500-7c919e40d2e4.png&quot; width=&quot;1564&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;xrHy&quot;&gt;Добавлю еще вот такую табличку, которая показывает сколько дней тратится на одну эпоху, обучая GPT-2 в разных конфигурациях на 512 GPUs (одна эпоха это 68507 итераций).&lt;/p&gt;
  &lt;figure id=&quot;EZB7&quot; class=&quot;m_retina&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/48/81/4881c0cd-5eae-4296-95bc-53d54827e098.png&quot; width=&quot;459&quot; /&gt;
  &lt;/figure&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;36CN&quot;&gt;На этом у меня все!&lt;/p&gt;
  &lt;p id=&quot;qPXN&quot;&gt;Спасибо, что дочитали до конца)&lt;/p&gt;

</content></entry><entry><id>kittybytes:rfMgEGnBsk1</id><link rel="alternate" type="text/html" href="https://teletype.in/@kittybytes/rfMgEGnBsk1?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=kittybytes"></link><title>Large Parallelism Post: Part I</title><published>2024-06-29T15:38:13.812Z</published><updated>2024-06-29T15:38:13.812Z</updated><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/9b/98/9b989a04-b2df-4e2b-aff2-151db1370f6f.png&quot;&gt;В этой части разобраны самые основные методы параллельного обучения сетей - Data Parallel, Distributed Data Parallel, Model Parallelism и Pipeline Parallelism.</summary><content type="html">
  &lt;p id=&quot;clPb&quot;&gt;В этой части разобраны самые основные методы параллельного обучения сетей - Data Parallel, Distributed Data Parallel, Model Parallelism и Pipeline Parallelism.&lt;/p&gt;
  &lt;p id=&quot;T0xz&quot;&gt;&lt;em&gt;Source&lt;/em&gt;: &lt;a href=&quot;https://huggingface.co/docs/transformers/v4.17.0/en/parallelism&quot; target=&quot;_blank&quot;&gt;Model Parallelism HF&lt;/a&gt;, &lt;a href=&quot;https://research.google/blog/introducing-gpipe-an-open-source-library-for-efficiently-training-large-scale-neural-network-models/&quot; target=&quot;_blank&quot;&gt;GPipe&lt;/a&gt;&lt;/p&gt;
  &lt;hr /&gt;
  &lt;h3 id=&quot;sozz&quot;&gt;Data Parallel (DP)&lt;/h3&gt;
  &lt;p id=&quot;xijF&quot;&gt;Классический параллелизм - параллелизм данных, реализованный в PyTorch и применяющийся одной строчкой:&lt;/p&gt;
  &lt;pre id=&quot;vpuh&quot;&gt;net = torch.nn.DataParallel(model, device_ids=[0, 1, 2])&lt;/pre&gt;
  &lt;p id=&quot;9Ayc&quot;&gt;В этом случае модель сначала копируется с &amp;quot;0&amp;quot; GPU на остальные, а данные делятся между карточками. Forward и Backward проходят на каждом GPU отдельно, а результаты полученных градиентов после каждого прохода суммируются в модель на &amp;quot;0&amp;quot; GPU. С нового цикла Forward и Backward все начинается заново.&lt;/p&gt;
  &lt;figure id=&quot;nQe7&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/cf/21/cf212123-4da7-413c-9557-1d9abeb7a6f8.png&quot; width=&quot;1446&quot; /&gt;
    &lt;figcaption&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=3A8AVsNNHOg&amp;ab_channel=IntelSoftware&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;kww5&quot;&gt;Важные уточнения:&lt;/p&gt;
  &lt;ol id=&quot;E6hs&quot;&gt;
    &lt;li id=&quot;kmFV&quot;&gt;При каждом Forward проходе модель копируется с &amp;quot;0&amp;quot; GPU на остальные - то есть если существуют процесс модели на &amp;quot;1&amp;quot; карте, то он будет потерян.&lt;/li&gt;
    &lt;li id=&quot;Ytfz&quot;&gt;Тензоры распределяются между GPU, но типы &lt;strong&gt;tuple&lt;/strong&gt;, &lt;strong&gt;list&lt;/strong&gt; и &lt;strong&gt;dict&lt;/strong&gt; будут скопированы (именно скопированы, без &lt;em&gt;deepcopy&lt;/em&gt;). Остальные типы данных будут использоваться сразу всеми карточкмаи, поэтому будут повреждены. Так что лучше все сразу переводить в тензора.&lt;/li&gt;
    &lt;li id=&quot;j5Ed&quot;&gt;&lt;u&gt;Очень важно подчеркнуть, что &lt;strong&gt;DataParallel&lt;/strong&gt; использует &lt;strong&gt;многопоточность GPU&lt;/strong&gt;, что снижает их эффективность.&lt;/u&gt;&lt;/li&gt;
    &lt;li id=&quot;Htrs&quot;&gt;DataParallel может параллелить GPU только на одной машине, что ограничивает размер модели и объем данных.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;CLYy&quot;&gt;Прокомментирую пункт 3 подробнее. Многопоточность предполагает общую память между потоками GPU, а значит существует риск непредвиденного обновления ячеек памяти. Это может возникнуть из-за неправильного освобождения и ее выделения - такое встречалось в работе с &lt;a href=&quot;https://docs.nvidia.com/&quot; target=&quot;_blank&quot;&gt;NCCL&lt;/a&gt; (вот &lt;a href=&quot;https://github.com/pytorch/pytorch/issues/22259&quot; target=&quot;_blank&quot;&gt;ссылка&lt;/a&gt; на тред).&lt;/p&gt;
  &lt;p id=&quot;Hfq5&quot;&gt;Также необходимо добавить, что потоки Python зависят от &lt;a href=&quot;https://ru.wikipedia.org/wiki/%D0%93%D0%BB%D0%BE%D0%B1%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F_%D0%B1%D0%BB%D0%BE%D0%BA%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%BF%D1%80%D0%B5%D1%82%D0%B0%D1%82%D0%BE%D1%80%D0%B0&quot; target=&quot;_blank&quot;&gt;GIL&lt;/a&gt;, который может блокироваться C bindings (это структурное связывание двух языков программирования C и Python - подробнее &lt;a href=&quot;https://en.wikipedia.org/wiki/Language_binding&quot; target=&quot;_blank&quot;&gt;тут&lt;/a&gt;). Вычисления на GPU обычно используют C bindings, а значит могут иногда блокировать GIL, что остановит работу основного кода.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;h3 id=&quot;iavO&quot;&gt;Distributed Data Parallel (DDP)&lt;/h3&gt;
  &lt;p id=&quot;8NAJ&quot;&gt;Для решения описанных проблем, сам PyTorch настоятельно &lt;a href=&quot;https://pytorch.org/docs/stable/generated/torch.nn.DataParallel.html#dataparallel:~:text=It%20is%20recommended%20to%20use%20DistributedDataParallel%252C%20instead%20of%20this%20class%252C%20to%20do%20multi-GPU%20training%252C%20even%20if%20there%20is%20only%20a%20single%20node.%20See%253A%20Use%20nn.parallel.DistributedDataParallel%20instead%20of%20multiprocessing%20or%20nn.DataParallel%20and%20Distributed%20Data%20Parallel.&quot; target=&quot;_blank&quot;&gt;рекомендует&lt;/a&gt; использовать метод DistributedDataParallel (DDP). Он вызывается также легко, как и DataParallel:&lt;/p&gt;
  &lt;pre id=&quot;u1OG&quot;&gt;from torch.nn.parallel import DistributedDataParallel as DDP

ddp_model = DDP(model, device_ids=[rank])&lt;/pre&gt;
  &lt;p id=&quot;1fvt&quot;&gt;Подход остался тем же самым - одна модель с &amp;quot;0&amp;quot; GPU копируется на остальные, а данные распределяются между процессами. Кстати делается это с помощью ссылки на состояние модели (state_dict), которое с помощью &lt;a href=&quot;https://docs.nvidia.com/deeplearning/nccl/user-guide/docs/usage/operations.html#broadcast&quot; target=&quot;_blank&quot;&gt;broadcast&lt;/a&gt; распространяется на все GPU. Это нужно для того, чтобы все копии модели запускались из того же самого состояния, что и оригинальная модель. При этом важно уточнить, что теперь вы не ограничены одной GPU для модели - вы работаете с машинами, где может находится сколько угодно GPU. Это означает, что для запуска больших моделей вы ограничены только бюджетом кластера.&lt;/p&gt;
  &lt;p id=&quot;p0X0&quot;&gt;Как я и сказал - подход остается действительно таким же, но все дело в мелочах. Например, DDP работает на мультипроцессинге, что и позволяет запускать его на нескольких машинах (и неважно сколько GPU на каждой машине). К тому же, так как каждый GPU имеет свой выделенный процесс, то это позволяет избежать перерасхода производительности, вызванного блокировками GIL. Пример работы DDP показан на рисунке ниже.&lt;/p&gt;
  &lt;figure id=&quot;xGB9&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/48/01/4801c8d9-56d5-4b01-9f84-57b14097704b.png&quot; width=&quot;2216&quot; /&gt;
    &lt;figcaption&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=bwNtfxEDjGA&amp;ab_channel=DevelopersHutt&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;bDM5&quot;&gt;Продолжая разговор о мелочах, необходимо добавить, что каждый процесс имеет свой метод &lt;a href=&quot;https://docs.nvidia.com/deeplearning/nccl/user-guide/docs/usage/operations.html#reduce&quot; target=&quot;_blank&quot;&gt;reduce&lt;/a&gt;, который нужен для запуска &lt;a href=&quot;https://docs.nvidia.com/deeplearning/nccl/user-guide/docs/usage/operations.html#allreduce&quot; target=&quot;_blank&quot;&gt;AllReduce &lt;/a&gt;усреднения градиентов при Forward и Backward проходах. Для повышения эффективности работы с градиентами, Reducer разбивает градиенты по buckets (размер можно настроить с помощью bucket_cap_mb), чтобы применять операции не к каждому градиенту, а сразу к их группе. Важно отметить, что параметры модели распределяются по бакетам примерно в порядке, обратном порядку Model.parameters() данной модели - это логично, ведь получение градиентов начинается с последних слоев к первым.&lt;/p&gt;
  &lt;figure id=&quot;IRdr&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/db/13/db13dfb0-44c5-4ebe-bea1-8a4a532ee29d.png&quot; width=&quot;1356&quot; /&gt;
    &lt;figcaption&gt;&lt;a href=&quot;https://pytorch.org/docs/stable/notes/ddp.html#torchdynamo-ddpoptimizer&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;0SGM&quot;&gt;Optimizer Step происходит для модели на &amp;quot;0&amp;quot; процессе, а затем ее состояние снова рассылается по всем процессам и цикл начинается заново.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;h3 id=&quot;uFFc&quot;&gt;Model Parallelism (MP)&lt;/h3&gt;
  &lt;p id=&quot;1GNm&quot;&gt;Очень хорошо, когда ваша модель помещается на одном GPU или даже на ноде кластера, но при работе с большими моделями такого ожидать не приходится - что делать если мы не обладаем достаточными мощностями для работы с большими моделями? Ответ очевиден - давайте параллелить модель.&lt;/p&gt;
  &lt;p id=&quot;V9zN&quot;&gt;Существует два варианта &amp;quot;резки&amp;quot; слоев модели - &lt;strong&gt;вертикальный&lt;/strong&gt; и &lt;strong&gt;горизонтальный&lt;/strong&gt;. Как обычно бывает в научной среде, в терминологиях путаются и иногда горизонтальный параллелизм называют вертикальным и наоборот (вот в &lt;a href=&quot;https://youtu.be/hc0u4avAkuM?si=CRStW1GHYCJRA8ml&amp;t=556&quot; target=&quot;_blank&quot;&gt;этом видео&lt;/a&gt; Aleksa именно так и делает). Я буду придерживаться терминологии &lt;a href=&quot;https://huggingface.co/docs/transformers/v4.17.0/en/parallelism#naive-model-parallelism-vertical-and-pipeline-parallelism&quot; target=&quot;_blank&quot;&gt;HuggingFace&lt;/a&gt;. Для начала расскажу про &lt;strong&gt;вертикальный&lt;/strong&gt; &lt;strong&gt;параллелизм&lt;/strong&gt; модели, а к горизонтальному мы вернемся в статье про Tensor Parallelism.&lt;/p&gt;
  &lt;p id=&quot;so4L&quot;&gt;Представьте, что вы можете нарезать слои модели и каждую такую часть отправить на отдельный GPU.&lt;/p&gt;
  &lt;figure id=&quot;HHFk&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/e3/16/e3166a55-4b00-455f-ba93-fda8a2903161.png&quot; width=&quot;1730&quot; /&gt;
    &lt;figcaption&gt;&lt;a href=&quot;https://dl.acm.org/doi/10.1145/3442442.3452055&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;0D5y&quot;&gt;Тогда вы будете пропускать данные сначала через первые слои модели на GPU[0], выход отправите на GPU[1], а после на GPU[2], где рассчитаете Loss, и начнете передавать градиенты с последних слоев к началу GPU[2-&amp;gt;0]. &lt;/p&gt;
  &lt;p id=&quot;0B0u&quot;&gt;В статье &lt;a href=&quot;https://arxiv.org/abs/1811.06965&quot; target=&quot;_blank&quot;&gt;GPipe: Efficient Training of Giant Neural Networks using Pipeline Parallelism&lt;/a&gt;, к которой мы обратимся чуть позднее, изображена схема такого пайплайна, на которой сразу видны очевидные минусы данного подхода.&lt;/p&gt;
  &lt;figure id=&quot;pH4S&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/22/51/2251799a-d514-4bfb-bd88-e4d35a81567c.png&quot; width=&quot;2618&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;pGF8&quot;&gt;Пока проходит Forward или Backward pass на каждом GPU, остальные простаивают. Более того, общие данные придется копировать между каждым GPU, что займет время и ресурсы.&lt;/p&gt;
  &lt;p id=&quot;VciF&quot;&gt;Чтобы решить данную проблему, разработчики Google решили придумать Pipeline Parallelism.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;h3 id=&quot;2fxc&quot;&gt;Pipeline Parallelism (PP)&lt;/h3&gt;
  &lt;p id=&quot;YTfL&quot;&gt;Снова обратимся к работе &lt;a href=&quot;https://arxiv.org/abs/1811.06965&quot; target=&quot;_blank&quot;&gt;GPipe: Efficient Training of Giant Neural Networks using Pipeline Parallelism&lt;/a&gt;. Pipeline Parallelism очень похож на MP - в нем также реализовано разделение модели на слои, которые хранятся на каждом GPU. Но в нем есть небольшое отличие в работе с входящими данными - каждый mini-batch разбивается на несколько micro-batches, то есть на еще более мелкие пакеты. Это позволяет обрабатывать каждый micro-batch параллельно и зон, где GPU простаивает, становится гораздо меньше. Оставшаяся зона простойки GPU называется Bubble.&lt;/p&gt;
  &lt;figure id=&quot;t0so&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/1f/82/1f828bbf-cd43-4e42-9d2c-7f8ba62d945d.png&quot; width=&quot;2618&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Jo9Q&quot;&gt;Условно, вместо обработки одного большого mini-batch F0 из прошлой схемы, GPU нужно обработать четыре последовательные части F0. После расчета каждой части, результаты можно передавать на следующий GPU. Аналогичная ситуация происходит на Backward pass. Конечно, некоторый простой GPU сохраняется и в этом варианте (Bubble на рисунке), но он значительно меньше, чем при MP, что ускоряет обучение.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;COaf&quot;&gt;Спасибо, что дочитали до конца!&lt;br /&gt;В следующей части углубимся в Tensor Parallelism на основе статьи Megatron-LM.&lt;/p&gt;

</content></entry><entry><id>kittybytes:STTZcsFauza</id><link rel="alternate" type="text/html" href="https://teletype.in/@kittybytes/STTZcsFauza?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=kittybytes"></link><title>xLSTM: Extended Long Short-Term Memory</title><published>2024-06-11T17:35:16.977Z</published><updated>2024-06-16T10:39:06.604Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img1.teletype.in/files/83/9c/839c66af-18b3-4393-9ab6-a1a8f57c703c.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/cb/72/cb72828f-1c22-45fa-8082-436e2aa1eb0b.png&quot;&gt;В статье разработано улучшение сети LSTM с помощью двух архитектур - sLSTM и mLSTM. Каждая архитектура, направлена на решение проблем оригинальной сети. Добавлены параллельные вычисления, способность корректировать запоминание информации, а также матричное представление данных внутри сети. Эксперименты доказывают, что xLSTM сравнима с GPT моделями.</summary><content type="html">
  &lt;p id=&quot;8cJB&quot;&gt;В статье разработано улучшение сети LSTM с помощью двух архитектур - sLSTM и mLSTM. Каждая архитектура, направлена на решение проблем оригинальной сети. Добавлены параллельные вычисления, способность корректировать запоминание информации, а также матричное представление данных внутри сети. Эксперименты доказывают, что xLSTM сравнима с GPT моделями.&lt;/p&gt;
  &lt;p id=&quot;6u45&quot;&gt;&lt;em&gt;Source&lt;/em&gt;: &lt;a href=&quot;https://arxiv.org/abs/2405.04517&quot; target=&quot;_blank&quot;&gt;Arxive&lt;/a&gt;&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;VhkJ&quot;&gt;Перед тем как разбирать новую архитектуру xLSTM, кратко напомню как работает оригинальная LSTM (если вы захотите прочитать больше, то крайне рекомендую &lt;a href=&quot;https://colah.github.io/posts/2015-08-Understanding-LSTMs/&quot; target=&quot;_blank&quot;&gt;источник&lt;/a&gt;).&lt;/p&gt;
  &lt;figure id=&quot;Bd7Y&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/30/15/30152d7a-edfb-4f81-8f22-3d3cde08f436.png&quot; width=&quot;2500&quot; /&gt;
    &lt;figcaption&gt;&lt;a href=&quot;https://web.stanford.edu/class/archive/cs/cs224n/cs224n.1224/slides/cs224n-2022-lecture06-fancy-rnn.pdf&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;ol id=&quot;j3Bi&quot;&gt;
    &lt;li id=&quot;DNNc&quot;&gt;Вначале получаем взвешенную сумму входного вектора и вектора скрытого состояния (коэффициенты в этой сумме и есть весовые матрицы).&lt;/li&gt;
    &lt;li id=&quot;iHJL&quot;&gt;Forget Gate - результат применения сигмоиды к скрытому и входному векторам. Результат умножается на вектор контекста, решая какую информацию нужно забыть с учетом полученных состояний.&lt;/li&gt;
    &lt;li id=&quot;YD2u&quot;&gt;New Cell Content - получается с помощью гиперболического тангенса. Расчитывается новый контекст и одновременно решается какая информация в новом векторе релевантна с помощью умножения на сигмоиду результата взвешенной суммы входного и скртого состояния (Input Gate). Новый контекст прибавляется к прошлому - именно эта операция и отвечает за способность LSTM запоминать длинный контекст.&lt;/li&gt;
    &lt;li id=&quot;7Xjb&quot;&gt;Вычисляется новое скрытое состояние с помощью гиперболиечского тангенса финального вектора контекста и очередным умножением на сигмоиду суммы входного и скрытого векторов (Output Gate).&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;hU0K&quot;&gt;У такой архитектуры есть пара проблем:&lt;/p&gt;
  &lt;ol id=&quot;9Nph&quot;&gt;
    &lt;li id=&quot;mV7N&quot;&gt;Невозможно производить вычисления параллельно&lt;/li&gt;
    &lt;li id=&quot;GQWM&quot;&gt;Невозможно корректировать решения сети запоминать/забывать информацию&lt;/li&gt;
    &lt;li id=&quot;Fn7U&quot;&gt;Скалярность памяти вносит некоторые ограничения на ее эффективность&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;KnRe&quot;&gt;В новой статье ученые решили эти недостатки.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;doZv&quot;&gt;Во-первых уточню, что xLSTM состоит из двух архитектур - sLSTM и mLSTM. Начнем с sLSTM.&lt;/p&gt;
  &lt;h2 id=&quot;uW0r&quot; data-align=&quot;center&quot;&gt;&lt;u&gt;sLSTM&lt;/u&gt;&lt;/h2&gt;
  &lt;figure id=&quot;7tSa&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/22/77/2277287f-3387-4620-bb3b-e4d3cc0f47b8.png&quot; width=&quot;1324&quot; /&gt;
    &lt;figcaption&gt;Картинку позаимствовал с разбора &lt;a href=&quot;https://datasecrets.ru/articles/10&quot; target=&quot;_blank&quot;&gt;Data Secrets&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;VkN2&quot;&gt;Давайте внимательно посмотрим что изменилось в схеме sLSTM.&lt;/p&gt;
  &lt;ol id=&quot;NZMg&quot;&gt;
    &lt;li id=&quot;A8kS&quot;&gt;Сразу замечаем новую красную ячейку памяти &lt;strong&gt;n&lt;/strong&gt; (normalization) над ячейкой контекста.&lt;/li&gt;
    &lt;li id=&quot;tacJ&quot;&gt;Первые две сигмоиды заменяют экспоненциальные функции.&lt;/li&gt;
    &lt;li id=&quot;d41K&quot;&gt;Деление вместо гиперболического тангенса при расчете нового скрытого состояния.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;ixD6&quot;&gt;Теперь перейдем к формулам.&lt;/p&gt;
  &lt;figure id=&quot;kvl3&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/92/dc/92dc5557-b32b-401a-b7be-7135de0ab495.png&quot; width=&quot;1338&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;JZNk&quot;&gt;Вот что пишут сами авторы о нововедениях:&lt;/p&gt;
  &lt;blockquote id=&quot;aGij&quot;&gt;Чтобы наделить LSTM способностью пересматривать решения о хранении, мы вводим экспоненциальные gates (красные) вместе с нормализацией и стабилизацией. В частности, input gates и forget gates могут иметь экспоненциальные функции активации.&lt;/blockquote&gt;
  &lt;p id=&quot;x3RO&quot;&gt;Что произошло? Раньше мы не могли получать большие значения из-за ограниченности сигмоиды. Теперь, расчитывая экспоненту, у LSTM есть возможность &lt;strong&gt;регулировать релевантность информации&lt;/strong&gt; в input gate и forget gate. &lt;/p&gt;
  &lt;p id=&quot;Gy53&quot;&gt;Например, если входной вектор является очень важным для сети, значение input gate будет высоким, а значит и умножение на вектор контекста даст большой результат. Одновременно с этим сеть понимает, что прошлые векторы были неважными, а значит мы получим малые значения в forget gate (кстати именно поэтому авторы на картинке указывают, что для forget gate можно применять как сигмоиду, так и экспоненту - неважно как сеть занулит прошлые значения).&lt;/p&gt;
  &lt;figure id=&quot;aWn1&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/06/44/0644a920-6db8-454c-b9ec-e9c783438ac9.png&quot; width=&quot;285&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure id=&quot;jII0&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/5c/9b/5c9b51a8-4434-4345-89d1-8c92c5d3ebc2.png&quot; width=&quot;168&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;zNPy&quot;&gt;Теперь перейдем к новой ячейке нормализации. Расчет данного значения можно увидеть в формуле 9, но зачем он нужен? По формуле мы видим, что состояние нормализации постоянно увеличивается за счет накопления значений input gate. Да, есть возможность забывать прошлую информацию с помощью forget gate, однако в этой ячейке все равно аккумулирована вся релевантная информация текста. А теперь обращаемся к формуле 10 - частному вектора контекста и вектора нормализации. По сути здесь сеть вычисляет &lt;strong&gt;насколько релевантная информация содержится в векторе контекста по отношению ко всему тексту документа&lt;/strong&gt;. А также это позволяет архитектуре передать значение этой релевантности в следующий слой.&lt;/p&gt;
  &lt;figure id=&quot;9UvK&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/bc/34/bc34a674-76f4-4494-bb1b-085527f9aa4c.png&quot; width=&quot;2354&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;cdlQ&quot;&gt;Перейдем к последней части sLSTM - стабилизации. Мы понимаем, что экспонента может выдавать очень большие значения, которые приводят к переполнению памяти. Поэтому авторы вводят алгоритм стабилизации этих значений - состояние &lt;strong&gt;m&lt;/strong&gt; (которое берут из статьи &lt;a href=&quot;https://arxiv.org/abs/1805.02867&quot; target=&quot;_blank&quot;&gt;Online normalizer calculation for softmax&lt;/a&gt;). Оно выбирает максимум из двух значений: суммы прошлого состояния стабилизации с логарифмом forget gate и логарифма input gate (формула 15). Далее считаются новые значения input gate и forget gate по формулам 16 и 17. &lt;/p&gt;
  &lt;p id=&quot;wgVZ&quot;&gt;На инференсе, конечно же, это является трюком для предотвращения взрыва градиентов и переполнения памяти. Однако на этапе тренировки данная часть вычислений очень важна и вот почему.&lt;/p&gt;
  &lt;ul id=&quot;v5zv&quot;&gt;
    &lt;li id=&quot;qqLL&quot;&gt;Когда левая часть максимума с логарифмом forget gate и прошлым стабилизатором превосходит логарифм от input gate, то финальное значение forget gate &lt;u&gt;обнуляется&lt;/u&gt; - то есть при высоком значении текущего forget gate и прошлой памяти, сеть умножает на ноль вектор контекста и нормализации.&lt;/li&gt;
    &lt;li id=&quot;ysle&quot;&gt;Напротив, когда логарифм input gate превосходит сумму логарифма forget gate и прошлого стабилизатора, то обнуляется финальное значение input gate, а значит зануляется текущий контекст и информация не добавляется в вектор нормализации.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;RiBb&quot;&gt;&lt;strong&gt;Мы учим сеть находить баланс между добавлением и забыванием информации&lt;/strong&gt;. Если инфомация важна, то нельзя сразу присвоить ей большое значение, иначе оно заглушит прошлые знания. Если мы хотим занулить текущую информацию, то, конечно, можно ей выдать высокие значения forget gate, однако &lt;u&gt;на следующем шаге&lt;/u&gt; это действие может перевесить нечто важное в input gate, даже если forget gate будет мал, и тогда снова произойдет забывание.&lt;/p&gt;
  &lt;p id=&quot;aozv&quot;&gt;Последнее, что сделали авторы - вместо работы с одной цепочкой блоков sLSTM авторы делают несколько голов с помощью матриц, подражая multi-head attention (они назвали это &lt;strong&gt;New Memory Mixing&lt;/strong&gt;, хотя вообще то об этом известно уже очень давно. Ниже я приведу код для более подробного объяснения). Матрицы &lt;strong&gt;Wz&lt;/strong&gt;, &lt;strong&gt;Wi&lt;/strong&gt;, &lt;strong&gt;Wf&lt;/strong&gt;, &lt;strong&gt;Wo&lt;/strong&gt;, &lt;strong&gt;Rz&lt;/strong&gt;, &lt;strong&gt;Ri&lt;/strong&gt;, &lt;strong&gt;Rf&lt;/strong&gt;, &lt;strong&gt;Ro&lt;/strong&gt; являются теперь блочно-диагональными, где каждый диагональный блок задает отдельную голову. В этом случае, скаляры становятся, очевидно, векторами. Сам Memory Mixing может происходить только внутри каждой головы, а не между голов.&lt;/p&gt;
  &lt;figure id=&quot;5BqA&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/bc/c9/bcc98f02-e90e-4df3-aea8-eee8f460f9df.png&quot; width=&quot;1430&quot; /&gt;
  &lt;/figure&gt;
  &lt;hr /&gt;
  &lt;h2 id=&quot;aef1&quot; data-align=&quot;center&quot;&gt;&lt;u&gt;mLSTM&lt;/u&gt;&lt;/h2&gt;
  &lt;p id=&quot;u6zj&quot;&gt;В сети mLSTM авторы увеличивают объем памяти с помощью агрейда скаляра &lt;strong&gt;с&lt;/strong&gt; до матрицы &lt;strong&gt;C&lt;/strong&gt;. Они используют терминологию трансформеров и вводят вектора &lt;strong&gt;q&lt;/strong&gt;, &lt;strong&gt;k&lt;/strong&gt; и &lt;strong&gt;v&lt;/strong&gt; для хранения и извелечения памяти. Извелечение необходимой информации из памяти основано на &lt;a href=&quot;https://link.springer.com/article/10.1007/BF00275079&quot; target=&quot;_blank&quot;&gt;правиле обновления ковариации&lt;/a&gt;, которое позволяет сохранять пары векторов (&lt;strong&gt;v, k&lt;/strong&gt;):&lt;/p&gt;
  &lt;figure id=&quot;SHxM&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/ce/27/ce272425-c3dd-415e-9114-ed73f5e4b469.png&quot; width=&quot;244.79746835443038&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;UnMI&quot;&gt;Разберем на простом примере как это работает. Допустим нам нужно сделать 2 итерации по сохранению векторов (&lt;strong&gt;v, k&lt;/strong&gt;) и мы получим :&lt;/p&gt;
  &lt;figure id=&quot;dmYI&quot; class=&quot;m_retina&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/70/85/7085908b-d014-4299-a355-e2f02ec1bbae.png&quot; width=&quot;388&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;EJgO&quot;&gt;А теперь нам нужно достать из сохраненной памяти вектор v0:&lt;/p&gt;
  &lt;figure id=&quot;9mVn&quot; class=&quot;m_retina&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/5d/03/5d030ec7-7c5d-4004-95ba-3b09550c509c.png&quot; width=&quot;339&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;ZTiI&quot;&gt;Потому что&lt;/p&gt;
  &lt;figure id=&quot;57zS&quot; class=&quot;m_retina&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/24/af/24af3f2a-603b-477d-a11f-39b4b5c71403.png&quot; width=&quot;255&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;bdcs&quot;&gt;Это возможно из-за вычислений в пространстве большой размерности - мы предполагаем, что если векторы разные, то они практически ортогональны, а значит их произведение будет равно нулю. Напротив, умножая одинаковые векторы друг на друга, мы получаем 1. &lt;/p&gt;
  &lt;p id=&quot;mnAg&quot;&gt;Это правило встречалось уже ранее в статье &lt;a href=&quot;https://teletype.in/@kittybytes/7hhzDO-Et3s&quot; target=&quot;_blank&quot;&gt;Leave No Context Behind: Efficient Infinite Context Transformers with Infini-attention&lt;/a&gt;, разбор которой я делал в канале. &lt;/p&gt;
  &lt;p id=&quot;rhd6&quot;&gt;Итак, разберемся подробнее. Как я писал ранее - авторы используют вектора &lt;strong&gt;q&lt;/strong&gt;, &lt;strong&gt;k&lt;/strong&gt; и &lt;strong&gt;v &lt;/strong&gt;наподобие трансформеров, вычисление которых вводится в формулах 22, 23 и 24. Здесь нет ничего нового - входной вектор умножается на матрицу весов и к результату добавляется bias. В случае &lt;strong&gt;k&lt;/strong&gt; видим классическое сохранение размерности путем деления на корень из d - это реализовано в ванильном self-attention.&lt;/p&gt;
  &lt;p id=&quot;QMwP&quot;&gt;Важно отметить, что в input, forget, output gate теперь не используется скрытое состояние (формулы 25, 26, 27) - это очень важное обновление, поскольку теперь &lt;strong&gt;появляется возможность распараллелить процесс обучения&lt;/strong&gt;.&lt;/p&gt;
  &lt;figure id=&quot;DyEM&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/ec/26/ec26a4ee-af9e-4411-a44e-71db810f7c1e.png&quot; width=&quot;2412&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;gbc8&quot;&gt;Добавлю, что для input и forget gate используется тот же процесс стабилизации, что и в sLSTM.&lt;/p&gt;
  &lt;p id=&quot;1CWU&quot;&gt;Теперь снова вернемся к правилу ковариации и к формулам 19, 20 и 21.&lt;/p&gt;
  &lt;p id=&quot;4cqp&quot;&gt;В 19 формуле мы видим формирование матрицы &lt;strong&gt;C&lt;/strong&gt; путем накопления пары векторов &lt;strong&gt;v&lt;/strong&gt; и &lt;strong&gt;k&lt;/strong&gt;. Да, мы снова прибегаем к forget, input gate для контроля забывания и релевантности накапливаемой информации.&lt;/p&gt;
  &lt;p id=&quot;fWBb&quot;&gt;Аналогично мы поступаем в формуле 20 с одним отличием - там происходит накопление только векторов &lt;strong&gt;k&lt;/strong&gt;.&lt;/p&gt;
  &lt;p id=&quot;yzfn&quot;&gt;Наконец, в 21 авторы приводят расчет скрытого состояния архитектуры mLSTM - &lt;strong&gt;извлекают и нормализуют релевантную информацию из памяти&lt;/strong&gt;. В правой части в числителе происходит извлечение необходимой информации из матрицы &lt;strong&gt;C&lt;/strong&gt; с помощью вектора &lt;strong&gt;q&lt;/strong&gt;. Да, в разобраном выше примере ковариации я использовал вектор &lt;strong&gt;k&lt;/strong&gt;, однако в таком случае мы извлекали только один конкретный вектор &lt;strong&gt;v&lt;/strong&gt;. В реальности нам необходимо регулировать извлечение релевантных векторов &lt;strong&gt;v&lt;/strong&gt;, в зависимости от текущего контекста, поэтому извлечение происходит с помощью вектора &lt;strong&gt;q&lt;/strong&gt;. Похожее извлечение мы видим и в знаменателе, только там мы работаем только с накопленными векторами &lt;strong&gt;k&lt;/strong&gt;, и берем максимум между получившимся значением модуля произведения векторов и единицы - это необходимо чтобы избежать деления на малые значения, близкие к нулю. В конце мы умножаем результат на output gate, как это делали в sLSTM.&lt;/p&gt;
  &lt;p id=&quot;B9Uf&quot;&gt;Выходом mLSTM является скрытое состояние h (это будет хорошо видно по коду далее).&lt;/p&gt;
  &lt;hr /&gt;
  &lt;h2 id=&quot;ViPD&quot; data-align=&quot;center&quot;&gt;&lt;u&gt;xLSTM&lt;/u&gt;&lt;/h2&gt;
  &lt;p id=&quot;jvFx&quot;&gt;Поздравляю, что вы дочитали до этого места, потому что представляю, как сложно за один раз уложить в голове все вышенаписанное (мне потребовалось 2,5 дня полной работы со статьей).&lt;/p&gt;
  &lt;p id=&quot;Gpm4&quot;&gt;Последнее, что нам остается - собрать две архитектуры в единую структуру и назвать ее xLSTM. Для каждого модуля разработан свой вариант построения сети.&lt;/p&gt;
  &lt;figure id=&quot;dq11&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/90/28/902873ac-a782-4457-9aa5-3f43ad6885e7.png&quot; width=&quot;1184&quot; /&gt;
    &lt;figcaption&gt;Еще раз спасибо &lt;a href=&quot;https://datasecrets.ru/articles/10&quot; target=&quot;_blank&quot;&gt;DataSecrets&lt;/a&gt; за их разбор и комментарии к изображениям!&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;WhiF&quot;&gt;Для sLSTM все начинается с &lt;a href=&quot;https://arxiv.org/abs/1607.06450&quot; target=&quot;_blank&quot;&gt;LayerNorm&lt;/a&gt; (LN), через который проходит входной вектор. Далее опционально применяется 1D свертка с окном 4 и нелинейная функция &lt;a href=&quot;https://en.wikipedia.org/wiki/Swish_function&quot; target=&quot;_blank&quot;&gt;Swish&lt;/a&gt; перед подачей данных в input и forget gate. Потом для input, forget, z и output gate данные подаются через блочно-диагональные линейные слои с четырьмя диагональными блоками (или головами).&lt;/p&gt;
  &lt;p id=&quot;aNzc&quot;&gt;Этот момент мне кажется довольно непростым для понимания, поэтому я решил порыться в коде имплементации. Если захотите подробно разобраться в этом шаге, то вам нужен класс &lt;a href=&quot;https://github.com/NX-AI/xlstm/blob/main/xlstm/components/linear_headwise.py&quot; target=&quot;_blank&quot;&gt;LinearHeadwiseExpand&lt;/a&gt;, но если описывать кратко, то в этом классе входные данные проецируются в более высокую размерность, разделяясь на несколько независимых линейных преобразований (тех самых голов), преобразуя входной тензор &lt;strong&gt;x&lt;/strong&gt; в форму &lt;em&gt;(..., num_heads, in_features // num_heads)&lt;/em&gt;&lt;/p&gt;
  &lt;pre id=&quot;1Zo4&quot;&gt;self.weight = nn.Parameter(
            torch.empty(num_heads, out_features_per_head, in_features // num_heads),
            requires_grad=config.trainable_weight,
        )&lt;/pre&gt;
  &lt;pre id=&quot;UCcb&quot;&gt;x = x.view(*shape[:-1], self.config.num_heads, -1)
x = torch.einsum(&amp;quot;...hd,hod-&amp;gt;...ho&amp;quot;, x, self.weight)
x = x.reshape(*shape[:-1], -1)&lt;/pre&gt;
  &lt;p id=&quot;x1n4&quot;&gt;Каждая голова имеет свой собственный набор весов (по сути это и есть то самое &lt;strong&gt;New Memory Mixing&lt;/strong&gt;). Их результаты объединяются в один выходной тензор.&lt;/p&gt;
  &lt;p id=&quot;bBXw&quot;&gt;Далее происходит сама работа sLSTM, где &lt;a href=&quot;https://github.com/NX-AI/xlstm/blob/main/xlstm/blocks/slstm/src/vanilla/slstm.py&quot; target=&quot;_blank&quot;&gt;sLSTMCell_vanilla&lt;/a&gt; возвращает ячейки памяти:&lt;/p&gt;
  &lt;pre id=&quot;PCqG&quot;&gt;return torch.stack((ynew, cnew, nnew, mnew), dim=0),
       torch.stack((igate, fgate, zraw, ogate), dim=0)&lt;/pre&gt;
  &lt;p id=&quot;aQLY&quot;&gt;Которые в дальнейшем (метод forward в классе &lt;a href=&quot;https://github.com/NX-AI/xlstm/blob/main/xlstm/blocks/slstm/layer.py&quot; target=&quot;_blank&quot;&gt;sLSTMLayer&lt;/a&gt;) проходят через Dropout, поступают в GroupNorm и передаются сначала в up-projection для увеличения размерности (снова происходит параллельное разделение с использованием функции &lt;a href=&quot;https://pytorch.org/docs/stable/generated/torch.nn.GELU.html&quot; target=&quot;_blank&quot;&gt;GeLU&lt;/a&gt;), а после в down-projection для возвращения данных к первоначальному размеру. Здесь происходит что то вроде отсева качественных данных, если можно это назвать так грубо. Не забудем про skip-connections, которые добавляются к результату, чтобы побороть затухание градиента.&lt;/p&gt;
  &lt;figure id=&quot;Vnql&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/0e/8d/0e8d7467-cfa6-4a48-afb6-6b5668dd01ad.png&quot; width=&quot;1264&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;WHne&quot;&gt;Теперь обратимся к сети с &lt;strong&gt;mLSTM&lt;/strong&gt;. При разборе, я опирался на код ее слоя &lt;a href=&quot;https://github.com/NX-AI/xlstm/blob/main/xlstm/blocks/mlstm/layer.py&quot; target=&quot;_blank&quot;&gt;mLSTMLayer&lt;/a&gt;. Ее pipeline выглядит примерно также, только входные данные сначала проходят через LayerNorm и up-projection, одновременно разделяясь на 2 потока - один проходит через mLSTM, другой через активацию Swish (или SiLU). Данные, поданные в mLSTM, снова разделяются (авторы очень любят паралеллить, как вы заметили) и для векторов &lt;strong&gt;q&lt;/strong&gt; и &lt;strong&gt;k&lt;/strong&gt; они предварительно проходят через слой 1D свертки с окном 4 аналогично sLSTM (от сюда же данные добавляются через LearnableSkip в пост обработку - этот LS является обучаемым). Точно также с помощью &lt;a href=&quot;https://github.com/NX-AI/xlstm/blob/main/xlstm/components/linear_headwise.py&quot; target=&quot;_blank&quot;&gt;LinearHeadwiseExpand&lt;/a&gt; данные переводятся в блочно-диагональный вид (Block Size = 4) для каждого вектора и подаются в &lt;a href=&quot;https://github.com/NX-AI/xlstm/blob/main/xlstm/blocks/mlstm/cell.py&quot; target=&quot;_blank&quot;&gt;mLSTMCell&lt;/a&gt;, который возвращает &lt;strong&gt;h&lt;/strong&gt;, &lt;strong&gt;c&lt;/strong&gt;, &lt;strong&gt;n&lt;/strong&gt; и &lt;strong&gt;m&lt;/strong&gt; &lt;a href=&quot;https://github.com/NX-AI/xlstm/blob/main/xlstm/blocks/mlstm/backends.py&quot; target=&quot;_blank&quot;&gt;значения&lt;/a&gt; (&lt;strong&gt;m&lt;/strong&gt; - это переменная из стабилизации):&lt;/p&gt;
  &lt;pre id=&quot;p78r&quot;&gt;h, (c_state_new, n_state_new, m_state_new)&lt;/pre&gt;
  &lt;p id=&quot;s4pB&quot;&gt;Вот так это выглядит в &lt;a href=&quot;https://github.com/NX-AI/xlstm/blob/main/xlstm/blocks/mlstm/layer.py&quot; target=&quot;_blank&quot;&gt;коде&lt;/a&gt;:&lt;/p&gt;
  &lt;pre id=&quot;EAlN&quot;&gt;h_tilde_state, mlstm_state = self.mlstm_cell.step(q=q, k=k, v=v, mlstm_state=mlstm_state)&lt;/pre&gt;
  &lt;p id=&quot;GP2B&quot;&gt;Я не очень понял множество линий передачи матриц &lt;strong&gt;q&lt;/strong&gt;, &lt;strong&gt;k&lt;/strong&gt; и &lt;strong&gt;v&lt;/strong&gt;, которые авторы нарисовали в блоке mLSTM на картинке выше. В коде не нашел ничего подобного - все сводится к скрытому состоянию h, к которому добавляется значение из LearnableSkip. Результат умножается на output gate от результата skip-connection через Swish (или SiLU) функцию, проходит через понижение размерности down-projection, слой dropout и возвращается вместе с вычисленными состояниями &lt;strong&gt;mlstm_state&lt;/strong&gt;, &lt;strong&gt;conv_state&lt;/strong&gt;:&lt;/p&gt;
  &lt;pre id=&quot;rDJX&quot;&gt;h_tilde_state_skip = h_tilde_state + (self.learnable_skip * x_mlstm_conv_act)

# output / z branch
h_state = h_tilde_state_skip * self.ogate_act_fn(z)

# down-projection
y = self.dropout(self.proj_down(h_state))
return y, {&amp;quot;mlstm_state&amp;quot;: mlstm_state, &amp;quot;conv_state&amp;quot;: conv_state}&lt;/pre&gt;
  &lt;p id=&quot;5QND&quot;&gt;Нужно отметить, что данные действительно проходят через GroupNorm, только реализован он в &lt;a href=&quot;https://github.com/NX-AI/xlstm/blob/main/xlstm/blocks/mlstm/cell.py&quot; target=&quot;_blank&quot;&gt;mLSTMCell&lt;/a&gt; в виде:&lt;/p&gt;
  &lt;pre id=&quot;MHda&quot;&gt;h_state_norm = self.outnorm(h_state)  # (B, NH, S, DH) &lt;/pre&gt;
  &lt;p id=&quot;7g12&quot;&gt;Как же работает xLSTM? Все просто - эти слои соединяются друг с другом, формируя единую сеть с названием xLSTM. Рекомендую посмотреть пример в &lt;a href=&quot;https://github.com/NX-AI/xlstm/blob/main/test/xLSTM_test_notebook.ipynb&quot; target=&quot;_blank&quot;&gt;ноутбуке&lt;/a&gt;. Количество блоков той или иной архитектуры регулируется пропорцией, то есть в xLSTM[7:1] будет 7 блоков mLSTM и 1 блок sLSTM (или 42 mLSTM и 6 sLSTM).&lt;/p&gt;
  &lt;hr /&gt;
  &lt;h3 id=&quot;0Ilw&quot;&gt;Результаты&lt;/h3&gt;
  &lt;p id=&quot;9ww4&quot;&gt;Их много, поэтому как обычно приведу несколько, которые меня привлекли больше всего (также не хочу увеличивать размер статьи). Если хотите узнать больше, то рекомендую прочитать их &lt;a href=&quot;https://t.me/gonzo_ML/2625&quot; target=&quot;_blank&quot;&gt;тут&lt;/a&gt;. Канал gonzo уважаю)&lt;/p&gt;
  &lt;p id=&quot;gSzN&quot;&gt;Итак, при сравнении предсказания следующей лексемы при обучении на 15B из SlimPajama, xLSTM показывает лучшие результаты (правда Llama бралась не 70B, а 1.3B). Аналогичная ситуация при трейне на 300B.&lt;/p&gt;
  &lt;figure id=&quot;8aPS&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/a6/21/a6217a52-95f4-470e-887e-329a13cd14cf.png&quot; width=&quot;563.6824324324325&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure id=&quot;XWyU&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/c2/25/c2254a3f-16b7-4a84-97d4-c4bfa31afde0.png&quot; width=&quot;557.6099585062241&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;uyb7&quot;&gt;Экстраполяция последовательностей в языковом моделировании. Это сравнение больших моделей xLSTM, RWKV-4, Llama и Mamba размером 1,3B при предсказании следующей лексемы на валидационном наборе SlimPajama после обучения на 300B лексем от туда же. Модели обучались на длине контекста 2048, а затем тестировались на длинах контекста до 16384. &lt;strong&gt;Слева&lt;/strong&gt;: оценка сложности лексем при различных длинах контекста. В отличие от других методов, модели xLSTM остаются на низком уровне сложности для более длинных контекстов. &lt;strong&gt;Справа&lt;/strong&gt;: Качество предсказания при экстраполяции на большие размеры контекста в терминах валидационной perplexity (PPL). xLSTM дает лучшие значения PPL.&lt;/p&gt;
  &lt;figure id=&quot;oUvW&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/80/01/8001d987-13cb-4066-9b0d-d69c550711f3.png&quot; width=&quot;686&quot; /&gt;
  &lt;/figure&gt;
  &lt;hr /&gt;
  &lt;h3 id=&quot;Tk8O&quot;&gt;Итог&lt;/h3&gt;
  &lt;p id=&quot;oaqn&quot;&gt;В целом видно, что RNN все также сильны и да, к ним в последнее время проявляется интерес. Сложно сказать из-за чего это происходит, но я полагаю, что люди пока не изобрели что-то лучше трансформеров и &lt;a href=&quot;https://huggingface.co/blog/lbourdois/get-on-the-ssm-train&quot; target=&quot;_blank&quot;&gt;SSM&lt;/a&gt;, поэтому обращаются к прошлому и улучшают его. &lt;/p&gt;
  &lt;p id=&quot;EXcq&quot;&gt;Я не думаю, что сейчас все бигтехи массово перейдут на эту архитектуру. И никто не будет переобучать gpt-4 и gpt-4o на xLSTM (хотя xLSTM уже приспособили для задач CV - вот статья &lt;a href=&quot;https://arxiv.org/abs/2406.04303&quot; target=&quot;_blank&quot;&gt;Vision-LSTM: xLSTM as Generic Vision Backbone&lt;/a&gt;).&lt;/p&gt;
  &lt;p id=&quot;bSd2&quot;&gt;Мое мнение - крутой апгрейд RNN, который, вероятно, локально применят в NLP/CV отделах RnD, в стартапах и в науке. Если он хорошо себя зарекомендует, есть вероятность, что увидим развитие этой технологии, а также новые решения в проде :)&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;dczB&quot;&gt;На этом у меня все!&lt;/p&gt;
  &lt;p id=&quot;Ve1S&quot;&gt;Спасибо, что дочитали до конца! Я знаю, что это было трудно, но вы справились)&lt;/p&gt;

</content></entry><entry><id>kittybytes:3DcZcuvHbyY</id><link rel="alternate" type="text/html" href="https://teletype.in/@kittybytes/3DcZcuvHbyY?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=kittybytes"></link><title>RAPTOR: Recursive Abstractive Processing for Tree-Organized Retrieval</title><published>2024-05-31T22:49:32.677Z</published><updated>2024-06-01T23:28:39.835Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/12/48/12486b6c-780c-4b95-a096-8340e523b497.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/67/4e/674e06c7-39a3-4b92-ad6e-2dc23d7da058.png&quot;&gt;В статье разработана новая архитектура RAG на основе итерационного процесса кластеризации фрагментов текстов БД и их суммаризации. Архитектура позволяет сети отвечать на тематические запросы, требующие суммаризованного контекста всего документа (пример - краткий пересказ книги).</summary><content type="html">
  &lt;p id=&quot;QZic&quot;&gt;В статье разработана новая архитектура RAG на основе итерационного процесса кластеризации фрагментов текстов БД и их суммаризации. Архитектура позволяет сети отвечать на тематические запросы, требующие суммаризованного контекста всего документа (пример - краткий пересказ книги).&lt;/p&gt;
  &lt;p id=&quot;Q1rt&quot;&gt;&lt;em&gt;Source:&lt;/em&gt; &lt;a href=&quot;https://arxiv.org/abs/2401.18059&quot; target=&quot;_blank&quot;&gt;Arxive&lt;/a&gt;&lt;/p&gt;
  &lt;hr /&gt;
  &lt;blockquote id=&quot;gfSr&quot;&gt;Все подходы RAG отлично работают на практике. Тем не менее, у них есть и недостатки. Один из них заключается в том, что большинство существующих методов извлекают только несколько коротких, непрерывных фрагментов текста, что ограничивает их способность представлять и использовать крупномасштабную структуру одного документа или даже нескольких. Это особенно актуально для тематических вопросов, требующих интеграции знаний из нескольких частей текста, таких как понимание целой книги.&lt;/blockquote&gt;
  &lt;p id=&quot;6fO7&quot;&gt;Если вкратце перефразировать проблему - &lt;u&gt;чем больше фрагментов текста вы включаете в запрос, тем меньше вам нужен RAG&lt;/u&gt;. Ведь по сути вы можете вместе с запросом отправлять в LLM целые книги (контекстные окна сегодняшних моделей позволяют это делать). Например top-K фрагментов текста не смогут дать полного ответа на запрос &amp;quot;Через что прошли дети капитана Гранта, чтобы найти своего отца?&amp;quot;.&lt;/p&gt;
  &lt;p id=&quot;vMY2&quot;&gt;Также они не смогут полно ответить на сравнительные вопросы типа &amp;quot;Как?&amp;quot;: &amp;quot;Как ты думаешь, идти мне на Data Fest или идти на свидание?&amp;quot; из-за большого количества релевантных фрагментов текста. В целом для любой системы RAG найти несколько конкретных частей текста в большом документе является сложной задачей.&lt;/p&gt;
  &lt;p id=&quot;nfkG&quot;&gt;&lt;em&gt;Что же делает RAPTOR?&lt;/em&gt;&lt;/p&gt;
  &lt;p id=&quot;Hhpz&quot;&gt;Вместо разделения документов на маленькие фрагменты и сохранения их в векторную БД для последующего извлечения, RAPTOR сначала их кластеризует, а после суммаризует каждый кластер с помощью LLM. Он повторяет этот процесс итерационно, пока не остается один, финальный фрагмент текста, в котором содержится вся информация документа.&lt;/p&gt;
  &lt;figure id=&quot;TFDC&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/67/4e/674e06c7-39a3-4b92-ad6e-2dc23d7da058.png&quot; width=&quot;2754&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;pdBy&quot;&gt;Каждая нода содержит следующую информацию:&lt;/p&gt;
  &lt;ol id=&quot;YWZM&quot;&gt;
    &lt;li id=&quot;kF1f&quot;&gt;Индекс ноды&lt;/li&gt;
    &lt;li id=&quot;vmGj&quot;&gt;Индексы дочерних нод&lt;/li&gt;
    &lt;li id=&quot;2S6N&quot;&gt;Summary текст дочерних нод&lt;/li&gt;
    &lt;li id=&quot;XplU&quot;&gt;Эмбеддинг summary&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;Cw0P&quot;&gt;Именно такая архитектура &amp;quot;от общего к частному&amp;quot; отлично работает на любых запросах - краткое содержание книги, сравнение двух книг или даже конкретные факты из обоих документов с сравнением. Все это извлекается в общих чертах с готовой суммаризированной информацией, а если необходимы факты, то можно опуститься на слой ниже и извлечь более детальное summary.&lt;/p&gt;
  &lt;p id=&quot;NNaO&quot;&gt;Авторы используют мягкую &lt;strong&gt;кластеризацию&lt;/strong&gt; - то есть узлы могут принадлежать к нескольким кластерам одновременно, не требуя фиксированного числа кластеров. Такая особенность необходима, поскольку отдельные фрагменты текста часто содержат информацию, относящуюся к различным темам, что оправдывает их включение в несколько кластеров.&lt;/p&gt;
  &lt;p id=&quot;pHL4&quot;&gt;Алгоритм кластеризации основан на &lt;em&gt;Gaussian Mixture Models (GMMs) &lt;/em&gt;- такой подход обеспечивает необходимую гибкость и вероятностную структуру. GMM - это вероятностная модель, которая предполагает, что все точки данных генерируются из смеси конечного числа гауссовских распределений с неизвестными параметрами.&lt;/p&gt;
  &lt;figure id=&quot;xxfv&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/83/d4/83d4fc54-3635-4408-86a2-b06dcf63901d.png&quot; width=&quot;776&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Q4AI&quot;&gt;В качестве финального распределения вероятности (к каким кластерам принадлежит вектор текста) авторы используют взвешенные Гауссовы распределения.&lt;/p&gt;
  &lt;p id=&quot;0gcX&quot;&gt;Первая формула - условная вероятность принадлежности вектора &lt;strong&gt;x &lt;/strong&gt;(эмбеддинга текста некоторой размерности) к некоторому кластеру &lt;em&gt;k&lt;/em&gt;. Остальные параметры - параметры гауссого распределения &lt;em&gt;N&lt;/em&gt;. Финальное распределение - взвешенная сумма всех распределений. Параметры &lt;em&gt;π &lt;/em&gt;определяют принадлежность каждого распределения к кластеру.&lt;/p&gt;
  &lt;figure id=&quot;DCXN&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://lh7-us.googleusercontent.com/slidesz/AGV_vUcdFSMnnz72RHr33rBOBTAIRyIOyG8JAGaFQmhyYnqWCgp0vuvYqsFucrLkNS7I2yrJdSURJFTPRWVYwmwrpFWwhT__TRUCdwUL057vuDDhdxcKlprt6pF6fH1Af0ZkWHfTmEnwfyAMcISbVNGHY9UzZvjC7xq-=nw?key=2VgCQjLCj_MBb_icA-DAVA&quot; width=&quot;322&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;9h77&quot;&gt;Однако высокая размерность эмбеддингов представляет собой проблему для традиционных GMM, поскольку метрики расстояний могут плохо себя вести при измерении сходства в высокоразмерных пространствах. Поэтому авторы используют &lt;a href=&quot;https://arxiv.org/abs/1802.03426&quot; target=&quot;_blank&quot;&gt;Uniform Manifold Approximation and Projection&lt;/a&gt; (UMAP) для уменьшения размерности. Варьируя параметр количества соседей в кластерах, они понижают размерность эмбеддингов в 2 этапа:&lt;/p&gt;
  &lt;ol id=&quot;7ukv&quot;&gt;
    &lt;li id=&quot;9NOC&quot;&gt;Сначала определяются глобальные кластеры&lt;/li&gt;
    &lt;li id=&quot;jI1U&quot;&gt;Затем выполняется локальная кластеризация внутри этих глобальных кластеров&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;bxVm&quot;&gt;Такой подход позволяет захватить как общие темы, так и конкретные детали.&lt;/p&gt;
  &lt;p id=&quot;I5xN&quot;&gt;Оптимальное количество кластеров определяется с помощью &lt;a href=&quot;https://en.wikipedia.org/wiki/Bayesian_information_criterion&quot; target=&quot;_blank&quot;&gt;Bayesian Information Criterion&lt;/a&gt; (BIC) - критерий, который сильно штрафует модель за увеличение количества параметров и вознаградает за уменьшение.&lt;/p&gt;
  &lt;figure id=&quot;G9uN&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/94/92/94923ffa-995e-4414-b1b3-8de5c7009b56.png&quot; width=&quot;335&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Hjsf&quot;&gt;Здесь &lt;em&gt;N&lt;/em&gt; - количество фрагментов текста, &lt;em&gt;k&lt;/em&gt; - параметры модели а &lt;em&gt;L&lt;/em&gt; - максимизированное значение функции правдоподобия модели (likelihood function). Благодаря этому критерию, у нас не возникнет ситуации равенства количества фрагментов текста и кластеров.&lt;/p&gt;
  &lt;p id=&quot;Wanr&quot;&gt;Для &lt;strong&gt;суммаризации&lt;/strong&gt; текста авторы использовали gpt-3.5-turbo, однако около 4% summary содержали незначительные галлюцинации. Они не распространялись&lt;br /&gt;на родительские узлы и не оказали заметного влияния на решение задач, связанных с ответами на вопросы.&lt;/p&gt;
  &lt;p id=&quot;wpGc&quot;&gt;RAPTOR предоставляет 2 способа запроса - &lt;u&gt;traversal&lt;/u&gt; и &lt;u&gt;collapsed&lt;/u&gt; tree.&lt;/p&gt;
  &lt;p id=&quot;teWH&quot;&gt;&lt;strong&gt;Traversal tree&lt;/strong&gt; - сначала выбирается top-k наиболее релевантных корневых узлов на основе их косинусного сходства с запросом. На следующем уровне рассматриваются дочерние узлы этих выбранных узлов, и из этого пула снова выбираются top-k узлов на основе их косинусного сходства с вектором запроса. Этот процесс повторяется до тех пор, пока мы не достигнем узлов листа.&lt;/p&gt;
  &lt;p id=&quot;hKGq&quot;&gt;&lt;strong&gt;Collapsed tree&lt;/strong&gt; - более простой способ поиска релевантной информации за счет одновременного рассмотрения всех узлов дерева. Вместо того чтобы переходить от слоя к слою, этот метод сглаживает многослойное дерево в один слой, выводя все узлы на один уровень для сравнения.&lt;/p&gt;
  &lt;figure id=&quot;Gvwf&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/36/b0/36b098bc-d3df-4b65-bc57-a082f114baba.png&quot; width=&quot;1546&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;DvrU&quot;&gt;&lt;strong&gt;Результаты&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;Qb7b&quot;&gt;RAPTOR превосходит базовые показатели каждого из соответствующих методов поиска (SBERT, BM25, DPR) на наборе данных NarrativeQA, используя в качестве языковой модели UnifiedQA-3B.&lt;/p&gt;
  &lt;figure id=&quot;jTr6&quot; class=&quot;m_retina&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/26/c0/26c0461e-34f6-4d74-932b-d56ece09bcfa.png&quot; width=&quot;653&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;erwj&quot;&gt;Также он превосходит поисковики на датасетах QuALITY и QASPER.&lt;/p&gt;
  &lt;figure id=&quot;rGdA&quot; class=&quot;m_retina&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/af/dc/afdc1b07-fdca-4992-b15f-3ab61c9eb1d0.png&quot; width=&quot;662&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;WZ8Y&quot;&gt;Сравнение оценок F-1 на наборе данных QASPER с использованием трех различных языковых моделей (GPT-3, GPT-4, UnifiedQA 3B) и различных методов поиска. «Title + Abstract» отражает производительность, когда для контекста используются только название и аннотация статей.&lt;/p&gt;
  &lt;figure id=&quot;U50H&quot; class=&quot;m_retina&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/98/8b/988ba29b-e16a-4903-a534-e441fc8c8c7b.png&quot; width=&quot;713&quot; /&gt;
  &lt;/figure&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;ZPxM&quot;&gt;Спасибо, что дочитали до конца!&lt;/p&gt;
  &lt;p id=&quot;kF1X&quot;&gt;Пишите свои мысли в комментариях)&lt;/p&gt;

</content></entry><entry><id>kittybytes:kaN0GoIgXSp</id><link rel="alternate" type="text/html" href="https://teletype.in/@kittybytes/kaN0GoIgXSp?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=kittybytes"></link><title>Advanced RAG Pipelines</title><published>2024-05-31T12:20:02.413Z</published><updated>2024-06-01T00:00:18.589Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/5a/ed/5aed2e2f-5a25-4b80-afaf-83f07ffa943e.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/43/6e/436ec8e9-c7ca-415e-bb4d-74e3e81c124f.png&quot;&gt;Начиная писать этот материал я решил не вдаваться в подробности базовой архитектуры Retrieval-Augmented Generation (RAG), поскольку про нее и так много известно (но на крайний случай оставляю ссылки на краткое объяснение и полный гайд). Также хочу поделиться классным репозиторием от LangChain - в нескольких ноутбуках from scratch реализован RAG в нескольких вариантах для разных БД. Шпаргалка по работе RAG именно от туда:</summary><content type="html">
  &lt;p id=&quot;oA04&quot;&gt;Начиная писать этот материал я решил не вдаваться в подробности базовой архитектуры Retrieval-Augmented Generation (RAG), поскольку про нее и так много известно (но на крайний случай оставляю ссылки на &lt;a href=&quot;https://habr.com/ru/articles/779526/&quot; target=&quot;_blank&quot;&gt;краткое объяснение&lt;/a&gt; и &lt;a href=&quot;https://habr.com/ru/companies/raft/articles/791034/&quot; target=&quot;_blank&quot;&gt;полный гайд&lt;/a&gt;). Также хочу поделиться классным &lt;a href=&quot;https://github.com/langchain-ai/rag-from-scratch/tree/main&quot; target=&quot;_blank&quot;&gt;репозиторием&lt;/a&gt; от LangChain - в нескольких ноутбуках from scratch реализован RAG в нескольких вариантах для разных БД. Шпаргалка по работе RAG именно от туда:&lt;/p&gt;
  &lt;figure id=&quot;1SzF&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/43/6e/436ec8e9-c7ca-415e-bb4d-74e3e81c124f.png&quot; width=&quot;1586&quot; /&gt;
    &lt;figcaption&gt;&lt;a href=&quot;https://github.com/langchain-ai/rag-from-scratch/tree/main&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;hr /&gt;
  &lt;h3 id=&quot;Nbdz&quot;&gt;Base RAG&lt;/h3&gt;
  &lt;p id=&quot;9v7U&quot;&gt;Все знают, что LLM помогают в любой работе - писать код, статьи, проводить собесы и так далее. Однако у них есть несколько проблем - устаревшая информация и галюцинации. Например, вводя запрос &amp;quot;Какой сейчас курс доллара?&amp;quot;, LLM, очевидно, не может воспользоваться той информацией, которую получала при обучении, потому что курс все время меняется. Спрашивая ее о конкретных фактах &amp;quot;Какое влияние оказали первые реформы Столыпина на экономику Российской Империи?&amp;quot;, LLM может сгенерировать неточную или неверную информацию.&lt;/p&gt;
  &lt;p id=&quot;MCEB&quot;&gt;Retrieval-Augmented Generation помогает решить эти проблемы - он ищет по запросам пользователя релевантные документы в базе данных (БД) и прикрепляет их к промпту в LLM. Сами документы разбиты в БД на небольшие фрагменты текста (chunks), которые перекрывают друг друга - это необходимо для подачи в сеть последовательности фрагментов и сохранения контекстной взаимосвязи между ними.&lt;/p&gt;
  &lt;figure id=&quot;pA5g&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/0e/10/0e106db3-b527-4e48-b444-72d07d73500d.png&quot; width=&quot;2624&quot; /&gt;
    &lt;figcaption&gt;&lt;a href=&quot;https://arxiv.org/abs/2402.01767&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;OuIE&quot;&gt;Фрагменты текста хранятся в векторной БД (например &lt;a href=&quot;https://qdrant.tech/&quot; target=&quot;_blank&quot;&gt;QDrant&lt;/a&gt;, &lt;a href=&quot;https://www.trychroma.com/&quot; target=&quot;_blank&quot;&gt;Chroma&lt;/a&gt;, &lt;a href=&quot;https://faiss.ai/&quot; target=&quot;_blank&quot;&gt;FAISS&lt;/a&gt;), а их релевантность с запросом пользователя можно оценивать с помощью CosSim, Cross-Encoder, Bi-Encoder. Так LLM получает точные данные из БД вместе с запросом, что и помогает ей давать релевантные ответы. &lt;/p&gt;
  &lt;figure id=&quot;suKj&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/79/87/79875190-f853-428e-91db-09ae0834a3ed.png&quot; width=&quot;2026&quot; /&gt;
    &lt;figcaption&gt;&lt;a href=&quot;https://aws.amazon.com/ru/what-is/retrieval-augmented-generation/&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;s3ZU&quot;&gt;Теперь давайте сфокусируемся на более сложных pipeline-ах RAG и попробуем их детально разобрать.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;h3 id=&quot;oS98&quot;&gt;Document Hierarchies&lt;/h3&gt;
  &lt;p id=&quot;MjPX&quot;&gt;Начнем с иерархии документов. Думаю по названию уже понятен основной смысл - мы можем создать что-то вроде оглавления к RAG чтобы структурировать и сегментировать наши данные. Закономерный вопрос - почему векторная БД не справиться с этой задачей и необходимо придумывать новую архитектуру хранения данных?&lt;/p&gt;
  &lt;p id=&quot;Rkym&quot;&gt;Попробуем ответить вопросом на вопросом - а что если в нашей системе миллиарды документов (например - бухгалтерская база экономики страны)? &lt;/p&gt;
  &lt;p id=&quot;2GvL&quot;&gt;1) Разделяя документ на чанки, мы получаем что каждый фрагмент содержит лишь &lt;strong&gt;минимальное представление содержимого исходного документа&lt;/strong&gt;. Такое сокращение содержания приводит к потере контекста и потере важной информации во время поиска, поскольку каждому фрагменту не хватает полного понимания исходного документа.&lt;/p&gt;
  &lt;p id=&quot;90FO&quot;&gt;2) Более того, с увеличением объема данных &lt;strong&gt;количество шума при каждом извлечении увеличивается&lt;/strong&gt;, а значит система гораздо чаще находит неверные данные, которые просто оказались ближе друг к другу.&lt;/p&gt;
  &lt;p id=&quot;AlDt&quot;&gt;Иерархическая структура документов пытается решить эти проблемы - она извлекает и сегментирует семантические представления текста. С помощью многоуровневого распределения система итеративно уточняет наше пространство поиска, чтобы использовать только тот набор данных, который, как мы знаем, имеет семантически релевантное содержание для нашего первоначального запроса. В этом случае документ (родительская нода) выступает суммарным эмбеддингом дочерних фрагментов текста.&lt;/p&gt;
  &lt;p id=&quot;rtXE&quot;&gt;Пример трехуровневой системы:&lt;/p&gt;
  &lt;ol id=&quot;hXfu&quot;&gt;
    &lt;li id=&quot;1A25&quot;&gt;Кластера или группы документов&lt;/li&gt;
    &lt;li id=&quot;xnpm&quot;&gt;Отдельные документы по соответствующим темам&lt;/li&gt;
    &lt;li id=&quot;qxbm&quot;&gt;Отдельные части релевантных документов&lt;/li&gt;
  &lt;/ol&gt;
  &lt;figure id=&quot;1OOi&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/6d/4c/6d4ce896-bfdf-4d23-8e1c-103da415399d.png&quot; width=&quot;2072&quot; /&gt;
    &lt;figcaption&gt;&lt;a href=&quot;https://www.arcus.co/blog/rag-at-planet-scale&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt; &lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;hDzK&quot;&gt;Еще одну реализацию иерархической структуры документов можно найти у &lt;a href=&quot;https://docs.llamaindex.ai/en/stable/&quot; target=&quot;_blank&quot;&gt;LlamaIndex&lt;/a&gt; - &lt;a href=&quot;https://docs.llamaindex.ai/en/stable/examples/retrievers/auto_merging_retriever/&quot; target=&quot;_blank&quot;&gt;HierarchicalNodeParser&lt;/a&gt;. В этом варианте иерархия строится на убывании размера чанков текста - от большего к меньшему.&lt;/p&gt;
  &lt;p id=&quot;NqVr&quot;&gt;Например:&lt;/p&gt;
  &lt;ol id=&quot;W1Ym&quot;&gt;
    &lt;li id=&quot;BXUJ&quot;&gt;Размер чанка 2048&lt;/li&gt;
    &lt;li id=&quot;UNY9&quot;&gt;Размер чанка 512&lt;/li&gt;
    &lt;li id=&quot;mpeS&quot;&gt;Размер чанка 128&lt;/li&gt;
  &lt;/ol&gt;
  &lt;figure id=&quot;F5iz&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/43/24/432408aa-6862-41f1-8c5b-64dcb2325b33.png&quot; width=&quot;1406&quot; /&gt;
  &lt;/figure&gt;
  &lt;hr /&gt;
  &lt;h3 id=&quot;TR0j&quot;&gt;Knowledge Graphs&lt;/h3&gt;
  &lt;p id=&quot;d4BD&quot;&gt;Конечно же не обойтись без графов знаний - их основное преимущество перед иерархиями в отображении связей с использованием естесственного языка, а значит можно разработать интутивно понятные инструкции поиска информации для LLM.&lt;/p&gt;
  &lt;p id=&quot;20a5&quot;&gt;Узлы таких графов представляют отдельные сущности, такие как &lt;em&gt;люди&lt;/em&gt;, &lt;em&gt;места&lt;/em&gt;, &lt;em&gt;объекты&lt;/em&gt; или &lt;em&gt;концепции&lt;/em&gt;. А ребра представляют взаимоотношения между этими узлами, указывая на то, как они связаны друг с другом.&lt;/p&gt;
  &lt;figure id=&quot;bHWt&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://lh7-us.googleusercontent.com/slidesz/AGV_vUf_xeMFV5IFBilH1-qjReyl10cRgxDg8C6f8sZbmSMoVYc4--kuJa2LCSh8G0ykpdYwmxzeSh62LKuGfFjQqtl_pZ7fJB_nFBAqTUc7Dz41VXxk9K3NsfM8jLvpZlswpkS_Lrdwp568d8pTF4l3PrmXJj37c3g=nw?key=2VgCQjLCj_MBb_icA-DAVA&quot; width=&quot;1600&quot; /&gt;
    &lt;figcaption&gt;&lt;a href=&quot;https://www.semrush.com/blog/knowledge-graph/&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt; &lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;e2U0&quot;&gt;Но давайте разберемся как это работает.&lt;/p&gt;
  &lt;p id=&quot;mcf6&quot;&gt;На самом деле LLM можно использовать еще на базовом уровне, генерируя запрос на языке запросов к графовой БД (например Cypher, GraphQL или Gremlin). Примерный промпт такой задачи Text2Cypher может выглядеть вот так:&lt;/p&gt;
  &lt;pre id=&quot;A7uJ&quot;&gt;You are a NebulaGraph Cypher expert. Based on the provided graph Schema
and the question, please write the query statement.
The schema is as follows:
---
{schema}
---
The question is:
---
{question}
---
Now, write down the query statement:&lt;/pre&gt;
  &lt;p id=&quot;7Bo9&quot;&gt;LLM отлично справляется с выделением ключевых сущностей в текстах, поэтому без проблем перестроит запрос на язык графовой БД и извлечет необходимые данные.&lt;/p&gt;
  &lt;figure id=&quot;lCjj&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/f7/05/f7053aca-3885-4d6c-8a1b-55fb26a6965c.png&quot; width=&quot;2080&quot; /&gt;
    &lt;figcaption&gt;&lt;a href=&quot;https://colab.research.google.com/drive/1tLjOg2ZQuIClfuWrAC2LdiZHCov8oUbs?usp=sharing#scrollTo=ehfTzmtaqK2J&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;VdkA&quot;&gt;Но можно пойти дальше и построить GraphRAG. Есть 2 возможности - работать с обычным текстом или с его эмбеддингами. В первом случае ничего не меняется - у нас есть узлы и отношения между сущностями в виде ребер в графе и все это в текстовом формате. Во втором случае все интереснее - узлы представлены эмбеддингами, как и связи между ними. А это значит можно проходить по графу для поиска наиболее релевантных на запрос сущностей (привет CosSim).&lt;/p&gt;
  &lt;p id=&quot;fO11&quot;&gt;В этом случае LLM выполняет 3 действия:&lt;/p&gt;
  &lt;ol id=&quot;0Hme&quot;&gt;
    &lt;li id=&quot;h0QR&quot;&gt;Если работаем с эмбеддингами, то извлекаем топ-N семантически похожих узла из KG&lt;/li&gt;
    &lt;li id=&quot;6qXX&quot;&gt;С помощью LLM делаем запрос в KG для релевантных узлов, связанных с извлеченными сущностями&lt;/li&gt;
    &lt;li id=&quot;5Vzd&quot;&gt;Формируем подграф контекста и формируем промпт для LLM вместе с запросом&lt;/li&gt;
  &lt;/ol&gt;
  &lt;figure id=&quot;eDrd&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/2c/b8/2cb86201-fd6e-4f1d-a6c2-ac613b8905d2.png&quot; width=&quot;1564&quot; /&gt;
    &lt;figcaption&gt;&lt;a href=&quot;https://colab.research.google.com/drive/1tLjOg2ZQuIClfuWrAC2LdiZHCov8oUbs?usp=sharing#scrollTo=ehfTzmtaqK2J&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;I8RO&quot;&gt;Фреймворки для построения графовых БД - &lt;a href=&quot;https://www.ontotext.com/knowledge-hub/&quot; target=&quot;_blank&quot;&gt;ontotext&lt;/a&gt;, &lt;a href=&quot;https://www.nebula-graph.io/&quot; target=&quot;_blank&quot;&gt;NebulaGraph&lt;/a&gt; и &lt;a href=&quot;https://neo4j.com/product/neo4j-graph-database/&quot; target=&quot;_blank&quot;&gt;Neo4j&lt;/a&gt;&lt;/p&gt;
  &lt;hr /&gt;
  &lt;h3 id=&quot;XI0k&quot;&gt;Hypothetical Document Embeddings&lt;/h3&gt;
  &lt;p id=&quot;P7Bn&quot;&gt;Представьте, что вы задаете довольно общий, но простой вопрос LLM (например &amp;quot;Какие самые продаваемые бургеры?&amp;quot;). Проблема заключается в том, что найти контекст из БД документов для детализации этого вопроса иногда бывает сложно из-за его общности. К тому же по контексту этого запроса в БД слишком много релевантных фрагментов и становится струдной задачей определить какие именно являются релевантными.&lt;/p&gt;
  &lt;p id=&quot;8pGG&quot;&gt;Но можно пойти по другому. Если до этого момента мы пытались загрузить в сеть наш запрос вместе с релевантной информацией, взятой из документов БД, то исследователи в работе &lt;a href=&quot;https://arxiv.org/abs/2212.10496&quot; target=&quot;_blank&quot;&gt;Precise Zero-Shot Dense Retrieval without Relevance Labels&lt;/a&gt; предположили обратное - что если просить сеть сгенерировать первичный гипотетический ответ?&lt;/p&gt;
  &lt;figure id=&quot;4Fr0&quot; class=&quot;m_custom&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/26/67/2667e868-db1b-4307-8311-d1ccec756b2b.png&quot; width=&quot;870&quot; /&gt;
    &lt;figcaption&gt;&lt;a href=&quot;https://arxiv.org/abs/2212.10496&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;OLpq&quot;&gt;Чтобы решить проблему &amp;quot;холодного старта&amp;quot;, исследователи предложили брать первичный ответ из LLM на запрос пользователя, учитывая, что он может быть неточным. Но нам это и нужно - зная, что ответ содержит в себе лишь гипотетическую информацию (и возможно он не совсем верен), мы можем сравнить этот эмбеддинг с эмбеддингами правильных ответов из нашей БД и, благодаря похожему контексту, быстро найти точный ответ на наш запрос.&lt;/p&gt;
  &lt;p id=&quot;hCDn&quot;&gt;Улучшением такого подхода является генерация сразу нескольких гипотетических ответов и последующее их усреднение для нахождения более точного эмбеддинга из БД.&lt;/p&gt;
  &lt;p id=&quot;xOuG&quot;&gt;Необходимо уточнить, что это не работает, когда модели подается запрос, не являющийся wide-knowledge (&amp;quot;Объясни физический смысл лагранжиана квантовой хромодинамики&amp;quot;) - в данном случае высока вероятность генерации галлюцинаций.&lt;/p&gt;
  &lt;h3 id=&quot;Bm63&quot;&gt;Contextual Compressors &amp;amp; Filters&lt;/h3&gt;
  &lt;p id=&quot;zcUB&quot;&gt;Бывают ситуации, когда на ваш конкретный запрос находится много ответов в БД, а в итоге вам нужно лишь несколько фактов из разных фрагментов документов. Остальные детали вас не интересуют и вы не хотите, чтобы LLM видела их при формировании ответа. Эту проблему решает контекстное сжатие и фильтры.&lt;/p&gt;
  &lt;figure id=&quot;8Ovm&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/e1/81/e18118e6-6b8f-4b2e-b8e5-3aca9bd06b7f.png&quot; width=&quot;2048&quot; /&gt;
    &lt;figcaption&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=4sRigbRITF0&amp;list=PL8motc6AQftn-X1HkaGG9KjmKtWImCKJS&amp;index=10&amp;ab_channel=SamWitteveen&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;wVQB&quot;&gt;Суть проста - у нас есть базовый retriever, который выдает набор релевантных документов, а compressor сжимает их в короткие ответы и оставляет только необходимую информацию (только то, что полезно для ответа на вопрос).&lt;/p&gt;
  &lt;p id=&quot;hTZg&quot;&gt;Фильтром может быть что угодно - например стороняя fine-tuned LLM на эмейлы пользователей (картинка ниже).&lt;/p&gt;
  &lt;figure id=&quot;0FWO&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/a7/e3/a7e3affd-8c47-4329-bdc0-5dcf0c65c10e.png&quot; width=&quot;2046&quot; /&gt;
    &lt;figcaption&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=4sRigbRITF0&amp;list=PL8motc6AQftn-X1HkaGG9KjmKtWImCKJS&amp;index=10&amp;ab_channel=SamWitteveen&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;vJe0&quot;&gt;Вы даже можете построить собственный pipeline фильтра - например добавлять эмбеддинг важной информации (или формата ответа - JSON) в конец и только потом отправлять получившийся запрос в LLM.&lt;/p&gt;
  &lt;figure id=&quot;fZUn&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/85/a6/85a686e9-2cdd-4d12-b6a3-a7e8edc97b6a.png&quot; width=&quot;2046&quot; /&gt;
    &lt;figcaption&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=4sRigbRITF0&amp;list=PL8motc6AQftn-X1HkaGG9KjmKtWImCKJS&amp;index=10&amp;ab_channel=SamWitteveen&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;XVX5&quot;&gt;Multi-Query Retrieval&lt;/h3&gt;
  &lt;p id=&quot;yj2r&quot;&gt;Метод мульти-запросов тоже довольно прост в своей реализации. Между Retriever-ом и пользовательским запросом находится еще одна LLM, которая генерирует множетсво вариантов первичного запроса, рассматривая его с разных точек зрения.&lt;/p&gt;
  &lt;figure id=&quot;BJbZ&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/7c/03/7c031137-a05c-485d-ac8d-e26abae37b54.png&quot; width=&quot;2074&quot; /&gt;
    &lt;figcaption&gt;&lt;a href=&quot;https://teetracker.medium.com/langchain-llama-index-rag-with-multi-query-retrieval-4e7df1a62f83&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;VakG&quot;&gt;Например на запрос &amp;quot;Расскажи о правлении Петра I&amp;quot; сеть может сгенерировать дополнительные вопросы &amp;quot;Экономическая стабильность во времена правления Петра I&amp;quot;, &amp;quot;Войны и итоги во времена правления Петра I&amp;quot;, &amp;quot;Градостроительство во времена Петра I&amp;quot; и так далее.&lt;/p&gt;
  &lt;p id=&quot;OQbw&quot;&gt;Все эти запросы подаются Retriver-у для нахождения фрагментов документов, соответствующих запросам, после чего отбираются релевантные и подаются в LLM вместе с основным запросом.&lt;/p&gt;
  &lt;p id=&quot;n2xJ&quot;&gt;Эта архитектура поможет, когда пользователь ничего не знает о запрашиваемом объекте и составляет запрос общего характера. Так LLM сможет составить подробный ответ и собрать в нем максимальное количество информации.&lt;/p&gt;
  &lt;h3 id=&quot;4Ht4&quot;&gt;RAG-Fusion&lt;/h3&gt;
  &lt;p id=&quot;US9o&quot;&gt;Один из самых новых pipeline-ов RAG, который вышел обычной &lt;a href=&quot;https://towardsdatascience.com/forget-rag-the-future-is-rag-fusion-1147298d8ad1&quot; target=&quot;_blank&quot;&gt;публикацией&lt;/a&gt; на &lt;a href=&quot;https://towardsdatascience.com/&quot; target=&quot;_blank&quot;&gt;TowardsDataScience&lt;/a&gt;, предлагает устранить вечный разрыв между тем, что пользователи &lt;em&gt;&lt;u&gt;явно задают в запросе&lt;/u&gt;&lt;/em&gt; и тем, что они &lt;em&gt;&lt;u&gt;собираются спрашивать&lt;/u&gt;&lt;/em&gt;. Ведь иногда пользователь хочет узнать много фактов о какой-то теме и просто вводит ее в виде запроса. С этой проблемой также сталкиваются все поисковые системы.&lt;/p&gt;
  &lt;p id=&quot;fZjc&quot;&gt;Первое - мы генерируем multi-query на основании запроса пользователя. По каждому запросу (включая оригинальный пользовательский) находятся релевантные фрагменты текста из векторной БД. Далее все результаты ранжируются и объединяются с помощью Reciprocal Rank Fusion (RRF).&lt;/p&gt;
  &lt;figure id=&quot;68Ye&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/6d/84/6d84c93d-b5e1-4259-8b42-dbc8f3e13a3a.png&quot; width=&quot;1872&quot; /&gt;
    &lt;figcaption&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=GchC5WxeXGc&amp;list=PL8motc6AQftn-X1HkaGG9KjmKtWImCKJS&amp;index=12&amp;ab_channel=SamWitteveen&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;W0L0&quot;&gt;RRF (или взаимное слияние рангов) - это метод, который основан на объединении нескольких результатов поиска для получения единого унифицированного рейтинга. Один запрос не может охватить все аспекты запросов пользователей, и он может быть слишком узким для предоставления полных результатов. Вот почему при создании нескольких запросов необходимо учитывать все различные элементы и предоставлять тщательно подобранный ответ, с учетом ранга релевантности каждого фрагмента текста.&lt;/p&gt;
  &lt;p id=&quot;FmTY&quot;&gt;Ниже представлен пример. Четыре retrieval системы выдали отранжированные списки докумнтов. Далее для каждого документа в каждом случае вычисляется его обратный ранг. И после для каждого документа вычисляется финальное значение ранга - оно состоит из обратных сумм, где в знаменателе стоят два слагаемых - &lt;em&gt;k&lt;/em&gt; и &lt;em&gt;r(d)&lt;/em&gt;. &lt;em&gt;k&lt;/em&gt; - постоянный параметр, который обычно равняется 60. &lt;em&gt;r(d)&lt;/em&gt; - вычисленный обратный ранг каждого документа в каждом случае. Далее суммы ранжируются по убыванию и мы получаем финальную выдачу документов по их релевантности. Необязательно подавать все фрагменты в контекст - можно брать top-K наиболее релевантных.&lt;/p&gt;
  &lt;figure id=&quot;8J37&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/72/c1/72c17ae9-287a-4ccb-8bab-0e55b1e65462.png&quot; width=&quot;2074&quot; /&gt;
    &lt;figcaption&gt;&lt;a href=&quot;https://plg.uwaterloo.ca/~gvcormac/cormacksigir09-rrf.pdf&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;BLM4&quot;&gt;Недостатки RAG-Fusion:&lt;/p&gt;
  &lt;ol id=&quot;wPfZ&quot;&gt;
    &lt;li id=&quot;Nhz8&quot;&gt;Существует риск выдачи слишком подробных ответов - в таком случае можно использовать Contextual Compressors &amp;amp; Filters&lt;/li&gt;
    &lt;li id=&quot;wNNR&quot;&gt;Нагрузка на контекстное окно модели&lt;/li&gt;
  &lt;/ol&gt;
  &lt;h3 id=&quot;pvKS&quot;&gt;Multimodal RAG&lt;/h3&gt;
  &lt;p id=&quot;82aQ&quot;&gt;Сегодняшние LLM способны работать с мультимодальными данными, а значит для таких данных тоже необходим свой RAG для уточнения запросов.&lt;/p&gt;
  &lt;p id=&quot;sLia&quot;&gt;Классическим примером где нужен RAG такого типа может быть поломка какой-то бытовой техники. Вы присылаете ее фотографию в LLM (например диодов на пылесосе) и формируете запрос с проблемой и просьбой ее решить. По фотографии и описанию, LLM получит от RAG контекст таких фотографий, который поможет определить модель техники, а также похожие ее проблемы. Фрагменты документов инструкций помогут понять как решить эту проблему - возможно ли починить дома или стоит обратиться в сервисный центр.&lt;/p&gt;
  &lt;p id=&quot;gx6p&quot;&gt;Работает Multimodal RAG аналогично векторному поиску по эмбеддингам текста, только теперь добавляется пространство эмбеддингов фотографий. Вот пример с &lt;a href=&quot;https://www.youtube.com/watch?v=LF7I6raAIL4&amp;ab_channel=GoogleforDevelopers&quot; target=&quot;_blank&quot;&gt;конференции Google&lt;/a&gt; поиска похожих фотографий в векторном пространстве.&lt;/p&gt;
  &lt;figure id=&quot;9Am7&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/e9/3c/e93c7b40-64c0-444f-8673-6450c5ae145f.png&quot; width=&quot;1556&quot; /&gt;
    &lt;figcaption&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=LF7I6raAIL4&amp;ab_channel=GoogleforDevelopers&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;u4Se&quot;&gt;Необходимо добавить, что эмбеддинги текстового описания картинки и самой картинки находятся рядом в векторном пространстве. Использование взаимосвязных эмбеддингов текста и картинки помогает LLM создавать более детальные генерации и уменьшает вероятность галлюцинаций, если БД картинок была тщательно отобрана и в ней не присутствуют асбтракции.&lt;/p&gt;
  &lt;figure id=&quot;GLKy&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/38/f0/38f0bff8-28f2-492b-832d-acfdf23916c5.png&quot; width=&quot;1556&quot; /&gt;
    &lt;figcaption&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=LF7I6raAIL4&amp;ab_channel=GoogleforDevelopers&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;hD7w&quot;&gt;Классический pipeline мультимодального RAG представлен на рисунке ниже. Все как обычно - берем запрос, векторным поиском находим релевантные фрагменты данных (текст и картинки), формируем на основе этого контекста ответ модели.&lt;/p&gt;
  &lt;figure id=&quot;IG5l&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/27/6a/276a6380-d8fd-4d3d-9e9f-5d9c952b7494.png&quot; width=&quot;2074&quot; /&gt;
    &lt;figcaption&gt;&lt;a href=&quot;https://medium.com/@bijit211987/multimodal-retrieval-augmented-generation-mm-rag-2e8f6dc59f11&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;g39s&quot;&gt;Вот примеры генераций LLM на запрос &amp;quot;Imagining a day at the beach&amp;quot;. Видим более детальное и насыщенное описание с использованием Multimodal RAG:&lt;/p&gt;
  &lt;p id=&quot;344d&quot;&gt;&lt;strong&gt;&lt;em&gt;Generated without MM-RAG:&lt;/em&gt; &lt;/strong&gt;I imagine a day at the beach would be very relaxing. I would sit in the sun and listen to the waves crash along the shore. Maybe I would go for a swim or build a sandcastle. It would be nice to get away from normal life for a while and enjoy the peaceful atmosphere.&lt;/p&gt;
  &lt;p id=&quot;6bcd&quot;&gt;&lt;em&gt;&lt;strong&gt;Generated with MM-RAG:&lt;/strong&gt;&lt;/em&gt; I imagine a day at the beach filled with golden sandy shores and the rhythmic crash of bright blue waves lapping gently at the coastline. My toes would sink into smooth sand as I breathed in the fresh and briny sea air under a bright sky, dotted with puffy clouds. I’d love to go snorkeling and glimpse the colorful fish swimming below or just nap on a towel, lulled into a rest by the glittering water and crying seagulls swooping overhead. Maybe later I could crack open a coconut or build an elaborate sandcastle with bridges and moats before taking a long walk at sunset, watching the glowing orange disk sink below the horizon.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;aFY9&quot;&gt;На этом у меня все)&lt;/p&gt;
  &lt;p id=&quot;Gtiv&quot;&gt;Спасибо, что дочитали до конца!&lt;/p&gt;
  &lt;p id=&quot;ZfAP&quot;&gt;Пишите свои мысли в комментарии)&lt;/p&gt;

</content></entry></feed>