core article
Browse files- content/article.md +422 -0
content/article.md
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#transformers #huggingface
|
| 2 |
+
## Digging through tenets and time
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
### Introduction
|
| 6 |
+
|
| 7 |
+
###context The `transformers` library, built with `PyTorch`, supports all state-of-the-art LLMs, many VLMs, task-specific vision language models, video models, audio models, table models, classical encoders, to a global count of almost 400 models. The name of the library itself is mostly majority driven as many models are not even transformers architectures, like Mamba/RWKV. Regardless, each of these is wrought by the research and engineering team that created them, then harmonized into a now famous interface, and callable with a simple `.from_pretrained`. Inference and training are supported. The library supports ML courses, cookbooks, and several thousands other open-source libraries depend on it. All models are tested as part of a daily CI ensuring their preservation and reproducibility. Most importantly, it is open-source and has been written by the community for a large part.
|
| 8 |
+
|
| 9 |
+
###tension The ML wave has not stopped, there's more and more models being added. `Transformers` is widely used, and we read the feedback that users post. Whether it's about a function that had 300+ keyword arguments, duplicated code and helpers, and mentions of `Copied from ... ` everywhere, along with optimisation concerns. Text-only models are relatively tamed, but multimodal models remain to be harmonized.
|
| 10 |
+
|
| 11 |
+
###scope Here we will dissect what is the design philosophy of transformers, as a continuation from the existing older [philosophy](https://huggingface.co/docs/transformers/en/philosophy) page, and an accompanying [blog post from 2022](https://huggingface.co/blog/transformers-design-philosophy) . Some time ago I dare not say how long, we discussed with transformers maintainers about the state of things. A lot of recent developments were satisfactory, but if we were only talking about these, self-congratulation would be the only goalpost. Reflecting on this philosophy now, as models pile up, is essential and will drive new developments.
|
| 12 |
+
|
| 13 |
+
###promise Every reader, whether an OSS maintainer, power user, or casual fine-tuner, will walk away knowing how to reason about the `transformers` code base, how to use it better, how to meaningfully contribute to it.
|
| 14 |
+
This will also showcase new features you might have missed so you'll be up-to-date.
|
| 15 |
+
|
| 16 |
+
So, what are the principles of `transformers`? We will try to summarize the foundations on which we've built everything, and write the "tenets" of the library. They behave like _software interfaces_, hence it is crucial that they are explicitly written down. However opinionated they are, they have evolved over time.
|
| 17 |
+
|
| 18 |
+
- 0. <a id="source-of-truth"></a>overarching "Guideline": we should be a source of truth for all model definitions. This is not a tenet, but something that still guides our decisions. Model implementations should be reliable, reproducible, and faithful to the original performances.
|
| 19 |
+
|
| 20 |
+
- 1. <a id="one-model-one-file"></a> One model, one file: all inference (and most of training, loss is separate, not a part of model) logic visible, top‑to‑bottom.
|
| 21 |
+
- 2. <a id="code-is-product"></a>Code is the product: optimize for reading, diffing, and tweaking, our users are power users. Variables can be explicit, full words, even several words, readability is primordial.
|
| 22 |
+
- 3. <a id="standardize-dont-abstract"></a>Standardize, don’t abstract: if it’s model behavior, keep it in the file; abstractions only for generic infra.
|
| 23 |
+
- 4. ###TOCHANGE <a id="do-repeat-yourself"></a>DRY* (DO Repeat Yourself) via the copy mechanism: copy when it helps users; keep successors in sync without centralizing behavior.
|
| 24 |
+
- 4 prime: We amend this tenet. With the introduction and global adoption of [`modular`](#modular) transformers, we do not repeat any logic in the `modular` files, but end user files remain faithful to the original tenet.
|
| 25 |
+
- 5. <a id="minimal-user-api"></a>Minimal user API: config, model, preprocessing; from_pretrained, save_pretrained, push_to_hub. We want the least amount of codepaths. Reading should be obvious, configurations should be obvious.
|
| 26 |
+
- 6. <a id="backwards-compatibility"></a>Backwards compatibility first: evolve by additive standardization, **never** break public APIs.
|
| 27 |
+
- Some models are showing almost no use, we also stopped adding new features for non-`torch` frameworks. Still, we adapt to models existing on the hub.
|
| 28 |
+
- 7. ###TOCHANGE <a id="consistent-public-surface"></a>Consistent public surface, enforced by tests: same argument names, same outputs, hidden states and attentions exposed.
|
| 29 |
+
-
|
| 30 |
+
- 8. ###TOCHANGE We are not a modular toolbox. Components should be separable and users encouraged to use PyTorch directly for further usage.
|
| 31 |
+
- This is the largest change. We ARE a toolbox. What we are not is a framework: you should not be FORCED to rewrite every modeling, but it is _better_ for your model to be able to inherit from PreTrainedModel and have enabled TensorParallel, from_pretrained, sharding, push_to_hub, loss, as well as PEFT/TRL/SGLang/vLLM.
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
When a PR is merged, it is because the contribution is worthwhile, and that the `transformers` team finds the design of the contribution to be aligned with what is above.
|
| 35 |
+
|
| 36 |
+
Does all the code in the library follow strictly these tenets? No. The library is a gigantic house with connected nooks, corridors, crannies everywhere built by thousands of different workers. We _try_ to make it so all the code added is inline, lest we break [backwards compatibility](#backwards-compatibility).
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
For instance, one function essential to the implementation of [Rotary Positional Embeddings](https://huggingface.co/papers/2104.09864) is identical in 70 `modeling_<file>.py` across `src/transformers/models/.` Why keep it? Because removing it would make those files unloadable checkpoints rather than self-contained blueprints. We [do repeat ourselves](#do-repeat-yourself).
|
| 42 |
+
|
| 43 |
+
```python
|
| 44 |
+
def rotate_half(x):
|
| 45 |
+
"""Rotates half the hidden dims of the input."""
|
| 46 |
+
x1 = x[..., : x.shape[-1] // 2]
|
| 47 |
+
x2 = x[..., x.shape[-1] // 2 :]
|
| 48 |
+
return torch.cat((-x2, x1), dim=-1)
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
You can use a script such as [[top_methods.py]] to look at all methods of a given name across your codebase and look at their differences and similarities, that's what I did (+ a hash to avoid quadraticity).
|
| 52 |
+
|
| 53 |
+
So.... why keep it in all modeling files? Because if we were to remove it, the model would not work anymore. Think of the modeling files as a car (I know, what a novel metaphor! But, it works out.). All manual transmission cars have a clutch, but we want each _view_ of one of our cars to be able to function. Remove the clutch, you can't drive. Remove the doors, might be uncomfortable but you'll get there. So doors can go, but you _have_ to keep the clutch, even though you know perfectly how it works.
|
| 54 |
+
|
| 55 |
+
As I was looking for things to improve and make better, it's one of the iterations I attempted: a function is almost everywhere the same, let's import it from some common file? But no! Goes against
|
| 56 |
+
|
| 57 |
+
## <a id="modular"></a> Going modular
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
However, both of these works were already pointing at some drawbacks, which have been iteratively addressed. [Transformers has gone modular](https://huggingface.co/docs/transformers/en/modular_transformers) , allowing a form of inheritance without breaking [One model, One file](#one-model-one-file). If you're familiar with this, you can [skip this section](#^attention-classes) and go to the next one.
|
| 61 |
+
|
| 62 |
+
We amended the principle of [DRY*](#do-repeat-yourself) by removing progressively
|
| 63 |
+
It is explained in details in the documentation above, but overall it works like this, you define a `modular_` file that can inherit from _any function across all other modeling, configuration and processor files_:
|
| 64 |
+
```python
|
| 65 |
+
class GlmMLP(Phi3MLP):
|
| 66 |
+
pass
|
| 67 |
+
|
| 68 |
+
class GlmAttention(LlamaAttention):
|
| 69 |
+
def __init__(self, config, layer_idx=None):
|
| 70 |
+
super().__init__(config, layer_idx)
|
| 71 |
+
self.o_proj = nn.Linear(config.num_attention_heads * self.head_dim, config.hidden_size, bias=False)
|
| 72 |
+
|
| 73 |
+
class GlmForCausalLM(LlamaForCausalLM):
|
| 74 |
+
pass
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
That will get auto-expanded into the modeling file, which will actually be run.
|
| 78 |
+
|
| 79 |
+
In other words, we now WRITE the modular file but READ the modeling file.
|
| 80 |
+
|
| 81 |
+
<details>
|
| 82 |
+
<summary>Auto-generated modeling code</summary>
|
| 83 |
+
```python
|
| 84 |
+
|
| 85 |
+
class GlmMLP(nn.Module):
|
| 86 |
+
def __init__(self, config):
|
| 87 |
+
super().__init__()
|
| 88 |
+
|
| 89 |
+
self.config = config
|
| 90 |
+
self.gate_up_proj = nn.Linear(config.hidden_size, 2 * config.intermediate_size, bias=False)
|
| 91 |
+
self.down_proj = nn.Linear(config.intermediate_size, config.hidden_size, bias=False)
|
| 92 |
+
self.activation_fn = ACT2FN[config.hidden_act]
|
| 93 |
+
|
| 94 |
+
def forward(self, hidden_states: torch.FloatTensor) -> torch.FloatTensor:
|
| 95 |
+
up_states = self.gate_up_proj(hidden_states)
|
| 96 |
+
|
| 97 |
+
gate, up_states = up_states.chunk(2, dim=-1)
|
| 98 |
+
up_states = up_states * self.activation_fn(gate)
|
| 99 |
+
|
| 100 |
+
return self.down_proj(up_states)
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
class GlmAttention(nn.Module):
|
| 104 |
+
"""Multi-headed attention from 'Attention Is All You Need' paper"""
|
| 105 |
+
|
| 106 |
+
def __init__(self, config: GlmConfig, layer_idx: Optional[int] = None):
|
| 107 |
+
super().__init__()
|
| 108 |
+
self.config = config
|
| 109 |
+
self.layer_idx = layer_idx
|
| 110 |
+
self.head_dim = getattr(config, "head_dim", config.hidden_size // config.num_attention_heads)
|
| 111 |
+
self.num_key_value_groups = config.num_attention_heads // config.num_key_value_heads
|
| 112 |
+
self.scaling = self.head_dim**-0.5
|
| 113 |
+
self.attention_dropout = config.attention_dropout
|
| 114 |
+
self.is_causal = True
|
| 115 |
+
|
| 116 |
+
self.q_proj = nn.Linear(
|
| 117 |
+
config.hidden_size, config.num_attention_heads * self.head_dim, bias=config.attention_bias
|
| 118 |
+
)
|
| 119 |
+
self.k_proj = nn.Linear(
|
| 120 |
+
config.hidden_size, config.num_key_value_heads * self.head_dim, bias=config.attention_bias
|
| 121 |
+
)
|
| 122 |
+
self.v_proj = nn.Linear(
|
| 123 |
+
config.hidden_size, config.num_key_value_heads * self.head_dim, bias=config.attention_bias
|
| 124 |
+
)
|
| 125 |
+
self.o_proj = nn.Linear(config.num_attention_heads * self.head_dim, config.hidden_size, bias=False)
|
| 126 |
+
|
| 127 |
+
def forward(
|
| 128 |
+
self,
|
| 129 |
+
hidden_states: torch.Tensor,
|
| 130 |
+
position_embeddings: Tuple[torch.Tensor, torch.Tensor],
|
| 131 |
+
attention_mask: Optional[torch.Tensor],
|
| 132 |
+
past_key_value: Optional[Cache] = None,
|
| 133 |
+
cache_position: Optional[torch.LongTensor] = None,
|
| 134 |
+
**kwargs: Unpack[FlashAttentionKwargs],
|
| 135 |
+
) -> Tuple[torch.Tensor, Optional[torch.Tensor], Optional[Tuple[torch.Tensor]]]:
|
| 136 |
+
input_shape = hidden_states.shape[:-1]
|
| 137 |
+
hidden_shape = (*input_shape, -1, self.head_dim)
|
| 138 |
+
|
| 139 |
+
query_states = self.q_proj(hidden_states).view(hidden_shape).transpose(1, 2)
|
| 140 |
+
key_states = self.k_proj(hidden_states).view(hidden_shape).transpose(1, 2)
|
| 141 |
+
value_states = self.v_proj(hidden_states).view(hidden_shape).transpose(1, 2)
|
| 142 |
+
|
| 143 |
+
cos, sin = position_embeddings
|
| 144 |
+
query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin)
|
| 145 |
+
|
| 146 |
+
if past_key_value is not None:
|
| 147 |
+
# sin and cos are specific to RoPE models; cache_position needed for the static cache
|
| 148 |
+
cache_kwargs = {"sin": sin, "cos": cos, "cache_position": cache_position}
|
| 149 |
+
key_states, value_states = past_key_value.update(key_states, value_states, self.layer_idx, cache_kwargs)
|
| 150 |
+
|
| 151 |
+
attention_interface: Callable = eager_attention_forward
|
| 152 |
+
|
| 153 |
+
if self.config._attn_implementation != "eager":
|
| 154 |
+
if self.config._attn_implementation == "sdpa" and kwargs.get("output_attentions", False):
|
| 155 |
+
logger.warning_once(
|
| 156 |
+
"`torch.nn.functional.scaled_dot_product_attention` does not support `output_attentions=True`. Falling back to "
|
| 157 |
+
'eager attention. This warning can be removed using the argument `attn_implementation="eager"` when loading the model.'
|
| 158 |
+
)
|
| 159 |
+
else:
|
| 160 |
+
attention_interface = ALL_ATTENTION_FUNCTIONS[self.config._attn_implementation]
|
| 161 |
+
|
| 162 |
+
attn_output, attn_weights = attention_interface(
|
| 163 |
+
self,
|
| 164 |
+
query_states,
|
| 165 |
+
key_states,
|
| 166 |
+
value_states,
|
| 167 |
+
attention_mask,
|
| 168 |
+
dropout=0.0 if not self.training else self.attention_dropout,
|
| 169 |
+
scaling=self.scaling,
|
| 170 |
+
**kwargs,
|
| 171 |
+
)
|
| 172 |
+
|
| 173 |
+
attn_output = attn_output.reshape(*input_shape, -1).contiguous()
|
| 174 |
+
attn_output = self.o_proj(attn_output)
|
| 175 |
+
return attn_output, attn_weights
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
@use_kernel_forward_from_hub("RMSNorm")
|
| 179 |
+
class GlmRMSNorm(nn.Module):
|
| 180 |
+
def __init__(self, hidden_size, eps=1e-6):
|
| 181 |
+
"""
|
| 182 |
+
GlmRMSNorm is equivalent to T5LayerNorm
|
| 183 |
+
"""
|
| 184 |
+
super().__init__()
|
| 185 |
+
self.weight = nn.Parameter(torch.ones(hidden_size))
|
| 186 |
+
self.variance_epsilon = eps
|
| 187 |
+
|
| 188 |
+
def forward(self, hidden_states):
|
| 189 |
+
input_dtype = hidden_states.dtype
|
| 190 |
+
hidden_states = hidden_states.to(torch.float32)
|
| 191 |
+
variance = hidden_states.pow(2).mean(-1, keepdim=True)
|
| 192 |
+
hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
|
| 193 |
+
return self.weight * hidden_states.to(input_dtype)
|
| 194 |
+
|
| 195 |
+
def extra_repr(self):
|
| 196 |
+
return f"{tuple(self.weight.shape)}, eps={self.variance_epsilon}"
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
class GlmRotaryEmbedding(nn.Module):
|
| 200 |
+
def __init__(self, config: GlmConfig, device=None):
|
| 201 |
+
super().__init__()
|
| 202 |
+
# BC: "rope_type" was originally "type"
|
| 203 |
+
if hasattr(config, "rope_scaling") and config.rope_scaling is not None:
|
| 204 |
+
self.rope_type = config.rope_scaling.get("rope_type", config.rope_scaling.get("type"))
|
| 205 |
+
else:
|
| 206 |
+
self.rope_type = "default"
|
| 207 |
+
self.max_seq_len_cached = config.max_position_embeddings
|
| 208 |
+
self.original_max_seq_len = config.max_position_embeddings
|
| 209 |
+
|
| 210 |
+
self.config = config
|
| 211 |
+
self.rope_init_fn = ROPE_INIT_FUNCTIONS[self.rope_type]
|
| 212 |
+
|
| 213 |
+
inv_freq, self.attention_scaling = self.rope_init_fn(self.config, device)
|
| 214 |
+
self.register_buffer("inv_freq", inv_freq, persistent=False)
|
| 215 |
+
self.original_inv_freq = self.inv_freq
|
| 216 |
+
```
|
| 217 |
+
</details>
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
## <a id="attention-classes"></a> External Attention classes
|
| 221 |
+
|
| 222 |
+
A chronological iteration over [modular](#modular), and a big improvement in terms of readabilty, was to remove the various attention-backend-specific attention classes across the repository. Before, we were adding specific torch operations for each backend (sdpa, flash-attention iterations, flex attention) but it wasn't a [minimal user api](#minimal-user-api).
|
| 223 |
+
|
| 224 |
+
What will forever stay in the modeling code is the `eager_attention_forward` because it is a core part of the modeling,
|
| 225 |
+
|
| 226 |
+
```python
|
| 227 |
+
attention_interface: Callable = eager_attention_forward
|
| 228 |
+
if self.config._attn_implementation != "eager":
|
| 229 |
+
attention_interface = ALL_ATTENTION_FUNCTIONS[self.config._attn_implementation]
|
| 230 |
+
```
|
| 231 |
+
|
| 232 |
+
We often read and understand that `kwargs` are criticized, and we are typing them however we can, but we cannot enforce them all the time because other libraries such as vLLM don''t use the same kwargs.
|
| 233 |
+
|
| 234 |
+
It is a strength of the new attention interface, where it can be plugged in various backends, because most of the signature is not enforced. We INFORM but do not ENFORCE. That way, the current system is a [minimal user api](#minimal-user-api).
|
| 235 |
+
|
| 236 |
+
For a better _information_, we plan to use `python`features such as `Annoted` for example, to inform users of what we expect typically in an argument. That way, higher-level information could be included directly in the
|
| 237 |
+
|
| 238 |
+
## Community Kernels
|
| 239 |
+
|
| 240 |
+
The same principle extends to normalization, activation, and other hot paths. The model defines **semantics**; a kernel defines **how** to execute them faster. We annotate the module to borrow a community‑provided forward, keeping a [consistent public surface](#consistent-public-surface)
|
| 241 |
+
|
| 242 |
+
```python
|
| 243 |
+
@use_kernel_forward_from_hub("RMSNorm")
|
| 244 |
+
class GlmRMSNorm(nn.Module):
|
| 245 |
+
...
|
| 246 |
+
```
|
| 247 |
+
|
| 248 |
+
Plus, this opened another angle of contribution for the community. People who are GPU whisperersYou can check on the [kernel community blog post](https://huggingface.co/blog/hello-hf-kernels) to learn more about it!
|
| 249 |
+
|
| 250 |
+
## The good modularity
|
| 251 |
+
|
| 252 |
+
Now, we have a form of inheritance in our codebase. Some models become standards, and model contributors are given the opportunity to _define standards_. Pushing the boundaries of scientific knowledge can translate into the boundaries of engineering if this effort is made, and we're striving for it.
|
| 253 |
+
|
| 254 |
+
My capacity for abstraction is not that great, compared to other computer scientists and engineers: I need to look at little doodles and drawings, especially when components pile up.
|
| 255 |
+
|
| 256 |
+
So I wanted to take a look at the current **state of modularity** across the repository. How many models are defined using components of others?
|
| 257 |
+
|
| 258 |
+
To get this graph, I used the heuristic of modular inheritance.
|
| 259 |
+
1. Does this model have a `modular` file?
|
| 260 |
+
2. In this `modular` file, what models, configurations and processings are imported?
|
| 261 |
+
3. Recurse through the model list that way.
|
| 262 |
+
|
| 263 |
+
So what do we see? Llama is a basis for many models, and it shows.
|
| 264 |
+
Radically different architectures such as mamba have spawned their own dependency subgraph.
|
| 265 |
+
[code relatedness](d3_dependency_graph.html)
|
| 266 |
+
|
| 267 |
+
![[Pasted image 20250729153809.png]]
|
| 268 |
+
|
| 269 |
+
But there is no similar miracle for VLMs across the board.
|
| 270 |
+
As you can see, there is a small DETR island, a little llava pocket, and so on, but it's not comparable to the centrality observed.
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
One problem is, this is only for `modular` models. Several models do NOT have a modular file. In other words, we have a big "hidden space here."
|
| 274 |
+
|
| 275 |
+
## Too many models, yet not enough, are alike
|
| 276 |
+
|
| 277 |
+
So I looked into Jaccard similarity, which we use to measure set differences. I know that code is more than a set of characters stringed together, but it is a correct proxy for now. You can check out [[find_dependencies.py]] .
|
| 278 |
+
|
| 279 |
+
{{TERMINAL}}
|
| 280 |
+
|
| 281 |
+
![[Pasted image 20250728175655.png]]
|
| 282 |
+
|
| 283 |
+
The yellow areas are places where models are very different to each other. We can see islands here and there corresponding to model families. Llava goes with Llava-onevision, LlavaNext, LlavaNext-video, etc.
|
| 284 |
+
## VLM improvements, avoiding abstraction
|
| 285 |
+
|
| 286 |
+
We don't have cookbook for common VLM patterns (image token scatter, multi‑tower encoders, cross‑attn bridges). This is one of the main improvement points where we can work.
|
| 287 |
+
|
| 288 |
+
So initially I thought of abstracting away the mixing of `inputs_embeds`, the tensor fed into an llm decoder in 95% of the existing VLMs. It would have looked like something like
|
| 289 |
+
|
| 290 |
+
```python
|
| 291 |
+
class InputsEmbeddingMixerMixin(nn.Module):
|
| 292 |
+
#
|
| 293 |
+
```
|
| 294 |
+
|
| 295 |
+
But this is breaking [Standardize, don't abstract](#standardize-dont-abstract). Embedding mixin is part of the model, removing it would break it. A user opening `modeling_qwen2.5_vl` should not have to go to another file.
|
| 296 |
+
|
| 297 |
+
This is the current state of abstractions across a modeling file:
|
| 298 |
+
|
| 299 |
+
![[Pasted image 20250728181550.png]]
|
| 300 |
+
|
| 301 |
+
The following [Pull request to standardize placeholder masking](https://github.com/huggingface/transformers/pull/39777) is a good example of what kind of changes are acceptable. In a VLM, we always need to insert embeddings from various encoders at various positions, so we can have a function to do it. For Qwen2 VL, for instance, it will look like this:
|
| 302 |
+
|
| 303 |
+
```python
|
| 304 |
+
def get_placeholder_mask(
|
| 305 |
+
self,
|
| 306 |
+
input_ids: torch.LongTensor,
|
| 307 |
+
inputs_embeds: torch.FloatTensor,
|
| 308 |
+
image_features: torch.FloatTensor = None,
|
| 309 |
+
video_features: torch.FloatTensor = None,
|
| 310 |
+
):
|
| 311 |
+
"""
|
| 312 |
+
Obtains multimodal placeholdr mask from `input_ids` or `inputs_embeds`, and checks that the placeholder token count is
|
| 313 |
+
equal to the length of multimodal features. If the lengths are different, an error is raised.
|
| 314 |
+
"""
|
| 315 |
+
if input_ids is None:
|
| 316 |
+
special_image_mask = inputs_embeds == self.get_input_embeddings()(
|
| 317 |
+
torch.tensor(self.config.image_token_id, dtype=torch.long, device=inputs_embeds.device)
|
| 318 |
+
)
|
| 319 |
+
special_image_mask = special_image_mask.all(-1)
|
| 320 |
+
special_video_mask = inputs_embeds == self.get_input_embeddings()(
|
| 321 |
+
torch.tensor(self.config.video_token_id, dtype=torch.long, device=inputs_embeds.device)
|
| 322 |
+
)
|
| 323 |
+
special_video_mask = special_video_mask.all(-1)
|
| 324 |
+
else:
|
| 325 |
+
special_image_mask = input_ids == self.config.image_token_id
|
| 326 |
+
special_video_mask = input_ids == self.config.video_token_id
|
| 327 |
+
|
| 328 |
+
n_image_tokens = special_image_mask.sum()
|
| 329 |
+
special_image_mask = special_image_mask.unsqueeze(-1).expand_as(inputs_embeds).to(inputs_embeds.device)
|
| 330 |
+
if image_features is not None and inputs_embeds[special_image_mask].numel() != image_features.numel():
|
| 331 |
+
raise ValueError(
|
| 332 |
+
f"Image features and image tokens do not match: tokens: {n_image_tokens}, features {image_features.shape[0]}"
|
| 333 |
+
)
|
| 334 |
+
|
| 335 |
+
n_video_tokens = special_video_mask.sum()
|
| 336 |
+
special_video_mask = special_video_mask.unsqueeze(-1).expand_as(inputs_embeds).to(inputs_embeds.device)
|
| 337 |
+
if video_features is not None and inputs_embeds[special_video_mask].numel() != video_features.numel():
|
| 338 |
+
raise ValueError(
|
| 339 |
+
f"Videos features and video tokens do not match: tokens: {n_video_tokens}, features {video_features.shape[0]}"
|
| 340 |
+
)
|
| 341 |
+
|
| 342 |
+
return special_image_mask, special_video_mask
|
| 343 |
+
```
|
| 344 |
+
|
| 345 |
+
But this is _within_ the modeling file, not in the `PreTrainedModel` base class. It will not move away from it, because it'd break the self-contained logic of the model.
|
| 346 |
+
|
| 347 |
+
## Modularity candidates
|
| 348 |
+
|
| 349 |
+
So the question abounds naturally: How can we modularize more?
|
| 350 |
+
I took again a similarity measure and looked at the existing graphs. The tool is available on this [ZeroGPU-enabled Space](https://huggingface.co/spaces/Molbap/transformers-modular-refactor). It scans the whole transformers repository, and outputs a graph of candidates across models, using either a Jaccard similarity index (simple) or a SentenceTransformers embedding model. It is understandable that [encoder models still have a lion's share of the game.](#encoders-ftw) See also [Tom Aarsen and Arhur Bresnu's great blog post on the topic of sparse embeddings.](https://huggingface.co/blog/train-sparse-encoder).
|
| 351 |
+
|
| 352 |
+
|
| 353 |
+
![[Pasted image 20250729174627.png]]
|
| 354 |
+
|
| 355 |
+
## <a id="encoders-ftw"></a> Encoders win !
|
| 356 |
+
|
| 357 |
+
Models popularity speaks for itself! This is because the usage of encoders lies in embeddings obviously. So we have to keep the encoders part viable, usable, fine-tune-able.
|
| 358 |
+
|
| 359 |
+
![[Pasted image 20250728175753.png]]
|
| 360 |
+
|
| 361 |
+
## On image processing and processors
|
| 362 |
+
|
| 363 |
+
Choosing to be a `torch`-first software meant relieving a tremendous amount of support from `jax ` and `TensorFlow` , and it also meant that we could be more lenient into the amount of torch-dependent utilities that we were able to add. One of these is the _fast processing_ of images. Where they were before assumed to be minimal ndarrays, making stronger assumptions and enforcing `torch` and `torchvision`native inputs allowed up to speed up massively the processing time for each model.
|
| 364 |
+
|
| 365 |
+
The gains in performance are immense, up to 20x speed for most models when compiled torchvision ops.
|
| 366 |
+
|
| 367 |
+
|
| 368 |
+
|
| 369 |
+
## Reduce barrier to entry/contribution
|
| 370 |
+
|
| 371 |
+
This is an overall objective, no transformers without community.
|
| 372 |
+
|
| 373 |
+
We didn't want to make a toolbox, old tenet, because _having a framework means forcing users into it_. It restrains flexibility and creativity, which are the fertile soil for new ideas to grow.
|
| 374 |
+
Among the most valuable contributions to `transformers`is of course the addition of new models.
|
| 375 |
+
|
| 376 |
+
|
| 377 |
+
## A surgical toolbox for model development
|
| 378 |
+
|
| 379 |
+
### Attention visualisation
|
| 380 |
+
|
| 381 |
+
If all models have the same API internally for attention computation, it allows us to build cool tools to visualize the inner workings of the attention mechanism. One particular piece of
|
| 382 |
+
machinery is the `attention mask`, cause of confusion. Thankfully, we can fix it.
|
| 383 |
+
|
| 384 |
+
{{ATTN_VIS}}
|
| 385 |
+
|
| 386 |
+
Because it is all PyTorch (and it is even more now that we support only PyTorch), we can easily debug any model when we want to add it to transformers. We now have a power-user tool for porting or adding models, that wraps a forward pass, intercepts every submodule call, and logs shapes, dtypes, and sample statistics of inputs/outputs to nested JSON.
|
| 387 |
+
|
| 388 |
+
It just works with PyTorch models and is especially useful when aligning outputs with a reference implementation, aligned with our core guideline, [source of truth for model definitions](#source-of-truth).
|
| 389 |
+
|
| 390 |
+
![[Pasted image 20250813175317.png]]
|
| 391 |
+
### Transformers-serve
|
| 392 |
+
|
| 393 |
+
Having all these models readily available allows to use all of them with transformers-serve, and enable interfacing with them with an Open API-like pattern.
|
| 394 |
+
|
| 395 |
+
#### add example
|
| 396 |
+
## Community reusability
|
| 397 |
+
|
| 398 |
+
|
| 399 |
+
Adding a model to transformers means:
|
| 400 |
+
- having it immediately available to the community
|
| 401 |
+
- usable in vLLM, SGLang, and so on without additional code.
|
| 402 |
+
|
| 403 |
+
## Inner cooking: Cache allocator
|
| 404 |
+
|
| 405 |
+
Having a clean _external_ API allows us to work on the true inner workings of transformers. One of the few recent additions was the _Cache pre-allocator_ which improved massively the loading footprint.
|
| 406 |
+
|
| 407 |
+
{{ALLOC_PLOT}}
|
| 408 |
+
|
| 409 |
+
### Linkedin post (to remove)
|
| 410 |
+
Linkedin post for videos:
|
| 411 |
+
|
| 412 |
+
In transformers, how do we deal with cross-model dependencies, while supporting ~400 models? Maybe you've seen the same 200-lines functions in too many _modeling_file.py_? Duplication isn’t inevitable.
|
| 413 |
+
|
| 414 |
+
The “one‑model/one‑file” rule keeps every model readable and runnable. It also means identical code is copied hundreds of times. Maintenance hurts, contributor PRs snowball, and vision–language models especially end up in siloed forks.
|
| 415 |
+
|
| 416 |
+
modular_*.py fixes the trade‑off, by auto-generating the modeling file from a modular file, which can use inheritance.
|
| 417 |
+
|
| 418 |
+
With a small analyser I’ve mapped which models already share modular pieces and which 100‑plus still repeat themselves. Red nodes in the graph = lowest‑hanging fruit for refactor; blue = already modular.
|
| 419 |
+
|
| 420 |
+
The result: contributors can focus on novel layers instead of boilerplate, reviews shrink from “new file diff” to “does this override make sense?”, and the codebase stays something you can actually open and read.
|
| 421 |
+
|
| 422 |
+
If you maintain or ship models on top of Transformers, take a look at modular, in 2025 it’s how we keep shipping breadth without the bloat. 🛠️
|