4 poin oleh GN⁺ 2023-10-04 | 1 komentar | Bagikan ke WhatsApp
  • OnnxStream adalah pustaka inferensi ultra-ringan yang ditujukan untuk menjalankan Stable Diffusion 1.5 dan SDXL 1.0 Base pada Raspberry Pi Zero 2 dengan RAM 512MB tanpa menambahkan swap atau menyimpan hasil antara ke disk
  • Mesin inferensi dipisahkan dari WeightsProvider dan mendukung DiskNoCache, DiskPrefetch, serta loading·caching·prefetching kustom; bobot juga bisa diambil langsung lewat HTTP dan diproses tanpa penulisan ke disk
  • SDXL 1.0 Base menggunakan file ONNX yang diekspor dari implementasi Hugging Face Diffusers 0.19.3, dan pembuatan gambar 10 langkah di Raspberry Pi Zero 2 memerlukan sekitar 11 jam
  • Decoder VAE SDXL memakai 4.4GB RAM pada FP32 dan sulit diatasi hanya dengan FP16·UINT8, sehingga decoding tile bertumpuk dan blending menurunkan penggunaan memori menjadi 298MB
  • Dibanding OnnxRuntime di CPU, penggunaan memorinya bisa hingga 55 kali lebih kecil, dengan konsekuensi bisa 0,5~2 kali lebih lambat serta memiliki batasan pada batch size 1 dan fokus pada sebagian operator ONNX

Tujuan dan desain OnnxStream

  • OnnxStream dimulai dengan tujuan menjalankan Stable Diffusion 1.5 di Raspberry Pi Zero 2
    • Raspberry Pi Zero 2 adalah mikrokomputer dengan RAM 512MB
    • RAM/VRAM minimum yang umumnya direkomendasikan untuk Stable Diffusion adalah 8GB
    • Syarat targetnya adalah tanpa penambahan ruang swap dan tanpa offloading hasil antara ke disk
  • Framework machine learning umum cenderung membesarkan penggunaan RAM karena memprioritaskan latensi inferensi atau throughput
  • OnnxStream adalah pustaka inferensi kecil yang mudah dimodifikasi dan berfokus pada meminimalkan penggunaan memori
  • Struktur intinya adalah pemisahan antara mesin inferensi dan komponen penyedia bobot model, yaitu WeightsProvider
    • Kelas turunan dapat mengimplementasikan cara memuat, caching, dan prefetching parameter model
    • WeightsProvider kustom bisa langsung mengunduh data dari server HTTP dan memprosesnya tanpa baca/tulis ke disk
    • Implementasi bawaan adalah DiskNoCache dan DiskPrefetch
  • Di CPU, penggunaan memorinya bisa hingga 55 kali lebih kecil dibanding OnnxRuntime, dengan kecepatan sekitar 0,5~2 kali lebih lambat

Menjalankan Stable Diffusion 1.5

  • Contoh Stable Diffusion 1.5 menghasilkan gambar dengan presisi decoder VAE yang berbeda menggunakan OnnxStream
  • Satu-satunya komponen yang tidak muat ke RAM Raspberry Pi Zero 2 adalah decoder VAE
    • Penyebabnya adalah residual connection, tensor besar, dan convolution besar di dalam model
    • Presisi single maupun half saja tidak cukup agar muat di RAM Raspberry Pi Zero 2
    • Solusinya adalah kuantisasi statis 8-bit
  • Gambar decoder VAE dengan presisi W8A8 dibuat di Raspberry Pi Zero 2, dan dengan opsi MAX_SPEED membutuhkan sekitar 1,5 jam
  • Gambar W16A16 untuk perbandingan dibuat di PC menggunakan latent yang sama

Dukungan Stable Diffusion XL 1.0 Base

  • Contoh Stable Diffusion di OnnxStream mendukung SDXL 1.0 Base dan tidak menyertakan Refiner
  • File ONNX diekspor dari implementasi SDXL 1.0 di pustaka Hugging Face Diffusers, dengan versi yang digunakan 0.19.3
  • SDXL 1.0 memiliki biaya komputasi yang jauh lebih besar daripada SD 1.5
    • Perbedaan terbesarnya adalah dapat menghasilkan gambar 1024x1024 alih-alih 512x512
    • Di PC 12-core dengan RAM 32GB, Hugging Face Diffusers membutuhkan 26 menit untuk membuat gambar 10 langkah
    • VRAM minimum yang umumnya direkomendasikan untuk SDXL adalah 12GB
  • OnnxStream dapat menjalankan SDXL 1.0 dengan RAM di bawah 300MB, sehingga tetap bisa berjalan di Raspberry Pi Zero 2
    • Tanpa penambahan swap
    • Tanpa penulisan ke disk selama inferensi
    • Pembuatan gambar 10 langkah di Raspberry Pi Zero 2 memerlukan sekitar 11 jam

Optimasi memori khusus SDXL

  • SDXL 1.0 menerapkan kumpulan optimasi yang sama seperti SD 1.5, dengan beberapa perbedaan
  • Model UNET menggunakan kuantisasi dinamis UINT8 agar bisa berjalan di Raspberry Pi Zero 2 dengan RAM di bawah 300MB
    • Target kuantisasinya dibatasi pada subset tertentu dari tensor antara yang besar
  • Decoder VAE SDXL 1.0 lebih sulit ditangani daripada SD 1.5
    • Decoder VAE SDXL 1.0 berukuran 4 kali lebih besar daripada decoder VAE SD 1.5
    • Saat dijalankan di OnnxStream dengan presisi FP32, ia menggunakan 4.4GB RAM
    • Pada SD 1.5, decoder VAE dapat dikuantisasi statis ke UINT8 sehingga penggunaan RAM turun menjadi 260MB
  • Decoder VAE SDXL 1.0 mengalami overflow pada aritmetika FP16, dan rentang nilai activation yang besar membuat kuantisasi UINT8 sulit menghasilkan gambar berkualitas baik
  • Ada solusi FP16 seperti sdxl-vae-fp16-fix, tetapi meski memori dipangkas setengah menjadi 2.2GB, itu masih terlalu besar untuk Raspberry Pi Zero 2
  • Solusi akhirnya adalah tile decoding yang terinspirasi dari implementasi decoder VAE di Hugging Face Diffusers
    • Bentuk tensor hasil diffusion adalah (1,4,128,128)
    • Ini dibagi menjadi tensor bertumpuk berukuran (1,4,32,32) sebanyak 5x5, total 25 buah
    • Tiap tile bertumpuk 25% dengan tile di kiri dan di atasnya
    • Hasil decoding tiap tile adalah tensor (1,3,256,256) dan diblend ke gambar akhir
    • Jika blending dimatikan, batas tile akan terlihat; jika diaktifkan, batas itu tidak terlihat pada hasil akhir
  • Dengan cara ini, penggunaan RAM decoder VAE SDXL turun dari 4.4GB menjadi 298MB

Fitur yang didukung dan dependensi

  • OnnxStream merangkum fitur yang dibutuhkan untuk inferensi hemat memori dalam paket kecil
    • Pemisahan mesin inferensi dan WeightsProvider
    • DiskNoCache, DiskPrefetch, WeightsProvider kustom
    • attention slicing
    • kuantisasi dinamis percentile asimetris unsigned 8-bit
    • kuantisasi statis W8A8 percentile asimetris unsigned
    • kalibrasi model terkuantisasi
    • dukungan FP16
    • implementasi 25 operator ONNX yang umum dipakai
  • Operasi dieksekusi secara berurutan, tetapi tiap operator berjalan multithread
  • Implementasinya menggunakan struktur satu file implementasi dan satu file header, dengan kelas XnnPack yang membungkus pemanggilan XNNPACK
  • Beberapa primitive akselerasi bergantung pada XNNPACK
    • MatMul
    • Convolution
    • element-wise Add/Sub/Mul/Div
    • Sigmoid
    • Softmax

Perbandingan performa dan batasan

  • Stable Diffusion terdiri dari tiga model
    • text encoder: 672 operasi, 123 juta parameter
    • UNET: 2050 operasi, 854 juta parameter
    • VAE decoder: 276 operasi, 49 juta parameter
  • Untuk pembuatan gambar 10 langkah dengan batch size 1, diperlukan eksekusi berikut
    • text encoder 2 kali
    • UNET 20 kali
    • VAE decoder 1 kali
  • Dengan UNET FP16, perbedaan memori dan waktu antara OnnxStream dan OnnxRuntime cukup besar
    • OnnxStream: sekitar 0.133GB, 18.2~19.8 detik
    • OnnxRuntime: 5.085~7.353GB, 7.28~12.8 detik
    • OnnxStream memakai memori hingga 55 kali lebih sedikit, tetapi 0,5~2 kali lebih lambat
  • Untuk text encoder FP32, OnnxStream menggunakan 0.147GB dan OnnxRuntime 0.641GB
  • Untuk VAE decoder FP32, OnnxStream menggunakan 1.004GB dan OnnxRuntime 1.330~2.026GB
  • Hasil perbandingan ini memiliki beberapa kondisi
    • Eksekusi pertama OnnxRuntime adalah inferensi warm-up sebelum InferenceSession digunakan ulang
    • OnnxStream tidak memiliki konsep warm-up karena desainnya eager, tetapi eksekusi berikutnya bisa mendapat keuntungan dari cache file weights milik OS
    • Saat ini OnnxStream tidak mendukung input selain batch size 1
    • OnnxRuntime dapat menggunakan batch size 2 pada eksekusi UNET untuk mempercepat keseluruhan diffusion secara signifikan
    • Perubahan SessionOptions OnnxRuntime seperti EnableCpuMemArena dan ExecutionMode tidak menghasilkan perbedaan berarti dalam pengujian
    • NCNN sangat mirip dengan OnnxRuntime dalam hal penggunaan memori dan waktu inferensi
    • Lingkungan pengujian adalah Windows Server 2019, RAM 16GB, CPU 8750H AVX2, SSD 970 EVO Plus, dan 8 virtual core VMWare

Attention slicing dan kuantisasi

  • Saat menjalankan UNET, attention slicing dan kuantisasi W8A8 pada decoder VAE sangat penting untuk menurunkan memori hingga ke tingkat yang bisa dijalankan di Raspberry Pi Zero 2
  • attention slicing mencegah materialisasi penuh matriks Q @ K^T saat menghitung scaled dot-product attention pada multi-head attention
  • Saat jumlah attention head pada model UNET adalah 8, bentuk tensornya sebagai berikut
    • Q: (8,4096,40)
    • K^T: (8,40,4096)
    • Hasil MatMul pertama: (8,4096,4096)
    • Pada presisi FP32, ini menjadi tensor sebesar 512MB
  • Solusinya adalah membagi Q secara vertikal dan menjalankan operasi attention untuk tiap chunk
    • Bentuk Q_sliced adalah (1,x,40)
    • x adalah nilai 4096 yang dibagi dengan onnxstream::Model::m_attention_fused_ops_parts
    • Nilai default adalah 2 dan bisa dikustomisasi
  • Cara ini menurunkan total penggunaan memori model UNET FP32 dari 1.1GB menjadi 300MB
  • FlashAttention mungkin menjadi alternatif yang lebih efisien, tetapi memerlukan kernel kustom per arsitektur yang didukung seperti AVX·NEON, dan dalam kasus ini harus melewati XNNPACK

Konversi model dan cara eksekusi

  • OnnxStream menjalankan model yang didefinisikan di path_to_model_folder/model.txt
    • Semua operasi model berada di model.txt dalam format ASCII
    • File weights harus ada sebagai rangkaian file .bin di folder yang sama
  • Beberapa parameter opsional dapat diatur pada objek Model
    • menentukan weights provider lain
    • membaca/menulis file rentang activation clipping untuk model terkuantisasi
    • mode kalibrasi model
    • menggunakan aritmetika FP16
    • menggunakan aritmetika UINT8
    • menggunakan kuantisasi dinamis UINT8
    • mengaktifkan attention slicing
    • mengatur jumlah pembagian attention
  • Untuk memakai file ONNX di OnnxStream, notebook onnx2txt.ipynb digunakan untuk mengekspor model.txt dan file weights .bin
  • Saat mengekspor nn.Module PyTorch ke ONNX, ada beberapa syarat
    • Saat memanggil torch.onnx.export, dynamic_axes harus dibiarkan kosong
    • OnnxStream tidak mendukung input dynamic shape
    • Menjalankan ONNX Simplifier sebelum konversi sangat dianjurkan

Persiapan build dan eksekusi

  • Contoh Stable Diffusion dapat dibangun di Linux, Mac, Windows, dan Termux
    • Di Windows digunakan x64 Native Tools Command Prompt dari Visual Studio Tools
    • Di Mac perlu memasang cmake dengan brew install cmake
  • Pertama, XNNPACK harus dibangun lebih dulu
    • Prototype fungsi XNNPACK bisa berubah kapan saja, jadi prosedurnya mencakup checkout ke commit yang sesuai pada titik waktu tertentu
    • Titik acuannya adalah commit master sebelum 2023-06-27 00:00
  • Setelah itu clone repositori OnnxStream dan build dengan cmake di src/build
    • MAX_SPEED=ON
    • tentukan jalur clone XNNPACK lewat XNNPACK_DIR
  • Opsi MAX_SPEED dapat meningkatkan performa, tetapi menggunakan lebih banyak memori saat build
    • Di Windows ada peningkatan performa sekitar 10%
    • Di Raspberry Pi ada peningkatan performa lebih dari 50%
    • File executable yang dihasilkan bisa saja tidak berjalan, dan ada masalah pada pengujian Termux
    • Jika bermasalah, disarankan lebih dulu mengubah MAX_SPEED=OFF
  • Weights contoh Stable Diffusion 1.5 dapat diunduh dari Releases repositori dan ukurannya sekitar 2GB
  • Weights Stable Diffusion XL 1.0 Base dapat diunduh dari Hugging Face dan ukurannya sekitar 8GB

Opsi contoh Stable Diffusion

  • File executable contoh mengontrol pemilihan model, input/output, prompt, dan metode decoding lewat opsi command line
    • --xl: menjalankan Stable Diffusion XL 1.0 alih-alih Stable Diffusion 1.5
    • --models-path: menentukan folder model Stable Diffusion
    • --ops-printf: mencetak operation saat ini ke stdout selama inferensi
    • --output: menentukan file PNG output
    • --decode-latents: melewati diffusion dan mendekode file latent yang ditentukan
    • --prompt: menentukan positive prompt
    • --neg-prompt: menentukan negative prompt
    • --steps: menentukan jumlah diffusion step
    • --save-latents: menyimpan latent ke file yang ditentukan setelah diffusion
  • Opsi terkait Raspberry Pi dan decoder juga disediakan secara terpisah
    • --decoder-calibrate: mengalibrasi decoder VAE terkuantisasi hanya untuk SD 1.5
    • --decoder-fp16: menggunakan decoder VAE FP16 hanya untuk SD 1.5
    • --not-tiled: tidak menggunakan tiled VAE decoder hanya untuk SDXL 1.0
    • --rpi: mengonfigurasi model agar berjalan di Raspberry Pi
    • --rpi-lowmem: menerapkan konfigurasi memori rendah untuk Raspberry Pi Zero 2 hanya pada SDXL 1.0

1 komentar

 
GN⁺ 2023-10-04
Opini Hacker News
  • Menarik. Kalimat kuncinya ini: “OnnxStream bisa memakai memori hingga 55 kali lebih sedikit daripada OnnxRuntime, sementara kecepatannya bisa hanya 0,5–2 kali lebih lambat”
    Trade-off antara memori video/penggunaan memori dan waktu inferensi tampaknya bisa menguntungkan bukan hanya pada kasus memori terbatas seperti Raspberry Pi, tetapi juga dalam situasi lain
    Kalau metode unloading bobot ini memang memungkinkan batch size yang lebih besar dalam memori yang sama, saya penasaran apakah throughput bisa meningkat besar meski latensinya bertambah

    • Saya ingin memakai ini untuk LLM. Kalau jejak memorinya berkurang sebanyak itu, kita bisa memuat lebih banyak model sekaligus di satu GPU, dan dengan asumsi clock-nya memadai, itu mungkin cukup untuk mengimbangi penurunan kecepatan inferensi tiap model
    • “0,5–2 kali lebih lambat” bukankah salah ketik? Kalau 0,5 kali lebih lambat, artinya justru 2 kali lebih cepat
      Mungkin maksudnya “1,5–2 kali lebih lambat”
    • Dari pembacaan saya yang agak sederhana, sepertinya ini bukan mengurangi bandwidth memori yang dibutuhkan, melainkan hanya mengurangi ukuran working set
      Inferensi biasanya terikat bandwidth memori setelah melewati level “apakah model ini muat di sistem ini”, jadi saya rasa teknik ini tidak akan banyak membantu menaikkan throughput lewat batch size yang lebih besar. Satu instance saja kemungkinan besar sudah menjenuhkan memory controller
      Namun mungkin ini bisa membantu di sisi training
  • 11 jam, jadi teringat masa-masa ray tracing di Amiga 500 dulu. Render “final” jelas pekerjaan yang dijalankan semalaman

    • Sekarang pun kadang masih begitu. Ray tracer bidirectional modern bisa memakai teknik yang menarik, dan saya ingin melihat caustics seperti garis terang yang muncul di genangan air
      Tapi meski caustics terlihat terang, secara statistik itu fenomena yang cukup jarang, jadi untuk mendapatkannya dengan benar, batasan render engine harus dilepas dan dibiarkan berjalan semalaman
      Hasilnya satu gambar adegan biasa-biasa saja dari seniman yang kurang terampil, tetapi dengan caustics yang keren. Sepertinya saya harus tetap menjalani pekerjaan utama
    • Menjalankan render berkualitas rendah terlebih dulu itu karena lebih baik membuang satu jam untuk memastikan semuanya benar daripada membuang semalaman dalam kondisi salah
      Saat itu saya merasa butuh hobi lain. Tepat sebelumnya, seseorang yang hebat merilis tool rendering yang memungkinkan melihat adegan dulu dengan OpenGL. Tidak akan bisa di Amiga, tapi di mesin saya nyaris jalan
    • Di 286 saya juga sama. Saya menyiapkan povray, lalu tidur, dan pagi sebelum berangkat sekolah mengecek gambarnya
    • Memang terjadi belakangan, tapi saya melakukan hal serupa di 386 dengan salinan 3DSMAX yang meragukan
    • Saya ingat menjalankan fraktal Mandelbrot di C64. Debugging kodenya benar-benar sulit
  • Saya memakai Stable Diffusion lewat invoke.ai di MBP; ada rekomendasi untuk mengatur parameter SD dengan lebih baik? Dengan prompt yang sama dan setting yang tampaknya sama, misalnya model yang sama seperti Euler A, saya sama sekali tidak bisa menyamai kualitas gambar yang terlihat di internet

    • Dari yang pernah saya coba sejauh ini, ini yang terbaik, tetapi sepertinya tidak mendukung Mac. Ini fork Fooocus yang kaya fitur, dibuat oleh pengembang asli ControlNet, dan kualitas dari prompt pendeknya mengejutkan: https://github.com/MoonRide303/Fooocus-MRE
      Untuk SD 1.5 dasar saya memakai Volta karena cepat: https://github.com/VoltaML/voltaML-fast-stable-diffusion/com...
      Kualitas gambar SD 1.5 yang benar-benar bagus biasanya muncul kalau memakai model fine-tuned, LoRA, ControlNet, dan fitur penguat lain secara maksimal. Misalnya membuatnya mengikuti gambar dasar sebagai struktur, atau menentukan prompt per area tertentu pada gambar. InvokeAI sebenarnya juga punya banyak fitur, dan banyak kemampuan tambahan seperti ini tersembunyi di node UI, tetapi UI lain seperti Volta menampilkannya lebih langsung
    • Apakah Anda memakai custom weights? Mungkin sudah, tetapi ada perbedaan besar antara bobot RunwayML 1.5 bawaan dan model yang di-fine-tune untuk tujuan tertentu
      Secara umum, model fine-tuned yang mengesankan jauh kurang serbaguna daripada bobot dasar, tetapi dalam penggunaan nyata itu biasanya bukan masalah besar dan hasilnya bisa jauh lebih baik
    • Saya mengalami hal yang sama saat memakai Invoke.ai atau MochiDiffusion di MBP M1. Satu-satunya yang bisa menyamai kualitas gambar lain adalah Automatic1111(https://github.com/AUTOMATIC1111/stable-diffusion-webui)
      Memang butuh lebih banyak waktu dan memori daripada Invoke atau kartu grafis Nvidia, tetapi tidak terlalu buruk. Gambar berkualitas standar 512x768px sekitar 1–2 s/it, sedangkan gambar berkualitas tinggi 1024x1536px dengan Hires Fix sekitar 14–20 s/it
    • Apakah gambar-gambar itu disebutkan keluar langsung dari generator? Video proses yang saya lihat biasanya dimulai dari “gadis berdiri di padang rumput hijau”, lalu melakukan inpainting lebih dari satu jam untuk memperbaiki tangan, pose, dan sebagainya
    • Draw Things menambahkan mode seed kompatibel CUDA, jadi di Mac pun bisa mencocokkan gambar yang dibuat di kartu NVDIA
  • Akan sangat keren kalau ini ditanamkan dan dijalankan di bingkai foto digital atau lukisan dinding

  • Ini pencapaian yang mengagumkan, tetapi tentu saja butuh waktu sangat lama untuk menghasilkan gambar. Di README tertulis 11 jam

    • Benar. Saya tidak membutuhkan atau akan memakai implementasi ini, tetapi teknik-teknik yang digunakan di sini akan berpindah ke alat lain, dan itu akan sangat bagus
    • Akan menarik melihat seberapa setara ini dari sisi biaya dan daya dibandingkan metode yang lebih canggih
      Misalnya membandingkan waktu, biaya, semua hardware yang diperlukan, dan listrik untuk membuat 100 gambar dengan 100 unit Pi Zero 2—bahkan tidak harus yang W—dengan sesuatu seperti PC kelas menengah rata-rata
      Mungkin PC masih akan menang
      Zero 2 terasa dipakai lebih sebagai tantangan daripada untuk kepraktisan, jadi Pi 4 atau 5 mungkin bisa menjadi patokan yang lebih baik
    • Intinya adalah ini berjalan di Raspberry Pi, bahkan Zero 2
  • Mengesankan
    Sungguh, zaman ketika bahkan lampu dan toaster menyimpan kecerdasan unggul sudah dekat
    Saya sudah mengikuti bidang ini selama bertahun-tahun, tetapi 10 tahun terakhir ini menakjubkan
    Alasan saya mengatakan “sudah” adalah karena akselerasi 6–18 bulan terakhir berada di level yang benar-benar berbeda lagi
    Saya bukan khawatir tentang apa yang bisa dilakukan 2 tahun lagi, melainkan seberapa jauh laju perkembangannya akan makin cepat. Dan kemudian akan makin cepat lagi, dan lagi

    • Toaster yang menerima prompt lalu menggambar gambar itu di atas roti panggang terdengar bagus. Panas GPU juga bisa dipakai untuk benar-benar memanggang roti
      Ayo bikin startup
  • Pada titik ini, bukankah upaya meregulasi teknologi sejenis Stable Diffusion sudah berakhir? Jika model dan infrastruktur inferensi bisa diperkecil hingga level yang bisa berjalan di PS2, tampaknya mustahil menghentikan teknologi ini tanpa negara pengawas totaliter, dan bahkan di negara seperti itu pun mungkin hanya nyaris bisa

    • Perang melawan komputasi serbaguna masih terus berlangsung, tetapi belum menembus sampai ke titik mencegah orang memiliki perangkat komputasi serbaguna
    • Dengan logika itu, bukankah regulasi pencurian juga sudah berakhir? Selama punya tubuh, orang bisa membuka jendela tanpa alat, jadi artinya pencurian tidak bisa dicegah tanpa negara pengawas totaliter
      Hal yang sama juga berlaku untuk “pembajakan” media atau ransomware
      Negara sejak lama meregulasi hal-hal yang tidak bisa ditegakkan secara teknis secara murni
    • Kebutuhan asli model seperti ini adalah RAM 16GB, dan itu bisa dibeli dengan harga di bawah 20 dolar. Ini berjalan jauh lebih cepat di GPU, dan GPU seperti itu juga bisa dibeli di bawah 200 dolar. Jutaan orang biasa sudah memiliki keduanya
    • PS2 hanya punya 32MB RAM. PS3 pun cuma 256MB
      Saya tahu itu contoh hiperbola yang lucu, tetapi untuk berjalan di PS2 perlu diperkecil jauh lebih banyak daripada ini
    • Saya kira sebagian besar upaya regulasi berfokus pada eksekusi training yang makin besar, bukan pada penggunaan generatif dari model yang sudah ada. Apakah ada regulasi tersendiri seputar penggunaan model itu sendiri?
  • Saya tidak sabar menunggu Stable Diffusion untuk Windows 3.1 hadir

  • Gila. Entah butuh 11 jam atau tidak, saya sama sekali tidak menyangka SD bisa berjalan di hardware seperti Pi Zero