tformal commited on
Commit
a8b4bc6
·
verified ·
1 Parent(s): 2493036

Upload folder using huggingface_hub

Browse files
config.json ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "SpladeModel"
4
+ ],
5
+ "attention_bias": false,
6
+ "attention_dropout": 0.0,
7
+ "auto_map": {
8
+ "AutoConfig": "modeling_splade.SpladeConfig",
9
+ "AutoModel": "modeling_splade.SpladeModel"
10
+ },
11
+ "bos_token_id": 50281,
12
+ "classifier_activation": "gelu",
13
+ "classifier_bias": false,
14
+ "classifier_dropout": 0.0,
15
+ "classifier_pooling": "mean",
16
+ "cls_token_id": 50281,
17
+ "decoder_bias": true,
18
+ "deterministic_flash_attn": false,
19
+ "doc_max_length": 512,
20
+ "document_prefix": "[D] ",
21
+ "dtype": "float32",
22
+ "embedding_dropout": 0.0,
23
+ "eos_token_id": 50282,
24
+ "global_attn_every_n_layers": 3,
25
+ "gradient_checkpointing": false,
26
+ "hidden_activation": "gelu",
27
+ "hidden_size": 768,
28
+ "initializer_cutoff_factor": 2.0,
29
+ "initializer_range": 0.02,
30
+ "intermediate_size": 1152,
31
+ "layer_norm_eps": 1e-05,
32
+ "layer_types": [
33
+ "full_attention",
34
+ "sliding_attention",
35
+ "sliding_attention",
36
+ "full_attention",
37
+ "sliding_attention",
38
+ "sliding_attention",
39
+ "full_attention",
40
+ "sliding_attention",
41
+ "sliding_attention",
42
+ "full_attention",
43
+ "sliding_attention",
44
+ "sliding_attention",
45
+ "full_attention",
46
+ "sliding_attention",
47
+ "sliding_attention",
48
+ "full_attention",
49
+ "sliding_attention",
50
+ "sliding_attention",
51
+ "full_attention",
52
+ "sliding_attention",
53
+ "sliding_attention",
54
+ "full_attention"
55
+ ],
56
+ "local_attention": 128,
57
+ "logit_shift": 15,
58
+ "max_position_embeddings": 8192,
59
+ "mlp_bias": false,
60
+ "mlp_dropout": 0.0,
61
+ "model_type": "modernbert",
62
+ "norm_bias": false,
63
+ "norm_eps": 1e-05,
64
+ "num_attention_heads": 12,
65
+ "num_hidden_layers": 22,
66
+ "pad_token_id": 50283,
67
+ "position_embedding_type": "absolute",
68
+ "position_top_k": 12,
69
+ "query_max_length": 128,
70
+ "query_prefix": "[Q] ",
71
+ "repad_logits_with_grad": false,
72
+ "rope_parameters": {
73
+ "full_attention": {
74
+ "rope_theta": 160000.0,
75
+ "rope_type": "default"
76
+ },
77
+ "sliding_attention": {
78
+ "rope_theta": 10000.0,
79
+ "rope_type": "default"
80
+ }
81
+ },
82
+ "sep_token_id": 50282,
83
+ "sparse_pred_ignore_index": -100,
84
+ "sparse_prediction": false,
85
+ "tie_word_embeddings": false,
86
+ "transformers_version": "5.3.0",
87
+ "vocab_fold": "case_space",
88
+ "vocab_size": 50370
89
+ }
config_sentence_transformers.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "model_type": "SparseEncoder",
3
+ "prompts": {"query": "[Q] ", "document": "[D] "},
4
+ "default_prompt_name": null,
5
+ "similarity_fn_name": "dot"
6
+ }
custom_st.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sentence_transformers.base.modules import InputModule
2
+ from transformers import AutoModel, AutoTokenizer
3
+ class SpladeSTModule(InputModule):
4
+ save_in_root = True
5
+ def __init__(self, model_name_or_path: str, **kwargs):
6
+ super().__init__()
7
+ self.model = AutoModel.from_pretrained(model_name_or_path, trust_remote_code=True)
8
+ self.tokenizer = AutoTokenizer.from_pretrained(model_name_or_path) # for SparseEncoder.decode
9
+ def preprocess(self, inputs, prompt=None, **kwargs):
10
+ prefix = prompt or ""
11
+ cfg = self.model.config
12
+ max_length = cfg.query_max_length if prefix == cfg.query_prefix else cfg.doc_max_length
13
+ ids, attn, pool, _ = self.model._tokenize(list(inputs), prefix, max_length)
14
+ return {"input_ids": ids, "attention_mask": attn, "pooling_mask": pool}
15
+ def forward(self, features, **kwargs):
16
+ features["sentence_embedding"] = self.model(
17
+ features["input_ids"], features["attention_mask"], features["pooling_mask"]
18
+ )
19
+ return features
20
+ def get_embedding_dimension(self):
21
+ return self.model.config.vocab_size
22
+ @classmethod
23
+ def load(cls, model_name_or_path, **kwargs):
24
+ return cls(model_name_or_path)
25
+ def save(self, output_path, **kwargs):
26
+ pass # repo is assembled by export.py, never by ST save
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8b9116e4234b03d5c5eae21d9036d4f1568205152cc019a113abb73c4bdcccbc
3
+ size 753780968
modeling_splade.py ADDED
@@ -0,0 +1,387 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """SPLADE head over a ModernBERT/LateOn MLM backbone.
2
+
3
+ Sparse vector = fold(max-pool(top_k-gate(log1p(relu(logits - shift))) * pooling_mask))
4
+ where the instruction prefix ("[Q] " / "[D] ") is attended by the backbone but
5
+ excluded from pooling. Scores are dot products. Load with:
6
+
7
+ model = AutoModel.from_pretrained(repo_id, trust_remote_code=True)
8
+ q = model.encode(["a query"], kind="query") # [N, V] float32
9
+ d = model.encode(["a document"]) # [N, V] float32
10
+ model.score(q, d) # [Nq, Nd] dot
11
+ model.encode_to_dict(["a query"], kind="query", top_k=20) # {token: weight}
12
+ model.attribute(["a query"], kind="query") # + winning input token
13
+ print(model.render(["a query"], kind="query")) # terminal bar chart
14
+ print(model.highlight(["a document"])) # text, fired words lit
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import sys
20
+
21
+ import torch
22
+ import torch.nn.functional as F
23
+ from transformers import AutoTokenizer, ModernBertConfig, ModernBertForMaskedLM
24
+
25
+
26
+ class SpladeConfig(ModernBertConfig):
27
+ def __init__(
28
+ self,
29
+ logit_shift: float = 0.0,
30
+ position_top_k: int | None = None,
31
+ vocab_fold: str | None = None,
32
+ query_prefix: str = "",
33
+ document_prefix: str = "",
34
+ query_max_length: int = 128,
35
+ doc_max_length: int = 512,
36
+ **kwargs,
37
+ ):
38
+ super().__init__(**kwargs)
39
+ self.logit_shift = logit_shift
40
+ self.position_top_k = position_top_k
41
+ self.vocab_fold = vocab_fold
42
+ self.query_prefix = query_prefix
43
+ self.document_prefix = document_prefix
44
+ self.query_max_length = query_max_length
45
+ self.doc_max_length = doc_max_length
46
+
47
+
48
+ class SpladeModel(ModernBertForMaskedLM):
49
+ config_class = SpladeConfig
50
+
51
+ def __init__(self, config: SpladeConfig):
52
+ super().__init__(config)
53
+ # [V] canonical-id map for vocab folding, computed once at export from
54
+ # the tokenizer and stored in the checkpoint (identity when unused).
55
+ self.register_buffer(
56
+ "vocab_fold_index", torch.arange(config.vocab_size), persistent=True
57
+ )
58
+ self._tokenizer = None
59
+
60
+ # -- forward path ---------------------------------------------------------
61
+
62
+ def _token_weights(
63
+ self, input_ids: torch.Tensor, attention_mask: torch.Tensor
64
+ ) -> torch.Tensor:
65
+ """[B, L] tokens -> [B, L, V] per-position activations (pre-pooling)."""
66
+ cfg = self.config
67
+ logits = super().forward(input_ids=input_ids, attention_mask=attention_mask).logits
68
+ weights = torch.log1p(F.relu(logits - cfg.logit_shift))
69
+ if cfg.position_top_k is not None and cfg.position_top_k < weights.shape[-1]:
70
+ # Keep each position's k largest dims (ties keep more than k).
71
+ cutoff = weights.topk(cfg.position_top_k, dim=-1).values[..., -1:]
72
+ weights = weights * (weights >= cutoff)
73
+ return weights
74
+
75
+ def _fold(
76
+ self, sparse: torch.Tensor, source_indices: torch.Tensor | None = None
77
+ ) -> tuple[torch.Tensor, torch.Tensor | None]:
78
+ """Reroute each fold group's mass onto its canonical vocab dim.
79
+
80
+ `source_indices` [B, V] (max-pool argmax positions) follows the winning
81
+ group member so attribution keeps pointing at a real input position.
82
+ """
83
+ index = self.vocab_fold_index.unsqueeze(0).expand_as(sparse)
84
+ folded = torch.zeros_like(sparse).scatter_reduce(
85
+ 1, index, sparse, reduce="amax", include_self=False
86
+ )
87
+ if source_indices is None:
88
+ return folded, None
89
+ winner = (sparse == folded.gather(1, index)) & (sparse > 0)
90
+ folded_sources = torch.full_like(source_indices, -1).scatter_reduce(
91
+ 1, index, torch.where(winner, source_indices, -1), reduce="amax", include_self=False
92
+ )
93
+ return folded, folded_sources.clamp_min_(0)
94
+
95
+ def forward(
96
+ self,
97
+ input_ids: torch.Tensor,
98
+ attention_mask: torch.Tensor,
99
+ pooling_mask: torch.Tensor | None = None,
100
+ **kwargs,
101
+ ) -> torch.Tensor:
102
+ """[B, L] tokens -> [B, V] sparse activations (dot-product scoring)."""
103
+ if pooling_mask is None:
104
+ pooling_mask = attention_mask
105
+ weights = self._token_weights(input_ids, attention_mask)
106
+ weights = weights * pooling_mask.unsqueeze(-1).to(weights.dtype)
107
+ sparse = weights.max(dim=1).values
108
+ if self.config.vocab_fold is not None:
109
+ sparse, _ = self._fold(sparse)
110
+ return sparse
111
+
112
+ @staticmethod
113
+ def score(queries: torch.Tensor, documents: torch.Tensor) -> torch.Tensor:
114
+ """Dot-product relevance scores: [Nq, V] x [Nd, V] -> [Nq, Nd]."""
115
+ return queries @ documents.T
116
+
117
+ # -- tokenization ---------------------------------------------------------
118
+
119
+ def _get_tokenizer(self):
120
+ if self._tokenizer is None:
121
+ self._tokenizer = AutoTokenizer.from_pretrained(self.config._name_or_path)
122
+ return self._tokenizer
123
+
124
+ def _encode_args(self, kind: str, max_length: int | None) -> tuple[str, int]:
125
+ cfg = self.config
126
+ if kind == "query":
127
+ return cfg.query_prefix, max_length or cfg.query_max_length
128
+ if kind == "document":
129
+ return cfg.document_prefix, max_length or cfg.doc_max_length
130
+ raise ValueError("`kind` must be 'query' or 'document'.")
131
+
132
+ def _tokenize(self, texts: list[str], prefix: str, max_length: int):
133
+ """-> (input_ids, attention_mask, pooling_mask, char_offsets)."""
134
+ enc = self._get_tokenizer()(
135
+ [prefix + t for t in texts],
136
+ padding=True,
137
+ truncation=True,
138
+ max_length=max_length,
139
+ return_tensors="pt",
140
+ return_offsets_mapping=True,
141
+ return_special_tokens_mask=True,
142
+ )
143
+ pooling_mask = enc["attention_mask"]
144
+ if prefix:
145
+ # Prefix tokens are attended but dropped from pooling: a token is
146
+ # prefix iff its char span starts inside the prefix string.
147
+ # Specials carry a zero-width (0, 0) span, so guard them.
148
+ in_prefix = (enc["offset_mapping"][..., 0] < len(prefix)) & ~enc[
149
+ "special_tokens_mask"
150
+ ].bool()
151
+ pooling_mask = pooling_mask.masked_fill(in_prefix, 0)
152
+ return enc["input_ids"], enc["attention_mask"], pooling_mask, enc["offset_mapping"]
153
+
154
+ def _encode_with_sources(self, texts: list[str], prefix: str, max_length: int):
155
+ """One batch through the model, keeping max-pool source positions.
156
+
157
+ -> (input_ids, pooling_mask, char_offsets, sparse [B, V], sources [B, V])
158
+ """
159
+ device = next(self.parameters()).device
160
+ ids, attn, pool, offsets = self._tokenize(texts, prefix, max_length)
161
+ ids, attn, pool = ids.to(device), attn.to(device), pool.to(device)
162
+ weights = self._token_weights(ids, attn)
163
+ weights = weights * pool.unsqueeze(-1).to(weights.dtype)
164
+ pooled = weights.max(dim=1)
165
+ sparse, sources = pooled.values, pooled.indices
166
+ if self.config.vocab_fold is not None:
167
+ sparse, sources = self._fold(sparse, sources)
168
+ return ids, pool, offsets, sparse, sources
169
+
170
+ # -- encoding APIs --------------------------------------------------------
171
+
172
+ @torch.inference_mode()
173
+ def encode(
174
+ self,
175
+ texts: list[str],
176
+ kind: str = "document",
177
+ batch_size: int = 32,
178
+ max_length: int | None = None,
179
+ ) -> torch.Tensor:
180
+ """Encode raw texts -> [N, V] float32 sparse vectors on CPU.
181
+
182
+ `kind` ("query" | "document") selects the instruction prefix and the
183
+ default max length.
184
+ """
185
+ prefix, max_length = self._encode_args(kind, max_length)
186
+ device = next(self.parameters()).device
187
+ rows = []
188
+ for start in range(0, len(texts), batch_size):
189
+ ids, attn, pool, _ = self._tokenize(
190
+ texts[start : start + batch_size], prefix, max_length
191
+ )
192
+ rows.append(
193
+ self(ids.to(device), attn.to(device), pool.to(device)).float().cpu()
194
+ )
195
+ return torch.cat(rows)
196
+
197
+ @torch.inference_mode()
198
+ def attribute(
199
+ self,
200
+ texts: list[str],
201
+ kind: str = "document",
202
+ top_k: int | None = 25,
203
+ batch_size: int = 32,
204
+ max_length: int | None = None,
205
+ round_to: int = 4,
206
+ ) -> list[list[dict]]:
207
+ """Encode texts and attribute each output dim to its input subtoken.
208
+
209
+ Per text, a weight-sorted list of entries
210
+ `{"token", "weight", "source", "position", "expansion"}`:
211
+ `source`/`position` name the input subtoken whose activation won the
212
+ max for that vocab dim (after folding); `expansion` is True when the
213
+ output term is not the source token's own (folded) dim.
214
+ """
215
+ prefix, max_length = self._encode_args(kind, max_length)
216
+ tokenizer = self._get_tokenizer()
217
+ fold_index = self.vocab_fold_index.cpu()
218
+ results = []
219
+ for start in range(0, len(texts), batch_size):
220
+ ids, _, _, sparse, sources = self._encode_with_sources(
221
+ texts[start : start + batch_size], prefix, max_length
222
+ )
223
+ for row, row_sources, row_ids in zip(
224
+ sparse.float().cpu(), sources.cpu(), ids.cpu()
225
+ ):
226
+ dims = torch.nonzero(row, as_tuple=False).flatten()
227
+ order = torch.argsort(row[dims], descending=True)[:top_k]
228
+ entries = []
229
+ for dim in dims[order].tolist():
230
+ pos = int(row_sources[dim])
231
+ src_id = int(row_ids[pos])
232
+ entries.append(
233
+ {
234
+ "token": tokenizer.convert_ids_to_tokens(dim),
235
+ "weight": round(float(row[dim]), round_to),
236
+ "source": tokenizer.convert_ids_to_tokens(src_id),
237
+ "position": pos,
238
+ "expansion": int(fold_index[src_id]) != dim,
239
+ }
240
+ )
241
+ results.append(entries)
242
+ return results
243
+
244
+ def encode_to_dict(
245
+ self, texts: list[str], kind: str = "document", top_k: int | None = None, **kwargs
246
+ ) -> list[dict[str, float]]:
247
+ """Encode texts -> {token: weight} dicts sorted by descending weight."""
248
+ return [
249
+ {e["token"]: e["weight"] for e in entries}
250
+ for entries in self.attribute(texts, kind=kind, top_k=top_k, **kwargs)
251
+ ]
252
+
253
+ # -- terminal displays ----------------------------------------------------
254
+
255
+ _FADE = "▓▒░"
256
+ _HEAT = (196, 202, 208, 214, 220, 190, 108, 66, 60, 241) # ANSI-256, hot -> cold
257
+
258
+ def _heat(self, ratio: float) -> int:
259
+ return self._HEAT[min(int((1 - ratio) * len(self._HEAT)), len(self._HEAT) - 1)]
260
+
261
+ @staticmethod
262
+ def _display(token: str) -> str:
263
+ """Strip the Ġ word marker; dot-prefix continuation pieces."""
264
+ if token.startswith("Ġ"):
265
+ return token[1:]
266
+ if token.startswith("["): # specials: [CLS], [SEP], [Q], [D]
267
+ return token
268
+ return "·" + token
269
+
270
+ def render(
271
+ self,
272
+ texts: list[str],
273
+ kind: str = "document",
274
+ top_k: int | None = 25,
275
+ width: int = 36,
276
+ color: bool | None = None,
277
+ **attribute_kwargs,
278
+ ) -> str:
279
+ """Terminal bar chart of the sparse expansions, with attributions.
280
+
281
+ One block per text: bars proportional to weight (peak-normalized), each
282
+ line ending with the input subtoken that produced the dim and `<exp>`
283
+ for pure expansions. `color=None` auto-detects a TTY.
284
+ """
285
+ if color is None:
286
+ color = sys.stdout.isatty()
287
+ blocks = []
288
+ for text, entries in zip(
289
+ texts, self.attribute(texts, kind=kind, top_k=top_k, **attribute_kwargs)
290
+ ):
291
+ shown = text if len(text) <= 70 else text[:67] + "..."
292
+ if not entries:
293
+ blocks.append(f"{kind} · {shown}\n (empty vector)")
294
+ continue
295
+ peak = entries[0]["weight"]
296
+ name_width = max(len(self._display(e["token"])) for e in entries)
297
+ lines = [f"{kind} · {shown}"]
298
+ for e in entries:
299
+ ratio = e["weight"] / peak
300
+ cells = max(1, round(ratio * width))
301
+ bar = ("█" * cells)[:-3] + self._FADE if cells > 3 else self._FADE[3 - cells :]
302
+ pad = " " * (width - cells)
303
+ token = self._display(e["token"]).rjust(name_width)
304
+ attrib = f"<- {self._display(e['source'])}@{e['position']}"
305
+ if e["expansion"]:
306
+ attrib += " <exp>"
307
+ if color:
308
+ heat = self._heat(ratio)
309
+ token = f"\033[38;5;{heat}m{token}\033[0m"
310
+ bar = f"\033[38;5;{heat}m{bar}\033[0m"
311
+ attrib = f"\033[2m{attrib}\033[0m"
312
+ lines.append(f"{token} {bar}{pad} {e['weight']:>6.2f} {attrib}")
313
+ blocks.append("\n".join(lines))
314
+ return "\n\n".join(blocks)
315
+
316
+ @torch.inference_mode()
317
+ def highlight(
318
+ self,
319
+ texts: list[str],
320
+ kind: str = "document",
321
+ color: bool | None = None,
322
+ batch_size: int = 32,
323
+ max_length: int | None = None,
324
+ ) -> str:
325
+ """Render each text with its firing words lit up.
326
+
327
+ A word fires when one of its subtokens wins the max for at least one
328
+ output dim; intensity is the largest weight it wins. TTY: reverse-video
329
+ heat colors. Plain: tiered markers `⟦strong⟧ «mid» ‹weak›`.
330
+ """
331
+ if color is None:
332
+ color = sys.stdout.isatty()
333
+ prefix, max_length = self._encode_args(kind, max_length)
334
+ blocks = []
335
+ for start in range(0, len(texts), batch_size):
336
+ batch = texts[start : start + batch_size]
337
+ ids, pool, offsets, sparse, sources = self._encode_with_sources(
338
+ batch, prefix, max_length
339
+ )
340
+ # [B, L] per-position intensity: max weight over the dims each
341
+ # position won. Zero-weight dims carry a clamped position 0 but
342
+ # contribute 0, so they can't corrupt the amax.
343
+ intensity = torch.zeros(
344
+ ids.shape, dtype=sparse.dtype, device=sparse.device
345
+ ).scatter_reduce(1, sources, sparse, reduce="amax", include_self=False)
346
+ for text, row_int, row_off, row_pool in zip(
347
+ batch, intensity.float().cpu(), offsets, pool.cpu()
348
+ ):
349
+ blocks.append(self._paint(text, row_int, row_off, row_pool, len(prefix), color))
350
+ return "\n".join(blocks)
351
+
352
+ def _paint(self, text, intensity, offsets, pooling_mask, prefix_len, color) -> str:
353
+ """Wrap fired char spans of `text` in intensity markers."""
354
+ peak = intensity.max().item()
355
+ if peak <= 0:
356
+ return text
357
+ # Byte-BPE offsets include the word's leading space: trim it, so merging
358
+ # only fuses glued subtokens of the same word (one span per word).
359
+ spans: list[list] = []
360
+ for pos in range(len(offsets)):
361
+ w = intensity[pos].item()
362
+ if w <= 0 or pooling_mask[pos] == 0:
363
+ continue
364
+ s, e = int(offsets[pos][0]) - prefix_len, int(offsets[pos][1]) - prefix_len
365
+ s = max(s, 0)
366
+ while s < e and text[s].isspace():
367
+ s += 1
368
+ if e <= s: # zero-width specials / whitespace-only
369
+ continue
370
+ if spans and s == spans[-1][1]:
371
+ spans[-1][1] = e
372
+ spans[-1][2] = max(spans[-1][2], w)
373
+ else:
374
+ spans.append([s, e, w])
375
+ out, cursor = [], 0
376
+ for s, e, w in spans:
377
+ ratio = w / peak
378
+ out.append(text[cursor:s])
379
+ if color:
380
+ # Reverse video with heat foreground = heat-colored highlighter.
381
+ out.append(f"\033[7;38;5;{self._heat(ratio)}m{text[s:e]}\033[0m")
382
+ else:
383
+ marks = "⟦⟧" if ratio > 0.66 else "«»" if ratio > 0.33 else "‹›"
384
+ out.append(f"{marks[0]}{text[s:e]}{marks[1]}")
385
+ cursor = e
386
+ out.append(text[cursor:])
387
+ return "".join(out)
modules.json ADDED
@@ -0,0 +1 @@
 
 
1
+ [{"idx": 0, "name": "0", "path": "", "type": "custom_st.SpladeSTModule"}]
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer_config.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "backend": "tokenizers",
3
+ "clean_up_tokenization_spaces": true,
4
+ "cls_token": "[CLS]",
5
+ "is_local": true,
6
+ "mask_token": "[MASK]",
7
+ "max_length": 299,
8
+ "model_input_names": [
9
+ "input_ids",
10
+ "attention_mask"
11
+ ],
12
+ "model_max_length": 299,
13
+ "pad_to_multiple_of": null,
14
+ "pad_token": "[MASK]",
15
+ "pad_token_type_id": 0,
16
+ "padding_side": "right",
17
+ "sep_token": "[SEP]",
18
+ "stride": 0,
19
+ "tokenizer_class": "TokenizersBackend",
20
+ "truncation_side": "right",
21
+ "truncation_strategy": "longest_first",
22
+ "unk_token": "[UNK]"
23
+ }