Документация / РусскийИсходный текст ↗

Ruda Руководство по программированию

English | 简体中文 | 日本語 | Deutsch | Русский

Документация · Среда выполнения API · Вычислительные библиотеки · 中文

1. Хост и устройство

Код хоста Rust выбирает устройства, подготавливает входные данные, создает аргументы и считывает результаты. Ядра устройств описывают параллельные вычисления. Интерфейс расширяет их до IR для внутренней компиляции и выполнения.

ruda-kernel::dsl — это интерфейс ядра общего назначения. Ядра используют синтаксис Rust с типами, макросами и операциями внешнего интерфейса; произвольные программы Rust и код стандартной библиотеки не могут быть просто скомпилированы в GPU.

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

2. Иерархия выполнения

Ruda концепция Цель
RudaCount Количество рабочих групп при запуске
RudaDim Параметры выполнения каждой рабочей группы
ABSOLUTE_POS Глобальная позиция в одномерном поэлементном ядре
Array<T> Доступ к одномерным массивам в ядрах
Tensor<T> Доступ к тензору ядра с метаданными формы и шага
Runtime Компилятор Associates, вычислительный сервер и типы устройств

Соответствующие концепции CUDA см. в руководстве по совместимости. Публичный экспорт находится в DSL prelude.

3. Ваше первое ядро

Это ядро взято из примера времени выполнения ptx, который включает в себя полную хост-программу и проверки выполнения:

use ruda_kernel::dsl::prelude::*;

#[ruda(launch)]
fn add(a: &Array<f32>, b: &Array<f32>, output: &mut Array<f32>) {
    if ABSOLUTE_POS < output.len() {
        output[ABSOLUTE_POS] = a[ABSOLUTE_POS] + b[ABSOLUTE_POS];
    }
}

Импортируйте макрос и введите его с помощью ruda_kernel::dsl::prelude::*. Выходная длина исключает излишки хвостовой резьбы; оба входных массива должны содержать как минимум столько же элементов, сколько и выходной.

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

4. Память и аргументы

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

Клонирование дескриптора или тензора не копирует базовые данные устройства. Чтобы изменить макет, используйте соответствующую операцию непрерывного преобразования, копирования или преобразования. Редактирование метаданных само по себе не приводит к перераспределению хранилища.

5. Отправка, возврат и синхронизация

Отправка ядра и доступность результатов — это отдельные этапы. Завершение отправки хоста не определяет время или успех выполнения устройства.

read_one ожидает обратного чтения и возвращает Result; read_async предоставляет асинхронные результаты. sync() возвращает будущее, которого следует ожидать. flush() отправляет команды в очереди; это не заменяет чтение результата.

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

6. Границы безопасности

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

Проверенный запуск не является полным доказательством безопасности произвольного ядра. В примере используются явные блоки unsafe с пояснениями безопасности для создания и запуска необработанных аргументов. См. Время выполнения API.

7. От ядер к вычислительным библиотекам

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