-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathembedding-server.py
More file actions
97 lines (71 loc) · 2.32 KB
/
Copy pathembedding-server.py
File metadata and controls
97 lines (71 loc) · 2.32 KB
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
#!/usr/bin/env python3
"""OpenAI-compatible embedding server wrapping Qwen3-Embedding-4B on GPU."""
import time
import torch
from contextlib import asynccontextmanager
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel, Field
from sentence_transformers import SentenceTransformer
MODEL_NAME = "Qwen/Qwen3-Embedding-4B"
EMBEDDING_DIM = 1024 # MRL truncation
model: SentenceTransformer | None = None
@asynccontextmanager
async def lifespan(app: FastAPI):
global model
print(f"Loading {MODEL_NAME} onto GPU...")
t0 = time.time()
model = SentenceTransformer(
MODEL_NAME,
trust_remote_code=True,
model_kwargs={"torch_dtype": torch.float16},
)
model.max_seq_length = 8192
model.truncate_dim = EMBEDDING_DIM
print(f"Model loaded in {time.time() - t0:.1f}s")
yield
del model
torch.cuda.empty_cache()
app = FastAPI(title="Kit Embeddings", lifespan=lifespan)
class EmbeddingRequest(BaseModel):
input: Union[str, list[str]]
model: str = "qwen3-embedding-4b"
encoding_format: str = "float"
class EmbeddingData(BaseModel):
object: str = "embedding"
embedding: list[float]
index: int
class Usage(BaseModel):
prompt_tokens: int
total_tokens: int
class EmbeddingResponse(BaseModel):
object: str = "list"
data: list[EmbeddingData]
model: str = "qwen3-embedding-4b"
usage: Usage
@app.post("/v1/embeddings")
async def create_embeddings(req: EmbeddingRequest) -> EmbeddingResponse:
texts = [req.input] if isinstance(req.input, str) else req.input
embeddings = model.encode(
texts,
batch_size=32,
normalize_embeddings=True,
show_progress_bar=False,
)
# Estimate token count (~4 chars per token)
total_chars = sum(len(t) for t in texts)
est_tokens = max(1, total_chars // 4)
data = [
EmbeddingData(embedding=emb.tolist(), index=i)
for i, emb in enumerate(embeddings)
]
return EmbeddingResponse(
data=data,
usage=Usage(prompt_tokens=est_tokens, total_tokens=est_tokens),
)
@app.get("/health")
async def health():
return {"status": "ok", "model": MODEL_NAME, "dim": EMBEDDING_DIM, "gpu": torch.cuda.is_available()}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8678)