Instructions to use Noctalin/Ornith-1.0-35B-oQ8-fp16 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use Noctalin/Ornith-1.0-35B-oQ8-fp16 with MLX:
# Make sure mlx-lm is installed # pip install --upgrade mlx-lm # Generate text with mlx-lm from mlx_lm import load, generate model, tokenizer = load("Noctalin/Ornith-1.0-35B-oQ8-fp16") prompt = "Write a story about Einstein" messages = [{"role": "user", "content": prompt}] prompt = tokenizer.apply_chat_template( messages, add_generation_prompt=True ) text = generate(model, tokenizer, prompt=prompt, verbose=True) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
- Pi
How to use Noctalin/Ornith-1.0-35B-oQ8-fp16 with Pi:
Start the MLX server
# Install MLX LM: uv tool install mlx-lm # Start a local OpenAI-compatible server: mlx_lm.server --model "Noctalin/Ornith-1.0-35B-oQ8-fp16"
Configure the model in Pi
# Install Pi: npm install -g @mariozechner/pi-coding-agent # Add to ~/.pi/agent/models.json: { "providers": { "mlx-lm": { "baseUrl": "http://localhost:8080/v1", "api": "openai-completions", "apiKey": "none", "models": [ { "id": "Noctalin/Ornith-1.0-35B-oQ8-fp16" } ] } } }Run Pi
# Start Pi in your project directory: pi
- MLX LM
How to use Noctalin/Ornith-1.0-35B-oQ8-fp16 with MLX LM:
Generate or start a chat session
# Install MLX LM uv tool install mlx-lm # Interactive chat REPL mlx_lm.chat --model "Noctalin/Ornith-1.0-35B-oQ8-fp16"
Run an OpenAI-compatible server
# Install MLX LM uv tool install mlx-lm # Start the server mlx_lm.server --model "Noctalin/Ornith-1.0-35B-oQ8-fp16" # Calling the OpenAI-compatible server with curl curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "Noctalin/Ornith-1.0-35B-oQ8-fp16", "messages": [ {"role": "user", "content": "Hello"} ] }' - Hermes Agent
How to use Noctalin/Ornith-1.0-35B-oQ8-fp16 with Hermes Agent:
Start the MLX server
# Install MLX LM: uv tool install mlx-lm # Start a local OpenAI-compatible server: mlx_lm.server --model "Noctalin/Ornith-1.0-35B-oQ8-fp16"
Configure Hermes
# Install Hermes: curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash hermes setup # Point Hermes at the local server: hermes config set model.provider custom hermes config set model.base_url http://127.0.0.1:8080/v1 hermes config set model.default Noctalin/Ornith-1.0-35B-oQ8-fp16
Run Hermes
hermes
- Atomic Chat
- OpenClaw
How to use Noctalin/Ornith-1.0-35B-oQ8-fp16 with OpenClaw:
Start the MLX server
# Install MLX LM: uv tool install mlx-lm # Start a local OpenAI-compatible server: mlx_lm.server --model "Noctalin/Ornith-1.0-35B-oQ8-fp16"
Configure OpenClaw
# Install OpenClaw: npm install -g openclaw@latest # Register the local server and set it as the default model: openclaw onboard --non-interactive --mode local \ --auth-choice custom-api-key \ --custom-base-url http://127.0.0.1:8080/v1 \ --custom-model-id "Noctalin/Ornith-1.0-35B-oQ8-fp16" \ --custom-provider-id mlx-lm \ --custom-compatibility openai \ --custom-text-input \ --accept-risk \ --skip-health
Run OpenClaw
openclaw agent --local --agent main --message "Hello from Hugging Face"
fix(weights): stack per-expert MoE tensors into switch_mlp layout for mlx-lm
Browse filesOrnith-1.0-35B ships experts in the legacy per-expert HF layout, which
mlx-lm's qwen3_5_moe sanitize() does not stack, so the quantized build
failed to load with 'Received 92160 parameters not in model'. Stack the
92,160 per-expert tensors into 360 switch_mlp tensors (bitwise-identical
values, no requantization) and rewrite the per-path quantization override
keys in config.json to post-sanitize module paths. Ship
repair_moe_experts.py (in-place repair with bitwise self-check) so broken
earlier downloads can be fixed locally, and document it in the README.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- README.md +21 -0
- config.json +0 -0
- model-00001-of-00008.safetensors → model-00001-of-00007.safetensors +2 -2
- model-00002-of-00008.safetensors → model-00002-of-00007.safetensors +2 -2
- model-00003-of-00008.safetensors → model-00003-of-00007.safetensors +2 -2
- model-00004-of-00008.safetensors → model-00004-of-00007.safetensors +2 -2
- model-00005-of-00007.safetensors +3 -0
- model-00005-of-00008.safetensors +0 -3
- model-00006-of-00007.safetensors +3 -0
- model-00006-of-00008.safetensors +0 -3
- model-00007-of-00007.safetensors +3 -0
- model-00007-of-00008.safetensors +0 -3
- model-00008-of-00008.safetensors +0 -3
- model.safetensors.index.json +0 -0
- repair_moe_experts.py +205 -0
|
@@ -99,6 +99,27 @@ To seamlessly route this model through agentic development workspaces like **Ope
|
|
| 99 |
|
| 100 |
---
|
| 101 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
## 🌡️ Thermal Optimization Notice
|
| 103 |
|
| 104 |
Sustained execution across massive context windows heavily taxes the Apple Silicon GPU/CPU complexes, causing rapid heat buildup. Because Apple's default fan profiles emphasize near-silent operation, they delay ramping up system fans until thermal throttling has already begun to affect generation tokens-per-second (TPS).
|
|
|
|
| 99 |
|
| 100 |
---
|
| 101 |
|
| 102 |
+
## 🩹 Repair Script (`repair_moe_experts.py`)
|
| 103 |
+
|
| 104 |
+
The weights in this repository are already fixed and load correctly — **you do not need to run this for normal use.**
|
| 105 |
+
|
| 106 |
+
It's included only for anyone who cached an earlier broken download of this repo, or who runs into the same issue when quantizing another MoE model with a similar per-expert weight layout. Symptom: loading fails with an error like:
|
| 107 |
+
|
| 108 |
+
```
|
| 109 |
+
Received 92160 parameters not in model:
|
| 110 |
+
language_model.model.layers.0.mlp.experts.0.down_proj.biases, ...
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
Cause: Ornith-1.0-35B ships its MoE experts as separate per-expert tensors (`mlp.experts.<0-255>.{gate,up,down}_proj`), but mlx-lm's `qwen3_5_moe` loader only understands the fused `switch_mlp` layout — so an affected build never quantizes them into a loadable shape, and its `config.json` also carries stale pre-sanitize key names for the per-path quantization overrides.
|
| 114 |
+
|
| 115 |
+
Usage, if ever needed:
|
| 116 |
+
|
| 117 |
+
```bash
|
| 118 |
+
python3 repair_moe_experts.py /path/to/Ornith-1.0-35B-oQ8-fp16
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
Requires only `mlx` (any environment with `mlx-lm`/`omlx` installed has it). It streams the weights shard-by-shard, stacks the per-expert tensors into `switch_mlp` (bitwise-identical values — no requantization), and fixes the `config.json` key names. The repair happens **in place**: new shards are written alongside the originals, verified bitwise against the source tensors, and only then swapped in — a failure at any point leaves the original model untouched. It needs free disk roughly equal to the model size while running, and is safe to re-run (exits early on an already-repaired model).
|
| 122 |
+
|
| 123 |
## 🌡️ Thermal Optimization Notice
|
| 124 |
|
| 125 |
Sustained execution across massive context windows heavily taxes the Apple Silicon GPU/CPU complexes, causing rapid heat buildup. Because Apple's default fan profiles emphasize near-silent operation, they delay ramping up system fans until thermal throttling has already begun to affect generation tokens-per-second (TPS).
|
|
The diff for this file is too large to render.
See raw diff
|
|
|
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:19546eef0eac6e498450aee132407b7270eb89857daafa8764544d7ec99fac43
|
| 3 |
+
size 5425639655
|
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:89fa11a0cbf88edff7ccd68203311ee2da26a9d7f4ac7e635f84600435be8751
|
| 3 |
+
size 5613406117
|
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0614d305661718a7815d0c4196992943763bc696c36b76c69ba7df9a712b4077
|
| 3 |
+
size 5427797286
|
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:467c53dd16c1436c9aa84cb2f63cf90ad4796a66322aaab207f98cf7b9a023d4
|
| 3 |
+
size 5577316492
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b023bb683cdcd411e427e92c8fa12732c4e9af2c4287f9468d2455b72d80bbff
|
| 3 |
+
size 5375722969
|
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:ec79d2cf9f2b4000091e01ff5d53f21359b2429383c0f26b7d4674f7f05d2511
|
| 3 |
-
size 5002098814
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:67610273a49c178b41e21fece5225bd72070fc6490c25339e294d247ad3b6cc0
|
| 3 |
+
size 5543233000
|
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:7c304bbf6e6759d54c52d59e2c87c64359417d6c2a15408294efa9e424d895a7
|
| 3 |
-
size 5001937681
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f4287b1548c15f8d23b77bb7f7a5b291de02f67cb7e197721e664ce0037d9b3d
|
| 3 |
+
size 3843707156
|
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:32bf527af70bb93a3094708ce3f31794aff74b6fdd16569509b46ce4631cf22a
|
| 3 |
-
size 5002688319
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:2b21890c5b1121a0c60c25f4caf9ac956ec194bd1917d5fb5819594586c31bc6
|
| 3 |
-
size 1803607514
|
|
|
|
|
|
|
|
|
|
|
|
|
The diff for this file is too large to render.
See raw diff
|
|
|
|
@@ -0,0 +1,205 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Repair oMLX-quantized Qwen3.5-MoE checkpoints that store routed experts
|
| 3 |
+
in the legacy per-expert layout (mlp.experts.<E>.{gate,up,down}_proj.*).
|
| 4 |
+
|
| 5 |
+
mlx-lm's qwen3_5_moe sanitize() only stacks the fused `experts.gate_up_proj`
|
| 6 |
+
layout, so per-expert checkpoints fail to load with
|
| 7 |
+
"Received NNNNN parameters not in model". This script repairs the model
|
| 8 |
+
in place:
|
| 9 |
+
|
| 10 |
+
1. Stacks every `<prefix>.mlp.experts.<E>.<proj>.<tensor>` group along a new
|
| 11 |
+
leading axis into `<prefix>.mlp.switch_mlp.<proj>.<tensor>`.
|
| 12 |
+
2. Rewrites per-path quantization overrides in config.json from raw HF key
|
| 13 |
+
names (model.language_model.*) to post-sanitize module paths
|
| 14 |
+
(language_model.model.*), which is how mlx-lm looks them up at load time.
|
| 15 |
+
|
| 16 |
+
New shards are written alongside the originals, verified bitwise against the
|
| 17 |
+
source tensors, and only then swapped in (originals deleted). A failure at any
|
| 18 |
+
point leaves the original model untouched. Needs free disk roughly equal to
|
| 19 |
+
the model size while running.
|
| 20 |
+
|
| 21 |
+
Usage:
|
| 22 |
+
python3 repair_moe_experts.py <model_dir>
|
| 23 |
+
|
| 24 |
+
Requires mlx; no other dependencies.
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
import json
|
| 28 |
+
import re
|
| 29 |
+
import struct
|
| 30 |
+
import sys
|
| 31 |
+
from pathlib import Path
|
| 32 |
+
|
| 33 |
+
import mlx.core as mx
|
| 34 |
+
|
| 35 |
+
EXPERT_RE = re.compile(
|
| 36 |
+
r"^(?P<prefix>.+\.mlp)\.experts\.(?P<e>\d+)\.(?P<proj>\w+_proj)\.(?P<t>weight|scales|biases)$"
|
| 37 |
+
)
|
| 38 |
+
SHARD_BYTES = 5 * 1024**3
|
| 39 |
+
TMP_PREFIX = "tmp-shard-"
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def quant_key_to_module_path(key):
|
| 43 |
+
if key.startswith("model.language_model"):
|
| 44 |
+
return key.replace("model.language_model", "language_model.model", 1)
|
| 45 |
+
if key.startswith("language_model."):
|
| 46 |
+
return key
|
| 47 |
+
return "language_model." + key
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def read_header(path):
|
| 51 |
+
with open(path, "rb") as f:
|
| 52 |
+
n = struct.unpack("<Q", f.read(8))[0]
|
| 53 |
+
return json.loads(f.read(n))
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def nbytes(a):
|
| 57 |
+
return a.size * a.dtype.size
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
class ShardWriter:
|
| 61 |
+
def __init__(self, out_dir, metadata):
|
| 62 |
+
self.out_dir = out_dir
|
| 63 |
+
self.metadata = metadata
|
| 64 |
+
self.buffer = {}
|
| 65 |
+
self.buffer_bytes = 0
|
| 66 |
+
self.files = [] # [(tmp_path, [keys])]
|
| 67 |
+
|
| 68 |
+
def add(self, key, array):
|
| 69 |
+
self.buffer[key] = array
|
| 70 |
+
self.buffer_bytes += nbytes(array)
|
| 71 |
+
if self.buffer_bytes >= SHARD_BYTES:
|
| 72 |
+
self.flush()
|
| 73 |
+
|
| 74 |
+
def flush(self):
|
| 75 |
+
if not self.buffer:
|
| 76 |
+
return
|
| 77 |
+
tmp = self.out_dir / f"{TMP_PREFIX}{len(self.files):05d}.safetensors"
|
| 78 |
+
mx.save_safetensors(str(tmp), self.buffer, metadata=self.metadata)
|
| 79 |
+
mx.clear_cache()
|
| 80 |
+
self.files.append((tmp, list(self.buffer)))
|
| 81 |
+
self.buffer = {}
|
| 82 |
+
self.buffer_bytes = 0
|
| 83 |
+
|
| 84 |
+
def tmp_weight_map(self):
|
| 85 |
+
return {k: tmp for tmp, keys in self.files for k in keys}
|
| 86 |
+
|
| 87 |
+
def commit(self, old_shards):
|
| 88 |
+
"""Delete the original shards and move tmp shards to final names."""
|
| 89 |
+
for shard in old_shards:
|
| 90 |
+
(self.out_dir / shard).unlink()
|
| 91 |
+
n = len(self.files)
|
| 92 |
+
weight_map, total = {}, 0
|
| 93 |
+
for i, (tmp, keys) in enumerate(self.files, 1):
|
| 94 |
+
name = f"model-{i:05d}-of-{n:05d}.safetensors"
|
| 95 |
+
tmp.rename(self.out_dir / name)
|
| 96 |
+
hdr = read_header(self.out_dir / name)
|
| 97 |
+
for k, v in hdr.items():
|
| 98 |
+
if k != "__metadata__":
|
| 99 |
+
total += v["data_offsets"][1] - v["data_offsets"][0]
|
| 100 |
+
for k in keys:
|
| 101 |
+
weight_map[k] = name
|
| 102 |
+
index = {"metadata": {"total_size": total}, "weight_map": weight_map}
|
| 103 |
+
with open(self.out_dir / "model.safetensors.index.json", "w") as f:
|
| 104 |
+
json.dump(index, f, indent=2)
|
| 105 |
+
return weight_map
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def cleanup_tmp(model_dir):
|
| 109 |
+
for p in model_dir.glob(f"{TMP_PREFIX}*.safetensors"):
|
| 110 |
+
p.unlink()
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def main():
|
| 114 |
+
if len(sys.argv) != 2:
|
| 115 |
+
sys.exit(__doc__)
|
| 116 |
+
model_dir = Path(sys.argv[1]).resolve()
|
| 117 |
+
index_file = model_dir / "model.safetensors.index.json"
|
| 118 |
+
if not index_file.exists():
|
| 119 |
+
sys.exit(f"error: {index_file} not found")
|
| 120 |
+
cleanup_tmp(model_dir) # leftovers from an interrupted run
|
| 121 |
+
|
| 122 |
+
weight_map = json.load(open(index_file))["weight_map"]
|
| 123 |
+
expert_keys = [k for k in weight_map if EXPERT_RE.match(k)]
|
| 124 |
+
if not expert_keys:
|
| 125 |
+
print("no per-expert tensors found — model is already repaired")
|
| 126 |
+
return
|
| 127 |
+
|
| 128 |
+
# group per-expert keys by their stacked target
|
| 129 |
+
groups = {} # stacked_key -> {expert_idx: source_key}
|
| 130 |
+
for k in expert_keys:
|
| 131 |
+
m = EXPERT_RE.match(k)
|
| 132 |
+
stacked = f"{m['prefix']}.switch_mlp.{m['proj']}.{m['t']}"
|
| 133 |
+
groups.setdefault(stacked, {})[int(m["e"])] = k
|
| 134 |
+
n_experts = {len(v) for v in groups.values()}
|
| 135 |
+
if len(n_experts) != 1:
|
| 136 |
+
sys.exit(f"error: inconsistent expert counts per group: {sorted(n_experts)}")
|
| 137 |
+
n_experts = n_experts.pop()
|
| 138 |
+
key_to_stacked = {sk: stacked for stacked, exps in groups.items() for sk in exps.values()}
|
| 139 |
+
print(f"{len(expert_keys)} per-expert tensors -> {len(groups)} stacked tensors ({n_experts} experts)")
|
| 140 |
+
|
| 141 |
+
mx.set_default_device(mx.cpu)
|
| 142 |
+
old_shards = sorted({v for v in weight_map.values()})
|
| 143 |
+
src_metadata = read_header(model_dir / old_shards[0]).get("__metadata__") or {"format": "mlx"}
|
| 144 |
+
writer = ShardWriter(model_dir, src_metadata)
|
| 145 |
+
pending = {} # stacked_key -> {expert_idx: array}
|
| 146 |
+
|
| 147 |
+
try:
|
| 148 |
+
for i, shard in enumerate(old_shards, 1):
|
| 149 |
+
print(f"[{i}/{len(old_shards)}] {shard}")
|
| 150 |
+
tensors = mx.load(str(model_dir / shard))
|
| 151 |
+
for key, array in tensors.items():
|
| 152 |
+
stacked = key_to_stacked.get(key)
|
| 153 |
+
if stacked is None:
|
| 154 |
+
writer.add(key, array)
|
| 155 |
+
continue
|
| 156 |
+
e = int(EXPERT_RE.match(key)["e"])
|
| 157 |
+
pending.setdefault(stacked, {})[e] = array
|
| 158 |
+
if len(pending[stacked]) == n_experts:
|
| 159 |
+
parts = pending.pop(stacked)
|
| 160 |
+
assert sorted(parts) == list(range(n_experts)), f"non-contiguous experts for {stacked}"
|
| 161 |
+
writer.add(stacked, mx.stack([parts[j] for j in range(n_experts)]))
|
| 162 |
+
del tensors
|
| 163 |
+
if pending:
|
| 164 |
+
raise RuntimeError(f"incomplete expert groups: {list(pending)[:3]}")
|
| 165 |
+
writer.flush()
|
| 166 |
+
|
| 167 |
+
# verify before touching the originals
|
| 168 |
+
tmp_map = writer.tmp_weight_map()
|
| 169 |
+
expected = len(weight_map) - len(expert_keys) + len(groups)
|
| 170 |
+
if len(tmp_map) != expected:
|
| 171 |
+
raise RuntimeError(f"tensor count mismatch: {len(tmp_map)} != {expected}")
|
| 172 |
+
check = [(sk, e) for sk in list(groups)[::max(1, len(groups) // 4)] for e in (0, n_experts - 1)]
|
| 173 |
+
by_tmp = {}
|
| 174 |
+
for sk, e in check:
|
| 175 |
+
by_tmp.setdefault(tmp_map[sk], []).append((sk, e))
|
| 176 |
+
for tmp, items in by_tmp.items():
|
| 177 |
+
out_tensors = mx.load(str(tmp))
|
| 178 |
+
for sk, e in items:
|
| 179 |
+
src_key = groups[sk][e]
|
| 180 |
+
orig = mx.load(str(model_dir / weight_map[src_key]))[src_key]
|
| 181 |
+
if not mx.array_equal(out_tensors[sk][e], orig).item():
|
| 182 |
+
raise RuntimeError(f"bitwise mismatch at {sk}[{e}]")
|
| 183 |
+
del out_tensors
|
| 184 |
+
print(f"spot-check passed ({len(check)} slices)")
|
| 185 |
+
except Exception as e:
|
| 186 |
+
cleanup_tmp(model_dir)
|
| 187 |
+
sys.exit(f"error: {e} — original model left untouched")
|
| 188 |
+
|
| 189 |
+
# point of no return: swap repaired shards in, rewrite index and config
|
| 190 |
+
writer.commit(old_shards)
|
| 191 |
+
config_file = model_dir / "config.json"
|
| 192 |
+
config = json.load(open(config_file))
|
| 193 |
+
for section in ("quantization", "quantization_config"):
|
| 194 |
+
if isinstance(config.get(section), dict):
|
| 195 |
+
config[section] = {
|
| 196 |
+
(quant_key_to_module_path(k) if isinstance(v, dict) else k): v
|
| 197 |
+
for k, v in config[section].items()
|
| 198 |
+
}
|
| 199 |
+
with open(config_file, "w") as f:
|
| 200 |
+
json.dump(config, f, indent=2)
|
| 201 |
+
print(f"done: {model_dir} repaired in place")
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
if __name__ == "__main__":
|
| 205 |
+
main()
|