nemabruh404 commited on
Commit
3c22ffe
·
verified ·
1 Parent(s): 2aff6e0

Create model_loader.py

Browse files
Files changed (1) hide show
  1. core/model_loader.py +126 -0
core/model_loader.py ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import threading
2
+ import time
3
+ import tempfile
4
+ import wave
5
+ import os
6
+ import numpy as np
7
+ import librosa
8
+ from openwakeword.model import Model
9
+
10
+ from config import (
11
+ MODEL_PATH, SAMPLE_RATE, CHUNK_SIZE,
12
+ WARMUP_ITERS, STABLE_COUNT, STABLE_MS, MODEL_TIMEOUT_S
13
+ )
14
+
15
+ # --- Singleton model + trạng thái ---
16
+ _model: Model | None = None
17
+ _model_ready = threading.Event()
18
+ _model_error: str | None = None
19
+
20
+
21
+ def get_model() -> Model | None:
22
+ return _model
23
+
24
+
25
+ def is_ready() -> bool:
26
+ return _model_ready.is_set() and _model is not None
27
+
28
+
29
+ def wait_until_ready(timeout: float = MODEL_TIMEOUT_S) -> bool:
30
+ """Block cho đến khi model sẵn sàng. Trả về False nếu timeout hoặc lỗi."""
31
+ return _model_ready.wait(timeout=timeout) and _model is not None
32
+
33
+
34
+ def get_error() -> str | None:
35
+ return _model_error
36
+
37
+
38
+ # -------------------------------------------------------
39
+ # BƯỚC 1 — Warm-up librosa (lần đầu librosa.load rất chậm
40
+ # do lazy import bên trong, phải kích hoạt trước)
41
+ # -------------------------------------------------------
42
+ def _warmup_librosa():
43
+ t = time.perf_counter()
44
+
45
+ dummy = np.zeros(SAMPLE_RATE, dtype=np.float32)
46
+ with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as f:
47
+ tmp_path = f.name
48
+ with wave.open(tmp_path, "w") as wf:
49
+ wf.setnchannels(1)
50
+ wf.setsampwidth(2)
51
+ wf.setframerate(SAMPLE_RATE)
52
+ wf.writeframes((dummy * 32767).astype(np.int16).tobytes())
53
+
54
+ librosa.load(tmp_path, sr=SAMPLE_RATE)
55
+ os.unlink(tmp_path)
56
+
57
+ print(f" [loader] librosa warm-up: {(time.perf_counter()-t)*1000:.0f}ms")
58
+
59
+
60
+ # -------------------------------------------------------
61
+ # BƯỚC 2 — Load ONNX model
62
+ # -------------------------------------------------------
63
+ def _load_oww_model() -> Model:
64
+ t = time.perf_counter()
65
+ m = Model(wakeword_model_paths=[MODEL_PATH])
66
+ print(f" [loader] ONNX load: {(time.perf_counter()-t)*1000:.0f}ms")
67
+ return m
68
+
69
+
70
+ # -------------------------------------------------------
71
+ # BƯỚC 3 — JIT warm-up: chạy predict nhiều lần cho đến khi
72
+ # ONNX runtime compile xong và latency ổn định
73
+ # (tránh lần predict đầu của user bị chậm bất thường)
74
+ # -------------------------------------------------------
75
+ def _warmup_jit(model: Model):
76
+ stable_count = 0
77
+ silence = np.zeros(CHUNK_SIZE, dtype=np.int16)
78
+
79
+ for i in range(WARMUP_ITERS):
80
+ t_iter = time.perf_counter()
81
+ model.predict(silence)
82
+ elapsed_ms = (time.perf_counter() - t_iter) * 1000
83
+
84
+ if i < 3:
85
+ # Vài iter đầu luôn bất thường, bỏ qua
86
+ continue
87
+
88
+ if elapsed_ms < STABLE_MS:
89
+ stable_count += 1
90
+ if stable_count >= STABLE_COUNT:
91
+ print(f" [loader] JIT stable sau {i+1} iters ({elapsed_ms:.1f}ms/iter)")
92
+ return
93
+ else:
94
+ stable_count = 0
95
+
96
+ print(f" [loader] JIT warm-up xong {WARMUP_ITERS} iters (chưa stable hoàn toàn)")
97
+
98
+
99
+ # -------------------------------------------------------
100
+ # MAIN — chạy trong background thread ngay khi app start
101
+ # -------------------------------------------------------
102
+ def _boot():
103
+ global _model, _model_error
104
+
105
+ t_total = time.perf_counter()
106
+ print("[loader] Bắt đầu khởi động model...")
107
+
108
+ try:
109
+ _warmup_librosa()
110
+ model = _load_oww_model()
111
+ _warmup_jit(model)
112
+ _model = model
113
+ print(f"[loader] ✅ Sẵn sàng — tổng boot: {time.perf_counter()-t_total:.1f}s")
114
+ except Exception as e:
115
+ _model_error = str(e)
116
+ print(f"[loader] ❌ Lỗi: {e}")
117
+ finally:
118
+ # Dù thành công hay lỗi đều set event
119
+ # để wait_until_ready() không bị block mãi
120
+ _model_ready.set()
121
+
122
+
123
+ def start_loading():
124
+ """Gọi hàm này 1 lần duy nhất trong app.py khi khởi động."""
125
+ thread = threading.Thread(target=_boot, daemon=True)
126
+ thread.start()