Text Generation
MLX
Safetensors
qwen3_5_moe
code
text-only
omlx
ornith
ornith-1.0
ornith-35B
MoE
conversational
4-bit precision
Instructions to use Noctalin/Ornith-1.0-35B-oQ4-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-oQ4-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-oQ4-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-oQ4-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-oQ4-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-oQ4-fp16" } ] } } }Run Pi
# Start Pi in your project directory: pi
- MLX LM
How to use Noctalin/Ornith-1.0-35B-oQ4-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-oQ4-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-oQ4-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-oQ4-fp16", "messages": [ {"role": "user", "content": "Hello"} ] }' - Hermes Agent
How to use Noctalin/Ornith-1.0-35B-oQ4-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-oQ4-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-oQ4-fp16
Run Hermes
hermes
- Atomic Chat
- OpenClaw
How to use Noctalin/Ornith-1.0-35B-oQ4-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-oQ4-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-oQ4-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"
| #!/usr/bin/env python3 | |
| """Repair oMLX-quantized Qwen3.5-MoE checkpoints that store routed experts | |
| in the legacy per-expert layout (mlp.experts.<E>.{gate,up,down}_proj.*). | |
| mlx-lm's qwen3_5_moe sanitize() only stacks the fused `experts.gate_up_proj` | |
| layout, so per-expert checkpoints fail to load with | |
| "Received NNNNN parameters not in model". This script repairs the model | |
| in place: | |
| 1. Stacks every `<prefix>.mlp.experts.<E>.<proj>.<tensor>` group along a new | |
| leading axis into `<prefix>.mlp.switch_mlp.<proj>.<tensor>`. | |
| 2. Rewrites per-path quantization overrides in config.json from raw HF key | |
| names (model.language_model.*) to post-sanitize module paths | |
| (language_model.model.*), which is how mlx-lm looks them up at load time. | |
| New shards are written alongside the originals, verified bitwise against the | |
| source tensors, and only then swapped in (originals deleted). A failure at any | |
| point leaves the original model untouched. Needs free disk roughly equal to | |
| the model size while running. | |
| Usage: | |
| python3 repair_moe_experts.py <model_dir> | |
| Requires mlx; no other dependencies. | |
| """ | |
| import json | |
| import re | |
| import struct | |
| import sys | |
| from pathlib import Path | |
| import mlx.core as mx | |
| EXPERT_RE = re.compile( | |
| r"^(?P<prefix>.+\.mlp)\.experts\.(?P<e>\d+)\.(?P<proj>\w+_proj)\.(?P<t>weight|scales|biases)$" | |
| ) | |
| SHARD_BYTES = 5 * 1024**3 | |
| TMP_PREFIX = "tmp-shard-" | |
| def quant_key_to_module_path(key): | |
| if key.startswith("model.language_model"): | |
| return key.replace("model.language_model", "language_model.model", 1) | |
| if key.startswith("language_model."): | |
| return key | |
| return "language_model." + key | |
| def read_header(path): | |
| with open(path, "rb") as f: | |
| n = struct.unpack("<Q", f.read(8))[0] | |
| return json.loads(f.read(n)) | |
| def nbytes(a): | |
| return a.size * a.dtype.size | |
| class ShardWriter: | |
| def __init__(self, out_dir, metadata): | |
| self.out_dir = out_dir | |
| self.metadata = metadata | |
| self.buffer = {} | |
| self.buffer_bytes = 0 | |
| self.files = [] # [(tmp_path, [keys])] | |
| def add(self, key, array): | |
| self.buffer[key] = array | |
| self.buffer_bytes += nbytes(array) | |
| if self.buffer_bytes >= SHARD_BYTES: | |
| self.flush() | |
| def flush(self): | |
| if not self.buffer: | |
| return | |
| tmp = self.out_dir / f"{TMP_PREFIX}{len(self.files):05d}.safetensors" | |
| mx.save_safetensors(str(tmp), self.buffer, metadata=self.metadata) | |
| mx.clear_cache() | |
| self.files.append((tmp, list(self.buffer))) | |
| self.buffer = {} | |
| self.buffer_bytes = 0 | |
| def tmp_weight_map(self): | |
| return {k: tmp for tmp, keys in self.files for k in keys} | |
| def commit(self, old_shards): | |
| """Delete the original shards and move tmp shards to final names.""" | |
| for shard in old_shards: | |
| (self.out_dir / shard).unlink() | |
| n = len(self.files) | |
| weight_map, total = {}, 0 | |
| for i, (tmp, keys) in enumerate(self.files, 1): | |
| name = f"model-{i:05d}-of-{n:05d}.safetensors" | |
| tmp.rename(self.out_dir / name) | |
| hdr = read_header(self.out_dir / name) | |
| for k, v in hdr.items(): | |
| if k != "__metadata__": | |
| total += v["data_offsets"][1] - v["data_offsets"][0] | |
| for k in keys: | |
| weight_map[k] = name | |
| index = {"metadata": {"total_size": total}, "weight_map": weight_map} | |
| with open(self.out_dir / "model.safetensors.index.json", "w") as f: | |
| json.dump(index, f, indent=2) | |
| return weight_map | |
| def cleanup_tmp(model_dir): | |
| for p in model_dir.glob(f"{TMP_PREFIX}*.safetensors"): | |
| p.unlink() | |
| def main(): | |
| if len(sys.argv) != 2: | |
| sys.exit(__doc__) | |
| model_dir = Path(sys.argv[1]).resolve() | |
| index_file = model_dir / "model.safetensors.index.json" | |
| if not index_file.exists(): | |
| sys.exit(f"error: {index_file} not found") | |
| cleanup_tmp(model_dir) # leftovers from an interrupted run | |
| weight_map = json.load(open(index_file))["weight_map"] | |
| expert_keys = [k for k in weight_map if EXPERT_RE.match(k)] | |
| if not expert_keys: | |
| print("no per-expert tensors found — model is already repaired") | |
| return | |
| # group per-expert keys by their stacked target | |
| groups = {} # stacked_key -> {expert_idx: source_key} | |
| for k in expert_keys: | |
| m = EXPERT_RE.match(k) | |
| stacked = f"{m['prefix']}.switch_mlp.{m['proj']}.{m['t']}" | |
| groups.setdefault(stacked, {})[int(m["e"])] = k | |
| n_experts = {len(v) for v in groups.values()} | |
| if len(n_experts) != 1: | |
| sys.exit(f"error: inconsistent expert counts per group: {sorted(n_experts)}") | |
| n_experts = n_experts.pop() | |
| key_to_stacked = {sk: stacked for stacked, exps in groups.items() for sk in exps.values()} | |
| print(f"{len(expert_keys)} per-expert tensors -> {len(groups)} stacked tensors ({n_experts} experts)") | |
| mx.set_default_device(mx.cpu) | |
| old_shards = sorted({v for v in weight_map.values()}) | |
| src_metadata = read_header(model_dir / old_shards[0]).get("__metadata__") or {"format": "mlx"} | |
| writer = ShardWriter(model_dir, src_metadata) | |
| pending = {} # stacked_key -> {expert_idx: array} | |
| try: | |
| for i, shard in enumerate(old_shards, 1): | |
| print(f"[{i}/{len(old_shards)}] {shard}") | |
| tensors = mx.load(str(model_dir / shard)) | |
| for key, array in tensors.items(): | |
| stacked = key_to_stacked.get(key) | |
| if stacked is None: | |
| writer.add(key, array) | |
| continue | |
| e = int(EXPERT_RE.match(key)["e"]) | |
| pending.setdefault(stacked, {})[e] = array | |
| if len(pending[stacked]) == n_experts: | |
| parts = pending.pop(stacked) | |
| assert sorted(parts) == list(range(n_experts)), f"non-contiguous experts for {stacked}" | |
| writer.add(stacked, mx.stack([parts[j] for j in range(n_experts)])) | |
| del tensors | |
| if pending: | |
| raise RuntimeError(f"incomplete expert groups: {list(pending)[:3]}") | |
| writer.flush() | |
| # verify before touching the originals | |
| tmp_map = writer.tmp_weight_map() | |
| expected = len(weight_map) - len(expert_keys) + len(groups) | |
| if len(tmp_map) != expected: | |
| raise RuntimeError(f"tensor count mismatch: {len(tmp_map)} != {expected}") | |
| check = [(sk, e) for sk in list(groups)[::max(1, len(groups) // 4)] for e in (0, n_experts - 1)] | |
| by_tmp = {} | |
| for sk, e in check: | |
| by_tmp.setdefault(tmp_map[sk], []).append((sk, e)) | |
| for tmp, items in by_tmp.items(): | |
| out_tensors = mx.load(str(tmp)) | |
| for sk, e in items: | |
| src_key = groups[sk][e] | |
| orig = mx.load(str(model_dir / weight_map[src_key]))[src_key] | |
| if not mx.array_equal(out_tensors[sk][e], orig).item(): | |
| raise RuntimeError(f"bitwise mismatch at {sk}[{e}]") | |
| del out_tensors | |
| print(f"spot-check passed ({len(check)} slices)") | |
| except Exception as e: | |
| cleanup_tmp(model_dir) | |
| sys.exit(f"error: {e} — original model left untouched") | |
| # point of no return: swap repaired shards in, rewrite index and config | |
| writer.commit(old_shards) | |
| config_file = model_dir / "config.json" | |
| config = json.load(open(config_file)) | |
| for section in ("quantization", "quantization_config"): | |
| if isinstance(config.get(section), dict): | |
| config[section] = { | |
| (quant_key_to_module_path(k) if isinstance(v, dict) else k): v | |
| for k, v in config[section].items() | |
| } | |
| with open(config_file, "w") as f: | |
| json.dump(config, f, indent=2) | |
| print(f"done: {model_dir} repaired in place") | |
| if __name__ == "__main__": | |
| main() | |