Как работает видеокарта изнутри tiny-gpu — ваш билет в мир аппаратной магии

18 Aug, 2024

Репозиторий давно не обновлялся

Последнее обновление было 1 год назад.

Знакомая ситуация? Вы с легкостью можете объяснить, как работает процессор: вот регистры, вот ALU, вот конвейер инструкций. Но когда речь заходит о графических процессорах, или GPU, многие из нас теряются. Информация о низкоуровневой архитектуре современных видеокарт — это, как правило, коммерческая тайна, тщательно оберегаемая производителями. Конечно, есть открытые реализации, но они часто настолько сложны и многофункциональны, что погружение в них без подготовки сродни попытке выучить язык по энциклопедии.

Именно здесь на сцену выходит tiny-gpu — проект, который обещает стать вашим личным проводником в мир аппаратной магии GPU. Забудьте о многотомных мануалах и сотнях файлов кода. Этот репозиторий создан специально для того, чтобы вы могли понять, как работают графические процессоры, буквально с нуля, шаг за шагом.

Что такое tiny-gpu и кому он нужен?

tiny-gpu — это минималистичная реализация GPU, написанная на Verilog. Её главная цель — не создание полноценной видеокарты для игр или майнинга, а обучение. Автор проекта, Адам Мадж, столкнулся с проблемой отсутствия доступных ресурсов для изучения аппаратной архитектуры GPU и решил создать свой, максимально упрощенный и понятный вариант.

Проект ориентирован на общие принципы работы GPU (GPGPU) и ускорителей машинного обучения, таких как Google TPU, а не на специфические графические детали. Это значит, что, изучив tiny-gpu, вы получите фундаментальные знания, применимые к широкому спектру современных аппаратных ускорителей.

Кому это будет интересно?

  • Студентам и начинающим инженерам, изучающим компьютерную архитектуру и проектирование цифровых систем.
  • Разработчикам высокопроизводительных вычислений, желающим глубже понять, как их код выполняется на GPU.
  • Любопытным энтузиастам, которые всегда хотели заглянуть под капот видеокарты.
  • Тем, кто переходит в сферу ML-инженерии и хочет разобраться в работе аппаратных ускорителей.

Ключевые особенности: простота и наглядность

Что делает tiny-gpu таким особенным?

1. Минимализм и чистота кода

Проект состоит менее чем из 15 файлов Verilog, каждый из которых тщательно документирован. Это не просто «мало кода», это целенаправленное упрощение, позволяющее сосредоточиться на главном. Никаких отвлекающих деталей, только суть.

2. Полная документация

Помимо комментариев в коде, tiny-gpu предлагает подробное описание архитектуры и системы команд (ISA). Это как иметь личного наставника, который объясняет каждую часть системы.

3. Рабочие ядра для матричных операций

В комплекте идут готовые ядра (kernels) для сложения и умножения матриц. Это не просто теоретические примеры, а полноценные, работающие программы, демонстрирующие принципы SIMD (Single Instruction, Multiple Data) — краеугольного камня параллельных вычислений на GPU.

4. Симуляция и трассировка выполнения

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

Погружаемся в архитектуру tiny-gpu

Давайте заглянем внутрь и посмотрим, из чего состоит этот маленький, но гордый GPU.

Общая структура GPU

tiny-gpu спроектирован для выполнения одного ядра (kernel) за раз. Процесс запуска ядра включает загрузку кода и данных в память, указание количества потоков и активацию сигнала запуска. Сам GPU состоит из нескольких ключевых блоков:

  • Регистр управления устройством (Device Control Register): Хранит метаданные, например, количество потоков для запуска.
  • Диспетчер (Dispatcher): Распределяет потоки по вычислительным ядрам, группируя их в «блоки» (blocks).
  • Вычислительные ядра (Compute Cores): Собственно, выполняют вычисления.
  • Контроллеры памяти (Memory Controllers): Управляют доступом к глобальной памяти.
  • Кэш (Cache): (В разработке) для ускорения доступа к часто используемым данным.

Архитектура GPU Архитектура ядра

Работа с памятью

GPU взаимодействует с внешней глобальной памятью, разделенной на память для данных и память для программ. Интересно, что tiny-gpu использует 8-битную адресацию для данных и программ, а инструкции имеют размер 16 бит. Контроллеры памяти играют ключевую роль, управляя запросами от ядер и балансируя их с пропускной способностью внешней памяти.

Сердце GPU: вычислительное ядро

Каждое ядро в tiny-gpu обрабатывает один блок потоков. Для каждого потока выделены свои ресурсы: ALU (арифметико-логическое устройство), LSU (блок загрузки/хранения), PC (счетчик программ) и файл регистров. Управление этими ресурсами — одна из самых сложных задач в проектировании GPU.

  • Планировщик (Scheduler): Управляет выполнением потоков. В tiny-gpu он выполняет инструкции для всех потоков в блоке синхронно и последовательно. В реальных GPU используются более продвинутые техники, такие как конвейеризация (pipelining) и планирование варпов (warp scheduling).
  • Файлы регистров (Register Files): Каждый поток имеет свой набор регистров, что позволяет реализовать SIMD. Важно, что некоторые регистры содержат служебную информацию (%blockIdx, %blockDim, %threadIdx), позволяющую потокам выполнять разные действия в зависимости от их ID.
  • ALU и LSU: Выполняют арифметические операции и операции загрузки/хранения данных, соответственно.
  • PC (Program Counter): Определяет следующую инструкцию. В tiny-gpu все потоки предполагаются «сходящимися» к одному PC, хотя в реальных GPU существует проблема расхождения ветвей (branch divergence).

Система команд (ISA)

tiny-gpu имеет простую 11-инструкционную ISA, достаточную для демонстрации матричных операций. Здесь есть инструкции для ветвления (BRnzp), сравнения (CMP), базовой арифметики (ADD, SUB, MUL, DIV), загрузки/хранения данных (LDR, STR), загрузки констант (CONST) и завершения потока (RET).

ISA

Регистры (всего 16) включают свободные для чтения/записи и специальные read-only регистры для %blockIdx, %blockDim и %threadIdx.

Ядра в действии: матричные операции

Чтобы показать, как все это работает, автор реализовал ядра для сложения и умножения матриц. Это отличные примеры SIMD-программирования и асинхронного управления памятью.

Сложение матриц

Ядро matadd.asm складывает две матрицы 1x8, выполняя 8 поэлементных сложений в отдельных потоках. Оно активно использует специальные регистры для определения индекса потока и адресов в памяти.

.threads 8
.data 0 1 2 3 4 5 6 7          ; matrix A (1 x 8)
.data 0 1 2 3 4 5 6 7          ; matrix B (1 x 8)

MUL R0, %blockIdx, %blockDim
ADD R0, R0, %threadIdx         ; i = blockIdx * blockDim + threadIdx

CONST R1, #0                   ; baseA (matrix A base address)
CONST R2, #8                   ; baseB (matrix B base address)
CONST R3, #16                  ; baseC (matrix C base address)

ADD R4, R1, R0                 ; addr(A[i]) = baseA + i
LDR R4, R4                     ; load A[i] from global memory

ADD R5, R2, R0                 ; addr(B[i]) = baseB + i
LDR R5, R5                     ; load B[i] from global memory

ADD R6, R4, R5                 ; C[i] = A[i] + B[i]

ADD R7, R3, R0                 ; addr(C[i]) = baseC + i
STR R7, R6                     ; store C[i] in global memory

RET                            ; end of kernel

Умножение матриц

Ядро matmul.asm умножает две матрицы 2x2. Оно демонстрирует использование ветвлений (CMP и BRnzp) внутри потоков, что является более сложным сценарием.

.threads 4
.data 1 2 3 4                  ; matrix A (2 x 2)
.data 1 2 3 4                  ; matrix B (2 x 2)

MUL R0, %blockIdx, %blockDim
ADD R0, R0, %threadIdx         ; i = blockIdx * blockDim + threadIdx

CONST R1, #1                   ; increment
CONST R2, #2                   ; N (matrix inner dimension)
CONST R3, #0                   ; baseA (matrix A base address)
CONST R4, #4                   ; baseB (matrix B base address)
CONST R5, #8                   ; baseC (matrix C base address)

DIV R6, R0, R2                 ; row = i // N
MUL R7, R6, R2
SUB R7, R0, R7                 ; col = i % N

CONST R8, #0                   ; acc = 0
CONST R9, #0                   ; k = 0

LOOP:
  MUL R10, R6, R2
  ADD R10, R10, R9
  ADD R10, R10, R3             ; addr(A[i]) = row * N + k + baseA
  LDR R10, R10                 ; load A[i] from global memory

  MUL R11, R9, R2
  ADD R11, R11, R7
  ADD R11, R11, R4             ; addr(B[i]) = k * N + col + baseB
  LDR R11, R11                 ; load B[i] from global memory

  MUL R12, R10, R11
  ADD R8, R8, R12              ; acc = acc + A[i] * B[i]

  ADD R9, R9, R1               ; increment k

  CMP R9, R2
  BRn LOOP                    ; loop while k < N

ADD R9, R5, R0                 ; addr(C[i]) = baseC + i
STR R9, R8                     ; store C[i] in global memory

RET                            ; end of kernel

Как запустить симуляцию?

Чтобы начать экспериментировать с tiny-gpu, вам потребуется установить iverilog и cocotb. После установки этих инструментов и создания директории build, вы сможете запустить симуляции с помощью команд make test_matadd или make test_matmul.

Результатом будет лог-файл в test/logs, содержащий начальное состояние памяти, полную трассировку выполнения ядра и конечное состояние памяти. Это позволяет увидеть не только результат, но и весь процесс его достижения, инструкция за инструкцией, цикл за циклом.

execution trace

Кстати, автор проекта очень открыт к помощи! Если у вас возникнут проблемы с запуском, он предлагает связаться с ним в Twitter. Это ли не показатель истинной любви к своему делу и сообществу?

Что дальше? Перспективы и возможности для вклада

Конечно, tiny-gpu — это упрощенная модель. В реальных GPU используются гораздо более сложные оптимизации, такие как многоуровневый кэш, совместная память (shared memory), коалесценция памяти (memory coalescing), конвейеризация, планирование варпов, обработка расхождения ветвей и барьеры синхронизации. Автор проекта планирует постепенно добавлять некоторые из этих функций, и, что самое интересное, приглашает сообщество к участию!

Если вы вдохновились и хотите внести свой вклад, это отличная возможность попрактиковаться в Verilog и углубить свои знания в аппаратном проектировании. Можно добавить кэш инструкций, реализовать адаптер для Tiny Tapeout 7, поработать над расхождением ветвей или коалесценцией памяти. Просто отправьте PR!

Выводы: стоит ли попробовать?

Безусловно! tiny-gpu — это не просто очередной GitHub-репозиторий, это уникальный образовательный инструмент. В условиях, когда низкоуровневые детали GPU остаются за семью печатями, такой проект становится настоящим подарком для всех, кто хочет понять, как на самом деле работают эти мощные вычислительные машины.

Если вы когда-либо задавались вопросом «как это работает?», глядя на свою видеокарту, или если вы студент, изучающий цифровую логику, то tiny-gpu — это ваш шанс получить практический опыт и глубокое понимание. Это не просто изучение теории, это возможность увидеть и потрогать аппаратную архитектуру своими руками, пусть и в симуляции. Не упустите эту возможность стать настоящим экспертом по GPU!