Instructions to use audreyt/Ornith-1.5-9B-DFlash-GGUF with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use audreyt/Ornith-1.5-9B-DFlash-GGUF with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf audreyt/Ornith-1.5-9B-DFlash-GGUF:Q4_K_M # Run inference directly in the terminal: llama cli -hf audreyt/Ornith-1.5-9B-DFlash-GGUF:Q4_K_M
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf audreyt/Ornith-1.5-9B-DFlash-GGUF:Q4_K_M # Run inference directly in the terminal: llama cli -hf audreyt/Ornith-1.5-9B-DFlash-GGUF:Q4_K_M
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf audreyt/Ornith-1.5-9B-DFlash-GGUF:Q4_K_M # Run inference directly in the terminal: ./llama-cli -hf audreyt/Ornith-1.5-9B-DFlash-GGUF:Q4_K_M
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf audreyt/Ornith-1.5-9B-DFlash-GGUF:Q4_K_M # Run inference directly in the terminal: ./build/bin/llama-cli -hf audreyt/Ornith-1.5-9B-DFlash-GGUF:Q4_K_M
Use Docker
docker model run hf.co/audreyt/Ornith-1.5-9B-DFlash-GGUF:Q4_K_M
- LM Studio
- Jan
- Ollama
How to use audreyt/Ornith-1.5-9B-DFlash-GGUF with Ollama:
ollama run hf.co/audreyt/Ornith-1.5-9B-DFlash-GGUF:Q4_K_M
- Unsloth Studio
How to use audreyt/Ornith-1.5-9B-DFlash-GGUF with Unsloth Studio:
Install Unsloth Studio (macOS, Linux, WSL)
curl -fsSL https://unsloth.ai/install.sh | sh # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for audreyt/Ornith-1.5-9B-DFlash-GGUF to start chatting
Install Unsloth Studio (Windows)
irm https://unsloth.ai/install.ps1 | iex # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for audreyt/Ornith-1.5-9B-DFlash-GGUF to start chatting
Using HuggingFace Spaces for Unsloth
# No setup required # Open https://huggingface.co/spaces/unsloth/studio in your browser # Search for audreyt/Ornith-1.5-9B-DFlash-GGUF to start chatting
- Pi
How to use audreyt/Ornith-1.5-9B-DFlash-GGUF with Pi:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf audreyt/Ornith-1.5-9B-DFlash-GGUF:Q4_K_M
Configure the model in Pi
# Install Pi: npm install -g @earendil-works/pi-coding-agent # Add to ~/.pi/agent/models.json: { "providers": { "llama-cpp": { "baseUrl": "http://localhost:8080/v1", "api": "openai-completions", "apiKey": "none", "models": [ { "id": "audreyt/Ornith-1.5-9B-DFlash-GGUF:Q4_K_M" } ] } } }Run Pi
# Start Pi in your project directory: pi
- Docker Model Runner
How to use audreyt/Ornith-1.5-9B-DFlash-GGUF with Docker Model Runner:
docker model run hf.co/audreyt/Ornith-1.5-9B-DFlash-GGUF:Q4_K_M
- Lemonade
How to use audreyt/Ornith-1.5-9B-DFlash-GGUF with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull audreyt/Ornith-1.5-9B-DFlash-GGUF:Q4_K_M
Run and chat with the model
lemonade run user.Ornith-1.5-9B-DFlash-GGUF-Q4_K_M
List all available models
lemonade list
- Hermes Agent
How to use audreyt/Ornith-1.5-9B-DFlash-GGUF with Hermes Agent:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf audreyt/Ornith-1.5-9B-DFlash-GGUF:Q4_K_M
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 audreyt/Ornith-1.5-9B-DFlash-GGUF:Q4_K_M
Run Hermes
hermes
- Atomic Chat
- OpenClaw
How to use audreyt/Ornith-1.5-9B-DFlash-GGUF with OpenClaw:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf audreyt/Ornith-1.5-9B-DFlash-GGUF:Q4_K_M
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 "audreyt/Ornith-1.5-9B-DFlash-GGUF:Q4_K_M" \ --custom-provider-id llama-cpp \ --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 | |
| """Distill a classic Qwen3.5 DFlash draft against Ornith with MLX. | |
| The pipeline creates target-generated responses, caches frozen Ornith hidden | |
| features, then fine-tunes the draft with the block objective from the DFlash | |
| paper. Target embeddings and the target LM head remain frozen. | |
| """ | |
| import argparse | |
| import json | |
| import math | |
| import random | |
| import shutil | |
| import time | |
| from pathlib import Path | |
| import mlx.core as mx | |
| from mlx import nn | |
| import mlx.optimizers as optim | |
| from mlx.utils import tree_flatten | |
| from datasets import load_dataset | |
| from huggingface_hub import snapshot_download | |
| from mlx_lm import generate, load as load_target | |
| from mlx_lm.models.cache import make_prompt_cache | |
| from mlx_lm.sample_utils import make_sampler | |
| from dflash.model_mlx import _patch_model, load_draft | |
| def parse_args(): | |
| parser = argparse.ArgumentParser() | |
| sub = parser.add_subparsers(dest="command", required=True) | |
| prepare = sub.add_parser("prepare") | |
| prepare.add_argument("--target", default="ornith-ai/Ornith-1.5-9B-MLX-4bit") | |
| prepare.add_argument("--draft", default="z-lab/Qwen3.5-9B-DFlash") | |
| prepare.add_argument("--output", type=Path, required=True) | |
| prepare.add_argument("--samples", type=int, default=128) | |
| prepare.add_argument("--eval-samples", type=int, default=16) | |
| prepare.add_argument("--max-new-tokens", type=int, default=192) | |
| prepare.add_argument("--max-sequence-tokens", type=int, default=384) | |
| prepare.add_argument("--dataset", default="tatsu-lab/alpaca") | |
| prepare.add_argument("--seed", type=int, default=20260821) | |
| recache = sub.add_parser("recache") | |
| recache.add_argument("--target", default="ornith-ai/Ornith-1.5-9B") | |
| recache.add_argument("--draft", default="z-lab/Qwen3.5-9B-DFlash") | |
| recache.add_argument("--data", type=Path, required=True) | |
| train = sub.add_parser("train") | |
| train.add_argument("--target", default="ornith-ai/Ornith-1.5-9B-MLX-4bit") | |
| train.add_argument("--draft", default="z-lab/Qwen3.5-9B-DFlash") | |
| train.add_argument("--data", type=Path, required=True) | |
| train.add_argument("--output", type=Path, required=True) | |
| train.add_argument("--steps", type=int, default=768) | |
| train.add_argument("--block-size", type=int, default=8) | |
| train.add_argument("--learning-rate", type=float, default=2e-5) | |
| train.add_argument("--weight-decay", type=float, default=0.01) | |
| train.add_argument("--warmup-ratio", type=float, default=0.04) | |
| train.add_argument("--clip-grad", type=float, default=1.0) | |
| train.add_argument("--loss-gamma", type=float, default=4.0) | |
| train.add_argument("--eval-every", type=int, default=96) | |
| train.add_argument("--save-every", type=int, default=192) | |
| train.add_argument( | |
| "--train-scope", choices=("all", "projection"), default="all" | |
| ) | |
| train.add_argument("--seed", type=int, default=20260821) | |
| evaluate = sub.add_parser("evaluate") | |
| evaluate.add_argument("--target", default="ornith-ai/Ornith-1.5-9B-MLX-4bit") | |
| evaluate.add_argument("--draft", required=True) | |
| evaluate.add_argument("--data", type=Path, required=True) | |
| evaluate.add_argument("--block-size", type=int, default=8) | |
| evaluate.add_argument("--anchors", type=int, default=64) | |
| evaluate.add_argument("--seed", type=int, default=20260821) | |
| return parser.parse_args() | |
| def dataset_prompts(name, limit): | |
| dataset = load_dataset(name, split="train", streaming=True) | |
| seen = set() | |
| for row in dataset: | |
| instruction = str(row.get("instruction") or row.get("prompt") or "").strip() | |
| extra = str(row.get("input") or "").strip() | |
| if not instruction or instruction in seen: | |
| continue | |
| seen.add(instruction) | |
| if extra: | |
| instruction = f"{instruction}\n\nInput:\n{extra}" | |
| yield instruction | |
| if len(seen) >= limit: | |
| return | |
| def token_list(value): | |
| if hasattr(value, "tolist"): | |
| value = value.tolist() | |
| if value and isinstance(value[0], list): | |
| value = value[0] | |
| return [int(token) for token in value] | |
| def target_features(model, tokens, layer_ids): | |
| cache = make_prompt_cache(model) | |
| model(mx.array(tokens, dtype=mx.int32)[None], cache) | |
| hidden = mx.concatenate(model._hidden_states, axis=-1)[0] | |
| hidden = mx.stop_gradient(hidden.astype(mx.bfloat16)) | |
| mx.eval(hidden) | |
| return hidden | |
| def prepare_data(args): | |
| random.seed(args.seed) | |
| mx.random.seed(args.seed) | |
| args.output.mkdir(parents=True, exist_ok=True) | |
| samples_dir = args.output / "samples" | |
| samples_dir.mkdir(exist_ok=True) | |
| model, tokenizer = load_target(args.target) | |
| draft = load_draft(args.draft) | |
| _patch_model(model, draft.config.target_layer_ids) | |
| model.eval() | |
| sampler = make_sampler(temp=0.6, top_p=0.95, top_k=20) | |
| total = args.samples + args.eval_samples | |
| manifest = [] | |
| started = time.perf_counter() | |
| for index, instruction in enumerate(dataset_prompts(args.dataset, total)): | |
| messages = [{"role": "user", "content": instruction}] | |
| prompt_ids = token_list(tokenizer.apply_chat_template( | |
| messages, tokenize=True, add_generation_prompt=True | |
| )) | |
| response = generate( | |
| model, | |
| tokenizer, | |
| prompt=prompt_ids, | |
| max_tokens=args.max_new_tokens, | |
| sampler=sampler, | |
| verbose=False, | |
| ) | |
| response_ids = token_list(tokenizer.encode(response, add_special_tokens=False)) | |
| tokens = (prompt_ids + response_ids)[:args.max_sequence_tokens] | |
| if len(tokens) < len(prompt_ids) + 9: | |
| continue | |
| hidden = target_features(model, tokens, draft.config.target_layer_ids) | |
| sample_name = f"{len(manifest):05d}.safetensors" | |
| mx.save_safetensors(str(samples_dir / sample_name), { | |
| "tokens": mx.array(tokens, dtype=mx.int32), | |
| "hidden": hidden, | |
| "prompt_length": mx.array([len(prompt_ids)], dtype=mx.int32), | |
| }) | |
| split = "eval" if len(manifest) < args.eval_samples else "train" | |
| manifest.append({ | |
| "file": sample_name, | |
| "split": split, | |
| "instruction": instruction, | |
| "response": response, | |
| "tokens": len(tokens), | |
| "prompt_tokens": len(prompt_ids), | |
| }) | |
| elapsed = time.perf_counter() - started | |
| print(f"[{len(manifest):3d}/{total}] {split:5s} tokens={len(tokens):3d} " | |
| f"elapsed={elapsed / 60:.1f}m", flush=True) | |
| mx.clear_cache() | |
| if len(manifest) >= total: | |
| break | |
| if len(manifest) < total: | |
| raise RuntimeError(f"prepared {len(manifest)} samples, expected {total}") | |
| with (args.output / "manifest.jsonl").open("w", encoding="utf-8") as out: | |
| for row in manifest: | |
| out.write(json.dumps(row, ensure_ascii=False) + "\n") | |
| metadata = { | |
| "target": args.target, | |
| "draft": args.draft, | |
| "dataset": args.dataset, | |
| "seed": args.seed, | |
| "samples": args.samples, | |
| "eval_samples": args.eval_samples, | |
| } | |
| (args.output / "config.json").write_text( | |
| json.dumps(metadata, indent=2) + "\n" | |
| ) | |
| def recache_features(args): | |
| model, _ = load_target(args.target) | |
| draft = load_draft(args.draft) | |
| _patch_model(model, draft.config.target_layer_ids) | |
| model.eval() | |
| rows = read_manifest(args.data, "eval") + read_manifest(args.data, "train") | |
| started = time.perf_counter() | |
| for index, row in enumerate(rows, 1): | |
| path = args.data / "samples" / row["file"] | |
| sample = mx.load(str(path)) | |
| tokens = mx.array(sample["tokens"]) | |
| prompt_length = mx.array(sample["prompt_length"]) | |
| mx.eval(tokens, prompt_length) | |
| hidden = target_features( | |
| model, tokens.tolist(), draft.config.target_layer_ids | |
| ) | |
| temporary = path.with_suffix(".tmp.safetensors") | |
| mx.save_safetensors(str(temporary), { | |
| "tokens": tokens, | |
| "hidden": hidden, | |
| "prompt_length": prompt_length, | |
| }) | |
| temporary.replace(path) | |
| print(f"[{index:3d}/{len(rows)}] {row['file']} " | |
| f"elapsed={(time.perf_counter() - started) / 60:.1f}m", | |
| flush=True) | |
| mx.clear_cache() | |
| def read_manifest(data_dir, split): | |
| rows = [] | |
| with (data_dir / "manifest.jsonl").open(encoding="utf-8") as src: | |
| for line in src: | |
| row = json.loads(line) | |
| if row["split"] == split: | |
| rows.append(row) | |
| if not rows: | |
| raise RuntimeError(f"no {split} samples in {data_dir}") | |
| return rows | |
| def load_sample(data_dir, row): | |
| return mx.load(str(data_dir / "samples" / row["file"])) | |
| def choose_anchor(sample, block_size, rng): | |
| tokens = sample["tokens"] | |
| prompt_length = int(sample["prompt_length"][0].item()) | |
| last = int(tokens.shape[0]) - block_size | |
| if last < prompt_length: | |
| raise RuntimeError("sample response is shorter than the draft block") | |
| return rng.randint(prompt_length, last) | |
| def bind_and_freeze(draft, target, scope="all"): | |
| draft.bind(target) | |
| draft.freeze() | |
| draft.fc.unfreeze() | |
| draft.hidden_norm.unfreeze() | |
| if scope == "all": | |
| for layer in draft.layers: | |
| layer.unfreeze() | |
| draft.norm.unfreeze() | |
| draft.train() | |
| target.eval() | |
| def loss_fn(draft, sample, anchor, block_size, loss_gamma): | |
| tokens = sample["tokens"] | |
| target_hidden = sample["hidden"][None, :anchor] | |
| draft_input = mx.concatenate(( | |
| tokens[anchor:anchor + 1], | |
| mx.full((block_size - 1,), draft.config.mask_token_id, dtype=mx.int32), | |
| ))[None] | |
| labels = tokens[anchor + 1:anchor + block_size][None] | |
| logits = draft(draft_input, target_hidden, draft.make_cache(), logits_start=1) | |
| losses = nn.losses.cross_entropy(logits, labels, reduction="none") | |
| positions = mx.arange(block_size - 1, dtype=mx.float32) | |
| weights = mx.exp(-positions / loss_gamma) | |
| return mx.sum(losses * weights[None]) / mx.sum(weights) | |
| def acceptance_for_sample(draft, sample, anchor, block_size): | |
| tokens = sample["tokens"] | |
| target_hidden = sample["hidden"][None, :anchor] | |
| draft_input = mx.concatenate(( | |
| tokens[anchor:anchor + 1], | |
| mx.full((block_size - 1,), draft.config.mask_token_id, dtype=mx.int32), | |
| ))[None] | |
| logits = draft(draft_input, target_hidden, draft.make_cache(), logits_start=1) | |
| predictions = mx.argmax(logits, axis=-1)[0] | |
| expected = tokens[anchor + 1:anchor + block_size] | |
| matches = mx.equal(predictions, expected).tolist() | |
| accepted = 0 | |
| for match in matches: | |
| if not match: | |
| break | |
| accepted += 1 | |
| return accepted | |
| def evaluate_draft(draft, data_dir, rows, block_size, anchors, seed): | |
| rng = random.Random(seed) | |
| draft.eval() | |
| accepted = [] | |
| exact = 0 | |
| for index in range(anchors): | |
| row = rows[index % len(rows)] | |
| sample = load_sample(data_dir, row) | |
| anchor = choose_anchor(sample, block_size, rng) | |
| count = acceptance_for_sample(draft, sample, anchor, block_size) | |
| accepted.append(count) | |
| exact += count == block_size - 1 | |
| if index % 8 == 7: | |
| mx.clear_cache() | |
| draft.train() | |
| mean = sum(accepted) / len(accepted) | |
| return { | |
| "anchors": len(accepted), | |
| "mean_accepted_drafts": mean, | |
| "mean_cycle_tokens": mean + 1.0, | |
| "full_blocks": exact, | |
| "full_block_rate": exact / len(accepted), | |
| } | |
| def draft_source_path(draft_id): | |
| return Path(snapshot_download(draft_id, allow_patterns=["*.safetensors", "*.json"])) | |
| def save_draft(draft, draft_id, output, metrics, training): | |
| output.mkdir(parents=True, exist_ok=True) | |
| source = draft_source_path(draft_id) | |
| original = { | |
| key | |
| for file in source.glob("*.safetensors") | |
| for key in mx.load(str(file)).keys() | |
| } | |
| parameters = dict(tree_flatten(draft.parameters())) | |
| missing = sorted(original - parameters.keys()) | |
| if missing: | |
| raise RuntimeError(f"trained model is missing original weights: {missing}") | |
| weights = {key: parameters[key] for key in sorted(original)} | |
| mx.eval(weights) | |
| mx.save_safetensors(str(output / "model.safetensors"), weights) | |
| shutil.copy2(source / "config.json", output / "config.json") | |
| (output / "metrics.json").write_text(json.dumps(metrics, indent=2) + "\n") | |
| (output / "training.json").write_text(json.dumps(training, indent=2) + "\n") | |
| def learning_rate(step, total, peak, warmup_ratio): | |
| warmup = max(1, round(total * warmup_ratio)) | |
| if step < warmup: | |
| return peak * (step + 1) / warmup | |
| progress = (step - warmup) / max(1, total - warmup) | |
| return peak * 0.5 * (1.0 + math.cos(math.pi * progress)) | |
| def train_draft(args): | |
| rng = random.Random(args.seed) | |
| mx.random.seed(args.seed) | |
| train_rows = read_manifest(args.data, "train") | |
| eval_rows = read_manifest(args.data, "eval") | |
| target, _ = load_target(args.target) | |
| draft = load_draft(args.draft) | |
| bind_and_freeze(draft, target, args.train_scope) | |
| optimizer = optim.AdamW( | |
| learning_rate=args.learning_rate, | |
| weight_decay=args.weight_decay, | |
| ) | |
| loss_and_grad = nn.value_and_grad(draft, loss_fn) | |
| baseline = evaluate_draft( | |
| draft, args.data, eval_rows, args.block_size, | |
| min(64, len(eval_rows) * 4), args.seed, | |
| ) | |
| print(f"baseline {json.dumps(baseline, sort_keys=True)}", flush=True) | |
| best = baseline | |
| started = time.perf_counter() | |
| for step in range(args.steps): | |
| row = train_rows[step % len(train_rows)] | |
| if step and step % len(train_rows) == 0: | |
| rng.shuffle(train_rows) | |
| sample = load_sample(args.data, row) | |
| anchor = choose_anchor(sample, args.block_size, rng) | |
| rate = learning_rate(step, args.steps, args.learning_rate, args.warmup_ratio) | |
| optimizer.learning_rate = mx.array(rate) | |
| loss, grads = loss_and_grad( | |
| draft, sample, anchor, args.block_size, args.loss_gamma | |
| ) | |
| grads, grad_norm = optim.clip_grad_norm(grads, args.clip_grad) | |
| optimizer.update(draft, grads) | |
| mx.eval(loss, grad_norm, draft.parameters(), optimizer.state) | |
| if step == 0 or (step + 1) % 8 == 0: | |
| elapsed = time.perf_counter() - started | |
| print(f"step={step + 1}/{args.steps} loss={loss.item():.5f} " | |
| f"grad={grad_norm.item():.3f} lr={rate:.3e} " | |
| f"steps_s={(step + 1) / elapsed:.3f}", flush=True) | |
| if (step + 1) % args.eval_every == 0 or step + 1 == args.steps: | |
| metrics = evaluate_draft( | |
| draft, args.data, eval_rows, args.block_size, | |
| min(64, len(eval_rows) * 4), args.seed, | |
| ) | |
| print(f"eval step={step + 1} {json.dumps(metrics, sort_keys=True)}", flush=True) | |
| if metrics["mean_accepted_drafts"] > best["mean_accepted_drafts"]: | |
| best = metrics | |
| save_draft(draft, args.draft, args.output / "best", metrics, json_safe_args(args)) | |
| if (step + 1) % args.save_every == 0: | |
| save_draft( | |
| draft, args.draft, args.output / f"step-{step + 1:06d}", | |
| metrics if "metrics" in locals() else baseline, json_safe_args(args), | |
| ) | |
| mx.clear_cache() | |
| final_metrics = evaluate_draft( | |
| draft, args.data, eval_rows, args.block_size, | |
| min(64, len(eval_rows) * 4), args.seed, | |
| ) | |
| save_draft(draft, args.draft, args.output / "final", final_metrics, json_safe_args(args)) | |
| summary = {"baseline": baseline, "best": best, "final": final_metrics} | |
| (args.output / "summary.json").write_text(json.dumps(summary, indent=2) + "\n") | |
| print(json.dumps(summary, indent=2), flush=True) | |
| def evaluate_command(args): | |
| rows = read_manifest(args.data, "eval") | |
| target, _ = load_target(args.target) | |
| draft = load_draft(args.draft) | |
| bind_and_freeze(draft, target) | |
| metrics = evaluate_draft( | |
| draft, args.data, rows, args.block_size, args.anchors, args.seed | |
| ) | |
| print(json.dumps(metrics, indent=2)) | |
| def json_safe_args(args): | |
| values = vars(args).copy() | |
| for key, value in values.items(): | |
| if isinstance(value, Path): | |
| values[key] = str(value) | |
| return values | |
| def main(): | |
| args = parse_args() | |
| if args.command == "prepare": | |
| prepare_data(args) | |
| elif args.command == "train": | |
| train_draft(args) | |
| elif args.command == "recache": | |
| recache_features(args) | |
| else: | |
| evaluate_command(args) | |
| if __name__ == "__main__": | |
| main() | |