Ketika pertama kali membuat OCR dari foto dokumen, pertanyaan saya sederhana:
Apakah modelnya bisa membaca foto dengan benar?
Setelah Qwen3-VL mulai memberikan hasil yang cukup baik, pertanyaannya berubah.
Bagaimana kalau fotonya bukan ratusan, tapi puluhan ribu?
Di titik ini, mengganti model dengan model yang lebih pintar bukan satu-satunya masalah.
Saya justru mulai berhadapan dengan masalah yang lebih praktis: bagaimana mengatur concurrency supaya foto bisa diproses secepat mungkin, GPU tidak menganggur, tetapi hasil extraction tetap akurat.
Karena kalau saya kirim satu foto setiap kali, hasilnya memang relatif aman, tetapi GPU tidak dimanfaatkan secara maksimal.
Sebaliknya, kalau saya kirim sebanyak mungkin secara paralel, belum tentu hasilnya menjadi lebih cepat.
Request bisa mulai mengantre, penggunaan VRAM meningkat, latency menjadi tidak stabil, bahkan ada kemungkinan model harus melakukan reload atau proses inference menjadi terganggu.
Jadi saya tidak mencari angka concurrency terbesar.
Saya mencari titik concurrency yang paling masuk akal untuk workload OCR saya.
Dari 1 Worker ke 4 Worker
Model yang saya gunakan adalah Qwen3-VL 8B yang dijalankan secara self-hosted menggunakan Ollama.
Server memiliki dua Tesla T4 16GB.
Konfigurasi awal saya cukup konservatif.
Saya mulai dengan parallelism 1.
Kemudian saya naikkan menjadi 4 dan melihat bagaimana sistem bereaksi.
Di sisi orchestration, saya menggunakan n8n.
Workflow berjalan setiap 60 detik untuk mengecek foto yang perlu diproses.
Tetapi saya tidak ingin semua foto yang ditemukan langsung ditembakkan ke model secara bersamaan.
Konsepnya lebih seperti antrean:
foto masuk → ambil pekerjaan → preprocessing → inference → extraction → selesai
Kemudian worker berikutnya mengambil pekerjaan berikutnya.
Dengan pendekatan seperti ini, jumlah foto yang sedang diproses bisa dikontrol.
Kenapa Tidak Diproses Semua Sekaligus?
Misalnya ada 1.000 foto yang menunggu.
Secara sederhana kita mungkin berpikir:
“Kalau 4 paralel bagus, berarti 100 paralel pasti lebih cepat.”
Kenyataannya tidak begitu.
GPU memiliki kapasitas terbatas.
Setiap request membawa gambar, membutuhkan memory, melakukan image understanding, kemudian menghasilkan output.
Ketika concurrency terlalu tinggi, kita tidak mendapatkan 100 inference yang berjalan lebih cepat.
Yang kita dapat bisa saja hanya 100 request yang sedang menunggu sumber daya yang sama.
Akibatnya throughput tidak naik secara linear.
Latency justru mulai meningkat.
Karena itu concurrency bagi saya bukan sekadar konfigurasi performa.
Concurrency adalah cara mengatur antrean pekerjaan supaya GPU terus bekerja tanpa dipaksa bekerja di luar kapasitasnya.
Yang Saya Ukur Bukan Cuma Kecepatan
Saat melakukan tuning, saya tidak hanya melihat:
“Berapa detik satu request?”
Saya juga melihat beberapa hal sekaligus:
- berapa banyak foto yang selesai diproses,
- latency median,
- P90 latency,
- penggunaan GPU,
- penggunaan VRAM,
- panjang antrean,
- error atau timeout,
- dan yang paling penting: apakah hasil extraction tetap benar.
Karena untuk OCR, throughput tinggi tetapi hasilnya salah bukan sebuah optimasi.
Misalnya sistem mampu memproses 10 foto per detik, tetapi banyak nomor dokumen, tanggal, atau nama yang salah terbaca.
Itu bukan sistem OCR yang lebih baik.
Itu hanya sistem yang lebih cepat menghasilkan kesalahan.
Accuracy Saya Jadikan Constraint
Ini yang membedakan pipeline OCR dengan benchmark inference biasa.
Saya tidak bisa hanya mengejar throughput.
Hasil extraction tetap harus memenuhi kebutuhan bisnis.
Jadi ketika melakukan tuning concurrency, saya tetap memperhatikan hasil akhirnya.
Kalau concurrency dinaikkan dan latency turun tetapi hasil extraction tetap stabil, konfigurasi tersebut masuk akal.
Kalau throughput naik tetapi akurasi mulai turun, saya tidak akan menganggapnya sebagai improvement.
Artinya konfigurasi tersebut terlalu agresif untuk workload saya.
Dengan cara berpikir seperti ini, target saya bukan:
maximum concurrency
tetapi:
maximum safe throughput.
Tidak Semua Foto Harus Diperlakukan Sama
Puluhan ribu foto juga tidak berarti semuanya memiliki tingkat kesulitan yang sama.
Ada foto yang jelas.
Dokumen terlihat penuh.
Teks mudah dibaca.
Extraction bisa langsung berhasil.
Tapi ada juga foto yang blur, miring, terlalu gelap, memiliki background yang mengganggu, atau sebagian dokumennya terpotong.
Kalau semua foto diperlakukan dengan konfigurasi yang sama, foto yang mudah akan ikut membayar biaya processing foto yang sulit.
Karena itu pendekatan yang menurut saya lebih masuk akal adalah membuat semacam fast path dan retry path.
Foto normal:
preprocessing → Qwen3-VL → extraction → selesai
Foto yang hasilnya tidak memenuhi validasi:
preprocessing → Qwen3-VL → validasi gagal → retry/reprocessing
Dengan begitu, kita tidak perlu memproses ulang semua foto hanya karena sebagian kecil foto bermasalah.
Preprocessing Juga Bagian dari Concurrency
Sebelumnya saya menganggap preprocessing hanya sebagai tahap sebelum model.
Setelah workload membesar, saya melihatnya berbeda.
Preprocessing juga menentukan seberapa cepat worker bisa menyelesaikan satu pekerjaan.
Kalau foto terlalu besar, data yang masuk ke inference menjadi lebih berat.
Kalau preprocessing terlalu lambat, GPU bisa saja malah menunggu input.
Jadi pipeline bukan hanya:
GPU secepat apa?
Tetapi:
seberapa cepat foto bisa sampai ke GPU dalam bentuk yang tepat?
Karena itu saya melihat keseluruhan pipeline sebagai satu sistem:
Queue → preprocessing → inference → extraction → validation
Bukan mengoptimalkan Ollama sendirian.
Setelah Dijalankan, Angkanya Mulai Berbicara
Dengan konfigurasi tersebut, saya kemudian membiarkan pipeline bekerja di production.
Untuk observasi ini saya mengambil log Ollama selama tujuh hari.
Dalam periode tersebut tercatat:
82.506 request
dengan total:
56.330 foto.
Rata-rata latency berada di sekitar 5,1 detik per request.
Pada hari dengan traffic tertinggi, request mencapai lebih dari 18 ribu.
Yang menarik, GPU juga bekerja sangat berat. Pada workload tertinggi, compute GPU tercatat sekitar 22,3 jam dari 24 jam.
Bagi saya ini menunjukkan bahwa concurrency yang digunakan sudah cukup untuk membuat GPU bekerja secara aktif.
Tetapi bukan berarti angka tersebut adalah angka final yang tidak bisa ditingkatkan.
Justru dari sini saya bisa melihat batas sistem dan mulai mencari bottleneck berikutnya.
Yang Saya Cari Bukan “Seberapa Banyak Bisa Paralel?”
Pertanyaan saya akhirnya berubah.
Bukan:
“Berapa concurrency terbesar yang bisa saya jalankan?”
Tetapi:
“Berapa concurrency yang masih memberikan throughput tinggi dengan latency yang masuk akal dan extraction tetap akurat?”
Karena ketiga hal tersebut harus berjalan bersama.
Throughput tinggi.
Latency terkendali.
Extraction tetap benar.
Kalau salah satunya dikorbankan terlalu jauh, optimasi tersebut tidak terlalu berguna untuk kebutuhan OCR saya.
Dan menurut saya, inilah bagian yang paling menarik ketika membawa VLM dari sekadar eksperimen menjadi sistem production.
Model memang penting.
Tetapi setelah model bisa bekerja, masalah sebenarnya mulai bergeser ke bagaimana kita mengatur pekerjaan yang masuk ke model.
Puluhan ribu foto tidak perlu diproses sekaligus.
Yang dibutuhkan adalah antrean yang sehat, concurrency yang sesuai kemampuan GPU, preprocessing yang efisien, dan mekanisme validasi supaya foto yang sulit tidak merusak keseluruhan pipeline.
Pada akhirnya, tujuan saya bukan membuat GPU bekerja sekeras mungkin.
Tujuannya adalah membuat GPU bekerja seefisien mungkin untuk menghasilkan OCR yang benar.



