Text Generation
MLX
lora
qlora
diffusion
diffusion-language-model
gemma
diffusiongemma
tool-use
agents
apple-silicon
Instructions to use Fild/diffusiongemma-26B-A4B-it-tool-selector-lora-mlx with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use Fild/diffusiongemma-26B-A4B-it-tool-selector-lora-mlx with MLX:
# Make sure mlx-lm is installed # pip install --upgrade mlx-lm # if on a CUDA device, also pip install mlx[cuda] # Generate text with mlx-lm from mlx_lm import load, generate model, tokenizer = load("Fild/diffusiongemma-26B-A4B-it-tool-selector-lora-mlx") prompt = "Once upon a time in" text = generate(model, tokenizer, prompt=prompt, verbose=True) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
- MLX LM
How to use Fild/diffusiongemma-26B-A4B-it-tool-selector-lora-mlx with MLX LM:
Generate or start a chat session
# Install MLX LM uv tool install mlx-lm # Generate some text mlx_lm.generate --model "Fild/diffusiongemma-26B-A4B-it-tool-selector-lora-mlx" --prompt "Once upon a time"
- Atomic Chat
File size: 7,831 Bytes
60b165e | 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 | #!/usr/bin/env python3
"""
Tool-selection benchmark for DiffusionGemma (zero-shot baseline or + LoRA adapter).
Generates with mlx-vlm's diffusion engine on the selector test set and scores
"- Tool" line predictions against expected tool sets: Jaccard, exact set match,
precision/recall, top-1. Numbers are comparable ONLY across runs of THIS harness
on the SAME test split (e.g. zero-shot vs adapter); cross-model or cross-harness
comparisons require re-running the other model through this same harness.
Note: per-sample outputs are order-coupled (device RNG is seeded once), so a
--max-samples k run replays the full run's first k samples; changing sample
order or count changes individual outputs.
Usage:
python3 diffusion_eval.py \
--model ./diffusiongemma-26B-A4B-it-4bit \
[--adapter ./adapters/my-adapter] \
--test ./data/test.jsonl --out ./eval.json \
[--max-samples 50] [--check-template]
"""
import argparse
import json
import re
import time
from pathlib import Path
import mlx.core as mx
from mlx_vlm.utils import load as vlm_load
# Note: the model-turn prefill ("<|turn>model\n<|channel>thought\n<channel|>")
# is already baked into each test sample's prompt field by build_diffusiongemma_data.py,
# so generation starts exactly where training responses started.
def parse_args():
p = argparse.ArgumentParser()
p.add_argument("--model", required=True)
p.add_argument("--adapter", default=None)
p.add_argument("--test", required=True)
p.add_argument("--out", required=True)
p.add_argument("--max-samples", type=int, default=0, help="0 = all")
p.add_argument("--max-tokens", type=int, default=96)
p.add_argument("--sample-timeout", type=int, default=45,
help="per-sample wall-clock cap (s); DiffusionGemma's long-context prefill "
"can hang on ~900-token prompts — timed-out samples count as failures")
p.add_argument("--seed", type=int, default=7)
p.add_argument("--check-template", action="store_true",
help="verify our offline-rendered prompt matches the official template")
return p.parse_args()
def parse_tools(text):
"""Extract '- Tool' lines; stop at turn end / eos markers."""
tools = []
for raw in text.split("\n"):
line = raw.strip()
for stop in ("<turn|>", "<eos>", "<|turn>"):
if stop in line:
line = line.split(stop)[0].strip()
if line.startswith("- "):
name = line[2:].strip()
if name and re.fullmatch(r"[A-Za-z0-9_\-.]+", name):
tools.append(name)
elif tools and line and not line.startswith("-"):
break # left list format — stop collecting
# de-dup preserving order
seen, out = set(), []
for t in tools:
if t not in seen:
seen.add(t)
out.append(t)
return out
def metrics(pred, expected):
ps, es = set(pred), set(expected)
inter = ps & es
union = ps | es
return {
"jaccard": len(inter) / len(union) if union else 1.0,
"exact": 1.0 if ps == es else 0.0,
"precision": len(inter) / len(ps) if ps else 0.0,
"recall": len(inter) / len(es) if es else 0.0,
"top1": 1.0 if pred and pred[0] in es else 0.0,
}
def main():
args = parse_args()
mx.random.seed(args.seed)
print(f"[load] {args.model} adapter={args.adapter}", flush=True)
model, processor = vlm_load(args.model, adapter_path=args.adapter)
model.eval()
tokenizer = processor.tokenizer
mx.set_cache_limit(2 * 1024**3) # bound the MLX buffer cache (paired with per-sample clear_cache)
# resolve the generate API across mlx-vlm layouts
try:
from mlx_vlm import generate as vlm_generate
except ImportError:
from mlx_vlm.generate import generate as vlm_generate
samples = []
with open(args.test) as f:
for line in f:
obj = json.loads(line)
expected_text = obj["response"].split("<turn|>")[0]
expected = parse_tools(expected_text)
samples.append({"prompt": obj["prompt"], "expected": expected})
if args.max_samples:
samples = samples[: args.max_samples]
print(f"[data] {len(samples)} test samples", flush=True)
# train/serve consistency guard: literal "<bos>" must tokenize to exactly one
# leading id 2 (the generate path tokenizes with add_special_tokens=False)
probe = tokenizer.encode("<bos>" + samples[0]["prompt"], add_special_tokens=False)
assert probe[0] == 2 and 2 not in probe[1:], "double-BOS or missing BOS in eval tokenization"
if args.check_template:
from mlx_vlm.prompt_utils import apply_chat_template
s = samples[0]["prompt"]
# reconstruct messages from our rendered prompt
sys_part = s.split("<|turn>system\n")[1].split("<turn|>")[0]
usr_part = s.split("<|turn>user\n")[1].split("<turn|>")[0]
messages = [{"role": "system", "content": sys_part},
{"role": "user", "content": usr_part}]
official = apply_chat_template(processor, model.config, messages, add_generation_prompt=True)
mine = s if s.startswith("<bos>") else "<bos>" + s
print(f"[check] official == ours: {official == mine}", flush=True)
if official != mine:
print("OFFICIAL >>>", repr(official[-300:]), flush=True)
print("OURS >>>", repr(mine[-300:]), flush=True)
# per-sample timeout: mlx-vlm's diffusion sampler is a Python loop calling mx
# ops, so SIGALRM is delivered between denoising steps and aborts a wedged sample.
import signal
class _Timeout(Exception):
pass
def _on_alarm(signum, frame):
raise _Timeout()
signal.signal(signal.SIGALRM, _on_alarm)
results, per_sample = [], []
timeouts = 0
t0 = time.time()
for i, s in enumerate(samples):
prompt_text = "<bos>" + s["prompt"] # template normally injects bos; tokenized add_special_tokens=False
timed_out = False
try:
signal.alarm(args.sample_timeout)
out = vlm_generate(
model, processor, prompt_text,
max_tokens=args.max_tokens, temperature=0.0, verbose=False,
)
signal.alarm(0)
text = out.text if hasattr(out, "text") else str(out)
except _Timeout:
signal.alarm(0)
text = ""
timed_out = True
timeouts += 1
pred = parse_tools(text)
m = metrics(pred, s["expected"])
results.append(m)
per_sample.append({"i": i, "pred": pred, "expected": s["expected"],
"raw": text[:300], "timed_out": timed_out, **m})
# CRITICAL: clear the MLX buffer cache between independent samples. Without
# this the cache grows unboundedly over a long eval (RSS 16GB -> 22GB+),
# progressively slowing generation until even short prompts hit the timeout.
mx.clear_cache()
if (i + 1) % 10 == 0:
el = time.time() - t0
print(f" {i+1}/{len(samples)} jacc so far="
f"{sum(r['jaccard'] for r in results)/len(results):.4f} "
f"timeouts={timeouts} ({el/(i+1):.1f}s/sample)", flush=True)
n = len(results)
agg = {k: sum(r[k] for r in results) / n for k in results[0] if k != "timed_out"}
agg["n"] = n
agg["timeouts"] = timeouts
agg["model"] = args.model
agg["adapter"] = args.adapter
agg["seconds_total"] = round(time.time() - t0, 1)
print(json.dumps(agg, indent=2), flush=True)
Path(args.out).write_text(json.dumps({"aggregate": agg, "samples": per_sample}, indent=2))
print(f"[done] wrote {args.out}", flush=True)
if __name__ == "__main__":
main()
|