Матричное умножение в линейной алгебре и машинном обучении. Часть 2 из 5

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


9. Матричное умножение в линейной алгебре и машинном обучении

9.1. Линейные преобразования и координаты

Одна из главных причин, почему матричное умножение так важно, состоит в его смысле: матрица может представлять линейное преобразование. Если вектор описывает координаты или признаки, то умножение матрицы на вектор переводит эти данные в новое пространство.

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

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

9.2. Системы уравнений, разложения и проекции

В численных методах матричное умножение встречается не только в виде прямой операции. Оно появляется внутри разложений, уточнений, проекций, обновления базисов и решения систем уравнений.

Например, в задачах наименьших квадратов часто работают с произведениями вида AᵀA или Aᵀb. В проекционных методах результат может быть связан с умножением на матрицу проектора. В итеративных методах решения систем основное время уходит на умножение матрицы на вектор или на блоки векторов.

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

Для глубокого погружения в эту тему обычно ориентируются на классические книги: Джин Голуб и Чарльз Ван Лоун, «Матричные вычисления», первое издание 1983 года, четвертое издание 2013 года; Николас Хайам, «Точность и устойчивость численных алгоритмов», 1996 год; Джеймс Деммель, «Прикладная численная линейная алгебра», 1997 год. Эти работы полезны тем, что показывают связь между математической постановкой, устойчивостью и реальной вычислительной ценой.

9.3. Обучение нейросетей и прямое распространение

В машинном обучении матричное умножение стало одной из основных операций. Почти каждый полносвязный слой можно представить как умножение входных данных на матрицу весов с последующим добавлением смещения и применением нелинейности.

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

Выход = Вход * Веса + Смещение

Если пакет большой, то большая часть вычислений в слое — это именно матричное умножение. Поэтому ускорение такой операции напрямую влияет на скорость обучения и инференса.

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

9.4. Механизм внимания и трансформеры

В трансформерах матричное умножение занимает особое место. Механизм внимания включает умножение запросов на ключи, применение масштаба и функции нормировки, затем умножение весов внимания на значения.

Упрощенно это можно представить так:

  1. Получаются запросы, ключи и значения.
  2. Запросы умножаются на транспонированные ключи.
  3. Результат масштабируется и нормируется.
  4. Полученные веса умножаются на значения.
  5. Результат проходит через проекцию.

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

9.5. Рекомендательные системы, графы и поиск

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

В графовых задачах умножение матрицы смежности на вектор или на другую матрицу описывает распространение информации по графу. Один шаг обхода соседей можно представить как матрично-векторное умножение. Несколько шагов — как произведение матриц или последовательность разреженных умножений.

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

Неочевидный факт: след произведения матриц обладает циклической инвариантностью. Если произведение определено и размеры согласованы, то след AB равен следу BA. Для нескольких матриц след можно циклически переставлять. Это свойство используется в выводах градиентов, статистике и некоторых оптимизациях, хотя на уровне обычного кода его редко замечают.

Еще один важный факт: если нужно перемножить цепочку из нескольких матриц, порядок расстановки скобок может радикально изменить число операций. Например, есть матрицы размером 10 на 100, 100 на 5 и 5 на 50. Если сначала умножить первые две, а затем результат на третью, потребуется примерно 7500 базовых операций умножения. Если сначала умножить вторую и третью, а затем первую на результат, потребуется уже около 75000 операций. Разница в десять раз при одних и тех же исходных данных.


10. Производительность: от формулы к реальному времени

10.1. Что такое FLOPS и почему его нельзя понимать буквально

FLOPS — это количество операций с плавающей точкой в секунду. Часто говорят, что устройство имеет определенную пиковую производительность в терафлопсах. Но для матричного умножения эта цифра сама по себе почти ничего не гарантирует.

Причины:

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

Полезнее думать не «сколько терафлопс написано в характеристике», а «какую устойчивую производительность я получу на своих размерах, формате и библиотеке».

10.2. Пиковая и устойчивая производительность

Пиковая производительность — это теоретический максимум при идеальных условиях. Устойчивая производительность — то, что реально удается удерживать на достаточно длинной нагрузке.

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

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

10.3. Размеры матриц и эффект краевых случаев

Размер матриц сильно влияет на поведение программы. Маленькие матрицы часто страдают от накладных расходов: вызов функции, подготовка данных, запуск потоков, передача данных на ускоритель. Большие матрицы начинают упираться в память и энергопотребление.

Есть несколько типичных режимов:

  1. Очень маленькие матрицы, например 4 на 4 или 16 на 16. Здесь важна низкая задержка, а не максимальная пропускная способность. Запуск сложного параллельного ядра может быть дороже самого вычисления.
  2. Средние матрицы, например сотни на сотни. Здесь уже можно получить пользу от блокировки и векторных инструкций, но кэш все еще очень важен.
  3. Большие матрицы, тысячи на тысячи. Здесь основной вопрос — как организовать память и параллелизм. Именно в этом режиме хорошо видна разница между удачной и неудачной реализацией.
  4. Очень большие матрицы, десятки тысяч на десятки тысяч. Здесь важны распределенные вычисления, эффективная память, планирование ресурсов и контроль перегрева или энергопотребления.

Для оценки объема работы удобно помнить простую формулу: количество операций с плавающей точкой для плотного умножения примерно равно 2 * m * n * p. Если матрицы квадратные размером 4096 на 4096, получается около 137 миллиардов операций. На устройстве с устойчивой производительностью 100 гигафлопс это займет примерно 1,37 секунды. На устройстве с устойчивой производительностью 1 терафлопс — примерно 0,137 секунды. Эти цифры очень грубые, но они помогают почувствовать масштаб.

10.4. Влияние выравнивания и формата данных

Даже если алгоритм хороший, данные могут быть неудобными. Выравнивание памяти, шаг между строками, наличие дополнительных полей, транспонирование, упаковка блоков — все это влияет на скорость.

Неудачные примеры:

  • матрица хранится с большим отступом между строками, из-за чего кэш используется хуже;
  • данные постоянно транспонируются перед операцией, хотя можно было выбрать другой порядок хранения;
  • размеры не кратны удобной ширине векторных инструкций;
  • один и тот же фрагмент данных копируется несколько раз без необходимости;
  • библиотека получает указатели, которые не может безопасно оптимизировать.

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

10.5. Профилирование и поиск настоящего узкого места

Без профилирования оптимизация часто превращается в гадание. Полезно измерять не только общее время, но и косвенные признаки:

  • загрузку вычислительных блоков;
  • пропускную способность памяти;
  • количество кэш-промахов;
  • время передачи данных между устройством и ускорителем;
  • задержку запуска ядер;
  • использование векторных или тензорных блоков;
  • конкуренцию потоков за память.

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

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


11. Библиотеки и готовые реализации

11.1. Уровень базовых подпрограмм: BLAS и его роль

В вычислительной математике давно сложился подход: основные плотные матричные операции реализуются в виде набора стандартных подпрограмм. Такой набор часто называют BLAS. Он делится на уровни по типу операций.

Первый уровень обычно охватывает операции над векторами: скалярные произведения, сложения, масштабирования. Второй уровень — операции матрицы на вектор. Третий уровень — операции матрицы на матрицу, включая то самое плотное матричное умножение, часто обозначаемое как GEMM.

GEMM расшифровывается как общая операция умножения матриц с возможностью сложения и масштабирования. В практическом виде она часто вычисляет выражение:

C = alpha * Op(A) * Op(B) + beta * C

Здесь Op означает возможность использовать матрицу как есть или транспонированно, alpha и beta — скалярные коэффициенты. Именно такая форма часто нужна в линейной алгебре и машинном обучении.

11.2. Открытые реализации: OpenBLAS, BLIS и аналоги

Существует несколько реализаций матричных операций с открытым кодом. Они различаются архитектурной поддержкой, качеством автонастройки, удобством сборки и поведением на разных процессорах.

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

При выборе стоит смотреть на:

  • поддержку конкретной архитектуры;
  • качество многопоточности;
  • работу с разными форматами точности;
  • возможность сборки под конкретный процессор;
  • стабильность версий;
  • наличие тестов и сопровождения;
  • совместимость с используемым фреймворком.

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

11.3. Коммерческие и платформенные библиотеки

Помимо открытых решений, существуют библиотеки, поставляемые производителями оборудования или платформ. Они часто хорошо настроены под конкретные процессоры, наборы инструкций и операционные системы.

Их сильные стороны:

  • глубокая оптимизация под конкретные микроархитектуры;
  • поддержка редких режимов работы;
  • стабильные интерфейсы;
  • интеграция с фирменными инструментами профилирования;
  • иногда более предсказуемое поведение на корпоративных системах.

Слабые стороны:

  • привязка к экосистеме;
  • возможные ограничения по лицензированию;
  • меньшая гибкость при переносе на другие платформы;
  • риск, что старая версия будет плохо работать на новом оборудовании.

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

11.4. Библиотеки для видеокарт и ускорителей

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

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

Для больших пакетных операций видеокарты часто выигрывают. Но если нагрузка состоит из множества мелких, нерегулярных или сильно ветвящихся задач, преимущество может исчезнуть.

11.5. Когда писать собственное умножение матриц оправданно

Писать собственную реализацию матричного умножения имеет смысл только при веской причине. Например:

  • нужен особый формат хранения;
  • операция должна быть объединена с последующей обработкой в одном проходе;
  • есть жесткие ограничения по памяти или задержке;
  • требуется детерминированное побитовое поведение результата;
  • используется нестандартное оборудование;
  • стандартные библиотеки не поддерживают нужную точность или форму данных;
  • нужно встроить минимальную версию без внешних зависимостей.

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


12. Центральный процессор против видеокарты против специализированных ускорителей

12.1. Сильные стороны центрального процессора

Центральный процессор хорош там, где нужна гибкость. Он лучше справляется с нерегулярным доступом к данным, ветвлениями, сложной логикой и небольшими задачами, где запуск ускорителя не окупается.

Сильные стороны процессора:

  • низкие задержки при одиночных операциях;
  • развитая система кэшей;
  • удобная работа с небольшими матрицами;
  • хорошая поддержка разреженных и нерегулярных структур;
  • меньше накладных расходов на передачу данных, если данные уже в основной памяти;
  • проще отладка и воспроизводимость во многих сценариях.

Для маленьких матриц, например в интерфейсах, физических расчетах с малыми объектами, локальных геометрических преобразованиях или быстрых прототипах, процессор часто оказывается удобнее и быстрее.

12.2. Сильные стороны видеокарты

Видеокарта создавалась для массовой параллельной обработки данных. Поэтому она хорошо подходит для регулярных операций над большими массивами чисел.

Сильные стороны видеокарты:

  • высокая суммарная пропускная способность памяти;
  • большое количество параллельных вычислительных блоков;
  • эффективная работа с регулярными плотными матрицами;
  • поддержка специальных блоков для матричных операций;
  • хорошая масштабируемость на больших пакетах данных.

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

12.3. Почему видеокарты часто выигрывают в больших GEMM

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

К этому добавляется архитектурная особенность: видеокарта может одновременно держать тысячи потоков, если они выполняют достаточно однородную работу. Матричное умножение как раз поддается такому распараллеливанию, если данные правильно разбиты на блоки.

Но есть ограничения:

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

Поэтому видеокарта не является универсально лучшим решением для всех случаев. Она сильна там, где нагрузка позволяет раскрыть ее архитектуру.

12.4. Когда процессор все же лучше

Процессор может быть предпочтительнее, если:

  • матрицы маленькие;
  • операции короткие и частые;
  • данные нецелесообразно копировать в ускоритель;
  • задача сильно разреженная и нерегулярная;
  • важна низкая задержка отдельного запроса;
  • вокруг матричного умножения много сложной логики;
  • нужна простота развертывания;
  • оборудование должно работать без специальных драйверов и стеков ускорителей.

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

12.5. Специализированные блоки и тензорные ускорители

Современные процессоры и ускорители часто содержат специализированные блоки для матричных операций. Они умеют выполнять небольшие матричные произведения быстрее, чем обычные векторные блоки, но требуют соблюдения условий.

Типичные требования:

  • подходящий формат чисел;
  • поддерживаемый размер блока;
  • правильное выравнивание;
  • отсутствие частых переходов между разными типами данных;
  • высокая загрузка полезной работой;
  • корректная упаковка данных.

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

Практическое наблюдение: если после перехода на видеокарту скорость почти не выросла, первым делом стоит проверить не «слабая ли видеокарта», а слишком ли мала задача, не слишком ли много копирования данных и подходит ли формат чисел для ускорения.


13. Типы данных и их влияние на скорость и точность

13.1. Двойная точность: где она обязательна

Двойная точность использует 64 бита на число. Она дает широкий диапазон значений и высокую точность представления. Это важно в задачах, где ошибки округления могут накапливаться и приводить к неверному результату.

Двойная точность часто нужна в:

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

Цена двойной точности высока: она требует больше памяти, больше пропускной способности и часто выполняется медленнее, чем одинарная точность. Поэтому использовать ее «на всякий случай» не всегда разумно. Но и экономить там, где она критична, опасно.

13.2. Одинарная точность и компромиссы

Одинарная точность использует 32 бита на число. Это стандартный рабочий формат для многих графических и машинно-обучающих задач. Он занимает в два раза меньше памяти по сравнению с двойной точностью и часто выполняется быстрее.

Преимущества одинарной точности:

  • меньший объем памяти;
  • лучшая пропускная способность при том же объеме данных;
  • высокая скорость на многих устройствах;
  • достаточность для большого класса задач.

Ограничения:

  • меньшее количество значащих цифр;
  • риск потери точности при вычитании близких чисел;
  • накопление ошибки при длинных суммированиях;
  • возможная недостаточность для плохо обусловленных задач.

На практике часто используют смешанный подход: основные данные хранят и обрабатывают в одинарной точности, но накопление или отдельные критичные шаги выполняют с более высокой точностью.

13.3. Половинная точность и bfloat16

Половинная точность обычно использует 16 бит. Она занимает меньше памяти и может значительно ускорять операции на оборудовании, которое ее хорошо поддерживает. Но у нее уже диапазон значений, чем у одинарной точности, и меньше значащих битов.

Формат bfloat16 тоже использует 16 бит, но иначе распределяет их: он сохраняет более широкий диапазон значений, жертвуя частью точности мантиссы. Это удобно в машинном обучении, где важно не слишком рано получить переполнение или исчезновение значений, но небольшая потеря точности часто допустима.

Сравнение в первом приближении:

ФорматПамять на числоСильная сторонаОграничение
Двойная точность8 байтвысокая точность и диапазонбольшой объем и высокая цена
Одинарная точность4 байтабаланс скорости и точностиошибки округления в чувствительных задачах
Половинная точность2 байтаскорость и экономия памятименьший диапазон и точность
bfloat162 байташирокий диапазон при малом размеременьше точности, чем у половинной точности в мантиссе

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

13.4. Целочисленные форматы и квантование

Для инференса и экономии памяти часто используют целочисленные форматы: 8 бит, иногда 4 бита и ниже. Идея квантования в том, чтобы представить веса или активации приближенно, но компактно.

Преимущества:

  • меньше памяти;
  • выше скорость передачи данных;
  • возможность использовать специализированные целочисленные блоки;
  • снижение энергопотребления в некоторых сценариях.

Риски:

  • потеря точности модели;
  • необходимость калибровки;
  • чувствительность к выбросам;
  • сложность воспроизводимости;
  • не все задачи хорошо переносят сильное сжатие.

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

13.5. Новые низкобитные форматы и их ограничения

К 2026 году в машинном обучении активно используются и более новые низкобитные форматы с плавающей точкой, включая варианты 8-битных представлений. Они пытаются совместить компактность, приемлемый диапазон и скорость.

Важно понимать: низкобитный формат сам по себе не делает задачу лучше. Он полезен, если:

  • оборудование поддерживает его эффективно;
  • модель или метод устойчивы к потере точности;
  • есть корректное накопление результатов в более высокой точности;
  • проверено качество конечного результата;
  • нет скрытых проблем с выбросами и масштабом данных.

Особенно важно учитывать накопление. Даже если перемножение выполняется в низком формате, сумма произведений часто должна накапливаться в более широком формате. Иначе ошибки могут быстро стать заметными.

Практический вывод простой: тип числа — это не только экономия памяти, но и контракт по точности. Если контракт нарушен, ускорение может обернуться неправильными результатами.


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *