Occa: Makro kualifikasi kernel baru

Dibuat pada 2 Apr 2020  ·  22Komentar  ·  Sumber: libocca/occa

Seringkali bermanfaat untuk memberikan beberapa informasi tambahan kepada kompiler kernel tentang jumlah utas (item kerja) dalam blok utas (grup kerja). Misalnya kita dapat memberikan kompiler HIP batas atas pada jumlah utas dalam blok utas (katakanlah 1024) sebagai berikut:

__launch_bounds__(1024) __global__ void fooKernel(...) { ... }

Sebenarnya untuk rilis HIP saat ini sayangnya hal ini harus ditentukan ketika ukuran blok utas melebihi 256 (lihat https://github.com/ROCm-Developer-Tools/HIP/issues/1310 )

CUDA juga memiliki atribut yang sama. Ada juga argumen tambahan untuk kualifikasi batas peluncuran untuk jumlah minimum blok utas (https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#launch-bounds).

Kualifikasi kernel di OpenCL sedikit berbeda (lihat 6.7.2 dari https://www.khronos.org/registry/OpenCL/specs/2.2/pdf/OpenCL_C.pdf )

Proposal v1 - di dunia yang ideal ketika kita mengetahui ukuran blok utas pada waktu kompilasi OCCA akan menambahkan kualifikasi kernel untuk batas peluncuran yang sesuai (CUDA, HIP) atau petunjuk ukuran grup kerja (OpenCL).

Proposal v2 - Jika terlalu rumit untuk melakukan Proposal v1, maka akan lebih baik untuk menambahkan atribut okl untuk batas peluncuran @qualifier("ukuran dalam", B) di mana B dapat menjadi definisi kompiler. Ini akan diperluas ke __launch_bounds__(nilai B) untuk CUDA/HIP atau __attribute__((work_group_size_hint(nilai B))) untuk OpenCL. Varian multi-redup juga akan membantu.

feature

Semua 22 komentar

Karena ini tampaknya khusus untuk HIP/CUDA/OpenCL, bagaimana dengan meneruskannya sebagai properti build?

  addVectors = device.buildKernel("addVectors.okl",
                                  "addVectors",
                                  "launch_bounds: 1024");

Itu akan bagus saya pikir.

Saya baru saja mengalami ini. Saya senang untuk berkontribusi ini tapi saya punya pertanyaan. Apakah OCCA mengetahui dimensi loop dalam pada waktu JIT?

Saya percaya dimensi loop dapat diteruskan sebagai argumen kernel.

Itu benar, tapi saya tidak mengerti mengapa itu berguna. Saya bertanya bagaimana occa dapat memancarkan atribut __launch_bounds__ pada waktu JIT. Perlu mengetahui dimensi loop pada saat itu. Ini ortogonal dengan argumen kernel, bukan?

Dia mengatakan redupnya loop dalam dapat diteruskan sebagai argumen, yaitu kita tidak perlu mengetahui dimensi threadblock pada waktu kompilasi JIT.

Oh benar. Saya mengerti apa yang Anda katakan sekarang @tcew. Poin bagus.

Inilah yang ada dalam pikiran saya:

<strong i="6">@kernel</strong> void runtimeArgs(const int B,
                         const int T,
                         const int N,
                         const float *x,
                         const float *y,
                         float *xPy) {
  for (int b=0;b<B;++b;@outer(0)){
    for (int t=0;t<T;++t;@inner(0)){

      if(b==0 && t==0) printf("B=%d, T=%d\n", B, T);

      int  n = t + T*b;
      if(n<N){
        xPy[n] = x[n] + y[n];
      }
    }
  }
}

OCCA jelas tidak dapat mengetahui batas loop numerik pada waktu JIT.

Namun, itu membuat peluncur yang menetapkan dimensi kisi utas:

extern "C" void runtimeArgs(occa::modeKernel_t * *deviceKernel,
                            const int & B,
                            const int & T,
                            const int & N,
                            occa::modeMemory_t * x,
                            occa::modeMemory_t * y,
                            occa::modeMemory_t * xPy) {
  {
    occa::dim outer, inner;
    outer.dims = 1;
    inner.dims = 1;
    int b = 0;
    outer[0] = B - 0;
    int t = 0;
    inner[0] = T - 0;
    occa::kernel kernel(deviceKernel[0]);
    kernel.setRunDims(outer, inner);
    kernel(B, T, N, x, y, xPy);
  }
}

Dengan demikian pengguna dapat menentukan ukuran apa pun untuk batas loop saat runtime.

Posting silang.

SYCL menggunakan sintaks yang serupa:

sycl::range<2> global_range(Bx*Tx,By*Ty);
sycl::range<2> local_range(Tx,Ty);
sycl::nd_range<2> kernel_range(global_range, local_range);

device_queue.submit([&](sycl::handler &cgh) {
    ...
  cgh.parallel_for(kernel_range, kernel);
});

(Buffer memiliki rentang yang terkait dengannya, yang menghindari kebutuhan untuk meneruskan N)

Sintaks SYCL sycl::range diadaptasi dari OpenCL, yang diadaptasi dari CUDA.

Dalam contoh Anda, spesifikasi dimensi ulir terpisah dari badan paralel untuk loop.

Sintaks OCCA OKL dirancang khusus untuk membawa dimensi loop dan kode tubuh menjadi paralel yang lebih familiar untuk sintaks loop.

for (int b=0;b<B;++b;@outer(0)){ /*  grid dimension defined here */
    for (int t=0;t<T;++t;@inner(0)){ /* thread block dimension defined here */

      if(b==0 && t==0) printf("B=%d, T=%d\n", B, T);

      int  n = t + T*b;
      if(n<N){
        xPy[n] = x[n] + y[n];
      }
    }
  }

Kode di dalam kernel seharusnya menjaga paralel untuk batas loop di dekat badan paralel untuk loop. Juga ikatan loop tidak ditentukan oleh larik input apa pun, karena kernel yang lebih umum mungkin memerlukan konfigurasi grid utas yang sangat berbeda dengan larik data.

Konstruksi kernel OKL adalah pilihan yang disengaja karena harus berulang kali menjelaskan sintaks kernel CUDA/OpenCL, parameter peluncuran kernel, dan filosofi threading kernel saat melatih orang.

Apakah SYCL memiliki petunjuk ukuran blok utas analog seperti __launch_bounds__ CUDA/HIP yang sedang kita diskusikan di sini?

Pertanyaan bagus. Saya memeriksa ulang standar SYCL (v1.2.1) untuk mengetahuinya. Atribut apa pun yang tersedia di OpenCL C didukung dan dapat diberikan dengan penentu atribut C++ 11 menggunakan ruang nama cl . Misalnya, __attribute__(((reqd_work_group_size(n))) di OpenCL C setara dengan [[cl::reqd_work_group_size(n)]] di SYCL.

Ada dua pilihan yang tersedia untuk menentukan ukuran grup utas: work_group_size_hint(n) adalah versi lunak—menyarankan ukuran grup utas adalah n —sedangkan req_work_group_size(n) adalah persyaratan yang ketat .

Beberapa opsi untuk OCCA:

  1. tambahkan fungsi anggota "innerDimHint" ke kelas occa::kernel, yang memaksa kompilasi ulang (jika belum ada di hash) dengan petunjuk redup utas untuk CUDA/OpenCL/HIP.

  2. tambahkan beberapa logika di dalam peluncur untuk memicu kompilasi ulang yang dilakukan ketika ukuran array utas baru ditentukan. Fitur ini mungkin diaktifkan/dinonaktifkan oleh beberapa definisi OCCA_ * .

Kedua hal ini dapat dilakukan dengan cara yang kompatibel ke belakang.

Saya benar-benar akan mempercayai runtime terpisah untuk mengelola ini, dan memilih untuk tidak mengkompilasi ulang apa pun. Intinya, gunakan proposal asli @dmed256 untuk menjadikannya prop build, lalu tambahkan petunjuk __launch_bound__ masing-masing ke kernel saat terjemahan jika backend mendukungnya.

Untuk CUDA dan HIP, __launch_bound__ sebenarnya hanyalah petunjuk, jadi mungkin lebih mirip dengan work_group_size_hint OpenCL. Ini hanya digunakan untuk memberi tahu kompiler berapa banyak register yang dapat diasumsikan akan tersedia untuk setiap utas di blok. Jika pengguna melanggar batas peluncuran, itu belum tentu merupakan kesalahan, karena kernel mungkin tidak banyak menggunakan register. Dalam kasus di mana pengguna melanggar batas peluncuran dan memang tidak ada cukup register, runtime akan memunculkan kesalahan yang harus ditangkap oleh OCCA.

Saat menjalankan tuner otomatis, saya melihat kesalahan segmentasi untuk OCCA:HIP kernel yang membutuhkan terlalu banyak LDS atau REG. Saya skeptis bahwa HIP akan membuang kesalahan yang akan ditangkap. Mudah-mudahan itu sudah diperbaiki sekarang.

Saya setuju dengan petunjuk yang diberikan pengguna, atau peluncur yang melihat konfigurasi utas baru saat runtime.

FYI Saya senang sebagai lark yang hanya menentukan batas loop runtime eksplisit dari kode Host melalui substitusi konstanta makro dalam kode kernel OKL (mis., K_blockDim untuk bagian luar, K_threadDim untuk bagian dalam) yang saya wariskan melalui props kernel. Tentu saja, saya memiliki kode API sendiri yang menanyakan karakteristik dan status GPU. Saya menggunakan informasi itu untuk menghitung batas loop untuk diturunkan. Ya, hal dinamis semacam itu menyebabkan JIT mengkompilasi ulang kadang-kadang selama runtime tetapi dalam kasus saya ini jarang terjadi karena saya mengubah ambang batas, sehingga batas loop yang sebenarnya biasanya tetap sama atau jatuh ke dalam beberapa set umum untuk kernel yang diberikan. Saya juga menggunakan pra-kompilasi untuk beberapa kernel saya sehingga juga mengurangi kompilasi ulang JIT.

Memiliki batas loop yang didefinisikan secara eksplisit saat runtime juga memfasilitasi runtime sizing GPU array memori lokal dengan mereka dalam kode OKL.

Sepertinya fitur ini akan menjadi penting:
https://rocmdocs.amd.com/en/latest/Current_Release_Notes/Current-Release-Notes.html#performance -impact-for-kernel-launch-bound-attribute

Dengan permintaan maaf atas implementasi yang canggung, ini adalah solusi yang saya gunakan untuk kernel di libparanumal yang membutuhkan utas Np (> 256):

occa::properties kernelInfo; 
...
 if(platform.device.mode()=="HIP"){
      char newflag[BUFSIZ];
      sprintf(newflag, " --gpu-max-threads-per-block=%d", mesh.Np);
      kernelInfo["compiler_flags"] += newflag;
    }

Menggunakan flag compiler hipcc untuk menentukan batas peluncuran disarankan oleh Noel Chalmers. Implementasi kotor adalah milikku.

Penting untuk berhati-hati saat melakukan ini karena tidak jelas apa yang terjadi jika kernel melanggar batas maksimal.

Untuk menghindari penggunaan batas yang tidak tepat, saya membuat salinan terpisah dari objek occa::properties untuk kernel yang menggunakan jumlah utas maksimum yang berbeda.

Kemungkinan di masa mendatang, melanggar batas peluncuran akan menjadi kesalahan runtime.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat