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).

Datos de origen

Repositorio
InternLM/xtuner
Última actividad en el origen
2 de junio de 2026 a las 04:59
Idioma detectado de SKILL.md
inglés
Estrellas
5207
Forks
455

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
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
Ver en GitHub
Este SKILL.md es muy grande, por eso SkillsMP muestra aqui solo la primera seccion. Ver en GitHub