Matematika lane dan reduksi

Melakukan aritmetika lane demi lane dan meringkasnya dengan metode reduce_add.

Daftar isi

Nilai SIMD memberikan keuntungan penuh apabila aritmetikanya dimanfaatkan lane demi lane. Topik ini membahas cara operator aritmetika bekerja pada setiap lane, penyiaran nilai skalar ke seluruh lane, dan reduksi yang meringkas semua lane menjadi satu nilai dengan metode reduce_add. Setelah topik ini dibaca, perhitungan per lane beserta perangkuman hasilnya dapat ditulis dalam bentuk yang alami bagi perangkat keras SIMD.

Aritmetika lane demi lane#

Operator +, -, *, dan / pada dua nilai SIMD berlebar sama bekerja secara independen pada setiap pasangan lane. Hasil lane ke-ii dari v+wv + w adalah vi+wiv_i + w_i, dan pola yang sama berlaku untuk operator lainnya. Tidak ada interaksi antar lane, sehingga luapan bilangan pada satu lane tidak memengaruhi lane tetangganya. Sifat ini berbeda dari penjumlahan bit bilangan bertanda yang membawa simpanan antar posisi digit, karena lane dihitung sebagai bilangan utuh yang terpisah.

Kemandirian antar lane inilah yang membuat satu instruksi SIMD dapat dijalankan perangkat keras secara efisien, karena tidak ada lane yang menunggu hasil lane lain. Konsekuensinya, kedua sisi operasi wajib bertipe sama, baik tipe elemen maupun lebar, sehingga vektor hasil memiliki lebar yang sama dengan vektor operandnya. Penyiaran otomatis hanya berlaku untuk skalar berlebar 1, sedangkan lebar lain yang berbeda ditolak compiler.

Bila salah satu sisi operasi berupa skalar, nilai itu disiarkan ke seluruh lane. Ekspresi celsius + 2.5 menambahkan 2.5 pada setiap lane karena skalar dianggap vektor berlebar 1 yang disiarkan. Bentuk yang lebih eksplisit adalah konstruktor broadcast, misalnya SIMD[DType.float64, 4](2.0), yang mengisi seluruh lane dengan nilai yang sama tanpa menulis argumen berulang.

Jalankan program berikut yang mengonversi empat pembacaan suhu dari Celsius ke Fahrenheit, kemudian menambahkan selisih tetap pada setiap lane.

mojo aritmetika.mojo
def main():
    var celsius = SIMD[DType.float64, 4](20.0, 25.0, 30.0, 27.5)
    var faktor = SIMD[DType.float64, 4](9.0 / 5.0)
    var offset = SIMD[DType.float64, 4](32.0)
    var fahrenheit = celsius * faktor + offset
    print(fahrenheit)
    print(celsius + 2.5)
bash
# jalankan program konversi suhu per lane
mojo run aritmetika.mojo
text Keluaran
[68.0, 77.0, 86.0, 81.5]
[22.5, 27.5, 32.5, 30.0]

Ekspresi celsius * faktor + offset menghitung konversi pada empat lane sekaligus, sehingga keluaran pertamanya adalah [68.0, 77.0, 86.0, 81.5]. Perkalian dan penjumlahan di dalam ekspresi itu masing-masing bekerja lane demi lane mengikuti urutan operasi aritmetika biasa. Keluaran kedua memperlihatkan penyiaran skalar, yaitu celsius + 2.5 menghasilkan [22.5, 27.5, 32.5, 30.0] dengan setiap lane bertambah 2.5. Kedua keluaran ini menegaskan bahwa satu ekspresi pada vektor sama artinya dengan perulangan yang menghitung setiap elemen, tetapi ditulis tanpa perulangan.

Reduksi dengan reduce_add#

Aritmetika lane menghasilkan vektor baru, sedangkan banyak perhitungan akhirnya membutuhkan satu nilai, misalnya total atau rata-rata. Proses penggabungan semua lane menjadi satu nilai disebut reduksi, dan bentuk penjumlahannya disediakan oleh metode reduce_add. Untuk vektor vv berlebar nn, hasilnya dinyatakan sebagai:

r=v0+v1+⋯+vn−1 r = v_0 + v_1 + \cdots + v_{n-1}

Metode ini tersedia untuk berbagai tipe elemen dan lebar, termasuk bilangan bulat. Hasilnya berupa skalar bertipe elemen vektor tersebut, sehingga nilai kembalian reduce_add dapat langsung dijumlahkan ke variabel Float64 maupun dipakai dalam perhitungan berikutnya. Reduksi berbeda sifatnya dari aritmetika lane karena lane-lane berinteraksi satu sama lain untuk membentuk satu hasil, bukan dihitung sendiri-sendiri.

Reduksi di dalam perangkat keras umumnya disusun bertahap, misalnya pasangan lane dijumlahkan lebih dahulu, kemudian hasil antar pasangan dijumlahkan lagi, sehingga jumlah langkahnya tumbuh lebih lambat daripada jumlah lanenya. Cara penyusunan ini berbeda dari perulangan penjumlahan berurutan yang menunggu hasil langkah sebelumnya pada setiap penambahan. Bagi penulis kode, perbedaan itu tidak terlihat karena bentuk pemanggilannya tetap satu metode, tetapi inilah alasan reduksi menjadi bagian penting dari perhitungan SIMD.

Jalankan program berikut yang menjumlahkan empat nilai ujian secara langsung dan setelah penambahan bonus per lane.

mojo reduksi.mojo
def main():
    var nilai = SIMD[DType.float64, 4](80.0, 75.0, 90.0, 85.0)
    print(nilai.reduce_add())

    var bonus = SIMD[DType.float64, 4](2.0)
    var total = (nilai + bonus).reduce_add()
    print(total)
bash
# jalankan program reduksi penjumlahan lane
mojo run reduksi.mojo
text Keluaran
330.0
338.0

Keluaran pertama berupa 330.0, yaitu hasil penjumlahan 80.0, 75.0, 90.0, dan 85.0. Keluaran kedua berupa 338.0 karena penjumlahan antar lane dilakukan lebih dahulu, yaitu (nilai + bonus) menghasilkan vektor dengan setiap lane bertambah 2.0, lalu reduce_add merangkumnya. Rata-rata diperoleh dengan membagi total itu oleh len(nilai), yaitu 330.0 dibagi 4 atau 82.5. Pola perhitungan per lane lalu reduksi inilah dasar perhitungan agregat dengan SIMD, mulai dari total sampai rata-rata.

Kesalahan umum#

  • Mengoperasikan dua vektor berlebar berbeda. Penjumlahan SIMD[DType.float64, 4] dengan SIMD[DType.float64, 2] ditolak compiler dengan pesan invalid call to __add__, karena keduanya tipe yang berbeda dan hanya skalar berlebar 1 yang disiarkan otomatis. Perbaikannya adalah menyamakan lebar kedua sisi atau memakai skalar bila nilai yang ditambahkan sama untuk semua lane.
  • Menuliskan nilai + bonus.reduce_add() ketika yang dimaksud adalah (nilai + bonus).reduce_add(). Bentuk pertama menghitung reduksi bonus lebih dahulu, lalu menyiarkan hasil skalar 2.0 ke setiap lane nilai, sehingga hasilnya vektor [82.0, 77.0, 92.0, 87.0] dan bukan total 338.0. Kurung pada bentuk kedua memastikan penjumlahan antar lane selesai sebelum reduksi dijalankan.
  • Menganggap hasil reduce_add masih berupa vektor. Hasilnya skalar bertipe elemen vektor, sehingga penulisan indeks atau pemanggilan metode lane pada hasilnya tidak beralasan. Perbaikannya adalah memperlakukan nilai kembalian reduksi sebagai skalar biasa.

Ringkasan#

  • Operator aritmetika pada SIMD bekerja independen per lane, dan hasil lane ke-ii dari v+wv + w adalah vi+wiv_i + w_i.
  • Skalar pada ekspresi vektor disiarkan ke semua lane, baik lewat skalar langsung maupun konstruktor broadcast.
  • reduce_add merangkum semua lane menjadi satu skalar bertipe elemen vektor dengan penjumlahan v0+v1+⋯+vn−1v_0 + v_1 + \cdots + v_{n-1}.
  • Pola perhitungan SIMD yang lazim adalah menghitung per lane lebih dahulu, lalu merangkum hasilnya dengan reduksi.

Referensi#