aikacl commited on
Commit
d35fa36
·
verified ·
1 Parent(s): 7da1f23

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +161 -0
app.py ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ KaLM-Reranker-V1-Nano on Hugging Face ZeroGPU Space.
3
+
4
+ Key design choices for ZeroGPU:
5
+ 1. `kalm_reranker.py` is downloaded from the model repo at startup (small file, no GPU needed).
6
+ 2. The `KaLMReranker` instance itself is created lazily INSIDE the `@spaces.GPU`
7
+ function, because at module-load time ZeroGPU has not yet allocated a CUDA device.
8
+ 3. `@spaces.GPU(duration=120)` asks ZeroGPU for up to 120 s of GPU time per call —
9
+ enough for one reranking batch on a 0.27B model.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import os
15
+ import sys
16
+ from typing import List, Tuple
17
+
18
+ import gradio as gr
19
+ import spaces
20
+ from huggingface_hub import hf_hub_download
21
+
22
+ # --------------------------------------------------------------------------- #
23
+ # 1. Bring in the `kalm_reranker.py` helper from the model repo.
24
+ # This file is ~14 KB; downloading it once at startup is much cleaner than
25
+ # copy-pasting the source into the Space repo.
26
+ # --------------------------------------------------------------------------- #
27
+ MODEL_ID = "KaLM-Embedding/KaLM-Reranker-V1-Nano"
28
+
29
+ _kalm_helper_path = hf_hub_download(repo_id=MODEL_ID, filename="kalm_reranker.py")
30
+ sys.path.insert(0, os.path.dirname(_kalm_helper_path))
31
+
32
+ from kalm_reranker import KaLMReranker # noqa: E402 (path set at runtime)
33
+
34
+ # --------------------------------------------------------------------------- #
35
+ # 2. Lazy-loaded global reranker. Instantiated on first call inside @spaces.GPU.
36
+ # --------------------------------------------------------------------------- #
37
+ _reranker: KaLMReranker | None = None
38
+
39
+
40
+ def get_reranker() -> KaLMReranker:
41
+ global _reranker
42
+ if _reranker is None:
43
+ print(f"[KaLM] Loading model {MODEL_ID} on GPU ...", flush=True)
44
+ _reranker = KaLMReranker(
45
+ MODEL_ID,
46
+ device="cuda", # ZeroGPU makes CUDA visible inside @spaces.GPU
47
+ dtype="bfloat16", # BF16 matches the published checkpoint
48
+ batch_size=32,
49
+ query_max_length=512,
50
+ max_length=1024, # encoder tokens for "<Document>: {passage}"
51
+ chunk_size=4, # encoder chunk pooling (Matryoshka)
52
+ )
53
+ print("[KaLM] Model ready.", flush=True)
54
+ return _reranker
55
+
56
+
57
+ # --------------------------------------------------------------------------- #
58
+ # 3. Inference function — wrapped with @spaces.GPU so ZeroGPU allocates a GPU.
59
+ # --------------------------------------------------------------------------- #
60
+ DEFAULT_QUERY = "What is the capital of China?"
61
+ DEFAULT_DOCS = (
62
+ "The capital of China is Beijing.\n"
63
+ "Gravity attracts bodies toward one another.\n"
64
+ "Beijing is located in northern China and has a population of over 20 million."
65
+ )
66
+ DEFAULT_INSTRUCTION = "Given a query, retrieve documents that answer the query."
67
+
68
+
69
+ @spaces.GPU(duration=120)
70
+ def rerank(
71
+ query: str,
72
+ documents_text: str,
73
+ instruction: str,
74
+ ) -> Tuple[str, list]:
75
+ """Rerank documents against a query.
76
+
77
+ Args:
78
+ query: User query string.
79
+ documents_text: Newline-separated candidate documents.
80
+ instruction: Task instruction (e.g. "Given a query, retrieve ...").
81
+
82
+ Returns:
83
+ (formatted_text, raw_rankings_json)
84
+ """
85
+ query = (query or "").strip()
86
+ if not query:
87
+ return "ERROR: query is empty.", []
88
+
89
+ docs: List[str] = [d.strip() for d in (documents_text or "").splitlines() if d.strip()]
90
+ if not docs:
91
+ return "ERROR: please provide at least one document (one per line).", []
92
+
93
+ instruction = (instruction or "").strip() or None # fall back to model default
94
+
95
+ r = get_reranker()
96
+ rankings = r.rank(query, docs, instruction=instruction)
97
+
98
+ # Pretty-print
99
+ lines = [f"Top {len(rankings)} results for query: \"{query}\""]
100
+ lines.append("-" * 60)
101
+ for i, item in enumerate(rankings, start=1):
102
+ cid = item["corpus_id"]
103
+ score = item["score"]
104
+ snippet = docs[cid][:100].replace("\n", " ")
105
+ lines.append(f"#{i:>2} score={score:.6f} doc#{cid + 1} {snippet}{'...' if len(docs[cid]) > 100 else ''}")
106
+ return "\n".join(lines), [
107
+ {"rank": i, "corpus_id": item["corpus_id"], "score": float(item["score"]), "document": docs[item["corpus_id"]]}
108
+ for i, item in enumerate(rankings, start=1)
109
+ ]
110
+
111
+
112
+ # --------------------------------------------------------------------------- #
113
+ # 4. Gradio UI
114
+ # --------------------------------------------------------------------------- #
115
+ with gr.Blocks(title="KaLM-Reranker-V1-Nano", theme=gr.themes.Soft()) as demo:
116
+ gr.Markdown(
117
+ "# 🚀 KaLM-Reranker-V1-Nano · ZeroGPU Demo\n"
118
+ "Encoder-decoder reranker (0.27B activated, BF16) served on Hugging Face ZeroGPU."
119
+ )
120
+
121
+ with gr.Row():
122
+ query_in = gr.Textbox(
123
+ label="Query",
124
+ value=DEFAULT_QUERY,
125
+ scale=2,
126
+ placeholder="The user query ...",
127
+ )
128
+ instr_in = gr.Textbox(
129
+ label="Instruction",
130
+ value=DEFAULT_INSTRUCTION,
131
+ scale=3,
132
+ placeholder="e.g. Given a query, retrieve documents that answer the query.",
133
+ )
134
+
135
+ docs_in = gr.Textbox(
136
+ label="Candidate documents (one per line)",
137
+ value=DEFAULT_DOCS,
138
+ lines=8,
139
+ placeholder="Paste one document per line ...",
140
+ )
141
+
142
+ btn = gr.Button("Rerank", variant="primary")
143
+ out_text = gr.Textbox(label="Ranking", lines=10, show_copy_button=True)
144
+ out_json = gr.JSON(label="Raw scores (rank / corpus_id / score / document)")
145
+
146
+ btn.click(
147
+ fn=rerank,
148
+ inputs=[query_in, docs_in, instr_in],
149
+ outputs=[out_text, out_json],
150
+ )
151
+
152
+ gr.Markdown(
153
+ "---\n"
154
+ "ℹ️ **First call after a cold start** takes ~30–60 s (the model is downloaded and moved "
155
+ "onto a freshly-allocated ZeroGPU). Subsequent calls in the same session are fast.\n\n"
156
+ "Model: [`KaLM-Embedding/KaLM-Reranker-V1-Nano`](https://huggingface.co/KaLM-Embedding/KaLM-Reranker-V1-Nano)"
157
+ )
158
+
159
+
160
+ if __name__ == "__main__":
161
+ demo.launch()