Как реализовать дескрипторы для библиотеки API драйвера CUDA?
Примечание: вопрос был обновлен, чтобы ответить на вопросы, которые были подняты в комментариях, и подчеркнуть, что суть вопроса заключается в взаимозависимостях между API времени выполнения и драйвера
Библиотеки времени выполнения CUDA (такие как CUBLAS или CUFFT) обычно используют концепцию "дескриптора", которая обобщает состояние и контекст такой библиотеки. Шаблон использования довольно прост:
// Create a handle
cublasHandle_t handle;
cublasCreate(&handle);
// Call some functions, always passing in the handle as the first argument
cublasSscal(handle, ...);
// When done, destroy the handle
cublasDestroy(handle);
Однако есть много тонких деталей о том, как эти дескрипторы взаимодействуют с контекстами Driver и Runtime и несколькими потоками и устройствами. В документации перечислено несколько разрозненных деталей об обработке контекста:
Общее описание контекстов в Руководстве по программированию CUDA по адресу http://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html
Обработка нескольких контекстов, как описано в Руководстве CUDA Best Practices по адресу http://docs.nvidia.com/cuda/cuda-c-best-practices-guide/index.html
Различия в управлении контекстом между средой выполнения и драйвером API описаны на http://docs.nvidia.com/cuda/cuda-driver-api/driver-vs-runtime-api.html
Общее описание контекстов / дескрипторов CUBLAS по адресу http://docs.nvidia.com/cuda/cublas/index.html и безопасность их потоков по адресу http://docs.nvidia.com/cuda/cublas/index.html
Тем не менее, некоторая информация, кажется, не совсем обновлена (например, я думаю, что следует использовать cuCtxSetCurrent
вместо cuCtxPushCurrent
а также cuCtxPopCurrent
?), некоторые из них, по-видимому, относятся к тому времени, когда обработка "Первичного контекста" была раскрыта через API драйвера, а некоторые части упрощены тем, что они показывают только самые простые шаблоны использования, делают только расплывчатые или неполные утверждения о многопоточности или не может быть применено к понятию "дескрипторов", которое используется в библиотеках времени выполнения.
Моя цель состоит в том, чтобы реализовать библиотеку времени выполнения, которая предлагает свой собственный тип "дескриптора" и который позволяет использовать шаблоны, которые эквивалентны другим библиотекам времени выполнения с точки зрения обработки контекста и безопасности потоков.
В случае, если библиотека может быть реализована внутри системы исключительно с использованием API времени выполнения, все может быть понятно: управление контекстом лежит исключительно на пользователе. Если он создает собственный контекст драйвера, применяются правила, изложенные в документации об управлении контекстом времени выполнения и драйвера. В противном случае функции API времени выполнения позаботятся об обработке основных контекстов.
Тем не менее, может случиться так, что библиотеке придется использовать API-интерфейс драйвера. Например, чтобы загрузить файлы PTX как CUmodule
объекты, и получить CUfunction
объекты от них. И когда библиотека должна - для пользователя - вести себя как библиотека времени выполнения, но внутренне должна использовать API драйвера, возникают некоторые вопросы о том, как обработка контекста должна быть реализована "под капотом".
То, что я понял до сих пор, набросано здесь.
(Это "псевдокод" в том смысле, что он пропускает проверки ошибок и другие детали, и... все это должно быть реализовано в Java, но это не должно быть здесь уместно)
1. "Ручка" - это, по сути, класс / структура, содержащая следующую информацию:
class Handle
{
CUcontext context;
boolean usingPrimaryContext;
CUdevice device;
}
2. При его создании необходимо рассмотреть два случая: его можно создать, когда контекст драйвера является текущим для вызывающего потока. В этом случае следует использовать этот контекст. В противном случае он должен использовать основной контекст текущего (во время выполнения) устройства:
Handle createHandle()
{
cuInit(0);
// Obtain the current context
CUcontext context;
cuCtxGetCurrent(&context);
CUdevice device;
// If there is no context, use the primary context
boolean usingPrimaryContext = false;
if (context == nullptr)
{
usingPrimaryContext = true;
// Obtain the device that is currently selected via the runtime API
int deviceIndex;
cudaGetDevice(&deviceIndex);
// Obtain the device and its primary context
cuDeviceGet(&device, deviceIndex);
cuDevicePrimaryCtxRetain(&context, device));
cuCtxSetCurrent(context);
}
else
{
cuCtxGetDevice(device);
}
// Create the actual handle. This might internally allocate
// memory or do other things that are specific for the context
// for which the handle is created
Handle handle = new Handle(device, context, usingPrimaryContext);
return handle;
}
3. При вызове ядра библиотеки контекст соответствующего дескриптора становится актуальным для вызывающего потока:
void someLibraryFunction(Handle handle)
{
cuCtxSetCurrent(handle.context);
callMyKernel(...);
}
Здесь можно утверждать, что вызывающая сторона отвечает за то, чтобы требуемый контекст был актуальным. Но если дескриптор был создан для основного контекста, этот контекст будет автоматически обновляться.
4. Когда ручка разрушена, это означает, что cuDevicePrimaryCtxRelease
должен быть вызван, но только когда контекст является основным контекстом:
void destroyHandle(Handle handle)
{
if (handle.usingPrimaryContext)
{
cuDevicePrimaryCtxRelease(handle.device);
}
}
Из моих экспериментов до сих пор это, похоже, демонстрирует то же поведение, что и дескриптор CUBLAS, например. Но мои возможности для тщательного тестирования этого ограничены, потому что у меня есть только одно устройство, и, таким образом, я не могу проверить критические случаи, например, наличие двух контекстов, по одному для каждого из двух устройств.
Итак, мои вопросы:
- Существуют ли какие-либо установленные шаблоны для реализации такой "Ручки"?
- Существуют ли какие-либо шаблоны использования (например, с несколькими устройствами и одним контекстом на устройство), которые не могут быть охвачены подходом, который описан выше, но будут охвачены реализациями "дескриптора" CUBLAS?
- В более общем плане: есть ли рекомендации по улучшению текущей реализации "дескриптора"?
- Риторика: доступен ли где-нибудь исходный код обработки дескриптора CUBLAS?
(Я также взглянул на обработку контекста в tenorflow, но я не уверен, можно ли извлечь из этого рекомендации о том, как реализовать дескрипторы для библиотеки времени выполнения...)
("Обновление" здесь удалено, потому что оно было добавлено в ответ на комментарии и больше не должно быть актуальным)
0 ответов
Мне жаль, что я не заметил этот вопрос раньше - так как мы могли бы немного поработать над этим. Кроме того, мне не совсем понятно, относится ли этот вопрос к этому вопросу, к codereview.SX или к programmers.SX, но давайте проигнорируем все это.
Я сделал то, что вы намеревались сделать, и, возможно, в более общем плане. Итак, я могу предложить как пример того, что делать с "ручками", так и более того, предположить перспективу того, что это вообще не нужно реализовывать.
Библиотека представляет собой расширение cuda-api-wrappers, которое также охватывает API драйверов и NVRTC; он еще не находится в стадии релиза, но находится на стадии тестирования в этой ветке.
Теперь, чтобы ответить на ваш конкретный вопрос:
Шаблон для написания класса, окружающего необработанный "дескриптор"
Существуют ли какие-либо установленные шаблоны для реализации такой "ручки"?
Да. Если вы читаете:
В чем разница между: Handle, Pointer и Reference
вы заметите, что дескриптор определяется как "непрозрачная ссылка на объект". Он чем-то похож на указатель. Таким образом, соответствующий шаблон является разновидностью идиомы PIMPL: в обычном PIMPL вы пишете класс реализации, а обращенный наружу класс содержит только указатель на класс реализации и пересылает ему вызовы методов. Когда у вас есть непрозрачный дескриптор непрозрачного объекта в какой-либо сторонней библиотеке или драйвере, вы используете дескриптор для перенаправления вызовов методов этой реализации.
Это означает, что ваш обращенный вовне класс не является дескриптором, он представляет объект, к которому у вас есть дескриптор.
Универсальность и гибкость
Существуют ли какие-либо шаблоны использования (например, с несколькими устройствами и одним контекстом для каждого устройства), которые не могут быть охвачены подходом, описанным выше, но могут быть охвачены реализациями CUBLAS "дескриптора"?
Я не уверен, что именно CUBLAS делает под капотом (и, честно говоря, я почти никогда не использовал CUBLAS), но если бы он был хорошо спроектирован и реализован, он создал бы свой собственный контекст и постарался бы не посягать на остальная часть вашего кода, т.е. он всегда будет делать:
- Поместите наш контекст CUBLAS в верхнюю часть стека
- Реальная работа
- Поднимите верхнюю часть стека контекста.
Ваш класс этого не делает.
В более общем плане: есть ли какие-либо рекомендации, как улучшить текущую реализацию "Handle"?
Да:
- Используйте RAII, когда это возможно и актуально. Если ваш код создания выделяет ресурс (например, через драйвер CUDA), деструктор возвращаемого вами объекта должен безопасно освободить эти ресурсы.
- Разрешить использование дескрипторов как ссылочного типа, так и типа значения, т.е. это может быть дескриптор, который я создал, но также может быть дескриптор, который я получил откуда-то еще, и это не моя ответственность. Это тривиально, если вы предоставите пользователю право высвобождать ресурсы, но немного сложно, если вы возьмете на себя эту ответственность.
- Вы предполагаете, что если есть какой-либо текущий контекст, это тот, который должен использовать ваш дескриптор. Кто говорит? По крайней мере, позвольте пользователю передать контекст, если он хочет.
- Избегайте написания низкоуровневых частей этого самостоятельно, если в этом нет необходимости. Вы, скорее всего, пропустите некоторые вещи (push-and-pop - не единственное, что вам может не хватать), и вы повторяете много работы, которая на самом деле является общей, а не специфичной для вашего приложения или библиотеки. Я могу быть здесь предвзятым, но теперь вы можете использовать красивые RAII-обертки для контекстов CUDA, потоков, модулей, устройств и т. Д., Даже не зная о необработанных дескрипторах для чего-либо.
Риторический: есть ли где-нибудь исходный код обработки дескрипторов CUBLAS?
Насколько мне известно, NVIDIA не выпустила его.