Model yang sudah tersimpan dalam paket produksi belum bisa menerima permintaan dari siapa pun — paket itu hanya berkas diam. Model serving adalah seni mengubah berkas tersebut menjadi layanan yang hidup: sebuah proses yang menunggu permintaan, menjalankan inference, dan mengirim balik hasilnya. Pilihan pola penyajian menentukan kualitas layanan: latensi, jumlah permintaan per detik yang mampu ditangani, hingga kelakuan sistem saat terjadi kegagalan. Chapter ini memetakan tiga pola serving yang dominan, membahas aspek yang wajib dijaga, lalu membangun server TCP mini dari pustaka standar Python yang benar-benar melayani prediksi.

Tiga Pola Penyajian Model #

PolaCara kerjaCocok untuk
Embedded librarymodel dimuat di dalam proses aplikasiaplikasi desktop, skrip analitik, latensi paling rendah
HTTP / TCP serviceserver terpisah menunggu permintaan jaringanaplikasi web, banyak klien, tim terpisah
Batch offlineprediksi dihitung massal terjadwalskor harian, rekomendasi katalog besar, pelaporan

Pola embedded menempatkan model di ruang memori yang sama dengan kode pemanggil sehingga tidak ada biaya jaringan. Pola service memisahkan model dari aplikasi — biayanya komunikasi jaringan, imbalannya model bisa dipakai banyak aplikasi sekaligus dan diperbarui tanpa men-deploy ulang aplikasi. Pola batch memindahkan seluruh pekerjaan ke luar jam sibuk; pembahasannya mendapat chapter sendiri di Batch Inference .

Alur sebuah request pada pola service dapat digambarkan sebagai berikut:

Setiap kotak di jalur utama menambah waktu ke total latensi — bukan hanya kotak Model inference. Kesadaran ini penting: optimasi latensi pada chapter Optimasi Latensi menyasar seluruh jalur, bukan modelnya saja.

Aspek yang Dijaga sebuah Server #

Tiga aspek menentukan apakah sebuah server model layak berjalan produksi. Pertama, concurrency: berapa permintaan yang datang bersamaan dan mampu dilayani — server berulir (multi-thread) atau multi-proses menjawab ini. Kedua, latency SLO: janji layanan seperti “P95 di bawah 100 ms” yang diukur dari ujung ke ujung (dibedah di Real-Time Inference ). Ketiga, health check: endpoint ringan yang dilakukan ping oleh sistem pemantau agar server yang berhenti cepat terdeteksi dan diganti.

Ekosistem menyediakan alat siap pakai untuk skala besar: TorchServe dari proyek PyTorch (github.com/pytorch/serve ), NVIDIA Triton Inference Server (docs.nvidia.com ), dan TensorFlow Serving (tensorflow.org/tfx/guide/serving ). Ketiganya mengurus versioning, batching otomatis, dan metrik — tetapi mekanisme intinya sama dengan server mini yang akan kita bangun: menerima permintaan, inference, membalas.

Praktik: Server TCP Mini dengan Pustaka Standar #

Berkas server_tcp_mini.py membangun server TCP memakai modul socketserver dari pustaka standar Python — tanpa framework apa pun. Server memuat model yang dilatih di awal skrip, menunggu koneksi di thread terpisah, dan merespons setiap baris JSON. Protokolnya sederhana: klien mengirim {"ping": true} untuk health check atau {"fitur": [x, y]} untuk prediksi:

python
"""Model serving minimal: server TCP + loop inferensi, stdlib saja.

Server berjalan di thread terpisah dan menerima koneksi TCP. Untuk
setiap koneksi, server membaca satu baris JSON, menjalankan inference,
lalu mengirim respons JSON kembali. Client mengirim tiga permintaan:
satu health check dan dua permintaan prediksi.
"""

import json
import socket
import socketserver
import threading

import torch
import torch.nn as nn
from sklearn.datasets import make_moons
from torch.utils.data import DataLoader, TensorDataset

torch.manual_seed(42)
X, y = make_moons(n_samples=256, noise=0.20, random_state=42)
X = (X - X.mean(axis=0)) / X.std(axis=0)
loader = DataLoader(
    TensorDataset(
        torch.tensor(X, dtype=torch.float32),
        torch.tensor(y, dtype=torch.long),
    ),
    batch_size=32,
    shuffle=True,
    num_workers=0,
    generator=torch.Generator().manual_seed(42),
)
model = nn.Sequential(nn.Linear(2, 12), nn.ReLU(), nn.Linear(12, 2))
kriteria = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.5)
for _ in range(20):
    for X_batch, y_batch in loader:
        optimizer.zero_grad()
        loss = kriteria(model(X_batch), y_batch)
        loss.backward()
        optimizer.step()
model.eval()


def tangani_permintaan(pesan):
    """Satu jalur inference: JSON masuk -> JSON keluar."""
    if "ping" in pesan:
        return {"status": "ok", "versi": "1.0.0"}
    fitur = torch.tensor([pesan["fitur"]], dtype=torch.float32)
    with torch.no_grad():
        probabilitas = torch.softmax(model(fitur), dim=1)[0]
    kelas = int(probabilitas.argmax())
    return {"kelas": kelas, "probabilitas_1": round(float(probabilitas[1]), 4)}


class PenanganKoneksi(socketserver.BaseRequestHandler):
    """Baca satu baris JSON, kirim satu baris JSON."""

    def handle(self):
        data = self.request.recv(1024).decode().strip()
        respons = tangani_permintaan(json.loads(data))
        self.request.sendall((json.dumps(respons) + "\n").encode())


class ServerModel(socketserver.ThreadingTCPServer):
    allow_reuse_address = True
    daemon_threads = True


server = ServerModel(("127.0.0.1", 0), PenanganKoneksi)  # port 0: port kosong otomatis
threading.Thread(target=server.serve_forever, daemon=True).start()
print("server model berjalan di 127.0.0.1 (port dipilih otomatis)")


def kirim(pesan):
    """Client: buat koneksi, kirim JSON, terima satu respons."""
    with socket.create_connection(server.server_address, timeout=5) as koneksi:
        koneksi.sendall((json.dumps(pesan) + "\n").encode())
        return json.loads(koneksi.recv(1024).decode())


print("permintaan 1 (health)  ->", kirim({"ping": True}))
print("permintaan 2 (prediksi)->", kirim({"fitur": [0.5, -0.8]}))
print("permintaan 3 (prediksi)->", kirim({"fitur": [-1.2, 0.9]}))
server.shutdown()
print("server dihentikan")

Output yang Dihasilkan:

text
server model berjalan di 127.0.0.1 (port dipilih otomatis)
permintaan 1 (health)  -> {'status': 'ok', 'versi': '1.0.0'}
permintaan 2 (prediksi)-> {'kelas': 1, 'probabilitas_1': 0.8687}
permintaan 3 (prediksi)-> {'kelas': 0, 'probabilitas_1': 0.0282}
server dihentikan

Mari kita telaah kode di atas baris per baris:

  1. Baris 17-46 (persiapan model): model dilatih sekali di awal proses, lalu masuk mode eval(). Dalam server sungguhan, bagian ini diganti pemuatan paket produksi dari chapter Menyimpan Model untuk Produksi — prinsipnya sama: inference tidak pernah mencampur training.
  2. Baris 49-57 (tangani_permintaan): seluruh jalur inference dipisah menjadi satu fungsi murni — dari JSON mentah sampai JSON respons, dijalankan di dalam torch.no_grad(). Fungsi inilah “otak” server; health check ditangani cabang khusus agar pemantau tidak membebani jalur model.
  3. Baris 60-67 (PenanganKoneksi.handle): setiap koneksi TCP baru memicu satu pemanggilan handle. Protokolnya satu-untuk-satu: satu baris JSON masuk, satu baris JSON keluar — bentuk paling sederhana dari request-response.
  4. Baris 70-79 (ServerModel, port 0): ThreadingTCPServer menjadikan setiap koneksi berjalan di threadnya sendiri — concurrency sederhana untuk model kecil. Port 0 memerintahkan sistem operasi memilih port kosong, sehingga skrip tidak pernah bentrok dengan layanan lain di mesin yang sama.
  5. Baris 81-83 (thread server): serve_forever berjalan sebagai daemon thread agar loop utama tetap bisa mengirim permintaan sebagai klien — satu skrip memainkan dua peran sekaligus.
  6. Baris 86-90 (kirim): fungsi klien membuka koneksi, mengirim JSON, dan membaca satu balasan — pola yang nanti digantikan HTTP client pada chapter REST API untuk Model .

Protokol TCP baris-per-baris seperti ini mudah diuji tetapi tidak lazim di dunia aplikasi modern. Standar de facto penyajian model adalah HTTP dengan JSON — bahasa yang dipahami semua bahasa pemrograman dan siap ditambah validasi, autentikasi, serta dokumentasi otomatis. Chapter berikutnya membangun REST API lengkap dengan FastAPI di atas fondasi yang sama.