Я столкнулся с некоторыми проблемами при использовании ядра, которое использует некоторые структуры, которые я определил в С++. Ошибка, которую выдает 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 в ядре, я получаю ошибку невыровненного доступа.
Итак, у меня есть три вопроса по этому вопросу:
Редактировать:
Благодаря принятому ответу я смог понять причину проблемы, с которой я столкнулся. 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 байтов заполнения в ЦП.
Отлично, это очень помогло, решило мою проблему и, возможно, поможет другим пользователям в будущем.
Архитекторы CUDA очень стараются обеспечить идентичность структур между хостом и устройством. Конечно, есть множество способов сломать это, и ответ указывает на очевидный способ сделать это: использовать наличие или отсутствие CUDA или дифференциацию хоста/устройства CUDA, чтобы сделать что-то другое, что влияет на выравнивание или размер. Это действительно плохая идея, к сожалению, кажется, что ваша версия Eigen делает это. Если основной/верхний Eigen также делает это, я бы подумал о регистрации проблемы с Eigen.
Я настоятельно рекомендую использовать текущую ветку разработки («по умолчанию») или дождаться Eigen 3.4 при использовании Eigen с CUDA. В Eigen 3.3 CUDA — это официально все еще экспериментальный. Если у вас также есть проблемы с веткой по умолчанию, я также рекомендую вам сообщить об ошибке.
@talonmies Я сомневаюсь, что без ссылки на ваше предыдущее предупреждение ваш комментарий поможет будущим читателям ...





Такая разница связана с тем, как 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 нет никаких гарантий по этому поводу).
Спасибо! Это большая помощь и на самом деле указывает на другое направление того, что я думал. Вы сказали, что я должен знать об этих низкоуровневых копиях. Должен ли я сделать это по-другому?
EIGEN_DONT_VECTORIZE не подразумевает EIGEN_DONT_ALIGN (подразумевается только наоборот)! По крайней мере, в ветке разработки выравнивание с CUDA должно работать корректно из коробки.
@chtz вы правы, но удаление векторизации также устраняет требование выравнивания, которое накладывает векторизация (в данном случае 2 x double, 16 байтов), и выравнивание всей матрицы устанавливается на основе значения EIGEN_MAX_ALIGN_BYTES (EIGEN_DONT_ALIGN теперь устарело и эквивалентно EIGEN_MAX_ALIGN_BYTES=0).
Этот ответ помог мне найти решение, которое мне подходит. Я собираюсь отметить его как принятый ответ и отредактировать вопрос, решение которого я реализовал.
я вас об этом предупреждал....