Ускорение трассировки лучей на ноутбуке в 8 раз

Компьютерная графика быстро улучшилась за последние несколько десятилетий, что привело к появлению все более реалистичных видеоигр и фильмов. Трассировка лучей, которая преобразует описания 3D-сцены в 2D-изображения, является одним из самых мощных методов. Хотя трассировка лучей генерирует наиболее подробные и реалистичные изображения, она требует значительно больше вычислительных ресурсов, чем сопоставимые методы, такие как глобальная освещенность. Мы рассмотрим, как Ray, среда распределенного выполнения для приложений Python, упрощает добавление параллельных вычислений к простому приложению трассировки лучей, сокращая время его выполнения в 8 раз на моем 8-ядерном ноутбуке.

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

И… в этом посте будет много слова «луч», поэтому я использую заглавную букву R «Луч» для обозначения структуры распределенных систем и строчную «трассировку лучей» для обозначения графической техники.

Фон

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

Трассировка лучей - это метод компьютерной графики для создания двухмерных изображений из трехмерной среды путем имитации способа, которым камера делает снимки. Однако, в то время как физическая камера принимает свет, чтобы запечатлеть изображение своего окружения, трассировщик лучей управляет процессом в обратном порядке. Он посылает лучи «света» из своей «камеры» через двухмерную плоскость, координаты которой соответствуют пикселям изображения. Эти лучи затем могут пересекаться и отражаться от объектов в сцене. Затем цвет пикселя определяется углом отражения по сравнению с источником света, а также свойствами и формой закрывающего материала. Когда луч отражается от объекта и движется прямо к источнику света, пиксель, соответствующий этому лучу, будет больше зависеть от свойств этого света, таких как его яркость (интенсивность света) и цвет. Вы можете представить, как меняют траекторию луча, который вы проследили, следуя за лучом от источника света, отражаясь от объекта и заканчиваясь на камере. Отслеживание возможных путей от камеры к источникам света более эффективно, чем отслеживание световых лучей от каждого источника света к камере, поскольку большинство световых лучей не улавливаются камерой.

Поскольку эти вычисления лучей независимы друг от друга, программа может свободно отслеживать лучи параллельно. Ray - это среда Python, разработанная RISELab в Калифорнийском университете в Беркли, которая позволит нам добиться этого с минимальными изменениями в нашей программе.

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

  1. Выясните, как передавать сообщения между процессами (например, GRPC, очереди сообщений и т. Д.)
  2. Копирование данных между процессами или определение схемы общей памяти
  3. Изящно справляйтесь с сбоями процессов

Рэй позволяет вам ни о чем из этого не беспокоиться. При использовании Ray передача сообщений происходит через вызовы функций и методов Python, поэтому вы можете запрограммировать свое приложение так, как будто все выполняется в одном потоке на одном компьютере. Использование Рэем Apache Arrow позволяет обмениваться данными между процессами, чтобы сэкономить на копировании без каких-либо мыслей со стороны пользователя. Задачи (функции) и субъекты (классы) перезапускаются автоматически в случае сбоя.

Наша примерная реализация

Рассматриваемая нами реализация трассировщика лучей находится здесь. Кредиты принадлежат Сириллу Россанту. Он небольшой, но достаточно производительный благодаря использованию numpy. [1] Он включает рекурсивные отскоки лучей для получения более точного освещения и отражательной способности.

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

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

Изменения в использовании Ray

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

Украшая эту функцию символом ray.remote, я разрешаю ей запускаться как задачу в распределенном контексте.

Кроме того, предыдущая итерация функции вычисляет результат, а затем сразу устанавливает интенсивность цвета пикселя в структуре данных изображения. Теперь, когда мы запускаем множество заданий параллельно, нам нужно изменить каждый вызов, чтобы он возвращал свой результат, вместо того, чтобы немедленно записывать в список результатов пикселей. [2]

Необходимые изменения:

  1. Запускайте задания и собирайте результаты вместе с соответствующими координатами. Эти результаты не являются фактическими возвращаемыми значениями прослеженных лучей. Скорее, они относятся к месту, где будет сохранено возвращаемое значение, и используются как фьючерсы Python. [3]
  2. Дождитесь результатов, прикрепите их к координатам и установите пиксели изображения в цикле.

Полученные результаты

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

Контрольные точки

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

Для каждой версии скрипта я провел 5 проб для каждого размера изображения: 600x450, 1200x900 и 1600x1200. В каждом испытании я фиксировал значение user из команды Unix time, чтобы уменьшить шум в данных из-за изменений в использовании других процессов на машине. Хотя я не приводил здесь результаты, время использования sys для версии Ray немного выше из-за системных вызовов, необходимых для распространения работы, хотя разница незначительна.

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

Другие улучшения, сделанные Ray

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

Простое совместное использование изменяющегося состояния
Хотя независимый характер трассировки отдельных лучей подчеркивает простоту использования задач Ray, бывают случаи, когда вы хотите обмениваться информацией между вычислениями лучей. Например, вы можете предоставить каждому вычислению доступ к иерархии ограничивающей рамки. Это структура данных, которая значительно ускоряет трассировку лучей за счет сокращения ненужных проверок столкновений с объектами. Это достигается за счет разбиения двухмерного пространства на сегменты, которые содержат только некоторую часть объектов сцены, что может привести к значительному ускорению. В случае трассировки лучей в реальном времени иерархия ограничивающего прямоугольника изменяется со временем вместе со сценой, и работникам нужен способ получить обновленную копию; актер Ray подходит для этого варианта использования.

Трассировка лучей в кластере
Используя средство запуска кластера Ray, вы можете запускать свою программу в облаке (с поддержкой спотовых экземпляров из-за отказоустойчивости Ray). Это означает, что если у вас закончатся алгоритмические оптимизации, вы сможете работать быстрее, добавив больше вычислений для решения проблемы!

Заключение

Я написал это сообщение в блоге, потому что считаю, что Ray может сделать для распределенных систем то, что краеугольные библиотеки, такие как Tensorflow и React, сделали для машинного обучения и создания интерфейсов соответственно. Распределенные вычисления становятся все более важными с каждым годом, поскольку закона Мура оказывается недостаточно для удовлетворения растущих вычислительных потребностей (подробнее читайте в статье Иона Стойки Будущее распределенных вычислений). Цель Рэя - демократизировать распределенные вычисления и вывести их за пределы компетенции специалистов. Проект Ray уже включает в себя несколько распределенных библиотек с батарейками для обучения с подкреплением, обслуживания HTTP, обмена данными в пандах и многого другого. Проверьте это!

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

Наконец, если вам это понравилось, вы можете посетить Ray Summit, чтобы получить больше интересного материала !

[1]: Между прочим, Ray поддерживает совместное использование numpy ndarrays с нулевым копированием среди рабочих, что может сэкономить огромное количество памяти.

[2]: Вы также можете создать актера Ray (класс Python) для представления изображения, а затем передать этого актера в функцию trace_ray_with_bounces.

[3]: Возникает резонный вопрос: «В чем дело с CHUNK_SIZE? Почему вы не поставили одну задачу в соответствие с трассировкой одного луча? » Что ж, когда задача становится слишком маленькой, мы видим снижение производительности, потому что накладные расходы на обмен данными превосходят преимущества параллелизма. В качестве примера, изображение размером 1600 на 1200 пикселей, которое я создал, потребует запуска более миллиона подзадач. Даже небольшие накладные расходы на задачу быстро складываются в такое количество маленьких задач. Идеальный размер отрезка лучей для трассировки составляет 1/8 от общего числа пикселей, что приводит к 8 задачам, распределенным по 8 ядрам.