A 12B-parameter Mixture-of-Experts model that fuses
Qwen3-4B
(host) with
260 coding experts
extracted from
DeepSeek-V4-Flash
, fine-tuned with QLoRA and optimized for local inference on consumer GPUs (RTX 3060 12GB).
The model has all runtime fixes
baked into the model code
(
fuse2_model.py
):
SwiGLU clamping (limit=10.0) — built into
SwiGLUExpert.forward
Router stability (softplus clamp) — built into
Fuse2Router.forward
Meta param auto-init + norm dtype fix — handled by
from_pretrained
override
So you can load and generate with zero post-load patching:
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
MODEL = "Akahsizrr/Mini-Whale-1-12B"# 4-bit NF4 quantization (matches config.json quantization_config)
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16,
)
# Load model — runtime fixes applied automatically by from_pretrained override
model = AutoModelForCausalLM.from_pretrained(
MODEL,
trust_remote_code=True,
quantization_config=bnb_config,
device_map="auto",
attn_implementation="sdpa",
)
model.eval()
# Tokenizer
tok = AutoTokenizer.from_pretrained(MODEL, trust_remote_code=True)
# Generate
messages = [{"role": "user", "content": "Write a Python fizzbuzz."}]
text = tok.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
input_ids = tok(text, return_tensors="pt").input_ids.to(model.device)
with torch.inference_mode():
out = model.generate(input_ids, max_new_tokens=512, do_sample=False,
repetition_penalty=1.3)
print(tok.decode(out[0], skip_special_tokens=True))
That's it — no manual SwiGLU patching, no meta device fixes, no norm dtype casting. The model code handles all of it.
Quick Start: Manual 4-bit Loading (~5 tok/s)
For more control over the loading process (e.g., selective quantization of attention vs MLP layers):
import torch, json, os, sys, gc
from transformers import AutoConfig, AutoTokenizer
from accelerate import init_empty_weights
from safetensors import safe_open
import bitsandbytes as bnb
MODEL_PATH = "./mini-whale-1-12b"
DEVICE = "cuda:0"
sys.path.insert(0, MODEL_PATH)
import fuse2_model_local
# Load config with SDPA attention
config = AutoConfig.from_pretrained(MODEL_PATH, trust_remote_code=True)
config._attn_implementation = "sdpa"# Create model on meta device (saves RAM)with init_empty_weights():
model = fuse2_model_local.Fuse2ForCausalLM(config)
# Load weights with 4-bit quantization
quant_suffixes = ("q_proj.weight", "k_proj.weight", "v_proj.weight", "o_proj.weight",
"gate_proj.weight", "up_proj.weight", "down_proj.weight")
withopen(f"{MODEL_PATH}/model.safetensors.index.json") as f:
index = json.load(f)
weight_map = index["weight_map"]
defnav(model, key):
parts = key.split(".")
obj = model
for p in parts[:-1]:
obj = obj[int(p)] if p.isdigit() elsegetattr(obj, p)
return obj, parts[-1]
deffind_linear(model, key):
parts = key.split(".")
obj = model
for p in parts[:-2]:
obj = obj[int(p)] if p.isdigit() elsegetattr(obj, p)
return obj, parts[-2]
param_names = set(dict(model.named_parameters()).keys())
replaced = {}
for shard_name insorted(set(weight_map.values())):
with safe_open(os.path.join(MODEL_PATH, shard_name), framework="pt", device="cpu") as f:
for key in [k for k, v in weight_map.items() if v == shard_name]:
if key notin param_names:
continue
tensor = f.get_tensor(key)
ifany(key.endswith(s) for s in quant_suffixes):
owner, attr = find_linear(model, key)
old = getattr(owner, attr)
new = bnb.nn.Linear4bit(old.in_features, old.out_features,
bias=False, quant_type="nf4",
compute_dtype=torch.bfloat16, device=DEVICE)
new.weight = bnb.nn.Params4bit(tensor.to(torch.bfloat16),
requires_grad=False, quant_type="nf4").cuda(0)
setattr(owner, attr, new)
else:
parent, pname = nav(model, key)
parent._parameters[pname] = torch.nn.Parameter(
tensor.to(torch.bfloat16).to(DEVICE), requires_grad=False)
del tensor
gc.collect(); torch.cuda.empty_cache()
# Fix tied embeddingsif model.lm_head.weight.device.type == 'meta':
model.lm_head.weight = torch.nn.Parameter(
model.model.embed_tokens.weight.data.clone(), requires_grad=False)
# Apply runtime fixes (SwiGLU clamp + router stability)from fuse2_model_local import Fuse2AugmentedLayer
import torch.nn.functional as F
for layer in model.model.layers:
ifnotisinstance(layer, Fuse2AugmentedLayer):
continueifhasattr(layer, 'coding_gate') and layer.coding_gate.device.type == 'meta':
layer.coding_gate = torch.nn.Parameter(torch.tensor(-2.0, device=DEVICE))
ifhasattr(layer, 'coding_norm') and layer.coding_norm.weight.device.type == 'meta':
layer.coding_norm = torch.nn.RMSNorm(
layer.coding_norm.weight.shape[0], eps=1e-6).to(DEVICE)
experts = getattr(layer, "experts", None)
if experts:
for expert in experts:
gp, up, dp = expert.gate_proj, expert.up_proj, expert.down_proj
defmake_fwd(g, u, d, lim=10.0):
defforward(x):
return d(torch.clamp(F.silu(g(x)) * u(x), -lim, lim))
return forward
expert.forward = make_fwd(gp, up, dp)
router = getattr(layer, "router", None)
if router:
gate, top_k = router.gate, router.top_k
defmake_router(g, tk):
defforward(h):
logits = g(h)
scores = torch.clamp(F.softplus(logits), min=1e-6).sqrt()
w, idx = scores.topk(tk, dim=-1)
return w / (w.sum(dim=-1, keepdim=True) + 1e-8), idx, logits
return forward
router.forward = make_router(gate, top_k)
model.set_coding_enabled(True)
model.to(DEVICE)
model.eval()
# Fix norm dtypes (float32 → bfloat16 for fused kernels)for module in model.modules():
ifhasattr(module, 'weight') andhasattr(module, 'eps'):
if module.weight.dtype == torch.float32:
module.weight.data = module.weight.data.to(torch.bfloat16)
# Tokenizer
tok = AutoTokenizer.from_pretrained(MODEL_PATH, trust_remote_code=True)
# Generate
messages = [{"role": "user", "content": "Write a Python fizzbuzz."}]
text = tok.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
input_ids = tok(text, return_tensors="pt").input_ids.to(DEVICE)
with torch.inference_mode():
out = model.generate(input_ids, max_new_tokens=512, do_sample=False,
repetition_penalty=1.3)
print(tok.decode(out[0], skip_special_tokens=True))
DSpark Speculative Decoding (~10 tok/s)
For 2x speedup, use DSpark speculative decoding. This requires the drafter model:
# The drafter is included in the model directory# It's a 5-layer Qwen3 model that predicts 7 tokens at once
See the
fuse2_dspark_fast.py
script (included in this repo) for the full implementation.
VRAM Breakdown
Configuration
VRAM (allocated)
VRAM (reserved)
Speed
4-bit target only
8.9 GB
9.4 GB
~5 tok/s
4-bit target + BF16 drafter
11.7 GB
12.2 GB
OVERFLOW
4-bit target + 4-bit drafter
10.3 GB
10.8 GB
~8 tok/s
BF16-attn target + 4-bit drafter (shared embed)
10.9 GB
11.4 GB
~10 tok/s
Critical
: On RTX 3060 12GB, Windows reserves ~0.5GB for display. If reserved VRAM exceeds ~11.5GB, CUDA silently spills to system RAM via PCIe, causing a
50x slowdown
. Always check
torch.cuda.memory_reserved(0)
.
DSpark Speculative Decoding
This model supports
DSpark
(Draft Speculative) decoding for 2x speedup:
How it works
Drafter
(5-layer Qwen3, 2560 hidden): Predicts 7 tokens in a single forward pass using masked attention + target hidden states
Target
(12B Fuse-2): Verifies all 7 tokens in ONE forward pass
Greedy verification
: Accept the longest prefix where drafter matches target's argmax
Bonus token
: Target always produces 1 bonus token (even if all 7 are rejected)
Key optimizations
Forward hooks
on 5 target layers (not
output_hidden_states=True
on all 36)
Single target forward
per block (no sequential fallback)
4-bit drafter
to save VRAM
Shared embedding
between target and drafter (saves 0.8 GB)
Sliding window KV cache
(512 tokens) for constant speed on long sequences
Acceptance rate
Short prompts: 3.0-3.4/7 (43-49%)
Long prompts: 2.3-2.5/7 (33-36%)
Code patterns: up to 4.5/7 (64%)
Performance
Speed (RTX 3060 12GB, 4-bit NF4)
Mode
Speed (short)
Speed (long, 512+ tok)
VRAM
Basic generation
5.5 tok/s
5.5 tok/s
8.9 GB
DSpark speculative
10.0 tok/s
8.5 tok/s
10.9 GB
Quality
The model produces:
Reasoning
: Qwen3-quality chain-of-thought (the host handles this)
Code
: DeepSeek-quality code generation (the experts handle this)
Mixed
: Seamless switching between reasoning and code
Example output
Prompt: "Write a Python function to check if a number is prime."
Output (excerpt):
Okay, I need to write a Python function to check if a number is prime.
Let me think about how to approach this.
First, a prime number is a number greater than 1 that has no divisors
other than 1 and itself...
1. Check if the number is less than 2 → return False.
2. Check if the number is 2 → return True
3. Check if the number is even → if yes, return False
4. Iterate from 3 to sqrt(n), stepping by 2
5. For each i, check if n is divisible by i
6. If none divide n, return True
Mini-Whale-1-12B huggingface.co is an AI model on huggingface.co that provides Mini-Whale-1-12B's model effect (), which can be used instantly with this Akahsizrr Mini-Whale-1-12B model. huggingface.co supports a free trial of the Mini-Whale-1-12B model, and also provides paid use of the Mini-Whale-1-12B. Support call Mini-Whale-1-12B model through api, including Node.js, Python, http.
Mini-Whale-1-12B huggingface.co is an online trial and call api platform, which integrates Mini-Whale-1-12B's modeling effects, including api services, and provides a free online trial of Mini-Whale-1-12B, you can try Mini-Whale-1-12B online for free by clicking the link below.
Akahsizrr Mini-Whale-1-12B online free url in huggingface.co:
Mini-Whale-1-12B is an open source model from GitHub that offers a free installation service, and any user can find Mini-Whale-1-12B on GitHub to install. At the same time, huggingface.co provides the effect of Mini-Whale-1-12B install, users can directly use Mini-Whale-1-12B installed effect in huggingface.co for debugging and trial. It also supports api for free installation.