Views
No views yet

SparseEncoder(
(0): MLMTransformer({'max_seq_length': 8192, 'do_lower_case': False, 'architecture': 'ModernBertForMaskedLM'})
(1): SpladePooling({'pooling_strategy': 'max', 'activation_function': 'relu', 'word_embedding_dimension': 50000})
)Tip: While many benchmark datasets are available for evaluation, in this project we chose to use only those that contain clean positive documents for each query. Keep in mind that a benchmark dataset is just that a benchmark. For real-world applications, it is best to construct an evaluation dataset tailored to your specific domain and evaluate embedding models, such as PIXIE, in that environment to determine the most suitable one.
| Model Name | # params | Avg. NDCG | NDCG@1 | NDCG@3 | NDCG@5 | NDCG@10 |
|---|---|---|---|---|---|---|
| telepix/PIXIE-Splade-Preview | 0.1B | 0.7253 | 0.6799 | 0.7217 | 0.7416 | 0.7579 |
| BM25 | N/A | 0.4714 | 0.4194 | 0.4708 | 0.4886 | 0.5071 |
| naver/splade-v3 | 0.1B | 0.0582 | 0.0462 | 0.0566 | 0.0612 | 0.0685 |
| Model Name | # params | Avg. NDCG | NDCG@1 | NDCG@3 | NDCG@5 | NDCG@10 |
|---|---|---|---|---|---|---|
| telepix/PIXIE-Spell-Preview-1.7B | 1.7B | 0.7567 | 0.7149 | 0.7541 | 0.7696 | 0.7882 |
| telepix/PIXIE-Spell-Preview-0.6B | 0.6B | 0.7280 | 0.6804 | 0.7258 | 0.7448 | 0.7612 |
| telepix/PIXIE-Rune-Preview | 0.5B | 0.7383 | 0.6936 | 0.7356 | 0.7545 | 0.7698 |
| nlpai-lab/KURE-v1 | 0.5B | 0.7312 | 0.6826 | 0.7303 | 0.7478 | 0.7642 |
| BAAI/bge-m3 | 0.5B | 0.7126 | 0.6613 | 0.7107 | 0.7301 | 0.7483 |
| Snowflake/snowflake-arctic-embed-l-v2.0 | 0.5B | 0.7050 | 0.6570 | 0.7015 | 0.7226 | 0.7390 |
| Qwen/Qwen3-Embedding-0.6B | 0.6B | 0.6872 | 0.6423 | 0.6833 | 0.7017 | 0.7215 |
| jinaai/jina-embeddings-v3 | 0.5B | 0.6731 | 0.6224 | 0.6715 | 0.6899 | 0.7088 |
| SamilPwC-AXNode-GenAI/PwC-Embedding_expr | 0.5B | 0.6709 | 0.6221 | 0.6694 | 0.6852 | 0.7069 |
| Alibaba-NLP/gte-multilingual-base | 0.3B | 0.6679 | 0.6068 | 0.6673 | 0.6892 | 0.7084 |
| openai/text-embedding-3-large | N/A | 0.6465 | 0.5895 | 0.6467 | 0.6646 | 0.6853 |
pip install -U sentence-transformers1import torch
2import numpy as np
3from collections import defaultdict
4from typing import Dict, List, Tuple
5from transformers import AutoTokenizer
6from sentence_transformers import SparseEncoder
7
8MODEL_NAME = "telepix/PIXIE-Splade-Preview"
9DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
10
11def _to_dense_numpy(x) -> np.ndarray:
12 """
13 Safely converts a tensor returned by SparseEncoder to a dense numpy array.
14 """
15 if hasattr(x, "to_dense"):
16 return x.to_dense().float().cpu().numpy()
17 # If it's already a numpy array or a dense tensor
18 if isinstance(x, torch.Tensor):
19 return x.float().cpu().numpy()
20 return np.asarray(x)
21
22def _filter_special_ids(ids: List[int], tokenizer) -> List[int]:
23 """
24 Filters out special token IDs from a list of token IDs.
25 """
26 special = set(getattr(tokenizer, "all_special_ids", []) or [])
27 return [i for i in ids if i not in special]
28
29def build_inverted_index(
30 model: SparseEncoder,
31 tokenizer,
32 documents: List[str],
33 batch_size: int = 8,
34 min_weight: float = 0.0,
35) -> Tuple[Dict[int, List[Tuple[int, float]]], List[str]]:
36 """
37 Generates document embeddings and constructs an inverted index.
38 The index maps token_id to a list of (doc_idx, weight) tuples.
39 index[token_id] = [(doc_idx, weight), ...]
40 """
41 with torch.no_grad():
42 doc_emb = model.encode_document(documents, batch_size=batch_size)
43 doc_dense = _to_dense_numpy(doc_emb)
44
45 index: Dict[int, List[Tuple[int, float]]] = defaultdict(list)
46
47 for doc_idx, vec in enumerate(doc_dense):
48 # Extract only active tokens (those with weight above the threshold)
49 nz = np.flatnonzero(vec > min_weight)
50 # Optionally, remove special tokens
51 nz = _filter_special_ids(nz.tolist(), tokenizer)
52
53 for token_id in nz:
54 index[token_id].append((doc_idx, float(vec[token_id])))
55
56 return index
57
58# -------------------------
59# Search + Token Overlap Explanation
60# -------------------------
61def splade_token_overlap_inverted(
62 model: SparseEncoder,
63 tokenizer,
64 inverted_index: Dict[int, List[Tuple[int, float]]],
65 documents: List[str],
66 queries: List[str],
67 top_k_docs: int = 3,
68 top_k_tokens: int = 10,
69 min_weight: float = 0.0,
70):
71 """
72 Calculates SPLADE similarity using an inverted index and shows the
73 contribution (qw*dw) of the top_k_tokens 'overlapping tokens' for each top-ranked document.
74 """
75 for qi, qtext in enumerate(queries):
76 with torch.no_grad():
77 q_vec = model.encode_query(qtext)
78 q_vec = _to_dense_numpy(q_vec).ravel()
79
80 # Active query tokens
81 q_nz = np.flatnonzero(q_vec > min_weight).tolist()
82 q_nz = _filter_special_ids(q_nz, tokenizer)
83
84 scores: Dict[int, float] = defaultdict(float)
85 # Token contribution per document: token_id -> (qw, dw, qw*dw)
86 per_doc_contrib: Dict[int, Dict[int, Tuple[float, float, float]]] = defaultdict(dict)
87
88 for tid in q_nz:
89 qw = float(q_vec[tid])
90 postings = inverted_index.get(tid, [])
91 for doc_idx, dw in postings:
92 prod = qw * dw
93 scores[doc_idx] += prod
94 # Store per-token contribution (can be summed if needed)
95 per_doc_contrib[doc_idx][tid] = (qw, dw, prod)
96
97 ranked = sorted(scores.items(), key=lambda x: x[1], reverse=True)[:top_k_docs]
98
99 print("\n============================")
100 print(f"[Query {qi}] {qtext}")
101 print("============================")
102
103 if not ranked:
104 print("→ 일치 토큰이 없어 문서 스코어가 생성되지 않았습니다.")
105 continue
106
107 for rank, (doc_idx, score) in enumerate(ranked, start=1):
108 doc = documents[doc_idx]
109 print(f"\n→ Rank {rank} | Document {doc_idx}: {doc}")
110 print(f" [Similarity Score ({score:.6f})]")
111
112 contrib = per_doc_contrib[doc_idx]
113 if not contrib:
114 print("(겹치는 토큰이 없습니다.)")
115 continue
116
117 # Extract top K contributing tokens
118 top = sorted(contrib.items(), key=lambda kv: kv[1][2], reverse=True)[:top_k_tokens]
119 token_ids = [tid for tid, _ in top]
120 tokens = tokenizer.convert_ids_to_tokens(token_ids)
121
122 print(" [Top Contributing Tokens]")
123 for (tid, (qw, dw, prod)), tok in zip(top, tokens):
124 print(f" {tok:20} {prod:.6f}")
125
126if __name__ == "__main__":
127 # 1) Load model and tokenizer
128 model = SparseEncoder(MODEL_NAME).to(DEVICE)
129 tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
130
131 # 2) Example data
132 queries = [
133 "텔레픽스는 어떤 산업 분야에서 위성 데이터를 활용하나요?",
134 "국방 분야에 어떤 위성 서비스가 제공되나요?",
135 "텔레픽스의 기술 수준은 어느 정도인가요?",
136 ]
137 documents = [
138 "텔레픽스는 해양, 자원, 농업 등 다양한 분야에서 위성 데이터를 분석하여 서비스를 제공합니다.",
139 "정찰 및 감시 목적의 위성 영상을 통해 국방 관련 정밀 분석 서비스를 제공합니다.",
140 "TelePIX의 광학 탑재체 및 AI 분석 기술은 Global standard를 상회하는 수준으로 평가받고 있습니다.",
141 "텔레픽스는 우주에서 수집한 정보를 분석하여 '우주 경제(Space Economy)'라는 새로운 가치를 창출하고 있습니다.",
142 "텔레픽스는 위성 영상 획득부터 분석, 서비스 제공까지 전 주기를 아우르는 솔루션을 제공합니다.",
143 ]
144
145 # 3) Build document index (inverted index)
146 inverted_index = build_inverted_index(
147 model=model,
148 tokenizer=tokenizer,
149 documents=documents,
150 batch_size=8,
151 min_weight=0.0, # Adjust to 1e-6 ~ 1e-4 to filter out very small noise
152 )
153
154 # 4) Search and explain token overlap
155 splade_token_overlap_inverted(
156 model=model,
157 tokenizer=tokenizer,
158 inverted_index=inverted_index,
159 documents=documents,
160 queries=queries,
161 top_k_docs=2, # Print only the top 3 documents
162 top_k_tokens=5, # Top 10 contributing tokens for each document
163 min_weight=0.0,
164 )@software{TelePIX-PIXIE-Splade-Preview,
title={PIXIE-Splade-Preview},
author={TelePIX AI Research Team and Bongmin Kim},
year={2025},
url={https://huggingface.co/telepix/PIXIE-Splade-Preview}
}