Разные размеры структуры в cpp и CUDA

Я столкнулся с некоторыми проблемами при использовании ядра, которое использует некоторые структуры, которые я определил в С++. Ошибка, которую выдает cuda-memcheck, связана с проблемой выравнивания.

Структура, которую я пытаюсь использовать, содержит некоторые указатели, которые, я думаю, создают проблемы. Я напечатал для консоли размер структуры на стороне C++ и на стороне CUDA, как в функции хоста в файле .cu, так и в ядре. Это дает разные результаты, что объясняет проблему, которую я вижу, но я не уверен, почему это происходит и как это исправить.

Структура, которую я использую, следующая

struct Node {};
struct S
{
    Node *node0;
    Node *node1;
    Node *node2;
    double p0;
    double p1;
    double p2;
    double p3;

    Eigen::Matrix<double, 3, 2> f1;
    Eigen::Matrix<double, 3, 2> f2;
}

Это имеет размер 160 байтов в C++ и 152 байта в CUDA. Для передачи данных я выделяю боковой буфер CUDA и выполняю cudaMemcpy.

std::vector<S> someVector; // Consider it exists
S *ptr;
cudaMalloc(&ptr, sizeof(S) * someVector.size());
cudaMemcpy(ptr, someVector.data(), sizeof(S)*someVector.size(), cudaMemcpyHostToDevice);

что, я думаю, неверно, так как размер в CUDA и C++ различен.

Как только я пытаюсь получить доступ к S::node0, S::node1 или S::node3 в ядре, я получаю ошибку невыровненного доступа.

Итак, у меня есть три вопроса по этому вопросу:

  • Почему размеры разные?
  • Как мне изменить код или выполнить копию, чтобы это работало правильно?
  • Должен ли я иметь боковую структуру CUDA и выполнять специальную копию?

Редактировать: Благодаря принятому ответу я смог понять причину проблемы, с которой я столкнулся. Eigen использует векторизацию, когда это возможно, и запрашивает для этого выравнивание по 16 байтам. Векторизация включается, когда размер объекта Eigen кратен 16 байтам. В моем конкретном случае два Eigen::Matrix<double, 3,2> действительны для векторизации.

Однако в CUDA Eigen не запрашивает выравнивание по 16 байтам.

Поскольку моя структура имеет 4 двойных и 3 указателя, это составляет 56 байтов, что не кратно 16, поэтому в ЦП он должен добавить 8 байтов заполнения, чтобы матрицы Eigen были выравниванием по 16 байтам. В CUDA этого не происходит, поэтому размеры разные.

Решение, которое я реализовал, состоит в том, чтобы добавить 8 байтов заполнения вручную, чтобы структура была одинаковой в ЦП и в CUDA. Это решает проблему и не требует отключения векторизации. Еще одно решение, которое я нашел работающим, — изменить Eigen::Matrix<double,3,2> на 2 Eigen::Matrix<double,3,1>. Eigen::Matrix<double,3,1> не соответствует требованиям к векторизации и поэтому не требует добавления 8 байтов заполнения в ЦП.

я вас об этом предупреждал....

talonmies 23.03.2019 22:13

Отлично, это очень помогло, решило мою проблему и, возможно, поможет другим пользователям в будущем.

jjcasmar 24.03.2019 02:06

Архитекторы CUDA очень стараются обеспечить идентичность структур между хостом и устройством. Конечно, есть множество способов сломать это, и ответ указывает на очевидный способ сделать это: использовать наличие или отсутствие CUDA или дифференциацию хоста/устройства CUDA, чтобы сделать что-то другое, что влияет на выравнивание или размер. Это действительно плохая идея, к сожалению, кажется, что ваша версия Eigen делает это. Если основной/верхний Eigen также делает это, я бы подумал о регистрации проблемы с Eigen.

Robert Crovella 24.03.2019 02:45

Я настоятельно рекомендую использовать текущую ветку разработки («по умолчанию») или дождаться Eigen 3.4 при использовании Eigen с CUDA. В Eigen 3.3 CUDA — это официально все еще экспериментальный. Если у вас также есть проблемы с веткой по умолчанию, я также рекомендую вам сообщить об ошибке.

chtz 24.03.2019 03:44

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

chtz 24.03.2019 03:51
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
5
624
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Такая разница связана с тем, как Eigen запрашивает выравнивание памяти в C++ и CUDA.

В C++ S выравнивается по 16 байтам (вы можете проверить это alignof(S) == 16). Это связано с матрицами Эйгена, которые выровнены по 16 байтам, возможно, из-за использования регистров SSE, которые требуют такого выравнивания. Остальные ваши поля выровнены по 8 байтам (64-битные указатели и двойники).

В заголовочном файле Eigen/Core директива EIGEN_DONT_VECTORIZE включена для CUDA. При проверке документация:

EIGEN_DONT_VECTORIZE - disables explicit vectorization when defined. Not defined by default, unless alignment is disabled by Eigen's platform test or the user defining EIGEN_DONT_ALIGN.

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

Лучший способ решить эту проблему — принудительно выровнять структуру для обеих архитектур. Не очень хорошо владеет CUDA прямо сейчас, я думаю, вы можете сделать это с помощью __align__(16) в CUDA (больше здесь) и с помощью alignas(16) в C++ (начиная с С++ 11). Вы можете определить макрос для использования правильного оператора, если вы разделяете объявление для обоих языков:

#ifdef __CUDACC__
# define MY_ALIGN(x) __align__(x)
#else
# define MY_ALIGN(x) alignas(x)
#endif

struct MY_ALIGN(16) S {
  // ...
};

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

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

jjcasmar 24.03.2019 00:41
EIGEN_DONT_VECTORIZE не подразумевает EIGEN_DONT_ALIGN (подразумевается только наоборот)! По крайней мере, в ветке разработки выравнивание с CUDA должно работать корректно из коробки.
chtz 24.03.2019 03:47

@chtz вы правы, но удаление векторизации также устраняет требование выравнивания, которое накладывает векторизация (в данном случае 2 x double, 16 байтов), и выравнивание всей матрицы устанавливается на основе значения EIGEN_MAX_ALIGN_BYTES (EIGEN_DONT_ALIGN теперь устарело и эквивалентно EIGEN_MAX_ALIGN_BYTES=0).

cbuchart 24.03.2019 08:52

Этот ответ помог мне найти решение, которое мне подходит. Я собираюсь отметить его как принятый ответ и отредактировать вопрос, решение которого я реализовал.

jjcasmar 24.03.2019 13:46

Другие вопросы по теме