AMALIA-9B (European Portuguese) support: SPM Metaspace prepend, ChatML detection, bf16 compute - #44
Merged
Merged
Conversation
…L template detection, bf16 compute
Adds layerx-labs/AMALIA-9B-0626-{DPO,SFT}-MLX-4bit (EuroLLM-9B base), golden-gated
against mlx-lm (reference/fixtures_amalia/). The forward pass is the stock
LlamaModel; the new pieces:
- tokenizer/spm: honor the Metaspace pre-tokenizer's prepend_scheme ("always"
prepends U+2581 to the input-leading segment only — never after special
tokens, never doubled onto a leading space; pinned by the corpus fixture) and
the decoder Strip step (drop N leading spaces on decode). Gemma unchanged.
- tokenizer: ChatFormat::Amalia (ChatML + default Portuguese system prompt),
detected from the checkpoint's chat_template.jinja via the new
chat_format_for_model_dir (model_type is "llama", so the type mapping can't
identify it). AMALIA's BOS id 3 IS <|im_start|>, so apply_chat_template
encodes the fully-rendered template without the BOS prepend (HF
add_special_tokens=False semantics).
- bf16 compute path (ModelConfig::bf16_compute): this family's residual stream
overflows fp16 from layer 9 — an fp16 forward is all-NaN logits, and the fp16
mlx-lm reference is equally degenerate. Selected by the family's config
fingerprint; the weight cast, batched mask, and prefill logit placeholders
follow compute_dtype(cfg); skinny_mm self-disables (fp16-only); kv-quant and
the prefix cache are rejected at engine creation for bf16 models (ungated).
Every other checkpoint keeps fp16 bit-for-bit.
- test harness: compare_close now fails on nonfinite values (NaN-vs-NaN
previously passed vacuously — exactly how a degenerate fp16 reference slipped
through); config.json quantization "mode" != affine is rejected at load.
Gates: tokenizer corpus byte-exact (33 strings incl. post-special and
byte-fallback cases), chat_ids exact, front-half/block0 close, argmax exact,
20-token greedy stream exact. CLI smoke: fluent pt-PT answer, EOS stop.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds support for
layerx-labs/AMALIA-9B-0626-DPO-MLX-4bit(and the SFT variant — identical architecture/tokenizer/template), the European-Portuguese LLM built on EuroLLM-9B (PROPOR 2026 paper). Golden-gated end-to-end against mlx-lm (reference/fixtures_amalia/).What's in it
The forward pass needed no new model class — AMALIA is a plain
LlamaForCausalLM(42 layers, GQA 32/8, head_dim 128, rope_theta 1e6, no rope scaling), socreate_modelfalls through to the stockLlamaModel. The support work is in three areas:Tokenizer (
tokenizer/spm)prepend_scheme:"always"prepends▁to the input-leading segment only — never after special tokens, never doubled onto a leading space. The exact semantics were settled empirically by the golden corpus (33 strings incl. post-special, adjacent-special, and byte-fallback cases); Gemma (never) is regression-gated unchanged."first"throws rather than approximating.Stripstep (drop N leading spaces on decode) is parsed and applied, sodecode(encode(s)) == s.Chat format (
tokenizer,runtime/engine)ChatFormat::Amalia: ChatML with the checkpoint's default Portuguese "Amália" system prompt.model_typeis"llama", so the newchat_format_for_model_dirdetects the format from the checkpoint'schat_template.jinja(falling back to the model_type mapping).<|im_start|>:apply_chat_templateencodes the fully-rendered template without the BOS prepend, mirroring HF'sadd_special_tokens=False. Gated byte-exact by thechat_idsfixture.bf16 compute path (
core/config,core/weights,model,runtime)The headline discovery: this family's residual stream overflows fp16 from layer 9 — an fp16 forward is all-NaN logits, and the fp16 mlx-lm reference is equally degenerate (the first fixture dump produced NaN goldens that gated nothing). Fixes:
ModelConfig::bf16_compute, selected by the family's config fingerprint (no config field can drive this — every MLX conversion declaresdtype: bfloat16, including the fp16-proven Llama/Qwen checkpoints). The weight cast, batched additive mask, and prefill logit placeholders all follow the newcompute_dtype(cfg)helper;skinny_mmself-disables (fp16-only kernels). Every other checkpoint keeps fp16 bit-for-bit (full regression suite passes untouched).compare_closenow fails on nonfinite values: NaN-vs-NaN previously passed vacuously, which is exactly how the degenerate fp16 reference slipped through.quantization.mode != "affine"is rejected at load (a future non-affine repo would otherwise run silent garbage throughmx::quantized_matmul).Validation
chat_idsexact, embeddings/front-half/block0 close, argmax exact, 20-token greedy stream exact (raw-logit closeness is deliberately loose: bf16 over 4-bit quantized matmuls is fusion-context-sensitive, per the kv-quant precedent).mlxforge-cli generate layerx-labs/AMALIA-9B-0626-DPO-MLX-4bit "Qual é a capital de Portugal? E diz-me uma curiosidade sobre ela." 120→ fluent pt-PT answer about Lisboa, clean EOS stop (eos [4, 2]) after 96 tokens, 21 tok/s decode.Docs updated:
doc/supported-models.md,doc/tokenizer.md,README,CHANGELOG,CLAUDE.mdgotchas. Future work noted: an engine-level chat-format/compute-dtype override (ABI append), kv-quant + prefix-cache goldens for bf16 models.🤖 Generated with Claude Code