Почему ссылка на среду выполнения cuda по-прежнему необходима для пользователя библиотеки даже при компиляции с -cudart static - PullRequest
0 голосов
/ 12 июля 2020

У меня есть простой код cuda, который я компилирую в библиотеку stati c с использованием nvcc, и некоторый пользовательский код, который я компилирую с g++ и связываю с ранее скомпилированной библиотекой stati c. При попытке установить ссылку я получаю ошибки компоновщика для таких вещей, как cudaMalloc, даже если я использую параметр -cudart static в командной строке nvcc компиляции.

Вот мой код:

//kern.hpp
#include <cstddef>

class Kern
{
    private:
        float* d_data;
        size_t size;

    public:
        Kern(size_t s);
        ~Kern();
        void set_data(float *d); 
};
//kern.cu
#include <iostream>
#include <kern.hpp>

__global__ void kern(float* data, size_t size)
{
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if(idx < size) 
    {
        data[idx] = 0;
    }
} 

Kern::Kern(size_t s) : size(s)
{
    cudaMalloc((void**)&d_data, size*sizeof(float));
}

Kern::~Kern()
{
    cudaFree(d_data);
}

void Kern::set_data(float* d)
{
    size_t grid_size = size;
    std::cout << "Starting kernel with grid size " << grid_size << " and block size " << 1 <<
        std::endl;
    kern<<<grid_size, 1>>>(d_data, size);
    cudaError_t err = cudaGetLastError();
    if(err != cudaSuccess)
        std::cout << "ERROR: " << cudaGetErrorString(err) << std::endl;
    cudaDeviceSynchronize();
    cudaMemcpy((void*)d, (void*)d_data, size*sizeof(float), cudaMemcpyDeviceToHost);
    cudaDeviceSynchronize();
}
//main.cpp
#include <iostream>
#include <kern.hpp>

int main(int argc, char** argv)
{
    std::cout << "starting" << std::endl;
    Kern k(256);
    float arr[256];
    k.set_data(arr);
    bool ok = true;
    for(int i = 0; i < 256; ++i) ok &= arr[i] == 0;
    std::cout << (ok ? "done" : "wrong") << std::endl;
}

Я компилирую керн с помощью nvcc следующим образом:

nvcc -I ./ -lib --compiler-options '-fPIC' -o libkern.a kern.cu -cudart static

И затем main с g++ следующим образом:

g++ -o main main.cpp -I ./ -L. -L/opt/cuda/lib64 -lkern

Что дает ошибки:

/usr/bin/ld: ./libkern.a(tmpxft_00001d30_00000000-8_kern.o): in function `Kern::Kern(unsigned long)':
tmpxft_00001d30_00000000-5_kern.cudafe1.cpp:(.text+0x4d): undefined reference to `cudaMalloc'
/usr/bin/ld: ./libkern.a(tmpxft_00001d30_00000000-8_kern.o): in function `Kern::~Kern()':
tmpxft_00001d30_00000000-5_kern.cudafe1.cpp:(.text+0x6b): undefined reference to `cudaFree'
/usr/bin/ld: ./libkern.a(tmpxft_00001d30_00000000-8_kern.o): in function `Kern::set_data(float*)':
tmpxft_00001d30_00000000-5_kern.cudafe1.cpp:(.text+0x152): undefined reference to `__cudaPushCallConfiguration'
/usr/bin/ld: tmpxft_00001d30_00000000-5_kern.cudafe1.cpp:(.text+0x175): undefined reference to `cudaGetLastError'
/usr/bin/ld: tmpxft_00001d30_00000000-5_kern.cudafe1.cpp:(.text+0x1a1): undefined reference to `cudaGetErrorString'
/usr/bin/ld: tmpxft_00001d30_00000000-5_kern.cudafe1.cpp:(.text+0x1c6): undefined reference to `cudaDeviceSynchronize'
/usr/bin/ld: tmpxft_00001d30_00000000-5_kern.cudafe1.cpp:(.text+0x1ee): undefined reference to `cudaMemcpy'
/usr/bin/ld: tmpxft_00001d30_00000000-5_kern.cudafe1.cpp:(.text+0x1f3): undefined reference to `cudaDeviceSynchronize'
/usr/bin/ld: ./libkern.a(tmpxft_00001d30_00000000-8_kern.o): in function `__cudaUnregisterBinaryUtil()':
tmpxft_00001d30_00000000-5_kern.cudafe1.cpp:(.text+0x24e): undefined reference to `__cudaUnregisterFatBinary'
/usr/bin/ld: ./libkern.a(tmpxft_00001d30_00000000-8_kern.o): in function `__nv_init_managed_rt_with_module(void**)':
tmpxft_00001d30_00000000-5_kern.cudafe1.cpp:(.text+0x269): undefined reference to `__cudaInitModule'
/usr/bin/ld: ./libkern.a(tmpxft_00001d30_00000000-8_kern.o): in function `__device_stub__Z4kernPfm(float*, unsigned long)':
tmpxft_00001d30_00000000-5_kern.cudafe1.cpp:(.text+0x305): undefined reference to `__cudaPopCallConfiguration'
/usr/bin/ld: ./libkern.a(tmpxft_00001d30_00000000-8_kern.o): in function `__nv_cudaEntityRegisterCallback(void**)':
tmpxft_00001d30_00000000-5_kern.cudafe1.cpp:(.text+0x430): undefined reference to `__cudaRegisterFunction'
/usr/bin/ld: ./libkern.a(tmpxft_00001d30_00000000-8_kern.o): in function `__sti____cudaRegisterAll()':
tmpxft_00001d30_00000000-5_kern.cudafe1.cpp:(.text+0x44b): undefined reference to `__cudaRegisterFatBinary'
/usr/bin/ld: tmpxft_00001d30_00000000-5_kern.cudafe1.cpp:(.text+0x47c): undefined reference to `__cudaRegisterFatBinaryEnd'
/usr/bin/ld: ./libkern.a(tmpxft_00001d30_00000000-8_kern.o): in function `cudaError cudaLaunchKernel<char>(char const*, dim3, dim3, void**, unsigned long, CUstream_st*)':
tmpxft_00001d30_00000000-5_kern.cudafe1.cpp:(.text+0x4d9): undefined reference to `cudaLaunchKernel'
collect2: error: ld returned 1 exit status

Но если я сделаю следующее:

g++ -o main main.cpp -I ./ -L. -L/opt/cuda/lib64 -lkern -lcudart

все работает. Мой вопрос в том, что, поскольку у меня есть -cudart static в скомпилированной строке nvcc, не должны ли libkern.a уже иметь символы для среды выполнения cuda? Почему -lcudart по-прежнему необходим в строке g++?

Кроме того, если я изменю libkern.a на общий объект, не будет работать связь со средой выполнения cuda в строке g++. То есть работает следующее:

nvcc -I ./ -shared --compiler-options '-fPIC' -o libkern.so kern.cu -cudart static
g++ -o main main.cpp -I ./ -L. -L/opt/cuda/lib64 -lkern

Почему версия библиотеки stati c не работает, а версия общего объекта работает?

Обратите внимание, что я пробовал описанный выше сценарий ios после замены -cudart static на -lcudart_static в строке nvcc, и после этой замены не произошло никаких изменений в поведении. Этого и следовало ожидать, поскольку два варианта по сути делают одно и то же, правильно?

Я на linux.

nvcc --version
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2019 NVIDIA Corporation
Built on Wed_Oct_23_19:24:38_PDT_2019
Cuda compilation tools, release 10.2, V10.2.89
g++ --version
g++ (GCC) 10.1.0
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Любая помощь и / или разъяснения приветствуются .

1 Ответ

2 голосов
/ 12 июля 2020

Если вы изучите документацию nv cc , совершенно очевидно, что опция -lib создает библиотеку stati c (и не указывает связывание), тогда как опция -shared создает разделяемая библиотека и определяет связывание. Например, выдержка:

4.2.2.1. --link (-link) Указать поведение по умолчанию: компилировать и связывать все входные файлы.

4.2.2.2. --lib (-lib) При необходимости скомпилировать все входные файлы в объектные файлы и добавить результаты в указанный выходной файл библиотеки.

4.2.3.11. --shared (-shared) Создать общую библиотеку во время связывания . Используйте параметр --linker-options, когда для большего контроля требуются другие параметры компоновщика.

Я считаю, что это более или менее согласуется с типичным использованием gcc / g ++. Если вы выполните поиск в Google по запросу «g ++ create stati c library», вы получите любое количество ссылок , которые указывают, что вы должны в основном сделать это:

g++ -c my_source_file.cpp ...
ar ...

в других словами, указывается компиляция источника в объект, но не указывается привязка. Чтобы выбрать один пример, cudaMalloc является частью библиотеки времени выполнения CUDA, и соединение с ней будет выполнено на этапе компоновки.

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

В сочетании с этим, я считаю, что вы хотите сделать здесь «частичное» связывание или инкрементное связывание. Выполнение части финальной фазы компоновки перед последней фазой компоновки.

Компоновщик GNU (опять же, то, что nvcc будет использовать, под капотом, на linux, по умолчанию) поддерживает это , поэтому, если мы оставим в стороне любые заботы о компиляции кода перемещаемого устройства, должна быть возможность делать то, что вы хотите, следующим образом:

$ nvcc  -Xcompiler '-fPIC' -I.  -c kern.cu
$ ld -o kern.ro -r kern.o -L/usr/local/cuda/lib64 -lcudart_static -lculibos
$ ar rs libkern.a kern.ro
ar: creating libkern.a
$ g++ -o main main.cpp  -I ./ -L.  -lkern -lpthread -lrt -ldl
$ cuda-memcheck ./main
========= CUDA-MEMCHECK
starting
Starting kernel with grid size 256 and block size 1
done
========= ERROR SUMMARY: 0 errors
$

Примечания:

  1. -lpthread -lrt -ldl - стандартные библиотечные зависимости cudart / culibos, поэтому они должны быть предусмотрены на этапе окончательной компоновки, но они не зависят от каких-либо элементов набора инструментов CUDA. Если вы хотите, чтобы эти зависимости также были удалены из инкрементально связанного объекта, я рассматриваю это как отдельный вопрос, не связанный с CUDA.

  2. Шаг архивирования (создание библиотеки) не принципиально для этого простого случая. Мы могли бы просто передать инкрементально связанный (-r) объект kern.ro непосредственно на последний этап компиляции / компоновки.

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

...