<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Latent State // Журнал</title><description>Технические эссе о машинном обучении, GPU-вычислениях и Kaggle-соревнованиях.</description><link>https://latentstate.dev/ru/</link><language>ru</language><item><title>Умножение матриц: от определения до кэш-линий</title><link>https://latentstate.dev/ru/posts/matrix-multiplication/</link><guid isPermaLink="true">https://latentstate.dev/ru/posts/matrix-multiplication/</guid><description>Одно умножение, три реализации, три порядка величины — измерено на десктопном Skylake и объяснено: кэш-линии, арифметическая интенсивность и идея тайлинга, на которой стоит каждый BLAS и каждое GPU-ядро матричного умножения.</description><pubDate>Tue, 28 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Каждая модель, которую я выводил в продакшен — каждый слой трансформера,
каждая value-голова, каждый речевой энкодер — тратит большую часть своих
FLOP внутри одной операции. Для &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;A&lt;/mi&gt;&lt;mo&gt;∈&lt;/mo&gt;&lt;msup&gt;&lt;mi mathvariant=&quot;double-struck&quot;&gt;R&lt;/mi&gt;&lt;mrow&gt;&lt;mi&gt;m&lt;/mi&gt;&lt;mo&gt;×&lt;/mo&gt;&lt;mi&gt;k&lt;/mi&gt;&lt;/mrow&gt;&lt;/msup&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;A \in \R^{m \times k}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt; и
&lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;B&lt;/mi&gt;&lt;mo&gt;∈&lt;/mo&gt;&lt;msup&gt;&lt;mi mathvariant=&quot;double-struck&quot;&gt;R&lt;/mi&gt;&lt;mrow&gt;&lt;mi&gt;k&lt;/mi&gt;&lt;mo&gt;×&lt;/mo&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;/mrow&gt;&lt;/msup&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;B \in \R^{k \times n}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt; произведение &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;C&lt;/mi&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mi&gt;A&lt;/mi&gt;&lt;mi&gt;B&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;C = AB&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt; определяется поэлементно:&lt;/p&gt;
&lt;span class=&quot;katex-display&quot;&gt;&lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot; display=&quot;block&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;c&lt;/mi&gt;&lt;mrow&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;mi&gt;j&lt;/mi&gt;&lt;/mrow&gt;&lt;/msub&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;munderover&gt;&lt;mo&gt;∑&lt;/mo&gt;&lt;mrow&gt;&lt;mi&gt;p&lt;/mi&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/mrow&gt;&lt;mi&gt;k&lt;/mi&gt;&lt;/munderover&gt;&lt;msub&gt;&lt;mi&gt;a&lt;/mi&gt;&lt;mrow&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;mi&gt;p&lt;/mi&gt;&lt;/mrow&gt;&lt;/msub&gt;&lt;mtext&gt; &lt;/mtext&gt;&lt;msub&gt;&lt;mi&gt;b&lt;/mi&gt;&lt;mrow&gt;&lt;mi&gt;p&lt;/mi&gt;&lt;mi&gt;j&lt;/mi&gt;&lt;/mrow&gt;&lt;/msub&gt;&lt;mo separator=&quot;true&quot;&gt;,&lt;/mo&gt;&lt;mspace width=&quot;2em&quot;&gt;&lt;/mspace&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo&gt;≤&lt;/mo&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;mo&gt;≤&lt;/mo&gt;&lt;mi&gt;m&lt;/mi&gt;&lt;mo separator=&quot;true&quot;&gt;,&lt;/mo&gt;&lt;mtext&gt; &lt;/mtext&gt;&lt;mtext&gt;  &lt;/mtext&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo&gt;≤&lt;/mo&gt;&lt;mi&gt;j&lt;/mi&gt;&lt;mo&gt;≤&lt;/mo&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mi mathvariant=&quot;normal&quot;&gt;.&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;c_{ij} = \sum_{p=1}^{k} a_{ip}\, b_{pj},
\qquad 1 \le i \le m,\ \; 1 \le j \le n.&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;p&gt;Это &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;m&lt;/mi&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mi&gt;k&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;mnk&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt; операций умножения-сложения — &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mi&gt;m&lt;/mi&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mi&gt;k&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;2mnk&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt; операций с плавающей
запятой — и определение дословно переводится в первый matmul, который пишет каждый. Определение —
это три цикла. А производительность — это история про память, и этот пост
спускается по ней до самой кэш-линии.&lt;/p&gt;
&lt;h2 id=&quot;три-способа-потратить-268-миллионов-flop&quot;&gt;Три способа потратить 268 миллионов FLOP&lt;/h2&gt;
&lt;p&gt;При &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;m&lt;/mi&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mi&gt;k&lt;/mi&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mn&gt;512&lt;/mn&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;m = n = k = 512&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt; произведение стоит
&lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mo&gt;⋅&lt;/mo&gt;&lt;msup&gt;&lt;mn&gt;512&lt;/mn&gt;&lt;mn&gt;3&lt;/mn&gt;&lt;/msup&gt;&lt;mo&gt;≈&lt;/mo&gt;&lt;mn&gt;2.7&lt;/mn&gt;&lt;mo&gt;×&lt;/mo&gt;&lt;msup&gt;&lt;mn&gt;10&lt;/mn&gt;&lt;mn&gt;8&lt;/mn&gt;&lt;/msup&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;2 \cdot 512^3 \approx 2.7 \times 10^8&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt; FLOP. Вот определение, дословно —
именно эту функцию замеряет &lt;a href=&quot;/code/matmul_bench.py&quot;&gt;скрипт бенчмарка&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;--shiki-light:#393a34;--shiki-dark:#dbd7caee;--shiki-light-bg:#ffffff;--shiki-dark-bg:#121212; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#AB5959;--shiki-dark:#CB7676&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;--shiki-light:#59873A;--shiki-dark:#80A665&quot;&gt; matmul_naive&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;A&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; B&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; n&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;    C &lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt; [[&lt;/span&gt;&lt;span style=&quot;--shiki-light:#2F798A;--shiki-dark:#4C9A91&quot;&gt;0.0&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;--shiki-light:#AB5959;--shiki-dark:#CB7676&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; n &lt;/span&gt;&lt;span style=&quot;--shiki-light:#1E754F;--shiki-dark:#4D9375&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; _ &lt;/span&gt;&lt;span style=&quot;--shiki-light:#1E754F;--shiki-dark:#4D9375&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;--shiki-light:#998418;--shiki-dark:#B8A965&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;n&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#1E754F;--shiki-dark:#4D9375&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;--shiki-light:#1E754F;--shiki-dark:#4D9375&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;--shiki-light:#998418;--shiki-dark:#B8A965&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;n&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#1E754F;--shiki-dark:#4D9375&quot;&gt;        for&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; j &lt;/span&gt;&lt;span style=&quot;--shiki-light:#1E754F;--shiki-dark:#4D9375&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;--shiki-light:#998418;--shiki-dark:#B8A965&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;n&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;            acc &lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#2F798A;--shiki-dark:#4C9A91&quot;&gt; 0.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#1E754F;--shiki-dark:#4D9375&quot;&gt;            for&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; p &lt;/span&gt;&lt;span style=&quot;--shiki-light:#1E754F;--shiki-dark:#4D9375&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;--shiki-light:#998418;--shiki-dark:#B8A965&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;n&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;                acc &lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; A&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;i&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;--shiki-light:#AB5959;--shiki-dark:#CB7676&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; B&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;j&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;            C&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;i&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;j&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; acc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#1E754F;--shiki-dark:#4D9375&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; C&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;На моём десктопе это занимает &lt;strong&gt;9,3 с&lt;/strong&gt; на списках Python. Очевидная
«оптимизация» — те же три цикла по массивам NumPy со скалярной индексацией
(&lt;code&gt;A[i, p] * B[p, j]&lt;/code&gt;) — занимает &lt;strong&gt;41,9 с&lt;/strong&gt;, в 4,5 раза дольше простых
списков. А &lt;code&gt;A @ B&lt;/code&gt;, уходящий в OpenBLAS, — &lt;strong&gt;5,1 мс&lt;/strong&gt; в один поток. Те же
матрицы, те же 268 миллионов FLOP, ответы совпадают с точностью до
&lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mn&gt;2.3&lt;/mn&gt;&lt;mo&gt;×&lt;/mo&gt;&lt;msup&gt;&lt;mn&gt;10&lt;/mn&gt;&lt;mrow&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;mn&gt;13&lt;/mn&gt;&lt;/mrow&gt;&lt;/msup&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;2.3 \times 10^{-13}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt;:&lt;/p&gt;
&lt;figure class=&quot;fig-light&quot;&gt;&lt;a href=&quot;https://latentstate.dev/ru/posts/matrix-multiplication/&quot;&gt;[рисунок — смотреть на сайте]&lt;/a&gt;&lt;figcaption&gt;рис. 1 — одно умножение, три реализации; обратите внимание на логарифмическую шкалу&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;Две вещи на этом графике требуют объяснения. Скандал — NumPy проигрывает
обычным спискам в 4,5 раза — объясняется просто: каждый &lt;code&gt;A[i, p]&lt;/code&gt;
пересекает границу C-API и упаковывает свежий Python-объект float, около
120 нс накладных расходов на каждое обращение к элементу, 268 миллионов
раз. Контракт NumPy — операции над &lt;em&gt;целыми массивами&lt;/em&gt;; индексируйте его
как список — и вы платите за всю машинерию, ни разу её не задействовав.&lt;/p&gt;
&lt;p&gt;Интересное число — второе: &lt;strong&gt;1800×&lt;/strong&gt; между честными циклами и &lt;code&gt;A @ B&lt;/code&gt;,
притом что OpenBLAS выполняет ровно те &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mi&gt;m&lt;/mi&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mi&gt;k&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;2mnk&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt; FLOP, которых требует
определение, в той же двойной точности. Интерпретатор отвечает примерно за
два порядка из этого разрыва — скомпилированная &lt;code&gt;-O3&lt;/code&gt;-версия того же
тройного цикла обычно выдаёт один-два GFLOP/s на ядре этого класса.
Остальное — множитель в тридцать с лишним, переживающий компиляцию, —
это &lt;em&gt;память&lt;/em&gt;.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;Окружение бенчмарка&lt;/summary&gt;
&lt;p&gt;Intel i7-6700K (Skylake, 4 ядра / 8 потоков, 4,0 ГГц база / 4,2 ГГц турбо
на одном ядре), кэши 32 КБ L1d + 256 КБ L2 на ядро, 8 МБ общего L3,
двухканальная DDR4. Python 3.11.7, NumPy 2.4.2 с scipy-openblas, всюду
float64. BLAS ограничен одним потоком через &lt;code&gt;OPENBLAS_NUM_THREADS=1&lt;/code&gt;,
кроме многопоточных замеров в конце. Два медленных цикла запускаются по
одному разу; все остальные замеры — лучший из 5 с фиксированным сидом.
Результаты сверены с &lt;code&gt;A @ B&lt;/code&gt;: максимальное абсолютное отклонение ≈ 2,3 × 10⁻¹³.&lt;/p&gt;
&lt;/details&gt;
&lt;h2 id=&quot;64-байтовая-правда&quot;&gt;64-байтовая правда&lt;/h2&gt;
&lt;p&gt;CPU никогда не читает из памяти один &lt;code&gt;float64&lt;/code&gt;. Он читает &lt;strong&gt;кэш-линию&lt;/strong&gt; —
64 байта, восемь double — и держит её в иерархии кэшей: на этой машине
32 КБ L1d на ядро, 256 КБ L2, 8 МБ L3 на всех. Загрузка из L1 стоит ~4
такта; поход в DRAM — пару сотен. Всё в быстром численном коде следует из
одного правила: &lt;em&gt;вытащил линию — используй то, что на ней, и переиспользуй
до того, как её вытеснят.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;NumPy хранит матрицы построчно (C-порядок): строка &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;i&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt; непрерывна, а
элемент &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;b&lt;/mi&gt;&lt;mrow&gt;&lt;mi&gt;p&lt;/mi&gt;&lt;mi&gt;j&lt;/mi&gt;&lt;/mrow&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;b_{pj}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt; лежит в 8 байтах после &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;b&lt;/mi&gt;&lt;mrow&gt;&lt;mi&gt;p&lt;/mi&gt;&lt;mo separator=&quot;true&quot;&gt;,&lt;/mo&gt;&lt;mi&gt;j&lt;/mi&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/mrow&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;b_{p,j-1}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt;, но на целую строку
дальше, чем &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;b&lt;/mi&gt;&lt;mrow&gt;&lt;mi&gt;p&lt;/mi&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo separator=&quot;true&quot;&gt;,&lt;/mo&gt;&lt;mi&gt;j&lt;/mi&gt;&lt;/mrow&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;b_{p-1,j}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt;. Теперь посмотрите на внутренний цикл определения:
он идёт по &lt;code&gt;A[i][p]&lt;/code&gt; &lt;em&gt;вдоль&lt;/em&gt; строки — последовательно, восемь полезных
double на каждую линию, паттерн, который аппаратный префетчер распознаёт
и начинает подкачивать данные с опережением, — и по &lt;code&gt;B[p][j]&lt;/code&gt; &lt;em&gt;вниз&lt;/em&gt; по столбцу, прыгая на 4 КБ за шаг.
Каждый шаг этого спуска открывает новую кэш-линию и новую страницу
памяти; один проход по столбцу трогает 512 разных линий (32 КБ трафика
ради 4 КБ полезных данных), и префетчеру не за что зацепиться.&lt;/p&gt;
&lt;p&gt;Эффект легко изолировать вообще без matmul. Возьмём одну матрицу
8192×8192 — 537 МБ, сильно больше любого кэша — и просуммируем её дважды:
раз по строкам, раз по столбцам:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;--shiki-light:#393a34;--shiki-dark:#dbd7caee;--shiki-light-bg:#ffffff;--shiki-dark-bg:#121212; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;M &lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; rng&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;standard_normal&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;--shiki-light:#2F798A;--shiki-dark:#4C9A91&quot;&gt;8192&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#2F798A;--shiki-dark:#4C9A91&quot;&gt; 8192&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;s &lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#2F798A;--shiki-dark:#4C9A91&quot;&gt; 0.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#1E754F;--shiki-dark:#4D9375&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;--shiki-light:#1E754F;--shiki-dark:#4D9375&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;--shiki-light:#998418;--shiki-dark:#B8A965&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#2F798A;--shiki-dark:#4C9A91&quot;&gt;8192&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;):&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; s &lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; M&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;i&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt; :].&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;sum&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;--shiki-light:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;   # по строкам:    44 мс&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;s &lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#2F798A;--shiki-dark:#4C9A91&quot;&gt; 0.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#1E754F;--shiki-dark:#4D9375&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; j &lt;/span&gt;&lt;span style=&quot;--shiki-light:#1E754F;--shiki-dark:#4D9375&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;--shiki-light:#998418;--shiki-dark:#B8A965&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#2F798A;--shiki-dark:#4C9A91&quot;&gt;8192&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;):&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; s &lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; M&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;[:,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; j&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;].&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;sum&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;--shiki-light:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;   # по столбцам: 597 мс&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Те же байты, те же сложения — &lt;strong&gt;разница в 13,6 раза&lt;/strong&gt;. Проход по строкам
прокачивает целые кэш-линии вслед за префетчером. Проход по столбцам
использует один double с каждой открытой линии за проход, а его шаг в
64 КБ трогает 8192 разных страницы памяти на столбец — сильно больше, чем
вмещает TLB, так что обращения платят за обход таблиц страниц
поверх кэш-промахов. (Соседние столбцы позже добирают остальные семь
double каждой линии из L3 — потому штраф 13,6×, а не хуже.) Проход по
строкам перекачивает 537 МБ за 44 мс — около &lt;strong&gt;12,2 ГБ/с&lt;/strong&gt;, практическая
пропускная способность чтения этого ядра. Запомните это число.&lt;/p&gt;
&lt;h2 id=&quot;арифметическая-интенсивность-или-почему-наивный-цикл-не-может-быть-быстрым&quot;&gt;Арифметическая интенсивность, или почему наивный цикл не может быть быстрым&lt;/h2&gt;
&lt;p&gt;Ядро ограничено двумя потолками: как быстро оно считает и как быстро его
кормят. Это ядро на 4,2 ГГц с двумя 256-битными FMA-портами упирается в&lt;/p&gt;
&lt;span class=&quot;katex-display&quot;&gt;&lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot; display=&quot;block&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mn&gt;4.2&lt;/mn&gt;&lt;mtext&gt; ГГц&lt;/mtext&gt;&lt;mo&gt;×&lt;/mo&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mtext&gt; &lt;/mtext&gt;&lt;mstyle scriptlevel=&quot;0&quot; displaystyle=&quot;false&quot;&gt;&lt;mfrac&gt;&lt;mtext&gt;FMA&lt;/mtext&gt;&lt;mtext&gt;такт&lt;/mtext&gt;&lt;/mfrac&gt;&lt;/mstyle&gt;&lt;mo&gt;×&lt;/mo&gt;&lt;mn&gt;4&lt;/mn&gt;&lt;mtext&gt; &lt;/mtext&gt;&lt;mstyle scriptlevel=&quot;0&quot; displaystyle=&quot;false&quot;&gt;&lt;mfrac&gt;&lt;mtext&gt;double&lt;/mtext&gt;&lt;mtext&gt;FMA&lt;/mtext&gt;&lt;/mfrac&gt;&lt;/mstyle&gt;&lt;mo&gt;×&lt;/mo&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mtext&gt; &lt;/mtext&gt;&lt;mstyle scriptlevel=&quot;0&quot; displaystyle=&quot;false&quot;&gt;&lt;mfrac&gt;&lt;mtext&gt;FLOP&lt;/mtext&gt;&lt;mtext&gt;double&lt;/mtext&gt;&lt;/mfrac&gt;&lt;/mstyle&gt;&lt;mtext&gt;  &lt;/mtext&gt;&lt;mo&gt;≈&lt;/mo&gt;&lt;mtext&gt;  &lt;/mtext&gt;&lt;mn&gt;67&lt;/mn&gt;&lt;mtext&gt; &lt;/mtext&gt;&lt;mstyle scriptlevel=&quot;0&quot; displaystyle=&quot;false&quot;&gt;&lt;mfrac&gt;&lt;mtext&gt;GFLOP&lt;/mtext&gt;&lt;mtext&gt;s&lt;/mtext&gt;&lt;/mfrac&gt;&lt;/mstyle&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;4.2\ \text{ГГц} \times 2\ \tfrac{\text{FMA}}{\text{такт}} \times 4\ \tfrac{\text{double}}{\text{FMA}} \times 2\ \tfrac{\text{FLOP}}{\text{double}}
\;\approx\; 67\ \tfrac{\text{GFLOP}}{\text{s}}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;p&gt;в двойной точности — но выдерживает лишь ~12,2 ГБ/с чтения. Какой потолок
действует, решает &lt;strong&gt;арифметическая интенсивность&lt;/strong&gt;: FLOP на каждый
перемещённый байт.&lt;/p&gt;
&lt;p&gt;Наивный цикл — на размерах, где операнды переросли кэши, — прогоняет оба
операнда через ядро по разу на каждое использование: два 8-байтовых чтения
на умножить-сложить, интенсивность &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;q&lt;/mi&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mstyle scriptlevel=&quot;0&quot; displaystyle=&quot;false&quot;&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mtext&gt; FLOP&lt;/mtext&gt;&lt;/mrow&gt;&lt;mrow&gt;&lt;mn&gt;16&lt;/mn&gt;&lt;mtext&gt; Б&lt;/mtext&gt;&lt;/mrow&gt;&lt;/mfrac&gt;&lt;/mstyle&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mn&gt;0.125&lt;/mn&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;q = \tfrac{2\ \text{FLOP}}{16\ \text{Б}}
= 0.125&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt;. При 12,2 ГБ/с это ограничивает даже &lt;em&gt;идеально векторизованный&lt;/em&gt;
цикл с таким паттерном доступа потолком примерно в
&lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mn&gt;0.125&lt;/mn&gt;&lt;mo&gt;×&lt;/mo&gt;&lt;mn&gt;12.2&lt;/mn&gt;&lt;mo&gt;≈&lt;/mo&gt;&lt;mn&gt;1.5&lt;/mn&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;0.125 \times 12.2 \approx 1.5&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt; GFLOP/s — в сорок с лишним раз ниже
вычислительного пика, ещё до единого такта интерпретатора. Этот потолок
компиляция не поднимает: цикл &lt;em&gt;сам по себе&lt;/em&gt; упёрт в память по построению.&lt;/p&gt;
&lt;p&gt;Чтобы стать быстрее, не нужно меньше FLOP. Нужно больше FLOP &lt;strong&gt;на байт&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;блочное-разбиение-идея-m&quot;&gt;Блочное разбиение: идея √M&lt;/h2&gt;
&lt;p&gt;Решению не один десяток лет, и на нём до сих пор держится каждый BLAS и
каждое GPU-ядро матричного умножения: &lt;strong&gt;разрежь задачу на тайлы так, чтобы
небольшое рабочее множество жило в кэше и переиспользовалось до
вытеснения&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Режем матрицы на тайлы &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;b&lt;/mi&gt;&lt;mo&gt;×&lt;/mo&gt;&lt;mi&gt;b&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;b \times b&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt; и накапливаем каждый тайл &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;C&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;C&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt; как
сумму маленьких тайловых произведений:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;--shiki-light:#393a34;--shiki-dark:#dbd7caee;--shiki-light-bg:#ffffff;--shiki-dark-bg:#121212; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#1E754F;--shiki-dark:#4D9375&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; i0 &lt;/span&gt;&lt;span style=&quot;--shiki-light:#1E754F;--shiki-dark:#4D9375&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;--shiki-light:#998418;--shiki-dark:#B8A965&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#2F798A;--shiki-dark:#4C9A91&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; n&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; b&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#1E754F;--shiki-dark:#4D9375&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; j0 &lt;/span&gt;&lt;span style=&quot;--shiki-light:#1E754F;--shiki-dark:#4D9375&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;--shiki-light:#998418;--shiki-dark:#B8A965&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#2F798A;--shiki-dark:#4C9A91&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; n&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; b&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;        # этот тайл C переиспользуется весь p-цикл&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#1E754F;--shiki-dark:#4D9375&quot;&gt;        for&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; p0 &lt;/span&gt;&lt;span style=&quot;--shiki-light:#1E754F;--shiki-dark:#4D9375&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;--shiki-light:#998418;--shiki-dark:#B8A965&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#2F798A;--shiki-dark:#4C9A91&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; n&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; b&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;            C&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;i0&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;i0&lt;/span&gt;&lt;span style=&quot;--shiki-light:#AB5959;--shiki-dark:#CB7676&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; j0&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;j0&lt;/span&gt;&lt;span style=&quot;--shiki-light:#AB5959;--shiki-dark:#CB7676&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt; +=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; A&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;i0&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;i0&lt;/span&gt;&lt;span style=&quot;--shiki-light:#AB5959;--shiki-dark:#CB7676&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; p0&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;p0&lt;/span&gt;&lt;span style=&quot;--shiki-light:#AB5959;--shiki-dark:#CB7676&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;--shiki-light:#AB5959;--shiki-dark:#CB7676&quot;&gt; @&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; B&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;p0&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;p0&lt;/span&gt;&lt;span style=&quot;--shiki-light:#AB5959;--shiki-dark:#CB7676&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; j0&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;j0&lt;/span&gt;&lt;span style=&quot;--shiki-light:#AB5959;--shiki-dark:#CB7676&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;--shiki-light:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;--shiki-light:#999999;--shiki-dark:#666666&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Один шаг внутреннего цикла трогает три тайла &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;b&lt;/mi&gt;&lt;mo&gt;×&lt;/mo&gt;&lt;mi&gt;b&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;b \times b&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt; —
&lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mn&gt;3&lt;/mn&gt;&lt;msup&gt;&lt;mi&gt;b&lt;/mi&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/msup&gt;&lt;mo&gt;×&lt;/mo&gt;&lt;mn&gt;8&lt;/mn&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;3b^2 \times 8&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt; байт — и выполняет над ними &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;msup&gt;&lt;mi&gt;b&lt;/mi&gt;&lt;mn&gt;3&lt;/mn&gt;&lt;/msup&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;2b^3&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt; FLOP:&lt;/p&gt;
&lt;span class=&quot;katex-display&quot;&gt;&lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot; display=&quot;block&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;q&lt;/mi&gt;&lt;mo stretchy=&quot;false&quot;&gt;(&lt;/mo&gt;&lt;mi&gt;b&lt;/mi&gt;&lt;mo stretchy=&quot;false&quot;&gt;)&lt;/mo&gt;&lt;mtext&gt;  &lt;/mtext&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mtext&gt;  &lt;/mtext&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;msup&gt;&lt;mi&gt;b&lt;/mi&gt;&lt;mn&gt;3&lt;/mn&gt;&lt;/msup&gt;&lt;/mrow&gt;&lt;mrow&gt;&lt;mn&gt;24&lt;/mn&gt;&lt;mtext&gt; &lt;/mtext&gt;&lt;msup&gt;&lt;mi&gt;b&lt;/mi&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/msup&gt;&lt;/mrow&gt;&lt;/mfrac&gt;&lt;mtext&gt; &lt;/mtext&gt;&lt;mstyle scriptlevel=&quot;0&quot; displaystyle=&quot;false&quot;&gt;&lt;mfrac&gt;&lt;mtext&gt;FLOP&lt;/mtext&gt;&lt;mtext&gt;Б&lt;/mtext&gt;&lt;/mfrac&gt;&lt;/mstyle&gt;&lt;mtext&gt;  &lt;/mtext&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mtext&gt;  &lt;/mtext&gt;&lt;mfrac&gt;&lt;mi&gt;b&lt;/mi&gt;&lt;mn&gt;12&lt;/mn&gt;&lt;/mfrac&gt;&lt;mtext&gt; &lt;/mtext&gt;&lt;mstyle scriptlevel=&quot;0&quot; displaystyle=&quot;false&quot;&gt;&lt;mfrac&gt;&lt;mtext&gt;FLOP&lt;/mtext&gt;&lt;mtext&gt;Б&lt;/mtext&gt;&lt;/mfrac&gt;&lt;/mstyle&gt;&lt;mo separator=&quot;true&quot;&gt;,&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;q(b) \;=\; \frac{2b^3}{24\, b^2}\ \tfrac{\text{FLOP}}{\text{Б}} \;=\; \frac{b}{12}\ \tfrac{\text{FLOP}}{\text{Б}},&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;p&gt;— интенсивность, которая &lt;em&gt;растёт с размером тайла&lt;/em&gt;. Чтобы поднять это ядро
из memory-bound в compute-bound, нужно &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;q&lt;/mi&gt;&lt;mo&gt;≳&lt;/mo&gt;&lt;mn&gt;67&lt;/mn&gt;&lt;mi mathvariant=&quot;normal&quot;&gt;/&lt;/mi&gt;&lt;mn&gt;12.2&lt;/mn&gt;&lt;mo&gt;≈&lt;/mo&gt;&lt;mn&gt;5.5&lt;/mn&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;q \gtrsim 67 / 12.2 \approx 5.5&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt;,
то есть &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;b&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;b&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt; около семидесяти — а три тайла 70×70 в float64 занимают
118 КБ и спокойно помещаются в 256 КБ L2. В этом весь трюк, и именно
поэтому ёмкость кэша &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;M&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;M&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt; по всей этой литературе ходит под квадратным
корнем: тайлы со стороной &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;b&lt;/mi&gt;&lt;mo&gt;∼&lt;/mo&gt;&lt;msqrt&gt;&lt;mrow&gt;&lt;mi&gt;M&lt;/mi&gt;&lt;mi mathvariant=&quot;normal&quot;&gt;/&lt;/mi&gt;&lt;mn&gt;3&lt;/mn&gt;&lt;/mrow&gt;&lt;/msqrt&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;b \sim \sqrt{M/3}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt; срезают суммарный трафик с
наивных &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi mathvariant=&quot;script&quot;&gt;O&lt;/mi&gt;&lt;mo stretchy=&quot;false&quot;&gt;(&lt;/mo&gt;&lt;msup&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mn&gt;3&lt;/mn&gt;&lt;/msup&gt;&lt;mo stretchy=&quot;false&quot;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;\mathcal{O}(n^3)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt; слов до&lt;/p&gt;
&lt;span class=&quot;katex-display&quot;&gt;&lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot; display=&quot;block&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi mathvariant=&quot;script&quot;&gt;O&lt;/mi&gt;&lt;mtext&gt; ⁣&lt;/mtext&gt;&lt;mrow&gt;&lt;mo fence=&quot;true&quot;&gt;(&lt;/mo&gt;&lt;mfrac&gt;&lt;msup&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mn&gt;3&lt;/mn&gt;&lt;/msup&gt;&lt;msqrt&gt;&lt;mi&gt;M&lt;/mi&gt;&lt;/msqrt&gt;&lt;/mfrac&gt;&lt;mo fence=&quot;true&quot;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;mo separator=&quot;true&quot;&gt;,&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;\mathcal{O}\!\left(\frac{n^3}{\sqrt{M}}\right),&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;p&gt;что, как доказали Хонг и Кунг в 1981 году, асимптотически оптимально для
любого порядка выполнения классического алгоритма. Больше кэш — меньше трафик, причём
выигрыш растёт как квадратный корень из ёмкости.&lt;/p&gt;
&lt;h2 id=&quot;что-добавляет-настоящий-blas&quot;&gt;Что добавляет настоящий BLAS&lt;/h2&gt;
&lt;p&gt;OpenBLAS — это та же идея, исполненная с одержимостью: размеры тайлов
подобраны под каждый уровень кэша, &lt;em&gt;packing&lt;/em&gt; операндов (каждый тайл
копируется в непрерывный выровненный буфер, так что даже спуск по &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;B&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;B&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt;
превращается в последовательный стриминг линия за линией) и внутреннее
регистровое микроядро, которое держит маленький блок &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;C&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;C&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt; целиком в
векторных регистрах, пока через него идёт поток FMA. Результат умещается в один
график:&lt;/p&gt;
&lt;figure class=&quot;fig-light&quot;&gt;&lt;a href=&quot;https://latentstate.dev/ru/posts/matrix-multiplication/&quot;&gt;[рисунок — смотреть на сайте]&lt;/a&gt;&lt;figcaption&gt;рис. 2 — однопоточный A @ B по размерам; плоская линия — и есть вся суть&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Плоско.&lt;/strong&gt; От &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mn&gt;256&lt;/mn&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;n = 256&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt;, где все три матрицы помещаются в L3, до
&lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mn&gt;4096&lt;/mn&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;n = 4096&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt;, где 268 МБ операндов (400 МБ вместе с результатом) живут в
DRAM, производительность держится на 51–54 GFLOP/s — 79% теоретического
пика одного ядра на самом большом размере — потому что блочное разбиение
делает отношение FLOP на байт свойством &lt;em&gt;тайла&lt;/em&gt;, а не размера задачи. Цикл из
определения тратит всё больше времени на каждый FLOP всякий раз, когда
матрицы перерастают очередной уровень кэша; блочному циклу всё равно.&lt;/p&gt;
&lt;p&gt;Две сноски с той же машины, обе измеренные. Первая — параллелизм: при
привязке к четырём физическим ядрам &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mn&gt;4096&lt;/mn&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;n = 4096&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt; выполняется на
&lt;strong&gt;179 GFLOP/s&lt;/strong&gt; — ускорение в 3,4 раза; недобор до 4× — это в основном all-core turbo ниже
одноядерного, плюс делёж L3 и каналов DRAM. Оставленный с настройками по
умолчанию, OpenBLAS поднимает на этой машине восемь потоков — по одному на
&lt;em&gt;гиперпоток&lt;/em&gt; — и проседает до 118 GFLOP/s: два SMT-соседа делят FMA-конвейеры
одного физического ядра, так что лишние потоки добавляют накладные расходы
на планирование и ноль вычислений. Считайте физические ядра. Вторая — 79% пика:
это уровень, на который зрелый BLAS выходит на этой микроархитектуре в
float64; недостающая пятая часть уходит на трафик упаковки, края тайлов и
накладные расходы на организацию циклов. Сто процентов не получает никто.&lt;/p&gt;
&lt;h2 id=&quot;та-же-идея--на-всех-этажах&quot;&gt;Та же идея — на всех этажах&lt;/h2&gt;
&lt;p&gt;Замените «L2» на «shared memory» — и это лекция по CUDA: GPU-ядро matmul
складывает тайлы &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;A&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;A&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt; и &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;B&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;B&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt; в разделяемую память каждого SM,
синхронизируется, умножает, сдвигается — тот же аргумент &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msqrt&gt;&lt;mi&gt;M&lt;/mi&gt;&lt;/msqrt&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;\sqrt{M}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt;,
исполняемый тысячами потоков. Тензорные ядра переносят тайл в сам
тракт данных: варп подаёт фрагменты фиксированного размера, и железо
выполняет каждое маленькое плотное произведение одной инструкцией. Этажом выше слой
внимания или MLP-блок — это &lt;em&gt;батч&lt;/em&gt; таких произведений, и потому
арифметическая интенсивность — теперь чаще в формулировке «FLOP на байт
весов» — по-прежнему решает, будет ваш трансформер compute-bound или
bandwidth-bound на H100.&lt;/p&gt;
&lt;p&gt;Определение — это три цикла, по существу неизменные с тех пор, как Бине
записал правило «строка на столбец» в 1812 году. Всё, что лежит между 9,3
секунды и 5,1 миллисекунды, — это знание о том, где ваши кэш-линии.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;Воспроизвести&lt;/summary&gt;
&lt;p&gt;&lt;a href=&quot;/code/matmul_bench.py&quot;&gt;&lt;code&gt;matmul_bench.py&lt;/code&gt;&lt;/a&gt; — ~130 строк Python со
стандартной библиотекой и NumPy: два медленных цикла (по одному запуску),
прогон BLAS по размерам матриц и демо обхода (лучший из 5), многопоточные замеры —
всё с фиксированным сидом. Скрипт замеряет ровно те функции, что показаны
выше. Абсолютные числа на вашей машине будут другими; соотношения — нет.
Запустите, прежде чем верить мне на слово.&lt;/p&gt;
&lt;/details&gt;</content:encoded></item></channel></rss>