Transformers documentation
Monkeyパッチング(実験的機能)
Monkeyパッチング(実験的機能)
Monkey patching は、元のモデルコードを変更せずに、モデルのコンポーネントをグローバルに置き換えることを可能にします。登録されたパッチは、from_pretrained() または ~PreTrainedModel.from_config でモデルを読み込む際に自動的に適用されます。これにより、量子化の互換性といった特定の要件に合わせてモデルを再構成したり、最適化を適用したり、アーキテクチャのバリアントをテストしたりできます。
モジュールのレイアウトや構造、またはその重みを変更する必要がある場合、モンキーパッチングは最終手段として使用してください。多くのカスタマイズや最適化のニーズには、代わりにAttention インターフェース、Experts インターフェース、またはKernels レジストリの利用をお試しください。モンキーパッチングは、カスタム forward 実装だけでは実現できない構造的な変更が必要な場合(例:量子化ライブラリとの互換性、レイヤーの融合、アーキテクチャの実験)にのみ使用してください。
クイックスタート
モデルのコンポーネントを置き換える方法を示す簡単な例を以下に示します。
from transformers import AutoModelForCausalLM
from transformers.models.llama.modeling_llama import LlamaAttention
from transformers.monkey_patching import register_patch_mapping
# Define your replacement class (must inherit from nn.Module)
class CustomLlamaAttention(LlamaAttention):
def forward(self, *args, **kwargs):
# Your custom implementation
print("Using custom attention!")
return super().forward(*args, **kwargs)
# Register the patch globally (only applies to transformers modeling modules)
register_patch_mapping(mapping={"LlamaAttention": CustomLlamaAttention})
# Load a model - the patch is automatically applied during initialization
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-3.2-1B")
# All LlamaAttention layers in the model are now CustomLlamaAttention instances
print(type(model.model.layers[0].self_attn)) # <class '__main__.CustomLlamaAttention'>仕組み
Monkey patch は2段階のプロセスで機能します:
登録:
register_patch_mappingを呼び出して、グローバルレジストリにマッピングを追加します。アプリケーション: パッチはモデルの初期化時に自動的に適用されます:
from_pretrained/from_config: パッチは内部のコンテキストマネージャーによって自動的に適用されます。追加の操作は不要です!- 手動構築(例:
Model(config)):apply_patchesコンテキストマネージャーを手動で使用する必要があります。
clear_patch_mappingでクリアするまで、パッチが登録されると永続化され、以降のすべてのモデル読み込みに影響します。
重要な制限事項:
transformersのモデリングモジュール内のクラスのみがパッチ適用の対象となります(例:LlamaAttention、LlamaMLP)。- マッピングのキーは、正確なクラス名または正規表現パターン(パターンマッチングを参照)のいずれかです。
グローバル登録
register_patch_mapping を使用して、マッピングをグローバルに登録します:
from transformers.monkey_patching import register_patch_mapping
# Register a single patch
register_patch_mapping(
mapping={"Qwen2MoeExperts": SequentialExperts}
)
# Register multiple patches at once
register_patch_mapping(
mapping={
"Qwen2MoeExperts": SequentialExperts,
"Qwen2MoeAttention": CustomAttention,
},
# Overwrite existing patches if they exist
overwrite=True,
)パターンマッチング
正規表現を使用して、1つのパターンで複数のクラスに一致させることができます:
from transformers.monkey_patching import register_patch_mapping
# Match all classes containing "Attention"
register_patch_mapping(
mapping={".*Attention": CustomAttention}
)
# More examples
register_patch_mapping(
mapping={
".*MoeExperts$": CustomExperts, # Ends with "MoeExperts"
"^Llama\\d+Attention$": CustomAttention, # Llama2Attention, Llama3Attention, etc.
}
)重要: 完全一致はパターンより優先されます。"LlamaAttention" と ".*Attention" の両方を登録した場合、LlamaAttention という名前のクラスは完全一致の置換が適用され、その他の一致するクラスにはパターン一致の置換が適用されます。
正規表現パターンは、モデルを静かに壊す可能性があります。
".*Attention"のような広範なパターンは、“Attention” を名前に含むすべてのクラスにマッチします。実際に置き換えたい attention をラップしているコンテナクラスも含まれます。例えば、BERT には attention 関連のクラスが 3 つあります:BertSelfAttentionとBertCrossAttention(内部の attention 実装) とBertAttention(これらの内部クラスの 1 つを含む外部モジュール) です。これら 3 つすべてを同じカスタム attention レイヤーでパッチすると、外部のBertAttentionがもはや内部のものをラップしなくなるため、モデルが壊れます。外部クラスは内部クラスをラップするのではなく、それ自体が 1 つの attention になるため、selfやoutputのような期待されるサブモジュールが失われます。意図しないマッチングを避けるため、狭いパターン (例:".*SelfAttention$") または正確なクラス名を使用してください。
パッチの登録を解除するには、unregister_patch_mappingを使用します。
from transformers.monkey_patching import unregister_patch_mapping
# Unregister a single patch (use exact name or pattern from registration)
unregister_patch_mapping(keys=["Qwen2MoeExperts"])
# Unregister multiple patches at once
unregister_patch_mapping(keys=["Qwen2MoeExperts", "Qwen2MoeAttention"])
# Unregister a pattern
unregister_patch_mapping(keys=[".*Attention"])登録されたすべてのパッチをクリアするには、clear_patch_mappingを使用します:
from transformers.monkey_patching import clear_patch_mapping
clear_patch_mapping()現在登録されているパッチを確認するには、get_patch_mappingを使用します:
from transformers.monkey_patching import get_patch_mapping
current_patches = get_patch_mapping()
print(current_patches)手動でのモデル構築
apply_patches コンテキストマネージャーは、from_pretrained や from_config を使わずにモデルを手動で構築する場合(例: Model(config))にのみ必要です:
from transformers import LlamaModel, LlamaConfig
from transformers.monkey_patching import register_patch_mapping, apply_patches
# Register patch globally
register_patch_mapping(mapping={"LlamaAttention": CustomAttention})
# For manual construction, you need the context manager
with apply_patches():
model = LlamaModel(LlamaConfig()) # Uses CustomAttention
# Without the context manager, manual construction uses original classes
model = LlamaModel(LlamaConfig()) # Uses LlamaAttention
# But from_pretrained and from_config will always apply registered patches
model = LlamaModel.from_pretrained("meta-llama/Llama-3.2-1B") # Uses CustomAttention重要な注意事項
重みの処理: Monkey patching はクラスを置き換えるだけで、重みは置き換えません。パッチを適用したクラスの重みレイアウトが異なる場合、事前学習済み重みとの互換性を確保するために、重みの変換を別途処理する必要があります。Monkey patch と重み変換マッピングを組み合わせる方法については、以下の完全な例を参照してください。
グローバルな効果:
register_patch_mappingで登録されたパッチは、登録後に読み込まれるすべてのモデルにグローバルに適用されます。完了時は、特にテスト、ノートブック、長時間実行されるアプリケーションでは、必ずclear_patch_mappingを使用してクリーンアップしてください。クラスの検証: API は、置換対象のクラスが
nn.Moduleのサブクラスであることを自動的に検証します。無効なクラスを渡すと、明確なエラーメッセージが表示されます。スレッドセーフ性: すべてのパッチ操作はスレッドセーフです。複数のスレッドから安全にパッチの登録、解除、適用を行うことができます。
マッチングの動作: 完全一致のクラス名を使用する場合、モデルのソースコードに記述されている元のクラス名と完全に一致する必要があります(大文字と小文字を区別します)。正規表現パターンを使用する場合、
re.search()を用いてクラス名と照合されます。
トラブルシューティング
私のパッチが適用されていません
クラス名またはパターンを確認: マッピング内のクラス名またはパターンが正しいことを確認してください:
# For exact names - must match exactly (case-sensitive)
register_patch_mapping(mapping={"LlamaAttention": CustomAttention})
# For patterns - use valid regex
register_patch_mapping(mapping={".*Attention": CustomAttention})登録の確認: get_patch_mappingを使用して、マッピングが登録されていることを確認します:
print(get_patch_mapping())
# Shows all registered mappings: {'LlamaAttention': <class 'CustomAttention'>, '.*MLP': <class 'CustomMLP'>}モデルのソースを確認する: モデルのソース内で正確なクラス名を見つけます:
from transformers.models.llama import modeling_llama
print(dir(modeling_llama)) # Look for the class nameパッチが正常に動作していることをどのように確認すればよいですか?
読み込まれたモデルを検査して、パッチを確認します:
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-3.2-1B")
# Check the type of a specific module
print(type(model.model.layers[0].self_attn)) # Should show your custom class
# Or iterate through all modules
for name, module in model.named_modules():
if 'attention' in name.lower():
print(f"{name}: {type(module)}")重みの形状が一致しないエラー
パッチを適用したクラスの重みの形状が異なる場合は、重みの変換を登録してください。
from transformers.conversion_mapping import register_checkpoint_conversion_mapping, WeightConverter
from transformers.monkey_patching import register_patch_mapping
register_patch_mapping(
mapping={
"LlamaAttention": LlamaFusedAttention,
}
)
register_checkpoint_conversion_mapping(
model_type_or_class_name="llama",
mapping=[
WeightConverter(
source_patterns=["q_proj", "k_proj", "v_proj"],
target_patterns=["qkv_proj"],
operations=[
Concatenate(dim=0),
],
)
],
overwrite=True,
)パッチの整理
作業が完了したら、他のコードに影響を与えないよう、パッチを必ずクリーンアップしてください:
from transformers.monkey_patching import register_patch_mapping, clear_patch_mapping
try:
register_patch_mapping(mapping={"LlamaAttention": CustomAttention})
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-2-7b-chat-hf")
# ... use model ...
finally:
clear_patch_mapping() # Always clean up完全な例
Mixture-of-Expertsモデル(qwen2_moe)のエキスパートとアテンションモジュールの両方を、最適化と量子化の互換性のために再構成する方法を示す包括的な例を以下に示します。この例では、以下をデモンス
- 同じインターフェースを維持するカスタム置換クラスの作成
- 複数のコンポーネントに対するモンキーパッチの登録
- 新しい構造における重みの変換の処理
from typing import Unpack
import torch
import torch.nn as nn
from transformers import AutoModelForCausalLM, Concatenate, WeightConverter
from transformers.activations import ACT2FN
from transformers.cache_utils import Cache
from transformers.conversion_mapping import register_checkpoint_conversion_mapping
from transformers.integrations.sdpa_attention import sdpa_attention_forward
from transformers.models.qwen2_moe.modeling_qwen2_moe import apply_rotary_pos_emb
from transformers.monkey_patching import register_patch_mapping
from transformers.utils.generic import TransformersKwargs
class MoeMLP(nn.Module):
def __init__(self, config):
super().__init__()
self.config = config
self.num_experts = config.num_experts
self.hidden_size = config.hidden_size
self.intermediate_size = config.moe_intermediate_size
self.gate_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False)
self.up_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False)
self.down_proj = nn.Linear(self.intermediate_size, self.hidden_size, bias=False)
self.act_fn = ACT2FN[config.hidden_act]
def forward(self, x):
down_proj = self.down_proj(self.act_fn(self.gate_proj(x)) * self.up_proj(x))
return down_proj
# Adapted from the original Qwen2MoeExperts
class ModuleListExperts(nn.ModuleList):
def __init__(self, config):
super().__init__()
self.config = config
self.num_experts = config.num_experts
for _ in range(self.num_experts):
self.append(MoeMLP(config))
def forward(
self, hidden_states: torch.Tensor, top_k_index: torch.Tensor, top_k_weights: torch.Tensor
) -> torch.Tensor:
final_hidden_states = torch.zeros_like(hidden_states)
with torch.no_grad():
expert_mask = torch.nn.functional.one_hot(top_k_index, num_classes=self.num_experts)
expert_mask = expert_mask.permute(2, 1, 0)
for expert_idx in range(self.num_experts):
top_k_pos, token_idx = torch.where(expert_mask[expert_idx])
current_state = hidden_states[token_idx]
current_hidden_states = self[expert_idx](current_state)
current_hidden_states = current_hidden_states * top_k_weights[token_idx, top_k_pos, None]
final_hidden_states.index_add_(0, token_idx, current_hidden_states.to(final_hidden_states.dtype))
return final_hidden_states
# Adapted from the original Qwen2MoeAttention
class FusedQKVAttention(nn.Module):
def __init__(self, config, layer_idx: int):
super().__init__()
self.config = config
self.layer_idx = layer_idx
self.head_dim = getattr(config, "head_dim", config.hidden_size // config.num_attention_heads)
self.num_key_value_groups = config.num_attention_heads // config.num_key_value_heads
self.scaling = self.head_dim**-0.5
self.attention_dropout = config.attention_dropout
self.is_causal = True
self.qkv_proj = nn.Linear(config.hidden_size, 3 * config.num_attention_heads * self.head_dim, bias=True)
self.o_proj = nn.Linear(config.num_attention_heads * self.head_dim, config.hidden_size, bias=False)
if self.config.layer_types[layer_idx] == "sliding_attention":
self.sliding_window = config.sliding_window
def forward(
self,
hidden_states: torch.Tensor,
position_embeddings: tuple[torch.Tensor, torch.Tensor] | None = None,
attention_mask: torch.Tensor | None = None,
past_key_values: Cache | None = None,
**kwargs: Unpack[TransformersKwargs],
) -> tuple[torch.Tensor, torch.Tensor]:
input_shape = hidden_states.shape[:-1]
hidden_shape = (*input_shape, -1, self.head_dim)
query_states, key_states, value_states = self.qkv_proj(hidden_states).chunk(3, dim=-1)
query_states = query_states.view(hidden_shape).transpose(1, 2)
key_states = key_states.view(hidden_shape).transpose(1, 2)
value_states = value_states.view(hidden_shape).transpose(1, 2)
cos, sin = position_embeddings
query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin)
if past_key_values is not None:
key_states, value_states = past_key_values.update(key_states, value_states, self.layer_idx)
attn_output, attn_weights = sdpa_attention_forward(
self,
query_states,
key_states,
value_states,
attention_mask,
dropout=0.0 if not self.training else self.attention_dropout,
scaling=self.scaling,
**kwargs,
)
attn_output = attn_output.reshape(*input_shape, -1).contiguous()
attn_output = self.o_proj(attn_output)
return attn_output, attn_weights
# Registering monkey patches for the new attention and experts modules.
register_patch_mapping(
mapping={
"Qwen2MoeExperts": ModuleListExperts,
"Qwen2MoeAttention": FusedQKVAttention,
}
)
# Registering weight conversion mappings adapted for the new modules. This registration will:
# - Override the original conversion mapping for qwen2_moe which concatenated the experts into a single parameter format.
# - Concatenate the q_proj, k_proj, v_proj weights/biases into a single qkv_proj weight/bias for the new fused attention module.
register_checkpoint_conversion_mapping(
model_type_or_class_name="qwen2_moe",
mapping=[
WeightConverter(
source_patterns=["q_proj", "k_proj", "v_proj"],
target_patterns=["qkv_proj"],
operations=[Concatenate(dim=0)],
),
],
overwrite=True,
)
model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen1.5-MoE-A2.7B")MoEエキスパートルーティングの記録と再生
RLHF などの Mixture-of-Experts トレーニングワークフローでは、生成中に各トークンがどのエキスパートにルーティングされたかを記録し、その正確なルーティングを別のトレーニングフォワードパスで再現する必要があります。この処理は、既存のモンキーパッチングと出力キャプチャの仕組みを用いてエンドツーエンドで構築でき、モデリングファイルの変更は不要です。
このパターンは3つの要素で構成されています:
- 必要に応じてインスタンス属性から強制エキスパートインデックスを読み取ることができる、再生可能なルーターのサブクラスです。
- コンテキストマネージャーで、フォワードパスの前にすべてのルーターでこれらの属性を設定し、その後にクリアします。
- モデルの出力キャプチャレジストリへのエントリであり、
output_<name>=Trueを設定することで、インデックスが標準的な@capture_outputsパスを通じて公開されます。
from contextlib import contextmanager
import torch
import torch.nn.functional as F
from transformers import Qwen3MoeConfig, Qwen3MoeForCausalLM
from transformers.models.qwen3_moe.modeling_qwen3_moe import Qwen3MoeTopKRouter
from transformers.monkey_patching import apply_patches, register_patch_mapping
from transformers.utils.output_capturing import _CAN_RECORD_REGISTRY, OutputRecorder
class ReplayableQwen3MoeTopKRouter(Qwen3MoeTopKRouter):
_forced_indices: torch.Tensor | None = None
def forward(self, hidden_states):
hidden_states = hidden_states.reshape(-1, self.hidden_dim)
router_logits = F.linear(hidden_states, self.weight)
router_logits = F.softmax(router_logits, dtype=torch.float, dim=-1)
if self._forced_indices is not None:
router_indices = self._forced_indices.to(router_logits.device).long()
# Megatron-style replay: preserve expert path, recompute current scores
router_top_value = router_logits.gather(-1, router_indices)
else:
router_top_value, router_indices = torch.topk(router_logits, self.top_k, dim=-1)
if self.norm_topk_prob:
router_top_value = router_top_value / router_top_value.sum(dim=-1, keepdim=True)
return router_logits, router_top_value.to(router_logits.dtype), router_indices
@contextmanager
def replay_moe_routing(model, selected_experts_per_layer):
routers = [m for m in model.modules() if isinstance(m, ReplayableQwen3MoeTopKRouter)]
if len(routers) != len(selected_experts_per_layer):
raise ValueError(f"Got {len(routers)} routers but {len(selected_experts_per_layer)} tensors")
for r, t in zip(routers, selected_experts_per_layer):
r._forced_indices = t
try:
yield
finally:
for r in routers:
r._forced_indices = None
# Swap the router class and construct the model
register_patch_mapping({"Qwen3MoeTopKRouter": ReplayableQwen3MoeTopKRouter})
with apply_patches():
model = Qwen3MoeForCausalLM(Qwen3MoeConfig(...)).eval()
# Expose `output_selected_experts=True` on the base model by adding an OutputRecorder
# at runtime. Index 2 of the router's tuple output is the expert indices.
inner = model.model
existing = _CAN_RECORD_REGISTRY.get(str(inner.__class__), {}) or {}
_CAN_RECORD_REGISTRY[str(inner.__class__)] = {
**existing,
"selected_experts": OutputRecorder(ReplayableQwen3MoeTopKRouter, index=2),
}
# Record
captured = inner(input_ids=input_ids, output_selected_experts=True)
selected_experts = captured.selected_experts # tuple of (num_tokens, top_k) LongTensors
# Replay — same expert path regardless of current router weights
with replay_moe_routing(inner, list(selected_experts)):
outputs = inner(input_ids=input_ids)Replay は正確なエキスパートのインデックスを保持し、現在のルーターの重みを用いてルーティングスコアを再計算します。これにより、エキスパートの選択が固定されたまま、ライブのパラメータを介して勾配が伝播します。これは、Megatron スタイルの MoE 学習で使用される最小限の replay 契約です。
vLLM との相互運用
vLLM の enable_return_routed_experts=True は、CompletionOutput.routed_experts を (seq_len, num_layers, top_k) の np.int32 配列として設定します。このパターンが期待するレイヤーごとのリストに変換するには、単一の式を使用します:
selected = [
torch.from_numpy(routed_experts[:, layer, :].copy()).long()
for layer in range(routed_experts.shape[1])
]
with replay_moe_routing(model, selected):
loss = model(input_ids=input_ids, labels=labels).loss他の MoE ファミリにも同じ手順が適用されます。ファミリーの *TopKRouter をサブクラス化し、元の戻り値の契約(通常は (router_logits, router_scores, router_indices))に合わせ、パッチを登録します。正確なシグネチャについては、各モデルのルータークラスを参照してください。
API リファレンス
transformers.monkey_patching.register_patch_mapping
< source >( mapping: dictoverwrite: bool = False )
パラメータ
- mapping (
Dict[str, type[nn.Module]]) — 元のクラス名(または正規表現パターン)から置換先のクラスへのマッピング。以下をサポートします:- 正確なクラス名:
"Qwen2MoeExperts"→CustomExperts - 正規表現パターン:
".*Attention"はLlamaAttentionやMistralAttentionなどにマッチします。 または、"^Llama\d+Attention$"はLlama2AttentionやLlama3Attentionなどにマッチします。
完全一致はパターンより優先されます。パターンは
re.search()を使用して一致させるため、アンカー(先頭は^、末尾は$)を使用しない限り、クラス名のどこでも一致する可能性があります。 - 正確なクラス名:
- overwrite (
bool、省略可能、デフォルトはFalse) — すでに登録されているクラス名の既存のマッピングを上書きするかどうか。
from_pretrained、from_config、またはapply_patchesコンテキストマネージャー内でモデルを作成する際に、自動パッチ適用を有効にするために、パッチのマッピングを登録します。
これを使用して、モデルの読み込み時に自動的に適用されるクラスの置換を登録します。 これは、量子化ライブラリとの互換性、構造の最適化、アーキテクチャの実験に有用です。このマッピングはグローバルであり、複数の呼び出しによって増加し、完全にクリアすることもできます。
例:
from transformers import AutoModelForCausalLM
from transformers.monkey_patching import register_patch_mapping
# Define custom expert implementation
class SequentialExperts(nn.Module):
...
# Register exact class name
register_patch_mapping(
mapping={"Qwen2MoeExperts": SequentialExperts}
)
# Register with regex pattern to match multiple classes
register_patch_mapping(
mapping={".*Attention": CustomAttention} # Matches LlamaAttention, MistralAttention, etc.
)
# Match specific model versions
register_patch_mapping(
mapping={"^Llama\d+Attention$": CustomLlamaAttention} # Matches Llama2Attention, Llama3Attention
)
# The patch will be automatically applied during loading
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-3.2-1B")注:
重みの変換には、代わりに~transformers.register_checkpoint_conversion_mappingを使用してください。
transformers.monkey_patching.unregister_patch_mapping
< source >( keys: list )
自動パッチングを無効にするには、パッチマッピングの登録を解除します。
これにより、指定されたマッピングがグローバルレジストリから削除され、モデルの読み込み時に適用されなくなります。登録時に使用された名前またはパターンと完全に一致するものを指定する必要があります。
例:
from transformers import AutoModelForCausalLM
from transformers.monkey_patching import register_patch_mapping, unregister_patch_mapping
# Register a patch
register_patch_mapping(
mapping={"Qwen2MoeExperts": CustomExperts}
)
# Unregister the patch
unregister_patch_mapping(["Qwen2MoeExperts"])
# The patch will no longer be applied during loading
model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen1.5-MoE-A2.7B")登録済みのすべてのパッチマッピングをクリアします。
これにより、グローバルレジストリに登録されているすべてのマッピングが削除されます。
transformers.monkey_patching.get_patch_mapping
< source >( ) → Dict[str, type[nn.Module]]
戻り値
Dict[str, type[nn.Module]]
クラス名またはパターンを置換先のクラスにマッピングする辞書。
登録済みのパッチマッピングをすべて取得します。
コードブロック内で登録済みのモンキーパッチを適用するためのコンテキストマネージャー。
この処理は、ブロックの実行中に元のクラスを一時的に登録済みの代替クラスに置き換え、実行後に元のクラスを復元します。
例:
from transformers import Qwen2MoeModel, Qwen2MoeConfig
from transformers.monkey_patching import register_patch_mapping, apply_patches
# Register a patch
register_patch_mapping(
mapping={"Qwen2MoeExperts": CustomExperts}
)
# Apply patches within the context
with apply_patches():
# The model will use CustomExperts instead of Qwen2MoeExperts
model = Qwen2MoeModel(Qwen2MoeConfig())
# Outside the context, original classes are restored
# The model will use Qwen2MoeExperts again
model = Qwen2MoeModel(Qwen2MoeConfig())