Inference servis: modelni HTTP orqali berish
Eshiksiz restoran
Shahar chekkasida ajoyib oshpaz bor. U chindan ham zo'r pishiradi — buni hamma tan oladi.
Faqat bir muammo: uning restorani yo'q. Eshik yo'q, ofitsiant yo'q, menyu yo'q, telefon yo'q.
Ovqat yemoqchi bo'lsangiz, uning uyiga borib, oshxonasiga kirib, o'zingiz idish olib, o'zingiz solib yeyishingiz kerak.
Oshpaz zo'r. Lekin restoran emas.
Hozirgacha bizda aynan shunday holat: model.onnx fayli bor, u to'g'ri bashorat qiladi.
Lekin undan foydalanish uchun Python bilish, kutubxonalarni o'rnatish va kod yozish kerak.
Mobil ilova buni qila olmaydi. Veb-sayt qila olmaydi. Hamkasbingizning PHP dagi tizimi ham.
Eshik, ofitsiant va menyu
Modelni foydalanishga yaroqli qilish uchun uch narsa kerak, va ular restorandagidek:
- Eshik — kirish nuqtasi. Dasturda bu HTTP manzil:
POST /bashorat. - Menyu — nima so'rash mumkinligi. Dasturda bu sxema: qaysi maydonlar, qanday turda.
- Ofitsiant — buyurtmani tekshiradi va oshxonaga uzatadi. Dasturda bu validatsiya: noto'g'ri so'rovni oshpazga umuman yetkazmaydi.
Oxirgisi eng muhim. Yaxshi ofitsiant "bizda bu taom yo'q" deb darhol aytadi. Yomon ofitsiant buyurtmani oshxonaga uzatadi, oshpaz nimadir pishiradi, va mijoz kutilmagan narsa oladi.
6-bobni eslang: o'sha yerda yomon ma'lumot modelga yetib borgan va jim noto'g'ri javob bergan edi. Bu bobda uni eshikda to'xtatamiz.
Modeldan xizmatga
Model fayli o'zi hech kimga foyda bermaydi. Foydalanuvchi uni so'rov yuborib ishlatishi kerak.
diagramma( { "Mijoz\n(veb, mobil)": (0, 1.4, 2.4, 1.1, "manba"), "FastAPI\nvalidatsiya": (3.2, 1.4, 2.4, 1.1, "qayta"), "Xususiyat\n(3-bob)": (6.2, 1.4, 2.2, 1.1, "qayta"), "ONNX\n(26-bob)": (9.0, 1.4, 2.2, 1.1, "saqlash"), "Xato 422\n(yomon kirish)": (3.2, 3.4, 2.4, 1.0, "xizmat"), }, [ ("Mijoz\n(veb, mobil)", "FastAPI\nvalidatsiya", "JSON"), ("FastAPI\nvalidatsiya", "Xususiyat\n(3-bob)", "to'g'ri"), ("FastAPI\nvalidatsiya", "Xato 422\n(yomon kirish)", "noto'g'ri"), ("Xususiyat\n(3-bob)", "ONNX\n(26-bob)"), ], nom="inference_servis.png", kenglik=13, izoh="Validatsiya chegarada: yomon so'rov modelga umuman yetib bormaydi.", )

Validatsiya chegarada: yomon so'rov modelga umuman yetib bormaydi.
Diqqat: 6-bobdagi qoida shu yerda ham amal qiladi — tekshiruv chegarada. Farqi shundaki, bu
yerda javob darhol qaytariladi: 422.
Servis kodi
shu faylni yaratingsrc/xizmat/servis.py
"""Broker aniqlash servisi. FastAPI + ONNX. Diqqat: bu fayl `oqitish/` dan hech narsa import QILMAYDI (3-bob qoidasi). Faqat `quvur/` dagi umumiy kodni ishlatadi. """ import os import time from pathlib import Path from typing import Annotated, Any from fastapi import FastAPI, HTTPException from pydantic import BaseModel, Field from quvur.format import onnx_ehtimol, onnx_sessiya from quvur.xususiyat import bitta_qator MODEL_YOLI = os.environ.get("MODEL_YOLI", "model.onnx") CHEGARA = float(os.environ.get("BROKER_CHEGARASI", "0.5")) app = FastAPI(title="Broker aniqlash", version="1.0.0") HOLAT: dict[str, Any] = {"sessiya": None, "yuklandi": None, "sorovlar": 0} class Elon(BaseModel): """Kirish sxemasi. Chegaralar 6-bobdagi IJARA_SXEMASI bilan mos.""" xonalar: Annotated[int, Field(ge=1, le=10)] maydon: Annotated[float, Field(ge=10, le=500)] qavat: Annotated[int, Field(ge=1, le=60)] jami_qavat: Annotated[int, Field(ge=1, le=60)] narx_mln: Annotated[float, Field(ge=0.5, le=200)] tavsif_uzunligi: Annotated[int, Field(ge=0, le=5000)] rasmlar_soni: Annotated[int, Field(ge=0, le=100)] telefon_takrorlanishi: Annotated[int, Field(ge=1, le=500)] kunlik_elonlar: Annotated[int, Field(ge=1, le=200)] egasi_tasdiqlangan: Annotated[int, Field(ge=0, le=1)] class Javob(BaseModel): broker_ehtimoli: float broker: bool chegara: float ms: float def yukla() -> None: """Modelni bir marta yuklaydi. Fayl yo'q bo'lsa — xizmat baribir ishga tushadi.""" p = Path(MODEL_YOLI) if p.exists(): HOLAT["sessiya"] = onnx_sessiya(p) HOLAT["yuklandi"] = time.time() yukla() @app.get("/healthz") def tirikmi() -> dict[str, str]: """Jarayon ishlayaptimi. Model kerak EMAS.""" return {"holat": "tirik"} @app.get("/ready") def tayyormi() -> dict[str, Any]: """Xizmat ko'rsatishga tayyormi. Model yuklanmagan bo'lsa — 503.""" if HOLAT["sessiya"] is None: raise HTTPException(status_code=503, detail="model yuklanmagan") return {"holat": "tayyor", "sorovlar": HOLAT["sorovlar"], "chegara": CHEGARA} @app.post("/bashorat", response_model=Javob) def bashorat(elon: Elon) -> Javob: if HOLAT["sessiya"] is None: raise HTTPException(status_code=503, detail="model yuklanmagan") t0 = time.perf_counter() x = bitta_qator(elon.model_dump()) e = float(onnx_ehtimol(HOLAT["sessiya"], x.values)[0]) HOLAT["sorovlar"] += 1 return Javob(broker_ehtimoli=round(e, 4), broker=e >= CHEGARA, chegara=CHEGARA, ms=round((time.perf_counter() - t0) * 1000, 2))
Tayyorgarlik va ishga tushirish
python3 -c " import sys, pathlib; sys.path.insert(0, 'src') if not pathlib.Path('model.onnx').exists(): import joblib, pandas as pd from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import train_test_split from quvur.xususiyat import XUSUSIYATLAR, tayyorla from quvur.format import onnx_ga d = pd.read_csv('../datasets/ijara.csv') x, y = tayyorla(d), d['broker'] Xtr, _, ytr, _ = train_test_split(x, y, test_size=0.25, stratify=y, random_state=42) m = RandomForestClassifier(n_estimators=150, min_samples_leaf=3, random_state=42, n_jobs=1).fit(Xtr, ytr) joblib.dump(m, 'sinov_model.joblib') onnx_ga(m, len(XUSUSIYATLAR), 'model.onnx') print('model va ONNX yaratildi') else: print('model.onnx allaqachon bor') "
model va ONNX yaratildi
Serverni 18000-portda ishga tushiramiz (boshqa xizmatlarga xalaqit bermaslik uchun):
export PYTHONPATH=src python3 -m uvicorn xizmat.servis:app --host 127.0.0.1 --port 18000 \ --log-level warning > servis.log 2>&1 < /dev/null & echo $! > servis.pid for i in $(seq 1 40); do curl -s http://127.0.0.1:18000/healthz > /dev/null 2>&1 && break sleep 0.5 done curl -s http://127.0.0.1:18000/healthz echo "" curl -s http://127.0.0.1:18000/ready
{"holat":"tirik"}
{"holat":"tayyor","sorovlar":0,"chegara":0.5}
Serverni orqa fonda ishga tushirishning ikki tuzog'i
Men shu bobni yozayotganda ikkalasiga ham tushdim.
1. & && ro'yxatiga yopishib qoladi. Men avval shunday yozgan edim:
cd "$(pwd)" && PYTHONPATH=src python3 -m uvicorn ... &
Bu yerda & faqat python3 ga emas, butun cd && python3 ro'yxatiga tegishli. Bash uni
alohida qobiqda bajaradi va o'sha qobiqni kutib qoladi. Skript qotib qoldi. O'lchadim:
oddiy cmd & — 0.00 s, cd ... && cmd & — 25.00 s (ya'ni sleep 25 tugaguncha).
Yechim: cd va export ni alohida qatorga chiqarish.
2. pkill -f skriptning o'zini o'ldiradi. Keyingi urinishim:
pkill -f "port 18000"
pkill -f butun buyruq qatorini qidiradi. Skriptni bajarayotgan bash -c jarayonining
buyruq qatori esa — skriptning o'z matni, ichida port 18000 bor. Ya'ni bu buyruq o'zini
o'ldiradi. Yechim: PID ni saqlab qo'yish (echo $! > servis.pid) va aynan shuni o'ldirish.
Ikkala xato ham "server ishlamayapti" kabi ko'rinadi, lekin server aybdor emas edi.
/healthz va /ready — ikki boshqa savol:
| Nuqta | Savol | Kim so'raydi |
|---|---|---|
/healthz |
Jarayon tirikmi? | Konteyner boshqaruvchi (qayta ishga tushirsinmi) |
/ready |
Ish qabul qila oladimi? | Yuk muvozanatlagich (so'rov yuborsinmi) |
Farqi muhim: model yuklanayotgan paytda jarayon tirik, lekin tayyor emas. /healthz
ni ikkalasiga ishlatsangiz, yuk muvozanatlagich tayyor bo'lmagan xizmatga so'rov yuboradi.
Bashorat so'raymiz
echo "=== aniq EGA ===" curl -s -X POST http://127.0.0.1:18000/bashorat -H "Content-Type: application/json" -d '{ "xonalar": 2, "maydon": 55, "qavat": 3, "jami_qavat": 9, "narx_mln": 4.5, "tavsif_uzunligi": 600, "rasmlar_soni": 15, "telefon_takrorlanishi": 1, "kunlik_elonlar": 1, "egasi_tasdiqlangan": 1}' echo "" echo "=== aniq BROKER ===" curl -s -X POST http://127.0.0.1:18000/bashorat -H "Content-Type: application/json" -d '{ "xonalar": 2, "maydon": 55, "qavat": 3, "jami_qavat": 9, "narx_mln": 4.2, "tavsif_uzunligi": 60, "rasmlar_soni": 2, "telefon_takrorlanishi": 18, "kunlik_elonlar": 9, "egasi_tasdiqlangan": 0}'
=== aniq EGA ===
{"broker_ehtimoli":0.0563,"broker":false,"chegara":0.5,"ms":2.83}
=== aniq BROKER ===
{"broker_ehtimoli":0.9179,"broker":true,"chegara":0.5,"ms":1.5}
ms maydoniga qarang — bu faqat model vaqti, tarmoq emas. 26-bobdagi ONNX tanlovi shu yerda
o'zini oqlaydi.
Validatsiya ishlaydimi
Endi ataylab yomon so'rov yuboramiz:
echo "=== maydon = 0 (mumkin emas) ===" curl -s -o /dev/null -w "HTTP %{http_code}\n" -X POST http://127.0.0.1:18000/bashorat \ -H "Content-Type: application/json" -d '{ "xonalar": 2, "maydon": 0, "qavat": 3, "jami_qavat": 9, "narx_mln": 4.5, "tavsif_uzunligi": 300, "rasmlar_soni": 8, "telefon_takrorlanishi": 1, "kunlik_elonlar": 1, "egasi_tasdiqlangan": 1}' echo "=== maydon yetishmaydi ===" curl -s -X POST http://127.0.0.1:18000/bashorat -H "Content-Type: application/json" \ -d '{"xonalar": 2}' | head -c 220 echo ""
=== maydon = 0 (mumkin emas) ===
HTTP 422
=== maydon yetishmaydi ===
{"detail":[{"type":"missing","loc":["body","maydon"],"msg":"Field required","input":{"xonalar":2}},{"type":"missing","loc":["body","qavat"],"msg":"Field required","input":{"xonalar":2}},{"type":"missing","loc":["body","j
422 qaytdi va qaysi maydon noto'g'ri ekani aytildi. Model bu so'rovni umuman ko'rmadi.
6-bobdagi tajribani eslang: o'sha yerda maydon = 0 modelga yetib borgan va jim yomon natija
bergan edi. Bu yerda u chegarada to'xtatildi.
Latency o'lchash
python3 -c " import json, time, urllib.request import numpy as np ELON = {'xonalar': 2, 'maydon': 55, 'qavat': 3, 'jami_qavat': 9, 'narx_mln': 4.5, 'tavsif_uzunligi': 300, 'rasmlar_soni': 8, 'telefon_takrorlanishi': 4, 'kunlik_elonlar': 2, 'egasi_tasdiqlangan': 0} xom = json.dumps(ELON).encode() def sorov(): t0 = time.perf_counter() r = urllib.request.Request('http://127.0.0.1:18000/bashorat', data=xom, headers={'Content-Type': 'application/json'}) with urllib.request.urlopen(r, timeout=5) as j: j.read() return (time.perf_counter() - t0) * 1000 for _ in range(20): sorov() # isitish v = np.array([sorov() for _ in range(300)]) print(f' p50 : {np.percentile(v, 50):6.2f} ms') print(f' p95 : {np.percentile(v, 95):6.2f} ms') print(f' p99 : {np.percentile(v, 99):6.2f} ms') print(f' max : {v.max():6.2f} ms') "
p50 : 2.07 ms p95 : 2.52 ms p99 : 3.12 ms max : 22.28 ms
O'rtacha latency yolg'on gapiradi
Ko'p hisobotda "o'rtacha javob vaqti 8 ms" deb yoziladi. Lekin foydalanuvchi o'rtachani sezmaydi — u o'zining so'rovini sezadi.
Agar 100 ta so'rovdan 99 tasi 5 ms, bittasi 500 ms bo'lsa, o'rtacha 10 ms chiqadi va "yaxshi" ko'rinadi. Lekin har yuzinchi foydalanuvchi yarim soniya kutadi.
Shuning uchun p95 va p99 o'lchanadi. SLA ham shularga qo'yiladi: "p99 < 100 ms".
Model yo'q bo'lsa nima bo'ladi
Bu bo'lim ahamiyatsiz tuyuladi — model doim bo'ladi-ku. Lekin u aynan eng yomon kunda bo'lmaydi.
Bir nechta haqiqiy holat:
- Yangi server ko'tarildi, lekin model fayli hali ko'chirilmagan.
- Registrdagi model o'chirilgan yoki nomi o'zgargan (23-bob).
- Diskda joy tugagan va yuklash yarmida to'xtagan.
Har uchalasida savol bitta: xizmat nima qilishi kerak?
Ikki yo'l bor va farqi katta.
Birinchi yo'l: yiqilib tushish. Model yo'q bo'lsa, jarayon ishga tushmaydi. Ko'rinishi mantiqiy — "ishlay olmasam, ishlamayman".
Lekin natija yomon: konteyner qayta-qayta ko'tarilib yiqiladi, yuk muvozanatlagich nima bo'layotganini bilmaydi, log da shunchaki "crash" yoziladi. Sabab noma'lum.
Ikkinchi yo'l: ko'tarilish, lekin rostini aytish. Jarayon ishga tushadi, /healthz
"tirikman" deydi, /ready esa 503 qaytaradi va sababini yozadi.
Endi yuk muvozanatlagich bu serverga so'rov yubormaydi, monitoring aniq sababni ko'radi, va siz log o'qimasdan nima yetishmayotganini bilasiz.
Ikkinchisi to'g'ri, va uni tekshiramiz:
kill "$(cat servis.pid)" 2>/dev/null; sleep 1 export PYTHONPATH=src MODEL_YOLI=yoq_fayl.onnx python3 -m uvicorn xizmat.servis:app --host 127.0.0.1 --port 18001 \ --log-level warning > servis2.log 2>&1 < /dev/null & echo $! > servis2.pid for i in $(seq 1 40); do curl -s http://127.0.0.1:18001/healthz >/dev/null 2>&1 && break; sleep 0.5; done echo -n "healthz : "; curl -s -o /dev/null -w "HTTP %{http_code}\n" http://127.0.0.1:18001/healthz echo -n "ready : "; curl -s -o /dev/null -w "HTTP %{http_code}" http://127.0.0.1:18001/ready; echo "" echo -n "bashorat: "; curl -s -X POST http://127.0.0.1:18001/bashorat \ -H "Content-Type: application/json" -d '{ "xonalar": 2, "maydon": 55, "qavat": 3, "jami_qavat": 9, "narx_mln": 4.5, "tavsif_uzunligi": 300, "rasmlar_soni": 8, "telefon_takrorlanishi": 1, "kunlik_elonlar": 1, "egasi_tasdiqlangan": 1}' echo "" kill "$(cat servis2.pid)" 2>/dev/null; echo "(sinov serveri to'xtatildi)"
healthz : HTTP 200
ready : HTTP 503
bashorat: {"detail":"model yuklanmagan"}
(sinov serveri to'xtatildi)
/healthz 200, /ready 503. Aynan shu kerak: jarayon tirik, lekin so'rov yubormang.
HTTP kodlari
import pandas as pd show(pd.DataFrame([ {"kod": 200, "ma'nosi": "Bashorat tayyor", "kim aybdor": "-"}, {"kod": 422, "ma'nosi": "Kirish sxemaga mos emas", "kim aybdor": "Mijoz"}, {"kod": 503, "ma'nosi": "Model yuklanmagan / tayyor emas", "kim aybdor": "Server"}, {"kod": 500, "ma'nosi": "Kutilmagan xato", "kim aybdor": "Server (bug)"}, {"kod": 429, "ma'nosi": "So'rov juda ko'p", "kim aybdor": "Mijoz (yoki cheklov)"}, ]))
| kod | ma'nosi | kim aybdor |
|---|---|---|
| 200 | Bashorat tayyor | - |
| 422 | Kirish sxemaga mos emas | Mijoz |
| 503 | Model yuklanmagan / tayyor emas | Server |
| 500 | Kutilmagan xato | Server (bug) |
| 429 | So'rov juda ko'p | Mijoz (yoki cheklov) |
4xx — mijoz xatosi, 5xx — server xatosi. Bu chegarani to'g'ri qo'yish muhim: 422 ni 500
qilib qaytarsangiz, monitoring soxta signal beradi va siz yo'q muammoni qidirasiz.
Xulosa
| Tushuncha | Asosiy fikr |
|---|---|
| Validatsiya chegarada | pydantic yomon so'rovni modelga yetkazmaydi |
422 |
Qaysi maydon noto'g'ri ekani aytiladi |
/healthz va /ready |
Tirikmi va tayyormi — boshqa savollar |
| Model yo'q | Xizmat ishga tushadi, /ready 503 qaytaradi |
| ONNX | Bitta qator uchun eng tez yo'l (26-bob) |
| p95 / p99 | O'rtacha yolg'on gapiradi — persentil o'lchang |
4xx va 5xx |
Aybdor kim — mijozmi yoki serverni ajrating |
| Import qoidasi | xizmat/ oqitish/ ni import qilmaydi (3-bob) |
Talaba ishi
/bashoratga ro'yxat qabul qiladigan/bashorat-toplamqo'shing. 100 ta e'lon uchun p99 qanday o'zgaradi?- Javobga model versiyasini qo'shing (23-bob registridan). Nega bu muhim?
CHEGARAni 0.3 qilib qayta ishga tushiring. Qaysi e'lonlar endi broker deb belgilanadi?- Bir vaqtda 50 ta so'rov yuboring (
ThreadPoolExecutor). p99 qanday o'zgardi va nega?
Bu bobni bepul o'qidingiz.
Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.