Немного о Моджо

В середине октября этого года во время встречи разработчиков LLVM 2023 состоялась подарок новый язык программирования Mojo, предназначенный для задач программирования, связанных с AI/ML (кстати, помимо поста в блоге рекомендую посмотреть сохранять сама презентация). Я не буду затрагивать вопрос, зачем в 2023 году нужен еще один язык программирования, а попрошу читателей ознакомиться с глава «Почему Моджо» лингвистическую документацию, прежде чем оставлять комментарий такого типа. Позвольте мне просто сказать, что этот компилируемый язык предназначен для использования всех возможностей современных компьютерных ускорителей, таких как графические процессоры, TPU и различные типы AI ASIC, без необходимости углубляться в дебри ассемблеров для конкретных платформ. Этот язык кажется естественным продолжением MLIR-экосистемы, разработанный для упрощения взаимодействия с MLIR для высокопроизводительных вычислительных задач. Чтобы лучше понять предмет нашего эксперимента, приведу пример. многопоточная реализация фрактала Мандельброта на Mojo:

from complex import ComplexSIMD, ComplexFloat64
from math import iota
from algorithm import parallelize, vectorize
from tensor import Tensor

alias float_type = DType.float64
alias simd_width = 2 * simdwidthof[float_type]()

alias width = 960
alias height = 960
alias MAX_ITERS = 200

alias min_x = -2.0
alias max_x = 0.6
alias min_y = -1.5
alias max_y = 1.5


fn mandelbrot_kernel_SIMD[
    simd_width: Int
](c: ComplexSIMD[float_type, simd_width]) -> SIMD[float_type, simd_width]:
    """A vectorized implementation of the inner mandelbrot computation."""
    let cx = c.re
    let cy = c.im
    var x = SIMD[float_type, simd_width](0)
    var y = SIMD[float_type, simd_width](0)
    var y2 = SIMD[float_type, simd_width](0)
    var iters = SIMD[float_type, simd_width](0)

    var t: SIMD[DType.bool, simd_width] = True
    for i in range(MAX_ITERS):
        if not t.reduce_or():
            break
        y2 = y * y
        y = x.fma(y + y, cy)
        t = x.fma(x, y2) <= 4
        x = x.fma(x, cx - y2)
        iters = t.select(iters + 1, iters)
    return iters


fn main() raises:
    let t = Tensor[float_type](height, width)

    @parameter
    fn worker(row: Int):
        let scale_x = (max_x - min_x) / width
        let scale_y = (max_y - min_y) / height

        @parameter
        fn compute_vector[simd_width: Int](col: Int):
            """Each time we operate on a `simd_width` vector of pixels."""
            let cx = min_x + (col + iota[float_type, simd_width]()) * scale_x
            let cy = min_y + row * scale_y
            let c = ComplexSIMD[float_type, simd_width](cx, cy)
            t.data().simd_store[simd_width](
                row * width + col, mandelbrot_kernel_SIMD[simd_width](c)
            )

        # Vectorize the call to compute_vector where call gets a chunk of pixels.
        vectorize[simd_width, compute_vector](width)

    parallelize[worker](height, height)

Как видите, язык имеет синтаксис, подобный Python, и предоставляет примитивы высокого уровня для ускорения вычислений: SIMD (со встроенным методом fmaмногократный предохранитель), ComplexSIMD , vectorize, parallelize и т. д.

ЧИТАТЬ   Работа с файлами в приложениях: как отказаться от сторонних .NET-библиотек

В настоящее время язык находится на стадии закрытого исходного кода (с проект с открытым исходным кодом после окончания начальной фазы разработки) и разрабатывается компанией Модульныйбаза Крис Лэттнер — один из создателей LLVM, MLIR, компилятора Clang и языка Swift. На Mojo уже есть несколько статей о Хабре, одну из них я рекомендую тем, кто хочет получить обзор языка в целом.

О чем мы будем говорить

Исследуя сайт Modular, я наткнулся на статью с кричащим названием. «Самое быстрое унифицированное умножение матриц в мире»который описывает, как Модульный механизм искусственного интеллекта влияет на производительность библиотеки Интел МКЛ, UnDNN И Собственный в задаче GEMM (общего матричного умножения). Потом я прочитал статью в их блоге «Фрагментация вычислений ИИ: чему нас учит умножение матриц» (TL DR: GEMM — это основа современных нейронных сетей, реализации SOTA представляют собой километры сложного кода в различных ассемблерах, и этот подход не масштабируем из-за привязки к платформе и сложности разработки, поэтому нужно что-то менять, то есть включить Mojo) и я добрался до глава документации, где описывается написание и оптимизация GEMM на Mojo: используя встроенные средства языка, мы начинаем с наивной реализации, и постепенно улучшаем ее, добавляя векторизацию, параллелизм, тайлинг и т. д. В целом, судя по изменить историю этот конкретный пример из документации его можно назвать условно оптимальным, поскольку многие люди (в том числе разработчики языка) языка внесли свой вклад в его оптимизацию. Учитывая все вышесказанное, нам просто нужно самостоятельно запустить этот код и сравнить его производительность с отраслевыми стандартами!

Что и как будем сравнивать

  • Сравним производительность немного модифицированной модели. Пример Матмула на Mojo 0.6.0, библиотеки C/C++ Intel MKL 2020.4.304, ОпенБЛАС 0.3.25 и Эйген 3.4.0, а также наивная реализация умножения матриц в C++.

  • Мы будем сравнивать процессор (в текущей версии Mojo 0.6.0 процессор является единственной доступной целью компиляции) Intel Xeon Platinum 8124M (16 ядер, экземпляр c5.4xlarge AWS EC2) и операционная система Ubuntu 22.04.

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

  • Для компиляции тестов на C++ мы будем использовать компилятор Clang с флагами -O3 -march=native — последний флаг означает, что наша наивная реализация автоматически становится векторизованной.

  • Сравнивать будем GFLOPS (Гига операций с плавающей запятой в секунду), вычисляя их по формуле GLOPS = 2 * M * N * K / time / 1e9Или M, N, K — размеры матрицы (MxN=MxK*KxN), А time — время в секундах, затраченное на выполнение операции умножения матрица-матрица.

ЧИТАТЬ   В ОАЭ начали выращивать пшеницу на сотнях гектаров пустынной земли

Весь код, используемый с инструкциями выполнения, доступен в Этот репозитории.

Сравнение

Многопоточная версия

Размер проблемы

Моджо («Обманенный»)

Собственный

МКЛ

OpenBLAS

128x128x128

24,4

109,0

543,8

143,6

256x256x256

129,6

190,3

939,5

380,9

256x1024x4096

835,4

378,8

1063,6

860,6

256x4096x1024

818,0

428,0

1001,6

770,9

256x1024x1024

690,8

387,8

1037,7

806,8

128x1024x4096

820,5

390,8

1078,6

784,4

128x4096x1024

795,0

404,6

1044,2

688,9

128x1024x1024

679,6

380,9

1028,9

707,4

256x768x768

582,2

351,1

1051,7

818,5

128x768x768

579,0

342,1

893,4

707,5

128x3072x768

783,4

396,7

990,2

755,0

128x768x3072

814,3

381,2

1085,7

784,2

256x3072x768

794,7

424,3

1096,7

846,4

256x768x3072

819,6

356,1

1116,0

865,4

128x768x2304

797,8

381,0

1089,7

826,5

1024x2560x1024

808,8

437,3

1176,1

1126,2

1024x1024x512

556,7

399,0

1227,9

933,6

1024x352x512

0,0*

206,3

1185,6

575,1

1024x512x256

245,4

313,5

1231,7

749,2

Однопоточная версия

5cc677f9f611dfbe86ba36f53e3aab3f

Размер проблемы

Моджо («Векторизованный»)

Собственный

МКЛ

OpenBLAS

Наивный

128x128x128

18,6

67,8

166,9

98,7

21.1

256x256x256

20,5

57,1

149,6

133,5

19,5

256x1024x4096

10,0

56,0

136,4

125,4

11,0

256x4096x1024

8.3

56,5

138,6

128,0

11,0

256x1024x1024

11,9

57,7

152,0

140,5

13.2

128x1024x4096

10.3

51,0

80,5

112,3

11.1

128x4096x1024

8,5

51,0

77,4

112,3

10,8

128x1024x1024

11,5

53,4

91,4

127,9

13.2

256x768x768

26,0

58,2

155,0

151,0

13.1

128x768x768

12,5

54,6

163,3

137,7

13.2

128x3072x768

9,6

52,1

87,7

124,5

12,5

128x768x3072

11.2

51,9

157,2

134,6

12,6

256x3072x768

9,7

58,6

147,4

137,0

12.4

256x768x3072

12,0

59,6

148,4

143,3

12,6

128x768x2304

12.4

52,4

162,7

135,2

12,7

1024x2560x1024

10,7

58,8

160,3

151,9

12,6

1024x1024x512

11,7

58,1

163,5

141,8

13.2

1024x352x512

0,0*

56,6

162,3

159,4

17.1

1024x512x256

25,6

57,5

167,7

143,8

20.3

* — Код Mojo аварийно завершает работу с segfault в случае 1024x352x512, поэтому в этом случае для Mojo указано 0 GFLOPS.

выводы

Итак, что мы видим? Во-первых, при однопоточном запуске векторизованный код Mojo работает так же, как код C++, векторизованный с помощью Clang. Это нормально, но это не очень интересно и впечатляюще: кому нужно однопоточное выполнение в эпоху, когда закон Мура больше не применяется?

ЧИТАТЬ   Береговая охрана США начала расследование трагедии с батискафом «Титан».

Результаты для многопоточной версии гораздо интереснее. Здесь мы видим это:

  • Intel MKL, как и ожидалось, дает преимущество всем остальным вариантам (как и ожидалось, поскольку эта библиотека для процессоров Intel разработана самой Intel).

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

  • Mojo показывает производительность, сравнимую с OpenBLAS.

Последний пункт особенно важен. За что? А потому, что OpenBLAS — это монстр ассемблерного кода, который годами оптимизировался красноглазыми гениями в подвалах под каждую существующую микроархитектуру Intel, а программа Mojo — это несколько сотен строк кода Python-подобного, условно платформо-независимого, который очень впечатляет.

Заключение

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

Данная статья (или, скорее, даже заметка), как и данный набор ориентиров, не претендует на научность и исчерпывающую точность, по крайней мере, по следующим причинам:

  • Тесты проводились на одном процессоре.

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

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

  • И последнее, но не менее важное: возможно, я где-то упустил момент и/или неправильно использовал одну из библиотек, поэтому производительность оказалась ужасной.

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

Source

От admin