Transformers documentation
Monkey patching (экспериментальная функция)
Monkey patching (экспериментальная функция)
Monkey patching позволяет глобально заменять компоненты модели без изменения исходного кода модели. После регистрации патчи автоматически применяются при загрузке любой модели с помощью from_pretrained() или ~PreTrainedModel.from_config. Это позволяет перестроить модель под конкретные требования, такие как совместимость с квантованием, применить оптимизации или экспериментировать с архитектурными вариантами.
Monkey patching следует использовать в крайнем случае, когда необходимо изменить компоновку и структуру модуля и/или его веса. Для многих задач кастомизации и оптимизации попробуйте вместо этого использовать интерфейс Attention, интерфейс Experts или реестр Kernels. Обращайтесь к monkey patching только тогда, когда требуются структурные изменения, которые невозможно реализовать с помощью кастомных реализаций 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-патчи работают в два этапа:
Регистрация: вызовите
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,
)Сопоставление шаблонов
Вы можете использовать регулярные выражения для сопоставления нескольких классов с помощью одного шаблона:
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», — включая контейнерные классы, которые оборачивают то самое внимание, которое вы хотите заменить. Например, в BERT есть три класса, связанные с вниманием:BertSelfAttentionиBertCrossAttention(внутренние реализации внимания) иBertAttention(внешний модуль, который содержит один из этих внутренних классов). Если заменить все три одним и тем же пользовательским слоем внимания, модель сломается, потому что внешнийBertAttentionбольше не будет оборачивать внутренний — он сам становится им, и ожидаемые подмодули вроде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 необходим только при ручном создании моделей вручную (например, Model(config)) без использования from_pretrained или from_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 заменяет только классы, но не веса. Если у вашего патча другой layout весов, вам нужно отдельно обрабатывать конвертацию весов, чтобы обеспечить совместимость с предобученными весами. См. Полный пример ниже, чтобы узнать, как сочетать monkey patching с маппингами конвертации весов.
Глобальное воздействие: патчи, зарегистрированные с помощью
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) для обеспечения совместимости с оптимизацией и квантованием. В примере демонстрируется:
- Создание пользовательских классов-заменителей, сохраняющих тот же интерфейс
- Регистрация monkey patches для нескольких компонентов
- Обработка преобразований весов для новой структуры
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
Mixture-of-Experts training workflows like RLHF need to record which experts each token was routed to during generation, then replay that exact routing in a separate training forward pass. You can build this end-to-end with the existing monkey patching and output capturing machinery — no modeling-file changes required.
Этот шаблон состоит из трёх частей:
- Повторяемый подкласс роутера, который при необходимости может читать принудительно заданные индексы экспертов из атрибута экземпляра.
- Менеджер контекста, который устанавливает эти атрибуты для всех роутеров перед прямым проходом и очищает их после завершения.
- Запись в реестре перехвата выходов модели, благодаря которой
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 сохраняет точные индексы экспертов и пересчитывает оценки маршрутизации с использованием текущих весов роутера, благодаря чему градиенты проходят через активные параметры, а выбор экспертов остаётся фиксированным. Это минимальный контракт replay, используемый в обучении MoE в стиле Megatron.
Взаимодействие с vLLM
Параметр enable_return_routed_experts=True в vLLM заполняет CompletionOutput.routed_experts массивом np.int32 с формой (seq_len, num_layers, top_k). Преобразуйте его в список по слоям, который ожидает данный паттерн, с помощью одного выражения:
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]]
Словарь, сопоставляющий имена классов или шаблоны с классами, на которые они заменяются.
Получить все зарегистрированные сопоставления патчей.
Контекстный менеджер для применения зарегистрированных monkey-patches внутри блока кода.
Во время выполнения блока это временно заменяет исходные классы на их зарегистрированные замены, а затем восстанавливает исходные классы.
Пример:
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())