Skip to main content

model-normalize

Normalize an HF model checkpoint — quantize bf16/fp16 weights to per-block FP8, and/or repack engine-named safetensors into HF-standard ~4GB shards.

Source facts

Repository
InternLM/xtuner
Last source activity
May 25, 2026 at 08:38
Detected SKILL.md language
English
Stars
5,207
Forks
455

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
6 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
model_normalize
description
Normalize an HF model checkpoint — quantize bf16/fp16 weights to per-block FP8, and/or repack engine-named safetensors into HF-standard ~4GB shards.
# model_normalize Two complementary capabilities for getting a training-engine checkpoint into a format that HF / vLLM / SGLang can consume: 1. **FP8 quantization** of large linears, in two modes: - **Reference-guided** — point at an existing FP8 checkpoint of the same architecture; the tool reads its index and reproduces the exact same quantization decisions on the source. - **Heuristic** — apply a built-in rule set that quantizes only the large matrix multiplications (attention, MLP, MoE experts) and keeps norms, routers, biases, embeddings, and vision tower in their original dtype. 2. **Standard repack** — re-shard arbitrary safetensors directories into the HF-standard ``model-{i:05d}-of-{n:05d}.safetensors`` layout with ~4GB shards and a fresh ``model.safetensors.index.json``. When ``to-fp8`` detects non-standard source shard names, it automatically runs the repack step on the quantized output, so the user-visible result is always a clean HF-conformant directory. ## Layout ``` .claude/skills/model_normalize/ ├── SKILL.md # this file ├── model_normalize.py # top-level CLI (to-fp8 / repack) ├── hf_to_fp8.py # FP8 conversion library + standalone CLI ├── heuristics.py # codified default quantization rule set ├── repack_hf.py # 4GB-shard repacker library + CLI └── convert_interns2_to_fp8.sh # example invocation for InternS2 ``` ## Usage ### Mode 1 — Reference-guided FP8 conversion ```bash python model_normalize.py to-fp8 \ --source /path/to/bf16-model \ --output /path/to/fp8-model \ --reference /path/to/fp8-reference ``` Behavior: - Loads the reference's ``model.safetensors.index.json``. - For each ``<name>_scale_inv`` entry in the reference index, marks ``<name>`` as an FP8-quantized tensor. - Applies that exact set to the source as a literal name match (no regex surprise; works even when names don't follow any pattern). ### Mode 2 — Heuristic FP8 conversion (no reference) ```bash python model_normalize.py to-fp8 \ --source /path/to/bf16-model \ --output /path/to/fp8-model ``` The rule set is defined in ``heuristics.py``. Concretely, **quantize iff** the tensor name matches one of: | Group | Pattern (anchored) | | -------------------------------- | ------------------------------------------------------------------------------------------------------------------- | | Self-attention projections | ``[…]layers.<i>.self_attn.{q,k,v,o}_proj.weight`` | | Dense MLP | ``[…]layers.<i>.mlp.{gate,up,down}_proj.weight`` | | MoE per-expert (unfused) | ``[…]layers.<i>.mlp.experts.<j>.{gate,up,down}_proj.weight`` | | MoE fused-expert (3D tensors) | ``[…]layers.<i>.mlp.experts.{gate_up_proj, down_proj}`` | | MoE shared expert | ``[…]layers.<i>.mlp.shared_expert.{gate,up,down}_proj.weight`` | | Linear-attn wide projections | ``[…]layers.<i>.linear_attn.{in_proj_qkv, in_proj_z, out_proj}.weight`` | The ``[…]`` prefix allows ``model.`` and ``model.language_model.``; the same patterns repeat under ``mtp.layers.<i>.`` for multi-token-prediction blocks. **Everything else is kept in the original dtype**, in particular: - all norms (``*norm*``, ``*_layernorm.*``) - MoE routers (``mlp.gate.weight``, ``mlp.shared_expert_gate.weight``) - embeddings (``embed_tokens``, ``lm_head``, ``pos_embed``, ``patch_embed``, ``mtp.fc``) - vision tower (``model.visual.*``) - all ``*.bias`` - ``linear_attn`` control-flow tensors (``A_log``, ``conv1d.weight``, ``dt_bias``, ``in_proj_a/b.weight``, ``norm.weight``) This rule set was validated against the InternS2 / Qwen3-MoE FP8 references: it reproduces their quantization decisions on every tensor (no false positives or false negatives). ### Mode 3 — Standalone repack (no quantization) ```bash python model_normalize.py repack \ --source /path/to/messy-shards \ --output /path/to/standard-shards \ --shard-size-gb 4 ``` Greedy bin-packs tensors into ``model-{i:05d}-of-{n:05d}.safetensors`` shards of at most ``--shard-size-gb`` GiB, writes a fresh ``model.safetensors.index.json`` (including ``metadata.total_size``), and copies all non-safetensors files (config, tokenizer, etc.) through. ## When to use which mode | Situation | Mode | | ---------------------------------------------------------------------------- | ------------------ | | You already have an FP8 reference for this architecture. | Reference-guided | | New architecture / first-time conversion; standard naming. | Heuristic | | Quantized model has engine-specific shard names; need HF-conformant output. | (auto, see below) | | You only need to standardize shard naming; weights are already FP8 / bf16. | ``repack`` | The ``to-fp8`` subcommand auto-detects non-standard source shard names (anything that isn't ``model.safetensors`` or ``model-NNNNN-of-NNNNN.safetensors``) and chains the repack step. Pass ``--no-repack`` to keep the source naming. ## Quantization details - Block size **128 × 128**, dtype ``float8_e4m3fn`` with saturating cast. - Per-block scale stored as ``<name>_scale_inv`` (``float32``) in the same shard as the parent weight. Layout matches what vLLM / SGLang load for per-block FP8. - 3D MoE-expert tensors (shape ``(E, D0, D1)``) are quantized per-expert; the per-expert scales are stacked along a leading dim. - ``config.json`` receives a ``quantization_config`` block with ``quant_method=fp8``, ``weight_block_size=[128, 128]``, ``scale_fmt=ue8m0``, and an explicit ``modules_to_not_convert`` list derived from the actual quantization decisions. ## Post-conversion handoff (mandatory for the agent) After **any** successful run of `to-fp8` or `repack`, the agent must ask the user — via `AskUserQuestion` — where to **place** the produced `*.safetensors` and `model.safetensors.index.json` files. The conversion output is treated as a staging area; the user decides the final home. Offer at least these options: 1. **Keep in place** — leave the files at the `--output` path as-is. 2. **Copy to a new directory** — let the user specify a target; copy the shards + index there. Always **copy** (`cp`), never move — the `--output` staging directory must remain intact unless the user explicitly asks to delete it as a separate step. 3. **Copy into an existing model directory** — copy shards/index into a user-specified directory while preserving its other files (tokenizer, config, etc.). If the user picks (2) or (3), execute the copy with `cp` (confirming the destination path back to the user before any overwrite). Do not infer the destination from earlier turns — the user may have a fresh target in mind each time. Cleaning up the staging directory afterwards is a separate, user-initiated step — never delete it as part of the handoff. ## Example: InternS2 Preview ```bash python model_normalize.py to-fp8 \ --source /mnt/shared-storage-user/puyudelivery/user/yehaochen/models/InternS2PreviewCandidate5 \ --output /tmp/InternS2-FP8 \ --reference /mnt/shared-storage-user/puyudelivery/user/yehaochen/models/InternS2PreviewCandidate5-FP8 ``` The source uses engine-specific names like ``model-language-0001-fused-save_rank0.safetensors``; the output will be repacked into the standard ``model-00001-of-NNNNN.safetensors`` layout.
View on GitHub