Yuki131's picture
Update app.py
f627c0a verified
Raw
History Blame Contribute Delete
13.7 kB
"""KaLM-Reranker-V1-Small interactive demo for Hugging Face Spaces."""
from __future__ import annotations
import importlib.util
import math
from functools import lru_cache
from typing import Any
import gradio as gr
import spaces
import torch
from huggingface_hub import hf_hub_download
MODEL_ID = "KaLM-Embedding/KaLM-Reranker-V1-Small"
DEFAULT_INSTRUCTION = "Given a query, retrieve documents that answer the query."
COMPRESSION_FACTORS = (1, 2, 4, 8, 16, 32)
MAX_DOCUMENTS = 50
def load_reranker_class() -> type:
"""Fetch the official lightweight inference wrapper from the model repository."""
module_path = hf_hub_download(repo_id=MODEL_ID, filename="kalm_reranker.py")
spec = importlib.util.spec_from_file_location("kalm_reranker", module_path)
if spec is None or spec.loader is None:
raise RuntimeError("Unable to load the official KaLM reranker wrapper.")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module.KaLMReranker
@lru_cache(maxsize=1)
def get_reranker() -> Any:
"""Load the 1B Small reranker once for this Space process."""
reranker_class = load_reranker_class()
device = "cuda" if torch.cuda.is_available() else "cpu"
dtype = "bfloat16" if device == "cuda" else "float32"
return reranker_class(
MODEL_ID,
device=device,
dtype=dtype,
batch_size=4,
query_max_length=512,
max_length=1024,
chunk_size=4,
)
def normalize_factor(value: int | float | str) -> int:
try:
factor = int(value)
except (TypeError, ValueError) as error:
raise gr.Error("Choose a valid compression factor.") from error
if factor not in COMPRESSION_FACTORS:
raise gr.Error("Compression must be one of 1, 2, 4, 8, 16, or 32.")
return factor
def configure_reranker(compression: int | float | str) -> tuple[Any, int]:
factor = normalize_factor(compression)
reranker = get_reranker()
# The official chunk_size pools every N encoder token states. Larger N means
# fewer passage representations passed to the decoder, i.e. more compression.
reranker.chunk_size = factor
return reranker, factor
def parse_documents(raw_documents: str) -> list[str]:
documents = [line.strip() for line in (raw_documents or "").splitlines() if line.strip()]
if not documents:
raise gr.Error("Please enter at least one document.")
if len(documents) > MAX_DOCUMENTS:
raise gr.Error(f"Please limit each run to {MAX_DOCUMENTS} documents.")
return documents
def validate_query(query: str) -> str:
query = (query or "").strip()
if not query:
raise gr.Error("Please enter a query.")
return query
def estimated_tokens(reranker: Any, document: str) -> int:
return len(
reranker.tokenizer(
f"<Document>: {document}",
add_special_tokens=False,
truncation=True,
max_length=reranker.max_length,
)["input_ids"]
)
@spaces.GPU(duration=90)
def rerank_documents(
query: str,
raw_documents: str,
instruction: str,
compression: int,
top_k: int,
) -> tuple[list[list[Any]], dict[str, Any]]:
query = validate_query(query)
documents = parse_documents(raw_documents)
instruction = (instruction or DEFAULT_INSTRUCTION).strip() or DEFAULT_INSTRUCTION
reranker, factor = configure_reranker(compression)
rankings = reranker.rank(query, documents, instruction=instruction, top_k=int(top_k))
rows = [
[
rank,
f"{float(item['score']):.4f}",
documents[int(item["corpus_id"])],
]
for rank, item in enumerate(rankings, start=1)
]
raw_token_count = sum(estimated_tokens(reranker, document) for document in documents)
return rows, {
"model": MODEL_ID,
"score": "P(yes): probability that the document satisfies the query and instruction",
"documents_reranked": len(documents),
"compression_factor": f"{factor}×",
"estimated_encoder_tokens": raw_token_count,
"estimated_tokens_after_pooling": math.ceil(raw_token_count / factor),
"device": "CUDA" if torch.cuda.is_available() else "CPU",
}
@spaces.GPU(duration=90)
def score_pair(
query: str,
document: str,
instruction: str,
compression: int,
) -> tuple[str, dict[str, Any]]:
query = validate_query(query)
document = (document or "").strip()
if not document:
raise gr.Error("Please enter a document.")
instruction = (instruction or DEFAULT_INSTRUCTION).strip() or DEFAULT_INSTRUCTION
reranker, factor = configure_reranker(compression)
score = float(reranker.predict([(query, document)], instruction=instruction)[0])
token_count = estimated_tokens(reranker, document)
return f"## Relevance score: **{score:.4f}**\n\nThis is the model's probability that the answer is **yes**.", {
"model": MODEL_ID,
"compression_factor": f"{factor}×",
"encoder_tokens": token_count,
"estimated_tokens_after_pooling": math.ceil(token_count / factor),
"instruction": instruction,
}
@spaces.GPU(duration=90)
def compare_compression(
query: str,
document: str,
instruction: str,
factors: list[int] | None,
) -> tuple[list[list[Any]], dict[str, Any]]:
query = validate_query(query)
document = (document or "").strip()
if not document:
raise gr.Error("Please enter a document.")
if not factors:
raise gr.Error("Choose at least one compression factor.")
instruction = (instruction or DEFAULT_INSTRUCTION).strip() or DEFAULT_INSTRUCTION
reranker = get_reranker()
token_count = estimated_tokens(reranker, document)
selected_factors = sorted({normalize_factor(factor) for factor in factors})
rows: list[list[Any]] = []
for factor in selected_factors:
reranker.chunk_size = factor
score = float(reranker.predict([(query, document)], instruction=instruction)[0])
rows.append(
[
f"{factor}×",
token_count,
math.ceil(token_count / factor),
f"{score:.4f}",
]
)
return rows, {
"how_to_read": "Larger factors pool more encoder token states into each chunk. This reduces the document representation length passed to the decoder.",
"model": MODEL_ID,
"instruction": instruction,
}
EXAMPLE_QUERY = "What is the capital of China?"
EXAMPLE_DOCUMENTS = """The capital of China is Beijing.
Paris is the capital of France.
Gravity attracts bodies with mass toward one another.
中国的首都是北京。"""
CSS = """
.gradio-container { max-width: 1120px !important; }
#hero { text-align: center; margin: 0.5rem 0 1.5rem; }
#hero h1 { margin-bottom: 0.35rem; }
.notice { border-left: 4px solid #4f46e5; padding: 0.65rem 0.9rem; background: #eef2ff; border-radius: 0.4rem; }
"""
with gr.Blocks(theme=gr.themes.Soft(), css=CSS, title="KaLM Reranker Demo") as demo:
gr.Markdown(
"""
<div id="hero">
<h1>KaLM Reranker — Compressed Document Reranking</h1>
<p>Rerank documents efficiently with <code>KaLM-Embedding/KaLM-Reranker-V1-Small</code>.</p>
</div>
"""
)
gr.HTML(
"<div class='notice'><strong>Compression factor</strong> controls the official encoder chunk pooling parameter. 1× preserves all encoder states; larger factors reduce decoder-side passage length and can improve throughput.</div>"
)
with gr.Tabs():
with gr.Tab("Semantic Reranking", id="rerank"):
with gr.Row():
query_input = gr.Textbox(label="Query / 查询", lines=2, scale=2)
top_k = gr.Slider(1, 10, value=3, step=1, label="Results to show", scale=1)
documents_input = gr.Textbox(
label="Documents / 文档",
placeholder="One document per line. The model will rank them by relevance.",
lines=10,
)
with gr.Accordion("Task and compression settings", open=True):
rerank_instruction = gr.Textbox(
label="Task instruction",
value=DEFAULT_INSTRUCTION,
lines=2,
)
rerank_compression = gr.Dropdown(
choices=list(COMPRESSION_FACTORS),
value=4,
label="Compression factor (encoder chunk pooling)",
info="1× = no sequence-length reduction; 32× = strongest pooling.",
)
rerank_button = gr.Button("Rerank documents", variant="primary")
rerank_results = gr.Dataframe(
headers=["Rank", "P(yes)", "Document"],
datatype=["number", "str", "str"],
interactive=False,
wrap=True,
label="Reranked results",
)
rerank_metadata = gr.JSON(label="Run details")
rerank_button.click(
rerank_documents,
inputs=[query_input, documents_input, rerank_instruction, rerank_compression, top_k],
outputs=[rerank_results, rerank_metadata],
)
gr.Examples(
examples=[[EXAMPLE_QUERY, EXAMPLE_DOCUMENTS, DEFAULT_INSTRUCTION, 4, 3]],
inputs=[query_input, documents_input, rerank_instruction, rerank_compression, top_k],
label="Try an example",
)
with gr.Tab("Pair Relevance", id="pair-score"):
with gr.Row():
pair_query = gr.Textbox(label="Query", lines=5, value=EXAMPLE_QUERY)
pair_document = gr.Textbox(label="Document", lines=5, value="The capital of China is Beijing.")
pair_instruction = gr.Textbox(label="Task instruction", value=DEFAULT_INSTRUCTION, lines=2)
pair_compression = gr.Dropdown(
choices=list(COMPRESSION_FACTORS),
value=4,
label="Compression factor",
)
pair_button = gr.Button("Score relevance", variant="primary")
pair_score = gr.Markdown()
pair_metadata = gr.JSON(label="Run details")
pair_button.click(
score_pair,
inputs=[pair_query, pair_document, pair_instruction, pair_compression],
outputs=[pair_score, pair_metadata],
)
with gr.Tab("Compression Explorer", id="compression"):
gr.Markdown(
"Compare the same query-document pair across pooling factors. Larger factors shorten the encoded document representation before cross-attention."
)
compression_query = gr.Textbox(label="Query", lines=3, value=EXAMPLE_QUERY)
compression_document = gr.Textbox(
label="Document",
lines=5,
value=(
"Beijing, also known as Peking, is the capital of China and one of the most populous "
"cities in the world. It is the country’s political, educational, and cultural center, "
"housing the headquarters of most of China’s largest state-owned companies. It is a "
"significant hub for the national highway, expressway, railway, and high-speed rail networks."
),
)
compression_instruction = gr.Textbox(label="Task instruction", value=DEFAULT_INSTRUCTION, lines=2)
comparison_factors = gr.CheckboxGroup(
choices=list(COMPRESSION_FACTORS),
value=[1, 4, 16, 32],
label="Compression factors to compare",
)
comparison_button = gr.Button("Compare compression", variant="primary")
comparison_results = gr.Dataframe(
headers=["Compression", "Encoder tokens", "After pooling", "P(yes)"],
datatype=["str", "number", "number", "str"],
interactive=False,
label="Compression comparison",
)
comparison_metadata = gr.JSON(label="How compression works")
comparison_button.click(
compare_compression,
inputs=[compression_query, compression_document, compression_instruction, comparison_factors],
outputs=[comparison_results, comparison_metadata],
)
gr.Markdown(
"""
---
**About KaLM-Reranker-V1** — A fast but not late-interaction reranker that pre-encodes passages and uses cross-attention to model fine-grained query-document relevance.
[Model card](https://huggingface.co/KaLM-Embedding/KaLM-Reranker-V1-Small) ·
[Homepage](https://kalm-embedding.github.io/)
"""
)
with gr.Accordion("Citation", open=True):
gr.Markdown(
"""
```bibtex
@misc{zhao2026kalmrerankerv1,
title={KaLM-Reranker-V1: Fast but Not Late Interaction for Compressed Document Reranking},
author={Xinping Zhao and Jiaxin Xu and Ziqi Dai and Xin Zhang and Shouzheng Huang and Danyu Tang and Xinshuo Hu and Meishan Zhang and Baotian Hu and Min Zhang},
year={2026},
eprint={2606.22807},
archivePrefix={arXiv},
primaryClass={cs.CL},
url={https://arxiv.org/abs/2606.22807},
}
```
"""
)
if __name__ == "__main__":
demo.queue(default_concurrency_limit=1, max_size=20).launch()