oki0ki commited on
Commit
c2f3a1e
·
verified ·
1 Parent(s): dcdf0ab

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +114 -0
app.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Ultralekki serwer OpenAI-compatible dla HF Spaces
5
+ Model: unsloth/granite-4.1-3b-GGUF (UD-IQ2_M)
6
+ ✅ Brak auth | ✅ Streaming | ✅ Odporny na zerwania | ✅ Optymalizacja CPU/RAM
7
+ """
8
+
9
+ import os
10
+ import sys
11
+ import signal
12
+ import asyncio
13
+ import logging
14
+ from contextlib import asynccontextmanager
15
+ from huggingface_hub import hf_hub_download
16
+ from fastapi import FastAPI, Request
17
+ from fastapi.middleware.cors import CORSMiddleware
18
+ from fastapi.responses import JSONResponse
19
+ import uvicorn
20
+
21
+ # ---------------- KONFIGURACJA ----------------
22
+ MODEL_REPO = "unsloth/granite-4.1-3b-GGUF"
23
+ MODEL_FILE = os.environ.get("MODEL_FILE", "granite-4.1-3b-UD-IQ2_M.gguf")
24
+ PORT = int(os.environ.get("PORT", 7860))
25
+ N_CTX = int(os.environ.get("N_CTX", 2048)) # Limit kontekstu dla oszczędności RAM
26
+ N_THREADS = int(os.environ.get("N_THREADS", 2)) # Dopasowane do free tier HF
27
+ N_BATCH = int(os.environ.get("N_BATCH", 512))
28
+ MAX_CONCURRENCY = int(os.environ.get("MAX_CONCURRENCY", 3))
29
+
30
+ # Wymuś CPU, wyłącz detekcję GPU i niepotrzebne overheady
31
+ os.environ.update({
32
+ "LLAMA_NO_METAL": "1",
33
+ "LLAMA_NO_CUDA": "1",
34
+ "LLAMA_NO_VULKAN": "1",
35
+ "USE_MMAP": "1",
36
+ "USE_MLOCK": "0",
37
+ "FLASH_ATTN": "0",
38
+ "VERBOSE": "0"
39
+ })
40
+
41
+ logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
42
+ logger = logging.getLogger(__name__)
43
+
44
+ # ---------------- POBIERANIE MODELU ----------------
45
+ def get_model_path() -> str:
46
+ logger.info(f"⬇️ Pobieranie/weryfikacja: {MODEL_REPO}/{MODEL_FILE}")
47
+ return hf_hub_download(repo_id=MODEL_REPO, filename=MODEL_FILE, resume_download=True)
48
+
49
+ # ---------------- LIFECYCLE & APP ----------------
50
+ @asynccontextmanager
51
+ async def lifespan(app: FastAPI):
52
+ # Startup
53
+ model_path = get_model_path()
54
+ os.environ["MODEL"] = model_path
55
+ logger.info("📦 Model gotowy. Inicjalizacja llama.cpp server...")
56
+
57
+ # Importuj dopiero po ustawieniu envów (llama_cpp czyta je przy starcie)
58
+ import llama_cpp.server.app as server_module
59
+ app.mount("/", server_module.app)
60
+
61
+ yield
62
+ # Shutdown
63
+ logger.info("🛑 Zamykanie serwera...")
64
+
65
+ app = FastAPI(title="Granite-4.1-3B-IQ2M OpenAI API", lifespan=lifespan)
66
+
67
+ # ---------------- MIDDLEWARE & RESILIENCE ----------------
68
+ app.add_middleware(
69
+ CORSMiddleware,
70
+ allow_origins=["*"],
71
+ allow_credentials=True,
72
+ allow_methods=["*"],
73
+ allow_headers=["*"],
74
+ )
75
+
76
+ @app.middleware("http")
77
+ async def connection_resilience(request: Request, call_next):
78
+ try:
79
+ response = await call_next(request)
80
+ return response
81
+ except (BrokenPipeError, ConnectionResetError, asyncio.CancelledError):
82
+ # Ciche ignorowanie zerwanych połączeń klienckich (np. zamknięcie karty, timeout sieci)
83
+ logger.debug("🔌 Połączenie klienta zerwane – ignorowanie błędu")
84
+ return JSONResponse(status_code=499, content={"error": "Client closed request"})
85
+ except Exception as e:
86
+ logger.error(f"❌ Błąd serwera: {e}")
87
+ return JSONResponse(status_code=500, content={"error": str(e)})
88
+
89
+ @app.get("/health")
90
+ async def health():
91
+ return {"status": "ok", "model": MODEL_FILE, "ctx": N_CTX, "threads": N_THREADS}
92
+
93
+ # ---------------- SIGNALS ----------------
94
+ def graceful_shutdown(signum, frame):
95
+ logger.info("📡 Otrzymano sygnał zakończenia. Zamykanie...")
96
+ sys.exit(0)
97
+
98
+ signal.signal(signal.SIGTERM, graceful_shutdown)
99
+ signal.signal(signal.SIGINT, graceful_shutdown)
100
+
101
+ # ---------------- START ----------------
102
+ if __name__ == "__main__":
103
+ logger.info(f"🚀 Start na porcie {PORT} | Context: {N_CTX} | Wątki: {N_THREADS} | Max concurrent: {MAX_CONCURRENCY}")
104
+ uvicorn.run(
105
+ app,
106
+ host="0.0.0.0",
107
+ port=PORT,
108
+ log_level="info",
109
+ timeout_keep_alive=120, # Utrzymuje połączenie przy krótkich zrywach sieci
110
+ limit_concurrency=MAX_CONCURRENCY, # Chroni przed OOM na free tier
111
+ backlog=16,
112
+ ws_ping_interval=30,
113
+ ws_ping_timeout=10
114
+ )