Files
Patrick Rachford 27608f217e [Skills] Adds a debugging skill for model development
MODULAR_ORIG_COMMIT_REV_ID: 30900b4e6168b17c9b280ba98edaa983a0d0c59a
2026-06-12 07:53:59 +00:00

167 lines
6.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Reading the layer-by-layer debug output
> **For parity/coherence failures** (server runs but output is wrong), load
> the [`debug-model`](../../debug-model/SKILL.md) skill
> first. That protocol mandates per-layer tensor-dump comparators and parallel
> investigation agents. This reference covers the quick `compare_layers.py`
> probe that `import-model` runs before handing off.
The divergence hunt has two layers of debugging:
1. **`compare_layers.py`**: automated logit probes (what the script actually
runs).
2. **Per-layer tensor dumps**: HF vs MAX comparators (see
`debug-model`; preferred over scalar `ops.output()` taps).
## Running `compare_layers.py`
From the repo root (via the OSS shim):
```bash
pixi run python compare_layers.py <HF_MODEL_ID> \
--slug <your_slug> \
--port 8000 \
--prompt "The capital of France is"
```
Requires `pixi run max serve` with `--custom-architectures <port_dir>` (the slug
folder, not its parent).
Flags:
| Flag | Purpose |
|---------------------------------|--------------------------------------------------------------------|
| `--dtype bfloat16\|float32` | Must match `--quantization-encoding` on serve (default `bfloat16`) |
| `--skip-hf-layers` | Skip HF hidden-state stats (faster) |
| `--skip-multi` | Skip multi-position prefix probe |
| `--long-prompt "..."` | Prompt for multi-position probe (~110 tokens default) |
| `--positions 1,5,20,50,100,200` | Prefix lengths to compare |
Preflight before serve (guard + scaffold):
```bash
pixi run python check_walls.py <HF_MODEL_ID>
pixi run python run_oss_gates.py <HF_MODEL_ID> --port-dir <port_dir>/
```
After serve (during the divergence hunt):
```bash
pixi run python run_oss_gates.py <HF_MODEL_ID> \
--port-dir <port_dir>/ --phase verify --slug <slug> --port 8000
```
---
## What the script prints
### 1. HF hidden-state stats (HF-only)
```text
layer mean max_abs norm
0 0.0001 0.4200 12.34
...
```
These rows are **diagnostic only** — MAX does not expose hidden states through
the completions API. Use them to confirm HF loads sanely (no NaNs, reasonable
norms). They do **not** compare against MAX.
### 2. Short-prompt top-1 logprob
```text
check hf max rel_diff verdict
top1_logprob -0.1234 -0.1250 0.0130 ok
```
| Verdict | Meaning |
|------------|--------------------------------------------------|
| `ok` | Relative logprob difference &lt; 5% |
| `DIVERGED` | Prefill logits disagree at the last prompt token |
If this row diverges at token 0, the bug is early: tokenizer, embedding,
norm order, or attention block 0.
### 3. Multi-position top-1 probe
```text
pos hf_id max_text verdict
1 1234 ' the' ok
5 5678 ' fox' ok
50 9012 ' lap' DIVERGED
```
The script decodes HF's argmax token id and compares to MAX's greedy completion
text at each prefix length.
**First `DIVERGED` row** localizes position-dependent bugs:
| First divergence at | Likely cause |
|---------------------|-----------------------------------------------|
| Position 1–5 | Tokenizer, embedding, early attention |
| Position 5–20 | GQA repeat, QK-norm, head layout |
| Position 50+ | RoPE style/theta, partial-RoPE padding |
| Position 100+ | Decode-path position handling, sliding window |
See [divergences.md](divergences.md) for fixes.
---
## Per-layer tensor dumps (when logit probes aren't enough)
When short and multi-position probes pass but output is still wrong, or you need
to know *which sublayer* diverged, follow
[`debug-model`](../../debug-model/SKILL.md): build HF and MAX dumpers, run the
comparator, and read
[`comparator-output-patterns.md`](../../debug-model/references/comparator-output-patterns.md).
Use `ops.output(...)` taps only as fine-grained sub-taps *after* the lead
agent localizes the broken subsystem, not as the primary bisect loop.
### Interpreting tap diffs
If the first divergence is at:
- **post-embed** — check `weight_adapters.py` for `embed_tokens.weight`; check
`tie_word_embeddings`.
- **post-attn (layer 0)** — attention: Q/K/V naming, RoPE style, GQA, QK-norm,
sliding-window mask.
- **post-mlp (layer 0)** — MLP: activation variant (`gelu_new` vs `gelu_tanh`),
gated layout, bias terms.
- **post-block-norm** but attn and MLP ok — block wiring: pre-norm vs parallel
vs dual-norm, residual order, MuP multipliers.
### Scaling vs direction bugs
- **Similar direction, different magnitude** — missing scale factor, wrong
softmax scale, wrong norm formulation (`x * weight` vs `x * (1 + weight)`).
- **Similar magnitude, low cos_sim** — RoPE style mismatch, head permutation,
sign error in `rotate_half`.
---
## Iteration loop
1. Run `compare_layers.py` (or `run_oss_gates.py --phase verify`).
2. Localize from the first `DIVERGED` row (logprob row or multi-position row).
3. Open the matching HF `forward()` for that layer/component.
4. Fix one operation in `<slug>.py` or `weight_adapters.py`.
5. **Restart `pixi run max serve`** — stale compiled graphs hide fixes.
6. Re-run.
Each pass should push the first divergence later. If it doesn't, revert and
re-read HF source.
---
## Limitations
- MAX completions API has no per-layer hidden-state export — taps are manual.
- Top-1 text comparison assumes greedy decode; use `temperature=0.0` on serve.
- HF reference loads with `trust_remote_code=True` and `device_map="auto"`.
- Both sides must use the same dtype (`--dtype` must match serve encoding).
For `trust_remote_code` models that NaN with `.to("cuda")`, see
[divergences.md](divergences.md) #19 and
[pitfalls-serving.md](pitfalls-serving.md#trust_remote_codetrue-with-tocuda-can-produce-nan).