Проверка CUDA-сборки на DGX Spark: архитектура GPU, SASS и PTX

Проверка CUDA-сборки на DGX Spark: архитектура GPU, SASS и PTX

Проверка CUDA-сборки на DGX Spark: архитектура GPU, SASS и PTX

Если CUDA-проект на DGX Spark компилируется, но при запуске ядра возникает ошибка no kernel image is available for execution on the device, первым делом проверьте, для каких архитектур GPU собраны артефакты, а не переустанавливайте всё окружение. Есть и менее очевидная проблема: приложение всё время работает за счёт JIT-компиляции PTX, хотя в документации к релизу ошибочно указано, что сборка содержит нативный код устройства. В этой статье небольшая автономная программа связывает определение целевой архитектуры, проверку артефактов и испытание двух путей выполнения в единую процедуру приёмки.

1. Разделяйте архитектуру хоста и целевую архитектуру GPU

DGX Spark построен на NVIDIA GB10 Grace Blackwell. Архитектура хоста — ARM64, операционная система — NVIDIA DGX OS на базе Ubuntu 24.04 с предустановленным стеком CUDA. После оплаты данные для подключения можно посмотреть в консоли, а дальнейшую работу выполнять по SSH.

ARM64 описывает архитектуру исполняемых файлов на стороне CPU, а вычислительная способность CUDA — целевую архитектуру кода для GPU. Это разные условия совместимости. Если file показывает AArch64, это подтверждает лишь правильный формат хостовой части, но не наличие пригодных для выполнения ядер внутри файла.

SASS — машинный код для целевого GPU, а PTX — промежуточное представление, которое драйвер может скомпилировать во время выполнения. Релизный пакет может содержать оба варианта, но проверять их нужно по отдельности. Успешный запуск в обычном режиме не означает, что проверены оба пути.

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

2. Запрашивайте фактическую вычислительную способность, а не угадывайте номер архитектуры

Сначала создайте отдельный рабочий каталог и сохраните сведения о системе и инструментах сборки. Версия CUDA в выводе nvidia-smi отражает возможности драйвера, а не версию инструментария, к которому относится текущий nvcc. Поэтому нужны оба вывода.

mkdir -p "$HOME/cuda-artifact-check"
cd "$HOME/cuda-artifact-check"
uname -m > host-arch.txt
nvidia-smi > driver.txt
nvcc --version > toolkit.txt
nvcc --list-gpu-code > supported-targets.txt

Сохраните следующий код в probe.cu. Программа через CUDA Runtime запрашивает вычислительную способность устройства с индексом ноль. При ошибке она возвращает ненулевой код завершения, а не выводит идентификатор архитектуры, который можно было бы ошибочно использовать дальше.

#include <cuda_runtime.h>
#include <cstdio>

int main() {
    cudaDeviceProp p{};
    cudaError_t e = cudaGetDeviceProperties(&p, 0);
    if (e != cudaSuccess) {
        std::fprintf(stderr, "%s\n", cudaGetErrorString(e));
        return 1;
    }
    std::printf("%d%d\n", p.major, p.minor);
    return 0;
}
nvcc probe.cu -o probe
CC=$(./probe) || exit 1
printf '%s\n' "$CC" > compute-capability.txt
grep -Fx "sm_${CC}" supported-targets.txt || exit 1

Идентификатор целевой архитектуры GB10 здесь намеренно не задан жёстко. Если в списке инструментария нет полученной цели sm_, сначала уточните, какие архитектуры он поддерживает. Нельзя просто подставить более старый номер и продолжить выпуск. Если программа проверки завершается с ошибкой, сначала устраните проблемы доступа к устройству или библиотек среды выполнения и лишь затем переходите к компиляции.

3. Явно включайте SASS и PTX в сборку и проверяйте результат

Сохраните следующий код в check.cu. Программа запускает всего один поток, который записывает целое число, затем копирует его на хост и проверяет результат. Ошибки проверяются после каждого вызова CUDA, чтобы сбой запуска ядра не сопровождался ложным сообщением об успехе.

#include <cuda_runtime.h>
#include <cstdio>

#define CUDA_OK(call) do { \
    cudaError_t e = (call); \
    if (e != cudaSuccess) { \
        std::fprintf(stderr, "%s\n", cudaGetErrorString(e)); \
        return 1; \
    } \
} while (0)

__global__ void write_value(int* p) {
    *p = 7;
}

int main() {
    int* device = nullptr;
    int host = 0;
    CUDA_OK(cudaMalloc(&device, sizeof(int)));
    write_value<<<1, 1>>>(device);
    CUDA_OK(cudaGetLastError());
    CUDA_OK(cudaDeviceSynchronize());
    CUDA_OK(cudaMemcpy(&host, device, sizeof(int),
                       cudaMemcpyDeviceToHost));
    CUDA_OK(cudaFree(device));
    if (host != 7) return 2;
    std::puts("PASS");
    return 0;
}

В том же рабочем каталоге выполните команды, явно указав генерацию и нативного кода, и PTX:

CC=$(cat compute-capability.txt)
nvcc -O2 \
  -gencode "arch=compute_${CC},code=[sm_${CC},compute_${CC}]" \
  check.cu -o check

file check > binary-format.txt
cuobjdump --list-elf ./check > elf-list.txt
cuobjdump --dump-sass ./check > sass.txt
cuobjdump --dump-ptx ./check > ptx.txt

Убедитесь, что список ELF и вывод SASS содержат нужную целевую архитектуру и ядро, а вывод PTX — директивы .version, .target и точку входа ядра. Пустые файлы, несовпадение архитектуры или отсутствие ожидаемого ядра должны блокировать приёмку. Одной проверки кодов завершения команд недостаточно.

Проверяйте итоговый файл, а не промежуточные объектные файлы

В реальном проекте могут быть этапы компоновки со статическими библиотеками, компоновки кода устройства или упаковки. Наличие PTX в объектном файле ещё не доказывает, что он сохранился в итоговом поставляемом файле. Повторите проверку для конечного исполняемого файла и фактически загружаемых разделяемых библиотек, а также зафиксируйте их хеши.

4. Проверяйте нативное выполнение и JIT-компиляцию по отдельности

Сначала запретите JIT-компиляцию PTX, чтобы текущий артефакт выполнялся только за счёт нативного кода устройства. Затем принудительно включите путь через PTX и отключите дисковый кеш компиляции. Для каждого испытания запускайте новый процесс, чтобы уже загруженные в процесс модули не влияли на результат.

env -u CUDA_FORCE_PTX_JIT \
  CUDA_DISABLE_PTX_JIT=1 \
  ./check > native.log 2>&1
native_rc=$?

env -u CUDA_DISABLE_PTX_JIT \
  CUDA_FORCE_PTX_JIT=1 CUDA_CACHE_DISABLE=1 \
  ./check > ptx-jit.log 2>&1
ptx_rc=$?

printf 'native=%s\nptx_jit=%s\n' \
  "$native_rc" "$ptx_rc" > execution-status.txt

cat execution-status.txt native.log ptx-jit.log
test "$native_rc" -eq 0 && test "$ptx_rc" -eq 0

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

Если нативное выполнение завершается с ошибкой, а PTX работает, в первую очередь перепроверьте целевую архитектуру нативного кода и финальную компоновку. Если нативный код работает, а PTX — нет, проверьте наличие PTX и поддержку его версии драйвером. Если не работают оба пути, исследуйте инструменты сборки, библиотеки среды выполнения и состояние устройства с учётом конкретных ошибок. Постоянная смена параметров компиляции не заменяет поиск причины.

5. Сделайте результаты проверки обязательным условием выпуска

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

Для каждого релиза рекомендуется сохранять четыре группы подтверждающих данных:

  • Архитектура хоста, вывод драйвера, версия инструментария и фактические команды компиляции.
  • Вычислительная способность GPU и результаты проверки SASS и PTX в итоговых файлах.
  • Коды завершения и журналы для обоих путей выполнения, а также записи о проверке результатов приложения.
  • Версия исходного кода, SHA-256 итоговых файлов и список динамических зависимостей.

Команда sha256sum ./check позволяет однозначно зафиксировать файл, прошедший эту приёмку. После повторной компоновки приёмку нужно провести заново, даже если имя файла не изменилось. При использовании контейнеров проверяйте инструменты сборки и итоговые файлы внутри фактически поставляемого образа, а испытания выполнения проводите в реальном окружении запуска.

PTX — это путь обеспечения совместимости, а не гарантия работы со всеми драйверами. Тем более один успешный тест не позволяет делать выводы о работе на других устройствах. Критерий допуска к выпуску следует формулировать явно: «Проверка пройдена с текущим драйвером, текущими артефактами и для охваченных тестами точек входа». При смене архитектуры или инструментария подтверждающие данные нужно собирать заново, а не использовать старые журналы.

Часто задаваемые вопросы

Доказывает ли запуск ARM64-файла правильность целевой архитектуры GPU?

Нет. ARM64 определяет совместимость кода CPU. Код устройства должен отдельно поддерживать compute capability GPU. Нужны проверка SASS и PTX и запуск ядра.

Гарантирует ли наличие PTX работу с любым драйвером?

Нет. Драйвер должен поддерживать версию PTX и требования целевой архитектуры. Успешный принудительный JIT проверяет только текущий артефакт с текущим драйвером.

Выделенный физический сервер EPYC 7543P — от $472.5/мес.

Настроить физический сервер TcpNode Посмотреть цены TcpNode за месяц

Физические серверы с конфигурацией на выбор

Полное выделенное оборудование AMD EPYC 7543P. Перед настройкой можно проверить месячную цену, дополнительные опции и сумму к оплате за выбранный период.

Настроить физический сервер TcpNode