rkazants commited on
Commit
ea59ce9
·
verified ·
1 Parent(s): ba09a26

Regenerate tiny random LFM3 from LiquidAI/LFM3-1.7B for transformers 5.20.0

Browse files

Regenerated with supported_models/lfm3/make_tiny_lfm3.py on transformers 5.20.0 from the released LiquidAI/LFM3-1.7B remote code (adds lfm3_conv.py). The previous remote code predates the release. The architecture of the tiny model is unchanged. Tested with the optimum-intel LFM3 tests on transformers 5.20.0.

Files changed (7) hide show
  1. chat_template.jinja +63 -13
  2. config.json +3 -1
  3. configuration_lfm3.py +114 -281
  4. lfm3_conv.py +202 -0
  5. modeling_lfm3.py +537 -486
  6. tokenizer.json +2 -2
  7. tokenizer_config.json +44 -2
chat_template.jinja CHANGED
@@ -43,6 +43,8 @@
43
  {%- endif -%}
44
  {%- endfor -%}
45
  {{- _ns.result -}}
 
 
46
  {%- endif -%}
47
  {%- endmacro -%}
48
 
@@ -78,9 +80,25 @@
78
  {{- "}" -}}
79
  {%- endmacro -%}
80
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  {%- macro render_tool_response(content) -%}
82
- {#- content that already carries the tag (pre-formatted by the caller) is
83
- passed through as-is instead of being wrapped a second time -#}
84
  {%- if content is string and "<tool_response>" in content -%}
85
  {{- content -}}
86
  {%- else -%}
@@ -92,7 +110,15 @@
92
  {%- elif content is iterable -%}
93
  {%- for item in content -%}
94
  {%- if not loop.first -%}{{- "\n" -}}{%- endif -%}
95
- {%- if item is mapping and item.result is defined and item.result is mapping and item.result.tools is defined -%}
 
 
 
 
 
 
 
 
96
  {{- "<additional_tools>\n" -}}
97
  {%- set _tl = item.result.tools -%}
98
  {%- if _tl is string -%}
@@ -110,6 +136,8 @@
110
  {{- parse_content(item) -}}
111
  {%- endif -%}
112
  {%- endfor -%}
 
 
113
  {%- endif -%}
114
  {{- "\n</tool_response>" -}}
115
  {%- endif -%}
@@ -143,15 +171,37 @@
143
  {%- set ns.last_user_index = loop.index0 -%}
144
  {%- endif -%}
145
  {%- endfor -%}
 
146
  {%- for message in messages -%}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  {{- "<|im_start|>" + message.role + "\n" -}}
148
  {%- if message.role == "assistant" -%}
149
  {%- generation -%}
 
150
  {%- set keep_thinking = preserve_thinking or loop.index0 > ns.last_user_index -%}
151
- {%- set thinking = message.thinking or message.reasoning or message.reasoning_content -%}
152
- {%- set thinking = thinking if thinking is string else "" -%}
153
- {%- if thinking and keep_thinking -%}
154
- {{- "<think>" + thinking + "</think>" -}}
155
  {%- endif -%}
156
  {%- set _cfm_tag = "CONTINUE_FINAL_MESSAGE_TAG " -%}
157
  {%- set _has_cfm = false -%}
@@ -182,15 +232,11 @@
182
  {%- elif message.role == "tool" -%}
183
  {%- if message.get("additional_tools") -%}
184
  {%- set _at = message["additional_tools"] -%}
185
- {#- definitions may live in additional_tools itself, or in content
186
- with additional_tools used as a plain truthy flag -#}
187
  {%- if _at is mapping or (_at is sequence and _at is not string) -%}
188
  {%- set _src = _at -%}
189
  {%- else -%}
190
  {%- set _src = message.get("content") -%}
191
  {%- endif -%}
192
- {#- nothing to reveal: skip the wrapper entirely rather than
193
- emitting an empty <additional_tools> block -#}
194
  {%- if _src is string and "<tool_response>" in _src -%}
195
  {{- _src -}}
196
  {%- elif _src is string and "<additional_tools>" in _src -%}
@@ -209,8 +255,11 @@
209
  {%- endif -%}
210
  {{- "\n</additional_tools>\n</tool_response>" -}}
211
  {%- endif -%}
212
- {%- elif message.get("content") -%}
213
- {{- render_tool_response(message["content"]) -}}
 
 
 
214
  {%- endif -%}
215
  {{- "<|im_end|>\n" -}}
216
  {%- else %}
@@ -219,6 +268,7 @@
219
  {%- endif -%}
220
  {{- "<|im_end|>\n" -}}
221
  {%- endif %}
 
222
  {%- endfor -%}
223
  {%- if add_generation_prompt -%}
224
  {{- "<|im_start|>assistant\n" -}}
 
43
  {%- endif -%}
44
  {%- endfor -%}
45
  {{- _ns.result -}}
46
+ {%- elif content is not none -%}
47
+ {{- content | tojson -}}
48
  {%- endif -%}
49
  {%- endmacro -%}
50
 
 
80
  {{- "}" -}}
81
  {%- endmacro -%}
82
 
83
+ {%- macro is_tool_reference(item) -%}
84
+ {{- "1" if item is mapping and item.get("type") == "tool_reference" else "" -}}
85
+ {%- endmacro -%}
86
+
87
+ {%- macro referenced_tool_to_json(name) -%}
88
+ {%- set _ns = namespace(found=none) -%}
89
+ {%- for tool in tools or [] -%}
90
+ {%- set fn = tool.function if tool.function is defined else tool -%}
91
+ {%- if fn.name == name -%}
92
+ {%- set _ns.found = fn -%}
93
+ {%- endif -%}
94
+ {%- endfor -%}
95
+ {%- if _ns.found is none -%}
96
+ {{- raise_exception("Tool reference '" + name | string + "' not found in available tools. Every tool referenced by a tool_reference must be in `tools` on every request.") -}}
97
+ {%- endif -%}
98
+ {{- tool_to_json(_ns.found) -}}
99
+ {%- endmacro -%}
100
+
101
  {%- macro render_tool_response(content) -%}
 
 
102
  {%- if content is string and "<tool_response>" in content -%}
103
  {{- content -}}
104
  {%- else -%}
 
110
  {%- elif content is iterable -%}
111
  {%- for item in content -%}
112
  {%- if not loop.first -%}{{- "\n" -}}{%- endif -%}
113
+ {%- if is_tool_reference(item) -%}
114
+ {%- if loop.first or not is_tool_reference(loop.previtem) -%}
115
+ {{- "<additional_tools>\n" -}}
116
+ {%- endif -%}
117
+ {{- referenced_tool_to_json(item.name) -}}
118
+ {%- if loop.last or not is_tool_reference(loop.nextitem) -%}
119
+ {{- "\n</additional_tools>" -}}
120
+ {%- endif -%}
121
+ {%- elif item is mapping and item.result is defined and item.result is mapping and item.result.tools is defined -%}
122
  {{- "<additional_tools>\n" -}}
123
  {%- set _tl = item.result.tools -%}
124
  {%- if _tl is string -%}
 
136
  {{- parse_content(item) -}}
137
  {%- endif -%}
138
  {%- endfor -%}
139
+ {%- elif content is not none -%}
140
+ {{- content | tojson -}}
141
  {%- endif -%}
142
  {{- "\n</tool_response>" -}}
143
  {%- endif -%}
 
171
  {%- set ns.last_user_index = loop.index0 -%}
172
  {%- endif -%}
173
  {%- endfor -%}
174
+ {%- set carry = namespace(pending=[]) -%}
175
  {%- for message in messages -%}
176
+ {%- set _next = messages[loop.index0 + 1] if not loop.last else none -%}
177
+ {%- set _merge = message.role == "tool" and not message.get("additional_tools")
178
+ and _next is not none and _next.role == "tool" and not _next.get("additional_tools")
179
+ and message.get("tool_call_id") and _next.get("tool_call_id") == message.get("tool_call_id") -%}
180
+ {%- set _items = [] -%}
181
+ {%- if message.role == "tool" and not message.get("additional_tools") -%}
182
+ {%- set _c = message.get("content") -%}
183
+ {%- if _c is string -%}
184
+ {%- set _items = [{"type": "text", "text": _c}] if _c else [] -%}
185
+ {%- elif _c is mapping -%}
186
+ {%- set _items = [_c] -%}
187
+ {%- elif _c is iterable -%}
188
+ {%- set _items = _c | list -%}
189
+ {%- elif _c is not none -%}
190
+ {%- set _items = [_c] -%}
191
+ {%- endif -%}
192
+ {%- endif -%}
193
+ {%- if _merge -%}
194
+ {%- set carry.pending = carry.pending + _items -%}
195
+ {%- else -%}
196
  {{- "<|im_start|>" + message.role + "\n" -}}
197
  {%- if message.role == "assistant" -%}
198
  {%- generation -%}
199
+ {%- set thinking = message.thinking if message.thinking is defined else message.reasoning if message.reasoning is defined else message.reasoning_content -%}
200
  {%- set keep_thinking = preserve_thinking or loop.index0 > ns.last_user_index -%}
201
+ {%- if thinking is defined and keep_thinking -%}
202
+ {{- "<think>" + (thinking or "") + "</think>" -}}
203
+ {%- elif keep_thinking and "</think>" not in (message.get("content") if message.get("content") is string else "") -%}
204
+ {{- "<think></think>" -}}
205
  {%- endif -%}
206
  {%- set _cfm_tag = "CONTINUE_FINAL_MESSAGE_TAG " -%}
207
  {%- set _has_cfm = false -%}
 
232
  {%- elif message.role == "tool" -%}
233
  {%- if message.get("additional_tools") -%}
234
  {%- set _at = message["additional_tools"] -%}
 
 
235
  {%- if _at is mapping or (_at is sequence and _at is not string) -%}
236
  {%- set _src = _at -%}
237
  {%- else -%}
238
  {%- set _src = message.get("content") -%}
239
  {%- endif -%}
 
 
240
  {%- if _src is string and "<tool_response>" in _src -%}
241
  {{- _src -}}
242
  {%- elif _src is string and "<additional_tools>" in _src -%}
 
255
  {%- endif -%}
256
  {{- "\n</additional_tools>\n</tool_response>" -}}
257
  {%- endif -%}
258
+ {%- elif carry.pending -%}
259
+ {{- render_tool_response(carry.pending + _items) -}}
260
+ {%- set carry.pending = [] -%}
261
+ {%- else -%}
262
+ {{- render_tool_response("" if message.get("content") is none else message.get("content")) -}}
263
  {%- endif -%}
264
  {{- "<|im_end|>\n" -}}
265
  {%- else %}
 
268
  {%- endif -%}
269
  {{- "<|im_end|>\n" -}}
270
  {%- endif %}
271
+ {%- endif -%}
272
  {%- endfor -%}
273
  {%- if add_generation_prompt -%}
274
  {{- "<|im_start|>assistant\n" -}}
config.json CHANGED
@@ -4,6 +4,7 @@
4
  ],
5
  "auto_map": {
6
  "AutoConfig": "configuration_lfm3.Lfm3Config",
 
7
  "AutoModelForCausalLM": "modeling_lfm3.Lfm3ForCausalLM"
8
  },
9
  "bos_token_id": 124894,
@@ -13,6 +14,7 @@
13
  "eos_token_id": 124900,
14
  "head_dim": 8,
15
  "hidden_size": 16,
 
16
  "intermediate_size": 32,
17
  "layer_types": [
18
  "conv",
@@ -23,7 +25,7 @@
23
  "conv",
24
  "sliding_attention"
25
  ],
26
- "max_position_embeddings": 196608,
27
  "model_type": "lfm3",
28
  "norm_eps": 1e-05,
29
  "num_attention_heads": 4,
 
4
  ],
5
  "auto_map": {
6
  "AutoConfig": "configuration_lfm3.Lfm3Config",
7
+ "AutoModel": "modeling_lfm3.Lfm3Model",
8
  "AutoModelForCausalLM": "modeling_lfm3.Lfm3ForCausalLM"
9
  },
10
  "bos_token_id": 124894,
 
14
  "eos_token_id": 124900,
15
  "head_dim": 8,
16
  "hidden_size": 16,
17
+ "initializer_range": 0.02,
18
  "intermediate_size": 32,
19
  "layer_types": [
20
  "conv",
 
25
  "conv",
26
  "sliding_attention"
27
  ],
28
+ "max_position_embeddings": 262144,
29
  "model_type": "lfm3",
30
  "norm_eps": 1e-05,
31
  "num_attention_heads": 4,
configuration_lfm3.py CHANGED
@@ -1,299 +1,132 @@
1
- from typing import Optional, Union
2
-
3
- from transformers.configuration_utils import PretrainedConfig
4
- from transformers.modeling_rope_utils import RopeParameters
5
-
6
-
7
- class Lfm3Config(PretrainedConfig):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  r"""
9
- Configuration class for [`Lfm3Model`] / [`Lfm3ForCausalLM`].
10
-
11
- LFM3 dense is a hybrid architecture composed of:
12
- - GQA full-attention layers (with headwise gated attention)
13
- - sliding-window attention layers (may use a different rope_theta from global)
14
- - gated short-conv layers (depthwise conv with B*x -> conv -> C*conv_out)
15
-
16
- Args:
17
- vocab_size (`int`, *optional*, defaults to 65536):
18
- Vocabulary size.
19
- hidden_size (`int`, *optional*, defaults to 2048):
20
- Hidden / residual stream dimension.
21
- intermediate_size (`int`, *optional*, defaults to 7168):
22
- SwiGLU MLP intermediate dimension.
23
- num_hidden_layers (`int`, *optional*, defaults to 32):
24
- Total number of decoder layers.
25
- num_attention_heads (`int`, *optional*, defaults to 32):
26
- Number of query heads.
27
- num_key_value_heads (`int`, *optional*, defaults to 8):
28
- Number of KV heads (GQA) for full-attention layers.
29
- head_dim (`int`, *optional*):
30
- Per-head dim. When `None`, defaults to `hidden_size // num_attention_heads`.
31
- May differ from that quotient (e.g. dim=1536, num_heads=16, head_dim=128).
32
- max_position_embeddings (`int`, *optional*, defaults to 128_000):
33
- Maximum sequence length the model is designed for.
34
- norm_eps (`float`, *optional*, defaults to 1e-5):
35
- RMSNorm epsilon.
36
- use_cache (`bool`, *optional*, defaults to `True`):
37
- Whether to return past_key_values.
38
- pad_token_id, bos_token_id, eos_token_id (`int`, *optional*):
39
- Special token ids.
40
- tie_word_embeddings (`bool`, *optional*, defaults to `True`):
41
- Tie input/output embeddings.
42
- rope_parameters (`RopeParameters` or `dict[str, RopeParameters]`, *optional*):
43
- Either a single RopeParameters dict (legacy form — broadcast to every
44
- attention layer type) or a Gemma3-style dict keyed by `layer_type`:
45
- `{"full_attention": {"rope_type": "default", "rope_theta": 1_000_000,
46
- "partial_rotary_factor": 0.5}, "sliding_attention":
47
- {"rope_type": "default", "rope_theta": 10_000}}`. Partial RoPE is a
48
- global-attention-only knob (Gemma 4 convention); SWA layers always
49
- use full RoPE, so the SWA entry never carries `partial_rotary_factor`.
50
- For backward compatibility, callers may also pass a legacy flat
51
- dict together with a separate `swa_rope_parameters` kwarg; both forms
52
- are migrated to the nested representation in `__init__`.
53
- conv_bias (`bool`, *optional*, defaults to `False`):
54
- Whether the depthwise conv layers use a bias term.
55
- conv_L_cache (`int`, *optional*, defaults to 3):
56
- Convolution kernel size and the number of conv-state slots cached per layer.
57
- sliding_window (`int`, *optional*):
58
- Sliding-window size used by sliding-window-attention layers.
59
- full_attn_idxs (`list[int]`, *optional*):
60
- Convenience: layer indices that use full attention. Only used to build the
61
- default `layer_types` when `layer_types` is not explicitly provided.
62
- layer_types (`list[str]`, *optional*):
63
- Per-layer operator type. Each entry is one of
64
- `"full_attention"`, `"sliding_attention"`, `"conv"`.
65
- When `None`, defaults to `"full_attention"` for indices in `full_attn_idxs`
66
- and `"conv"` for the rest.
67
- use_sandwich_norm (`bool`, *optional*, defaults to `False`):
68
- Whether to apply an additional RMSNorm AFTER the operator and AFTER the
69
- FFN (sandwich norm; quantization-friendly variant).
70
  """
71
 
72
  model_type = "lfm3"
73
  keys_to_ignore_at_inference = ["past_key_values"]
74
- # Attention layer types that consume a per-layer-type entry in
75
- # `rope_parameters`. `"conv"` layers don't use RoPE.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  _attention_layer_types = ("full_attention", "sliding_attention")
 
 
 
77
 
78
- def __init__(
79
- self,
80
- vocab_size: int = 65536,
81
- hidden_size: int = 2048,
82
- intermediate_size: int = 7168,
83
- num_hidden_layers: int = 32,
84
- num_attention_heads: int = 32,
85
- num_key_value_heads: int = 8,
86
- head_dim: Optional[int] = None,
87
- max_position_embeddings: int = 128_000,
88
- norm_eps: float = 1e-5,
89
- use_cache: bool = True,
90
- pad_token_id: int = 0,
91
- bos_token_id: int = 1,
92
- eos_token_id: int = 2,
93
- tie_word_embeddings: bool = True,
94
- rope_parameters: Optional[Union[RopeParameters, dict[str, RopeParameters]]] = None,
95
- conv_bias: bool = False,
96
- conv_L_cache: int = 3,
97
- sliding_window: Optional[int] = None,
98
- full_attn_idxs: Optional[list[int]] = None,
99
- layer_types: Optional[list[str]] = None,
100
- use_sandwich_norm: bool = False,
101
- **kwargs,
102
- ):
103
- self.vocab_size = vocab_size
104
- self.hidden_size = hidden_size
105
- self.intermediate_size = intermediate_size
106
- self.num_hidden_layers = num_hidden_layers
107
- self.num_attention_heads = num_attention_heads
108
- self.num_key_value_heads = num_key_value_heads
109
- self.head_dim = head_dim
110
- self.max_position_embeddings = max_position_embeddings
111
- self.norm_eps = norm_eps
112
- self.use_cache = use_cache
113
-
114
- self.conv_bias = conv_bias
115
- self.conv_L_cache = conv_L_cache
116
-
117
- self.sliding_window = sliding_window
118
-
119
- self.use_sandwich_norm = use_sandwich_norm
120
-
121
- self.layer_types = layer_types
122
  if self.layer_types is None:
123
- full_attn_idxs = (
124
- full_attn_idxs if full_attn_idxs is not None else list(range(num_hidden_layers))
125
- )
126
  self.layer_types = [
127
- "full_attention" if i in full_attn_idxs else "conv"
128
- for i in range(num_hidden_layers)
129
  ]
130
 
131
- # Catch the inconsistent combination at construction time so users see
132
- # a clear error here instead of an opaque one from
133
- # `create_sliding_window_causal_mask` deep in the forward pass.
134
- if "sliding_attention" in self.layer_types and sliding_window is None:
135
- raise ValueError(
136
- "Lfm3Config has `sliding_attention` in `layer_types` but "
137
- "`sliding_window` is None. Set `sliding_window` to the window size "
138
- "(in tokens) you trained with."
139
- )
140
-
141
- # rope_parameters is the Gemma3-style nested dict keyed by attention
142
- # layer_type (e.g. {"full_attention": {...}, "sliding_attention": {...}}).
143
- # The trainer→HF conversion writes this nested form directly.
144
- self.rope_parameters = self._validate_rope_parameters(rope_parameters)
145
-
146
- tie_word_embeddings = kwargs.get("tie_embedding", tie_word_embeddings)
147
- super().__init__(
148
- pad_token_id=pad_token_id,
149
- bos_token_id=bos_token_id,
150
- eos_token_id=eos_token_id,
151
- tie_word_embeddings=tie_word_embeddings,
152
- **kwargs,
153
- )
154
-
155
- def _validate_rope_parameters(
156
- self,
157
- rope_parameters: Optional[dict[str, dict]],
158
- ) -> Optional[dict[str, dict]]:
159
- """Keep the per-attention-layer-type rope entries present in the model.
160
-
161
- `rope_parameters` must be the Gemma3-style nested form keyed by attention
162
- `layer_type`, e.g. ``{"full_attention": {"rope_type": "default",
163
- "rope_theta": 1e6, "partial_rotary_factor": 0.5}, "sliding_attention":
164
- {"rope_type": "default", "rope_theta": 1e4}}``. Conv layers have no rope.
165
- Partial RoPE is a full-attention-only knob — the SWA entry never carries
166
- `partial_rotary_factor`.
167
- """
168
- if rope_parameters is None:
169
- return None
170
-
171
- attention_types_in_model = [
172
- lt for lt in (self.layer_types or []) if lt in self._attention_layer_types
173
- ]
174
- # Conv-only models don't use RoPE.
175
- if not attention_types_in_model:
176
- return None
177
 
178
- if not (
179
- isinstance(rope_parameters, dict)
180
- and all(k in self._attention_layer_types for k in rope_parameters)
181
- ):
182
  raise ValueError(
183
- "Lfm3 expects nested per-layer-type `rope_parameters` keyed by "
184
- "attention layer_type (e.g. {'full_attention': {...}, "
185
- "'sliding_attention': {...}}); got "
186
- f"{list(rope_parameters) if isinstance(rope_parameters, dict) else type(rope_parameters).__name__}."
187
  )
188
-
189
- return {
190
- k: dict(v) for k, v in rope_parameters.items()
191
- if k in attention_types_in_model
192
- } or None
193
-
194
- def convert_rope_params_to_dict(self, **kwargs):
195
- """Override the base PretrainedConfig hook so it doesn't pollute our
196
- per-layer-type `rope_parameters` dict with top-level `rope_theta` /
197
- `rope_type` keys.
198
-
199
- The base implementation (modeling_rope_utils.py
200
- `RotaryEmbeddingConfigMixin.convert_rope_params_to_dict`) does
201
- `self.rope_parameters.setdefault("rope_theta", ...)` directly on
202
- whatever we set — fine for legacy flat configs, but it puts top-level
203
- keys onto our nested dict, which then makes `validate_rope` treat the
204
- whole thing as flat and warn about the layer-type keys as
205
- "Unrecognized". This override mirrors Gemma3Config: when the user
206
- already supplied a nested dict, we trust it and only run validation.
207
- """
208
- rope_scaling = kwargs.pop("rope_scaling", None)
209
- if rope_scaling is not None and self.rope_parameters is None:
210
- self.rope_parameters = rope_scaling
211
-
212
- if self.rope_parameters is None:
213
- # Conv-only models don't carry RoPE. Skipping the base helpers
214
- # avoids a noisy "no RoPE parameters were found" log on every
215
- # config construction.
216
- return kwargs
217
-
218
- is_nested = isinstance(self.rope_parameters, dict) and all(
219
- k in self._attention_layer_types for k in self.rope_parameters.keys()
220
- )
221
- if is_nested:
222
- # Ensure each entry has rope_type; default to "default" if missing.
223
- for entry in self.rope_parameters.values():
224
- entry.setdefault("rope_type", "default")
225
- # validate_rope reads self.ignore_keys_at_rope_validation internally.
226
- self.validate_rope()
227
- return kwargs
228
-
229
- # Flat / legacy form — fall back to the base behavior.
230
- return super().convert_rope_params_to_dict(**kwargs)
231
-
232
- def standardize_rope_params(self):
233
- """Override the base PretrainedConfig hook to skip non-attention
234
- layer types (e.g. ``"conv"``).
235
-
236
- Callers like vLLM's ``patch_rope_parameters`` (in
237
- ``vllm/transformers_utils/config.py``) invoke
238
- ``config.standardize_rope_params()`` directly after loading our
239
- config, bypassing our ``convert_rope_params_to_dict`` override.
240
-
241
- The base implementation in
242
- ``transformers/modeling_rope_utils.py:standardize_rope_params``
243
- Case 2 (nested rope) does:
244
-
245
- for layer_type in set(layer_types):
246
- rope_parameters[layer_type].setdefault("rope_type", ...)
247
-
248
- This iterates EVERY entry in ``layer_types`` and indexes
249
- ``rope_parameters[layer_type]`` blindly. Lfm3 has ``"conv"`` in
250
- ``layer_types`` (conv layers don't use RoPE), and our nested
251
- ``rope_parameters`` correctly omits a ``"conv"`` entry — so the
252
- blind index raises ``KeyError: 'conv'`` and vLLM fails to load.
253
-
254
- Gemma3 dodges this because its ``layer_types`` only contains
255
- attention types. Upstream LFM2 dodges it because its
256
- ``rope_parameters`` is flat (Case 1), not nested (Case 2).
257
-
258
- Our override: iterate only the layer types that actually have an
259
- entry in ``rope_parameters``, applying the same defaulting logic
260
- the base does. For the flat / legacy form, defer to base.
261
- """
262
- rope_parameters = getattr(self, "rope_parameters", None)
263
- if not rope_parameters:
264
- return
265
-
266
- is_nested = isinstance(rope_parameters, dict) and all(
267
- k in self._attention_layer_types for k in rope_parameters.keys()
268
- )
269
- if not is_nested:
270
- return super().standardize_rope_params()
271
-
272
- rope_theta = getattr(self, "rope_theta", None)
273
- partial_rotary_factor = getattr(self, "partial_rotary_factor", None)
274
- for layer_type in rope_parameters:
275
- entry = rope_parameters[layer_type]
276
- entry.setdefault("rope_type", entry.get("type", "default"))
277
- if rope_theta is not None:
278
- entry.setdefault("rope_theta", rope_theta)
279
- # Partial RoPE is a global-attention-only knob (Gemma 4 invariant);
280
- # never apply it to SWA, even if a top-level
281
- # `partial_rotary_factor` is set as a legacy attribute.
282
- if partial_rotary_factor is not None and layer_type == "full_attention":
283
- entry["partial_rotary_factor"] = partial_rotary_factor
284
- if entry["rope_type"] in ("llama3", "yarn", "longrope"):
285
- entry.setdefault(
286
- "original_max_position_embeddings", self.max_position_embeddings
287
  )
288
 
289
- @property
290
- def rope_scaling(self):
291
- """Backward compatibility property: rope_scaling maps to rope_parameters."""
292
- return self.rope_parameters
293
-
294
- @rope_scaling.setter
295
- def rope_scaling(self, value):
296
- self.rope_parameters = value
297
-
298
 
299
  __all__ = ["Lfm3Config"]
 
1
+ # Copyright 2026 The HuggingFace Team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from huggingface_hub.dataclasses import strict
15
+
16
+ from transformers.configuration_utils import PreTrainedConfig
17
+ from transformers.utils import auto_docstring
18
+
19
+
20
+ @auto_docstring(checkpoint="LiquidAI/LFM3-1.7B-Preview")
21
+ @strict
22
+ class Lfm3Config(PreTrainedConfig):
23
  r"""
24
+ rope_parameters (`dict[str, RopeParameters]`, *optional*):
25
+ RoPE parameters keyed by attention layer type, for example `{"full_attention": {"rope_type": "default",
26
+ "rope_theta": 1000000.0, "partial_rotary_factor": 0.5}, "sliding_attention": {"rope_type": "default",
27
+ "rope_theta": 10000.0}}`. `conv` layers do not use RoPE and have no entry. Full-attention layers rotate the
28
+ first `partial_rotary_factor * head_dim` dimensions and leave the rest unrotated.
29
+ conv_bias (`bool`, *optional*, defaults to `False`):
30
+ Whether to use bias in the conv layers.
31
+ conv_L_cache (`int`, *optional*, defaults to 7):
32
+ L_cache dim in the conv layers.
33
+ layer_types (`list[str]`, *optional*):
34
+ Per-layer operator type, each one of `"full_attention"`, `"sliding_attention"` or `"conv"`. Defaults to the
35
+ LFM3 pattern of a full-attention layer every fourth layer starting from the third, a sliding-attention layer
36
+ in between, and gated short convolutions elsewhere.
37
+ sliding_window (`int`, *optional*, defaults to 256):
38
+ Sliding-window size of the `sliding_attention` layers.
39
+ use_sandwich_norm (`bool`, *optional*, defaults to `True`):
40
+ Whether to apply an RMSNorm to the operator and feed-forward outputs before they enter the residual stream.
41
+
42
+ ```python
43
+ >>> from transformers import Lfm3Model, Lfm3Config
44
+
45
+ >>> # Initializing a LFM3 model
46
+ >>> configuration = Lfm3Config()
47
+
48
+ >>> # Initializing a model from the LFM3-1.7B style configuration
49
+ >>> model = Lfm3Model(configuration)
50
+
51
+ >>> # Accessing the model configuration
52
+ >>> configuration = model.config
53
+ ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  """
55
 
56
  model_type = "lfm3"
57
  keys_to_ignore_at_inference = ["past_key_values"]
58
+ default_theta = 1000000.0
59
+
60
+ vocab_size: int = 128_000
61
+ hidden_size: int = 2048
62
+ intermediate_size: int = 7680
63
+ num_hidden_layers: int = 24
64
+ num_attention_heads: int = 16
65
+ num_key_value_heads: int = 4
66
+ max_position_embeddings: int = 128_000
67
+ initializer_range: float = 0.02
68
+ norm_eps: float = 0.00001
69
+ use_cache: bool = True
70
+ pad_token_id: int | None = 124_893
71
+ bos_token_id: int | None = 124_894
72
+ eos_token_id: int | list[int] | None = 124_900
73
+ tie_word_embeddings: bool = True
74
+ rope_parameters: dict | None = None
75
+ conv_bias: bool = False
76
+ conv_L_cache: int = 7
77
+ layer_types: list[str] | None = None
78
  _attention_layer_types = ("full_attention", "sliding_attention")
79
+ head_dim: int | None = None
80
+ sliding_window: int | None = 256
81
+ use_sandwich_norm: bool = True
82
 
83
+ def __post_init__(self, **kwargs):
84
+ if self.head_dim is None:
85
+ self.head_dim = self.hidden_size // self.num_attention_heads
86
+ if self.rope_parameters is None:
87
+ self.rope_parameters = {
88
+ "full_attention": {"rope_type": "default", "rope_theta": 1_000_000.0, "partial_rotary_factor": 0.5},
89
+ "sliding_attention": {"rope_type": "default", "rope_theta": 10_000.0},
90
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  if self.layer_types is None:
 
 
 
92
  self.layer_types = [
93
+ "full_attention" if i % 4 == 2 else "sliding_attention" if i % 4 == 0 and i > 0 else "conv"
94
+ for i in range(self.num_hidden_layers)
95
  ]
96
 
97
+ self.tie_word_embeddings = kwargs.pop("tie_embedding", self.tie_word_embeddings)
98
+ super().__post_init__(**kwargs)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
 
100
+ def validate_architecture(self):
101
+ """Part of ``@strict``-powered validation."""
102
+ if len(self.layer_types) != self.num_hidden_layers:
 
103
  raise ValueError(
104
+ f"`layer_types` has {len(self.layer_types)} entries but `num_hidden_layers` is {self.num_hidden_layers}."
 
 
 
105
  )
106
+ allowed_layer_types = (*self._attention_layer_types, "conv")
107
+ invalid_layer_types = [layer_type for layer_type in self.layer_types if layer_type not in allowed_layer_types]
108
+ if invalid_layer_types:
109
+ raise ValueError(f"`layer_types` entries must be one of {allowed_layer_types}; got {invalid_layer_types}.")
110
+ if "sliding_attention" in self.layer_types and self.sliding_window is None:
111
+ raise ValueError("`layer_types` has `sliding_attention` layers but `sliding_window` is None.")
112
+ missing_rope = [
113
+ layer_type
114
+ for layer_type in self._attention_layer_types
115
+ if layer_type in self.layer_types and layer_type not in self.rope_parameters
116
+ ]
117
+ if missing_rope:
118
+ raise ValueError(f"`rope_parameters` has no entry for the {missing_rope} layers in `layer_types`.")
119
+ for layer_type, rope_params in self.rope_parameters.items():
120
+ if not isinstance(rope_params, dict):
121
+ continue
122
+ if (
123
+ rope_params.get("rope_type", "default") in ("dynamic", "longrope")
124
+ and rope_params.get("partial_rotary_factor", 1.0) < 1
125
+ ):
126
+ raise ValueError(
127
+ f"`{rope_params['rope_type']}` RoPE recomputes its frequencies during the forward pass over the "
128
+ f"rotated dimension only and cannot keep LFM3's partial rotation on `{layer_type}` layers."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  )
130
 
 
 
 
 
 
 
 
 
 
131
 
132
  __all__ = ["Lfm3Config"]
lfm3_conv.py ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 The HuggingFace Team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ """Inference kernels for LFM3's width-7 depthwise convolution."""
15
+
16
+ import torch
17
+
18
+ from transformers.utils import is_triton_available
19
+
20
+
21
+ if is_triton_available():
22
+ import triton
23
+ import triton.language as tl
24
+
25
+ # the length and strides are runtime arguments, or every prompt length would compile its own kernel
26
+ @triton.jit(do_not_specialize=["T", "X0", "X1", "X2", "S0", "S1", "ST"])
27
+ def _prefill(
28
+ X,
29
+ W,
30
+ BIAS,
31
+ SEQ,
32
+ Y,
33
+ T,
34
+ D: tl.constexpr,
35
+ X0,
36
+ X1,
37
+ X2,
38
+ S0,
39
+ S1,
40
+ ST,
41
+ HAS_BIAS: tl.constexpr,
42
+ PACKED: tl.constexpr,
43
+ BLOCK: tl.constexpr,
44
+ ):
45
+ # int64 offsets: batch * channels * length can exceed 2**31 elements
46
+ b, d = tl.program_id(0).to(tl.int64), tl.program_id(1).to(tl.int64)
47
+ t = tl.program_id(2).to(tl.int64) * BLOCK + tl.arange(0, BLOCK)
48
+ acc = tl.full((BLOCK,), 0, tl.float32)
49
+ if PACKED:
50
+ doc = tl.load(SEQ + b * S0 + (t - T + ST) * S1, (t < T) & (t >= T - ST), other=-1)
51
+ for k in tl.static_range(7):
52
+ pos = t + k - 6
53
+ valid = (t < T) & (pos >= 0)
54
+ if PACKED:
55
+ prev = tl.load(SEQ + b * S0 + (pos - T + ST) * S1, valid & (pos >= T - ST), other=-1)
56
+ valid = valid & (doc == prev)
57
+ x = tl.load(X + b * X0 + d * X1 + pos * X2, valid, other=0).to(tl.float32)
58
+ w = tl.load(W + d * 7 + k).to(tl.float32)
59
+ acc += x * w
60
+ if HAS_BIAS:
61
+ acc += tl.load(BIAS + d).to(tl.float32)
62
+ tl.store(Y + (b * D + d) * T + t, acc, t < T)
63
+
64
+ @triton.jit
65
+ def _decode(
66
+ X,
67
+ W,
68
+ BIAS,
69
+ STATE,
70
+ Y,
71
+ D: tl.constexpr,
72
+ K: tl.constexpr,
73
+ X0: tl.constexpr,
74
+ X1: tl.constexpr,
75
+ S0: tl.constexpr,
76
+ S1: tl.constexpr,
77
+ S2: tl.constexpr,
78
+ HAS_BIAS: tl.constexpr,
79
+ BLOCK: tl.constexpr,
80
+ ):
81
+ b = tl.program_id(0).to(tl.int64)
82
+ d = tl.program_id(1).to(tl.int64) * BLOCK + tl.arange(0, BLOCK)
83
+ k = tl.arange(0, 8)
84
+ # Load the complete state before writing the shifted window.
85
+ old = tl.load(STATE + b * S0 + d[:, None] * S1 + k[None, :] * S2, (d[:, None] < D) & (k[None, :] < K), other=0)
86
+ x = tl.load(X + b * X0 + d * X1, d < D, other=0)
87
+ shifted = tl.gather(old, tl.broadcast_to(tl.minimum(k + 1, 7)[None, :], (BLOCK, 8)), 1)
88
+ shifted = tl.where(k[None, :] == K - 1, x[:, None], shifted)
89
+ # A cache may store either K=6 previous inputs or K=7 including the oldest discarded input.
90
+ positions = k + K - 6
91
+ values = tl.gather(old, tl.broadcast_to(tl.minimum(positions, 7)[None, :], (BLOCK, 8)), 1)
92
+ values = tl.where(k[None, :] == 6, x[:, None], values)
93
+ weights = tl.load(W + d[:, None] * 7 + k[None, :], (d[:, None] < D) & (k[None, :] < 7), other=0)
94
+ acc = tl.sum(values.to(tl.float32) * weights.to(tl.float32), 1)
95
+ if HAS_BIAS:
96
+ acc += tl.load(BIAS + d, d < D, other=0).to(tl.float32)
97
+ tl.store(STATE + b * S0 + d[:, None] * S1 + k[None, :] * S2, shifted, (d[:, None] < D) & (k[None, :] < K))
98
+ tl.store(Y + b * D + d, acc, d < D)
99
+
100
+
101
+ def can_use_lfm3_conv(hidden_states, weight):
102
+ return (
103
+ is_triton_available()
104
+ and hasattr(tl, "gather") # the decode kernel needs Triton >= 3.3
105
+ and hidden_states.is_cuda
106
+ and not torch.is_grad_enabled()
107
+ and hidden_states.dtype == weight.dtype
108
+ and hidden_states.dtype in (torch.float16, torch.bfloat16, torch.float32)
109
+ and weight.shape[-1] == 7
110
+ )
111
+
112
+
113
+ def lfm3_causal_conv1d(hidden_states, weight, bias=None, seq_idx=None):
114
+ batch, channels, length = hidden_states.shape
115
+ # the kernels index every tensor by the batch and channel of `hidden_states` and cannot bound the others
116
+ if weight.shape != (channels, 7) or (bias is not None and bias.shape != (channels,)):
117
+ raise ValueError(
118
+ f"`weight` must have shape ({channels}, 7) and `bias` ({channels},), got {tuple(weight.shape)}"
119
+ )
120
+ if seq_idx is not None and (
121
+ seq_idx.ndim != 2 or seq_idx.shape[0] not in (1, batch) or seq_idx.dtype not in (torch.int32, torch.int64)
122
+ ):
123
+ raise ValueError(
124
+ f"`seq_idx` must be an int32 or int64 tensor of shape (1, length) or ({batch}, length), got "
125
+ f"{seq_idx.dtype} {tuple(seq_idx.shape)}"
126
+ )
127
+ if any(t is not None and t.device != hidden_states.device for t in (weight, bias, seq_idx)):
128
+ raise ValueError(
129
+ f"`weight`, `bias` and `seq_idx` must be on the device of `hidden_states` ({hidden_states.device})"
130
+ )
131
+ output = torch.empty_like(hidden_states, memory_format=torch.contiguous_format)
132
+ # Triton launches on the current device; the model may live on another one (`device_map`, `.to("cuda:1")`).
133
+ with torch.cuda.device(hidden_states.device):
134
+ _prefill[(batch, channels, triton.cdiv(length, 128))](
135
+ hidden_states,
136
+ # the kernels index `bias` and `weight` densely
137
+ weight.contiguous(),
138
+ bias.contiguous() if bias is not None else None,
139
+ seq_idx,
140
+ output,
141
+ length,
142
+ channels,
143
+ *hidden_states.stride(),
144
+ *(
145
+ (0 if seq_idx.shape[0] == 1 else seq_idx.stride(0), seq_idx.stride(1))
146
+ if seq_idx is not None
147
+ else (0, 0)
148
+ ),
149
+ seq_idx.shape[-1] if seq_idx is not None else 0,
150
+ bias is not None,
151
+ seq_idx is not None,
152
+ 128,
153
+ enable_fp_fusion=False,
154
+ )
155
+ return output
156
+
157
+
158
+ def lfm3_causal_conv1d_update(hidden_states, conv_state, weight, bias=None):
159
+ batch, channels, length = hidden_states.shape
160
+ # the kernel addresses one position per row and bounds nothing by the other tensors' shapes
161
+ if length != 1:
162
+ raise ValueError(f"`hidden_states` must hold one position per row, got {tuple(hidden_states.shape)}")
163
+ # the 8-wide tile holds a 6- or 7-wide state (see `positions` in `_decode`)
164
+ if (
165
+ conv_state.ndim != 3
166
+ or conv_state.shape[:2] != (batch, channels)
167
+ or conv_state.shape[-1] not in (6, 7)
168
+ or weight.shape != (channels, 7)
169
+ ):
170
+ raise ValueError(
171
+ f"`conv_state` must have shape ({batch}, {channels}, 6 or 7) and `weight` ({channels}, 7), got "
172
+ f"{tuple(conv_state.shape)} and {tuple(weight.shape)}"
173
+ )
174
+ if bias is not None and bias.shape != (channels,):
175
+ raise ValueError(f"`bias` must have shape ({channels},), got {tuple(bias.shape)}")
176
+ # the kernel writes the state in place: rows or channels sharing memory (an `expand`ed cache) would race
177
+ extent = 0
178
+ for stride, size in sorted(zip(conv_state.stride(), conv_state.shape)):
179
+ if size > 1 and stride <= extent:
180
+ raise ValueError(f"`conv_state` must not overlap itself, got strides {conv_state.stride()}")
181
+ extent += stride * (size - 1)
182
+ if any(t is not None and t.device != hidden_states.device for t in (conv_state, weight, bias)):
183
+ raise ValueError(
184
+ f"`conv_state`, `weight` and `bias` must be on the device of `hidden_states` ({hidden_states.device})"
185
+ )
186
+ output = torch.empty_like(hidden_states, memory_format=torch.contiguous_format)
187
+ with torch.cuda.device(hidden_states.device):
188
+ _decode[(batch, triton.cdiv(channels, 32))](
189
+ hidden_states,
190
+ weight.contiguous(),
191
+ bias.contiguous() if bias is not None else None,
192
+ conv_state,
193
+ output,
194
+ channels,
195
+ conv_state.shape[-1],
196
+ *hidden_states.stride()[:2],
197
+ *conv_state.stride(),
198
+ bias is not None,
199
+ 32,
200
+ enable_fp_fusion=False,
201
+ )
202
+ return output
modeling_lfm3.py CHANGED
@@ -1,44 +1,64 @@
1
- # LFM3 dense model.
2
- from typing import Any, Callable, Optional, Union
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
  import torch
5
  import torch.nn.functional as F
6
  from torch import nn
7
 
 
 
8
  from transformers.cache_utils import Cache, DynamicCache
 
9
  from transformers.generation import GenerationMixin
10
  from transformers.integrations import (
11
  use_kernel_forward_from_hub,
12
- use_kernel_func_from_hub,
13
  use_kernelized_func,
14
  )
15
- from transformers.masking_utils import create_causal_mask, create_sliding_window_causal_mask
 
 
 
 
 
16
  from transformers.modeling_layers import GradientCheckpointingLayer
17
  from transformers.modeling_outputs import BaseModelOutputWithPast, CausalLMOutputWithPast
18
  from transformers.modeling_rope_utils import ROPE_INIT_FUNCTIONS, dynamic_rope_update
19
  from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS, PreTrainedModel
20
  from transformers.processing_utils import Unpack
21
- from transformers.utils import TransformersKwargs, auto_docstring, can_return_tuple
22
- from transformers.utils.generic import check_model_inputs, maybe_autocast
23
- from transformers.utils.import_utils import is_causal_conv1d_available, is_torchdynamo_compiling
 
24
 
25
  from .configuration_lfm3 import Lfm3Config
26
-
27
-
28
- if is_causal_conv1d_available():
29
- from causal_conv1d import causal_conv1d_fn, causal_conv1d_update
30
- else:
31
- causal_conv1d_fn, causal_conv1d_update = None, None
32
 
33
 
34
  @use_kernel_forward_from_hub("RMSNorm")
35
  class Lfm3RMSNorm(nn.Module):
36
- def __init__(self, hidden_size, eps=1e-6):
 
 
 
37
  super().__init__()
38
  self.weight = nn.Parameter(torch.ones(hidden_size))
39
  self.variance_epsilon = eps
40
 
41
- def forward(self, hidden_states):
42
  input_dtype = hidden_states.dtype
43
  hidden_states = hidden_states.to(torch.float32)
44
  variance = hidden_states.pow(2).mean(-1, keepdim=True)
@@ -50,18 +70,9 @@ class Lfm3RMSNorm(nn.Module):
50
 
51
 
52
  class Lfm3RotaryEmbedding(nn.Module):
53
- """Per-layer-type rotary embedding (Gemma 3-style).
54
-
55
- `config.rope_parameters` is a dict keyed by `layer_type`
56
- (`"full_attention"`, `"sliding_attention"`). For each layer type
57
- present in `config.layer_types`, an `{layer_type}_inv_freq` buffer is
58
- registered. `forward(x, position_ids, layer_type=...)` selects the right
59
- one. Partial RoPE (`partial_rotary_factor < 1.0`) follows the Gemma 4
60
- convention: the first `rope_angles = int(partial_rotary_factor * dim) // 2`
61
- frequency pairs receive the standard rotation, the remaining
62
- `dim//2 - rope_angles` entries are zero (cos=1, sin=0 → identity). The
63
- SWA entry must never carry `partial_rotary_factor` — the Lfm3Config
64
- normalizer strips it during migration of legacy configs.
65
  """
66
 
67
  def __init__(self, config: Lfm3Config, device=None):
@@ -69,151 +80,99 @@ class Lfm3RotaryEmbedding(nn.Module):
69
  self.max_seq_len_cached = config.max_position_embeddings
70
  self.original_max_seq_len = config.max_position_embeddings
71
  self.config = config
72
-
73
- # Deterministic dedupe of attention-layer types present in this model.
74
- # `dict.fromkeys` preserves insertion order so the buffer-registration
75
- # order is stable across processes / Python releases — `set()` would
76
- # not, which matters for reproducible test output.
77
- layer_types_in_model = list(dict.fromkeys(config.layer_types or []))
78
- self.attention_layer_types = [
79
- lt for lt in layer_types_in_model if lt in config._attention_layer_types
80
- ]
81
- self.rope_type: dict[str, str] = {}
82
-
83
- rope_parameters = config.rope_parameters or {}
84
- for layer_type in self.attention_layer_types:
85
- entry = rope_parameters.get(layer_type)
86
- if entry is None:
87
- continue
88
- self.rope_type[layer_type] = entry["rope_type"]
89
- if entry["rope_type"] != "default":
90
- rope_init_fn: Callable = ROPE_INIT_FUNCTIONS[entry["rope_type"]]
91
- inv_freq, attention_scaling = rope_init_fn(self.config, device)
92
- else:
93
- inv_freq, attention_scaling = self.compute_default_rope_parameters(
94
- self.config, device, layer_type=layer_type
95
- )
96
- self.register_buffer(f"{layer_type}_inv_freq", inv_freq, persistent=False)
97
- self.register_buffer(
98
- f"{layer_type}_original_inv_freq", inv_freq.clone(), persistent=False
99
- )
100
- setattr(self, f"{layer_type}_attention_scaling", attention_scaling)
101
 
102
  @staticmethod
103
  def compute_default_rope_parameters(
104
- config: Optional[Lfm3Config] = None,
105
- device: Optional["torch.device"] = None,
106
- seq_len: Optional[int] = None,
107
- layer_type: Optional[str] = None,
108
- ) -> tuple["torch.Tensor", float]:
109
- entry = config.rope_parameters[layer_type]
110
- base = entry["rope_theta"]
111
- partial_rotary_factor = entry.get("partial_rotary_factor", 1.0)
112
- dim = getattr(config, "head_dim", None) or config.hidden_size // config.num_attention_heads
113
- attention_factor = 1.0
114
 
 
 
 
 
 
 
115
  rope_angles = int(partial_rotary_factor * dim) // 2
116
- assert 0 < rope_angles <= dim // 2, (
117
- f"partial_rotary_factor={partial_rotary_factor} with dim={dim} produced "
118
- f"rope_angles={rope_angles}, must be in (0, dim//2={dim // 2}]."
119
- )
120
- inv_freq_rotated = 1.0 / (
121
- base
122
- ** (
123
- torch.arange(0, 2 * rope_angles, 2, dtype=torch.int64).to(
124
- device=device, dtype=torch.float
125
- )
126
- / dim
127
- )
128
- )
129
- nope_angles = dim // 2 - rope_angles
130
- if nope_angles > 0:
131
- inv_freq = torch.cat(
132
- (
133
- inv_freq_rotated,
134
- torch.zeros(nope_angles, dtype=torch.float, device=device),
135
- ),
136
- dim=0,
137
- )
138
- else:
139
- inv_freq = inv_freq_rotated
140
- return inv_freq, attention_factor
141
 
142
  @torch.no_grad()
143
  @dynamic_rope_update # power user: used with advanced RoPE types (e.g. dynamic rope)
144
- def forward(self, x, position_ids, layer_type: Optional[str] = None):
145
  inv_freq = getattr(self, f"{layer_type}_inv_freq")
146
  attention_scaling = getattr(self, f"{layer_type}_attention_scaling")
147
 
148
- inv_freq_expanded = (
149
- inv_freq[None, :, None].float().expand(position_ids.shape[0], -1, 1).to(x.device)
150
- )
151
- position_ids_expanded = position_ids[:, None, :].float()
152
-
153
- device_type = (
154
- x.device.type if isinstance(x.device.type, str) and x.device.type != "mps" else "cpu"
155
- )
156
- with maybe_autocast(device_type=device_type, enabled=False): # Force float32
157
- freqs = (inv_freq_expanded.float() @ position_ids_expanded.float()).transpose(1, 2)
158
- emb = torch.cat((freqs, freqs), dim=-1)
159
- cos = emb.cos() * attention_scaling
160
- sin = emb.sin() * attention_scaling
161
 
162
  return cos.to(dtype=x.dtype), sin.to(dtype=x.dtype)
163
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
 
165
  class Lfm3MLP(nn.Module):
166
- def __init__(self, config: Lfm3Config, intermediate_size: Optional[int] = None):
167
  super().__init__()
168
- self.hidden_size = config.hidden_size
169
- self.intermediate_size = (
170
- config.intermediate_size if intermediate_size is None else intermediate_size
171
- )
172
- self.w1 = nn.Linear(self.hidden_size, self.intermediate_size, bias=False)
173
- self.w3 = nn.Linear(self.hidden_size, self.intermediate_size, bias=False)
174
- self.w2 = nn.Linear(self.intermediate_size, self.hidden_size, bias=False)
175
 
176
  def forward(self, x):
177
  return self.w2(F.silu(self.w1(x)) * self.w3(x))
178
 
179
 
180
-
181
  def rotate_half(x):
182
  """Rotates half the hidden dims of the input."""
183
  x1 = x[..., : x.shape[-1] // 2]
184
- x2 = x[..., x.shape[-1] // 2:]
185
  return torch.cat((-x2, x1), dim=-1)
186
 
187
 
188
- @use_kernel_func_from_hub("rotary_pos_emb")
189
- def apply_rotary_pos_emb(q, k, cos, sin, position_ids=None, unsqueeze_dim=1):
190
- """Apply Rotary Position Embedding to q / k.
191
-
192
- The rotation is computed in fp32 to match the native (Llama-style) RoPE
193
- path: native does a complex multiply in fp32 and casts to bf16 once at the
194
- end. The upstream default form (multiplying bf16 q/k by bf16 cos/sin)
195
- accrues ~3× more ULP-level rounding error per element, which propagates
196
- through softmax to ~5% per-layer native↔HF divergence in bf16. When the
197
- kernel hub provides a fused rotary kernel, that takes over via the
198
- decorator; this body is the Python fallback.
199
- """
200
- in_dtype = q.dtype
201
- cos = cos.unsqueeze(unsqueeze_dim).float()
202
- sin = sin.unsqueeze(unsqueeze_dim).float()
203
- q_f = q.float()
204
- k_f = k.float()
205
- q_embed = (q_f * cos) + (rotate_half(q_f) * sin)
206
- k_embed = (k_f * cos) + (rotate_half(k_f) * sin)
207
- return q_embed.to(in_dtype), k_embed.to(in_dtype)
208
-
209
-
210
  def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:
 
 
 
 
211
  batch, num_key_value_heads, slen, head_dim = hidden_states.shape
212
  if n_rep == 1:
213
  return hidden_states
214
- hidden_states = hidden_states[:, :, None, :, :].expand(
215
- batch, num_key_value_heads, n_rep, slen, head_dim
216
- )
217
  return hidden_states.reshape(batch, num_key_value_heads * n_rep, slen, head_dim)
218
 
219
 
@@ -222,7 +181,7 @@ def eager_attention_forward(
222
  query: torch.Tensor,
223
  key: torch.Tensor,
224
  value: torch.Tensor,
225
- attention_mask: Optional[torch.Tensor],
226
  scaling: float,
227
  dropout: float = 0.0,
228
  **kwargs: Unpack[TransformersKwargs],
@@ -232,8 +191,7 @@ def eager_attention_forward(
232
 
233
  attn_weights = torch.matmul(query, key_states.transpose(2, 3)) * scaling
234
  if attention_mask is not None:
235
- causal_mask = attention_mask[:, :, :, : key_states.shape[-2]]
236
- attn_weights = attn_weights + causal_mask
237
 
238
  attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query.dtype)
239
  attn_weights = nn.functional.dropout(attn_weights, p=dropout, training=module.training)
@@ -243,58 +201,44 @@ def eager_attention_forward(
243
  return attn_output, attn_weights
244
 
245
 
246
- @use_kernelized_func(apply_rotary_pos_emb)
 
 
 
 
 
 
 
 
247
  class Lfm3Attention(nn.Module):
248
- """Multi-headed attention. Supports GQA, optional sliding window, and a
249
- headwise sigmoid gate on the attention output.
250
-
251
- The caller (``Lfm3Model.forward``) is responsible for building the right
252
- mask for this layer's type (sliding-window-causal for SWA layers, full
253
- causal otherwise). This forward only also passes ``sliding_window=...`` to
254
- the attention backend for FA2's native fast path; SDPA / eager rely on the
255
- precomputed mask alone.
256
- """
257
 
258
- def __init__(self, config: Lfm3Config, layer_idx: int, sliding_window: Optional[int] = None):
259
  super().__init__()
260
  self.config = config
261
  self.layer_idx = layer_idx
262
- self.head_dim = getattr(config, "head_dim", None) or (
263
- config.hidden_size // config.num_attention_heads
264
- )
265
- self.sliding_window = sliding_window
266
- self.is_causal = True
267
-
268
- self.num_key_value_heads = config.num_key_value_heads
269
  self.num_key_value_groups = config.num_attention_heads // config.num_key_value_heads
270
- self.scaling = self.head_dim ** -0.5
271
- self.q_proj = nn.Linear(
272
- config.hidden_size, config.num_attention_heads * self.head_dim, bias=False
273
- )
274
- self.k_proj = nn.Linear(
275
- config.hidden_size, config.num_key_value_heads * self.head_dim, bias=False
276
- )
277
- self.v_proj = nn.Linear(
278
- config.hidden_size, config.num_key_value_heads * self.head_dim, bias=False
279
- )
280
- self.o_proj = nn.Linear(
281
- config.num_attention_heads * self.head_dim, config.hidden_size, bias=False
282
- )
283
  self.q_norm = Lfm3RMSNorm(self.head_dim, eps=config.norm_eps)
284
  self.k_norm = Lfm3RMSNorm(self.head_dim, eps=config.norm_eps)
285
- # Headwise sigmoid gate on the attention output (one scalar per query head).
286
- self.gate_proj = nn.Linear(
287
- config.hidden_size, config.num_attention_heads, bias=False
288
- )
289
 
290
  def forward(
291
  self,
292
  hidden_states: torch.Tensor,
293
  position_embeddings: tuple[torch.Tensor, torch.Tensor],
294
- attention_mask: Optional[torch.Tensor],
295
- past_key_values: Optional[Cache] = None,
296
- **kwargs,
297
- ) -> tuple[torch.Tensor, Optional[torch.Tensor]]:
298
  input_shape = hidden_states.shape[:-1]
299
  hidden_shape = (*input_shape, -1, self.head_dim)
300
 
@@ -306,13 +250,11 @@ class Lfm3Attention(nn.Module):
306
  query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin)
307
 
308
  if past_key_values is not None:
309
- key_states, value_states = past_key_values.update(
310
- key_states, value_states, self.layer_idx
311
- )
312
 
313
- attention_interface: Callable = eager_attention_forward
314
- if self.config._attn_implementation != "eager":
315
- attention_interface = ALL_ATTENTION_FUNCTIONS[self.config._attn_implementation]
316
 
317
  attn_output, attn_weights = attention_interface(
318
  self,
@@ -325,43 +267,71 @@ class Lfm3Attention(nn.Module):
325
  sliding_window=self.sliding_window,
326
  **kwargs,
327
  )
328
- attn_output = attn_output.reshape(*input_shape, -1).contiguous()
329
- # Headwise sigmoid gate: one scalar per query head, broadcast over head_dim.
330
- num_heads = self.config.num_attention_heads
331
- gate = torch.sigmoid(self.gate_proj(hidden_states)).view(*input_shape, num_heads, 1)
332
- attn_output = attn_output.view(*input_shape, num_heads, self.head_dim)
333
- attn_output = (gate * attn_output).view(*input_shape, -1)
334
- output = self.o_proj(attn_output)
335
- return output, attn_weights
336
 
337
 
338
  def apply_mask_to_padding_states(hidden_states, attention_mask):
339
- """Zero out the hidden states for padding tokens, see https://github.com/state-spaces/mamba/issues/66 .
340
-
341
- NOTE (upstream-faithful, mamba/Lfm2): the `shape[0] > 1` (batch > 1) guard means
342
- padding is NOT zeroed for a single (batch=1) sequence — so the conv can bleed
343
- pad positions for a left-padded batch-of-1. Matches upstream; single-sequence
344
- decode isn't padded in practice, but it's a latent footgun for padded bsz-1 prefill.
345
  """
346
- if attention_mask is not None and attention_mask.shape[1] > 1 and attention_mask.shape[0] > 1:
 
 
 
347
  dtype = hidden_states.dtype
348
  hidden_states = (hidden_states * attention_mask[:, :, None]).to(dtype)
349
- return hidden_states
350
-
351
 
352
- kernel_modules = (causal_conv1d_fn, causal_conv1d_update)
353
- is_fast_path_available = all(kernel_modules)
354
 
355
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
356
  class Lfm3ShortConv(nn.Module):
357
- """Standard gated short conv block:
358
-
359
- proj = in_proj(x) # 3*D
360
- B, C, x' = proj.chunk(3, dim=-1)
361
- conv_in = B * x'
362
- y = C * depthwise_conv1d(conv_in)
363
- out = out_proj(y)
364
- """
365
 
366
  def __init__(
367
  self,
@@ -371,165 +341,118 @@ class Lfm3ShortConv(nn.Module):
371
  super().__init__()
372
  self.config = config
373
  self.layer_idx = layer_idx
374
- self.L_cache = config.conv_L_cache
375
  self.bias = config.conv_bias
376
 
377
- D = config.hidden_size
378
  self.conv = nn.Conv1d(
379
- in_channels=D,
380
- out_channels=D,
381
- kernel_size=self.L_cache,
382
- groups=D,
383
  bias=self.bias,
384
- padding=self.L_cache - 1,
385
  )
386
- self.in_proj = nn.Linear(D, 3 * D, bias=self.bias)
387
- self.out_proj = nn.Linear(D, D, bias=self.bias)
388
-
389
- def cuda_kernels_forward(
390
- self,
391
- x: torch.Tensor,
392
- past_key_values: Optional[Cache] = None,
393
- attention_mask: Optional[torch.Tensor] = None,
394
- seq_idx: Optional[torch.Tensor] = None,
395
- ):
396
- x = apply_mask_to_padding_states(x, attention_mask)
397
- D = self.config.hidden_size
398
- proj = self.in_proj(x).transpose(-1, -2)
399
- # Split order matches Lfm3MoeShortConv("both") chunk(3): B first, C second, x third
400
- B, C, x = proj.split([D, D, D], dim=-2)
401
- conv_in = B * x
402
-
403
- conv_weights = self.conv.weight.view(self.conv.weight.size(0), self.conv.weight.size(2))
404
- if past_key_values is not None and past_key_values.has_previous_state(self.layer_idx):
405
- conv_states = past_key_values.layers[self.layer_idx].conv_states
406
- if isinstance(conv_states, dict):
407
- conv_states = conv_states[0]
408
- conv_out = causal_conv1d_update(
409
- conv_in.squeeze(-1),
410
- conv_states,
411
- conv_weights,
412
- self.conv.bias,
413
- None,
414
- )
415
- conv_out = conv_out.unsqueeze(-1)
416
- else:
417
- if past_key_values is not None:
418
- conv_state = nn.functional.pad(conv_in, (self.L_cache - conv_in.shape[-1], 0))
419
- past_key_values.update_conv_state(conv_state, self.layer_idx)
420
 
421
- # `seq_idx` (per-token document index, int32) resets the conv at packed
422
- # document boundaries so it never bleeds across documents.
423
- conv_out = causal_conv1d_fn(
424
- conv_in, conv_weights, self.conv.bias, seq_idx=seq_idx, activation=None
425
- )
426
-
427
- y = C * conv_out
428
- y = self.out_proj(y.transpose(-1, -2).contiguous())
429
- return y
430
-
431
- def _segmented_conv(self, conv_in: torch.Tensor, seq_idx: torch.Tensor) -> torch.Tensor:
432
- """Causal depthwise conv that resets at packed document boundaries.
433
-
434
- Pure-torch equivalent of `causal_conv1d_fn(..., seq_idx=...)`: out[t] only
435
- sums conv_in[t-(L-1)..t] entries that share t's document id. `conv_in` is
436
- (b, D, seqlen); `seq_idx` is (b, seqlen) int. Used only on the packed
437
- path — the unpacked path keeps the plain `nn.Conv1d` call below.
438
- """
439
- L = self.L_cache
440
- cin_p = nn.functional.pad(conv_in, (L - 1, 0)) # (b, D, L-1+seqlen)
441
- windows = cin_p.unfold(-1, L, 1) # (b, D, seqlen, L)
442
- # window element k of position t is conv_in[t-(L-1)+k]; current token is k=L-1.
443
- sidx_p = nn.functional.pad(seq_idx, (L - 1, 0), value=-1) # (b, L-1+seqlen)
444
- sidx_win = sidx_p.unfold(-1, L, 1) # (b, seqlen, L)
445
- same_doc = (sidx_win == sidx_win[..., -1:]).unsqueeze(1) # (b, 1, seqlen, L)
446
- windows = windows * same_doc.to(windows.dtype)
447
- w = self.conv.weight.squeeze(1) # (D, L)
448
- conv_out = torch.einsum("bdtk,dk->bdt", windows, w)
449
- if self.bias:
450
- conv_out = conv_out + self.conv.bias[None, :, None]
451
- return conv_out
452
 
453
- def slow_forward(
 
454
  self,
455
- x: torch.Tensor,
456
- past_key_values: Optional[Cache] = None,
457
- attention_mask: Optional[torch.Tensor] = None,
458
- seq_idx: Optional[torch.Tensor] = None,
459
  ):
460
- seqlen = x.shape[1]
461
-
462
- x = apply_mask_to_padding_states(x, attention_mask)
463
- D = self.config.hidden_size
464
- proj = self.in_proj(x).transpose(-1, -2)
465
- B, C, x = proj.split([D, D, D], dim=-2)
466
- conv_in = B * x
467
-
468
- if past_key_values is not None and past_key_values.has_previous_state(self.layer_idx):
469
- # update_conv_state rolls the cached window left and appends conv_in.
470
- conv_state = past_key_values.update_conv_state(conv_in, self.layer_idx)[..., -self.L_cache :]
471
- conv_out = torch.sum(conv_state.to(conv_in.device) * self.conv.weight[:, 0, :], dim=-1)
472
- if self.bias:
473
- conv_out += self.conv.bias
474
-
475
- conv_out = conv_out.unsqueeze(-1)
 
 
 
 
 
 
 
 
 
 
 
 
476
  else:
477
- if past_key_values is not None:
478
- conv_state = nn.functional.pad(conv_in, (self.L_cache - conv_in.shape[-1], 0))
479
- past_key_values.update_conv_state(conv_state, self.layer_idx)
 
 
 
 
 
 
 
 
480
 
481
- if seq_idx is not None:
482
- conv_out = self._segmented_conv(conv_in, seq_idx)
 
 
 
 
483
  else:
484
- conv_out = self.conv(conv_in)[..., :seqlen]
 
 
 
 
485
 
486
- y = C * conv_out
487
  y = y.transpose(-1, -2).contiguous()
488
  y = self.out_proj(y)
489
  return y
490
 
491
- def forward(
492
- self,
493
- hidden_states: torch.Tensor,
494
- past_key_values: Optional[Cache] = None,
495
- attention_mask: Optional[torch.Tensor] = None,
496
- seq_idx: Optional[torch.Tensor] = None,
497
- ):
498
- # causal_conv1d's stock build only supports conv widths 2-4 and raises
499
- # otherwise; build it for wider kernels (e.g. L_cache=7) or omit it for
500
- # the pure-torch slow path.
501
- if (
502
- is_fast_path_available
503
- and "cuda" in hidden_states.device.type
504
- and not is_torchdynamo_compiling()
505
- ):
506
- return self.cuda_kernels_forward(
507
- hidden_states, past_key_values, attention_mask, seq_idx
508
- )
509
- return self.slow_forward(
510
- hidden_states, past_key_values, attention_mask, seq_idx
511
- )
512
 
513
 
514
  class Lfm3DecoderLayer(GradientCheckpointingLayer):
515
  def __init__(self, config: Lfm3Config, layer_idx: int):
516
  super().__init__()
517
- self.layer_type = config.layer_types[layer_idx]
518
- self.is_attention_layer = self.layer_type in ("full_attention", "sliding_attention")
519
- self.use_sandwich_norm = getattr(config, "use_sandwich_norm", False)
520
 
521
- if self.layer_type == "full_attention":
522
  self.self_attn = Lfm3Attention(config, layer_idx)
523
- elif self.layer_type == "sliding_attention":
524
- self.self_attn = Lfm3Attention(config, layer_idx, sliding_window=config.sliding_window)
525
- elif self.layer_type == "conv":
526
- self.conv = Lfm3ShortConv(config, layer_idx)
527
  else:
528
- raise ValueError(
529
- f"Unsupported layer_type {self.layer_type!r} at layer {layer_idx}. "
530
- "Lfm3 supports only {full_attention, sliding_attention, conv}."
531
- )
532
- self.feed_forward = Lfm3MLP(config, intermediate_size=config.intermediate_size)
533
  self.operator_norm = Lfm3RMSNorm(config.hidden_size, eps=config.norm_eps)
534
  self.ffn_norm = Lfm3RMSNorm(config.hidden_size, eps=config.norm_eps)
535
  if self.use_sandwich_norm:
@@ -539,12 +462,11 @@ class Lfm3DecoderLayer(GradientCheckpointingLayer):
539
  def forward(
540
  self,
541
  hidden_states: torch.Tensor,
542
- position_embeddings: tuple[torch.Tensor, torch.Tensor],
543
- attention_mask: Optional[torch.Tensor] = None,
544
- position_ids: Optional[torch.LongTensor] = None,
545
- past_key_values: Optional[Cache] = None,
546
- cache_position: Optional[torch.LongTensor] = None,
547
- seq_idx: Optional[torch.Tensor] = None,
548
  **kwargs,
549
  ) -> torch.Tensor:
550
  residual = hidden_states
@@ -567,6 +489,7 @@ class Lfm3DecoderLayer(GradientCheckpointingLayer):
567
  if self.use_sandwich_norm:
568
  hidden_states = self.post_operator_norm(hidden_states)
569
  hidden_states = hidden_states + residual
 
570
  ffn_out = self.feed_forward(self.ffn_norm(hidden_states))
571
  if self.use_sandwich_norm:
572
  ffn_out = self.post_ffn_norm(ffn_out)
@@ -575,6 +498,38 @@ class Lfm3DecoderLayer(GradientCheckpointingLayer):
575
  return hidden_states
576
 
577
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
578
  @auto_docstring
579
  class Lfm3PreTrainedModel(PreTrainedModel):
580
  config: Lfm3Config
@@ -585,43 +540,55 @@ class Lfm3PreTrainedModel(PreTrainedModel):
585
  _supports_flash_attn = True
586
  _supports_sdpa = True
587
  _supports_flex_attn = True
588
- _can_compile_fullgraph = False
 
589
  _supports_attention_backend = True
590
  _can_record_outputs = {
591
  "hidden_states": Lfm3DecoderLayer,
592
  "attentions": Lfm3Attention,
593
  }
594
 
 
595
  def _init_weights(self, module):
596
- # Re-compute RotaryEmbedding inv_freq buffers. Required because
597
- # `from_pretrained` constructs the model on a meta device and
598
- # materializes persistent=False buffers as uninitialized memory after
599
- # the fact. We can't rely on values registered during meta-device
600
- # __init__ — they aren't preserved through materialization. Re-run
601
- # `compute_default_rope_parameters` for each attention layer type so
602
- # the per-layer-type inv_freq buffers (`full_attention_inv_freq`,
603
- # `sliding_attention_inv_freq`) get the correct values for
604
- # their configured theta / partial_rotary_factor, instead of HF's
605
- # default branch which assumes a single global table.
606
- if isinstance(module, Lfm3RotaryEmbedding):
607
- for layer_type in module.attention_layer_types:
608
- if layer_type not in module.rope_type:
609
- continue
610
- inv_freq, attention_scaling = module.compute_default_rope_parameters(
611
- module.config,
612
- device=getattr(module, f"{layer_type}_inv_freq").device,
613
- layer_type=layer_type,
614
- )
615
- with torch.no_grad():
616
- getattr(module, f"{layer_type}_inv_freq").copy_(inv_freq)
617
- getattr(module, f"{layer_type}_original_inv_freq").copy_(inv_freq)
618
- setattr(module, f"{layer_type}_attention_scaling", attention_scaling)
619
- return
620
  super()._init_weights(module)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
621
 
622
 
623
  @auto_docstring
624
  class Lfm3Model(Lfm3PreTrainedModel):
 
 
 
625
  def __init__(self, config: Lfm3Config):
626
  super().__init__(config)
627
  self.padding_idx = config.pad_token_id
@@ -631,123 +598,161 @@ class Lfm3Model(Lfm3PreTrainedModel):
631
  self.layers = nn.ModuleList(
632
  [Lfm3DecoderLayer(config, layer_idx) for layer_idx in range(config.num_hidden_layers)]
633
  )
 
634
  self.gradient_checkpointing = False
635
- # Single Gemma3-style rotary module. Per-attention-layer-type inv_freq
636
- # buffers (`full_attention_inv_freq`, `sliding_attention_inv_freq`)
637
- # are registered inside the module based on `config.rope_parameters`.
638
- # `forward` is dispatched per layer below via `layer_type=...`.
639
- self.rotary_emb = Lfm3RotaryEmbedding(config)
640
  self.embedding_norm = Lfm3RMSNorm(config.hidden_size, eps=config.norm_eps)
641
 
642
  # Initialize weights and apply final processing
643
  self.post_init()
644
 
645
- @check_model_inputs
 
646
  @auto_docstring
647
  def forward(
648
  self,
649
- input_ids: Optional[torch.LongTensor] = None,
650
- attention_mask: Optional[torch.Tensor] = None,
651
- position_ids: Optional[torch.LongTensor] = None,
652
- past_key_values: Optional[Cache] = None,
653
- inputs_embeds: Optional[torch.FloatTensor] = None,
654
- use_cache: Optional[bool] = None,
655
- cache_position: Optional[torch.LongTensor] = None,
656
  **kwargs: Unpack[TransformersKwargs],
657
  ) -> BaseModelOutputWithPast:
658
  r"""
659
- cache_position (`torch.LongTensor` of shape `(sequence_length)`, *optional*):
660
- Indices depicting the position of the input sequence tokens in the sequence. Used to update the
661
- cache in the correct position and to infer the complete sequence length.
 
662
  """
663
  if (input_ids is None) ^ (inputs_embeds is not None):
664
  raise ValueError("You must specify exactly one of input_ids or inputs_embeds")
 
 
 
 
665
 
666
  if inputs_embeds is None:
667
  inputs_embeds = self.embed_tokens(input_ids)
668
 
669
  if use_cache and past_key_values is None:
670
- # DynamicCache(config) builds the right per-layer cache from
671
- # config.layer_types: DynamicLayer (full_attention),
672
- # DynamicSlidingWindowLayer (sliding_attention, auto-bounded), and
673
- # LinearAttentionLayer (conv, conv-state buffer).
674
  past_key_values = DynamicCache(config=self.config)
675
-
676
- if cache_position is None:
677
- past_seen_tokens = (
678
- past_key_values.get_seq_length() if past_key_values is not None else 0
679
- )
680
- cache_position = torch.arange(
681
- past_seen_tokens,
682
- past_seen_tokens + inputs_embeds.shape[1],
683
- device=inputs_embeds.device,
 
 
 
684
  )
 
 
 
 
 
 
 
 
 
685
 
686
- if position_ids is None:
687
- position_ids = cache_position.unsqueeze(0)
688
-
689
- # Packed-sequence document boundaries for the conv layers: when several
690
- # documents are concatenated into one row, `position_ids` restarts at 0
691
- # per document, so a cumulative count of those resets gives a per-token
692
- # document id. Conv layers use it to avoid bleeding across documents.
693
- # Only meaningful during prefill (multi-token); a single decode token has
694
- # nothing to segment.
 
 
695
  seq_idx = None
696
- if inputs_embeds.shape[1] > 1:
697
- doc_id = (position_ids == 0).cumsum(-1) - 1
698
- if doc_id[..., -1].max() > 0: # >1 document present
699
- seq_idx = doc_id.to(torch.int32)
700
-
701
- # Build one causal mask per attention layer type present in the model.
702
- # SWA layers get a sliding-window causal mask; full-attention layers
703
- # get a plain causal mask. Conv layers receive the RAW 2D padding mask
704
- # (matching upstream `Lfm2Model.forward`'s `linear_attention` route)
705
- # so `Lfm3ShortConv.apply_mask_to_padding_states` can zero pad
706
- # positions during prefill. Conv during decode (single token) doesn't
707
- # need a mask — None is fine.
708
- mask_kwargs = {
709
- "config": self.config,
710
- "inputs_embeds": inputs_embeds,
711
- "attention_mask": attention_mask,
712
- "past_key_values": past_key_values,
713
- "position_ids": position_ids,
714
- }
715
- causal_masks_by_type: dict[str, Optional[torch.Tensor]] = {}
716
- attention_types_in_model = {
717
- lt for lt in self.config.layer_types if lt in self.config._attention_layer_types
718
- }
719
- if "full_attention" in attention_types_in_model:
720
- causal_masks_by_type["full_attention"] = create_causal_mask(**mask_kwargs)
721
- if "sliding_attention" in attention_types_in_model:
722
- causal_masks_by_type["sliding_attention"] = create_sliding_window_causal_mask(
723
- **mask_kwargs
724
- )
725
- # Conv layers want the 2D padding mask only during prefill. For
726
- # single-token decode (`shape[1] == 1`) there's nothing to pad-zero.
727
- conv_mask = attention_mask if inputs_embeds.shape[1] != 1 else None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
728
 
729
  hidden_states = inputs_embeds
730
- # Pre-compute one (cos, sin) per attention layer type. Conv-only models
731
- # leave this dict empty; the per-layer dispatch below only reads it for
732
- # attention layers.
733
- position_embeddings_by_type = {
734
- layer_type: self.rotary_emb(hidden_states, position_ids, layer_type=layer_type)
735
- for layer_type in self.rotary_emb.rope_type
736
  }
737
 
738
- for decoder_layer in self.layers[: self.config.num_hidden_layers]:
739
- if decoder_layer.is_attention_layer:
740
- layer_mask = causal_masks_by_type.get(decoder_layer.layer_type)
741
- else:
742
- layer_mask = conv_mask
743
- layer_pos_embeddings = position_embeddings_by_type.get(decoder_layer.layer_type)
744
  hidden_states = decoder_layer(
745
  hidden_states,
746
- attention_mask=layer_mask,
 
747
  position_ids=position_ids,
748
  past_key_values=past_key_values,
749
- cache_position=cache_position,
750
- position_embeddings=layer_pos_embeddings,
751
  seq_idx=seq_idx,
752
  **kwargs,
753
  )
@@ -760,11 +765,46 @@ class Lfm3Model(Lfm3PreTrainedModel):
760
  )
761
 
762
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
763
  @auto_docstring
764
  class Lfm3ForCausalLM(Lfm3PreTrainedModel, GenerationMixin):
765
  _tied_weights_keys = {"lm_head.weight": "model.embed_tokens.weight"}
766
- _tp_plan = {"lm_head": "colwise_rep"}
767
  _pp_plan = {"lm_head": (["hidden_states"], ["logits"])}
 
768
 
769
  def __init__(self, config):
770
  super().__init__(config)
@@ -779,22 +819,31 @@ class Lfm3ForCausalLM(Lfm3PreTrainedModel, GenerationMixin):
779
  @auto_docstring
780
  def forward(
781
  self,
782
- input_ids: Optional[torch.LongTensor] = None,
783
- attention_mask: Optional[torch.Tensor] = None,
784
- position_ids: Optional[torch.LongTensor] = None,
785
- past_key_values: Optional[Cache] = None,
786
- inputs_embeds: Optional[torch.FloatTensor] = None,
787
- labels: Optional[torch.LongTensor] = None,
788
- use_cache: Optional[bool] = None,
789
- cache_position: Optional[torch.LongTensor] = None,
790
- logits_to_keep: Union[int, torch.Tensor] = 0,
791
  **kwargs: Unpack[TransformersKwargs],
792
  ) -> CausalLMOutputWithPast:
793
  r"""
794
- cache_position (`torch.LongTensor` of shape `(sequence_length)`, *optional*):
795
- Indices depicting the position of the input sequence tokens in the sequence. Used to update the
796
- cache in the correct position and to infer the complete sequence length.
797
- """
 
 
 
 
 
 
 
 
 
 
798
  outputs: BaseModelOutputWithPast = self.model(
799
  input_ids=input_ids,
800
  attention_mask=attention_mask,
@@ -802,21 +851,17 @@ class Lfm3ForCausalLM(Lfm3PreTrainedModel, GenerationMixin):
802
  past_key_values=past_key_values,
803
  inputs_embeds=inputs_embeds,
804
  use_cache=use_cache,
805
- cache_position=cache_position,
806
  **kwargs,
807
  )
808
 
809
  hidden_states = outputs.last_hidden_state
810
- slice_indices = (
811
- slice(-logits_to_keep, None) if isinstance(logits_to_keep, int) else logits_to_keep
812
- )
813
  logits = self.lm_head(hidden_states[:, slice_indices, :])
814
 
815
  loss = None
816
  if labels is not None:
817
- loss = self.loss_function(
818
- logits=logits, labels=labels, vocab_size=self.config.vocab_size, **kwargs
819
- )
820
 
821
  return CausalLMOutputWithPast(
822
  loss=loss,
@@ -826,5 +871,11 @@ class Lfm3ForCausalLM(Lfm3PreTrainedModel, GenerationMixin):
826
  attentions=outputs.attentions,
827
  )
828
 
 
 
 
 
 
 
829
 
830
  __all__ = ["Lfm3ForCausalLM", "Lfm3Model", "Lfm3PreTrainedModel"]
 
1
+ # Copyright 2026 The HuggingFace Team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ import copy
15
+ from collections.abc import Callable
16
 
17
  import torch
18
  import torch.nn.functional as F
19
  from torch import nn
20
 
21
+ from transformers import initialization as init
22
+ from transformers.activations import ACT2FN
23
  from transformers.cache_utils import Cache, DynamicCache
24
+ from transformers.configuration_utils import PreTrainedConfig
25
  from transformers.generation import GenerationMixin
26
  from transformers.integrations import (
27
  use_kernel_forward_from_hub,
28
+ use_kernel_func_from_hub_with_fallback,
29
  use_kernelized_func,
30
  )
31
+ from transformers.integrations.accelerate import force_accelerate_hooks
32
+ from transformers.masking_utils import (
33
+ create_causal_mask,
34
+ create_recurrent_attention_mask,
35
+ create_sliding_window_causal_mask,
36
+ )
37
  from transformers.modeling_layers import GradientCheckpointingLayer
38
  from transformers.modeling_outputs import BaseModelOutputWithPast, CausalLMOutputWithPast
39
  from transformers.modeling_rope_utils import ROPE_INIT_FUNCTIONS, dynamic_rope_update
40
  from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS, PreTrainedModel
41
  from transformers.processing_utils import Unpack
42
+ from transformers.utils import TransformersKwargs, auto_docstring, can_return_tuple, torch_compilable_check
43
+ from transformers.utils.generic import merge_with_config_defaults
44
+ from transformers.utils.import_utils import is_tracing
45
+ from transformers.utils.output_capturing import capture_outputs
46
 
47
  from .configuration_lfm3 import Lfm3Config
48
+ from .lfm3_conv import can_use_lfm3_conv, lfm3_causal_conv1d, lfm3_causal_conv1d_update
 
 
 
 
 
49
 
50
 
51
  @use_kernel_forward_from_hub("RMSNorm")
52
  class Lfm3RMSNorm(nn.Module):
53
+ def __init__(self, hidden_size, eps: float = 1e-6) -> None:
54
+ """
55
+ Lfm3RMSNorm is equivalent to T5LayerNorm
56
+ """
57
  super().__init__()
58
  self.weight = nn.Parameter(torch.ones(hidden_size))
59
  self.variance_epsilon = eps
60
 
61
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
62
  input_dtype = hidden_states.dtype
63
  hidden_states = hidden_states.to(torch.float32)
64
  variance = hidden_states.pow(2).mean(-1, keepdim=True)
 
70
 
71
 
72
  class Lfm3RotaryEmbedding(nn.Module):
73
+ """
74
+ Rotary embedding per attention layer type, like Gemma 3; `conv` layers register no buffers. Partial RoPE keeps the
75
+ frequency progression of the full head dimension and zeroes the tail instead of shrinking the rotated dimension.
 
 
 
 
 
 
 
 
 
76
  """
77
 
78
  def __init__(self, config: Lfm3Config, device=None):
 
80
  self.max_seq_len_cached = config.max_position_embeddings
81
  self.original_max_seq_len = config.max_position_embeddings
82
  self.config = config
83
+ self.layer_types = sorted(set(config.layer_types) & set(config.rope_parameters))
84
+ self.rope_type = {}
85
+ for layer_type in self.layer_types:
86
+ rope_params = self.config.rope_parameters[layer_type]
87
+ self.rope_type[layer_type] = rope_params["rope_type"]
88
+ curr_inv_freq, curr_attention_scaling = self.compute_rope_parameters(config, device, layer_type=layer_type)
89
+ setattr(self, f"{layer_type}_inv_freq", nn.Buffer(curr_inv_freq, persistent=False))
90
+ setattr(self, f"{layer_type}_original_inv_freq", nn.Buffer(curr_inv_freq.clone(), persistent=False))
91
+ setattr(self, f"{layer_type}_attention_scaling", curr_attention_scaling)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
 
93
  @staticmethod
94
  def compute_default_rope_parameters(
95
+ config: Lfm3Config, device=None, layer_type: str | None = None, **kwargs
96
+ ) -> tuple[torch.Tensor, float]:
97
+ """
98
+ Inverse frequencies of the default RoPE with LFM3's zero-tail partial rotation: the first
99
+ `partial_rotary_factor * head_dim // 2` frequencies follow the progression of the full head dimension and the
100
+ rest are zero, leaving those dimensions unrotated.
 
 
 
 
101
 
102
+ Returns:
103
+ Tuple of (`torch.Tensor`, `float`): the inverse frequencies and the attention scaling factor (always 1).
104
+ """
105
+ base = config.rope_parameters[layer_type]["rope_theta"]
106
+ partial_rotary_factor = config.rope_parameters[layer_type].get("partial_rotary_factor", 1.0)
107
+ dim = getattr(config, "head_dim", None) or config.hidden_size // config.num_attention_heads
108
  rope_angles = int(partial_rotary_factor * dim) // 2
109
+ inv_freq = 1.0 / (base ** (torch.arange(0, 2 * rope_angles, 2, dtype=torch.float) / dim))
110
+ inv_freq = torch.cat((inv_freq, torch.zeros(dim // 2 - rope_angles, dtype=torch.float)))
111
+ return inv_freq.to(device), 1.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
  @torch.no_grad()
114
  @dynamic_rope_update # power user: used with advanced RoPE types (e.g. dynamic rope)
115
+ def forward(self, x, position_ids, layer_type):
116
  inv_freq = getattr(self, f"{layer_type}_inv_freq")
117
  attention_scaling = getattr(self, f"{layer_type}_attention_scaling")
118
 
119
+ freqs = position_ids[..., None].float() * inv_freq.to(device=x.device, dtype=torch.float)
120
+ emb = torch.cat((freqs, freqs), dim=-1)
121
+ cos = emb.cos() * attention_scaling
122
+ sin = emb.sin() * attention_scaling
 
 
 
 
 
 
 
 
 
123
 
124
  return cos.to(dtype=x.dtype), sin.to(dtype=x.dtype)
125
 
126
+ @classmethod
127
+ def compute_rope_parameters(
128
+ cls, config: Lfm3Config, device=None, layer_type: str | None = None
129
+ ) -> tuple[torch.Tensor, float]:
130
+ """Inverse frequencies of any RoPE type in the zero-tail layout: scaled types are evaluated over the full head
131
+ dimension and truncated to the rotated prefix."""
132
+ rope_params = config.rope_parameters[layer_type]
133
+ if rope_params["rope_type"] == "default":
134
+ return cls.compute_default_rope_parameters(config, device, layer_type=layer_type)
135
+ full_config = copy.copy(config)
136
+ full_config.rope_parameters = {
137
+ **config.rope_parameters,
138
+ layer_type: {**rope_params, "partial_rotary_factor": 1.0},
139
+ }
140
+ inv_freq, attention_factor = ROPE_INIT_FUNCTIONS[rope_params["rope_type"]](
141
+ full_config, device, layer_type=layer_type
142
+ )
143
+ dim = getattr(config, "head_dim", None) or config.hidden_size // config.num_attention_heads
144
+ rope_angles = int(rope_params.get("partial_rotary_factor", 1.0) * dim) // 2
145
+ inv_freq = torch.cat((inv_freq[:rope_angles], inv_freq.new_zeros(dim // 2 - rope_angles)))
146
+ return inv_freq, attention_factor
147
+
148
 
149
  class Lfm3MLP(nn.Module):
150
+ def __init__(self, config: Lfm3Config):
151
  super().__init__()
152
+ self.w1 = nn.Linear(config.hidden_size, config.intermediate_size, bias=False)
153
+ self.w3 = nn.Linear(config.hidden_size, config.intermediate_size, bias=False)
154
+ self.w2 = nn.Linear(config.intermediate_size, config.hidden_size, bias=False)
 
 
 
 
155
 
156
  def forward(self, x):
157
  return self.w2(F.silu(self.w1(x)) * self.w3(x))
158
 
159
 
 
160
  def rotate_half(x):
161
  """Rotates half the hidden dims of the input."""
162
  x1 = x[..., : x.shape[-1] // 2]
163
+ x2 = x[..., x.shape[-1] // 2 :]
164
  return torch.cat((-x2, x1), dim=-1)
165
 
166
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:
168
+ """
169
+ This is the equivalent of torch.repeat_interleave(x, dim=1, repeats=n_rep). The hidden states go from (batch,
170
+ num_key_value_heads, seqlen, head_dim) to (batch, num_attention_heads, seqlen, head_dim)
171
+ """
172
  batch, num_key_value_heads, slen, head_dim = hidden_states.shape
173
  if n_rep == 1:
174
  return hidden_states
175
+ hidden_states = hidden_states[:, :, None, :, :].expand(batch, num_key_value_heads, n_rep, slen, head_dim)
 
 
176
  return hidden_states.reshape(batch, num_key_value_heads * n_rep, slen, head_dim)
177
 
178
 
 
181
  query: torch.Tensor,
182
  key: torch.Tensor,
183
  value: torch.Tensor,
184
+ attention_mask: torch.Tensor | None,
185
  scaling: float,
186
  dropout: float = 0.0,
187
  **kwargs: Unpack[TransformersKwargs],
 
191
 
192
  attn_weights = torch.matmul(query, key_states.transpose(2, 3)) * scaling
193
  if attention_mask is not None:
194
+ attn_weights = attn_weights + attention_mask
 
195
 
196
  attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query.dtype)
197
  attn_weights = nn.functional.dropout(attn_weights, p=dropout, training=module.training)
 
201
  return attn_output, attn_weights
202
 
203
 
204
+ def apply_rotary_pos_emb(q, k, cos, sin, unsqueeze_dim=1):
205
+ """Applies RoPE to the query and key tensors in float32, like the reference implementation."""
206
+ cos = cos.unsqueeze(unsqueeze_dim).float()
207
+ sin = sin.unsqueeze(unsqueeze_dim).float()
208
+ q_embed = (q.float() * cos) + (rotate_half(q.float()) * sin)
209
+ k_embed = (k.float() * cos) + (rotate_half(k.float()) * sin)
210
+ return q_embed.to(q.dtype), k_embed.to(k.dtype)
211
+
212
+
213
  class Lfm3Attention(nn.Module):
214
+ """Llama attention with QK RMSNorm, a sigmoid output gate per head, a sliding window on `sliding_attention`
215
+ layers and RoPE in float32."""
 
 
 
 
 
 
 
216
 
217
+ def __init__(self, config: Lfm3Config, layer_idx: int):
218
  super().__init__()
219
  self.config = config
220
  self.layer_idx = layer_idx
221
+ self.head_dim = getattr(config, "head_dim", config.hidden_size // config.num_attention_heads)
 
 
 
 
 
 
222
  self.num_key_value_groups = config.num_attention_heads // config.num_key_value_heads
223
+ self.scaling = self.head_dim**-0.5
224
+ self.is_causal = True
225
+ self.q_proj = nn.Linear(config.hidden_size, config.num_attention_heads * self.head_dim, bias=False)
226
+ self.k_proj = nn.Linear(config.hidden_size, config.num_key_value_heads * self.head_dim, bias=False)
227
+ self.v_proj = nn.Linear(config.hidden_size, config.num_key_value_heads * self.head_dim, bias=False)
228
+ self.o_proj = nn.Linear(config.num_attention_heads * self.head_dim, config.hidden_size, bias=False)
 
 
 
 
 
 
 
229
  self.q_norm = Lfm3RMSNorm(self.head_dim, eps=config.norm_eps)
230
  self.k_norm = Lfm3RMSNorm(self.head_dim, eps=config.norm_eps)
231
+ self.gate_proj = nn.Linear(config.hidden_size, config.num_attention_heads, bias=False)
232
+ self.sliding_window = config.sliding_window if config.layer_types[layer_idx] == "sliding_attention" else None
 
 
233
 
234
  def forward(
235
  self,
236
  hidden_states: torch.Tensor,
237
  position_embeddings: tuple[torch.Tensor, torch.Tensor],
238
+ attention_mask: torch.Tensor | None,
239
+ past_key_values: Cache | None = None,
240
+ **kwargs: Unpack[TransformersKwargs],
241
+ ) -> tuple[torch.Tensor, torch.Tensor | None]:
242
  input_shape = hidden_states.shape[:-1]
243
  hidden_shape = (*input_shape, -1, self.head_dim)
244
 
 
250
  query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin)
251
 
252
  if past_key_values is not None:
253
+ key_states, value_states = past_key_values.update(key_states, value_states, self.layer_idx)
 
 
254
 
255
+ attention_interface: Callable = ALL_ATTENTION_FUNCTIONS.get_interface(
256
+ self.config._attn_implementation, eager_attention_forward
257
+ )
258
 
259
  attn_output, attn_weights = attention_interface(
260
  self,
 
267
  sliding_window=self.sliding_window,
268
  **kwargs,
269
  )
270
+ gate = torch.sigmoid(self.gate_proj(hidden_states)).unsqueeze(-1)
271
+ attn_output = (attn_output * gate).reshape(*input_shape, -1).contiguous()
272
+ attn_output = self.o_proj(attn_output)
273
+ return attn_output, attn_weights
 
 
 
 
274
 
275
 
276
  def apply_mask_to_padding_states(hidden_states, attention_mask):
 
 
 
 
 
 
277
  """
278
+ Tunes out the hidden states for padding tokens, see https://github.com/state-spaces/mamba/issues/66
279
+ """
280
+ # NOTE: attention mask is a 2D boolean tensor
281
+ if attention_mask is not None:
282
  dtype = hidden_states.dtype
283
  hidden_states = (hidden_states * attention_mask[:, :, None]).to(dtype)
 
 
284
 
285
+ return hidden_states
 
286
 
287
 
288
+ @use_kernel_func_from_hub_with_fallback("causal_conv1d_update", "causal_conv1d")
289
+ def causal_conv1d_update(
290
+ hidden_states: torch.Tensor,
291
+ conv_state: torch.Tensor,
292
+ weight: nn.Parameter,
293
+ bias: nn.Parameter | None = None,
294
+ activation: str | None = None,
295
+ ):
296
+ _, hidden_size, seq_len = hidden_states.shape
297
+ state_len = conv_state.shape[-1]
298
+
299
+ hidden_states_new = torch.cat([conv_state, hidden_states], dim=-1).to(weight.dtype)
300
+ conv_state.copy_(hidden_states_new[:, :, -state_len:])
301
+ out = F.conv1d(hidden_states_new, weight.unsqueeze(1), bias, padding=0, groups=hidden_size)
302
+ out = out[:, :, -seq_len:]
303
+ if activation is not None:
304
+ out = ACT2FN[activation](out)
305
+ return out.to(hidden_states.dtype)
306
+
307
+
308
+ @use_kernel_func_from_hub_with_fallback("causal_conv1d_fn", "causal_conv1d")
309
+ def causal_conv1d_fn(
310
+ hidden_states: torch.Tensor,
311
+ weight: nn.Parameter,
312
+ bias: nn.Parameter | None = None,
313
+ activation: str | None = None,
314
+ **kwargs,
315
+ ):
316
+ _, hidden_size, seq_len = hidden_states.shape
317
+ padding = weight.shape[-1] - 1
318
+
319
+ out = F.conv1d(
320
+ hidden_states.to(weight.dtype),
321
+ weight=weight.unsqueeze(1),
322
+ bias=bias,
323
+ padding=padding,
324
+ groups=hidden_size,
325
+ )[:, :, :seq_len]
326
+ if activation is not None:
327
+ out = ACT2FN[activation](out)
328
+ return out.to(hidden_states.dtype)
329
+
330
+
331
+ @use_kernelized_func([causal_conv1d_update, causal_conv1d_fn])
332
  class Lfm3ShortConv(nn.Module):
333
+ """LFM2 gated short convolution with packed-document boundaries (`seq_idx`), the padding mask re-applied after a
334
+ biased `in_proj`, and width-7 Triton inference kernels with a PyTorch fallback."""
 
 
 
 
 
 
335
 
336
  def __init__(
337
  self,
 
341
  super().__init__()
342
  self.config = config
343
  self.layer_idx = layer_idx
344
+ self.conv_kernel_size = config.conv_L_cache
345
  self.bias = config.conv_bias
346
 
 
347
  self.conv = nn.Conv1d(
348
+ in_channels=config.hidden_size,
349
+ out_channels=config.hidden_size,
350
+ kernel_size=self.conv_kernel_size,
351
+ groups=config.hidden_size,
352
  bias=self.bias,
353
+ padding=self.conv_kernel_size - 1,
354
  )
355
+ self.in_proj = nn.Linear(config.hidden_size, 3 * config.hidden_size, bias=self.bias)
356
+ self.out_proj = nn.Linear(config.hidden_size, config.hidden_size, bias=self.bias)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
357
 
358
+ self.layer_type = config.layer_types[layer_idx]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
359
 
360
+ @force_accelerate_hooks("conv")
361
+ def forward(
362
  self,
363
+ hidden_states: torch.Tensor,
364
+ past_key_values: Cache | None = None,
365
+ attention_mask: torch.Tensor | None = None,
366
+ seq_idx: torch.IntTensor | None = None,
367
  ):
368
+ seq_len = hidden_states.shape[1]
369
+
370
+ hidden_states = apply_mask_to_padding_states(hidden_states, attention_mask)
371
+ BCx = self.in_proj(hidden_states).transpose(-1, -2)
372
+ B, C, x = BCx.chunk(3, dim=-2)
373
+ hidden_states = B * x
374
+ if attention_mask is not None:
375
+ # with `conv_bias` the projection makes the padded positions non-zero again
376
+ hidden_states = hidden_states * attention_mask[:, None, :].to(hidden_states.dtype)
377
+
378
+ use_precomputed_states = past_key_values is not None and past_key_values.has_previous_state(self.layer_idx)
379
+ use_kernels = 2 <= self.conv_kernel_size <= 4
380
+ use_wide_kernel = can_use_lfm3_conv(hidden_states, self.conv.weight)
381
+
382
+ if use_precomputed_states and seq_len == 1 and not past_key_values.layers[self.layer_idx].record_past:
383
+ conv_state = past_key_values.layers[self.layer_idx].conv_states[0]
384
+ if use_wide_kernel and conv_state.shape[-1] in (6, 7):
385
+ hidden_states = lfm3_causal_conv1d_update(
386
+ hidden_states, conv_state, self.conv.weight.squeeze(1), self.conv.bias
387
+ )
388
+ elif use_kernels:
389
+ hidden_states = causal_conv1d_update(
390
+ hidden_states, conv_state, self.conv.weight.squeeze(1), self.conv.bias
391
+ )
392
+ else:
393
+ hidden_states = torch.cat([conv_state, hidden_states], dim=-1)
394
+ conv_state.copy_(hidden_states[:, :, -conv_state.shape[-1] :])
395
+ hidden_states = self._conv(hidden_states)[:, :, -seq_len:]
396
  else:
397
+ if past_key_values is not None and seq_idx is not None:
398
+ # `seq_idx` is only set on an empty cache, and the decode steps read the cached window without it:
399
+ # only the last document (after any positional left padding) may enter the window
400
+ last_document = (seq_idx == seq_idx[:, -1:])[:, None].to(hidden_states.dtype)
401
+ past_key_values.update_conv_state(
402
+ hidden_states * last_document, self.layer_idx, conv_kernel_size=self.conv_kernel_size
403
+ )
404
+ elif past_key_values is not None:
405
+ hidden_states = past_key_values.update_conv_state(
406
+ hidden_states, self.layer_idx, conv_kernel_size=self.conv_kernel_size
407
+ )
408
 
409
+ if use_wide_kernel:
410
+ hidden_states = lfm3_causal_conv1d(hidden_states, self.conv.weight.squeeze(1), self.conv.bias, seq_idx)
411
+ elif seq_idx is not None:
412
+ hidden_states = self._segmented_conv(hidden_states, seq_idx)
413
+ elif use_kernels:
414
+ hidden_states = causal_conv1d_fn(hidden_states, self.conv.weight.squeeze(1), self.conv.bias)
415
  else:
416
+ hidden_states = self._conv(hidden_states)
417
+
418
+ # Drop the additional previous states
419
+ if past_key_values is not None:
420
+ hidden_states = hidden_states[:, :, -seq_len:]
421
 
422
+ y = C * hidden_states
423
  y = y.transpose(-1, -2).contiguous()
424
  y = self.out_proj(y)
425
  return y
426
 
427
+ def _conv(self, hidden_states: torch.Tensor) -> torch.Tensor:
428
+ """Causal depthwise conv in torch: the `causal_conv1d` kernels only accept widths 2 to 4."""
429
+ conv_out = self.conv(hidden_states.to(self.conv.weight.dtype))[..., : hidden_states.shape[-1]]
430
+ return conv_out.to(hidden_states.dtype)
431
+
432
+ def _segmented_conv(self, hidden_states: torch.Tensor, seq_idx: torch.Tensor) -> torch.Tensor:
433
+ """Causal depthwise conv that only reads positions sharing the current token's document index."""
434
+ kernel_size = self.conv_kernel_size
435
+ seq_idx = F.pad(seq_idx, (hidden_states.shape[-1] - seq_idx.shape[-1] + kernel_size - 1, 0), value=-1)
436
+ windows = F.pad(hidden_states, (kernel_size - 1, 0)).unfold(-1, kernel_size, 1)
437
+ seq_idx_windows = seq_idx.unfold(-1, kernel_size, 1)
438
+ same_document = (seq_idx_windows == seq_idx_windows[..., -1:]).unsqueeze(1)
439
+ conv_out = torch.einsum("bdtk,dk->bdt", windows * same_document.to(windows.dtype), self.conv.weight.squeeze(1))
440
+ if self.bias:
441
+ conv_out = conv_out + self.conv.bias[None, :, None]
442
+ return conv_out.to(hidden_states.dtype)
 
 
 
 
 
443
 
444
 
445
  class Lfm3DecoderLayer(GradientCheckpointingLayer):
446
  def __init__(self, config: Lfm3Config, layer_idx: int):
447
  super().__init__()
448
+ self.is_attention_layer = config.layer_types[layer_idx] in config._attention_layer_types
449
+ self.use_sandwich_norm = config.use_sandwich_norm
 
450
 
451
+ if self.is_attention_layer:
452
  self.self_attn = Lfm3Attention(config, layer_idx)
 
 
 
 
453
  else:
454
+ self.conv = Lfm3ShortConv(config, layer_idx)
455
+ self.feed_forward = Lfm3MLP(config)
 
 
 
456
  self.operator_norm = Lfm3RMSNorm(config.hidden_size, eps=config.norm_eps)
457
  self.ffn_norm = Lfm3RMSNorm(config.hidden_size, eps=config.norm_eps)
458
  if self.use_sandwich_norm:
 
462
  def forward(
463
  self,
464
  hidden_states: torch.Tensor,
465
+ position_embeddings: tuple[torch.Tensor, torch.Tensor] | None = None,
466
+ attention_mask: torch.Tensor | None = None,
467
+ position_ids: torch.LongTensor | None = None,
468
+ past_key_values: Cache | None = None,
469
+ seq_idx: torch.IntTensor | None = None,
 
470
  **kwargs,
471
  ) -> torch.Tensor:
472
  residual = hidden_states
 
489
  if self.use_sandwich_norm:
490
  hidden_states = self.post_operator_norm(hidden_states)
491
  hidden_states = hidden_states + residual
492
+
493
  ffn_out = self.feed_forward(self.ffn_norm(hidden_states))
494
  if self.use_sandwich_norm:
495
  ffn_out = self.post_ffn_norm(ffn_out)
 
498
  return hidden_states
499
 
500
 
501
+ def create_masks_by_layer_type(
502
+ config: PreTrainedConfig,
503
+ inputs_embeds: torch.Tensor,
504
+ attention_mask: torch.Tensor | None,
505
+ past_key_values: Cache | None,
506
+ position_ids: torch.Tensor | None = None,
507
+ ) -> dict:
508
+ """The cache sizes each mask from the layer it is asked about, and the generic mask utilities ask about the first
509
+ non-sliding layer, which for a leading `conv` layer redirects to the first attention layer of any type: name the
510
+ first layer of each mask's own type instead."""
511
+ config = config.get_text_config()
512
+ mask_kwargs = {
513
+ "config": config,
514
+ "inputs_embeds": inputs_embeds,
515
+ "attention_mask": attention_mask,
516
+ "past_key_values": past_key_values,
517
+ "position_ids": position_ids,
518
+ }
519
+ layer_types, causal_mask_mapping = config.layer_types, {}
520
+ if "full_attention" in layer_types:
521
+ layer_idx = layer_types.index("full_attention")
522
+ causal_mask_mapping["full_attention"] = create_causal_mask(**mask_kwargs, layer_idx=layer_idx)
523
+ if "sliding_attention" in layer_types:
524
+ layer_idx = layer_types.index("sliding_attention")
525
+ causal_mask_mapping["sliding_attention"] = create_sliding_window_causal_mask(
526
+ **mask_kwargs, layer_idx=layer_idx
527
+ )
528
+ if "conv" in layer_types:
529
+ causal_mask_mapping["conv"] = create_recurrent_attention_mask(**mask_kwargs)
530
+ return causal_mask_mapping
531
+
532
+
533
  @auto_docstring
534
  class Lfm3PreTrainedModel(PreTrainedModel):
535
  config: Lfm3Config
 
540
  _supports_flash_attn = True
541
  _supports_sdpa = True
542
  _supports_flex_attn = True
543
+
544
+ _can_compile_fullgraph = True
545
  _supports_attention_backend = True
546
  _can_record_outputs = {
547
  "hidden_states": Lfm3DecoderLayer,
548
  "attentions": Lfm3Attention,
549
  }
550
 
551
+ @torch.no_grad()
552
  def _init_weights(self, module):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
553
  super()._init_weights(module)
554
+ if isinstance(module, Lfm3RotaryEmbedding):
555
+ for layer_type in module.layer_types:
556
+ curr_inv_freq, _ = module.compute_rope_parameters(module.config, layer_type=layer_type)
557
+ init.copy_(getattr(module, f"{layer_type}_inv_freq"), curr_inv_freq)
558
+ init.copy_(getattr(module, f"{layer_type}_original_inv_freq"), curr_inv_freq)
559
+
560
+ @staticmethod
561
+ def create_masks_for_generate(
562
+ config: PreTrainedConfig,
563
+ inputs_embeds: torch.Tensor,
564
+ attention_mask: torch.Tensor | None,
565
+ past_key_values: Cache | None,
566
+ position_ids: torch.Tensor | None = None,
567
+ **kwargs,
568
+ ) -> dict:
569
+ return create_masks_by_layer_type(config, inputs_embeds, attention_mask, past_key_values, position_ids)
570
+
571
+
572
+ def packed_under_padding(position_ids: torch.Tensor, padding_mask: torch.Tensor, filled) -> torch.Tensor:
573
+ """Padding repeats position 0, so packing is a restart between two kept tokens, or a kept position 0 on a filled
574
+ cache whose row holds a kept token; attention under a padding mask cannot isolate it."""
575
+ restarts = torch.diff(position_ids, prepend=position_ids[:, :1] - 1, dim=-1) != 1
576
+ valid = padding_mask[:, -restarts.shape[-1] :].bool()
577
+ packed = restarts[:, 1:] & valid[:, 1:] & valid[:, :-1]
578
+ if filled:
579
+ # a row whose cached tokens are all padding (chunked prefill of a left-padded batch) has no document to pack
580
+ # against; a mask covering only the chunk keeps the strict rule
581
+ cached = padding_mask[:, : padding_mask.shape[-1] - restarts.shape[-1]]
582
+ cached_valid = cached.bool().any(-1, keepdim=True) if cached.shape[-1] else torch.ones_like(valid[:, :1])
583
+ packed = packed | (valid[:, :1] & (position_ids[:, :1] == 0) & cached_valid)
584
+ return packed
585
 
586
 
587
  @auto_docstring
588
  class Lfm3Model(Lfm3PreTrainedModel):
589
+ """LFM2 model with sliding-attention layers as a third layer type: the mask and the rotary embedding are built per
590
+ layer type, and packed documents (`position_ids` restarting) reset the short-conv window through `seq_idx`."""
591
+
592
  def __init__(self, config: Lfm3Config):
593
  super().__init__(config)
594
  self.padding_idx = config.pad_token_id
 
598
  self.layers = nn.ModuleList(
599
  [Lfm3DecoderLayer(config, layer_idx) for layer_idx in range(config.num_hidden_layers)]
600
  )
601
+ self.rotary_emb = Lfm3RotaryEmbedding(config=config)
602
  self.gradient_checkpointing = False
 
 
 
 
 
603
  self.embedding_norm = Lfm3RMSNorm(config.hidden_size, eps=config.norm_eps)
604
 
605
  # Initialize weights and apply final processing
606
  self.post_init()
607
 
608
+ @merge_with_config_defaults
609
+ @capture_outputs
610
  @auto_docstring
611
  def forward(
612
  self,
613
+ input_ids: torch.LongTensor | None = None,
614
+ attention_mask: torch.Tensor | None = None,
615
+ position_ids: torch.LongTensor | None = None,
616
+ past_key_values: Cache | None = None,
617
+ inputs_embeds: torch.FloatTensor | None = None,
618
+ use_cache: bool | None = None,
 
619
  **kwargs: Unpack[TransformersKwargs],
620
  ) -> BaseModelOutputWithPast:
621
  r"""
622
+ attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)` or `dict`, *optional*):
623
+ Mask to avoid performing attention on padding token indices (1 keeps a token, 0 masks it), or the mask
624
+ dict of `create_masks_for_generate`. A mask dict is used as given: `generate` refuses packed
625
+ `position_ids` before its prefill.
626
  """
627
  if (input_ids is None) ^ (inputs_embeds is not None):
628
  raise ValueError("You must specify exactly one of input_ids or inputs_embeds")
629
+ # `seq_idx` is a shared kwarg (`DataCollatorWithFlattening(return_seq_idx=True)`, read by Lfm2); LFM3 derives
630
+ # the documents from the restarting `position_ids` that come with it
631
+ if kwargs.pop("seq_idx", None) is not None and position_ids is None:
632
+ raise ValueError("Pass packed documents as `position_ids` restarting at 0; LFM3 does not read `seq_idx`")
633
 
634
  if inputs_embeds is None:
635
  inputs_embeds = self.embed_tokens(input_ids)
636
 
637
  if use_cache and past_key_values is None:
 
 
 
 
638
  past_key_values = DynamicCache(config=self.config)
639
+ filled = past_key_values is not None and (
640
+ past_key_values.get_seq_length() > 0
641
+ if any(t != "conv" for t in self.config.layer_types)
642
+ else past_key_values.has_previous_state()
643
+ )
644
+ packed_cache = getattr(past_key_values, "lfm3_packed", None)
645
+ if isinstance(packed_cache, torch.Tensor) or packed_cache:
646
+ # Reset layers have no history to mix. Ordinary caches carry a Python `False` and skip this, so the decode
647
+ # steps do no tensor work here; only a compiled packed prefill leaves a tensor flag, and `filled` stays
648
+ # tensor-valued for static caches.
649
+ packed_message = (
650
+ "A cache filled by a packed-document forward cannot be continued: its attention spans the documents"
651
  )
652
+ packed_cache = packed_cache & filled if isinstance(packed_cache, torch.Tensor) else filled
653
+ if isinstance(packed_cache, torch.Tensor):
654
+ torch_compilable_check(~packed_cache, packed_message)
655
+ if not is_tracing(packed_cache):
656
+ past_key_values.lfm3_packed = bool(packed_cache)
657
+ elif packed_cache:
658
+ raise ValueError(packed_message)
659
+ else:
660
+ past_key_values.lfm3_packed = False
661
 
662
+ # The mask utilities detect packing only with `attention_mask=None`, so a multi-token forward with explicit
663
+ # positions drops the all-ones mask tokenizers emit (a data-dependent check, hence not when traced).
664
+ explicit_positions = position_ids is not None and inputs_embeds.shape[1] > 1
665
+ if (
666
+ explicit_positions
667
+ and isinstance(attention_mask, torch.Tensor)
668
+ and attention_mask.ndim == 2
669
+ and not is_tracing(position_ids)
670
+ and bool(attention_mask.all())
671
+ ):
672
+ attention_mask = None
673
  seq_idx = None
674
+ if position_ids is None:
675
+ past_seen_tokens = past_key_values.get_seq_length() if past_key_values is not None else 0
676
+ position_ids = torch.arange(inputs_embeds.shape[1], device=inputs_embeds.device) + past_seen_tokens
677
+ position_ids = position_ids.unsqueeze(0)
678
+ elif explicit_positions and not isinstance(attention_mask, dict):
679
+ # a mask dict comes from `generate`, which refused packing before its prefill
680
+ if isinstance(attention_mask, torch.Tensor) and attention_mask.ndim == 2:
681
+ # data-dependent: a traced forward skips it (`generate` refuses packing eagerly before its prefill)
682
+ if not is_tracing(position_ids):
683
+ torch_compilable_check(
684
+ ~packed_under_padding(position_ids, attention_mask, filled),
685
+ "Packed documents (`position_ids` restarting) cannot be combined with an attention mask: "
686
+ "attention would not isolate the documents. Pass `attention_mask=None` without a static cache.",
687
+ )
688
+ else:
689
+ restarts = torch.diff(position_ids, prepend=position_ids[:, :1] - 1, dim=-1) != 1
690
+ document_ids = restarts.cumsum(-1)
691
+ if is_tracing(position_ids) or bool((document_ids[..., -1] > 0).any()):
692
+ seq_idx = document_ids.to(torch.int32)
693
+ # Packed documents are prefilled in one forward on an empty cache (cached tokens would join the first
694
+ # document), and their cache is never continued
695
+ if filled:
696
+ torch_compilable_check(
697
+ (~restarts[:, 1:]).all() & (position_ids[:, 0] != 0).all(),
698
+ "Packed documents must be prefilled in one forward; the cache already holds tokens",
699
+ )
700
+ # Ordinary continuation must retain the convolution window from the cached document.
701
+ seq_idx = None
702
+ if attention_mask is None:
703
+ # without a mask only the cacheless packed-sequence mask isolates repeated zero positions (left
704
+ # padding): a cache-sized mask and every later decode step would attend them, so they count too
705
+ packed = (document_ids[..., -1] > 0).any()
706
+ else:
707
+ # a prepared 4D mask is the caller's and carries the padding (repeated zero positions), but may
708
+ # or may not isolate documents, so the cache records the packing and is never continued
709
+ steps = torch.diff(position_ids, dim=-1)
710
+ packed = ((steps != 1) & ~((steps == 0) & (position_ids[:, :-1] == 0))).any()
711
+ if past_key_values is not None:
712
+ # resolved here, where the prefill already syncs, so the decode steps carry a Python flag: no
713
+ # per-step host sync, data-dependent op or CUDA-graph output on the cache
714
+ past_key_values.lfm3_packed = packed if is_tracing(position_ids) else bool(packed)
715
+
716
+ if not isinstance(causal_mask_mapping := attention_mask, dict):
717
+ mask_kwargs = {
718
+ "config": self.config,
719
+ "inputs_embeds": inputs_embeds,
720
+ "attention_mask": attention_mask,
721
+ "past_key_values": past_key_values,
722
+ "position_ids": position_ids,
723
+ }
724
+ # The mask utilities only isolate packed documents without a cache, and an empty cache adds nothing.
725
+ layer_types = self.config.layer_types
726
+ if seq_idx is not None and past_key_values is not None:
727
+ attention_layer = next((i for i, t in enumerate(layer_types) if t != "conv"), None)
728
+ if attention_layer is not None and past_key_values.get_seq_length(attention_layer) == 0:
729
+ if past_key_values.is_compileable:
730
+ # static layers always return their full length, so the masks are sized by the cache, which
731
+ # isolates neither documents nor positional-only left padding
732
+ torch_compilable_check(
733
+ ~packed,
734
+ "Packed documents, or left padding without an attention mask, cannot be prefilled into a "
735
+ "static cache: its masks are sized by the cache. Pass the padding as `attention_mask`, or "
736
+ "use a `DynamicCache` for packed documents",
737
+ )
738
+ else:
739
+ mask_kwargs["past_key_values"] = None
740
+ causal_mask_mapping = create_masks_by_layer_type(**mask_kwargs)
741
 
742
  hidden_states = inputs_embeds
743
+ position_embeddings = {
744
+ layer_type: self.rotary_emb(hidden_states, position_ids=position_ids, layer_type=layer_type)
745
+ for layer_type in self.rotary_emb.layer_types
 
 
 
746
  }
747
 
748
+ for i, decoder_layer in enumerate(self.layers[: self.config.num_hidden_layers]):
749
+ layer_type = self.config.layer_types[i]
 
 
 
 
750
  hidden_states = decoder_layer(
751
  hidden_states,
752
+ attention_mask=causal_mask_mapping[layer_type],
753
+ position_embeddings=position_embeddings.get(layer_type),
754
  position_ids=position_ids,
755
  past_key_values=past_key_values,
 
 
756
  seq_idx=seq_idx,
757
  **kwargs,
758
  )
 
765
  )
766
 
767
 
768
+ def reject_packed_generation(config: PreTrainedConfig, input_ids, generation_config, model_kwargs, is_first_iteration):
769
+ """`generate` decodes from the cache its prefill fills, and no mask it builds isolates packed documents
770
+ (`position_ids` restarting): the prompt is refused before the prefill, one forward or every chunk, writes to it."""
771
+ position_ids, attention_mask = model_kwargs.get("position_ids"), model_kwargs.get("attention_mask")
772
+ cache = model_kwargs.get("past_key_values")
773
+ if cache is None or position_ids is None or position_ids.ndim != 2:
774
+ return
775
+ if attention_mask is not None and attention_mask.ndim != 2:
776
+ return
777
+ has_attention = any(t != "conv" for t in config.get_text_config().layer_types)
778
+ filled = bool(cache.get_seq_length() > 0 if has_attention else cache.has_previous_state())
779
+ inputs_embeds = model_kwargs.get("inputs_embeds") if is_first_iteration else None
780
+ length = input_ids.shape[1] if inputs_embeds is None else inputs_embeds.shape[1]
781
+ if generation_config.prefill_chunk_size is not None:
782
+ # the chunks run from the start of the prompt
783
+ position_ids = position_ids[:, :length]
784
+ else:
785
+ if filled and has_attention:
786
+ # the slice `_prefill` feeds: a prompt that spans the cache is cut to its uncached part
787
+ mask_length = getattr(generation_config, "_mask_length", -1)
788
+ if attention_mask is not None:
789
+ mask_length = attention_mask.shape[1]
790
+ if inputs_embeds is not None or mask_length == length:
791
+ length -= int(cache.get_seq_length())
792
+ position_ids = position_ids[:, position_ids.shape[1] - length :]
793
+ if attention_mask is None:
794
+ attention_mask = torch.ones_like(position_ids)
795
+ if bool(packed_under_padding(position_ids, attention_mask, filled).any()):
796
+ raise ValueError(
797
+ "`generate` cannot decode packed documents (`position_ids` restarting, or starting at 0 on a cache that "
798
+ "holds tokens): attention would span them. Generate each document in its own row of a left-padded batch."
799
+ )
800
+
801
+
802
  @auto_docstring
803
  class Lfm3ForCausalLM(Lfm3PreTrainedModel, GenerationMixin):
804
  _tied_weights_keys = {"lm_head.weight": "model.embed_tokens.weight"}
805
+ _tp_plan = {"lm_head": "colwise_gather_output"}
806
  _pp_plan = {"lm_head": (["hidden_states"], ["logits"])}
807
+ _fsdp_plan = {"lm_head": "keep_full_weight"}
808
 
809
  def __init__(self, config):
810
  super().__init__(config)
 
819
  @auto_docstring
820
  def forward(
821
  self,
822
+ input_ids: torch.LongTensor | None = None,
823
+ attention_mask: torch.Tensor | None = None,
824
+ position_ids: torch.LongTensor | None = None,
825
+ past_key_values: Cache | None = None,
826
+ inputs_embeds: torch.FloatTensor | None = None,
827
+ labels: torch.LongTensor | None = None,
828
+ use_cache: bool | None = None,
829
+ logits_to_keep: int | torch.Tensor = 0,
 
830
  **kwargs: Unpack[TransformersKwargs],
831
  ) -> CausalLMOutputWithPast:
832
  r"""
833
+ Example:
834
+
835
+ ```python
836
+ >>> from transformers import AutoTokenizer, Lfm3ForCausalLM
837
+
838
+ >>> model = Lfm3ForCausalLM.from_pretrained("LiquidAI/LFM3-1.7B-Preview")
839
+ >>> tokenizer = AutoTokenizer.from_pretrained("LiquidAI/LFM3-1.7B-Preview")
840
+
841
+ >>> inputs = tokenizer.apply_chat_template(
842
+ ... [{"role": "user", "content": "What is C. elegans?"}], add_generation_prompt=True, return_tensors="pt"
843
+ ... )
844
+ >>> generate_ids = model.generate(**inputs, max_new_tokens=64)
845
+ >>> tokenizer.decode(generate_ids[0, inputs["input_ids"].shape[1] :], skip_special_tokens=True)
846
+ ```"""
847
  outputs: BaseModelOutputWithPast = self.model(
848
  input_ids=input_ids,
849
  attention_mask=attention_mask,
 
851
  past_key_values=past_key_values,
852
  inputs_embeds=inputs_embeds,
853
  use_cache=use_cache,
 
854
  **kwargs,
855
  )
856
 
857
  hidden_states = outputs.last_hidden_state
858
+ # Only compute necessary logits, and do not upcast them to float if we are not computing the loss
859
+ slice_indices = slice(-logits_to_keep, None) if isinstance(logits_to_keep, int) else logits_to_keep
 
860
  logits = self.lm_head(hidden_states[:, slice_indices, :])
861
 
862
  loss = None
863
  if labels is not None:
864
+ loss = self.loss_function(logits=logits, labels=labels, vocab_size=self.config.vocab_size, **kwargs)
 
 
865
 
866
  return CausalLMOutputWithPast(
867
  loss=loss,
 
871
  attentions=outputs.attentions,
872
  )
873
 
874
+ def _prefill(self, input_ids, generation_config, model_kwargs, is_first_iteration=True):
875
+ # every prefill passes here once before its first forward: a check per chunk would leave the earlier chunks
876
+ # in the cache, and a dynamic cache would only refuse packing at the first decode step
877
+ reject_packed_generation(self.config, input_ids, generation_config, model_kwargs, is_first_iteration)
878
+ return super()._prefill(input_ids, generation_config, model_kwargs, is_first_iteration=is_first_iteration)
879
+
880
 
881
  __all__ = ["Lfm3ForCausalLM", "Lfm3Model", "Lfm3PreTrainedModel"]
tokenizer.json CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:a39eed58008cbcbc09e5b629b81338a654269c64cd13348aaa6090f4db75c7e7
3
- size 17908258
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:41927bd314d17d7d7fadb44e10cd0919f7b71528d7741596d8cbd15c6945ec44
3
+ size 17908634
tokenizer_config.json CHANGED
@@ -1,20 +1,62 @@
1
  {
 
 
2
  "backend": "tokenizers",
3
  "bos_token": "<|startoftext|>",
4
  "eos_token": "<|im_end|>",
5
  "extra_special_tokens": [],
6
  "image_token": "<|image|>",
7
- "is_local": true,
8
- "legacy": false,
9
  "local_files_only": false,
10
  "model_max_length": 1000000000000000019884624838656,
11
  "model_specific_special_tokens": {
 
 
12
  "image_token": "<|image|>",
13
  "video_token": "<|video|>",
14
  "vision_end_token": "<|vision_end|>",
15
  "vision_start_token": "<|vision_start|>"
16
  },
17
  "pad_token": "<|pad|>",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  "tokenizer_class": "TokenizersBackend",
19
  "video_token": "<|video|>",
20
  "vision_end_token": "<|vision_end|>",
 
1
  {
2
+ "audio_out_token": "<|audio_out|>",
3
+ "audio_token": "<|audio|>",
4
  "backend": "tokenizers",
5
  "bos_token": "<|startoftext|>",
6
  "eos_token": "<|im_end|>",
7
  "extra_special_tokens": [],
8
  "image_token": "<|image|>",
9
+ "is_local": false,
 
10
  "local_files_only": false,
11
  "model_max_length": 1000000000000000019884624838656,
12
  "model_specific_special_tokens": {
13
+ "audio_out_token": "<|audio_out|>",
14
+ "audio_token": "<|audio|>",
15
  "image_token": "<|image|>",
16
  "video_token": "<|video|>",
17
  "vision_end_token": "<|vision_end|>",
18
  "vision_start_token": "<|vision_start|>"
19
  },
20
  "pad_token": "<|pad|>",
21
+ "response_template": {
22
+ "defaults": {
23
+ "role": "assistant"
24
+ },
25
+ "fields": {
26
+ "content": {
27
+ "close_pattern": "<\\|im_end\\|>\\s*",
28
+ "content": "text"
29
+ },
30
+ "reasoning_content": {
31
+ "close_pattern": "</think>\\s*",
32
+ "content": "text",
33
+ "open_pattern": "<think>\\n?"
34
+ },
35
+ "tool_calls": {
36
+ "close_pattern": "</tool_call>\\s*",
37
+ "content": "xml-inline",
38
+ "content_args": {
39
+ "tag_pattern": "<parameter=(?P<key>[^>\\n]+)>\\n?(?P<value>.*?)\\n?</parameter>(?=\\s*(?:<parameter=|</function>\\s*\\Z|\\Z))",
40
+ "value_parser": {
41
+ "args": {
42
+ "strip": false
43
+ },
44
+ "name": "text"
45
+ }
46
+ },
47
+ "open_pattern": "<tool_call>\\s*<function=(?P<name>[^\\n>]+)>",
48
+ "repeats": true,
49
+ "transform": {
50
+ "function": {
51
+ "arguments": "{content}",
52
+ "name": "{name}"
53
+ },
54
+ "type": "function"
55
+ }
56
+ }
57
+ },
58
+ "start_anchor_pattern": "<\\|im_start\\|>assistant\\n"
59
+ },
60
  "tokenizer_class": "TokenizersBackend",
61
  "video_token": "<|video|>",
62
  "vision_end_token": "<|vision_end|>",