Cuda Stream Processing для нескольких ядер
Привет несколько вопросов, касающихся обработки потоков Cuda для нескольких ядер. Предположим, что s потоков и ядро в совместимом устройстве 3.5, где s <= 32. Ядро использует массив dev_input размера n и выходной массив dev размера s*n. Ядро считывает данные из входного массива, сохраняет их значение в регистре, манипулирует им и записывает свой результат обратно в dev_output в позиции s*n + tid.
Мы стремимся запускать одно и то же ядро s раз, используя один из n потоков каждый раз. Похож на пример simpleHyperQ. Можете ли вы прокомментировать, если и как любое из следующих действий влияет на параллелизм, пожалуйста?
- dev_input и dev_output не закреплены;
- dev_input как он есть против dev_input размер s * n, где каждое ядро читает уникальные данные (нет конфликтов чтения)
- ядра читают данные из постоянной памяти
- 10 КБ общей памяти выделяется на блок.
- ядро использует 60 регистров
Любые хорошие комментарии будут оценены...!!!
ура, Танасио
Роберт, большое спасибо за ваш подробный ответ. Это было очень полезно. Я отредактировал 4, это 10 КБ на блок. Так что в моей ситуации я запускаю сетки из 61 блока и 256 потоков. Ядра довольно вычислительно связаны. Я запускаю 8 потоков одного и того же ядра. Профилируйте их, и тогда я вижу очень хорошее совпадение между первыми двумя, а затем становится все хуже и хуже. Время выполнения ядра составляет около 6 мс. После того, как первые два потока выполняются почти идеально одновременно, остальные имеют расстояние 3 мс между ними. Что касается 5, я использую K20, который имеет 255 регистров. Так что я бы не ожидал от этого недостатков. Я действительно не могу понять, почему я не достигаю параллелизма, эквивалентного тому, что указано для gk110s..
Пожалуйста, посмотрите на следующую ссылку. Существует изображение с именем kF.png . Оно показывает вывод профилировщика для потоков..!!!
2 ответа
Параллельность между ядрами зависит от ряда факторов, но один из них, который многие пропускают, - это просто размер ядра (то есть количество блоков в сетке). Ядра такого размера, которые могут эффективно использовать GPU сами по себе, обычно не будут работать в большой степени одновременно, и пропускная способность будет незначительной, даже если они это сделают. Распределитель работы внутри GPU обычно начинает распределять блоки, как только запускается ядро, поэтому, если одно ядро запускается раньше другого, и оба имеют большое количество блоков, то первое ядро, как правило, будет занимать GPU, пока оно не будет почти завершено, в этот момент блоки второго ядра будут затем запланированы и выполнены, возможно, с небольшим количеством "одновременного перекрытия".
Суть в том, что ядра, у которых достаточно блоков, чтобы "заполнить графический процессор", не позволят другим ядрам фактически выполняться, и, кроме планирования, это ничем не отличается от устройства Compute 3.5. Кроме того, вместо четких ответов полезно не только указать несколько параметров для ядра в целом, но и указать параметры запуска и статистику (например, использование регистров, использование общих записей памяти и т. Д.) На уровне блоков. Преимущества архитектуры Compute 3.5 в этой области по-прежнему будут в основном получены из "маленьких" ядер "нескольких" блоков, пытающихся работать вместе. Compute 3.5 имеет некоторые преимущества.
Вам также следует ознакомиться с ответом на этот вопрос.
- Когда глобальная память, используемая ядром, не закреплена, это влияет на скорость передачи данных, а также на возможность дублирования копирования и вычисления, но не влияет на способность двух ядер работать одновременно. Тем не менее, ограничения на копирование и наложение вычислений могут искажать поведение вашего приложения.
- Не должно быть "конфликтов чтения", я не уверен, что вы подразумеваете под этим. Два независимых потока / блоков / сеток могут считывать одно и то же место в глобальной памяти. Как правило, это будет отсортировано на уровне кэша L2. Пока мы говорим только о чтениях, не должно быть никакого конфликта и никакого особого влияния на параллелизм.
- Постоянная память - это ограниченный ресурс, который используется всеми ядрами, выполняющимися на устройстве (попробуйте запустить deviceQuery). Если вы не превысили общий лимит устройства, то единственной проблемой будет проблема использования постоянного кэша и таких вещей, как перегрузка кэша. Помимо этих вторичных отношений, нет прямого влияния на параллелизм.
- Было бы более поучительно определить количество разделяемой памяти на блок, а не на ядро. Это напрямую повлияет на количество блоков, которые можно запланировать на SM. Но ответить на этот вопрос было бы гораздо четче, если бы вы указали конфигурацию запуска каждого ядра, а также относительную синхронизацию вызовов запуска. Если совместно используемая память оказалась ограничивающим фактором при планировании, то вы можете разделить общую доступную разделяемую память на SM на количество, используемое каждым ядром, чтобы получить представление о возможном параллелизме, основанном на этом. Мое собственное мнение таково, что количество блоков в каждой сетке, вероятно, будет более серьезной проблемой, если только у вас нет ядер, которые используют 10k на сетку, но всего несколько блоков во всей сетке.
- Мои комментарии здесь будут почти такими же, как и мой ответ на 4. Посмотрите на deviceQuery для вашего устройства, и если регистры стали ограничивающим фактором при планировании блоков на каждом SM, то вы могли бы разделить доступные регистры на SM на использование регистров на Ядро (опять же, гораздо разумнее поговорить об использовании регистров на блок и количестве блоков в ядре), чтобы выяснить, каким может быть ограничение.
Опять же, если у вас есть ядра разумного размера (сотни или тысячи блоков или более), то планирование блоков рабочим распределителем, скорее всего, будет доминирующим фактором в степени параллелизма между ядрами.
РЕДАКТИРОВАТЬ: в ответ на новую информацию, размещенную в вопросе. Я посмотрел на kF.png
- Сначала давайте проанализируем с точки зрения каждого блока SM. CC 3.5 допускает 16 "открытых" или текущих запланированных блоков на SM. Если вы запускаете 2 ядра по 61 блоку в каждом, этого вполне может быть достаточно, чтобы заполнить "готовую к работе" очередь на устройстве CC 3.5. Другими словами, графический процессор может обрабатывать 2 из этих ядер одновременно. Поскольку блоки одного из этих ядер "стекают", то другое ядро планирует рабочий дистрибьютор. Блоки первого ядра "истощают" достаточно примерно за половину общего времени, так что следующее ядро будет запланировано примерно на полпути после завершения первых двух ядер, поэтому в любой заданной точке (нарисуйте вертикальную линию на временной шкале) вы иметь 2 или 3 ядра, выполняющихся одновременно. (3-е запущенное ядро перекрывает первые 2 примерно на 50% в соответствии с графиком, я не согласен с вашим утверждением, что между каждым последующим запуском ядра существует расстояние 3 мс). Если мы скажем, что на пике у нас запланировано 3 ядра (есть множество вертикальных линий, которые будут пересекать 3 временные шкалы ядра), и каждое ядро имеет ~60 блоков, то это около 180 блоков. Ваш K20 имеет 13 SM, и на каждом SM может быть запланировано не более 16 блоков. Это означает, что на пике у вас запланировано около 180 блоков (возможно) против теоретического пика 16*13 = 208. Таким образом, вы достаточно близки к максимуму здесь, и вы можете получить немного больше. Но, может быть, вы думаете, что вы получаете только 120/208, я не знаю.
- Теперь давайте посмотрим с точки зрения общей памяти. Ключевой вопрос - какова настройка вашего L1/ разделяемого разделения? Я полагаю, что по умолчанию это 48 КБ общей памяти на SM, но если вы изменили этот параметр, это будет очень важно. В любом случае, согласно вашему заявлению, каждый запланированный блок будет использовать 10 КБ разделяемой памяти. Это означает, что мы могли бы максимально использовать около 4 блоков, запланированных на SM, или 4*13 блоков = 52 блока, которые можно запланировать в любой момент времени. Вы явно превышаете это число, поэтому, вероятно, у меня недостаточно информации об использовании общей памяти вашими ядрами. Если вы действительно используете 10 КБ / блок, это более или менее лишает вас возможности выполнять более одного ядра потоковых блоков одновременно. Там все еще могут быть некоторые совпадения, и я считаю, что это, вероятно, будет фактическим ограничивающим фактором в вашем приложении. Первое ядро из 60 блоков запланировано. После утечки нескольких блоков (или, возможно, потому что 2 ядра были запущены достаточно близко друг к другу), второе ядро начинает планироваться, то есть почти одновременно. Затем нам нужно подождать некоторое время, пока не истощатся блоки примерно в ядре, прежде чем можно будет запланировать 3-е ядро, что вполне может быть на уровне 50%, как указано на временной шкале.
В любом случае, я думаю, что анализы 1 и 2, приведенные выше, ясно показывают, что вы получаете большую часть возможностей устройства, основываясь на ограничениях, присущих структуре вашего ядра. (Мы могли бы провести аналогичный анализ на основе регистров, чтобы выяснить, является ли это существенным ограничивающим фактором.) Относительно этого утверждения: "Я действительно не могу понять, почему я не достигаю параллелизма, эквивалентного тому, что указано для gk110s…". Я надеюсь, вы видите что спецификация параллелизма (например, 32 ядра) является максимальной спецификацией, и в большинстве случаев вы столкнетесь с каким-то другим типом машинного ограничения, прежде чем достигнете ограничения на максимальное количество ядер, которые могут выполняться одновременно.
РЕДАКТИРОВАТЬ: в отношении документации и ресурсов, ответ, на который я ссылался выше от Грега Смита, дает некоторые ссылки на ресурсы. Вот еще несколько:
- Руководство по программированию на C содержит раздел об асинхронном параллельном выполнении.
- Презентация GPU Concurrency and Streams от доктора Стивена Ренниха в NVIDIA находится на странице вебинара NVIDIA
До сих пор мой опыт работы с HyperQ был параллелизацией моих ядер в 2-3 (3,5) раза, поскольку ядра обычно больше для немного более сложных вычислений. С маленькими ядрами это другая история, но обычно ядра более сложные.
На это также отвечает Nvidia в своей документации по cuda 5.0, что более сложные ядра уменьшат количество параллелизации.
Но все же, GK110 имеет большое преимущество, просто позволяя это.