C for Metal - logam mulia untuk komputasi pada kartu grafis Intel

Berapa inti prosesor Intel yang Anda miliki di komputer? Jika Anda menggunakan sistem berbasis Intel, maka dalam sebagian besar kasus, Anda perlu menambahkan satu ke jawaban Anda. Hampir semua prosesor Intel - dari Atom hingga Xeon E3, tentu saja, tanpa kehilangan Core, sudah bertahun-tahun memasukkan inti grafis terintegrasi Intel Graphics, yang pada dasarnya adalah prosesor penuh, dan karenanya, tidak hanya mampu menampilkan gambar di layar dan mempercepat video, tetapi juga melakukan perhitungan tujuan umum "biasa". Bagaimana ini bisa digunakan secara efektif? Lihat di bawah luka.



Pertama, kami akan menjelaskan secara singkat mengapa Anda harus mengandalkan Intel GPU. Tentu saja, kinerja CPU dalam sistem hampir selalu secara signifikan melebihi GPU, untuk itu juga merupakan Central Processor.

Tetapi menarik untuk dicatat bahwa kinerja GPU terintegrasi Intel selama dekade terakhir telah tumbuh dalam persentase jauh lebih banyak daripada CPU, dan tren ini akan terus berlanjut dengan munculnya kartu grafis Intel diskrit baru. Selain itu, GPU, berdasarkan arsitekturnya (banyak perangkat eksekusi vektor), jauh lebih cocok untuk melakukan jenis tugas tertentu - pemrosesan gambar, yaitu, pada kenyataannya, untuk melakukan semua jenis operasi pada array data. GPU melakukan ini dengan paralelisasi internal penuh, menghabiskan lebih sedikit energi daripada CPU, dan dalam beberapa kasus bahkan melampauinya dalam kecepatan absolut. Akhirnya, GPU dan CPU dapat bekerja secara paralel, masing-masing pada tugasnya sendiri, memberikan kinerja maksimum dan / atau konsumsi daya minimum dari keseluruhan sistem.

- Ok, Intel. Kami memutuskan untuk menggunakan Intel GPU untuk perhitungan tujuan umum, bagaimana cara melakukannya?
- Cara paling sederhana yang tidak memerlukan pengetahuan khusus dalam grafik (Direct3D dan OpenGL shaders) adalah OpenCL.

Kernel OpenCL adalah platform independen dan akan secara otomatis dijalankan pada semua perangkat komputasi yang tersedia dalam sistem - CPU, GPU, FPGA, dll. Tetapi biaya untuk keserbagunaan tersebut jauh dari kinerja maksimum yang mungkin pada setiap jenis perangkat, dan terutama pada GPU Intel terintegrasi. Di sini kita dapat memberikan contoh: ketika mengeksekusi kode pada GPU Intel apa pun yang mentransposasikan matriks 16x16 byte, keuntungan kinerja pemrograman langsung GPU Intel akan 8 kali lebih tinggi daripada dengan versi OpenCL!

Selain itu, beberapa fungsi yang diperlukan untuk mengimplementasikan algoritma umum (misalnya, "filter lebar" yang menggunakan data dari sekelompok besar piksel dalam satu transformasi tunggal), OpenCL tidak mendukung.

Oleh karena itu, jika Anda memerlukan kecepatan maksimum pada GPU dan \ atau sesuatu yang lebih rumit daripada bekerja secara independen dengan setiap elemen array dan tetangga terdekatnya, maka Intel C for Metal (ICM), alat untuk mengembangkan aplikasi yang berjalan pada Intel Graphics, akan membantu Anda .

ICM - selamat datang di bengkel!


Dalam hal kinerja dan fungsionalitas, ICM dapat dianggap "assembler untuk kartu grafis Intel", dan dalam hal sirkuit dan kegunaan - "analog OpenCL untuk kartu grafis Intel."

Selama bertahun-tahun, ICM telah digunakan secara internal oleh Intel dalam pengembangan produk pemrosesan media pada GPU Intel. Tetapi pada tahun 2018, ICM dirilis ke publik, dan bahkan dengan open source!

Intel C for Metal mendapatkan namanya saat ini beberapa bulan yang lalu, sebelum itu disebut Intel C for Media (akronim ICM yang sama atau hanya CM atau bahkan Cm), dan bahkan sebelumnya - Media Development Framework (MDF). Jadi, jika suatu tempat atas nama komponen, dalam dokumentasi atau dalam komentar open source, nama lama bertemu - jangan khawatir, ini adalah nilai historis.

Jadi, kode aplikasi ICM, seperti di OpenCL, berisi dua bagian: yang "administratif", dieksekusi pada prosesor, dan kernel, dieksekusi pada GPU. Tidak mengherankan, bagian pertama disebut host, dan yang kedua adalah kernel.

Kernel adalah fungsi memproses blok piksel yang diberikan (atau hanya data), ditulis dalam Intel C untuk bahasa Metal dan dikompilasi ke dalam set instruksi GPU Intel (ISA) menggunakan kompiler ICM.

Tuan rumah adalah semacam "manajer tim kernel", yang mengelola proses transfer data antara CPU dan GPU dan melakukan "pekerjaan manajerial" lainnya melalui pustaka runtime ICM Runtime dan driver media Intel GPU.
Alur kerja ICM terperinci terlihat seperti ini:


  • Kode host ICM dikompilasi oleh kompiler x86 C / C ++ apa pun beserta seluruh aplikasi;
  • Kode kernel ICM dikompilasi oleh kompiler ICM menjadi file biner dengan beberapa set instruksi umum (Common ISA);
  • Pada saat runtime, rangkaian umum instruksi JIT ini diterjemahkan ke GPU Intel tertentu;
  • Host ICM memanggil pustaka runtime ICM untuk berkomunikasi dengan GPU dan sistem operasi.

Beberapa poin yang lebih penting dan berguna:

  • Permukaan yang digunakan dalam ICM untuk mewakili / menyimpan data dapat dibagikan dengan DirectX 11 dan 9 (DXVA di Linux).
  • GPU dapat mengambil dan menulis data dari memori video dan memori sistem yang dibagikan dengan CPU. ICM mencakup fungsi-fungsi khusus untuk kedua kasus transfer data di kedua arah. Pada saat yang sama, memori sistem tepat dibagi, dan penyalinan yang sebenarnya di dalamnya tidak diperlukan - untuk ini, yang disebut salinan nol disediakan di ICM.

ICM - di lubang gunung berapi!


Sudah dari nama "C for Iron" itu sendiri, berarti perangkat bahasa sesuai dengan perangkat grafis internal Intel. Artinya, memperhitungkan fakta bahwa kode akan dieksekusi pada beberapa lusin unit eksekusi (Unit Eksekusi) dari kartu grafis, yang masing-masing adalah prosesor vektor penuh yang mampu mengeksekusi beberapa utas secara bersamaan.

Bahasa ICM itu sendiri adalah C ++ dengan beberapa batasan dan ekstensi. Dibandingkan dengan C ++, ICM tidak memiliki ... pointer, alokasi memori, dan variabel statis. Di bawah larangan juga fungsi rekursif. Tetapi ada pemrograman model vektor eksplisit (SIMD): tipe data vektor - vektor, matriks, dan permukaan; operasi vektor pada tipe data ini, kondisi vektor jika / yang lain, dilakukan secara independen untuk setiap elemen vektor; serta fungsi bawaan untuk mengakses fungsi tetap perangkat keras GPU Intel.

Pekerjaan dengan vektor, matriks dan permukaan dalam tugas nyata difasilitasi oleh objek "subset" - dari objek dasar yang sesuai, Anda hanya dapat memilih blok "referensi" yang menarik bagi Anda atau, sebagai kasus khusus, elemen individual dengan topeng.

Sebagai contoh, mari kita lihat kode ICM yang mengimplementasikan filter linier - mengganti nilai
Warna RGB setiap piksel berdasarkan nilai rata-rata dan 8 tetangga dalam gambar:
I (x, y) = [I (x-1, y-1) + I (x-1, y) + I (x-1, y + 1) + I (x, y-1) +
+ I (x, y) + I (x, y + 1) + I (x + 1, y-1) + I (x + 1, y) + I (x + 1, y + 1)] / 9

Jika warna (data) dalam matriks ditempatkan sebagai R8G8B8 , maka perhitungan dengan memecah gambar input menjadi blok 6x8 piksel (elemen data 6x24 byte) adalah sebagai berikut:

_GENX_MAIN_ void linear(SurfaceIndex inBuf, SurfaceIndex outBuf, uint h_pos, uint v_pos){ //    8x32 matrix<uchar, 8, 32> in; //   6x24 matrix<uchar, 6, 24> out; matrix<float, 6, 24> m; //    read(inBuf h_pos*24, v_pos*6, in); //    -  m = in.select<6,1,24,1>(1,3); m += in.select<6,1,24,1>(0,0); m += in.select<6,1,24,1>(0,3); m += in.select<6,1,24,1>(0,6); m += in.select<6,1,24,1>(1,0); m += in.select<6,1,24,1>(1,6); m += in.select<6,1,24,1>(2,0); m += in.select<6,1,24,1>(2,3); m += in.select<6,1,24,1>(2,6); //  -   9   * 0.111f; out = m * 0.111f; //   write(outBuf, h_pos*24, v_pos*6, out); } 

  • Ukuran matriks diatur dalam bentuk <tipe data, tinggi, lebar>;
  • operator pilih <v_size, v_stride, h_size, h_stride> (i, j) mengembalikan submatrix dimulai dengan elemen (i, j) , v_size menunjukkan jumlah baris yang dipilih, v_stride - jarak antara baris yang dipilih h_size - jumlah kolom yang dipilih, h_stride - jarak di antara mereka .

Harap dicatat bahwa ukuran matriks input 8x32 dipilih karena meskipun blok 8x30 secara algoritmik cukup untuk menghitung nilai semua piksel dalam blok 6x24, blok data dibaca dalam ICM bukan byte, tetapi oleh elemen kata 32-bit.

Kode di atas sebenarnya adalah ICM kernel lengkap. Seperti yang disebutkan, itu akan dikompilasi oleh kompiler ICM dalam dua tahap (prakompilasi dan terjemahan JIT berikutnya). Kompiler ICM dibangun berdasarkan LLVM dan, jika diinginkan, dapat dipelajari dalam sumber dan dibuat sendiri oleh Anda .

Tapi apa yang dilakukan host ICM? Meminta fungsi pustaka runtime ICM Runtime yang:

  • Buat, inisialisasi, dan hapus setelah menggunakan perangkat GPU (CmDevice), serta permukaan yang berisi data pengguna yang digunakan dalam kernel (CmSurface);
  • Bekerja dengan kernel - unduh dari file .isa yang telah dikompilasi, siapkan argumen mereka, yang mengindikasikan bagian dari data yang akan digunakan oleh masing-masing kernel;
  • Membuat dan mengelola antrian eksekusi kernel;
  • Mereka mengontrol operasi utas yang mengeksekusi setiap kernel pada GPU;
  • Kelola acara (CmEvent) - objek sinkronisasi GPU dan CPU;
  • Transfer data antara GPU dan CPU, atau lebih tepatnya, antara memori sistem dan video;
  • Laporkan kesalahan, ukur waktu operasi kernel.

Kode host paling sederhana terlihat seperti ini:

 //  CmDevice cm_result_check(::CreateCmDevice(p_cm_device, version)); //  hello_world_genx.isa std::string isa_code = isa::loadFile("hello_world_genx.isa"); //    isa  CmProgram CmProgram *p_program = nullptr; cm_result_check(p_cm_device->LoadProgram(const_cast<char* >(isa_code.data()),isa_code.size(), p_program)); //  hello_world . CmKernel *p_kernel = nullptr; cm_result_check(p_cm_device->CreateKernel(p_program, "hello_world", p_kernel)); //       CmKernel CmThreadSpace *p_thread_space = nullptr; cm_result_check(p_cm_device->CreateThreadSpace(thread_width, thread_height, p_thread_space)); //   . cm_result_check(p_kernel->SetKernelArg(0, sizeof(thread_width), &thread_width)); //  CmTask –      //         //     . CmTask *p_task = nullptr; cm_result_check(p_cm_device->CreateTask(p_task)); cm_result_check(p_task->AddKernel(p_kernel)); //   CmQueue *p_queue = nullptr; cm_result_check(p_cm_device->CreateQueue(p_queue)); //    GPU (    ). CmEvent *p_event = nullptr; cm_result_check(p_queue->Enqueue(p_task, p_event, p_thread_space)); //   . cm_result_check(p_event->WaitForTaskFinished()); 

Seperti yang Anda lihat, tidak ada yang rumit dalam membuat dan menggunakan kernel dan host. Semuanya sederhana!

Satu-satunya kesulitan untuk memperingatkan untuk kembali ke dunia nyata: saat ini dalam versi ICM yang tersedia untuk umum, satu-satunya cara untuk men-debug kernel adalah pesan printf. Cara menggunakannya dengan benar dapat dilihat pada contoh Hello, World .

ICM - bukan logam berat!


Sekarang mari kita lihat cara kerjanya dalam praktik. Kit Pengembang ICM tersedia untuk Windows dan Linux , dan untuk kedua sistem operasi berisi Kompiler ICM, dokumentasi, dan kasus penggunaan tutorial. Deskripsi rinci tentang contoh-contoh pelatihan ini diunduh secara terpisah .

Untuk Linux, paket ini juga menyertakan driver media mode pengguna untuk VAAPI dengan pustaka runtime ICM Runtime terintegrasi. Untuk Windows, Driver Grafik Intel biasa untuk Windows akan bekerja dengan ICM. Pustaka runtime ICM Runtime termasuk dalam set dll driver ini. Paket ICM hanya menyertakan file tautan .lib untuknya. Jika driver hilang dari sistem Anda karena suatu alasan, itu diunduh dari situs web Intel, dan operasi ICM yang benar dalam driver dijamin, mulai dari versi 15.60 - 2017).

Kode sumber komponen dapat ditemukan di sini:


Konten lebih lanjut dari bagian ini berlaku secara eksklusif untuk Windows, tetapi prinsip-prinsip umum bekerja dengan ICM juga berlaku untuk Linux.

Untuk pekerjaan "reguler" dengan paket ICM, Anda akan membutuhkan Visual Studio mulai tahun 2015 dan Cmake mulai dari versi 3.2. Pada saat yang sama, file konfigurasi dan skrip dari contoh pelatihan dirancang untuk VS 2015, untuk menggunakan versi file VS yang lebih baru, Anda harus mempelajari dan mengedit sendiri path ke komponen VS.

Jadi, mengenal ICM untuk Windows:

  • Unduh arsipnya ;
  • Buka kemasannya;
  • Kita mulai (lebih disukai pada baris perintah VS) skrip konfigurasi lingkungan setupenv.bat dengan tiga parameter - generasi Intel GPU (sesuai dengan prosesor tempat GPU dibangun, ia dapat dibiarkan secara default: gen9), platform kompilasi: x86 \ x64 dan versi DirectX untuk berbagi dengan ICM: dx9 / dx11.

Setelah itu, Anda cukup membuat semua contoh pelatihan - di folder contoh, skrip build_all.bat akan melakukan ini atau menghasilkan proyek untuk Microsoft Visual Studio - ini akan membuat skrip create_vs.bat dengan nama contoh spesifik sebagai parameter.

Seperti yang Anda lihat, aplikasi ICM akan menjadi file .exe dengan bagian host dan file .isa dengan bagian GPU yang sudah dikompilasi sebelumnya.

Berbagai contoh disertakan dalam paket ICM - dari Hello, World yang paling sederhana, yang menunjukkan prinsip-prinsip dasar operasi ICM, hingga yang agak rumit - implementasi algoritma untuk menemukan "aliran maksimum - potongan minimum" dari grafik (masalah min-cut max-flow) yang digunakan dalam segmentasi gambar dan jahitan .

Semua studi kasus ICM didokumentasikan dengan baik tepat dalam kode dan dalam deskripsi terpisah yang telah disebutkan. Disarankan untuk mempelajari ICM secara tepat - mempelajari dan menjalankan contoh secara berurutan, dan kemudian - memodifikasinya agar sesuai dengan kebutuhan Anda.

Untuk pemahaman umum tentang semua fitur ICM yang ada, sangat disarankan agar Anda mempelajari "spesifikasi" - deskripsi ICM cmlangspec.html di folder \ document \ compiler \ html \ cmlangspec .

Secara khusus, ini menggambarkan API dari fungsi ICM yang diimplementasikan dalam perangkat keras - akses ke apa yang disebut sebagai sampler tekstur (Sampler) - suatu mekanisme untuk memfilter gambar dengan format yang berbeda, serta untuk mengevaluasi pergerakan (Estimasi Gerakan) antara bingkai video dan beberapa kemampuan analitik video.

ICM - serang selagi panas!


Berbicara tentang kinerja aplikasi ICM, perlu dicatat bahwa studi kasus termasuk mengukur waktu pekerjaan mereka, sehingga dengan menjalankannya pada sistem target dan membandingkannya dengan tugas Anda, Anda dapat mengevaluasi kelayakan menggunakan ICM untuk mereka.

Dan pertimbangan umum mengenai kinerja ICM cukup sederhana:

  • Saat menurunkan perhitungan pada GPU, ingat overhead mentransfer CPU <-> data GPU dan menyinkronkan perangkat ini. Oleh karena itu, contoh seperti Hello, World bukanlah kandidat yang baik untuk implementasi ICM. Tetapi algoritma dari visi komputer, AI, dan pemrosesan array data yang tidak sepele, terutama dengan perubahan urutan data ini dalam proses atau pada output, adalah apa yang dibutuhkan ICM.
  • Selain itu, ketika merancang kode ICM, perlu untuk mempertimbangkan perangkat GPU internal, yaitu, disarankan untuk membuat jumlah yang cukup (> 1000) utas GPU dan memuat semuanya dengan pekerjaan. Dalam hal ini, merupakan ide bagus untuk membagi gambar untuk diproses menjadi blok kecil. Tetapi cara khusus mempartisi, serta pilihan algoritma pemrosesan tertentu untuk mencapai kinerja maksimum, bukanlah tugas yang sepele. Namun, ini berlaku untuk cara apa pun bekerja dengan GPU (dan CPU) apa pun.

Apakah Anda memiliki kode OpenCL, tetapi kinerjanya tidak menyenangkan Anda? Atau kode CUDA, tetapi Anda ingin bekerja pada platform yang jauh lebih banyak? Maka ada baiknya melihat ICM.

ICM adalah produk yang hidup dan berkembang. Anda dapat berpartisipasi dalam penggunaan dan pengembangannya - repositori terkait di github sedang menunggu komitmen Anda. Semua informasi yang diperlukan untuk kedua proses ada di artikel ini dan readme file di github. Dan jika ada sesuatu yang hilang, itu akan muncul setelah pertanyaan Anda di komentar.

Source: https://habr.com/ru/post/id466521/


All Articles