Write a hermetic "tiny" model-saving test for an mflux model — a fast twin of the slow save/load test that runs the real ModelSaver/WeightLoader/WeightApplier seam on real component classes at toy dimensions, with no downloads or image generation. Use when asked to "make tiny test for <model>".
95%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
A tiny test proves a model's quantized checkpoint survives a save/load roundtrip byte-for-byte, in about a second, without downloading anything. It builds the model's real component classes at toy dimensions and pushes them through the real save/load code path.
<model>" or add a fast twin of a slow save/load test.mflux-model-porting) and want cheap checkpoint coverage.Related: mflux-testing for running the suite and golden-image rules.
Read these before writing a new one — they are the source of truth:
tests/model_saving/tiny_checkpoint_helper.py — the shared harnesstests/model_saving/test_tiny_model_saving_ernie_image.py — components take explicit kwargstests/model_saving/test_tiny_model_saving_ideogram4.py — components take a Config dataclassDo not trust code quoted in this skill over those files. The pattern landed as an RFC (PR #599, "proposal + demo on 2 models") and is expected to evolve.
Models that already have tiny tests: ernie_image, ideogram4.
One file, no src/ changes:
tests/model_saving/test_tiny_model_saving_<model>.pyThe whole test is a single call into TinyCheckpointRoundtrip. The real work is figuring out how to build that model's components small. Match the two examples exactly:
TestTiny<Model>ModelSavingtest_tiny_quantized_checkpoint_roundtrips_exactly, decorated @pytest.mark.fast@staticmethod _tiny_components() returning the component dictsrc/mflux/models/<model>/weights/<model>_weight_definition.pyget_components() is authoritative: it gives the exact set of component names you must produce, and how many. While there, note two things:
quantization_group_size — a class attribute on the weight definition. Defaults to 64 but is not always 64 (boogu uses 32). Every tiny dimension must be a multiple of this model's value. Check with grep -n "quantization_group_size" src/mflux/models/<model>/weights/*.py.skip_quantization=True on any component (krea2 and qwen each have one). In production, WeightApplier._quantize honors this flag and leaves the component unquantized. TinyCheckpointRoundtrip._quantize does not — it calls nn.quantize on every saved component regardless of the flag, so a skip_quantization component comes out of the "saved" side quantized (with scales/biases) while the reloaded "fresh" side stays unquantized (no scales/biases). The helper's exact key-set comparison (saved_weights.keys() == fresh_weights.keys()) will then fail for that component. This is a real gap in tiny_checkpoint_helper.py, not a quirk to work around in the test file — if you're writing a tiny test for krea2, qwen, or any other model with a skip_quantization component, stop and fix TinyCheckpointRoundtrip._quantize to skip those components (mirroring WeightApplier._quantize) rather than special-casing it in the test.__init__Derive the shrink knobs from the actual constructor. Do not pattern-match one example — they differ deliberately: ERNIE's components take explicit kwargs (hidden_size=, num_layers=), Ideogram 4's transformer takes an Ideogram4Config dataclass.
Look under src/mflux/models/<model>/model/. Shrink layer counts to 2, hidden and intermediate sizes to 128, head counts to 2, vocab to 128.
TinyVAEStandIn for the VAEThe real VAEs are fixed-size (~170M param) architectures with no dimension knobs, so they cannot be shrunk. ModelSaver/WeightLoader treat every component as an opaque parameter tree, so the stand-in walks the identical code path. Don't spend a loop trying to shrink a real VAE.
Dimensions are not independent. Look for constraints in the component's __init__ and attention/rope code:
rope_axes_dim must sum to head_dim (ERNIE: [16, 24, 24] → 64)head_dim must be consistent with hidden_size / num_attention_headsnum_key_value_heads must divide num_attention_headstensors_per_shardStart with tensors_per_shard=8. It forces shard boundaries so the index.json / weight_map multi-shard paths get exercised — the production size-based split (≥1 GB) never shards test-sized tensors.
The helper asserts len(shard_files) > component_count. If a model's tiny components have too few parameter tensors to clear that bar, lower the number until it passes — do not drop the argument, that silently removes multi-shard coverage.
The examples carry Fast twin of tests/model_saving/test_model_saving_<model>.py. Several models have weight definitions but no slow save test. Verify with ls tests/model_saving/ and adapt the comment rather than pointing at a file that isn't there.
component.name, not model_attr. The helper does a bare components[component.name] lookup, so a missing or misnamed key is a raw KeyError, not a helpful message. Most common first-try failure. Every component in get_components() needs an entry.seed=0 and fresh ones with seed=1. That difference is exactly what makes the final equality assertion prove values came off disk. Unifying them leaves the test green and meaningless.WeightDefinitionType. The helper types weight_definition as plain type on purpose — that TYPE_CHECKING union has drifted and omits several definitions. Widening a src type alias is out of scope; see the helper's comment.src/. A tiny test is a test-only addition. If a model appears to genuinely require a src change to be testable, stop and report that rather than making it.uv run --no-sync python -m pytest tests/model_saving/test_tiny_model_saving_<model>.py -vThen confirm nothing else broke with just test-fast (see mflux-testing).
The test should run in roughly a second. If it takes noticeably longer, or the network is touched, something is loading a real checkpoint — investigate rather than accepting it.
No. Verified empirically: MLX raises loudly on a bad dimension rather than silently skipping —
ValueError: [quantize] The last dimension of the matrix needs to be divisible
by the quantization group size 64. However the provided matrix has shape (100,100)so a dimension that isn't a clean multiple of the group size fails the test outright. It cannot leave a green-but-weak twin.
Relatedly, when inspecting a quantized tree you'll see some .weight entries with no matching .scales. Confirmed benign: those are 1-D norm weights and Conv2d layers, which nn.quantize correctly leaves alone. Only Linear-style layers quantize. Don't chase these.
| Symptom | Cause |
|---|---|
KeyError: '<name>' | _tiny_components() is missing a component, or keyed it by model_attr instead of name |
ValueError: [quantize] ... divisible by the quantization group size | A dimension isn't a multiple of this model's quantization_group_size |
assert len(shard_files) > component_count | Lower tensors_per_shard (step 5) |
| Values differ after reload | A real save/load bug — report it, do not weaken the assertion |
Shape assertion inside a component's __init__/forward | An interlocking dimension constraint (step 4) |
mflux-community/mflux
a3b21fc
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.