Spaces:
Sleeping
Sleeping
File size: 6,484 Bytes
7b60154 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 | """
Model Loader - Tải model và tokenizer từ HuggingFace Hub.
Module này chịu trách nhiệm:
1. Tải model + tokenizer DUY NHẤT MỘT LẦN khi app khởi động
2. Chuyển model sang eval() mode để tối ưu inference
3. Cung cấp hàm generate_embeddings() để trích xuất vector nhúng
"""
import torch
from transformers import AutoModel, AutoTokenizer
from app.config import settings
# Biến toàn cục - lưu trữ model và tokenizer sau khi load
model = None
tokenizer = None
def load_model():
"""
Tải model và tokenizer từ HuggingFace Hub vào bộ nhớ.
Hàm này được gọi DUY NHẤT MỘT LẦN khi FastAPI app khởi động.
Model được chuyển sang eval() mode và đưa lên device phù hợp (CPU/GPU).
"""
global model, tokenizer
print(f"[INFO] Loading model: {settings.MODEL_NAME}")
print(f"[INFO] Device: {settings.DEVICE}")
# Load tokenizer
tokenizer = AutoTokenizer.from_pretrained(
settings.MODEL_NAME,
token=settings.HF_TOKEN,
)
# Load model
model = AutoModel.from_pretrained(
settings.MODEL_NAME,
token=settings.HF_TOKEN,
)
# Chuyển sang eval mode (tắt dropout, batch norm ở inference mode)
model.eval()
# Đưa model lên device (cpu hoặc cuda)
model.to(settings.DEVICE)
print("[INFO] Model loaded successfully! Ready to serve.")
def generate_embeddings(texts: list[str]) -> list[list[float]]:
"""
Trích xuất vector nhúng từ danh sách văn bản.
Pipeline xử lý:
1. Tokenize input texts
2. Feed tokens qua model (không tính gradient)
3. Mean pooling trên token embeddings (bỏ qua padding tokens)
4. L2 normalize vector kết quả
Args:
texts: Danh sách các chuỗi văn bản cần embedding.
Returns:
Danh sách các vector nhúng (mỗi vector là list[float]).
"""
# Bước 1: Tokenize
encoded = tokenizer(
texts,
padding=True,
truncation=True,
max_length=settings.MAX_LENGTH,
return_tensors="pt",
)
# Đưa input tensors lên cùng device với model
encoded = {key: val.to(settings.DEVICE) for key, val in encoded.items()}
# Bước 2: Forward pass (không tính gradient để tiết kiệm bộ nhớ)
with torch.no_grad():
outputs = model(**encoded)
# Bước 3: Mean Pooling
# outputs.last_hidden_state có shape: (batch_size, seq_length, hidden_dim)
token_embeddings = outputs.last_hidden_state
attention_mask = encoded["attention_mask"]
# Mở rộng attention_mask để khớp shape với token_embeddings
# attention_mask: (batch_size, seq_length) -> (batch_size, seq_length, hidden_dim)
mask_expanded = attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float()
# Tính trung bình có trọng số (chỉ tính các token thực, bỏ qua padding)
sum_embeddings = torch.sum(token_embeddings * mask_expanded, dim=1)
sum_mask = torch.clamp(mask_expanded.sum(dim=1), min=1e-9)
mean_embeddings = sum_embeddings / sum_mask
# Bước 4: L2 Normalize
normalized = torch.nn.functional.normalize(mean_embeddings, p=2, dim=1)
# Chuyển tensor về list Python
return normalized.cpu().tolist()
def compute_cosine_similarity(vector1: list[float], vector2: list[float]) -> float:
"""
Tinh cosine similarity giua 2 vector embedding.
Cosine similarity do goc giua 2 vector trong khong gian nhieu chieu.
Gia tri tra ve nam trong khoang [-1, 1]:
- 1.0 = hoan toan giong nhau
- 0.0 = khong lien quan
- -1.0 = hoan toan doi lap
Args:
vector1: Vector embedding thu nhat (list[float]).
vector2: Vector embedding thu hai (list[float]).
Returns:
Diem cosine similarity (float).
"""
# Chuyen list Python thanh tensor
v1 = torch.tensor(vector1, dtype=torch.float32)
v2 = torch.tensor(vector2, dtype=torch.float32)
# Tinh cosine similarity bang PyTorch
similarity = torch.nn.functional.cosine_similarity(
v1.unsqueeze(0), v2.unsqueeze(0)
)
return similarity.item()
# ==================== MINILM MODEL ====================
# Bien toan cuc MiniLM - tach biet hoan toan voi Gemma
minilm_model = None
minilm_tokenizer = None
def load_minilm():
"""
Tai MiniLM model va tokenizer tu HuggingFace Hub.
MiniLM chay tren CPU vi:
- Model chi co 22M params, CPU van cuc nhanh (~5-10ms/request)
- Tiet kiem GPU memory cho Gemma 300M (model chinh)
"""
global minilm_model, minilm_tokenizer
print(f"[INFO] Loading MiniLM: {settings.MODEL_NAME_MINILM}")
print("[INFO] MiniLM device: cpu")
# Load tokenizer
minilm_tokenizer = AutoTokenizer.from_pretrained(
settings.MODEL_NAME_MINILM,
)
# Load model
minilm_model = AutoModel.from_pretrained(
settings.MODEL_NAME_MINILM,
)
# Eval mode + CPU
minilm_model.eval()
minilm_model.to("cpu")
print("[INFO] MiniLM loaded successfully!")
def generate_embeddings_minilm(texts: list[str]) -> list[list[float]]:
"""
Trich xuat vector nhung bang MiniLM (384 chieu).
Pipeline giong voi Gemma nhung:
- Chay tren CPU (khong dung GPU)
- Vector output co 384 chieu (thay vi 768 cua Gemma)
- MAX_LENGTH ngan hon (256 thay vi 512)
Args:
texts: Danh sach van ban can embedding (search query, job title, ...).
Returns:
Danh sach vector nhung 384 chieu.
"""
# Buoc 1: Tokenize
encoded = minilm_tokenizer(
texts,
padding=True,
truncation=True,
max_length=settings.MAX_LENGTH_MINILM,
return_tensors="pt",
)
# MiniLM chay tren CPU
encoded = {key: val.to("cpu") for key, val in encoded.items()}
# Buoc 2: Forward pass
with torch.no_grad():
outputs = minilm_model(**encoded)
# Buoc 3: Mean Pooling
token_embeddings = outputs.last_hidden_state
attention_mask = encoded["attention_mask"]
mask_expanded = attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float()
sum_embeddings = torch.sum(token_embeddings * mask_expanded, dim=1)
sum_mask = torch.clamp(mask_expanded.sum(dim=1), min=1e-9)
mean_embeddings = sum_embeddings / sum_mask
# Buoc 4: L2 Normalize
normalized = torch.nn.functional.normalize(mean_embeddings, p=2, dim=1)
return normalized.cpu().tolist()
|