NVIDIA CUDA Toolkit 12.4 Derleyicisini Kullanarak Çalışma Zamanı Fatbin Oluşturma

6


CUDA Toolkit 12.4, çalışma zamanında yağ kutuları oluşturmak için yeni bir nvFatbin kitaplığını tanıttı. Fatbins, NVIDIA cihaz kodu olarak da bilinir şişman ikili dosyalar, farklı mimarileri depolamak için birden fazla kod sürümünü depolayan kaplardır. NVIDIA bunları özellikle farklı GPU mimarilerine yönelik kodları paketlemek için kullanır; sm_61 Ve sm_90.

Şimdiye kadar bir yağ kutusu oluşturmak için komut satırı aracına güvenmek zorundaydınız fatbinarydinamik kod üretimi için uygun değildi. Bu, oluşturulan kodu bir dosyaya koyacağınız için, yağ kutularının dinamik olarak oluşturulmasını zorlaştırdı. fatbinary ile exec veya benzeri bir şey yapın ve ardından çıktıları işleyin. Bu, yağ kutularını dinamik olarak üretmenin zorluğunu önemli ölçüde arttırdı ve çeşitli kaplar aracılığıyla yağ kutularını taklit etme girişimlerine yol açtı.

CUDA Toolkit 12.4, bir fatbin'in programlı olarak oluşturulmasını sağlayan yeni bir kütüphane olan nvFatbin'i sunarak bu görevi büyük ölçüde kolaylaştırıyor. Artık dosyalara yazmak yok, artık aramak yok execArtık komut satırı çıktılarını ayrıştırmaya ve çıktı dosyalarını dizinlerden almaya gerek yok.

Yeni kütüphane çalışma zamanı fatbin oluşturma desteği sunuyor

nvFatbin kitaplığının kullanımı, NVRTC, nvPTXCompiler ve nvJitLink gibi diğer bilinen kitaplıkların kullanımına benzer. Nvrtc ile birlikte gelen tüm platformlar için nvFatbin kütüphanesinin statik ve dinamik versiyonları bulunmaktadır.

Uygun değerlendirmelerle nvFatbin kitaplığı aracılığıyla oluşturulan yağ kutuları CUDA uyumluluk garantilerine uygundur. Bu yazı öncelikle nvFatbin kütüphanesi aracılığıyla mevcut olan çalışma zamanı fatbin'i oluşturmayı kapsamakta ve uygun olduğunda mevcut komut satırı fatbinary'si ile olan farklılıkları vurgulamaktadır. Kod örnekleri, uyumluluk garantileri ve avantajlarla özelliğin ayrıntılarına dalıyoruz. Ek bir bonus olarak, NVIDIA TensorRT'nin bu özellikten nasıl ve neden yararlanmayı planladığına dair kısa bir bakış sunuyoruz.

CUDA C++, her biri CUDA C++ kodundan PTX, CUBIN veya LTOIR üretebilen NVRTC veya NVCC'ye aktarılabilir.  Bu PTX, CUBIN'ler ve LTOIR, nvFatbin'e veya fatbinary'ye aktarılabilir; bunların her ikisi de girdilerden yağ kutuları üretebilir.
Şekil 1. Mevcut fatbinary komut satırı aracının nvFatbin kütüphanesinin kullanımıyla karşılaştırılması

Çalışma zamanı yağ kutusu oluşturma işleminin nasıl çalıştırılacağı

İlgili cihaz kodu parçalarını yağ ikilisine eklemek için daha sonra başvurulacak tanıtıcıyı oluşturun.

nvFatbinCreate(&handle, numOptions, options);

Giriş türüne bağlı bir işlevi kullanarak, yağ kutusuna yerleştirilecek cihaz kodunu ekleyin.

nvFatbinAddCubin(handle, data, size, arch, name);
nvFatbinAddPTX(handle, data, size, arch, name, ptxOptions);
nvFatbinAddLTOIR(handle, data, size, arch, name, ltoirOptions);

PTX ve LTO-IR (JIT LTO için kullanılan bir ara temsil biçimi) için, JIT derlemesi sırasında kullanılacak ek seçenekleri burada belirtin.

Ortaya çıkan yağ kutusunu alın. Bunu yapmak için açıkça bir arabellek ayırın. Bunu yaparken yeterli alan ayırdığınızdan emin olmak için ortaya çıkan yağ haznesinin boyutunu mutlaka sorgulayın.

nvFatbinSize(linker, &fatbinSize);
void* fatbin = malloc(fatbinSize);
nvFatbinGet(handle, fatbin);

Kolu temizleyin.

nvFatbinDestroy(&handle);

NVCC ile çevrimdışı olarak yağ kutuları oluşturun

NVCC ile çevrimdışı bir yağ kutusu oluşturmak için seçeneği ekleyin -fatbin. Örneğin, dosyaya göre aşağıdaki komut loader.cubir giriş içeren bir yağ kutusu üretir, sm_90kodun LTO-IR sürümünü içeren, loader.fatbin:

nvcc -arch lto_90 -fatbin loader.cu

Eğer belirtirseniz -arch=sm_90nvcc, hem PTX hem de CUBIN (SASS) içeren bir yağ kutusu oluşturur. Nesne, aşağıdakiler için hem spesifik SASS talimatlarını içerir: sm_90 ve PTX, daha sonra herhangi bir mimariye >= 90 JIT olabilir:

nvcc -arch sm_90 -fatbin loader.cu

Birden çok girişe sahip bir fatbin oluşturmak için birden çok mimariyi belirtin. -gencode:

nvcc -gencode arch=compute_80,code=sm_80 -gencode arch=compute_90,code=sm_90 -gencode arch=compute_52,code=compute_52

Bu, aşağıdakileri içeren bir yağ kutusu oluşturur: sm_80 ELF, sm_90 ELFVe compute_52 PTX. Kullanabilirsiniz cuobjdump yağ kutusunun içeriğini görmek için.

Çalışma zamanında yağ depoları oluşturun

Daha önce açıklanan çevrimdışı derleme ve çalışma zamanı yağ kutusu oluşturma modeline ek olarak (Şekil 1), yağ kutuları, nesne kodunu oluşturmak için NVRTC kullanılarak çalışma zamanında tamamen oluşturulabilir. Çalışma zamanında nvFatbin API'si kullanılarak yağ kutusuna eklenirler. Aşağıdaki kod örneğinde nvFatbin API'lerinin kullanımına ilişkin ilgili değişiklikler bulunmaktadır.

#include 
#include 
#include 
#include 
#include 

#define NUM_THREADS 128
#define NUM_BLOCKS 32

#define NVRTC_SAFE_CALL(x)                                        \
do {                                                              \
  nvrtcResult result = x;                                        \
  if (result != NVRTC_SUCCESS) {                                 \
	 std::cerr << "\nerror: " #x " failed with error "           \
			  << nvrtcGetErrorString(result) << '\n';            \
	 exit(1);                                                    \
  }                                                              \
} while(0)

#define CUDA_SAFE_CALL(x)                                         \
do {                                                              \
  CUresult result = x;                                           \
  if (result != CUDA_SUCCESS) {                                  \
	 const char *msg;                                            \
	 cuGetErrorName(result, &msg);                               \
	 std::cerr << "\nerror: " #x " failed with error "           \
			  << msg << '\n';                                    \
	 exit(1);                                                    \
  }                                                              \
} while(0)

#define NVFATBIN_SAFE_CALL(x)                            \
do                                                       \
{                                                        \
  nvFatbinResult result = x;                            \
  if (result != NVFATBIN_SUCCESS)                       \
  {                                                     \
	 std::cerr << "\nerror: " #x " failed with error "  \
			  << nvFatbinGetErrorString(result) << '\n';\
	 exit(1);                                           \
  }                                                     \
} while (0)

const char *fatbin_saxpy = "                                  \n\
__device__  float compute(float a, float x, float y) {        \n\
return a * x + y;                                             \n\
}                                                             \n\
															 \n\
extern \"C\" __global__                                       \n\
void saxpy(float a, float *x, float *y, float *out, size_t n) \n\
{                                                             \n\
size_t tid = blockIdx.x * blockDim.x + threadIdx.x;           \n\
if (tid < n) {                                                \n\
  out[tid] = compute(a, x[tid], y[tid]);                     \n\
}                                                             \n\
}                                                             \n";

size_t process(const void* input, const char* input_name, void** output, const char* arch)
{
// Create an instance of nvrtcProgram with the code string.
nvrtcProgram prog;
NVRTC_SAFE_CALL(
nvrtcCreateProgram(&prog,         // prog
	 (const char*) input,                       // buffer
	 input_name,                  // name
	 0,                           // numHeaders
	 NULL,                        // headers
	 NULL));                      // includeNames

// specify that LTO IR should be generated for LTO operation
const char *opts[1];
opts[0] = arch;
nvrtcResult compileResult = nvrtcCompileProgram(prog,  // prog
			  1,     // numOptions
			  opts); // options
// Obtain compilation log from the program.
size_t logSize;
NVRTC_SAFE_CALL(nvrtcGetProgramLogSize(prog, &logSize));
char *log = new char[logSize];
NVRTC_SAFE_CALL(nvrtcGetProgramLog(prog, log));
std::cout << log << '\n';
delete[] log;
if (compileResult != NVRTC_SUCCESS) {
exit(1);
}
// Obtain generated CUBIN from the program.
size_t CUBINSize;
NVRTC_SAFE_CALL(nvrtcGetCUBINSize(prog, &CUBINSize));
char *CUBIN = new char[CUBINSize];
NVRTC_SAFE_CALL(nvrtcGetCUBIN(prog, CUBIN));
// Destroy the program.
NVRTC_SAFE_CALL(nvrtcDestroyProgram(&prog));
*output = (void*) CUBIN;
return CUBINSize;
}

int main(int argc, char *argv[])
{
void* known = NULL;
size_t known_size = process(fatbin_saxpy, "fatbin_saxpy.cu", &known, "-arch=sm_52");



CUdevice cuDevice;
CUcontext context;
CUmodule module;
CUfunction kernel;
CUDA_SAFE_CALL(cuInit(0));
CUDA_SAFE_CALL(cuDeviceGet(&cuDevice, 0));
CUDA_SAFE_CALL(cuCtxCreate(&context, 0, cuDevice));

// Dynamically determine the arch to make one of the entries of the fatbin with
int major = 0;
int minor = 0;
CUDA_SAFE_CALL(cuDeviceGetAttribute(&major, 
				 CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR, cuDevice));
CUDA_SAFE_CALL(cuDeviceGetAttribute(&minor, 
				 CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR, cuDevice));
int arch = major*10 + minor;
char smbuf[16];
sprintf(smbuf, "-arch=sm_%d", arch);

void* dynamic = NULL;
size_t dynamic_size = process(fatbin_saxpy, "fatbin_saxpy.cu", &dynamic, smbuf);
sprintf(smbuf, "%d", arch);

// Load the dynamic CUBIN and the statically known arch CUBIN
// and put them in a fatbin together.
nvFatbinHandle handle;
const char* fatbin_options[] = {"-cuda"};
NVFATBIN_SAFE_CALL(nvFatbinCreate(&handle, fatbin_options, 1));

NVFATBIN_SAFE_CALL(nvFatbinAddCubin(handle, 
						  (void *)dynamic, dynamic_size, smbuf, "dynamic"));
NVFATBIN_SAFE_CALL(nvFatbinAddCubin(handle, 
						  (void *)known, known_size, "52", "known"));

size_t fatbinSize;
NVFATBIN_SAFE_CALL(nvFatbinSize(handle, &fatbinSize));
void *fatbin = malloc(fatbinSize);
NVFATBIN_SAFE_CALL(nvFatbinGet(handle, fatbin));
NVFATBIN_SAFE_CALL(nvFatbinDestroy(&handle));

CUDA_SAFE_CALL(cuModuleLoadData(&module, fatbin));
CUDA_SAFE_CALL(cuModuleGetFunction(&kernel, module, "saxpy"));
				 
// Generate input for execution, and create output buffers.
#define NUM_THREADS 128
#define NUM_BLOCKS 32
size_t n = NUM_THREADS * NUM_BLOCKS;
size_t bufferSize = n * sizeof(float);
float a = 5.1f;
float *hX = new float[n], *hY = new float[n], *hOut = new float[n];
for (size_t i = 0; i < n; ++i) {
  hX[i] = static_cast(i);
  hY[i] = static_cast(i * 2);
}
CUdeviceptr dX, dY, dOut;
CUDA_SAFE_CALL(cuMemAlloc(&dX, bufferSize));
CUDA_SAFE_CALL(cuMemAlloc(&dY, bufferSize));
CUDA_SAFE_CALL(cuMemAlloc(&dOut, bufferSize));
CUDA_SAFE_CALL(cuMemcpyHtoD(dX, hX, bufferSize));
CUDA_SAFE_CALL(cuMemcpyHtoD(dY, hY, bufferSize));
// Execute SAXPY.
void *args[] = { &a, &dX, &dY, &dOut, &n };
CUDA_SAFE_CALL(
  cuLaunchKernel(kernel,
				 NUM_BLOCKS, 1, 1,    // grid dim
				 NUM_THREADS, 1, 1,   // block dim
				 0, NULL,             // shared mem and stream
				 args, 0));           // arguments
CUDA_SAFE_CALL(cuCtxSynchronize());
// Retrieve and print output.
CUDA_SAFE_CALL(cuMemcpyDtoH(hOut, dOut, bufferSize));
				 
for (size_t i = 0; i < n; ++i) {
  std::cout << a << " * " << hX[i] << " + " << hY[i]
			<< " = " << hOut[i] << '\n';
}
// Release resources.
CUDA_SAFE_CALL(cuMemFree(dX));
CUDA_SAFE_CALL(cuMemFree(dY));
CUDA_SAFE_CALL(cuMemFree(dOut));
CUDA_SAFE_CALL(cuModuleUnload(module));
CUDA_SAFE_CALL(cuCtxDestroy(context));
delete[] hX;
delete[] hY;
delete[] hOut;
// Release resources.
free(fatbin);
delete[] ((char*)known);
delete[] ((char*)dynamic);

return 0;
}

Örneğin tamamını görmek için bkz. nvFatbin.

Fatbin nesne uyumluluğu

nvFatbin kütüphanesi doğrudan giriş dosyalarından bir fatbin oluşturur. Kendisi herhangi bir bağlantı veya derleme yapmaz ve CUDA sürücüsüne güvenmez. GPU'su olmayan sistemlerde bile çalıştırılabilir.

Girişleri işleyen nvFatbin kütüphanesinin araç seti sürümüdür ve önemli olan derlenmiş girişlerin araç seti sürümüdür.

nvFatbin kütüphanesi, sürümden bağımsız olarak eski girişlere yönelik desteği korur. Ancak bu, bir kap formatı olarak bir yağ kutusunun kullanılmasından bağımsız olan söz konusu versiyonların yüklenmesi sırasında sürücü tarafından konulan herhangi bir kısıtlamanın yerini almaz. Bununla birlikte, oluşturulan çıktı yağ kutusu yalnızca aynı ana sürüme sahip veya nvFatbin kitaplığınınkinden daha büyük bir CUDA sürücüsüyle yükleme için uyumludur.

Ayrıca nvFatbin, aynı ana sürümde oldukları sürece daha yeni NVCC veya NVRTC'den gelen girişleri işleyebilir. Bu nedenle, hedef sistemdeki nvFatbin kitaplığı sürümü, her zaman, herhangi bir girdiyi oluşturmak için kullanılan araç setinin en yeni sürümüyle en azından aynı veya daha yeni ana sürüm olmalıdır.

Örneğin, 12.4 ile birlikte gelen nvFatbin, herhangi bir CUDA Toolkit 12.X veya önceki sürümü tarafından oluşturulan kodu destekleyebilir, ancak CUDA Toolkit 13.0 veya sonraki sürümü tarafından oluşturulan herhangi bir kodla çalışacağı garanti edilmez.

Fatbinary, çevrimdışı araç ve nvFatbin, aynı çıktı dosyası türünü üretir ve aynı girdi türlerini kullanır; dolayısıyla çevrimiçi ve çevrimdışı araçlar belirli durumlarda birbirinin yerine kullanılabilir. Örneğin, NVCC ile derlenmiş bir CUBIN, çalışma zamanında nvFatbin tarafından bir yağ kutusuna yerleştirilebilir ve NVRTC ile derlenmiş bir CUBIN, çevrimdışı araç yağ ikilisi tarafından çevrimdışı bir yağ kutusuna yerleştirilebilir. İki yağ kutusu oluşturma aracı da aynı uyumluluk kurallarına uyar.

CUDA ve nvFatbin uyumluluğu

NVIDIA yalnızca nvFatbin'in, nvFatbin kitaplığıyla aynı ana sürüme veya daha düşük bir sürüme sahip CUDA Araç Takımından alınan kodla oluşturulan girişlerle uyumlu olacağını garanti eder. Gelecekteki bir CUDA Toolkit 13 sürümüyle oluşturulan PTX ile 12.4'ten nvFatbin'i kullanarak bir yağ kutusu oluşturmaya çalışırsanız bir başarısızlıkla karşılaşabilirsiniz. Ancak 11.8 gibi eski CUDA Araç Setlerinden gelen girdilerle uyumluluğu desteklemelidir.

CUDA küçük sürüm uyumluluğu

Daha önce belirtildiği gibi nvFatbin kütüphanesi, küçük sürüme bakılmaksızın aynı CUDA araç seti ana sürümündeki tüm girişlerle uyumlu olacaktır. Bu, 12.4 için nvFatbin'in 12.5'ten itibaren girişlerle uyumlu olacağı anlamına gelir.

Yeni tip fatbin girişlerinin eklenmesi gibi yeni tanıtılan bazı özellikler önceki sürümlerde mevcut olmayacaktır. Ancak bir sürümde halihazırda kabul edilmiş olan herhangi bir format kabul edilmeye devam edecektir.

Geriye dönük uyumluluk

nvFatbin kitaplığı, CUDA araç setinin önceki sürümlerinden gelen girişleri destekler.

Büyük Resim

Artık tüm ana derleyici bileşenlerinin çalışma zamanı eşdeğerleri olduğuna göre, bunların hepsi birbirleriyle nasıl etkileşime giriyor?

nvPTXDerleyici

Çalışma zamanı PTX derleyicisi nvPTXCompiler, bağımsız bir araç olarak mevcuttur ancak kolaylık sağlamak için NVRTC ve nvJitLink'e de entegre edilmiştir. Bir yağ deposuna koymak üzere CUBIN'ler oluşturmak için nvFatbin ile birlikte kullanılabilir.

NVRTC

Çalışma zamanı derleyicisi NVRTC, bir CUDA programını derlemek için kullanılabilir. NvPTXCompiler'ı entegre ederek PTX ve LTO-IR'nin yanı sıra CUBIN'i de destekler, ancak PTX üretebilir ve CUBIN'leri üretmek için nvPTXCompiler'ı manuel olarak kullanabilirsiniz. Bu sonuç formatlarının tümü nvFatbin tarafından bir fatbin'e konabilir.

Çalışma zamanı bağlayıcısı nvJitLink, çalışma zamanında bir CUDA programını derlemek ve bağlamak için NVRTC ile birlikte kullanılabilir. Sonuç, doğrudan sürücü API'leri aracılığıyla çalıştırılabilir veya nvFatbin ile bir fatbin'e yerleştirilebilir.

Çözüm

NvFatbin'in kullanıma sunulmasıyla birlikte esnek kitaplıkları dinamik olarak oluşturmak her zamankinden daha kolay.

TensorRT, hem mevcut mimariler için CUBIN'leri hem de gelecekteki mimariler için PTX'i depolamak istiyor. Bu şekilde, mümkün olduğunda kodun optimize edilmiş sürümleri kullanılırken aynı zamanda uyumlu kalır. Gelecekteki mimariler için belki de ideal olmasa da, mevcut mimariler için en uygun kodu sağlar ve yine de gelecekteki mimarilerle uyumlu olacaktır.

NvFatbin'in kullanıma sunulmasından önce, bunu halletmenin alternatif bir yolunu bulmanız gerekiyordu; bu da, ilgili verileri gereksiz yere dosyalara yazmaya güvenmeyi önlemek için fatbin'e benzer bir çevrimiçi format oluşturmak için çoğaltma çabalarına yol açıyordu.

Artık nvFatbin ile siz ve TensorRT'nin arkasındaki ekip bu işlemi gerçekleştirmek için kütüphaneyi kullanabilir, gereksiz G/Ç işlemlerini önleyebilir ve CUBIN'leri PTX ile depolamak için özel bir formattan kaçınabilirsiniz.

Kaynak: Nvidia

Doğrudan cihazınızda gerçek zamanlı güncellemeleri alın, şimdi abone olun.

Yorumlar