mflux-debugging
Compares PyTorch and MLX tensors to identify discrepancies in shapes, RoPE, and mathematical operations.
Install
mkdir -p .claude/skills/mflux-debugging && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/2707" && unzip -o skill.zip -d .claude/skills/mflux-debugging && rm skill.zipInstalls to .claude/skills/mflux-debugging
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Debug MLX ports by comparing against a PyTorch/diffusers reference via exported tensors/images (export-then-compare).Key capabilities
- →Export tensors and images from PyTorch reference
- →Load artifacts in MLX for numerical comparison
- →Isolate divergence via layer-by-layer checkpoints
- →Validate VAE decode and transformer forward paths
- →Inject reference latents to isolate RNG issues
How it works
The skill uses an export-then-compare workflow where deterministic artifacts are dumped from a PyTorch reference and loaded into MLX to verify numerical consistency across model layers.
Inputs & outputs
When to use mflux-debugging
- →Verifying numerical parity in MLX ports
- →Debugging layer-by-layer output divergence
- →Comparing MLX and PyTorch model outputs
- →Isolating math discrepancies in model architecture
About this skill
mflux debugging (MLX parity vs PyTorch/diffusers)
Use this skill when you are porting a model to MLX and need to prove numerical parity (or isolate where it diverges) versus a PyTorch reference implementation (often from diffusers).
This skill defaults to export-then-compare:
- Run the reference once and export deterministic artifacts (tensors + optional images).
- Load those artifacts in MLX and compare with clear thresholds.
When to Use
- You suspect a port mismatch (wrong shapes/layout, RoPE, scheduler math, dtype casting, etc).
- You want a repeatable workflow to narrow down the first layer/block where outputs diverge.
- You need evidence of correctness before refactoring (see
mflux-model-porting).
Ground Rules (repo norms)
- Use
uvto run Python:uv run python -m ... - If you run pytest, preserve outputs:
MFLUX_PRESERVE_TEST_OUTPUT=1(seemflux-testingand the Makefile test targets). - Do not update or replace reference (“golden”) images unless explicitly asked.
- Debug artifacts (tensor dumps) should live in a local folder and must not be committed unless explicitly asked.
- If you need the broader porting workflow (milestones, ordering, when to refactor), follow
mflux-model-porting. - RNG warning: PyTorch and MLX RNGs are different. Matching the same integer
seedis not enough for parity—export the exact initial noise/latents from the reference and load them in MLX. - Practical setup: the PyTorch reference repo (often
diffusers/) andmflux/are frequently next to each other on disk (e.g. both on your Desktop). Use absolute paths when in doubt.
Default Workflow (export-then-compare)
Preferred workflow: two tiny scripts + inline dumps
For day-to-day debugging, prefer a minimal paired repro:
- One simple script in the reference repo (often
diffusers/), e.g.diffusers/flux2_klein_edit_debug.py - One simple script in
mflux/, e.g.mflux/flux2_klein_edit_debug.py
Keep them “boring”: hardcoded variables, no cli, no framework, and just a few np.savez(...) / mx.save(...) lines at the right spot.
The key trick for RNG parity:
- In the reference script, compute latents once, save them, then pass them back into the pipeline (
latents=...) so the run definitely uses the dumped tensor. - In rare cases where a tensor needs to be saved from within a loop, make sure its name reflects the loop conditions (e.g the 4th noise predcition in a 10 step loop etc).
- In the MLX script, load that same latents file and feed it into the MLX run (do not rely on matching integer seeds).
1) Pick a single deterministic repro
- Fix seed(s), prompt(s), height/width, steps, guidance, and any input image paths.
- Keep the first repro small if possible (fewer steps, smaller resolution) to iterate quickly.
2) Decide your checkpoints (what to dump)
Start coarse, then narrow:
- VAE: packed latents before decode; optional intermediate activations for one block if needed.
- Transformer: hidden states at entry/exit of the model, then per-block (or every N blocks), then inside attention/MLP.
- Text encoder: token ids + attention mask, embeddings output, then per-layer hidden states if needed.
- Scheduler: timesteps/sigmas/alphas and the predicted noise/velocity per step.
Tip: work “backwards from pixels” like mflux-model-porting suggests: validate VAE decode first with exported latents, then the diffusion/transformer loop, then text encoder.
3) Export artifacts from the PyTorch reference (no logic changes)
Create a run directory like:
./debug_artifacts/<run_id>/ref/
Export with one of these patterns:
- NumPy:
np.savez(path, **tensors_as_numpy) - PyTorch:
torch.save(dict_of_tensors, path)
4) Run the MLX side with the same inputs and compare
Create a matching run directory:
./debug_artifacts/<run_id>/mlx/
Load and compare tensors. For each checkpoint, report:
- Shape + dtype
- max_abs_diff, mean_abs_diff
- max_rel_diff (guarding division by zero)
- Pass/fail with a clearly stated rtol/atol
- It is more important to inspect actual tensor values (e.g., first 10 elements) than rely on summary stats.
- Statistics can mislead; small-looking stats can hide systematic drift or sign flips.
- Prefer runtime tensor dumps over code reading; code can use different conventions yet still represent the same math.
Suggested tolerance starting points (adjust per component):
- fp32 comparisons:
atol=1e-5,rtol=1e-5 - fp16/bf16 comparisons:
atol=1e-2,rtol=1e-2 - If comparing images: compare both (a) tensor space before final clamp and (b) saved
pngvisually, since tiny numeric diffs can look identical.
If a checkpoint fails:
- Add an earlier checkpoint and repeat (binary search through the forward path).
Common Causes of Divergence (high-signal checklist)
- Layout mistakes: NCHW vs NHWC, transposes around convs/attention, flatten/reshape ordering. Some operations like convolutions can have different conventions between libraries.
- Broadcasting: scale/shift vectors applied on the wrong axis (common in RoPE and modulation).
- Dtype casting: reference silently upcasts to fp32 for norm/softmax; MLX path stays in fp16.
- RoPE details: position ids, reshape order, whether cos/sin are broadcast over heads vs sequence.
- Scheduler math: timestep indexing, sigma/alpha definitions, and off-by-one step order.
- Scheduler config: compare sigma schedules directly.
- Seed/RNG: ensure you aren’t comparing stochastic paths (dropout, noise sampling) without controlling RNG.
- Device dtype: MPS
float16can produce NaNs; preferbfloat16for reference dumps if you see NaNs. - Do not use CPU for comparisons; always keep reference runs on MPS to avoid misleading behavior.
Artifact Hygiene
- Prefer
debug_artifacts/<run_id>/...at repo root. - Do not commit
debug_artifacts/unless explicitly asked. - If you convert the parity check into a test, follow the repo’s testing conventions and preserve outputs (see
mflux-testing). - Clean up old artifacts when they are no longer needed, only focus on the current problem and avoid confusion with older artifacts that are not relevant for the current task.
Isolate transformer + VAE (inject reference initial latents)
When full end-to-end images differ between mflux and diffusers but you need to trust the core model (transformer, text encoder, VAE decode), export the reference initial noise and run mflux denoising from that tensor. If injected-latent mflux looks good while native-seed runs differ, the forward path is likely sound and any gap is probably RNG, scheduler, or optional reference-only components.
Do not commit these scripts or Desktop .npy files unless asked. Use inline uv run python <<'PY' ... PY heredocs in the reference repo and in mflux/.
Step 1 — Export initial latents from diffusers
Run from the local diffusers/ clone (often next to mflux/ on Desktop). Match mflux settings: height, width, seed, dtype (bfloat16 on MPS).
Load the reference pipeline the same way you would for a normal generation, but disable optional subsystems that mflux does not implement or download (check model_index.json vs mflux get_download_patterns() / weight definition). Pass None for unused components, set pipeline flags to skip them, and use local_files_only=True so missing weights surface immediately instead of downloading extras.
Then export noise with the same shape helper the pipeline uses internally:
from diffusers.utils.torch_utils import randn_tensor
pipe = <ReferencePipeline>.from_pretrained(
"<org/model>",
torch_dtype=torch.bfloat16,
local_files_only=True,
# plus any kwargs to omit optional reference-only modules
)
latent_h = height // pipe.vae_scale_factor # or read from pipeline/config
latent_w = width // pipe.vae_scale_factor
ch = pipe.transformer.config.in_channels # name varies by architecture
generator = torch.Generator(device="mps").manual_seed(seed)
latents = randn_tensor(
(1, ch, latent_h, latent_w),
generator=generator,
device="mps",
dtype=torch.bfloat16,
)
np.save("/path/to/init_latent.npy", latents.detach().cpu().float().numpy())
Step 2 — Denoise in mflux from the loaded latent
If generate_image() has no latents= argument, inline the denoising loop from the variant class instead of patching production code:
latents = mx.array(np.load(path)).astype(mx.bfloat16)- Text encode step from the variant (e.g.
_encode_prompts, prompt cache, etc.) - Any position encoding / conditioning setup the variant does before the loop
- Denoise loop: noise prediction +
config.scheduler.step(...)overconfig.time_steps - VAE decode +
ImageUtil.to_image(...)(or the variant’s decode helper) → save PNG
Also run one generate_image(seed=...) baseline on the same prompt for side-by-side.
How to read results
| Comparison | What it tells you |
|---|---|
| mflux native seed vs itself (re-run) | Sampling is deterministic on this hardware |
| mflux + diffusers latent vs diffusers full run | Transformer + VAE + CFG path quality |
| mflux native seed vs diffusers full run | Not a parity test — different RNG (mx.random vs torch.Generator) and often different sigma schedules |
| mflux + diffusers latent vs mflux native seed | Large diff is expected if schedulers/sigmas differ, even with identical starting noise |
If injected-latent mflux looks good, the port’s core forward path is trustworthy. Golden CI tests should still lock mflux-native sampling (see mflux-testing), not diffusers pixel parity.
diffusers reference runs (side-by-side images)
- mflux does not depend on diffusers; run reference scripts from a sibling
diffusers/clone withcd .../diffusers && uv run python. - Use
HF_HUB_OFFLINE=1when
Content truncated.
When not to use it
- →When the task requires end-to-end pixel parity without isolating components
- →When the reference implementation is not available or non-deterministic
Prerequisites
Limitations
- →Requires manual creation of paired repro scripts
- →Numerical stats can hide systematic drift or sign flips
- →Does not support automated cross-framework RNG matching
How it compares
Unlike manual debugging, this skill enforces a structured parity workflow by injecting reference latents and comparing intermediate tensor activations against defined thresholds.
Compared to similar skills
mflux-debugging side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| mflux-debugging (this skill) | 3 | 2mo | No flags | Advanced |
| langsmith-observability | 4 | 7mo | Review | Intermediate |
| debug-distributed | 1 | 5mo | Review | Advanced |
| aoti-debug | 1 | 5mo | Review | Advanced |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by filipstrand
View all by filipstrand →You might also like
langsmith-observability
davila7
LLM observability platform for tracing, evaluation, and monitoring. Use when debugging LLM applications, evaluating model outputs against datasets, monitoring production systems, or building systematic testing pipelines for AI applications.
debug-distributed
inclusionAI
Guide for debugging distributed training issues in AReaL. Use when user encounters hangs, wrong results, OOM, or communication errors.
aoti-debug
pytorch
Debug AOTInductor (AOTI) errors and crashes. Use when encountering AOTI segfaults, device mismatch errors, constant loading failures, or runtime errors from aot_compile, aot_load, aoti_compile_and_package, or aoti_load_package.
benchmark-kernel
flashinfer-ai
Guide for benchmarking FlashInfer kernels with CUPTI timing
debug-model
vladmandic
Debug a broken SD.Next or Diffusers model integration. Use when a newly added or ported model fails to load, misdetects, crashes during prompt encoding or sampling, or produces incorrect outputs.
tokenizer-checker
openvinotoolkit
Validate a HuggingFace tokenizer with OpenVINO Tokenizers and OpenVINO GenAI. Use when: checking if a tokenizer converts and works correctly, verifying tokenizer/detokenizer accuracy, testing normalization steps, checking GenAI Tokenizer compatibility.