Skip to content

Add Laguna XS.2 - #1223

Closed
Blaizzy wants to merge 12 commits into
ml-explore:mainfrom
Blaizzy:pc/add-lg
Closed

Blaizzy wants to merge 12 commits into
ml-explore:mainfrom
Blaizzy:pc/add-lg

Conversation

@Blaizzy

@Blaizzy Blaizzy commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

No description provided.

@Blaizzy
Blaizzy marked this pull request as ready for review April 28, 2026 14:53
@johntdavies

Copy link
Copy Markdown

Running the 4-bit on my M5 Max: 6000-token generation @ 116 tok/s, 19.9 GB peak. Nice work!

@Blaizzy

Blaizzy commented Apr 28, 2026

Copy link
Copy Markdown
Contributor Author

My pleasure John! 🙌🏽

@pierre427

Copy link
Copy Markdown
Contributor

Thanks for this — got AtomicChat/Laguna-XS-2.1-MLX-8bit running on this. Two small additions that might be worth folding in:

1. make_cache() for the sliding-window layers. Laguna is 10 full_attention + 30 sliding_attention (512) layers, but without a make_cache() the loader allocates a full KVCache on all 40 — the 30 sliding layers keep keys they never attend to. Adding the standard mixed pattern (as in gpt_oss / gemma3_text / cohere2_moe):

def make_cache(self):
    caches = []
    for lt in self.args.layer_types:
        if lt == "sliding_attention" and self.args.sliding_window:
            caches.append(RotatingKVCache(max_size=self.args.sliding_window))
        else:
            caches.append(KVCache())
    return caches

(with from .cache import KVCache, RotatingKVCache). On an M5 Max I measured ~2.7× decode + ~4 GB less KV at 32k context vs full-KV, and it's bit-identical for ≤512-token context — the forward already builds the sliding mask with window_size=self.args.sliding_window, so this just stops storing masked-out keys.

2. Loading VLM-style repacks. Some 8-bit repacks (e.g. AtomicChat's) wrap every tensor under a language_model. prefix. A guarded strip at the top of sanitize() loads them (same repack shape cohere2_moe already handles), and it's a no-op when the prefix is absent:

if any(k.startswith("language_model.") for k in weights):
    weights = {(k[len("language_model."):] if k.startswith("language_model.") else k): v
               for k, v in weights.items()}

Happy to open a PR against your branch if that's easier. Thanks again!

@pierre427

Copy link
Copy Markdown
Contributor

Running the 8-bit repack (AtomicChat/Laguna-XS-2.1-MLX-8bit) on an M5 Max against this branch, two additions made it production-ready for us:

1. make_cache() for the sliding-window layers. As shipped, make_prompt_cache allocates a full KVCache on all 40 layers, including the 30 sliding-window (512) layers — so long-context runs store/attend over history those layers can't use. Returning RotatingKVCache(max_size=<window>) for the sliding layers and KVCache for the 10 global layers gives up to 2.7× decode and ~4 GB less KV at 32k, verified bitwise-identical to the full cache at ≤ window context.

2. language_model. prefix strip in sanitize(). The VLM-style repacks (this 8-bit one included) prefix every tensor with language_model., so a stock load misses all weights — a one-line strip handles them.

Both are tested on our end and independent of the core model code here. Happy to push them to this branch or open a small follow-up once this lands — whichever you'd prefer.

@pierre427

Copy link
Copy Markdown
Contributor

Turned the two additions into a ready-to-merge follow-up onto your branch: Blaizzy#26 (windowed make_cache() + language_model. prefix strip). Tested against the current pc/add-lg and black-clean — merging it updates this PR. No worries if you'd rather fold them in yourself.

@ahmedandaloes

Copy link
Copy Markdown

any updates!

erbmi1 pushed a commit to mit-orcd/m5-max-model-testing that referenced this pull request Sep 12, 2026
…a XS 2.1

Architecture table was wrong for three models. The "-A3B" and "-a4b" suffixes in
the model IDs mean activated parameters, so these are all MoE, not dense:
  qwen3.6-35b   Qwen3.6-35B-A3B    35B total / 3B active, 256 experts, top-8 +1
  qwen3.5-35b   Qwen3.5-35B-A3B    35B total / 3B active
  gemma-4-26b   gemma-4-26b-a4b    25.2B total / 3.8B active, 128 experts
ornith (36B-A3B) and both Lagunas were missing entirely and are now included.
The correction strengthens the section's own thesis rather than weakening it:
laguna-xs.2 turns out to have the best tok/s per active billion of anything
tested, which is the same model the cost table independently ranks cheapest.

deepseek-r1 32b moves to a new "kept for the record, not in the running"
section. A dense 32B that reasons at length costs more wall clock than its score
justifies. Its per-task detail is still published; it is only removed from the
main table, the cost table, the architecture table and the nav, so a ranking is
not led by a model nobody would pick.

Dropped the phantom devstral-small entry: it named the same checkpoint as
devstral/devstral2, was never in bench.py, and produced no row — it only ever
surfaced as a misleading name in the cost table's exclusion note.

Laguna XS 2.1 prepared but not yet run: bench target, runner, and a thinking
budget. Served with mlx_vlm.server because stock mlx-lm has no laguna class
(ml-explore/mlx-lm#1223); the GGUF route needs two unmerged llama.cpp PRs and
returns empty text on Metal. The runner smoke-tests for that empty-content bug
and refuses to score it as 126 model failures.

Co-authored-by: Cursor <cursoragent@cursor.com>
@michalk8

Copy link
Copy Markdown
Collaborator

closed via #1704

@michalk8 michalk8 closed this Sep 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants