Kernel yang benar saja tidak cukup untuk sebuah pustaka, karena API-nya juga menentukan siapa yang memiliki data dan siapa yang hanya melihatnya. Topik ini merancang API pustaka statistik dalam tiga lapisan, yaitu kontrak fungsi yang sadar kepemilikan (ownership), parameter compile-time untuk dtype dan lebar vektor, serta mutasi yang dilakukan langsung pada buffer pemanggil. Hasil akhirnya adalah berkas statistik.mojo berisi tiga kernel terparameterisasi yang berjalan dengan keluaran terverifikasi, beserta pesan error yang muncul ketika kontrak kepemilikannya dilanggar.
Kontrak fungsi yang sadar kepemilikan#
Kontrak fungsi menyatakan hubungan pustaka dengan data yang diterimanya. Kernel pembaca, yaitu jumlah dan rata_kuadrat, menerima data sebagai peminjaman bawaan yang tidak menyalin apa pun, sehingga biaya panggilannya tidak bergantung pada ukuran data. Hasil komputasi kembali sebagai satu nilai kembalian, bukan sebagai buffer baru, karena hasil reduksi memang berukuran satu elemen. Kernel mutasi, yaitu skala, menerima peminjaman yang dapat diubah dan menulis hasilnya langsung ke buffer milik pemanggil, sehingga pustaka tidak pernah menampung salinan data di dalamnya.
Tipe masukan yang dipilih adalah Span, yaitu tampilan (view) atas buffer kontigu yang tidak memiliki buffer itu. Pilihan ini menjadikan tipe masukan sebagai titik temu yang netral, sebab Span dapat dibentuk dari Array Mojo dan juga dari array NumPy melalui jembatan yang dipakai pada pengukuran nanti. Konsekuensi desainnya jelas pada sisi pemanggil: Array milik pemanggil tetap berlaku dan tetap dapat dibaca sesudah kernel dipanggil, karena tidak ada kepemilikan yang berpindah.
Kepemilikan yang tidak berpindah juga menyederhanakan penalaran error. Apabila kernel gagal atau hasilnya tidak dipakai, tidak ada sumber daya yang tertahan di dalam pustaka, karena pustaka tidak pernah menjadi pemilik buffer. Pola ini adalah pola baku untuk API kernel, yaitu masukan dipinjam, hasil berukuran kecil dikembalikan sebagai nilai, dan mutasi besar dilakukan langsung pada buffer pemanggil.
Parameter dtype dan lebar#
Kernel pustaka dideklarasikan dengan dua parameter compile-time pada kurung siku, sebagaimana bentuk umum berikut.
# tidak-dijalankan
def jumlah[dtype: DType, lebar: Int](data: Span[Scalar[dtype], _]) -> Scalar[dtype]:Parameter dtype menentukan tipe elemen dan lebar menentukan jumlah lane yang dihitung sekaligus. Tipe Scalar[dtype] menamai nilai SIMD berlebar satu secara generik, sehingga tipe akumulator dan nilai kembalian mengikuti dtype yang dipilih pemanggil tanpa ditulis ulang per tipe. Parameter kedua pada Span ditulis _ karena menyatakan origin, yaitu asal peminjaman data yang mencakup pemilik buffer dan mutabilitasnya. Nilai origin ditetapkan compiler dari pemanggil, sehingga parameter itu dilonggarkan dengan _ alih-alih disebut eksplisit. Tanpa pelonggaran itu, compiler menolak deklarasi dengan pesan 'Span' failed to infer parameter 'origin', specify the parameter or use '_' or '...' to unbind the parameter explicitly.
Di dalam body, kernel memuat data sebagai vektor utuh lewat pointer tampilannya. Pemanggilan data.unsafe_ptr() mengambil pointer ke elemen pertama, dan ptr.unsafe_load[width=lebar](i) memuat lebar elemen sekaligus ke satu vektor, yang kemudian dijumlahkan dengan reduce_add(). Perulangan tetap dua fase, yaitu potongan penuh dengan kondisi i + lebar <= len(data) dan ekor skalar untuk sisa elemen. rata_kuadrat memakai pola yang sama pada vektor kuadratnya, lalu mengonversi akumulator dengan cast[DType.float64]() sebelum dibagi Float64(len(data)) agar hasilnya pecahan untuk dtype mana pun.
Pustaka statistik lengkap#
Berkas berikut memuat seluruh pustaka beserta program ujinya. Ketiga kernel berbagi pola perulangan yang sama, sedangkan main menguji urutan kerja yang wajar, yaitu membaca, menghitung, memutasi, lalu membaca ulang.
from std.collections.span import Span, MutSpan
def jumlah[dtype: DType, lebar: Int](data: Span[Scalar[dtype], _]) -> Scalar[dtype]:
var ptr = data.unsafe_ptr()
var total: Scalar[dtype] = Scalar[dtype](0)
var i = 0
while i + lebar <= len(data):
total += ptr.unsafe_load[width=lebar](i).reduce_add()
i += lebar
while i < len(data):
total += data[i]
i += 1
return total
def rata_kuadrat[dtype: DType, lebar: Int](data: Span[Scalar[dtype], _]) -> Float64:
var ptr = data.unsafe_ptr()
var total: Scalar[dtype] = Scalar[dtype](0)
var i = 0
while i + lebar <= len(data):
var v = ptr.unsafe_load[width=lebar](i)
total += (v * v).reduce_add()
i += lebar
while i < len(data):
total += data[i] * data[i]
i += 1
return total.cast[DType.float64]() / Float64(len(data))
def skala[dtype: DType, lebar: Int](mut data: MutSpan[Scalar[dtype], _], faktor: Scalar[dtype]):
var ptr = data.unsafe_ptr()
var i = 0
while i + lebar <= len(data):
ptr.unsafe_store(i, ptr.unsafe_load[width=lebar](i) * faktor)
i += lebar
while i < len(data):
data[i] = data[i] * faktor
i += 1
def main():
var nilai: Array[Float64, 10] = [
80.0, 75.0, 90.0, 85.0, 70.0, 88.0, 92.0, 79.0, 81.0, 65.0
]
print(jumlah[DType.float64, 4](Span(nilai)))
print(rata_kuadrat[DType.float64, 4](Span(nilai)))
var tampilan = MutSpan(nilai)
skala[DType.float64, 4](tampilan, 0.5)
print(nilai[0], nilai[1], nilai[2])
print(jumlah[DType.float64, 4](Span(nilai)))# jalankan pustaka statistik beserta program ujinya
mojo run statistik.mojo805.0
6548.5
40.0 37.5 45.0
402.5Keluaran baris pertama adalah jumlah sepuluh nilai sebelum mutasi, dan baris kedua adalah rata-rata kuadratnya, yaitu 65485 dibagi 10. Kernel skala dipanggil dengan faktor 0.5 dan perubahannya terlihat langsung pada nilai, yang ditunjukkan oleh tiga nilai pertama 40.0 37.5 45.0, setengah dari nilai awalnya. Baris terakhir menghitung ulang jumlah pada buffer yang elemennya sudah dikalikan setengah, sehingga hasilnya 402.5. Urutan keluaran ini memperlihatkan kontrak kepemilikannya secara nyata, yaitu mutasi terjadi pada buffer pemanggil dan hasil pembacaan selalu mengikuti keadaan terbaru buffer itu.
Mutasi pada skala menuntut dua hal sekaligus, yaitu tampilan yang dapat diubah bertipe MutSpan dan konvensi argumen mut pada deklarasi. Penulisan elemen ditentukan oleh tipe tampilannya, bukan oleh kebiasaan pemanggil, sehingga pustaka yang menerima Span biasa tidak dapat mengubah buffer pemanggil walaupun pemanggil mengizinkannya.
Kesalahan umum#
Kesalahan pertama adalah menulis ke tampilan baca saja. Berkas salah-tulis.mojo mendeklarasikan skala dengan Span biasa lalu melakukan penugasan pada elemennya.
from std.collections.span import Span
def skala(data: Span[Float64, _], faktor: Float64):
for i in range(len(data)):
data[i] = data[i] * faktor
def main():
var nilai = Array[Float64, 3](fill=0.0)
var tampilan = Span(nilai)
skala(tampilan, 2.0)
print(nilai[0])# penugasan elemen pada Span baca saja ditolak compiler
mojo run salah-tulis.mojo/home/kal/my-project/content/mojo/pengerjaan-proyek/desain-parameterisasi/salah-tulis.mojo:5:13: error: expression must be mutable in assignment
data[i] = data[i] * faktor
~~~~^~~
/home/kal/.pixi/envs/mojo/bin/mojo: error: failed to parse the provided Mojo source modulePesan expression must be mutable in assignment menyatakan bahwa elemen tampilan itu tidak dapat ditulis. Perbaikannya mengikuti kontrak mutasi pada pustaka, yaitu tipe parameternya menjadi MutSpan[Float64, _] dengan konvensi mut pada argumen, dan pemanggil menyerahkan MutSpan yang disimpan dalam variabel.
Kesalahan kedua adalah menyerahkan MutSpan sementara kepada parameter mut. Berkas salah-mut.mojo memanggil skala dengan hasil konstruksi MutSpan(nilai) yang langsung ditulis di tempat pemanggilan.
from std.collections.span import MutSpan
def skala(mut data: MutSpan[Float64, _], faktor: Float64):
for i in range(len(data)):
data[i] = data[i] * faktor
def main():
var nilai = Array[Float64, 3](fill=0.0)
skala(MutSpan(nilai), 2.0)
print(nilai[0])# MutSpan sementara ditolak pada parameter mut
mojo run salah-mut.mojo/home/kal/my-project/content/mojo/pengerjaan-proyek/desain-parameterisasi/salah-mut.mojo:9:18: error: invalid call to 'skala': value passed to mutable argument 'data' must be mutable
skala(MutSpan(nilai), 2.0)
~~~~~ ~~~~~~~^~~~~~~
/home/kal/my-project/content/mojo/pengerjaan-proyek/desain-parameterisasi/salah-mut.mojo:3:5: note: function declared here
def skala(mut data: MutSpan[Float64, _], faktor: Float64):
^
/home/kal/.pixi/envs/mojo/bin/mojo: error: failed to parse the provided Mojo source modulePesan value passed to mutable argument 'data' must be mutable menuntut argumen yang terikat pada variabel yang dapat diubah. Perbaikannya adalah menyimpan tampilan dalam variabel, seperti var tampilan = MutSpan(nilai) pada main pustaka, lalu menyerahkan variabel itu kepada kernel.
Kesalahan ketiga adalah menghilangkan pelonggaran origin pada tipe masukan. Deklarasi Span[Scalar[dtype]] tanpa _ ditolak dengan pesan 'Span' failed to infer parameter 'origin', karena origin baru diketahui dari pemanggil dan bukan dari deklarasi. Perbaikannya adalah menulis Span[Scalar[dtype], _] sebagaimana dipakai ketiga kernel pustaka.
Ringkasan#
- Kontrak API kernel yang sadar kepemilikan meminjam masukan lewat
Span, mengembalikan hasil reduksi sebagai nilai, dan melakukan mutasi langsung pada buffer pemanggil lewatMutSpandengan konvensimut. - Parameter compile-time
dtypedanlebarmenjadikanScalar[dtype]sebagai tipe akumulator yang mengikuti pemanggilan, sedangkan_melonggarkan parameter originSpanyang ditetapkan compiler dari pemanggil. - Pemuatan vektor berlangsung lewat
unsafe_ptr()danunsafe_load[width=lebar], dengan ekor skalar untuk sisa elemen. - Penulisan elemen ditolak compiler pada
Spanbiasa, danMutSpansementara ditolak pada parametermut, sehingga pelanggaran kontrak terlihat pada waktu kompilasi.
Referensi#
- Parameterized declarations — Mojo Manual — Dokumentasi resmi deklarasi berparameter yang menjadi dasar signature kernel dtype dan lebar.
- Mojo standard library — Modular
— Rujukan API tipe
Span,MutSpan,Scalar, dan pointer yang dipakai pustaka statistik.