Layanan inferensi

Server model sederhana dengan FastAPI untuk melayani inferensi.

Daftar isi

Topik ini menyusun model PyTorch menjadi layanan inferensi, yaitu program yang menerima permintaan (request) melalui jaringan dan mengembalikan hasil prediksi sebagai respons. Framework yang digunakan adalah FastAPI, dengan model regresi linear kecil sebagai penyedia prediksi. Pembahasan mencakup struktur file aplikasi, cara menjalankan server pengembangan, dan cara menguji endpoint tanpa menjalankan server sama sekali. Setelah selesai, pola yang sama dapat digunakan untuk model apa pun yang sudah selesai dilatih.

Bentuk dasar layanan model#

Melayani model berarti mengubahnya dari skrip yang dijalankan sekali menjadi program yang terus berjalan dan menjawab permintaan. Model dimuat satu kali ke memori saat aplikasi dimulai, dan setiap permintaan yang datang cukup dilayani dengan memanggil model lalu mengembalikan hasilnya. Pola ini menghindari pemuatan ulang bobot pada setiap prediksi, yang jauh lebih mahal daripada perhitungan prediksinya sendiri.

FastAPI, yaitu framework API untuk Python, mengurus bagian jaringan dan serialisasi JSON. Aplikasi ditulis sebagai sekumpulan fungsi yang diberi alamat endpoint, yakni jalur URL tertentu seperti /health atau /predict. Server yang lazim dipasangkan dengannya adalah uvicorn, server ASGI yang menerima koneksi HTTP lalu meneruskannya ke aplikasi. ASGI sendiri adalah standar antarmuka server Python untuk aplikasi asinkron. Diagram berikut menggambarkan alur satu permintaan prediksi, dari klien yang mengirim JSON hingga respons kembali:

Alur permintaan inferensi pada layanan FastAPIKliencurl / aplikasiPOST /predictFastAPIvalidasi pydantictensorModeleval + no_gradrespons JSON
Alur permintaan inferensi: klien, validasi pydantic pada FastAPI, model PyTorch, lalu respons JSON kembali

Setiap kotak pada diagram berada di dalam satu proses aplikasi. Yang berubah ketika model berganti hanyalah isi kotak ketiga, sedangkan bentuk kontrak, yaitu alamat endpoint dan struktur JSON, tetap dirancang lebih dahulu agar klien tidak perlu mengetahui detail model yang digunakan.

Struktur app.py#

File app.py berisi seluruh aplikasi. Pydantic, pustaka validasi yang digunakan FastAPI untuk memeriksa data, mendefinisikan bentuk badan permintaan melalui kelas Masukan: satu bidang jam_belajar bertipe float dengan batasan ge=0 dan le=24. Apabila nilai yang datang berada di luar batas itu, FastAPI menolaknya dengan kode status 422 tanpa kode pemeriksaan tambahan apa pun. Model sendiri dibuat pada tingkat modul, sehingga pemuatannya berlangsung satu kali saat aplikasi berjalan, bukan pada setiap permintaan.

python app.py
import torch
import torch.nn as nn
from fastapi import FastAPI
from pydantic import BaseModel, Field

# model regresi linear sederhana: skor ujian diprediksi dari jam belajar
# pada proyek nyata, bobot diisi dari berkas checkpoint, bukan ditebak
model = nn.Linear(1, 1)
with torch.no_grad():
    model.weight.fill_(5.0)  # setiap jam belajar menambah lima poin
    model.bias.fill_(40.0)   # nilai dasar tanpa belajar
model.eval()

app = FastAPI(title="Layanan skor ujian")


class Masukan(BaseModel):
    jam_belajar: float = Field(ge=0, le=24)


@app.get("/health")
def health():
    return {"status": "siap"}


@app.post("/predict")
def predict(masukan: Masukan):
    # daftar dari JSON diubah menjadi tensor berbentuk (1, 1)
    x = torch.tensor([[masukan.jam_belajar]])
    with torch.no_grad():
        skor = model(x).item()
    skor = min(max(skor, 0.0), 100.0)
    return {"skor": skor}

Endpoint /health hanya menjawab status agar aplikasi pemantau mengetahui bahwa layanan masih hidup. Endpoint /predict berisi pekerjaan inti: nilai yang datang dari JSON diubah menjadi tensor berbentuk (1, 1), model dipanggil di dalam torch.no_grad() karena inferensi tidak membutuhkan gradien, dan hasil tensor dikonversi menjadi bilangan Python dengan .item() sebelum dikirim. Konversi tersebut wajib karena JSON tidak mengenal tipe tensor. Pemangkasan dengan min dan max menjaga skor tetap pada rentang 0 sampai 100 bila masukannya besar.

Menjalankan server dengan uvicorn#

Blok perintah berikut menjalankan server pada terminal. Notasi app:app berarti objek bernama app di dalam file app.py, dan opsi --reload memulai ulang server secara otomatis setiap file berubah, yang berguna saat pengembangan tetapi tidak diperlukan di production. Perintah curl menyimulasikan klien yang mengirim badan JSON ke endpoint /predict.

bash
# tidak-dijalankan
# server berjalan terus dan memblokir sesi shell, sehingga blok ini tidak
# dieksekusi di sandbox. Jalankan perintahnya pada terminal sendiri dari
# folder yang memuat app.py
uvicorn app:app --reload
curl -X POST http://127.0.0.1:8000/predict -H "Content-Type: application/json" -d '{"jam_belajar": 3}'

Server yang berjalan terus tidak dapat dieksekusi di sandbox karena memblokir sesi, sehingga blok di atas hanya berupa petunjuk perintah. Apabila dijalankan pada terminal sendiri, permintaan curl tersebut mengembalikan {"skor": 55.0}, persis seperti hasil uji pada bagian berikutnya, dan server menunggu permintaan selanjutnya hingga dihentikan.

Menguji endpoint tanpa server#

Menjalankan server untuk setiap pengujian lambat dan sulit diautomasi. FastAPI menyediakan TestClient, yaitu klien uji yang memanggil aplikasi secara langsung di dalam proses yang sama, tanpa koneksi jaringan dan tanpa uvicorn. Kelas ini berada di fastapi.testclient dan memerlukan paket httpx yang memang menjadi dependensi FastAPI. Kode berikut menguji kedua endpoint beserta satu kasus masukan yang tidak sah:

python Uji endpoint dengan TestClient
import sys
sys.path.insert(0, ".")  # pastikan app.py pada direktori kerja dapat diimpor

from fastapi.testclient import TestClient
from app import app

klien = TestClient(app)

respons = klien.get("/health")
print("GET /health           :", respons.status_code, respons.json())

respons = klien.post("/predict", json={"jam_belajar": 3})
print("POST /predict (3 jam) :", respons.status_code, respons.json())

respons = klien.post("/predict", json={"jam_belajar": 20})
print("POST /predict (20 jam):", respons.status_code, respons.json())

respons = klien.post("/predict", json={"jam_belajar": -2})
print("POST /predict (-2 jam):", respons.status_code)
text Keluaran
GET /health           : 200 {'status': 'siap'}
POST /predict (3 jam) : 200 {'skor': 55.0}
POST /predict (20 jam): 200 {'skor': 100.0}
POST /predict (-2 jam): 422

Empat baris keluaran menjawab empat hal berbeda. Endpoint /health mengembalikan 200 dengan status siap. Permintaan dengan tiga jam belajar mengembalikan skor 55.0, hasil dari lima poin per jam ditambah nilai dasar 40, sedangkan dua puluh jam menghasilkan prediksi mentah 140.0 yang dipangkas menjadi 100.0 oleh pemangkasan. Masukan -2 tidak pernah sampai ke model karena validasi pydantic menolaknya dengan kode 422, yaitu kode bawaan FastAPI untuk badan permintaan yang tidak sah. Karena seluruh panggilan dilakukan secara langsung, rangkaian ini dapat dijadikan bagian dari pengujian otomatis tanpa menyentuh jaringan.

Kesalahan umum#

Kesalahan pertama adalah mengembalikan objek tensor dari endpoint, misalnya {"skor": model(x)} tanpa .item(). Tensor bukan tipe data JSON, sehingga FastAPI tidak dapat mengubahnya menjadi teks respons dan hasilnya tidak dapat digunakan klien. Perbaikannya selalu berupa konversi ke bilangan Python terlebih dahulu, baik dengan .item() untuk satu nilai maupun .tolist() untuk satu daftar nilai.

Kesalahan kedua adalah menjalankan uvicorn app:app dari direktori yang salah. Uvicorn mencari file app.py pada direktori kerja saat itu, dan apabila tidak ditemukan, server tidak dapat dimulai dan menampilkan pesan Could not import module "app". Perbaikannya adalah memastikan terminal berada di folder yang memuat app.py sebelum menjalankan perintah, atau menyertakan jalur paket yang benar pada notasi file:objek.

Ringkasan#

  • Layanan inferensi memuat model satu kali saat aplikasi mulai, lalu menjawab permintaan HTTP dengan prediksi berbentuk JSON.
  • FastAPI menangani validasi badan permintaan melalui model pydantic dan serialisasi respons, sedangkan uvicorn melayani aplikasi tersebut sebagai server ASGI.
  • Konversi wajib dilakukan dua kali: list dari JSON menjadi tensor sebelum model dipanggil, dan tensor menjadi bilangan Python dengan .item() sebelum dikirim.
  • TestClient menguji seluruh endpoint di dalam proses yang sama tanpa server, sehingga cocok untuk pengujian otomatis.

Referensi#