Text Generation
MLX
Safetensors
qwen3_5
1-bit
experimental
layer-pruned
apple-silicon
conversational
Instructions to use VertexAIco/Bonsai-27b-MLX-UltraFastUnStable with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use VertexAIco/Bonsai-27b-MLX-UltraFastUnStable 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("VertexAIco/Bonsai-27b-MLX-UltraFastUnStable") 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 VertexAIco/Bonsai-27b-MLX-UltraFastUnStable with Pi:
Start the MLX server
# Install MLX LM: uv tool install mlx-lm # Start a local OpenAI-compatible server: mlx_lm.server --model "VertexAIco/Bonsai-27b-MLX-UltraFastUnStable"
Configure the model in Pi
# Install Pi: npm install -g @earendil-works/pi-coding-agent # Add to ~/.pi/agent/models.json: { "providers": { "mlx-lm": { "baseUrl": "http://localhost:8080/v1", "api": "openai-completions", "apiKey": "none", "models": [ { "id": "VertexAIco/Bonsai-27b-MLX-UltraFastUnStable" } ] } } }Run Pi
# Start Pi in your project directory: pi
- MLX LM
How to use VertexAIco/Bonsai-27b-MLX-UltraFastUnStable with MLX LM:
Generate or start a chat session
# Install MLX LM uv tool install mlx-lm # Interactive chat REPL mlx_lm.chat --model "VertexAIco/Bonsai-27b-MLX-UltraFastUnStable"
Run an OpenAI-compatible server
# Install MLX LM uv tool install mlx-lm # Start the server mlx_lm.server --model "VertexAIco/Bonsai-27b-MLX-UltraFastUnStable" # Calling the OpenAI-compatible server with curl curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "VertexAIco/Bonsai-27b-MLX-UltraFastUnStable", "messages": [ {"role": "user", "content": "Hello"} ] }' - Hermes Agent
How to use VertexAIco/Bonsai-27b-MLX-UltraFastUnStable 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 "VertexAIco/Bonsai-27b-MLX-UltraFastUnStable"
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 VertexAIco/Bonsai-27b-MLX-UltraFastUnStable
Run Hermes
hermes
- Atomic Chat
- OpenClaw
How to use VertexAIco/Bonsai-27b-MLX-UltraFastUnStable with OpenClaw:
Start the MLX server
# Install MLX LM: uv tool install mlx-lm # Start a local OpenAI-compatible server: mlx_lm.server --model "VertexAIco/Bonsai-27b-MLX-UltraFastUnStable"
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 "VertexAIco/Bonsai-27b-MLX-UltraFastUnStable" \ --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"
File size: 16,233 Bytes
7ae0b4d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 | """Text-only loader for the Bonsai 2 MLX pack.
This keeps the language model numerically identical to the pack's bundled loader,
but does not instantiate or evaluate the 0.92 GB vision tower for text-only use.
"""
from __future__ import annotations
import json
import math
import os
import sys
from pathlib import Path
import mlx.core as mx
import mlx.nn as nn
from mlx_lm.models.qwen3_5 import TextModel, TextModelArgs
from mlx_lm.models.activations import swiglu
from mlx_lm.models.base import scaled_dot_product_attention
def _touch_placeholder_cache(cache):
"""Give pruned-layer ArraysCache entries serializable, inert state."""
if cache is not None and cache[0] is None:
cache[0] = mx.zeros((1,), dtype=mx.uint8)
cache[1] = mx.zeros((1,), dtype=mx.uint8)
class SkippedDecoderLayer(nn.Module):
"""Identity replacement used only by the explicitly enabled Turbo mode."""
def __init__(self, is_linear, scale=1.0):
super().__init__()
self.original_is_linear = is_linear
# Identity layers need only a harmless cache placeholder. ArraysCache
# tolerates an empty state; KVCache.state assumes allocated keys.
self.is_linear = True
self.scale = float(scale)
def __call__(self, x, mask=None, cache=None):
_touch_placeholder_cache(cache)
return x if self.scale == 1.0 else x * self.scale
class AffineSkippedDecoderLayer(nn.Module):
"""Cheap calibrated diagonal approximation for an omitted decoder block."""
def __init__(self, is_linear, scale, bias):
super().__init__()
self.original_is_linear = is_linear
self.is_linear = True
self.scale = scale
self.bias = bias
def __call__(self, x, mask=None, cache=None):
_touch_placeholder_cache(cache)
return x * self.scale + self.bias
class SkippedMLP(nn.Module):
"""Zero residual used to omit only a decoder block's MLP branch."""
def __call__(self, x):
return mx.zeros_like(x)
class AffineSkippedMLP(nn.Module):
"""Calibrated diagonal approximation of an MLP residual branch."""
def __init__(self, scale, bias):
super().__init__()
self.scale = scale
self.bias = bias
def __call__(self, x):
return x * self.scale + self.bias
class FusedDecodeProjection(nn.Module):
"""Fuse sibling ternary GEMVs only when autoregressive decode benefits."""
def __init__(self, projections, fwht):
super().__init__()
first = projections[0]
if any(
projection.block != first.block or projection.signs is not first.signs
for projection in projections[1:]
):
raise ValueError("Cannot fuse projections with different transforms")
self.weight = mx.concatenate([p.weight for p in projections], axis=0)
self.scales = mx.concatenate([p.scales for p in projections], axis=0)
self.biases = mx.concatenate([p.biases for p in projections], axis=0)
self.signs = first.signs
self.block = first.block
sizes = [projection.weight.shape[0] for projection in projections]
self.boundaries = [sum(sizes[: index + 1]) for index in range(len(sizes) - 1)]
self.slices = []
start = 0
for size in sizes:
self.slices.append(slice(start, start + size))
start += size
self._fwht = fwht
def _matmul(self, x, weight, scales, biases):
return mx.quantized_matmul(
x,
weight,
scales,
biases,
transpose=True,
group_size=128,
bits=2,
)
def __call__(self, x):
x = self._fwht(x, self.block, self.signs)
# One wide GEMV is faster during autoregressive decode. For prompt
# batches, two narrower QMMs are slightly faster on Apple M4.
if math.prod(x.shape[:-1]) == 1:
combined = self._matmul(x, self.weight, self.scales, self.biases)
return tuple(mx.split(combined, self.boundaries, axis=-1))
else:
return tuple(
self._matmul(
x,
self.weight[section],
self.scales[section],
self.biases[section],
)
for section in self.slices
)
class FusedDecodeMLP(nn.Module):
def __init__(self, projection, down):
super().__init__()
self.projection = projection
self.down_proj = down
def __call__(self, x):
gate, up = self.projection(x)
return self.down_proj(swiglu(gate, up))
class FusedDecodeAttention(nn.Module):
def __init__(self, attention, projection):
super().__init__()
self.projection = projection
self.num_key_value_heads = attention.num_key_value_heads
self.num_attention_heads = attention.num_attention_heads
self.head_dim = attention.head_dim
self.scale = attention.scale
self.q_norm = attention.q_norm
self.k_norm = attention.k_norm
self.rope = attention.rope
self.o_proj = attention.o_proj
def __call__(self, x, mask=None, cache=None):
batch, length, _ = x.shape
q_proj_output, keys, values = self.projection(x)
queries, gate = mx.split(
q_proj_output.reshape(batch, length, self.num_attention_heads, -1),
2,
axis=-1,
)
gate = gate.reshape(batch, length, -1)
queries = self.q_norm(queries).transpose(0, 2, 1, 3)
keys = self.k_norm(
keys.reshape(batch, length, self.num_key_value_heads, -1)
).transpose(0, 2, 1, 3)
values = values.reshape(
batch, length, self.num_key_value_heads, -1
).transpose(0, 2, 1, 3)
if cache is not None:
queries = self.rope(queries, offset=cache.offset)
keys = self.rope(keys, offset=cache.offset)
keys, values = cache.update_and_fetch(keys, values)
else:
queries = self.rope(queries)
keys = self.rope(keys)
output = scaled_dot_product_attention(
queries, keys, values, cache=cache, scale=self.scale, mask=mask
)
output = output.transpose(0, 2, 1, 3).reshape(batch, length, -1)
return self.o_proj(output * mx.sigmoid(gate))
def load_text_model(directory: str | Path):
directory = Path(directory)
config = json.loads((directory / "config.json").read_text())
if config.get("schema_version") != 2:
raise ValueError(f"Unsupported schema version: {config.get('schema_version')!r}")
if config.get("model_type") != "prism_hadamard_qwen35":
raise ValueError(f"Unsupported model type: {config.get('model_type')!r}")
runtime = directory / "runtime"
sys.path.insert(0, str(runtime))
from runtime import Packed, fwht
model = TextModel(TextModelArgs.from_dict(config["text_config"]))
all_weights = mx.load(str(directory / "model.safetensors"))
prefix = "language_model."
layout = config.get("optimized_weight_layout", {})
share_signs = layout.get("sign_sharing") == "by_input_width"
Packed.share_fwht_activations = bool(layout.get("shared_fwht_activations", False))
seen = set()
for record in config["modules"]:
path = record["path"]
if path in seen:
raise ValueError(f"Duplicate packed module: {path}")
seen.add(path)
if record["dtype"] != "float16":
raise ValueError(f"Unsupported activation dtype at {path}")
parts = path.split(".")
parent = model
for part in parts[:-1]:
parent = parent[int(part)] if part.isdigit() else getattr(parent, part)
key = prefix + path
arrays = [all_weights[key + "." + suffix] for suffix in ("weight", "scales", "biases")]
block = record["block"]
if block and block not in (512, 1024, 2048, 4096):
raise ValueError(f"Unsupported Hadamard block size at {path}: {block}")
signs = all_weights.get(key + ".signs")
if block and signs is None:
raise ValueError(f"Missing Hadamard signs at {path}")
if signs is not None and not mx.all((signs == 1) | (signs == -1)).item():
raise ValueError(f"Invalid Hadamard signs at {path}")
setattr(
parent,
parts[-1],
Packed(arrays, block, signs, record["embedding"], mx.float16),
)
text_weights = [
(name[len(prefix) :], value)
for name, value in all_weights.items()
if name.startswith(prefix)
]
model.load_weights(text_weights, strict=True)
del text_weights
# The checkpoint contains 402 sign tensors but only three unique, byte-identical
# payloads (one for each input width). Point modules with the same width at one
# canonical array after load_weights has assigned the checkpoint parameters.
# This is an exact representation change: ternary weights, scales, biases, and
# every sign value remain untouched.
if share_signs:
canonical_signs = {}
for record in config["modules"]:
if not record["block"]:
continue
parent = model
parts = record["path"].split(".")
for part in parts:
parent = parent[int(part)] if part.isdigit() else getattr(parent, part)
signs = parent.signs
key = (tuple(signs.shape), signs.dtype)
canonical = canonical_signs.get(key)
if canonical is None:
canonical_signs[key] = signs
else:
parent.signs = canonical
expected = layout.get("unique_sign_payloads")
if expected is not None and len(canonical_signs) != expected:
raise ValueError(
f"Expected {expected} canonical sign tensors, found {len(canonical_signs)}"
)
fuse_decode = os.environ.get("BONSAI_FUSE_DECODE") == "1" or bool(
layout.get("fused_decode_projections", False)
)
if fuse_decode:
for layer_index, layer in enumerate(model.model.layers):
old_mlp = layer.mlp
gate, up = old_mlp.gate_proj, old_mlp.up_proj
projection = FusedDecodeProjection((gate, up), fwht)
fused = FusedDecodeMLP(projection, old_mlp.down_proj)
# Materialize one layer at a time and release its old gate/up arrays,
# keeping peak memory bounded while the checkpoint is memory-mapped.
mx.eval(projection.weight, projection.scales, projection.biases)
layer.mlp = fused
stem = f"{prefix}model.layers.{layer_index}.mlp."
for projection in ("gate_proj", "up_proj"):
for suffix in ("weight", "scales", "biases", "signs"):
all_weights.pop(stem + projection + "." + suffix, None)
if not layer.is_linear:
old_attention = layer.self_attn
qkv = FusedDecodeProjection(
(
old_attention.q_proj,
old_attention.k_proj,
old_attention.v_proj,
),
fwht,
)
mx.eval(qkv.weight, qkv.scales, qkv.biases)
layer.self_attn = FusedDecodeAttention(old_attention, qkv)
stem = f"{prefix}model.layers.{layer_index}.self_attn."
for projection_name in ("q_proj", "k_proj", "v_proj"):
for suffix in ("weight", "scales", "biases", "signs"):
all_weights.pop(
stem + projection_name + "." + suffix, None
)
profile_name = os.environ.get("BONSAI_PROFILE", "").strip()
profile = {}
if profile_name:
profiles = config.get("optimized_runtime_profiles", {})
if profile_name not in profiles:
raise ValueError(f"Unknown BONSAI_PROFILE value: {profile_name!r}")
profile = profiles[profile_name]
profile_layers = profile.get("skip_layers", [])
skip_text = ",".join(str(index) for index in profile_layers)
config["active_runtime_profile"] = profile_name
else:
skip_text = os.environ.get("BONSAI_SKIP_LAYERS", "").strip()
if skip_text:
skip_layers = sorted({int(value) for value in skip_text.split(",")})
adapter_path = os.environ.get("BONSAI_SKIP_ADAPTERS", "").strip()
if not adapter_path and profile.get("skip_adapters"):
adapter_path = str(directory / profile["skip_adapters"])
skip_adapters = mx.load(adapter_path) if adapter_path else {}
scale_text = os.environ.get("BONSAI_SKIP_SCALES", "").strip()
skip_scales = {}
if scale_text:
for item in scale_text.split(","):
index_text, scale_value = item.split(":", 1)
skip_scales[int(index_text)] = float(scale_value)
if any(index < 0 or index >= len(model.model.layers) for index in skip_layers):
raise ValueError(f"Invalid BONSAI_SKIP_LAYERS value: {skip_text!r}")
# Layers 0 and 3 own the reference SSM/attention caches used to build
# masks for the whole stack and therefore cannot be identity-pruned.
if {0, model.model.fa_idx} & set(skip_layers):
raise ValueError("Turbo mode cannot skip the reference cache layers")
for index in skip_layers:
old_layer = model.model.layers[index]
scale_key = f"layers.{index}.scale"
bias_key = f"layers.{index}.bias"
if adapter_path:
if scale_key not in skip_adapters or bias_key not in skip_adapters:
raise ValueError(f"Missing affine adapter for skipped layer {index}")
model.model.layers[index] = AffineSkippedDecoderLayer(
old_layer.is_linear,
skip_adapters[scale_key],
skip_adapters[bias_key],
)
else:
model.model.layers[index] = SkippedDecoderLayer(
old_layer.is_linear, skip_scales.get(index, 1.0)
)
config["active_runtime_skip_layers"] = skip_layers
if adapter_path:
config["active_runtime_skip_adapters"] = adapter_path
if skip_scales:
config["active_runtime_skip_scales"] = skip_scales
skip_mlp_text = os.environ.get("BONSAI_SKIP_MLP_LAYERS", "").strip()
if not skip_mlp_text and profile.get("skip_mlp_layers"):
skip_mlp_text = ",".join(str(index) for index in profile["skip_mlp_layers"])
if skip_mlp_text:
skip_mlp_layers = sorted({int(value) for value in skip_mlp_text.split(",")})
mlp_adapter_path = os.environ.get("BONSAI_SKIP_MLP_ADAPTERS", "").strip()
mlp_adapters = mx.load(mlp_adapter_path) if mlp_adapter_path else {}
if any(index < 0 or index >= len(model.model.layers) for index in skip_mlp_layers):
raise ValueError(f"Invalid BONSAI_SKIP_MLP_LAYERS value: {skip_mlp_text!r}")
active_full_skips = set(config.get("active_runtime_skip_layers", []))
if active_full_skips & set(skip_mlp_layers):
raise ValueError("A layer cannot be both fully skipped and MLP-skipped")
for index in skip_mlp_layers:
scale_key = f"mlp_layers.{index}.scale"
bias_key = f"mlp_layers.{index}.bias"
if mlp_adapter_path:
if scale_key not in mlp_adapters or bias_key not in mlp_adapters:
raise ValueError(f"Missing affine MLP adapter for layer {index}")
model.model.layers[index].mlp = AffineSkippedMLP(
mlp_adapters[scale_key], mlp_adapters[bias_key]
)
else:
model.model.layers[index].mlp = SkippedMLP()
config["active_runtime_skip_mlp_layers"] = skip_mlp_layers
if mlp_adapter_path:
config["active_runtime_skip_mlp_adapters"] = mlp_adapter_path
model.eval()
mx.eval(model.parameters())
return model, config
|