Перейти к содержимому
Ступень 11.1 Линейная алгебра

Матрицы и матричное умножение

Матрица как преобразование пространства, умножение матриц как композиция преобразований и линейный слой нейросети как одно матричное умножение плюс сдвиг.

УРОКstage-1/1-1-linear-algebra/02-matrices.mdx1 ч 40 минУрок 2 из 3
После урока вы сможете
  • Умножать матрицу на вектор двумя способами — «строки на вектор» и «комбинация столбцов» — и видеть в матрице преобразование пространства
  • Перемножать матрицы руками и в коде, проверять формы по правилу (m×n)(n×k)→(m×k)(m \times n)(n \times k) \to (m \times k) и оценивать стоимость умножения
  • Объяснить, почему линейный слой — это XW⊤+bXW^\top + b, какую форму имеют веса nn.Linear и почему два линейных слоя без активации равны одному

Если посмотреть, на что уходит время при обучении и работе трансформера, почти всё окажется умножением матриц. Проекции запросов, ключей и значений во внимании (формулу attention вы читали в уроке 1.0.3), оба слоя FFNполносвязный блок (FFN)EN feed-forward network (FFN) (feed-forward network — блок из двух слоёв нейронов, который стоит в каждом слое трансформера), выходной слой со словарём в сотню тысяч токенов — это матричные умножения. Видеокарты потому и стали главным железом ИИ, что умеют делать их невероятно быстро.

В прошлом уроке мы научились работать с векторами. Теперь научимся их преобразовывать. Матрица — это способ одним объектом записать преобразование «вектор → вектор». А линейный слойлинейный (полносвязный) слойEN linear layer (fully connected layer)y = xWᵀ + b. В PyTorch nn.Linear(in, out) хранит weight формы (out, in) — частый источник путаницы. нейросети — это ровно одна матрица плюс сдвиг. В конце урока вы напишете матричное умножение руками и поймёте, что именно делает nn.Linear в PyTorch.

МатрицаматрицаEN matrixПрямоугольная таблица чисел размера m×n. Задаёт линейное преобразование из n-мерного пространства в m-мерное. размера m×nm \times n — это прямоугольная таблица из mm строк и nn столбцов. Размер читается «эм на эн», сначала строки:

A=(123456)∈R2×3A = \begin{pmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{pmatrix} \in \mathbb{R}^{2 \times 3}

Элемент в строке ii и столбце jj обозначают aija_{ij}: здесь a23=6a_{23} = 6. В NumPy форма матрицы — A.shape == (2, 3), а тот же элемент — A[1, 2], потому что в Python счёт идёт с нуля. Эта разница в индексах — классический источник ошибок при переводе формул в код.

Матрицы в LLM огромные. Таблица эмбеддингов DeepSeek-V3 — матрица 129 280×7168129\,280 \times 7168: по строке на каждый токен словаря, всего около 927 млн чисел. Каждый слой содержит ещё несколько матриц весов.

Матрица, умноженная на вектор: два взгляда

Заголовок раздела «Матрица, умноженная на вектор: два взгляда»

Возьмём A=(2013)A = \begin{pmatrix} 2 & 0 \\ 1 & 3 \end{pmatrix} и x=(1,2)\mathbf{x} = (1, 2). Произведение AxA\mathbf{x} можно понимать двумя способами, и оба нужны.

Взгляд 1: строки на вектор. Каждая координата результата — скалярное произведение строки матрицы на x\mathbf{x}:

Ax=(2⋅1+0⋅21⋅1+3⋅2)=(27)A\mathbf{x} = \begin{pmatrix} 2 \cdot 1 + 0 \cdot 2 \\ 1 \cdot 1 + 3 \cdot 2 \end{pmatrix} = \begin{pmatrix} 2 \\ 7 \end{pmatrix}

Так удобно считать. И так удобно думать о нейронах: каждая строка — веса одного нейрона, а mm строк — это mm нейронов, которые смотрят на один и тот же вход.

Взгляд 2: комбинация столбцов. Результат — это линейная комбинация (урок 1.1.1) столбцов матрицы с коэффициентами из x\mathbf{x}: первый столбец берём x1=1x_1 = 1 раз, второй — x2=2x_2 = 2 раза и складываем:

Ax=1⋅(21)+2⋅(03)=(27)A\mathbf{x} = 1 \cdot \begin{pmatrix} 2 \\ 1 \end{pmatrix} + 2 \cdot \begin{pmatrix} 0 \\ 3 \end{pmatrix} = \begin{pmatrix} 2 \\ 7 \end{pmatrix}

Так удобно понимать, что делает матрица. Об этом следующий раздел.

Формы: матрица (m×n)(m \times n) принимает вектор длины nn и возвращает вектор длины mm. Значит, матрица — это функция Rn→Rm\mathbb{R}^n \to \mathbb{R}^m. Если длина вектора не равна числу столбцов, умножать нельзя: NumPy ответит ValueError: matmul: Input operand 1 has a mismatch in its core dimension 0 … (size 2 is different from 3). Сообщение длинное, но главное в нём — в конце: какие размеры не совпали.

Матрица — это преобразование пространства

Заголовок раздела «Матрица — это преобразование пространства»

Возьмём базисные векторы — единичные стрелки вдоль осей e1=(1,0)\mathbf{e}_1 = (1, 0) и e2=(0,1)\mathbf{e}_2 = (0, 1) — и умножим на них матрицу. По «взгляду 2» Ae1A\mathbf{e}_1 — это просто первый столбец AA, а Ae2A\mathbf{e}_2 — второй. Отсюда главная идея урока:

Столбцы матрицы — это то, куда она переводит базисные векторы. Зная, куда попали e1\mathbf{e}_1 и e2\mathbf{e}_2, мы знаем, куда попадёт любой вектор x=x1e1+x2e2\mathbf{x} = x_1 \mathbf{e}_1 + x_2 \mathbf{e}_2.

Такие преобразования называют линейнымилинейное преобразованиеEN linear map (linear transformation)Функция f, для которой f(a + b) = f(a) + f(b) и f(c·a) = c·f(a). Каждое такое преобразование — умножение на матрицу.. Они уважают сложение и умножение на число: A(a+b)=Aa+AbA(\mathbf{a} + \mathbf{b}) = A\mathbf{a} + A\mathbf{b} и A(c a)=c AaA(c\,\mathbf{a}) = c\,A\mathbf{a}. Геометрически это значит, что прямые остаются прямыми (или схлопываются в точку, как при проекции ниже), параллельные — параллельными, а начало координат остаётся на месте.

РастяжениеAe₁ = (2, 0)Ae₂ = (0, 0.5)Поворот на 90°Ae₁ = (0, 1)Ae₂ = (−1, 0)СкосAe₁ = (1, 0)Ae₂ = (1, 1)ПроекцияAe₁ = (1, 0)Ae₂ = (0, 0)
Пунктир — единичный квадрат до преобразования, зелёная фигура — после. Красная и жёлтая стрелки — куда попали базисные векторы e₁ = (1, 0) и e₂ = (0, 1); это и есть столбцы матрицы.
ПреобразованиеМатрицаЧто происходит
Растяжение(2000.5)\begin{pmatrix} 2 & 0 \\ 0 & 0.5 \end{pmatrix}по xx вдвое длиннее, по yy вдвое короче
Поворот на 90°(0−110)\begin{pmatrix} 0 & -1 \\ 1 & 0 \end{pmatrix}всё вращается против часовой стрелки
Скос (shear)(1101)\begin{pmatrix} 1 & 1 \\ 0 & 1 \end{pmatrix}квадрат «съезжает» вбок и становится параллелограммом
Проекция(1000)\begin{pmatrix} 1 & 0 \\ 0 & 0 \end{pmatrix}всё сплющивается на ось xx, информация об yy теряется
Ничего не делать(1001)\begin{pmatrix} 1 & 0 \\ 0 & 1 \end{pmatrix}единичная матрицаединичная матрицаEN identity matrixКвадратная матрица I с единицами на диагонали и нулями вне её; Ix = x. В NumPy — np.eye(n). II: Ix=xI\mathbf{x} = \mathbf{x}

Обратите внимание на проекцию: она «схлопывает» плоскость в линию. Такие матрицы теряют информацию, и в следующем уроке мы научимся это измерять числом — рангом.

Неквадратные матрицы меняют размерность. Матрица 3×23 \times 2 переносит плоскость в трёхмерное пространство, матрица 2×32 \times 3 сплющивает трёхмерное пространство в плоскость. В нейросетях это обычное дело: в FFN-блоке GPT-2 вектор размерности 768 сначала «раздувают» до 3072, а потом сжимают обратно до 768.

Умножение матриц — композиция преобразований

Заголовок раздела «Умножение матриц — композиция преобразований»

Что будет, если применить к вектору сначала преобразование BB, а потом AA? Получится A(Bx)A(B\mathbf{x}). Оказывается, это тоже линейное преобразование, и его матрицу называют произведением ABAB:

(AB) x=A (Bx)(AB)\,\mathbf{x} = A\,(B\mathbf{x})

Читать произведение нужно справа налево: в ABAB сначала действует BB, потом AA — как в композиции функций f(g(x))f(g(x)) из урока 1.0.1.

Матричное умножениематричное умножениеEN matrix multiplication (matmul)(m×n)·(n×k) → (m×k), элемент cᵢⱼ = Σₜ aᵢₜbₜⱼ. Соответствует композиции преобразований. Стоит ≈ 2·m·n·k операций. C=ABC = AB определено, когда число столбцов AA равно числу строк BB. Элемент cijc_{ij} — скалярное произведение ii-й строки AA на jj-й столбец BB:

cij=∑t=1nait btjc_{ij} = \sum_{t=1}^{n} a_{it}\, b_{tj}

Пример:

(123456)(789101112)=(5864139154)\begin{pmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{pmatrix} \begin{pmatrix} 7 & 8 \\ 9 & 10 \\ 11 & 12 \end{pmatrix} = \begin{pmatrix} 58 & 64 \\ 139 & 154 \end{pmatrix}

Левый верхний элемент: первая строка (1,2,3)(1, 2, 3) на первый столбец (7,9,11)(7, 9, 11) — это 7+18+33=587 + 18 + 33 = 58. Остальные три — так же.

Правило форм: (m×n)⋅(n×k)→(m×k)(m \times n) \cdot (n \times k) \to (m \times k). Внутренние размеры совпадают и «сокращаются», внешние остаются. Проверяйте формы раньше, чем значения, — это сэкономит часы отладки:

Что умножаемФормыРезультат
Батч эмбеддингов на веса слоя(32×768)⋅(768×3072)(32 \times 768) \cdot (768 \times 3072)(32×3072)(32 \times 3072)
Матрица на вектор-столбец(m×n)⋅(n×1)(m \times n) \cdot (n \times 1)(m×1)(m \times 1)
Строка на столбец(1×n)⋅(n×1)(1 \times n) \cdot (n \times 1)(1×1)(1 \times 1) — скалярное произведение
Столбец на строку(m×1)⋅(1×n)(m \times 1) \cdot (1 \times n)(m×n)(m \times n) — внешнее произведение
  • Порядок важен: в общем случае AB≠BAAB \ne BA. Возьмём поворот на 90° RR и растяжение по оси xx S=(2001)S = \begin{pmatrix} 2 & 0 \\ 0 & 1 \end{pmatrix}. Вектор e1=(1,0)\mathbf{e}_1 = (1, 0) после RSRS (сначала растянуть, потом повернуть) превращается в (0,2)(0, 2), а после SRSR (сначала повернуть, потом растянуть) — в (0,1)(0, 1). Растяжение по xx уже ничего не делает с вектором, который смотрит вверх.
  • Скобки можно переставлять: (AB)C=A(BC)(AB)C = A(BC). Это свойство называют ассоциативностью. Результат одинаковый, а стоимость — нет (см. ниже).
  • Единичная матрица — «единица» умножения: AI=IA=AAI = IA = A.
import numpy as np
R = np.array([[0, -1], [1, 0]]) # поворот на 90°
S = np.array([[2, 0], [0, 1]]) # растяжение по x
e1 = np.array([1, 0])
print((R @ S) @ e1) # [0 2] — сначала S, потом R
print((S @ R) @ e1) # [0 1] — сначала R, потом S

Каждый из m⋅km \cdot k элементов результата — скалярное произведение длины nn, то есть около 2n2n операций. Итого умножение (m×n)(n×k)(m \times n)(n \times k) стоит примерно 2mnk2mnk операций с плавающей точкой (FLOPs). Пример: один слой FFN GPT-2 (768→3072768 \to 3072) на 1024 токенах — это 2⋅1024⋅768⋅3072≈4.82 \cdot 1024 \cdot 768 \cdot 3072 \approx 4.8 млрд операций.

Из ассоциативности следует важный инженерный трюк. Пусть AA и BB — матрицы 4096×40964096 \times 4096, а x\mathbf{x} — вектор.

  • (AB)x(AB)\mathbf{x}: сначала перемножаем матрицы (2⋅40963≈1372 \cdot 4096^3 \approx 137 млрд операций), потом умножаем на вектор.
  • A(Bx)A(B\mathbf{x}): два умножения матрицы на вектор, 2⋅2⋅40962≈672 \cdot 2 \cdot 4096^2 \approx 67 млн операций.

Ответ одинаковый, а второй путь в 2048 раз дешевле. На перестановке скобок построено «поглощение матриц» в механизме MLA от DeepSeek (урок 5.2.2): там тоже выбирают, в каком порядке перемножать, чтобы не делать лишней работы.

И ещё о скорости. Наивный тройной цикл на чистом Python перемножает две матрицы 200×200200 \times 200 примерно за 1 секунду, а NumPy на одном ядре процессора — примерно за 1 миллисекунду, то есть в тысячу с лишним раз быстрее (замер на машине автора; у вас числа будут другими, но порядок тот же). NumPy вызывает библиотеку BLAS, где умножение матриц оптимизировано под кэши и векторные инструкции процессора. Поэтому в практике ниже вы напишете умножение руками один раз, чтобы понять, а дальше всегда будете использовать @.

ТранспонированиетранспонированиеEN transposeСтроки становятся столбцами — Aᵀ. Полезное правило — (AB)ᵀ = BᵀAᵀ. В NumPy и PyTorch — A.T. меняет строки и столбцы местами: (A⊤)ij=aji(A^\top)_{ij} = a_{ji}, форма (m×n)(m \times n) становится (n×m)(n \times m). В коде — A.T.

(123456) ⁣⊤=(142536)\begin{pmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{pmatrix}^{\!\top} = \begin{pmatrix} 1 & 4 \\ 2 & 5 \\ 3 & 6 \end{pmatrix}

Полезное правило: (AB)⊤=B⊤A⊤(AB)^\top = B^\top A^\top — при транспонировании произведения множители меняются местами. Проверьте на примере выше: (AB)⊤=(5813964154)(AB)^\top = \begin{pmatrix} 58 & 139 \\ 64 & 154 \end{pmatrix}, и B.T @ A.T даёт то же самое.

В статьях векторы обычно считают столбцами (матрицами n×1n \times 1). Тогда скалярное произведение записывают как x⊤y\mathbf{x}^\top \mathbf{y} — строка на столбец, получается 1×11 \times 1. А столбец на строку, xy⊤\mathbf{x}\mathbf{y}^\top, даёт целую матрицу — внешнее произведениевнешнее произведениеEN outer productСтолбец на строку — u vᵀ, матрица ранга 1. В китайских текстах «внешним произведением» иногда называют векторное произведение (cross product), смотрите по контексту.:

(12)(345)=(3456810)\begin{pmatrix} 1 \\ 2 \end{pmatrix} \begin{pmatrix} 3 & 4 & 5 \end{pmatrix} = \begin{pmatrix} 3 & 4 & 5 \\ 6 & 8 & 10 \end{pmatrix}

Все строки этой матрицы пропорциональны друг другу. Такие матрицы «ранга 1» — главные герои следующего урока и формулы градиента в уроке 1.2.3.

Теперь соберём всё вместе. Слой из mm нейронов, каждый со своими весами (строка матрицы WW) и своим смещениемсмещение (bias)EN biasСвободный член b в y = Wx + b. Не путать с «предвзятостью» модели (bias в смысле fairness) и со смещением в bias–variance. bib_i (его ещё называют сдвигом — то самое bb из y=kx+by = kx + b, урок 1.0.1), считает

y=Wx+b,W∈Rm×n,  x∈Rn,  y,b∈Rm\mathbf{y} = W\mathbf{x} + \mathbf{b}, \qquad W \in \mathbb{R}^{m \times n}, \; \mathbf{x} \in \mathbb{R}^{n}, \; \mathbf{y}, \mathbf{b} \in \mathbb{R}^{m}

Это линейный (полносвязный) слой. Числа: пусть W=(100111)W = \begin{pmatrix} 1 & 0 \\ 0 & 1 \\ 1 & 1 \end{pmatrix} (3 нейрона, у каждого 2 входа), b=(0,0,10)\mathbf{b} = (0, 0, 10) и x=(1,2)\mathbf{x} = (1, 2). Строки WW на x\mathbf{x} дают (1,2,3)(1, 2, 3), плюс сдвиг — y=(1,2,13)\mathbf{y} = (1, 2, 13). Этот же пример печатает заготовка задания в конце урока.

В коде векторы обычно лежат строками матрицы. Так удобно обрабатывать сразу батч — пачку из BB примеров, которые модель считает одновременно (на видеокарте это почти так же быстро, как один пример). Батч XX формы (B,n)(B, n) превращается в YY формы (B,m)(B, m) по формуле

Y=XW⊤+bY = X W^\top + \mathbf{b}

Читаем: каждая строка XX (один пример) умножается на W⊤W^\top — это то же «строки WW на вектор», только для всех примеров сразу, — и к каждой строке результата прибавляется b\mathbf{b}. Сдвиг прибавляется к каждой строке благодаря broadcasting (урок 0.3.2). На NumPy:

import numpy as np
W = np.array([[1.0, 0.0], [0.0, 1.0], [1.0, 1.0]]) # (out, in) = (3, 2): строка — веса одного нейрона
b = np.array([0.0, 0.0, 10.0]) # по одному сдвигу на нейрон
X = np.array([[1.0, 2.0], # батч из двух примеров, форма (2, 2)
[3.0, -1.0]])
Y = X @ W.T + b # (2, 2) @ (2, 3) → (2, 3), затем + b к каждой строке
print(Y)
[[ 1. 2. 13.]
[ 3. -1. 12.]]

Так устроен nn.Linear в PyTorch — тот же расчёт, только веса создаются случайными и хранятся внутри слоя:

import torch
torch.manual_seed(0) # фиксируем случайность, как seed в NumPy
layer = torch.nn.Linear(768, 3072) # слой 768 → 3072, как в FFN GPT-2; веса — случайные числа
print(layer.weight.shape) # torch.Size([3072, 768]) — (out, in)!
print(layer.bias.shape) # torch.Size([3072])
# parameters() перебирает все обучаемые массивы слоя (weight и bias), numel() — число элементов в массиве
print(sum(p.numel() for p in layer.parameters())) # 2362368 = 768·3072 + 3072
x = torch.randn(32, 768) # батч из 32 случайных векторов (как rng.standard_normal)
y = layer(x) # вызов слоя как функции: считает x @ W.T + b
print(y.shape) # torch.Size([32, 3072])
print(torch.allclose(y, x @ layer.weight.T + layer.bias, atol=1e-6)) # True — та же формула
X = torch.randn(4, 10, 768) # (батч, токены, признаки)
print(layer(X).shape) # torch.Size([4, 10, 3072]) — слой действует на последнюю ось

В уроке 1.0.1 мы видели, что 2(3x+1)+5=6x+72(3x + 1) + 5 = 6x + 7: композиция прямых — снова прямая. Матричная версия того же факта:

W2(W1x+b1)+b2=(W2W1)⏟W x+(W2b1+b2)⏟bW_2 (W_1 \mathbf{x} + \mathbf{b}_1) + \mathbf{b}_2 = \underbrace{(W_2 W_1)}_{W}\, \mathbf{x} + \underbrace{(W_2 \mathbf{b}_1 + \mathbf{b}_2)}_{\mathbf{b}}

Сколько линейных слоёв ни ставь подряд, получится одна матрица WW и один сдвиг b\mathbf{b}. Проверим:

torch.manual_seed(0)
l1 = torch.nn.Linear(4, 8)
l2 = torch.nn.Linear(8, 3)
W = l2.weight @ l1.weight # (3, 8) @ (8, 4) → (3, 4)
b = l2.weight @ l1.bias + l2.bias # (3,)
x = torch.randn(5, 4)
print(torch.allclose(l2(l1(x)), x @ W.T + b, atol=1e-6)) # True

Поэтому между линейными слоями всегда стоит нелинейность — ReLU, GELU или SiLU (урок 3.1.1). А сами линейные слои делают всю «тяжёлую» работу: именно в их матрицах хранится почти всё, что знает модель.

flowchart LR
X["x, 768 чисел"] --> L1["Linear 768 → 3072<br/>W₁: 3072×768"] --> G["GELU"] --> L2["Linear 3072 → 768<br/>W₂: 768×3072"] --> Y["y, 768 чисел"]
FFN-блок GPT-2: две матрицы и нелинейность между ними

Реализуйте shape, transpose, matmul и linear на чистом Python. Тесты сверят ваш ответ с NumPy на случайных неквадратных матрицах — в них перепутанные индексы сразу дают ошибку.

Матричное умножение руками и линейный слойnumpy · нужен интернет6 тестов
# Матрица — список строк: [[1, 2, 3], [4, 5, 6]] — это 2 строки и 3 столбца.


def shape(A):
    """Форма матрицы: кортеж (число строк, число столбцов).

    Пример: shape([[1, 2, 3], [4, 5, 6]]) → (2, 3).
    """
    # TODO: строк — len(A), столбцов — длина первой строки len(A[0])
    ...


def transpose(A):
    """Транспонирование: строки становятся столбцами. Форма (m, n) → (n, m).

    Пример: transpose([[1, 2, 3], [4, 5, 6]]) → [[1, 4], [2, 5], [3, 6]].
    Элемент [j][i] результата равен A[i][j].
    """
    # TODO: ваш код
    ...


def matmul(A, B):
    """Матричное умножение: (m, n) @ (n, k) → (m, k).

    C[i][j] = сумма по t от A[i][t] * B[t][j] — «строка i на столбец j».
    Если внутренние размеры не совпадают, бросьте ValueError.
    Пример: matmul([[1, 2], [3, 4]], [[5, 6], [7, 8]]) → [[19, 22], [43, 50]]
    (19 = 1·5 + 2·7 — первая строка на первый столбец).
    """
    # TODO: m, n = shape(A); n2, k = shape(B); проверка n == n2; затем двойной цикл по i и j
    ...


def linear(X, W, b):
    """Линейный слой как в PyTorch (nn.Linear): Y = X @ Wᵀ + b.

    X — батч входов формы (batch, in), W — веса формы (out, in), b — список длины out.
    Возвращает матрицу формы (batch, out). Используйте свои transpose и matmul.
    Пример: linear([[1, 2]], [[1, 0], [0, 1], [1, 1]], [0, 0, 10]) → [[1, 2, 13]].
    """
    # TODO: Y = matmul(X, transpose(W)), затем к элементу столбца j каждой строки прибавьте b[j]
    ...


# Проверьте себя кнопкой «Запустить»:
A = [[1, 2, 3], [4, 5, 6]]
B = [[7, 8], [9, 10], [11, 12]]
print(shape(A), transpose(A))   # (2, 3) [[1, 4], [2, 5], [3, 6]]
print(matmul(A, B))             # [[58, 64], [139, 154]]
print(linear([[1, 2]], [[1, 0], [0, 1], [1, 1]], [0, 0, 10]))   # [[1, 2, 13]]

Задание для Jupyter (по желанию). Возьмите своё matmul и замерьте время умножения матриц n×nn \times n для n=50,100,200n = 50, 100, 200 через time.perf_counter(). Во сколько раз растёт время при удвоении nn? Сравните с формулой 2n32n^3 (подсказка: должно получиться около 8 раз). Затем сделайте то же для A @ B в NumPy при n=500,1000,2000n = 500, 1000, 2000.

Проверь себя6 вопросов
1 Батч эмбеддингов формы (32,768)(32, 768) умножают на матрицу формы (768,3072)(768, 3072). Какая форма у результата?
2 Вычислите элемент во второй строке и первом столбце произведения (1234)(5678)\begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} \begin{pmatrix} 5 & 6 \\ 7 & 8 \end{pmatrix}.
3 Какие утверждения верны?Можно выбрать несколько ответов
4 Сколько обучаемых параметров у nn.Linear(512, 2048) (веса и смещение)?
5 Какую форму имеет nn.Linear(768, 3072).weight?
6 AA и BB — матрицы 4096×40964096 \times 4096, x\mathbf{x} — вектор. Как дешевле вычислить ABxAB\mathbf{x}?
  • Матрица m×nm \times n — это линейное преобразование Rn→Rm\mathbb{R}^n \to \mathbb{R}^m. Её столбцы — образы базисных векторов.
  • AxA\mathbf{x} — это и «строки на вектор» (так считать), и «комбинация столбцов» (так понимать).
  • Умножение матриц — композиция преобразований: (AB)x=A(Bx)(AB)\mathbf{x} = A(B\mathbf{x}), формы (m×n)(n×k)→(m×k)(m \times n)(n \times k) \to (m \times k), порядок важен, скобки переставлять можно, стоимость ≈2mnk\approx 2mnk.
  • Линейный слой — Y=XW⊤+bY = XW^\top + \mathbf{b}; в PyTorch веса хранятся как (out, in). Без нелинейности стопка линейных слоёв равна одному слою.

Дальше: ранг и SVD — как понять, сколько информации на самом деле несёт матрица, и как сжать её в десятки раз. На этом стоят LoRA и MLA.