Naphula commited on
Commit
6a2122d
·
verified ·
1 Parent(s): 0caf794

Upload 5 files

Browse files
Mergekit-Robustness-Patch-embed_v2.md ADDED
@@ -0,0 +1,355 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Mergekit Robustness Patch: `embed.py` (v2)
2
+
3
+ Attached is one for Mistral Nemo 12B (v2d), and another for Mistral Small 24B (v2a)
4
+
5
+ ## Overview
6
+ This patch provides a high-resilience version of Mergekit’s `tokenizer/embed.py`. It is specifically designed to handle "dirty" model merges where a donor model’s `tokenizer.json` and its physical `model.safetensors` weights are out of sync—a common issue when merging models that have different vocabulary sizes (e.g., mixing Mistral Tekken with ChatML or Llama 3).
7
+
8
+ ## The Problem: "Ghost Tokens"
9
+ In the standard Mergekit (`embed.py`), the engine assumes that if a token exists in a model's vocabulary, a corresponding row **must** exist in its embedding weights.
10
+
11
+ However, in many community-made merges:
12
+ 1. A model might have 131,081 tokens in its tokenizer.
13
+ 2. But its weight matrix (`embed_tokens`) only contains 131,072 rows.
14
+ 3. **Standard Result:** Mergekit attempts to read index 131,073, hits a boundary error, and the entire merge crashes with: `IndexError: index X is out of bounds for dimension 0 with size Y`.
15
+
16
+ ## The Solution: v2d Robustness & Audit
17
+ The `v2d` patch introduces **Bounds-Aware Permutation**. Instead of blindly trusting the tokenizer, it verifies the physical existence of every token row before attempting to merge it.
18
+
19
+ ### Key Features:
20
+ * **Crash Prevention:** Automatically detects if a donor model is "too small" for the requested token index. Instead of crashing, it gracefully skips that donor for that specific token.
21
+ * **Live Vocab Audit:** Prints detailed warnings to the console identifying exactly which model is missing which token. This allows you to identify "buggy" donors in your config without trial-and-error.
22
+ * **Intelligent Fallback:**
23
+ * If a token is missing from one donor but present in others, it averages the token using only the valid donors.
24
+ * If a token is missing from its primary "default" source, it falls back to a zero-vector rather than terminating the merge.
25
+ * **Result Mapping Safety:** Ensures that the final output tensors for every donor are correctly aligned, even if the donor was physically smaller than the target union vocabulary.
26
+
27
+ ---
28
+
29
+ ## Comparison: v1 vs. v2d
30
+
31
+ | Feature | `embed.py` (Default) | `embed_v2d.py` (Ours) |
32
+ | :--- | :--- | :--- |
33
+ | **Mismatched Vocab** | **Crashes** with `IndexError`. | **Succeeds** via graceful skipping. |
34
+ | **Error Reporting** | Generic Python Traceback. | Detailed `[VOCAB AUDIT]` log with Model Path & Token Name. |
35
+ | **Special Token Support** | Requires perfectly synced weights. | Handles "Ghost Tokens" (tokens in JSON but not in Tensors). |
36
+ | **Mathematical Integrity** | N/A (Process stops). | Maintains correct averaging by adjusting the donor count dynamically. |
37
+ | **Use Case** | Clean, base-model merges. | Complex merges of merges, cross-architecture vocab unions. |
38
+
39
+ ---
40
+
41
+ ## How to Use
42
+ Replace your existing `mergekit/tokenizer/embed.py` with the `embed_v2d.py` code.
43
+
44
+ ### Example Audit Log
45
+ When running a merge with mismatched models, you will now see helpful diagnostic output instead of a crash:
46
+ ```text
47
+ [VOCAB AUDIT] Model 'B:\12B\SLERP15' is missing token '<|im_start|>' (ID: 131073).
48
+ Donor size: 131072, Requested Index: 131073. Skipping.
49
+
50
+ [VOCAB AUDIT] Default source model 'B:\12B\SLERP13' is missing token '<SPECIAL_4>'
51
+ from its physical tensor. Falling back to zero.
52
+ ```
53
+
54
+ ## Why this matters for 12B/24B Merges
55
+ When merging models like **Mistral-Nemo (12B)** or **Mistral-Small (24B)**, different fine-tunes often add different special tokens (ChatML, Tool-use, etc.). If you use `tokenizer_source: union`, Mergekit tries to create a "Super-Vocab."
56
+
57
+ Standard Mergekit is too fragile for this process if even one model in your list has a slightly truncated embedding matrix. **v2d** makes the merging process "production-grade" by allowing the merge to complete regardless of minor inconsistencies in the donor models.
58
+
59
+ This patch is **safe and beneficial** for any model architecture (12B, 24B, 70B, etc.) using `tokenizer_source: union`.
60
+
61
+ Here is the breakdown of how it affects other scenarios like 24B Mistral (Tekken):
62
+
63
+ ### 1. It prevents "Ghost Token" crashes
64
+ In many Mistral-based merges (especially Tekken), developers sometimes add special tokens to the `tokenizer.json` but forget to resize the embedding layer in the `model.safetensors`.
65
+ * **Without this patch:** Mergekit sees the token in the config, calculates a high index for it, tries to read it from the tensor, and **crashes**.
66
+ * **With this patch:** Mergekit sees the mismatch, logs a warning, and uses a zero-vector or an average from other models instead. The merge finishes successfully.
67
+
68
+ ### 2. Handling "Tekken" Vocab Discrepancies
69
+ Mistral Tekken usually has a vocab size of `32768` or `131072`. If you merge a model with `131072` and one that was accidentally truncated to `131070`:
70
+ * The patch ensures that for those last 2 tokens, the "truncated" model simply doesn't contribute to the average.
71
+ * The resulting model will have the full `131072` vocab, and those 2 tokens will be populated by the weights from the model that actually had them.
72
+
73
+ ### 3. No Negative Impact on "Clean" Models
74
+ If you merge two models where the `vocab_size` in `config.json` perfectly matches the number of rows in `model.safetensors`, **this code does nothing.** The `if` condition (`p[token_id] >= tensors[model].shape[0]`) will always be false, and the code will run at full speed with no warnings.
75
+
76
+ ### 4. Why this is better than the "Padding" patch
77
+ The previous attempt to pad tensors in `generalized_task_arithmetic.py` was specific to one merge method. This `embed.py` patch works at the **tokenizer level**.
78
+ Whether you are doing a `linear`, `slerp`, `ties`, or `della` merge, this patch ensures that the "Input Tensors" are standardized correctly before the math even starts.
79
+
80
+ ### Summary of behavior for 24B/Tekken:
81
+ | Scenario | Result with Patch |
82
+ | :--- | :--- |
83
+ | **Vocabs Match Exactly** | Normal merge, no warnings. |
84
+ | **One model has extra Tekken tokens** | Merge completes; missing tokens are averaged from models that have them. |
85
+ | **Tokenizer says 131072, but Tensor is 131070** | **Merge completes instead of crashing.** |
86
+ | **Mixing Tekken and Llama3 Vocab** | Merge completes; shared tokens are averaged, unique tokens are preserved from their respective sources. |
87
+
88
+ **Conclusion:** This is a "Robustness Patch." It makes Mergekit more resilient to poorly-configured donor models (where the tokenizer and the weights are out of sync), which is very common in the community-made merges you are working with.
89
+
90
+ ---
91
+
92
+ ## Addendum
93
+ This is a perfect synergy of two diagnostic tools. Here is why the **v2d Robustness Patch** and the **DELLA Audit Chart** work so well together:
94
+
95
+ ### 1. Complete "Chain of Custody" for Weights
96
+ The **v2d Patch** handles the "Input" phase, while the **DELLA Audit** handles the "Processing" phase.
97
+ * **v2d** ensures that every model provides a valid tensor to the merge engine, even if it has to skip missing tokens or provide a zero-vector fallback.
98
+ * **DELLA Audit** then takes those tensors and shows you the "Share of Voice" for each model.
99
+ * **The Synergy:** If you see a model in the Audit chart with a **0.0% impact** or an unusually low **Norm (N)**, you can look up at the **v2d Audit log** to see if that model was missing critical tokens. It allows you to see exactly how "damaged" a donor is before it hits the final weights.
100
+
101
+ ### 2. Identifying "Poisoned" Donors
102
+ In your screenshot, look at **SLERP1**. It has a massive **16.7% impact** with a **Norm of 12.02**, while others like **SLERP3** are at **1.0%**.
103
+ * Because the **v2d Patch** prevented the crash, you can now actually see these statistics.
104
+ * If a model was missing tokens (as seen in your log for SLERP11, 15, 13, etc.), the Audit chart helps you decide if that model is still "contributing" enough to keep in the config, or if the vocab mismatches have made its task vector too noisy.
105
+
106
+ ### 3. Mathematical Safety for DELLA
107
+ DELLA is sensitive to the magnitude of changes (the `epsilon` and `density` parameters).
108
+ * By using the **v2d Patch**, you ensure that the "Base" and "Donor" tensors passed to DELLA are always the same shape.
109
+ * Without this, DELLA would be trying to calculate magnitude-based pruning on mismatched arrays, which would lead to corrupted logic even if it didn't crash. v2d "sanitizes" the data so DELLA's math remains pure.
110
+
111
+ ### 4. Real-Time Debugging of "Ghost" Contributions
112
+ Your log shows **SLERP15** is missing almost all the special tokens (`<|im_start|>`, `[SYSTEM_PROMPT]`, `[PAD]`).
113
+ * Standard Mergekit would have died instantly.
114
+ * Now, the merge continues, and the **DELLA Audit** shows SLERP15 is still contributing **2.4%** to the overall model.
115
+ * This tells you: "SLERP15 is broken for ChatML/Special tokens, but its weights for normal language (the other 131,000 tokens) are still being merged correctly."
116
+
117
+ ### Summary
118
+ > "When paired with the **DELLA Audit logic**, the **v2d patch** provides a full-stack diagnostic suite. It allows the user to see which models are physically incompatible at the vocabulary level (via the Audit Log) and then immediately see how those incompatibilities affect the final weight distribution (via the Impact Chart). This combination turns a 'black box' crash into a transparent, manageable merging workflow."
119
+
120
+ ![embed_v2d](https://cdn-uploads.huggingface.co/production/uploads/68e840caa318194c44ec2a04/2kvn9_bOFoybh17-QCxC0.png)
121
+
122
+ ![embed_v2d_layer10](https://cdn-uploads.huggingface.co/production/uploads/68e840caa318194c44ec2a04/aRnwjZoUdKq3DeFOO1YmB.png)
123
+
124
+ ## Audit Analysis
125
+ This is a fascinating look at the "DNA" of your model. Now that the **v2d patch** has stabilized the merge, this audit chart reveals the true internal dynamics of a DELLA merge that were previously invisible.
126
+
127
+ Here is a breakdown of what this specific chart is telling you about your "knowledge distribution":
128
+
129
+ ### 1. The "Anchor" Models
130
+ Look at **pdq (13.5%)** and **SLERP1 (9.9%)**.
131
+ * Even though every model has a weight of `0.10`, these two are dominating the "Share of Voice."
132
+ * **Why?** Their **Norm (N)** values are the highest (4.48 and 3.30). This means these models have the most significant "Task Vectors"—they have moved the furthest away from the Mistral-Nemo base. In a DELLA merge, these are the models providing the most "new" information or behavioral changes to the final result.
133
+
134
+ ### 2. The "Subtle" Contributors
135
+ Models like **SLERP9 (0.7%)** and **SLERP8 (0.9%)** are barely touching the weights.
136
+ * Their Norms are tiny (0.22 and 0.31).
137
+ * **Insight:** These models are very similar to your base model (`Mistral-Nemo-Instruct-2407`). They aren't "bad," but they are essentially acting as votes for the status quo. If you wanted to "clean up" your config, these are the ones you could remove with almost zero impact on the final output.
138
+
139
+ ### 3. The "Middle Class"
140
+ Models like **SLERP7 (8.8%)** and **SLERP3 (6.6%)** represent the healthy average. They are providing a solid amount of unique knowledge without overwhelming the others.
141
+
142
+ ### 4. Why the v2d Patch makes this chart "Truthful"
143
+ Without the **v2d patch**, if a model like **SLERP15** was missing tokens, the merge would have crashed. Now, you can see **SLERP15** is contributing **6.3%** (Norm 2.11).
144
+ * Because of the patch, you know that this 6.3% is based on the *valid* parts of SLERP15.
145
+ * The audit chart is now a "Health Report": if you saw a model with a high Norm but a 0% impact, you'd know the vocab mismatch was so bad it wiped out the model's contribution. Here, we see that despite the warnings, the models are still successfully injecting their "knowledge" into the merge.
146
+
147
+ ### 5. The "pdq" Factor
148
+ The model **pdq** is currently your strongest influencer in this layer (`mlp.gate_proj`). It is contributing nearly **20x more** than SLERP9. If the final model behaves more like `pdq` than anything else, this chart explains exactly why.
149
+
150
+ **This is the "X-Ray" of model merging.** You aren't just guessing if the merge worked; you can see exactly which donor's "brain" is being used for this specific layer.
151
+
152
+ `embed_v2d.py`
153
+ ```py
154
+ # Copyright (C) 2025 Arcee AI
155
+ # SPDX-License-Identifier: LGPL-3.0-only
156
+
157
+ import logging
158
+ from typing import Dict, Optional
159
+
160
+ import torch
161
+
162
+ from mergekit.common import ImmutableMap, ModelReference
163
+ from mergekit.graph import Task
164
+ from mergekit.io.tasks import GatherTensors
165
+ from mergekit.tokenizer.build import BuildTokenizer, TokenizerInfo
166
+ from mergekit.tokenizer.config import (
167
+ ModelTokenEmbedding,
168
+ TokenEmbeddingConfig,
169
+ ZeroEmbedding,
170
+ )
171
+
172
+
173
+ class PermutedEmbeddings(Task[Dict[ModelReference, torch.Tensor]]):
174
+ gather_tensors: GatherTensors
175
+ tokenizer_task: BuildTokenizer
176
+ tokens: Optional[ImmutableMap[str, TokenEmbeddingConfig]]
177
+ pad_to_multiple_of: Optional[int]
178
+ base_model: Optional[ModelReference]
179
+
180
+ def arguments(self) -> Dict[str, Task]:
181
+ return {"tokenizer_info": self.tokenizer_task, "tensors": self.gather_tensors}
182
+
183
+ def execute(
184
+ self, tokenizer_info: TokenizerInfo, tensors: Dict[ModelReference, torch.Tensor]
185
+ ) -> Dict[ModelReference, torch.Tensor]:
186
+ tokenizer = tokenizer_info.tokenizer
187
+ permutations = tokenizer_info.permutations
188
+
189
+ models = set(tensors.keys())
190
+ if self.base_model:
191
+ models.add(self.base_model)
192
+ models = list(models)
193
+
194
+ vocab = tokenizer.get_vocab()
195
+ vocab_size = len(vocab)
196
+ if self.pad_to_multiple_of and vocab_size % self.pad_to_multiple_of:
197
+ vocab_size = (
198
+ vocab_size // self.pad_to_multiple_of + 1
199
+ ) * self.pad_to_multiple_of
200
+ embed_size = tensors[models[0]].shape[1]
201
+ assert all(
202
+ t.shape[1] == embed_size for t in tensors.values()
203
+ ), "Embedding sizes must match"
204
+
205
+ dtype = tensors[models[0]].dtype
206
+ device = tensors[models[0]].device
207
+
208
+ token_configs = dict(**(self.tokens or {}))
209
+ tokens_to_average = self.assign_embedding_sources(
210
+ permutations, models, vocab, token_configs
211
+ )
212
+
213
+ default_embeds = {}
214
+ for token, token_id in vocab.items():
215
+ embed = torch.zeros(embed_size, dtype=dtype, device=device)
216
+ if token in tokens_to_average:
217
+ count = 0
218
+ for model in models:
219
+ p = permutations[model]
220
+ if p[token_id] < 0:
221
+ continue
222
+
223
+ # --- AUDIT & BOUNDS CHECK ---
224
+ if p[token_id] >= tensors[model].shape[0]:
225
+ logging.warning(f"[VOCAB AUDIT] Model '{model}' is missing token '{token}' (ID: {token_id}). "
226
+ f"Donor size: {tensors[model].shape[0]}, Requested Index: {p[token_id]}. Skipping.")
227
+ continue
228
+ # ----------------------------
229
+
230
+ embed += tensors[model][p[token_id]]
231
+ count += 1
232
+ embed /= count
233
+ elif cfg := token_configs.get(token, None):
234
+ cfg: TokenEmbeddingConfig
235
+ embed = self.compute_default_embedding(
236
+ tokenizer_info, tensors, permutations, token, token_id, cfg
237
+ )
238
+ else:
239
+ continue
240
+ default_embeds[token] = embed
241
+
242
+ result = {}
243
+ for model in models:
244
+ p = permutations[model]
245
+ old_embed = tensors[model]
246
+ new_embed = torch.zeros(
247
+ (vocab_size, embed_size), dtype=dtype, device=device
248
+ )
249
+ for token, token_id in vocab.items():
250
+ force = False
251
+ if token in token_configs:
252
+ force = token_configs[token].force
253
+
254
+ if p[token_id] >= 0 and not force:
255
+ # --- BOUNDS CHECK FOR RESULT MAPPING ---
256
+ if p[token_id] < old_embed.shape[0]:
257
+ new_embed[token_id, :] = old_embed[p[token_id]]
258
+ else:
259
+ # Fallback to the averaged/default version if the donor is too small
260
+ new_embed[token_id, :] = default_embeds.get(token, torch.zeros_like(new_embed[0]))
261
+ # ---------------------------------------
262
+ elif token in default_embeds:
263
+ new_embed[token_id, :] = default_embeds[token]
264
+ else:
265
+ logging.error(
266
+ f"No embedding for token {repr(token)} in model {model}!"
267
+ )
268
+
269
+ if vocab_size > len(vocab):
270
+ # as suggested by https://nlp.stanford.edu/~johnhew/vocab-expansion.html
271
+ avg_embed = torch.mean(new_embed[: len(vocab), :], dim=0)
272
+ new_embed[len(vocab) :, :] = avg_embed
273
+ result[model] = new_embed
274
+
275
+ return result
276
+
277
+ def assign_embedding_sources(
278
+ self,
279
+ permutations: Dict[ModelReference, Dict[int, int]],
280
+ models: list[ModelReference],
281
+ vocab: Dict[str, int],
282
+ token_configs: Dict[str, TokenEmbeddingConfig],
283
+ ):
284
+ permutation_list = [permutations[model] for model in models]
285
+
286
+ tokens_to_average = set()
287
+ # find tokens that are only present in one model
288
+ for token, token_id in vocab.items():
289
+ if token in token_configs:
290
+ continue
291
+
292
+ has_token = [p[token_id] >= 0 for p in permutation_list]
293
+ num_present = sum(int(x) for x in has_token)
294
+ if num_present == 1:
295
+ donor_model = models[has_token.index(True)]
296
+ token_configs[token] = TokenEmbeddingConfig(source=donor_model)
297
+ continue
298
+
299
+ if num_present == 0:
300
+ token_configs[token] = TokenEmbeddingConfig(source=ZeroEmbedding())
301
+ logging.warning(f"Token {repr(token)} not found in any model")
302
+ continue
303
+
304
+ if num_present > 0 and self.base_model is not None:
305
+ if permutations[self.base_model][token_id] >= 0:
306
+ token_configs[token] = TokenEmbeddingConfig(source=self.base_model)
307
+ continue
308
+
309
+ tokens_to_average.add(token)
310
+ return tokens_to_average
311
+
312
+ def compute_default_embedding(
313
+ self,
314
+ tokenizer_info: TokenizerInfo,
315
+ tensors: Dict[ModelReference, torch.Tensor],
316
+ permutations: Dict[ModelReference, Dict[int, int]],
317
+ token: str,
318
+ token_id: int,
319
+ cfg: TokenEmbeddingConfig,
320
+ ) -> torch.Tensor:
321
+ if isinstance(cfg.source, ZeroEmbedding):
322
+ pass
323
+ elif isinstance(cfg.source, ModelTokenEmbedding):
324
+ model = cfg.source.model
325
+ assert (
326
+ model in permutations
327
+ ), f"Model {model} referenced but not part of merge"
328
+ p = permutations[model]
329
+ src_token_id = cfg.source.token_id
330
+ if src_token_id is None:
331
+ src_token = cfg.source.token
332
+ assert (
333
+ src_token in tokenizer_info.original_vocabs[model]
334
+ ), f"Token {repr(src_token)} not found in model {model}"
335
+ src_token_id = tokenizer_info.original_vocabs[model][src_token]
336
+ assert (
337
+ src_token_id >= 0 and src_token_id < tensors[model].shape[0]
338
+ ), f"Token ID {src_token_id} out of range for model {model}"
339
+ embed = tensors[model][src_token_id]
340
+ elif isinstance(cfg.source, ModelReference):
341
+ model = cfg.source
342
+ p = permutations[model]
343
+ assert p[token_id] >= 0, f"Token {repr(token)} not found in model {model}"
344
+
345
+ # --- BOUNDS CHECK FOR DEFAULT EMBED ---
346
+ if p[token_id] >= tensors[model].shape[0]:
347
+ logging.warning(f"[VOCAB AUDIT] Default source model '{model}' is missing token '{token}' from its physical tensor. Falling back to zero.")
348
+ return torch.zeros(tensors[model].shape[1], dtype=tensors[model].dtype, device=tensors[model].device)
349
+ # --------------------------------------
350
+
351
+ embed = tensors[model][p[token_id]]
352
+ else:
353
+ raise NotImplementedError(cfg)
354
+ return embed
355
+ ```
arcee_fusion_salience_scanner_v3.py ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from safetensors import safe_open
3
+ import os
4
+ import re
5
+ from collections import defaultdict
6
+
7
+ # --- CONFIGURATION ---
8
+ base_model_path = r'B:\12B\models--SicariusSicariiStuff--Impish_Bloodmoon_12B'
9
+ merged_model_path = r'B:\12B\21-Della'
10
+ # ---------------------
11
+
12
+ def get_tensor_map(path):
13
+ tensor_map = {}
14
+ files = [f for f in os.listdir(path) if f.endswith('.safetensors')]
15
+ for f in files:
16
+ full_path = os.path.join(path, f)
17
+ with safe_open(full_path, framework="pt") as st:
18
+ for k in st.keys():
19
+ tensor_map[k] = full_path
20
+ return tensor_map
21
+
22
+ print("🔍 Indexing model shards...")
23
+ base_map = get_tensor_map(base_model_path)
24
+ merged_map = get_tensor_map(merged_model_path)
25
+
26
+ # Group results by layer
27
+ layer_stats = defaultdict(lambda: {"changed": 0, "total": 0})
28
+
29
+ print("📊 Scanning tensors and calculating saliency density...")
30
+ common_tensors = set(base_map.keys()) & set(merged_map.keys())
31
+
32
+ for k in sorted(common_tensors):
33
+ # Extract layer number from name (e.g., 'model.layers.5.self_attn...')
34
+ layer_match = re.search(r'\.layers\.(\0?(\d+))\.', k)
35
+ layer_id = int(layer_match.group(1)) if layer_match else "Non-Layer"
36
+
37
+ # with safe_open(base_map[k], framework="pt") as b_st:
38
+ # base_t = b_st.get_tensor(k)
39
+ # with safe_open(merged_map[k], framework="pt") as m_st:
40
+ # merged_t = m_st.get_tensor(k)
41
+
42
+ ## Arcee Fusion logic: if weights are identical, they came from Base.
43
+ ## If they are different, they are "New Info" from the fusion.
44
+ ## We use a tiny atol to account for potential bf16/f16 casting jitters
45
+ # changed_mask = ~torch.isclose(base_t, merged_t, rtol=1e-05, atol=1e-08)
46
+
47
+ with safe_open(base_map[k], framework="pt") as b_st:
48
+ base_t = b_st.get_tensor(k)
49
+ with safe_open(merged_map[k], framework="pt") as m_st:
50
+ merged_t = m_st.get_tensor(k)
51
+
52
+ # --- VOCAB SIZE ROBUSTNESS PATCH ---
53
+ if base_t.shape != merged_t.shape:
54
+ # Find the smallest dimensions common to both
55
+ min_dim0 = min(base_t.shape[0], merged_t.shape[0])
56
+
57
+ # If it's a 2D tensor (like embeddings), handle both dims
58
+ if base_t.dim() > 1:
59
+ min_dim1 = min(base_t.shape[1], merged_t.shape[1])
60
+ base_t = base_t[:min_dim0, :min_dim1]
61
+ merged_t = merged_t[:min_dim0, :min_dim1]
62
+ else:
63
+ base_t = base_t[:min_dim0]
64
+ merged_t = merged_t[:min_dim0]
65
+
66
+ print(f" [!] Resized {k} from {list(merged_t.shape)} to {min_dim0} for comparison.")
67
+ # -----------------------------------
68
+
69
+ ## # Arcee Fusion logic: if weights are identical, they came from Base.
70
+ ## changed_mask = ~torch.isclose(base_t, merged_t, rtol=1e-05, atol=1e-08)
71
+
72
+ # If you want to be strict (only see major changes): Use diff > (0.1 * torch.abs(base_t)) (10% change).
73
+ # If you want to be balanced: Use the "diff > (1e-3 + 0.05 * torch.abs(base_t)) " code (5% change).
74
+
75
+ # --- SIGNIFICANT CHANGE LOGIC (Salience) ---
76
+ # Instead of looking for ANY change, we look for changes that exceed
77
+ # a standard deviation threshold. This filters out the "DELLA noise."
78
+ diff = torch.abs(base_t - merged_t)
79
+ threshold = 0.01 # Adjust this: 0.01 = 1% absolute change, 0.005 = 0.5%
80
+
81
+ # Alternatively, use a relative threshold for more precision:
82
+ # We consider it "New Info" only if the change is significant
83
+ # compared to the original weight magnitude.
84
+ changed_mask = diff > (1e-3 + 0.05 * torch.abs(base_t))
85
+ # -------------------------------------------
86
+
87
+ layer_stats[layer_id]["changed"] += torch.count_nonzero(changed_mask).item()
88
+
89
+
90
+ layer_stats[layer_id]["changed"] += torch.count_nonzero(changed_mask).item()
91
+ layer_stats[layer_id]["total"] += merged_t.numel()
92
+
93
+ print("\n" + "="*60)
94
+ print(f"{'LAYER':<12} | {'NEW INFO %':<12} | {'VISUAL DENSITY (█ = New, ░ = Base)'}")
95
+ print("="*60)
96
+
97
+ # Sort layers: Non-Layer first, then 0, 1, 2...
98
+ sorted_keys = sorted([k for k in layer_stats.keys() if isinstance(k, int)])
99
+ if "Non-Layer" in layer_stats:
100
+ sorted_keys = ["Non-Layer"] + sorted_keys
101
+
102
+ for lid in sorted_keys:
103
+ stats = layer_stats[lid]
104
+ percentage = (stats["changed"] / stats["total"]) * 100
105
+
106
+ # Create ASCII bar
107
+ bar_width = 30
108
+ filled = int((percentage / 100) * bar_width)
109
+ bar = "█" * filled + "░" * (bar_width - filled)
110
+
111
+ label = f"Layer {lid}" if isinstance(lid, int) else lid
112
+ print(f"{label:<12} | {percentage:>10.2f}% | {bar}")
113
+
114
+ print("="*60)
115
+ print("Analysis Complete.")
embed_12B.py ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (C) 2025 Arcee AI
2
+ # SPDX-License-Identifier: LGPL-3.0-only
3
+ ## Version 2D by Naphula
4
+
5
+ import logging
6
+ from typing import Dict, Optional
7
+
8
+ import torch
9
+
10
+ from mergekit.common import ImmutableMap, ModelReference
11
+ from mergekit.graph import Task
12
+ from mergekit.io.tasks import GatherTensors
13
+ from mergekit.tokenizer.build import BuildTokenizer, TokenizerInfo
14
+ from mergekit.tokenizer.config import (
15
+ ModelTokenEmbedding,
16
+ TokenEmbeddingConfig,
17
+ ZeroEmbedding,
18
+ )
19
+
20
+
21
+ class PermutedEmbeddings(Task[Dict[ModelReference, torch.Tensor]]):
22
+ gather_tensors: GatherTensors
23
+ tokenizer_task: BuildTokenizer
24
+ tokens: Optional[ImmutableMap[str, TokenEmbeddingConfig]]
25
+ pad_to_multiple_of: Optional[int]
26
+ base_model: Optional[ModelReference]
27
+
28
+ def arguments(self) -> Dict[str, Task]:
29
+ return {"tokenizer_info": self.tokenizer_task, "tensors": self.gather_tensors}
30
+
31
+ def execute(
32
+ self, tokenizer_info: TokenizerInfo, tensors: Dict[ModelReference, torch.Tensor]
33
+ ) -> Dict[ModelReference, torch.Tensor]:
34
+ tokenizer = tokenizer_info.tokenizer
35
+ permutations = tokenizer_info.permutations
36
+
37
+ models = set(tensors.keys())
38
+ if self.base_model:
39
+ models.add(self.base_model)
40
+ models = list(models)
41
+
42
+ vocab = tokenizer.get_vocab()
43
+ vocab_size = len(vocab)
44
+ if self.pad_to_multiple_of and vocab_size % self.pad_to_multiple_of:
45
+ vocab_size = (
46
+ vocab_size // self.pad_to_multiple_of + 1
47
+ ) * self.pad_to_multiple_of
48
+ embed_size = tensors[models[0]].shape[1]
49
+ assert all(
50
+ t.shape[1] == embed_size for t in tensors.values()
51
+ ), "Embedding sizes must match"
52
+
53
+ dtype = tensors[models[0]].dtype
54
+ device = tensors[models[0]].device
55
+
56
+ token_configs = dict(**(self.tokens or {}))
57
+ tokens_to_average = self.assign_embedding_sources(
58
+ permutations, models, vocab, token_configs
59
+ )
60
+
61
+ default_embeds = {}
62
+ for token, token_id in vocab.items():
63
+ embed = torch.zeros(embed_size, dtype=dtype, device=device)
64
+ if token in tokens_to_average:
65
+ count = 0
66
+ for model in models:
67
+ p = permutations[model]
68
+ if p[token_id] < 0:
69
+ continue
70
+
71
+ # --- AUDIT & BOUNDS CHECK ---
72
+ if p[token_id] >= tensors[model].shape[0]:
73
+ logging.warning(f"[VOCAB AUDIT] Model '{model}' is missing token '{token}' (ID: {token_id}). "
74
+ f"Donor size: {tensors[model].shape[0]}, Requested Index: {p[token_id]}. Skipping.")
75
+ continue
76
+ # ----------------------------
77
+
78
+ embed += tensors[model][p[token_id]]
79
+ count += 1
80
+ embed /= count
81
+ elif cfg := token_configs.get(token, None):
82
+ cfg: TokenEmbeddingConfig
83
+ embed = self.compute_default_embedding(
84
+ tokenizer_info, tensors, permutations, token, token_id, cfg
85
+ )
86
+ else:
87
+ continue
88
+ default_embeds[token] = embed
89
+
90
+ result = {}
91
+ for model in models:
92
+ p = permutations[model]
93
+ old_embed = tensors[model]
94
+ new_embed = torch.zeros(
95
+ (vocab_size, embed_size), dtype=dtype, device=device
96
+ )
97
+ for token, token_id in vocab.items():
98
+ force = False
99
+ if token in token_configs:
100
+ force = token_configs[token].force
101
+
102
+ if p[token_id] >= 0 and not force:
103
+ # --- BOUNDS CHECK FOR RESULT MAPPING ---
104
+ if p[token_id] < old_embed.shape[0]:
105
+ new_embed[token_id, :] = old_embed[p[token_id]]
106
+ else:
107
+ # Fallback to the averaged/default version if the donor is too small
108
+ new_embed[token_id, :] = default_embeds.get(token, torch.zeros_like(new_embed[0]))
109
+ # ---------------------------------------
110
+ elif token in default_embeds:
111
+ new_embed[token_id, :] = default_embeds[token]
112
+ else:
113
+ logging.error(
114
+ f"No embedding for token {repr(token)} in model {model}!"
115
+ )
116
+
117
+ if vocab_size > len(vocab):
118
+ # as suggested by https://nlp.stanford.edu/~johnhew/vocab-expansion.html
119
+ avg_embed = torch.mean(new_embed[: len(vocab), :], dim=0)
120
+ new_embed[len(vocab) :, :] = avg_embed
121
+ result[model] = new_embed
122
+
123
+ return result
124
+
125
+ def assign_embedding_sources(
126
+ self,
127
+ permutations: Dict[ModelReference, Dict[int, int]],
128
+ models: list[ModelReference],
129
+ vocab: Dict[str, int],
130
+ token_configs: Dict[str, TokenEmbeddingConfig],
131
+ ):
132
+ permutation_list = [permutations[model] for model in models]
133
+
134
+ tokens_to_average = set()
135
+ # find tokens that are only present in one model
136
+ for token, token_id in vocab.items():
137
+ if token in token_configs:
138
+ continue
139
+
140
+ has_token = [p[token_id] >= 0 for p in permutation_list]
141
+ num_present = sum(int(x) for x in has_token)
142
+ if num_present == 1:
143
+ donor_model = models[has_token.index(True)]
144
+ token_configs[token] = TokenEmbeddingConfig(source=donor_model)
145
+ continue
146
+
147
+ if num_present == 0:
148
+ token_configs[token] = TokenEmbeddingConfig(source=ZeroEmbedding())
149
+ logging.warning(f"Token {repr(token)} not found in any model")
150
+ continue
151
+
152
+ if num_present > 0 and self.base_model is not None:
153
+ if permutations[self.base_model][token_id] >= 0:
154
+ token_configs[token] = TokenEmbeddingConfig(source=self.base_model)
155
+ continue
156
+
157
+ tokens_to_average.add(token)
158
+ return tokens_to_average
159
+
160
+ def compute_default_embedding(
161
+ self,
162
+ tokenizer_info: TokenizerInfo,
163
+ tensors: Dict[ModelReference, torch.Tensor],
164
+ permutations: Dict[ModelReference, Dict[int, int]],
165
+ token: str,
166
+ token_id: int,
167
+ cfg: TokenEmbeddingConfig,
168
+ ) -> torch.Tensor:
169
+ if isinstance(cfg.source, ZeroEmbedding):
170
+ pass
171
+ elif isinstance(cfg.source, ModelTokenEmbedding):
172
+ model = cfg.source.model
173
+ assert (
174
+ model in permutations
175
+ ), f"Model {model} referenced but not part of merge"
176
+ p = permutations[model]
177
+ src_token_id = cfg.source.token_id
178
+ if src_token_id is None:
179
+ src_token = cfg.source.token
180
+ assert (
181
+ src_token in tokenizer_info.original_vocabs[model]
182
+ ), f"Token {repr(src_token)} not found in model {model}"
183
+ src_token_id = tokenizer_info.original_vocabs[model][src_token]
184
+ assert (
185
+ src_token_id >= 0 and src_token_id < tensors[model].shape[0]
186
+ ), f"Token ID {src_token_id} out of range for model {model}"
187
+ embed = tensors[model][src_token_id]
188
+ elif isinstance(cfg.source, ModelReference):
189
+ model = cfg.source
190
+ p = permutations[model]
191
+ assert p[token_id] >= 0, f"Token {repr(token)} not found in model {model}"
192
+
193
+ # --- BOUNDS CHECK FOR DEFAULT EMBED ---
194
+ if p[token_id] >= tensors[model].shape[0]:
195
+ logging.warning(f"[VOCAB AUDIT] Default source model '{model}' is missing token '{token}' from its physical tensor. Falling back to zero.")
196
+ return torch.zeros(tensors[model].shape[1], dtype=tensors[model].dtype, device=tensors[model].device)
197
+ # --------------------------------------
198
+
199
+ embed = tensors[model][p[token_id]]
200
+ else:
201
+ raise NotImplementedError(cfg)
202
+ return embed
embed_24B.py ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (C) 2025 Arcee AI
2
+ # SPDX-License-Identifier: LGPL-3.0-only
3
+
4
+ import logging
5
+ from typing import Dict, Optional
6
+
7
+ import torch
8
+
9
+ from mergekit.common import ImmutableMap, ModelReference
10
+ from mergekit.graph import Task
11
+ from mergekit.io.tasks import GatherTensors
12
+ from mergekit.tokenizer.build import BuildTokenizer, TokenizerInfo
13
+ from mergekit.tokenizer.config import (
14
+ ModelTokenEmbedding,
15
+ TokenEmbeddingConfig,
16
+ ZeroEmbedding,
17
+ )
18
+
19
+
20
+ class PermutedEmbeddings(Task[Dict[ModelReference, torch.Tensor]]):
21
+ gather_tensors: GatherTensors
22
+ tokenizer_task: BuildTokenizer
23
+ tokens: Optional[ImmutableMap[str, TokenEmbeddingConfig]]
24
+ pad_to_multiple_of: Optional[int]
25
+ base_model: Optional[ModelReference]
26
+
27
+ def arguments(self) -> Dict[str, Task]:
28
+ return {"tokenizer_info": self.tokenizer_task, "tensors": self.gather_tensors}
29
+
30
+ def execute(
31
+ self, tokenizer_info: TokenizerInfo, tensors: Dict[ModelReference, torch.Tensor]
32
+ ) -> Dict[ModelReference, torch.Tensor]:
33
+ tokenizer = tokenizer_info.tokenizer
34
+ permutations = tokenizer_info.permutations
35
+
36
+ models = set(tensors.keys())
37
+ if self.base_model:
38
+ models.add(self.base_model)
39
+ models = list(models)
40
+
41
+ vocab = tokenizer.get_vocab()
42
+ vocab_size = len(vocab)
43
+ if self.pad_to_multiple_of and vocab_size % self.pad_to_multiple_of:
44
+ vocab_size = (
45
+ vocab_size // self.pad_to_multiple_of + 1
46
+ ) * self.pad_to_multiple_of
47
+ embed_size = tensors[models[0]].shape[1]
48
+ assert all(
49
+ t.shape[1] == embed_size for t in tensors.values()
50
+ ), "Embedding sizes must match"
51
+
52
+ dtype = tensors[models[0]].dtype
53
+ device = tensors[models[0]].device
54
+
55
+ token_configs = dict(**(self.tokens or {}))
56
+ tokens_to_average = self.assign_embedding_sources(
57
+ permutations, models, vocab, token_configs
58
+ )
59
+
60
+ default_embeds = {}
61
+ for token, token_id in vocab.items():
62
+ embed = torch.zeros(embed_size, dtype=dtype, device=device)
63
+ if token in tokens_to_average:
64
+ count = 0
65
+ for model in models:
66
+ p = permutations[model]
67
+ if p[token_id] < 0:
68
+ continue
69
+ embed += tensors[model][p[token_id]]
70
+ count += 1
71
+ embed /= count
72
+ elif cfg := token_configs.get(token, None):
73
+ cfg: TokenEmbeddingConfig
74
+ embed = self.compute_default_embedding(
75
+ tokenizer_info, tensors, permutations, token, token_id, cfg,
76
+ embed_size, dtype, device
77
+ )
78
+ else:
79
+ continue
80
+ default_embeds[token] = embed
81
+
82
+ result = {}
83
+ for model in models:
84
+ p = permutations[model]
85
+ old_embed = tensors[model]
86
+ new_embed = torch.zeros(
87
+ (vocab_size, embed_size), dtype=dtype, device=device
88
+ )
89
+ for token, token_id in vocab.items():
90
+ force = False
91
+ if token in token_configs:
92
+ force = token_configs[token].force
93
+
94
+ if p[token_id] >= 0 and not force:
95
+ new_embed[token_id, :] = old_embed[p[token_id]]
96
+ elif token in default_embeds:
97
+ new_embed[token_id, :] = default_embeds[token]
98
+ else:
99
+ logging.error(
100
+ f"No embedding for token {repr(token)} in model {model}!"
101
+ )
102
+
103
+ if vocab_size > len(vocab):
104
+ # as suggested by https://nlp.stanford.edu/~johnhew/vocab-expansion.html
105
+ avg_embed = torch.mean(new_embed[: len(vocab), :], dim=0)
106
+ new_embed[len(vocab) :, :] = avg_embed
107
+ result[model] = new_embed
108
+
109
+ return result
110
+
111
+ def assign_embedding_sources(
112
+ self,
113
+ permutations: Dict[ModelReference, Dict[int, int]],
114
+ models: list[ModelReference],
115
+ vocab: Dict[str, int],
116
+ token_configs: Dict[str, TokenEmbeddingConfig],
117
+ ):
118
+ permutation_list = [permutations[model] for model in models]
119
+
120
+ tokens_to_average = set()
121
+ # find tokens that are only present in one model
122
+ for token, token_id in vocab.items():
123
+ if token in token_configs:
124
+ continue
125
+
126
+ has_token = [p[token_id] >= 0 for p in permutation_list]
127
+ num_present = sum(int(x) for x in has_token)
128
+ if num_present == 1:
129
+ donor_model = models[has_token.index(True)]
130
+ token_configs[token] = TokenEmbeddingConfig(source=donor_model)
131
+ continue
132
+
133
+ if num_present == 0:
134
+ token_configs[token] = TokenEmbeddingConfig(source=ZeroEmbedding(kind="zero"))
135
+ logging.warning(f"Token {repr(token)} not found in any model")
136
+ continue
137
+
138
+ if num_present > 0 and self.base_model is not None:
139
+ if permutations[self.base_model][token_id] >= 0:
140
+ token_configs[token] = TokenEmbeddingConfig(source=self.base_model)
141
+ continue
142
+
143
+ tokens_to_average.add(token)
144
+ return tokens_to_average
145
+
146
+ def compute_default_embedding(
147
+ self,
148
+ tokenizer_info: TokenizerInfo,
149
+ tensors: Dict[ModelReference, torch.Tensor],
150
+ permutations: Dict[ModelReference, Dict[int, int]],
151
+ token: str,
152
+ token_id: int,
153
+ cfg: TokenEmbeddingConfig,
154
+ embed_size: int,
155
+ dtype: torch.dtype,
156
+ device: torch.device,
157
+ ) -> torch.Tensor:
158
+ if isinstance(cfg.source, ZeroEmbedding):
159
+ embed = torch.zeros(embed_size, dtype=dtype, device=device)
160
+ pass
161
+ elif isinstance(cfg.source, ModelTokenEmbedding):
162
+ model = cfg.source.model
163
+ assert (
164
+ model in permutations
165
+ ), f"Model {model} referenced but not part of merge"
166
+ p = permutations[model]
167
+ src_token_id = cfg.source.token_id
168
+ if src_token_id is None:
169
+ src_token = cfg.source.token
170
+ assert (
171
+ src_token in tokenizer_info.original_vocabs[model]
172
+ ), f"Token {repr(src_token)} not found in model {model}"
173
+ src_token_id = tokenizer_info.original_vocabs[model][src_token]
174
+ assert (
175
+ src_token_id >= 0 and src_token_id < tensors[model].shape[0]
176
+ ), f"Token ID {src_token_id} out of range for model {model}"
177
+ embed = tensors[model][src_token_id]
178
+ elif isinstance(cfg.source, ModelReference):
179
+ model = cfg.source
180
+ p = permutations[model]
181
+ assert p[token_id] >= 0, f"Token {repr(token)} not found in model {model}"
182
+ embed = tensors[model][p[token_id]]
183
+ else:
184
+ raise NotImplementedError(cfg)
185
+ return embed
model_tools.md CHANGED
@@ -8,7 +8,7 @@ pinned: false
8
  ---
9
 
10
  # Model Tools by Naphula
11
- Tools to enhance LLM quantizations and merging. Merge and audit large language models with low VRAM.
12
 
13
  # [graph_v18.py](https://huggingface.co/spaces/Naphula/model_tools/blob/main/graph_v18.py)
14
  - Merge models in minutes instead of hours on low VRAM. For a 3060/3060 Ti user: This script enables functionality that is otherwise impossible (merging 70B models or large 7B merges with `--cuda`) without OOM. [More details here](https://huggingface.co/spaces/Naphula/model_tools/blob/main/mergekit_low-VRAM-graph_patch.md)
@@ -17,6 +17,10 @@ Tools to enhance LLM quantizations and merging. Merge and audit large language m
17
  # config.py
18
  - Simply replace line 13 | BEFORE `ScalarOrGradient: TypeAlias = Union[float, List[float]]` → AFTER `ScalarOrGradient: TypeAlias = Union[float, List[float], str, bool]` | to allow for custom filepath strings within parameter settings.
19
 
 
 
 
 
20
  # [enable_fix_mistral_regex_true.md](https://huggingface.co/spaces/Naphula/model_tools/blob/main/enable_fix_mistral_regex_true.md)
21
  - Merge models with extreme tokenizer incompatibility. Requires modifying the `mergekit.yaml` `tokenizer` section and adding `--fix-mistral-regex` to your merge commands. (Note: Do not use `token_surgeon.py`, `gen_id_patcher.py`, or `vocab_id_patcher.py` with this, they are obsolete now.) Configured for MN 12B by default. Follow the steps in this guide to modify these scripts:
22
  - `mergekit/merge.py`
@@ -55,6 +59,7 @@ Tools to enhance LLM quantizations and merging. Merge and audit large language m
55
 
56
  # [arcee_fusion_salience_scanner.py](https://huggingface.co/spaces/Naphula/model_tools/blob/main/arcee_fusion_salience_scanner.py)
57
  - Scan the salience % of your arcee_fusion merges. The default `tukey_fence` value is 1.5 which results in 12.5% salience, but [this can be adjusted (see guide here)](modify_arcee_fusion_tukey_fence_parameter.md).
 
58
 
59
  # [eos_scanner.py](https://huggingface.co/spaces/Naphula/model_tools/blob/main/eos_scanner.py)
60
  - Updated! This tool scans the tokenizer jsons to detect any mismatches with EOS tokens, which cause early termination bugs. You can then use the [gen_id_patcher.py](https://huggingface.co/spaces/Naphula/model_tools/blob/main/gen_id_patcher.py) and [vocab_id_patcher.py](https://huggingface.co/spaces/Naphula/model_tools/blob/main/vocab_id_patcher.py), or the [chatml_to_mistral.py](https://huggingface.co/spaces/Naphula/model_tools/blob/main/chatml_to_mistral.py) to patch missing `generation_config.json` files for EOS token. See [this post](https://huggingface.co/Naphula/Q0_Bench/discussions/1?not-for-all-audiences=true#6987717c762f0a45f672e250) as well as the [EOS Scanner ReadMe](https://huggingface.co/spaces/Naphula/model_tools/blob/main/eos_scanner_readme.md) for more info.
 
8
  ---
9
 
10
  # Model Tools by Naphula
11
+ Tools to enhance LLM quantizations and merging. Merge and audit large language models on low VRAM GPUs.
12
 
13
  # [graph_v18.py](https://huggingface.co/spaces/Naphula/model_tools/blob/main/graph_v18.py)
14
  - Merge models in minutes instead of hours on low VRAM. For a 3060/3060 Ti user: This script enables functionality that is otherwise impossible (merging 70B models or large 7B merges with `--cuda`) without OOM. [More details here](https://huggingface.co/spaces/Naphula/model_tools/blob/main/mergekit_low-VRAM-graph_patch.md)
 
17
  # config.py
18
  - Simply replace line 13 | BEFORE `ScalarOrGradient: TypeAlias = Union[float, List[float]]` → AFTER `ScalarOrGradient: TypeAlias = Union[float, List[float], str, bool]` | to allow for custom filepath strings within parameter settings.
19
 
20
+ # [embed_12B.py](https://huggingface.co/spaces/Naphula/model_tools/blob/main/embed_12B.py) and [embed_24B.py](https://huggingface.co/spaces/Naphula/model_tools/blob/main/embed_24B.py)
21
+ - This is an alternate solution in cases where `--fix-mistral-regex` and `tokensurgeon` fail, such as `della` or `passthrough` merges between models with mismatched `vocab_size`. Read [the guide](https://huggingface.co/spaces/Naphula/model_tools/blob/main/Mergekit-Robustness-Patch-embed_v2.md) here, download either file and save it as `mergekit-main\mergekit\tokenizer\embed.py`. Attached is one for Mistral Nemo 12B (v2d), and another for Mistral Small 24B (v2a).
22
+ - I noticed that sometimes the default `embed.py` works best so keep a copy of that too, and if it fails for some reason try the 12B or 24B version.
23
+
24
  # [enable_fix_mistral_regex_true.md](https://huggingface.co/spaces/Naphula/model_tools/blob/main/enable_fix_mistral_regex_true.md)
25
  - Merge models with extreme tokenizer incompatibility. Requires modifying the `mergekit.yaml` `tokenizer` section and adding `--fix-mistral-regex` to your merge commands. (Note: Do not use `token_surgeon.py`, `gen_id_patcher.py`, or `vocab_id_patcher.py` with this, they are obsolete now.) Configured for MN 12B by default. Follow the steps in this guide to modify these scripts:
26
  - `mergekit/merge.py`
 
59
 
60
  # [arcee_fusion_salience_scanner.py](https://huggingface.co/spaces/Naphula/model_tools/blob/main/arcee_fusion_salience_scanner.py)
61
  - Scan the salience % of your arcee_fusion merges. The default `tukey_fence` value is 1.5 which results in 12.5% salience, but [this can be adjusted (see guide here)](modify_arcee_fusion_tukey_fence_parameter.md).
62
+ - Updated version here [arcee_fusion_salience_scanner_v3.py](https://huggingface.co/spaces/Naphula/model_tools/blob/main/arcee_fusion_salience_scanner_v3.py)
63
 
64
  # [eos_scanner.py](https://huggingface.co/spaces/Naphula/model_tools/blob/main/eos_scanner.py)
65
  - Updated! This tool scans the tokenizer jsons to detect any mismatches with EOS tokens, which cause early termination bugs. You can then use the [gen_id_patcher.py](https://huggingface.co/spaces/Naphula/model_tools/blob/main/gen_id_patcher.py) and [vocab_id_patcher.py](https://huggingface.co/spaces/Naphula/model_tools/blob/main/vocab_id_patcher.py), or the [chatml_to_mistral.py](https://huggingface.co/spaces/Naphula/model_tools/blob/main/chatml_to_mistral.py) to patch missing `generation_config.json` files for EOS token. See [this post](https://huggingface.co/Naphula/Q0_Bench/discussions/1?not-for-all-audiences=true#6987717c762f0a45f672e250) as well as the [EOS Scanner ReadMe](https://huggingface.co/spaces/Naphula/model_tools/blob/main/eos_scanner_readme.md) for more info.