Skip to main content

add-hf-model

Add support for a HuggingFace model in XTuner so it can be trained with the full set of parallelism strategies and optimization switches. The user can provide a HuggingFace Hub repo id, a local model directory, or a model family already supported by `transformers`. This skill walks through the full path: locating the reference implementation, classifying the model, splitting new code across the model / module / ops layers, implementing the XTuner model class and config, registering the entry point, validating bitwise numerical parity with HuggingFace, writing regression tests, and enabling the training optimizations (EP/SP, micro-batch, torch.compile, fp8, activation offload).

Quellinformationen

Repository
InternLM/xtuner
Letzte Quellaktivität
2. Juni 2026 um 04:59
Erkannte Sprache von SKILL.md
Englisch
Sterne
5.211
Forks
457

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
add_hf_model
description
Add support for a HuggingFace model in XTuner so it can be trained with the full set of parallelism strategies and optimization switches. The user can provide a HuggingFace Hub repo id, a local model directory, or a model family already supported by `transformers`. This skill walks through the full path: locating the reference implementation, classifying the model, splitting new code across the model / module / ops layers, implementing the XTuner model class and config, registering the entry point, validating bitwise numerical parity with HuggingFace, writing regression tests, and enabling the training optimizations (EP/SP, micro-batch, torch.compile, fp8, activation offload).
# Add a HuggingFace model to XTuner A complete handbook for porting any HuggingFace causal LM into XTuner with correct training behavior. Covers four buckets: **Dense LLM**, **MoE LLM**, **VLM / compose model**, and **`trust_remote_code` model**. Every step below is grounded in concrete `file:line` references in this repository — read the cited code before writing yours. > **Anti-spaghetti reminder** — XTuner separates *what the model is* (`*Config`), > *what it computes* (`Dense` / `MoE` / `BaseComposeModel` subclass), and *how its > weights map to HF safetensors* (`to_hf_key_list`, optional `safetensors_to_params` > / `param_to_safetensor`). Keep new code inside those three seams. **Never** add > `if model_name == "..."` branches to the base classes. --- ## 0. Locate the source code The only goal of this step is to find the **HuggingFace reference implementation you will port from**, and to note the `model_type` you will register under. Do not pick attention/router/layer here — that happens naturally in §1 as you read the code. 1. If the user gives a **remote-code** model, the modeling code ships in the repo as `modeling_*.py` — read it directly. 2. If the user gives **weights only**, confirm the target Python environment with the user, then find the matching implementation inside that env's `transformers` install. If no code exists for that `model_type`, stop and ask the user. 3. Confirm the weights are **training-suitable** — at least bf16 — so training precision can be aligned later. Flag fp8/int4-only checkpoints to the user. 4. Note two strings from the HF config — you will need both downstream: ```python from transformers import AutoConfig cfg = AutoConfig.from_pretrained(path, trust_remote_code=True) cfg.model_type # registration key (§4) cfg.architectures[0] # the modeling class you are mirroring ``` --- ## 1. How to do the migration XTuner splits a model into three layers. You port by reusing what already exists at each layer and only adding what is genuinely new — so the work is "read the HF modeling code, then match each of its pieces to the XTuner component below". The locations are listed plainly; you do not need extra guidance to map your model's attention / router / layer onto the matching one. - **model layer** (`xtuner/v1/model`) — the model body. - Dense body: `xtuner/v1/model/dense/dense.py` - MoE body: `xtuner/v1/model/moe/moe.py` - Multi-modal (compose) bodies: `xtuner/v1/model/compose/` — reuse the language model and add only the ViT / projector pieces. - **module layer** (`xtuner/v1/module`): - Decoder layers: `DenseDecoderLayer`, `MoEDecoderLayer` — `xtuner/v1/module/decoder_layer`. Small changes add a switch on the existing layer; a genuinely novel architecture may add `{model_name}_decoder_layer`. - Attention configs: `MHAConfig` (`xtuner/v1/module/attention/mha.py`), `MLAConfig` (`.../mla.py`, DeepSeek-style latent), `GatedDeltaNetConfig` (`.../gated_deltanet.py`, linear). - Router configs (MoE): `GreedyRouterConfig` (`xtuner/v1/module/router/greedy.py`, softmax top-k — Qwen3 MoE, GptOss), `NoAuxRouterConfig` (`xtuner/v1/module/router/noaux_router.py`, grouped sigmoid aux-loss-free — DeepSeek V3). - Also: rope, rms_norm, etc. - **ops layer** (`xtuner/v1/ops`) — kernels such as attention and rms_norm. ### Existing models to copy from Pick the one whose attention + (router) match yours; the closer it is, the smaller your diff. | Your model is… | Copy from | |--------------------------------------|--------------------------------------| | Dense LLM | `xtuner/v1/model/dense/qwen3.py` | | MoE LLM | `xtuner/v1/model/moe/qwen3.py` | | MoE with MLA + aux-loss-free router | `xtuner/v1/model/moe/deepseek_v3.py` | | MoE needing weight-layout transform | `xtuner/v1/model/moe/gpt_oss.py` | | Compose / VLM | `xtuner/v1/model/compose/qwen3_vl/` | After you have a plan, **write an integration design doc into `docs/design/model`** and confirm with the user that the decomposition is reasonable before implementing. ### File layout for the new model | Bucket | Model file | Public re-export | Test file | |----------------------|-------------------------------------------------------------|-----------------------------------------------|--------------------------------------------| | Dense LLM | `xtuner/v1/model/dense/<name>.py` | `xtuner/v1/model/__init__.py` | `tests/model/test_<name>_dense.py` | | MoE LLM | `xtuner/v1/model/moe/<name>.py` | `xtuner/v1/model/__init__.py` | `tests/model/test_<name>_moe.py` | | Compose / VLM | `xtuner/v1/model/compose/<name>/...` | `xtuner/v1/model/__init__.py` | `tests/model/test_<name>.py` | --- ## 2. Implement the model class Subclass `Dense`, `MoE`, or `BaseComposeModel`. You typically only need to override one method. ### 2.1 `to_hf_key_list(self, key: str) -> list[str]` — **mandatory, abstract on `BaseModel`** Declared at `xtuner/v1/model/base.py`. Translates an **XTuner-side** parameter name into one or more **HF-side** safetensors keys. Return-list cardinality: - **1 → 1** for plain weights (most params). - **1 → N** for fused MoE experts (one fused param explodes into N per-expert HF keys; see `xtuner/v1/model/moe/qwen3.py`). Patterns you almost always need (study `xtuner/v1/model/dense/qwen3.py` and `xtuner/v1/model/moe/qwen3.py`): ```python # 1. Tied embeddings: redirect lm_head → embed_tokens before adding the model. prefix. if self.config.tie_word_embeddings and "lm_head" in key: key = key.replace("lm_head", "embed_tokens") # 2. Add the "model." prefix that HF wraps everything in. if "layers" in key or "embed_tokens" in key: key = "model." + key # 3. HF MoE nests experts/gate under .mlp.; XTuner does not. if "layers" in key: key = re.sub(r"layers\.(\d+)\.(experts|gate)", r"layers.\1.mlp.\2", key) # 4. Top-level norm. if key.startswith("norm."): return [key.replace("norm.", "model.norm.")] # 5. MoE expert fusion: one fused param → N HF keys. if "fused_w1w3.weight" in key: out = [] for i in range(self.config.n_routed_experts): out.append(key.replace("fused_w1w3.weight", f"{i}.gate_proj.weight")) out.append(key.replace("fused_w1w3.weight", f"{i}.up_proj.weight")) return out # 6. Model-specific buffers (e.g. FoPE: rotary_emb.sin_coef / cos_coef are persistent buffers). if key.startswith("rotary_emb."): return [key.replace("rotary_emb.", "model.rotary_emb.")] ``` A prefix-region remap that differs by *deployment* (e.g. a text tower nested under a VLM as `model.language_model.`) does **not** belong here — put it in the config's `hf_key_mapping`. See §5.1. ### 2.2 `safetensors_to_params` / `param_to_safetensor` — **optional, only for layout mismatches** If the HF storage layout cannot be expressed as “same tensor, different name”, you must transform the bytes. Canonical example: GptOss stores expert weights as `(num_experts, hidden_size, expert_dim * 2)` but XTuner fuses them as `(num_experts * 2 * expert_dim, hidden_size)` — see overrides at `xtuner/v1/model/moe/gpt_oss.py`. Use this hook only when transposing/reshaping is unavoidable. Renames go in `to_hf_key_list`; numerics go here. ### 2.3 Do **not** override - `build_layers`, `build_embeddings`, `_init_weights` — the base handles them via the config. Override only if the new model adds a layer type the base cannot express (rare). - `forward` — same reasoning. If you find yourself touching it, re-evaluate the bucket choice; the problem is probably in the config (`attention`, `router`, `first_k_dense_replace`). --- ## 3. Implement the config Two layers: a **base config** that reads HF and a **size-specific subclass** per released checkpoint. ### 3.1 Base config — three required members Mirror `Qwen3MoEConfig` (`xtuner/v1/model/moe/qwen3.py`) or `Qwen3DenseConfig` (`xtuner/v1/model/dense/qwen3.py`). 1. **`build(self) -> <ModelClass>`** — return `<ModelClass>(self)`. 2. **`@classmethod from_hf(cls, hf_path) -> Self`** — read the HF config and map its fields one-to-one onto your config. The field set is whatever the live config class declares (`TransformerConfig` in `xtuner/v1/model/base.py`, `MoEConfig` in `xtuner/v1/model/moe/moe.py`); read it, don't work from a hard-coded list. A few non-obvious traps: - Use `RopeParametersConfig.from_hf_config(hf_config)` from `xtuner/v1/module/rope/rope.py`. It already handles both the `rope_parameters` dict (HF ≥ 5.2.0) and the legacy `rope_scaling` dict (HF 4.57.x), plus YARN / FoPE / mrope special fields. **Do not** parse rope yourself. - For optional HF fields, use `getattr(hf_config, "<field>", <default>)` rather than `hf_config.<field>` — older checkpoints drop fields. 3. **`@property hf_config(self) -> <HFConfigClass> | None`** — the inverse of `from_hf`: every field `from_hf` reads must be re-emitted so `save_hf` round-trips. **Built-in vs. `trust_remote_code` — the only axis that changes `from_hf` / `hf_config`:** a `trust_remote_code` model is structurally still Dense / MoE / VLM; the *only* difference is whether `transformers` ships a built-in `<Name>Config` class. Decide which case you are in and follow the matching column — this is the single place that contract is defined. | | Built-in config (e.g. Qwen3) | `trust_remote_code` (no built-in config) | |---|---|---| | `from_hf` reads via | `<HFConfigClass>.from_pretrained(hf_path)` | `AutoConfig.from_pretrained(hf_path, trust_remote_code=True)`, then `assert hf_config.model_type == "<expected>"` | | `hf_config` returns | a populated `<HFConfigClass>` re-emitting every field | `None` | | `save_hf` weight-map | written from `hf_config` | same | | `save_hf` side files | derived from `hf_config` | falls back to copying `config.json` / tokenizer / `*.py` from `self._hf_path` so the dir stays loadable with `trust_remote_code=True`; module-cache test in the matrix below | Compare `Qwen3MoEConfig.hf_config` (built-in) and `Qwen3MoEFoPEConfig.hf_config` (returns `None`), both in `xtuner/v1/model/moe/qwen3.py`. **Do not** invent a built-in HF config class for a remote-code model — return `None`. **Invariants:** - `from_hf` ↔ `hf_config` must round-trip. The `test_save_hf` test (§7) is the enforcement mechanism — every key in the original index must appear in the saved index with byte-equal tensors. - The config must be **pickleable and side-effect free**. No env vars, no file I/O, no `torch.cuda.*` at construction time. ### 3.2 Size-specific subclasses Add one subclass per released checkpoint that hard-codes the published dimensions. Mirror `Qwen3MoE30BA3Config` / `Qwen3MoE235BA22Config` in `xtuner/v1/model/moe/qwen3.py`. Even if only one size ships, define at least one — it is the entry point for `get_model_config` and for tests. --- ## 4. Register the entry point Open `xtuner/v1/model/__init__.py` — all four edits are short: 1. **Import** — bring in the new config classes. 2. **Alias** — add `"<alias>": <NewSizeConfig>()` to `model_mapping`. Lookup
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen