Safetensors
GGUF
qwen3_5_moe
qwen4_exp
mixture-of-experts
hyper-connections
per-layer-embeddings
n-gram-memory
model-compression
research
conversational
Instructions to use logic65/whittle-next 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 logic65/whittle-next 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 logic65/whittle-next:F16 # Run inference directly in the terminal: llama cli -hf logic65/whittle-next:F16
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf logic65/whittle-next:F16 # Run inference directly in the terminal: llama cli -hf logic65/whittle-next:F16
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 logic65/whittle-next:F16 # Run inference directly in the terminal: ./llama-cli -hf logic65/whittle-next:F16
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 logic65/whittle-next:F16 # Run inference directly in the terminal: ./build/bin/llama-cli -hf logic65/whittle-next:F16
Use Docker
docker model run hf.co/logic65/whittle-next:F16
- LM Studio
- Jan
- Ollama
How to use logic65/whittle-next with Ollama:
ollama run hf.co/logic65/whittle-next:F16
- Unsloth Desktop
- Pi
How to use logic65/whittle-next with Pi:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf logic65/whittle-next:F16
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": "logic65/whittle-next:F16" } ] } } }Run Pi
# Start Pi in your project directory: pi
- Docker Model Runner
How to use logic65/whittle-next with Docker Model Runner:
docker model run hf.co/logic65/whittle-next:F16
- Lemonade
How to use logic65/whittle-next with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull logic65/whittle-next:F16
Run and chat with the model
lemonade run user.whittle-next-F16
List all available models
lemonade list
- Hermes Agent
How to use logic65/whittle-next with Hermes Agent:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf logic65/whittle-next:F16
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 logic65/whittle-next:F16
Run Hermes
hermes
- Atomic Chat
- OpenClaw
How to use logic65/whittle-next with OpenClaw:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf logic65/whittle-next:F16
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 "logic65/whittle-next:F16" \ --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 | |
| """mini-next: our mini implementation of the Flash-Next recipe on Whittle-16B. | |
| Composition (each piece independently verifiable): | |
| 1. MoE FFN - carved 192-shared + 67x192 experts (see carve.py, CARVE_GATE) | |
| 2. Hyper-connections - n residual streams with learned mixing, wrapped AROUND | |
| unmodified HF decoder layers via hooks. The layer computes out = h0 + T(h0) | |
| internally, so T(h0) = out - h0, and the HC update is | |
| H_i <- sum_j Ar[i,j] H_j + B_i * T(h0), h0 = sum_i Am[k,i] H_i. | |
| Eq-14 identity init (HC paper, ICLR 2025): Am = e_{k mod n}, Ar = I, B = 1 | |
| -> all streams stay equal to the standard residual, and the final row-sum's | |
| factor n cancels in the scale-invariant RMSNorm => logits IDENTICAL (HC_GATE). | |
| 3. mHC constraint - Ar is parameterised through Sinkhorn-Knopp so residual | |
| mixing is doubly stochastic (can average, never amplify; the 3000x | |
| divergence fix from mHC). | |
| 4. PLE - n-gram table with per-layer gated injection (train_ple.py checkpoint). | |
| """ | |
| import json, math | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| def sinkhorn(logits, iters=8): | |
| """Project exp(logits) onto (approx) doubly-stochastic via Sinkhorn-Knopp.""" | |
| M = torch.exp(logits - logits.max()) | |
| for _ in range(iters): | |
| M = M / (M.sum(-1, keepdim=True) + 1e-9) | |
| M = M / (M.sum(-2, keepdim=True) + 1e-9) | |
| return M | |
| class HCState: | |
| def __init__(self): self.H = None | |
| def reset(self): self.H = None | |
| class HyperConnections(nn.Module): | |
| """Static-matrix hyper-connections for L layers, expansion n (mHC-constrained).""" | |
| def __init__(self, n_layers, n=2, sinkhorn_iters=8): | |
| super().__init__() | |
| self.n, self.L, self.si = n, n_layers, sinkhorn_iters | |
| # Eq-14 identity init | |
| am = torch.zeros(n_layers, n) | |
| for k in range(n_layers): am[k, k % n] = 1.0 | |
| self.Am_logit = nn.Parameter(torch.log(am + 1e-4)) # softmax -> ~e_{k mod n} | |
| eye = torch.eye(n).unsqueeze(0).repeat(n_layers, 1, 1) | |
| self.Ar_logit = nn.Parameter(torch.log(eye * 8.0 + 1.0)) # sinkhorn(exp) ~= I | |
| self.B = nn.Parameter(torch.ones(n_layers, n)) # write weights | |
| # Eq-14 init taken LITERALLY: while in identity mode, read/write use | |
| # exact index/add paths (zero arithmetic). The soft softmax/Sinkhorn | |
| # parameterisation is only engaged when training starts - in bf16 the | |
| # soft mix injects ~2^-8 error per layer and compounds to ~0.6 rel over | |
| # 44 layers, which is noise, not signal. | |
| self.identity_mode = True | |
| def release_identity(self): | |
| self.identity_mode = False | |
| def Am(self, k): return F.softmax(self.Am_logit[k], -1) # non-neg, sums to 1 | |
| def Ar(self, k): return sinkhorn(self.Ar_logit[k], self.si) # doubly stochastic | |
| def read(self, k, H): | |
| if self.identity_mode: | |
| return H[k % self.n] | |
| # mixing coefficients are tiny (n, n^2); follow the streams' device - | |
| # layers span GPU boundaries under device_map. | |
| a = self.Am(k).to(dtype=H[0].dtype, device=H[0].device) | |
| return sum(a[i] * H[i] for i in range(self.n)) | |
| def write(self, k, H, T_out): | |
| if self.identity_mode: | |
| return [H[i].to(T_out.device) + T_out for i in range(self.n)] | |
| R = self.Ar(k).to(dtype=H[0].dtype, device=H[0].device) | |
| b = self.B[k].to(dtype=H[0].dtype, device=H[0].device) | |
| return [sum(R[i, j] * H[j] for j in range(self.n)) + b[i] * T_out | |
| for i in range(self.n)] | |
| def attach_hc(model, n=2): | |
| """Wrap every decoder layer of a HF qwen3_5(_moe) model in hyper-connections.""" | |
| layers = model.model.layers | |
| hc = HyperConnections(len(layers), n=n) | |
| dev = next(layers[0].parameters()).device | |
| hc.to(dev).to(next(model.parameters()).dtype) | |
| st = HCState() | |
| inbuf = {} | |
| def mk_pre(k): | |
| def pre(mod, args, kwargs): | |
| h = kwargs.get("hidden_states", args[0] if args else None) | |
| if k == 0 or st.H is None: | |
| st.H = [h.clone() for _ in range(hc.n)] | |
| h0 = hc.read(k, [x.to(h.device) for x in st.H]) | |
| inbuf[k] = h0 | |
| if "hidden_states" in kwargs: | |
| kwargs["hidden_states"] = h0; return (args, kwargs) | |
| return ((h0,) + tuple(args[1:]), kwargs) | |
| return pre | |
| def mk_post(k, last): | |
| def post(mod, args, kwargs, out): | |
| o = out[0] if isinstance(out, tuple) else out | |
| if hc.identity_mode: | |
| # Eq-14 identity, taken to its bit-exact conclusion: with B=1, | |
| # Ar=I and equal streams, H_i <- H_i + (o - h0) == o. Assign | |
| # directly - zero extra arithmetic, so the wrapped model IS the | |
| # base model, bitwise. (T = o - h0 re-add costs one extra bf16 | |
| # rounding per layer and flipped 5% of top-1s by layer 44.) | |
| inbuf.pop(k, None) | |
| st.H = [o for _ in range(hc.n)] | |
| new = o | |
| if last: st.reset() | |
| if isinstance(out, tuple): return (new,) + tuple(out[1:]) | |
| return new | |
| T = o - inbuf.pop(k).to(o.device) # layer may span a GPU boundary | |
| st.H = hc.write(k, [x.to(o.device) for x in st.H], T) | |
| new = sum(st.H) if last else st.H[0] | |
| # note: what we return only matters for the LAST layer (final norm | |
| # consumes it); intermediate layers are re-mixed by the next pre-hook. | |
| if last: st.reset() | |
| if isinstance(out, tuple): return (new,) + tuple(out[1:]) | |
| return new | |
| return post | |
| hs = [] | |
| for k, layer in enumerate(layers): | |
| hs.append(layer.register_forward_pre_hook(mk_pre(k), with_kwargs=True)) | |
| hs.append(layer.register_forward_hook(mk_post(k, k == len(layers) - 1), with_kwargs=True)) | |
| model._hc = hc | |
| model._hc_hooks = hs | |
| return hc | |
| def detach_hc(model): | |
| for h in getattr(model, "_hc_hooks", []): h.remove() | |
| model._hc_hooks = [] | |