Nano Hash - криптовалюты, майнинг, программирование

CUDA: почему та или иная операция копирования памятки всегда стоит в 10 раз дороже других подобных операций

Я считаю, что следующий код выполняет типичный

  • скопировать на устройство
  • вызов ядра
  • скопировать обратно на хост

рабочий процесс.

  1. Что я обнаружил очень странным, так это то, что когда я использовал параметр Trace Application с помощью NSight Profiler, в отчете с включенной «трассировкой стека» я обнаружил, что самая затратная операция — это строка, выделенная жирным шрифтом, и только эта строка, в то время как другая операция memoCopy стоит почти всего 10% или меньше этой операции memoCopy.

    Это потому, что это первая строка после вызова ядра, поэтому профилировщик каким-то образом включил стоимость некоторой синхронизации в стоимость этой конкретной операции memoCopy?

  2. Что касается проблемы, над которой я работаю, которая требует очень частой синхронизации и «возвращения» результата на хост, может ли кто-нибудь дать несколько общих советов по лучшей практике? Я думал, в частности, о двух вариантах, которые, как я не уверен, в конечном итоге помогут

    • use 'zero-copy' memory, (CUDA by Example 11.2)
    • создать мою синхронизацию с использованием атомарных операций

{

int numP = p_psPtr->P.size();
int numL = p_psPtr->L.size();

// Out partition is in Unit of the Number of Particles
int block_dim = BLOCK_DIM_X;
int grid_dim = numP/block_dim + (numP%block_dim == 0 ? 0:1);

vector<Particle> pVec(p_psPtr->P.begin(), p_psPtr->P.end());
Particle *d_part_arr = 0;
Particle *part_arr = pVec.data(); 
HANDLE_ERROR(cudaMalloc((void**)&d_part_arr, numP * sizeof(Particle)));
HANDLE_ERROR(cudaMemcpy(d_part_arr, part_arr, numP * sizeof(Particle), cudaMemcpyHostToDevice));

vector<SpringLink> lVec(p_psPtr->L.begin(), p_psPtr->L.end());
SpringLink *d_link_arr = 0;
SpringLink *link_arr = lVec.data(); 
HANDLE_ERROR(cudaMalloc((void**)&d_link_arr, numL * sizeof(SpringLink)));
HANDLE_ERROR(cudaMemcpy(d_link_arr, link_arr, numL * sizeof(SpringLink), cudaMemcpyHostToDevice));

Point3D *d_oriPos_arr = 0;
Point3D *oriPos_arr = p_originalPos.data(); 
HANDLE_ERROR(cudaMalloc((void**)&d_oriPos_arr, numP * sizeof(Point3D)));
HANDLE_ERROR(cudaMemcpy(d_oriPos_arr, oriPos_arr, numP * sizeof(Point3D), cudaMemcpyHostToDevice));

Vector3D *d_oriVel_arr = 0;
Vector3D *oriVel_arr = p_originalVel.data(); 
HANDLE_ERROR(cudaMalloc((void**)&d_oriVel_arr, numP * sizeof(Vector3D)));
HANDLE_ERROR(cudaMemcpy(d_oriVel_arr, oriVel_arr, numP * sizeof(Vector3D), cudaMemcpyHostToDevice));

Point3D *d_updPos_arr = 0;
Point3D *updPos_arr = p_updatedPos.data(); 
HANDLE_ERROR(cudaMalloc((void**)&d_updPos_arr, numP * sizeof(Point3D)));
HANDLE_ERROR(cudaMemcpy(d_updPos_arr, updPos_arr, numP * sizeof(Point3D), cudaMemcpyHostToDevice));

Vector3D *d_updVel_arr = 0;
Vector3D *updVel_arr = p_updatedVel.data(); 
HANDLE_ERROR(cudaMalloc((void**)&d_updVel_arr, numP * sizeof(Vector3D)));
HANDLE_ERROR(cudaMemcpy(d_updVel_arr, updVel_arr, numP * sizeof(Vector3D), cudaMemcpyHostToDevice));

int *d_converged_arr = 0;
int *converged_arr = &p_converged[0]; 
HANDLE_ERROR(cudaMalloc((void**)&d_converged_arr, numP * sizeof(int)));
HANDLE_ERROR(cudaMemcpy(d_converged_arr, converged_arr, numP * sizeof(int), cudaMemcpyHostToDevice));

// Run the function on the device
handleParticleKernel<<<grid_dim, block_dim>>>(d_part_arr, d_link_arr, numP,
    d_oriPos_arr, d_oriVel_arr, d_updPos_arr, d_updVel_arr, 
    d_converged_arr, p_innerLoopIdx, p_dt);

**HANDLE_ERROR(cudaMemcpy(oriPos_arr, d_oriPos_arr, numP * sizeof(Point3D), cudaMemcpyDeviceToHost));**
HANDLE_ERROR(cudaMemcpy(oriVel_arr, d_oriVel_arr, numP * sizeof(Vector3D), cudaMemcpyDeviceToHost));
HANDLE_ERROR(cudaMemcpy(updPos_arr, d_updPos_arr, numP * sizeof(Point3D), cudaMemcpyDeviceToHost));
HANDLE_ERROR(cudaMemcpy(updVel_arr, d_updVel_arr, numP * sizeof(Vector3D), cudaMemcpyDeviceToHost));
HANDLE_ERROR(cudaMemcpy(converged_arr, d_converged_arr, numP * sizeof(int), cudaMemcpyDeviceToHost));

}

02.05.2013

  • добавьте вызов cudaDeviceSynchronize() после запуска ядра и посмотрите, как это повлияет на время выполнения этого cudamemcpy. 02.05.2013
  • @alrikai Спасибо! Я тоже собирался это попробовать. Могли бы вы сказать, что ожидаемый результат будет таким, что cudaDeviceSynchronize() станет самым дорогим вызовом во время выполнения, а общая стоимость останется прежней, если не увеличится? Если да, то есть ли способ обойти это? 02.05.2013

Ответы:


1

Этот конкретный вызов cudaMemcpy занимает больше времени, потому что он ожидает завершения работы вашего ядра. Если вы добавите cudaDeviceSynchronize после ядра, ваше предполагаемое время выполнения этого вызова cudaMemcpy должно соответствовать всем остальным. (Конечно, это дополнительное время, которое вы видите, вместо этого будет потрачено на ваш cudaDeviceSynchronize вызов).

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

Если ваша программа позволяет, вы можете попробовать разбить запуск ядра и передачу памяти на куски и запустить их с использованием разных потоков, хотя жизнеспособность этого зависит от нескольких факторов (например, ваше ядро ​​может плохо разлагаться на независимые части). Если вы пойдете по этому пути, в лучшем случае сценарий будет таким (взято из документы с рекомендациями по CUDA)

введите здесь описание изображения

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

02.05.2013
  • еще раз спасибо, поэтому после некоторой корректировки моего кода я увидел кое-что (видимо, хорошее, но) довольно странное: в представлении временной шкалы отчета об отслеживании приложений: 03.05.2013
  • (продолжение предыдущего комментария): - самый дорогой вызов cudaMemcpy (отображается в строке CUDA Runtime API) и вызов моей функции ядра 'kernelFunc' (в строке Compute) выполняются примерно в один и тот же период времени. 03.05.2013
  • Конечно, это кажется мне идеальным поведением, но мне интересно, как это возможно - разве ядро ​​не должно запускаться после и заканчиваться до любого копирования мемо, если программа написана как мой код? или мне действительно нужно явно вызвать cudaDeviceSynchronize() после вызова ядра и прямо перед тем, как я скопирую данные обратно на хост, чтобы обеспечить целостность данных? 03.05.2013
  • ядра и memcpy в одном потоке сериализуются, вам не нужна явная синхронизация, она делается за вас. Я не знаю, почему профилировщик показывает это, но, вероятно, это не точное отражение того, что происходит. 03.05.2013
  • Поддерживает ли ваша карта одновременное копирование и вычисления? (вы можете проверить в образце SDK deviceQuery). Если на то пошло, вы можете просто прочитать о параллельном копировании и вычислениях в этот раздел документов cuda 03.05.2013
  • Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

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

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

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

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

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

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..