Transformers documentation
モジュラーなtransformersでモデルを追加する
モジュラーなtransformersでモデルを追加する
Modular transformers は、単一モデル、単一ファイルのポリシーとは対照的に、インポートと継承を許可することで、モデルを追加するために必要なコード量を削減します。モデルのコンポーネントをファイル間で繰り返す代わりに、モデルフォルダに modular ファイルを追加し、既存のクラスから継承します。
コンバーターは、モジュール化されたファイルからスタンドアロンファイルを生成します。ユーザーは、すでに慣れ親しんでいる単一ファイルのインターフェースをそのまま利用できます。
Modular transformersは、レガシーなモデリングコードを置き換えることを目的としていません。既存のモデルをベースにしないモデルの場合は、
modeling.pyファイルを手動で追加してください。同様に、類似のファイルからクリーンに継承できない設定、トークナイズ、またはプロセッシングのファイルにも、同じことが当てはまります。唯一の正しい順序というものもありません。一部のコントリビューターはまずモジュール化されたファイルを作成し、そこから生成します。別の一部のコントリビューターは、手書きの
modeling.pyから始め、後でモジュール化されたファイルにリファクタリングします。どちらのアプローチでも機能します。
モジュール化されたファイルの実装
まずは、Transformers で自分のモデルに似たものを探してください。Mistral、Qwen2、Cohere、Cohere2、Llama が良い出発点です。以下の表は、一般的なコンポーネントを継承できるモデルに対応付けしています。
| コンポーネント | モデル |
|---|---|
| エキスパートの混合 | MixtralまたはQwen2-MoE |
| インターリーブ(および/または部分的)ローテーション埋め込み | GLM、Phi |
| 状態空間モデル | Jamba、Bamba、Zamba、Mamba2 |
| 再帰的隠れ状態 | Gemma2 |
| レイヤーごとのスライディングウィンドウアテンション/フルアテンションパターン | Gemma2、Cohere2 |
| QKVクリッピング | Olmo |
| QK正規化 | Olmo2、Cohere |
| Fused QKV(非推奨) | Phi3 |
modular-detector-v2ツールを使用して、継承元の既存実装を探します。コードスニペットを貼り付けると、Transformers内で最も類似したメソッドが返されるため、コーディングを開始する前に最適な親クラスを特定できます。
既存のモデルを、新しいモデルの継承を機能させるために変更しないでください。親クラスの名前変更やサブクラス化が不自然な場合は、関連するコードを直接コピーする方が適切です。
src/transformers/models/<name>/modular_<name>.py を作成します。ここで <name> は、snake_case のモデルディレクトリ名と一致する必要があります。このセクションでは、モジュラーアプローチを用いて Olmo から Olmo2 を実装する方法を説明します(元の modular_olmo2.py ファイルを参照してください)。
設定
Olmo2ConfigとOlmoConfigには、2つの点で違いがあります。
- 新しい引数
rms_norm_epsが追加されました。 clip_qkv引数はもはや使用されません。
新しい引数は、デフォルト値を持つクラスレベルの型アノテーションとして宣言します。削除する引数については、生成されるファイル内で継承された属性を抑制するために、AttributeError()を代入します(属性の削除を参照)。
- @auto_docstring(checkpoint="allenai/OLMo-7B-hf")
+ @auto_docstring(checkpoint="allenai/Olmo2-7B-1124-hf")
+ @strict
- class OlmoConfig(PreTrainedConfig):
+ class Olmo2Config(OlmoConfig):
...
- model_type = "olmo"
+ model_type = "olmo2"
...
+ rms_norm_eps: float = 1e-5
- clip_qkv: float | None = None
+ clip_qkv = AttributeError()@auto_docstring は標準的な引数のドキュメントを自動的に生成します(@auto_docstring ガイドを参照)。@strict はインスタンス化時に未知の kwargs を拒否し、タイポや古い引数を早期に検出します。デコレーターは親クラスから継承されないため、すべての config クラスに両方を追加してください。親 config にすでにこれらが存在する場合でも、明示的に宣言してください。
派生属性を設定したり、後方互換性のロジックを処理したりする場合は、__init__ の代わりに __post_init__ を使用します。例えば、Cohere2 は初期化時に head_dim を計算し、layer_types を導出します。
def __post_init__(self, **kwargs):
if self.num_key_value_heads is None:
self.num_key_value_heads = self.num_attention_heads
self.head_dim = self.hidden_size // self.num_attention_heads
super().__post_init__(**kwargs)テンソル並列またはパイプライン並列をサポートするモデルでは、base_model_tp_plan と base_model_pp_plan を config のクラスレベルの辞書として定義します。これらの辞書は、モデルをデバイス間でシャードする方法を定義します。例として、Olmo2 や Cohere2 などの既存の config を参照してください。
class MyNewModelConfig(PreTrainedConfig):
model_type = "my_new_model"
# Tensor parallelism: maps layer name patterns to sharding strategies.
# Use "colwise" / "rowwise" for standard sharding, or the "gather_output" /
# "split_input" variants when an extra op (e.g. a QK norm) prevents fusing.
base_model_tp_plan = {
"layers.*.self_attn.q_proj": "colwise",
"layers.*.self_attn.k_proj": "colwise",
"layers.*.self_attn.v_proj": "colwise",
"layers.*.self_attn.o_proj": "rowwise",
"layers.*.mlp.gate_proj": "colwise",
"layers.*.mlp.up_proj": "colwise",
"layers.*.mlp.down_proj": "rowwise",
}
# Pipeline parallelism: maps submodule names to their (input, output) tensor names.
base_model_pp_plan = {
"embed_tokens": (["input_ids"], ["inputs_embeds"]),
"layers": (["hidden_states", "attention_mask"], ["hidden_states"]),
"norm": (["hidden_states"], ["hidden_states"]),
}ノルム
変更を加えずに親クラスをコピーするには、pass を継承します。リンターは親クラスのコンテンツをコピーし、新しいモデルに合わせてすべての参照をリネームします。
from ..olmo.modeling_olmo import OlmoRotaryEmbedding
class Olmo2RotaryEmbedding(OlmoRotaryEmbedding):
pass特定の動作を変更するには、異なる部分のみを継承してオーバーライドします。Olmo2RMSNormはLlamaRMSNormと1行だけ異なります。乗算は、入力dtypeへキャストし直す前に行われ、後に行われるわけではありません。
from ..llama.modeling_llama import LlamaRMSNorm
class Olmo2RMSNorm(LlamaRMSNorm):
def forward(self, hidden_states):
input_dtype = hidden_states.dtype
hidden_states = hidden_states.to(torch.float32)
variance = hidden_states.pow(2).mean(-1, keepdim=True)
hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
- return self.weight * hidden_states.to(input_dtype)
+ return (self.weight * hidden_states).to(input_dtype)アテンション
Olmo2 のアテンションは Olmo と同一ですが、クエリとキーに RMSNorm を適用し、qkv クリッピングを削除しています。super().__init__(...) は親クラスの本体をコピーし、2 行の新しい norm 行を追加します。forward は、クエリとキーが投影の前に norm を通るようになったため、完全に再定義されています。リンターは、apply_rotary_pos_emb、eager_attention_forward およびそれらの依存関係を含む、インポートされた関数をすべて生成ファイルに引き込みます。
class Olmo2Attention(OlmoAttention):
def __init__(self, config: Olmo2Config, layer_idx: int | None = None):
super().__init__(config, layer_idx=layer_idx)
+ self.q_norm = Olmo2RMSNorm(config.num_attention_heads * self.head_dim, config.rms_norm_eps)
+ self.k_norm = Olmo2RMSNorm(config.num_key_value_heads * self.head_dim, config.rms_norm_eps)
def forward(self, ...):
...
- query_states = self.q_proj(hidden_states)
- key_states = self.k_proj(hidden_states)
+ query_states = self.q_norm(self.q_proj(hidden_states))
+ key_states = self.k_norm(self.k_proj(hidden_states))
value_states = self.v_proj(hidden_states)
- if self.config.clip_qkv is not None:
- query_states.clamp_(min=-self.config.clip_qkv, max=self.config.clip_qkv)
- key_states.clamp_(min=-self.config.clip_qkv, max=self.config.clip_qkv)
- value_states.clamp_(min=-self.config.clip_qkv, max=self.config.clip_qkv)
-
...DecoderLayer
super().__init__(...) の後、norm 属性を Olmo2RMSNorm のインスタンスで上書きし、self.self_attn を新しい Olmo2Attention クラスに再代入します。del self.input_layernorm は、Olmo2 が attention の前ではなく後で norm を適用するため、親クラスの input_layernorm の代入を削除します。del が何を削除し、何を削除しないかについては、属性の削除を参照してください。
forward は、attention 後の norm の配置を反映するように書き換えられています。属性がリネームされる場合のみ forward の書き換えが必要であり、型だけが変更される場合は不要です。
class Olmo2DecoderLayer(OlmoDecoderLayer):
def __init__(self, config: Olmo2Config, layer_idx: int):
super().__init__(config, layer_idx=layer_idx)
- self.self_attn = OlmoAttention(config=config, layer_idx=layer_idx)
- self.input_layernorm = OlmoLayerNorm(config.hidden_size)
- self.post_attention_layernorm = OlmoLayerNorm(config.hidden_size)
+ self.self_attn = Olmo2Attention(config=config, layer_idx=layer_idx)
+ del self.input_layernorm
+ self.post_attention_layernorm = Olmo2RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
+ self.post_feedforward_layernorm = Olmo2RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
def forward(self, ...):
residual = hidden_states
- hidden_states = self.input_layernorm(hidden_states)
# Self Attention
hidden_states, _ = self.self_attn(...)
- hidden_states = residual + hidden_states
+ hidden_states = self.post_attention_layernorm(hidden_states)
+ hidden_states = residual + hidden_states
# Fully Connected
residual = hidden_states
- hidden_states = self.post_attention_layernorm(hidden_states)
hidden_states = self.mlp(hidden_states)
- hidden_states = residual + hidden_states
+ hidden_states = self.post_feedforward_layernorm(hidden_states)
+ hidden_states = residual + hidden_states
return hidden_statesモデル
ここではself.normの型だけが変更されます。forwardメソッドは親クラスと同一であるため、リンターが自動的に引き継ぎます。
class Olmo2Model(OlmoModel):
def __init__(self, config: Olmo2Config):
super().__init__(config)
- self.layers = nn.ModuleList(
- [OlmoDecoderLayer(config, layer_idx) for layer_idx in range(config.num_hidden_layers)]
- )
- self.norm = OlmoLayerNorm(config.hidden_size)
+ self.norm = Olmo2RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
+ self.layers = nn.ModuleList(
+ [Olmo2DecoderLayer(config, layer_idx) for layer_idx in range(config.num_hidden_layers)]
+ )モデルヘッド
ロジックはOlmoForCausalLMと同一であるため、変更は不要です。
from ..olmo.modeling_olmo import OlmoForCausalLM
class Olmo2ForCausalLM(OlmoForCausalLM):
passその他のクラス
linter が生成した modeling_olmo2.py には、modular_olmo2.py で明示的に定義されていないクラス(Olmo2MLP、Olmo2RotaryEmbedding、Olmo2PreTrainedModel)も含まれています。
linter は、明示的に再定義しない限り、継承クラスが依存するすべてのクラスを参照します。apply_rotary_pos_emb などのインポートされた関数も、同じルールに従います。
例えば、OlmoDecoderLayerにはself.mlp = OlmoMLP(config)があります。Olmo2MLPはモジュラーファイル内で定義されていなかったため、リンターはpassを使用するのと同様に、これを自動的に作成します。
from ..olmo.modeling_olmo import OlmoMLP
class Olmo2MLP(OlmoMLP):
passOlmo2MLPが別のモデルを継承するようにしたい場合は、明示的に指定してください。
# switch to Mistral definition
from ..mistral.modeling_mistral import MistralMLP
class Olmo2MLP(MistralMLP):
passファイルの仕上げ
すべてのモジュールファイルでは、モジュールレベルでloggerと__all__リストを宣言する必要があります。
logger = logging.get_logger(__name__)
__all__ = [
"Olmo2Config",
"Olmo2ForCausalLM",
"Olmo2Model",
"Olmo2PreTrainedModel",
]__all__ には、ファイル内のすべての公開クラスを列挙する必要があります。コンバーターや下流のインポートがこれに依存しているためです。__all__ に漏れているクラスは、正しくエクスポートされません。
モデリングファイルの生成
modular_model_converter.py スクリプトは、モジュラーファイルから独立した modeling.py、configuration.py およびその他のファイルを生成します。継承された各クラスについて、親クラスの本体を子クラスにコピーし、すべての参照を新しいモデルに合わせて名前を変更し、これらの親クラスが依存するヘルパー関数やクラスを取り込みます。
出力ファイルには、モデル間のインポートや他のモデルディレクトリからの継承は含まれません。リンターは継承を1階層にフラット化します。Olmo2AttentionがOlmoAttentionを継承する場合、生成されるOlmo2Attentionは完全に自己完結しています。ただし、OlmoAttention自体が別のものを継承している場合、リンターはその祖クラスをインライン化しません。
以下のコマンドを実行して、モジュール化されたファイルからファイルを生成します。
python utils/modular_model_converter.py your_model
生成されたファイルを直接編集しないでください。次回の実行時に、すべての変更が上書きされるためです。
モジュール化されたファイルのパターン
以下のセクションでは、モジュラーファイルで作業する際の一般的な使用パターン(属性の削除や、デコレータ付きメソッドのオーバーライドなど)について説明します。
アトリビュートの削除
継承された属性を削除するかどうかは、nn.Moduleのサブクラスで作業しているのか、それともconfigクラスで作業しているのかによって異なります。
config クラスでは、クラスレベルで属性に AttributeError() を代入します。
class MyNewConfig(ParentConfig):
removed_attr = AttributeError()linter は、生成された設定ファイルから属性の宣言を完全に削除します。設定クラスは __init__ を持たない dataclass 形式のレイアウトを使用しているため、クラスレベルで AttributeError() を代入するのが正しいアプローチです。
nn.Module のサブクラスでは、super().__init__(...) の後に del self.attribute を使用します。
class MyNewModel(ParentModel):
def __init__(self, config: MyNewConfig):
super().__init__(config)
del self.attributedel self.attribute は、コピーされた親クラスの本体から self.attribute = ... の代入行のみを削除します。self.attribute を参照している他の行は削除されません。親クラスの forward やその他のメソッドでもその属性を参照している場合は、それらのメソッドもオーバーライドしてください。
class DummyModel(nn.Module):
def __init__(self, config: DummyConfig):
super().__init__()
self.attribute = config.attribute
if self.attribute:
# do more stuff with `self.attribute` here
...
class MyNewDummyModel(DummyModel):
def __init__(self, config: MyNewDummyConfig):
super().__init__(config)
del self.attribute
# 'self.attribute = config.attribute' is removed, but the 'if self.attribute:' block remains.
# Override forward() or any other method that references self.attribute.super() の使用
super().__init__(config) は、コンバーターに親の本体を子にコピーするよう指示します。この動作を上書きするには、2 つのパターンがあります。
- 生成された出力がモジュールの親クラスではなく、祖クラス(
nn.Module.__init__)を呼び出す必要がある場合は、特定の親クラスを直接呼び出してください。 **super_kwargsを使用すると、親メソッドの完全なシグネチャを継承しつつ、カスタムのドキュメント文字列を追加したり、デコレーターを置き換えたりできます。
グランドパーセントクラスを直接呼び出す
Be explicit about which class you’re calling when you need super() to target the generated class parent rather than the modular parent. The example below calls nn.Module.__init__(self) directly. DummyModule is itself an nn.Module, so the converter writes it as super().__init__() in the generated MyNewDummyModule.
class MyNewDummyModule(DummyModule): | class MyNewDummyModule(nn.Module):
|
def __init__(self): | def __init__(self):
nn.Module.__init__(self) | super().__init__()
self.foo = config.foo | self.foo = config.foo
... | ...super_kwargs
**super_kwargs を使用すると、親メソッドの完全なシグネチャを継承しつつ、カスタムのドキュメント文字列を追加したり、デコレーターを置き換えたりできます。オーバーライドされたシグネチャにおいて、この引数はリンターに対して、生成される出力に親のすべての引数を展開するよう指示します。
最も一般的な用途は、labels引数のドキュメント化など、モデル固有のdocstringを追加することであり、完全なシグネチャを書き直す必要はありません。
# modular_gemma.py
class GemmaForCausalLM(LlamaForCausalLM):
def forward(**super_kwargs):
r"""
Example:
```python
>>> from transformers import AutoTokenizer, GemmaForCausalLM
>>> model = GemmaForCausalLM.from_pretrained("google/gemma-7b")
...
```"""
return super().forward(**super_kwargs)生成されたGemmaForCausalLM.forwardには、手動でのコピーを必要としない完全なLlamaForCausalLMのシグネチャが含まれています。
**super_kwargs は、ニッチなケース向けのショートカットです。動作を変更する場合は、代わりに完全なシグネチャを記述してください。
未使用方法の削除
raise AttributeError("")ステートメントでオーバーライドすることにより、親クラスのメソッドを削除できます。リンターは、生成されたファイルからそのメソッドを削除します。
class GemmaTokenizer(LlamaTokenizer):
...
def get_spm_processor(self):
raise AttributeError("Not needed for Gemma")
def unk_token_length(self):
raise AttributeError("Not needed for Gemma")デコレータ付きメソッドの上書き
デコレータ付きの親メソッドをオーバーライドする場合、親のデコレータは自動的に引き継がれます。独自のデコレータを追加すると、親のデコレータは置き換えられます。
このライブラリ全体では、モデルの中間出力をキャプチャするためのものと、ドキュメント文字列を自動生成するためのもの、2つのデコレーターが登場します。
以下の例では、サブクラスがデコレータ付きの親メソッドをオーバーライドしていますが、独自のデコレータは追加していません。親のデコレータが引き継がれます。
class NewModel(DummyModel): | class NewModel(nn.Module):
... | ...
|
def forward(...): | @decorator(...)
... | def forward(...):
| ...新しいデコレーターを追加すると、親のデコレーターが上書きされます。
class NewModel(DummyModel): | class NewModel(nn.Module):
... | ...
|
@my_new_decorator(...) | @my_new_decorator(...)
def forward(...): | def forward(...):
... | ...特別な命名
linterは、クラスを継承する際に自動的にすべてをリネームします。同じファイル内のすべてのクラスで、同じクラス名のプレフィックスを使用してください。
以下の例のように、プレフィックスを混在させないでください。MyModelIncredibleMLP は命名規則に違反しており、上位の依存関係をリネームする際に、linter が MyModelIncredible を使うべきか MyModel を使うべきか判断できません。
class MyModelIncredibleMLP(LlamaMLP):
...
class MyModelDecoderLayer(LlamaDecoderLayer):
...暗黙の依存関係がないため、単一のクラスをローカルで名前変更できます。そのクラスへの他のすべての言及を、新しい名前パターンに合わせて明示的に再定義してください。そうしないと、リンターがMyModelIncredibleMLPの隣に不要なMyModelMLPクラスを追加してしまいます。
linterは、曖昧なプレフィックスを検出すると警告を出力します。
We detected multiple prefix names when inheriting from transformers.models.llama.modeling_llama: ('Emu3Text', 'Emu3'). We will only use the most used 'Emu3' prefix when grabbing args and dependencies. Make sure to subclass the intermediate classes with the prefix you want (if different from 'Emu3') or use a single prefix in all the modular (best).Text のようなモダリティ修飾子がクラス名に含まれるマルチモーダルモデルでは、曖昧なプレフィックスが最もよく見られます。依存関係に特定のプレフィックスを付与するには、pass を使用して明示的に名前を変更します。
class Emu3TextMLP(LlamaMLP):
pass設定のドキュメント文字列
linter は、部分ドキュメント文字列の継承をまだサポートしていません。設定属性を追加または削除する場合は、クラス定義の下に、完全なドキュメント文字列をモジュラーファイルに直接追加してください。
チェックポイントの変換
モデルファイルの生成が完了したら、実際の重みが正しくロードされることを確認してください。アップストリームのチェックポイント形式をTransformers互換形式に変換するスクリプトを作成し、Hubに保存してください。
変換スクリプトを作成する
src/transformers/models/<model>/ に convert_<model>_to_hf.py ファイルを追加します。このスクリプトは、アップストリームの重みを読み込み、モジュールのパラメータ名に一致するようにキーの名前を変更し、形を変えたうえで、save_pretrained() を使用して結果を保存します。
コピーして適応できる既存のスクリプトを探してください。
src/transformers/models/配下のモデルには、出発点として利用できるconvert_*_to_hf.pyが含まれています。
スクリプトの実行後、from_pretrained() で保存されたチェックポイントを読み込み、すべての期待される重みが正しく読み込まれたことを確認してください。未使用のチェックポイントキーは名前が一致していないことを示すため、問題を早期に検出するためにそれらを出力してください。
model = YourModelForTask.from_pretrained("path/to/output/")キーを反復処理する際、shapeとnameの一致を確認してください。shapeの不一致は、通常、config内のパラメータが誤っている、アーキテクチャがオリジナルと異なる、または重みを転置する必要があることを意味します。
for key, tensor in original_state_dict.items():
hf_tensor = hf_model.state_dict().get(mapped_key)
assert hf_tensor.shape == tensor.shape, (
f"Shape mismatch for {key}: expected {tensor.shape}, got {hf_tensor.shape}"
)モジュールファイル、生成されたモデリングファイル、変換スクリプトの間を反復して確認し、すべての重みが正常にロードされるまで問題を修正してください。
チェックポイントが正常に読み込まれたら、push_to_hub() を使用して Hub にプッシュしてください。詳細については、モデル共有 ガイドを参照してください。
model.push_to_hub("username/your-model-name")ランタイム変換マッピング
公開されている重みがモジュールのパラメータ構成と一致しない場合は、src/transformers/conversion_mapping.py にランタイムマッピングを追加してください。一般的なケースとしては、個別に保存されている融合重みや、スタッキングが必要な MoE エキスパートテンソルなどが挙げられます。このマッピングにより、from_pretrained() が Hub のチェックポイントを、別途のエクスポート手順なしにロードできるようになります。
WeightRenaming と WeightConverter のルールを作成し、model_type に登録する方法については、動的ウェイト読み込みガイドを参照してください。
次のステップ
- モデル構造のルールは、すべての
modeling_*.py、modular_*.py、configuration_*.pyファイルに適用される静的なルールです。PR を作成する前に、make typingを実行してこれらをチェックしてください。 - ビジョン処理コンポーネントの追加では、マルチモーダルモデルに画像プロセッサ、動画プロセッサ、プロセッサを追加する手順を説明します。
- ドキュメント文字列の自動生成では、共有モデル API に対して引数のドキュメントを手動で記述する必要がないように、
@auto_docstringを使用する方法を示しています。 - モデルのテストの作成では、新しいモデルの統合テストを作成し、ローカルで実行する方法について説明します。
- Pull request checks は、PR をマージする前にパスしなければならない CI チェック、およびそれらをローカルで再現・修正する方法を説明します。