Add tiny random LFM3-VL model for optimum-intel tests

#1
by rkazants - opened
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ tokenizer.json filter=lfs diff=lfs merge=lfs -text
chat_template.jinja ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {{- bos_token -}}
2
+ {%- set preserve_thinking = preserve_thinking | default(false) -%}
3
+ {%- set enable_thinking = enable_thinking | default(true) -%}
4
+
5
+ {%- macro render_value(v) -%}
6
+ {%- if v is string -%}
7
+ {{- v -}}
8
+ {%- elif v is boolean -%}
9
+ {{- "true" if v else "false" -}}
10
+ {%- elif v is none -%}
11
+ {{- "null" -}}
12
+ {%- elif v is mapping or (v is sequence and v is not string) -%}
13
+ {{- v | tojson -}}
14
+ {%- else -%}
15
+ {{- v -}}
16
+ {%- endif -%}
17
+ {%- endmacro -%}
18
+
19
+ {%- macro parse_content(content) -%}
20
+ {%- if content is string -%}
21
+ {{- content -}}
22
+ {%- elif content is mapping and content.get("type") == "image" -%}
23
+ {{- "<|vision_start|><|image|><|vision_end|>" -}}
24
+ {%- elif content is mapping and content.get("type") == "video" -%}
25
+ {{- "<|vision_start|><|video|><|vision_end|>" -}}
26
+ {%- elif content is mapping and content.get("type") == "text" -%}
27
+ {{- (content.get("text") or "") | string -}}
28
+ {%- elif content is mapping -%}
29
+ {{- content | tojson -}}
30
+ {%- elif content is iterable -%}
31
+ {%- set _ns = namespace(result="") -%}
32
+ {%- for item in content -%}
33
+ {%- if item is string -%}
34
+ {%- set _ns.result = _ns.result + item -%}
35
+ {%- elif item is mapping and item.get("type") == "image" -%}
36
+ {%- set _ns.result = _ns.result + "<|vision_start|><|image|><|vision_end|>" -%}
37
+ {%- elif item is mapping and item.get("type") == "video" -%}
38
+ {%- set _ns.result = _ns.result + "<|vision_start|><|video|><|vision_end|>" -%}
39
+ {%- elif item is mapping and item.get("type") == "text" -%}
40
+ {%- set _ns.result = _ns.result + ((item.get("text") or "") | string) -%}
41
+ {%- else -%}
42
+ {%- set _ns.result = _ns.result + (item | tojson) -%}
43
+ {%- endif -%}
44
+ {%- endfor -%}
45
+ {{- _ns.result -}}
46
+ {%- endif -%}
47
+ {%- endmacro -%}
48
+
49
+ {%- macro render_tool_calls(tool_calls) -%}
50
+ {%- for tool_call in tool_calls -%}
51
+ {%- if not loop.first -%}{{- "\n\n" -}}{%- endif -%}
52
+ {%- set func = tool_call["function"] if "function" in tool_call else tool_call -%}
53
+ {%- set func_args = func.get("arguments") -%}
54
+ {%- if func_args is string and (func_args | trim) not in ["", "{}", "null"] -%}
55
+ {{- raise_exception("Tool call arguments must be a mapping, got a JSON-encoded string: parse arguments with json.loads() before applying the chat template") -}}
56
+ {%- endif -%}
57
+ {{- "<tool_call>\n<function=" + func["name"] + ">\n" -}}
58
+ {%- if func_args is mapping -%}
59
+ {%- for arg_name, arg_value in func_args.items() -%}
60
+ {{- "<parameter=" + arg_name + ">\n" + render_value(arg_value) + "\n</parameter>\n" -}}
61
+ {%- endfor -%}
62
+ {%- endif -%}
63
+ {{- "</function>\n</tool_call>" -}}
64
+ {%- endfor -%}
65
+ {%- endmacro -%}
66
+
67
+ {%- macro tool_to_json(tool) -%}
68
+ {%- set fn = tool.function if tool.function is defined else tool -%}
69
+ {%- set ns_t = namespace(first=true) -%}
70
+ {{- "{" -}}
71
+ {%- for k, v in fn.items() -%}
72
+ {%- if k != "defer_loading" -%}
73
+ {%- if not ns_t.first -%}{{- ", " -}}{%- endif -%}
74
+ {%- set ns_t.first = false -%}
75
+ {{- k | tojson -}}{{- ": " -}}{{- v | tojson -}}
76
+ {%- endif -%}
77
+ {%- endfor -%}
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 -%}
87
+ {{- "<tool_response>\n" -}}
88
+ {%- if content is string -%}
89
+ {{- content -}}
90
+ {%- elif content is mapping -%}
91
+ {{- parse_content(content) -}}
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 -%}
99
+ {{- _tl -}}
100
+ {%- elif _tl is mapping -%}
101
+ {{- tool_to_json(_tl) -}}
102
+ {%- else -%}
103
+ {%- for t in _tl -%}
104
+ {%- if not loop.first -%}{{- "\n" -}}{%- endif -%}
105
+ {{- tool_to_json(t) -}}
106
+ {%- endfor -%}
107
+ {%- endif -%}
108
+ {{- "\n</additional_tools>" -}}
109
+ {%- else -%}
110
+ {{- parse_content(item) -}}
111
+ {%- endif -%}
112
+ {%- endfor -%}
113
+ {%- endif -%}
114
+ {{- "\n</tool_response>" -}}
115
+ {%- endif -%}
116
+ {%- endmacro -%}
117
+
118
+ {%- set ns = namespace(system_prompt="", last_user_index=-1) -%}
119
+ {%- if messages and messages[0]["role"] == "system" -%}
120
+ {%- if messages[0].get("content") -%}
121
+ {%- set ns.system_prompt = parse_content(messages[0]["content"]) -%}
122
+ {%- endif -%}
123
+ {%- set messages = messages[1:] -%}
124
+ {%- endif -%}
125
+ {%- if tools -%}
126
+ {%- set tools_ns = namespace(lines=[]) -%}
127
+ {%- for tool in tools -%}
128
+ {%- set fn = tool.function if tool.function is defined else tool -%}
129
+ {%- set _dyn = (fn.defer_loading is defined and fn.defer_loading) or (tool.defer_loading is defined and tool.defer_loading) -%}
130
+ {%- if not _dyn -%}
131
+ {%- set tools_ns.lines = tools_ns.lines + [tool_to_json(fn)] -%}
132
+ {%- endif -%}
133
+ {%- endfor -%}
134
+ {%- if tools_ns.lines -%}
135
+ {%- set ns.system_prompt = ns.system_prompt + ("\n\n" if ns.system_prompt else "") + "<tools>\n" + (tools_ns.lines | join("\n")) + "\n</tools>" -%}
136
+ {%- endif -%}
137
+ {%- endif -%}
138
+ {%- if ns.system_prompt -%}
139
+ {{- "<|im_start|>system\n" + ns.system_prompt + "<|im_end|>\n" -}}
140
+ {%- endif -%}
141
+ {%- for message in messages -%}
142
+ {%- if message["role"] == "user" -%}
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 thinking = message.thinking if message.thinking is defined else message.reasoning if message.reasoning is defined else message.reasoning_content -%}
151
+ {%- set keep_thinking = preserve_thinking or loop.index0 > ns.last_user_index -%}
152
+ {%- if thinking is defined and keep_thinking -%}
153
+ {{- "<think>" + (thinking or "") + "</think>" -}}
154
+ {%- endif -%}
155
+ {%- set _cfm_tag = "CONTINUE_FINAL_MESSAGE_TAG " -%}
156
+ {%- set _has_cfm = false -%}
157
+ {%- set content = "" -%}
158
+ {%- if message.get("content") -%}
159
+ {%- set content = parse_content(message.content) -%}
160
+ {%- endif -%}
161
+ {%- if not keep_thinking -%}
162
+ {%- if "</think>" in content -%}
163
+ {%- set content = content.split("</think>")[-1] | trim -%}
164
+ {%- endif -%}
165
+ {%- set content = "<think></think>" + content -%}
166
+ {%- endif -%}
167
+ {%- if content.endswith(_cfm_tag) -%}
168
+ {%- set _has_cfm = true -%}
169
+ {%- set _trunc_len = (content | length) - (_cfm_tag | length) -%}
170
+ {%- set content = content[:_trunc_len] -%}
171
+ {%- endif -%}
172
+ {{- content -}}
173
+ {%- if message.tool_calls -%}
174
+ {{- render_tool_calls(message.tool_calls) -}}
175
+ {%- endif -%}
176
+ {%- if _has_cfm -%}
177
+ {{- _cfm_tag -}}
178
+ {%- endif -%}
179
+ {{- "<|im_end|>\n" -}}
180
+ {%- endgeneration -%}
181
+ {%- elif message.role == "tool" -%}
182
+ {%- if message.get("additional_tools") -%}
183
+ {%- set _at = message["additional_tools"] -%}
184
+ {#- definitions may live in additional_tools itself, or in content
185
+ with additional_tools used as a plain truthy flag -#}
186
+ {%- if _at is mapping or (_at is sequence and _at is not string) -%}
187
+ {%- set _src = _at -%}
188
+ {%- else -%}
189
+ {%- set _src = message.get("content") -%}
190
+ {%- endif -%}
191
+ {#- nothing to reveal: skip the wrapper entirely rather than
192
+ emitting an empty <additional_tools> block -#}
193
+ {%- if _src is string and "<tool_response>" in _src -%}
194
+ {{- _src -}}
195
+ {%- elif _src is string and "<additional_tools>" in _src -%}
196
+ {{- "<tool_response>\n" + _src + "\n</tool_response>" -}}
197
+ {%- elif _src -%}
198
+ {{- "<tool_response>\n<additional_tools>\n" -}}
199
+ {%- if _src is string -%}
200
+ {{- _src -}}
201
+ {%- elif _src is mapping -%}
202
+ {{- tool_to_json(_src) -}}
203
+ {%- else -%}
204
+ {%- for t in _src -%}
205
+ {%- if not loop.first -%}{{- "\n" -}}{%- endif -%}
206
+ {{- tool_to_json(t) -}}
207
+ {%- endfor -%}
208
+ {%- endif -%}
209
+ {{- "\n</additional_tools>\n</tool_response>" -}}
210
+ {%- endif -%}
211
+ {%- elif message.get("content") -%}
212
+ {{- render_tool_response(message["content"]) -}}
213
+ {%- endif -%}
214
+ {{- "<|im_end|>\n" -}}
215
+ {%- else %}
216
+ {%- if message.get("content") -%}
217
+ {{- parse_content(message["content"]) -}}
218
+ {%- endif -%}
219
+ {{- "<|im_end|>\n" -}}
220
+ {%- endif %}
221
+ {%- endfor -%}
222
+ {%- if add_generation_prompt -%}
223
+ {{- "<|im_start|>assistant\n" -}}
224
+ {%- if enable_thinking -%}
225
+ {{- "<think>" -}}
226
+ {%- else -%}
227
+ {{- "<think></think>" -}}
228
+ {%- endif -%}
229
+ {%- endif -%}
config.json ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "Lfm3VlForConditionalGeneration"
4
+ ],
5
+ "auto_map": {
6
+ "AutoConfig": "configuration_lfm3_vl.Lfm3VlConfig",
7
+ "AutoModelForCausalLM": "modeling_lfm3_vl.Lfm3VlForConditionalGeneration",
8
+ "AutoModelForImageTextToText": "modeling_lfm3_vl.Lfm3VlForConditionalGeneration"
9
+ },
10
+ "bos_token_id": 124894,
11
+ "conv_L_cache": 7,
12
+ "conv_bias": false,
13
+ "downsample_factor": 2,
14
+ "dtype": "float32",
15
+ "eos_token_id": 124900,
16
+ "full_attention_rope_theta": 1000000.0,
17
+ "head_dim": 8,
18
+ "hidden_size": 16,
19
+ "image_token_id": 125030,
20
+ "intermediate_size": 32,
21
+ "layer_types": [
22
+ "conv",
23
+ "conv",
24
+ "full_attention",
25
+ "conv",
26
+ "sliding_attention",
27
+ "conv",
28
+ "sliding_attention"
29
+ ],
30
+ "max_position_embeddings": 128000,
31
+ "model_type": "lfm3_vl",
32
+ "num_attention_heads": 4,
33
+ "num_hidden_layers": 7,
34
+ "num_key_value_heads": 2,
35
+ "pad_token_id": 124893,
36
+ "partial_rotary_factor": 0.5,
37
+ "projector_hidden_size": 32,
38
+ "projector_layer_norm_eps": 1e-06,
39
+ "rms_norm_eps": 1e-05,
40
+ "sliding_attention_rope_theta": 10000,
41
+ "sliding_window": 4,
42
+ "tie_word_embeddings": true,
43
+ "transformers_version": "5.20.0",
44
+ "use_cache": true,
45
+ "use_sandwich_norm": true,
46
+ "video_token_id": 125029,
47
+ "vision_end_token_id": 125010,
48
+ "vision_hidden_size": 16,
49
+ "vision_intermediate_size": 32,
50
+ "vision_layer_types": [
51
+ "conv",
52
+ "full_attention",
53
+ "conv",
54
+ "full_attention"
55
+ ],
56
+ "vision_max_position_embeddings": 2304,
57
+ "vision_num_attention_heads": 2,
58
+ "vision_num_channels": 3,
59
+ "vision_num_hidden_layers": 4,
60
+ "vision_num_key_value_heads": 1,
61
+ "vision_patch_dim": 1536,
62
+ "vision_patch_size": 16,
63
+ "vision_rope_theta": 1000000.0,
64
+ "vision_spatial_merge_size": 2,
65
+ "vision_start_token_id": 125009,
66
+ "vision_temporal_patch_size": 2,
67
+ "vocab_size": 128000
68
+ }
configuration_lfm3_vl.py ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 The HuggingFace Team. All rights reserved.
2
+ # Licensed under the Apache License, Version 2.0
3
+
4
+ from transformers.configuration_utils import PreTrainedConfig
5
+
6
+
7
+ class Lfm3VlConfig(PreTrainedConfig):
8
+ model_type = "lfm3_vl"
9
+ keys_to_ignore_at_inference = ["past_key_values"]
10
+
11
+ def __init__(
12
+ self,
13
+ vocab_size=128000,
14
+ hidden_size=2048,
15
+ intermediate_size=7680,
16
+ num_hidden_layers=24,
17
+ num_attention_heads=16,
18
+ num_key_value_heads=4,
19
+ head_dim=None,
20
+ layer_types=None,
21
+ conv_L_cache=7,
22
+ sliding_window=256,
23
+ full_attention_rope_theta=1_000_000.0,
24
+ sliding_attention_rope_theta=10_000.0,
25
+ partial_rotary_factor=0.5,
26
+ max_position_embeddings=128000,
27
+ vision_patch_size=16,
28
+ vision_temporal_patch_size=2,
29
+ vision_spatial_merge_size=2,
30
+ vision_num_channels=3,
31
+ vision_patch_dim=1536,
32
+ vision_hidden_size=1024,
33
+ vision_intermediate_size=2560,
34
+ vision_num_hidden_layers=14,
35
+ vision_num_attention_heads=16,
36
+ vision_num_key_value_heads=8,
37
+ vision_layer_types=None,
38
+ vision_rope_theta=1_000_000.0,
39
+ vision_max_position_embeddings=2304,
40
+ projector_hidden_size=4096,
41
+ projector_layer_norm_eps=1e-6,
42
+ downsample_factor=2,
43
+ image_token_id=125030,
44
+ video_token_id=125029,
45
+ vision_start_token_id=125009,
46
+ vision_end_token_id=125010,
47
+ rms_norm_eps=1e-5,
48
+ use_cache=True,
49
+ pad_token_id=124893,
50
+ bos_token_id=124894,
51
+ eos_token_id=124900,
52
+ tie_word_embeddings=True,
53
+ **kwargs,
54
+ ):
55
+ self.vocab_size = vocab_size
56
+ self.hidden_size = hidden_size
57
+ self.intermediate_size = intermediate_size
58
+ self.num_hidden_layers = num_hidden_layers
59
+ self.num_attention_heads = num_attention_heads
60
+ self.num_key_value_heads = num_key_value_heads
61
+ self.head_dim = head_dim
62
+ self.layer_types = layer_types or [
63
+ "conv",
64
+ "conv",
65
+ "full_attention",
66
+ "conv",
67
+ "sliding_attention",
68
+ "conv",
69
+ "full_attention",
70
+ "conv",
71
+ "sliding_attention",
72
+ "conv",
73
+ "full_attention",
74
+ "conv",
75
+ "sliding_attention",
76
+ "conv",
77
+ "full_attention",
78
+ "conv",
79
+ "sliding_attention",
80
+ "conv",
81
+ "full_attention",
82
+ "conv",
83
+ "sliding_attention",
84
+ "conv",
85
+ "full_attention",
86
+ "conv",
87
+ ]
88
+ self.conv_L_cache = conv_L_cache
89
+ self.sliding_window = sliding_window
90
+ self.full_attention_rope_theta = full_attention_rope_theta
91
+ self.sliding_attention_rope_theta = sliding_attention_rope_theta
92
+ self.partial_rotary_factor = partial_rotary_factor
93
+ self.max_position_embeddings = max_position_embeddings
94
+ self.vision_patch_size = vision_patch_size
95
+ self.vision_temporal_patch_size = vision_temporal_patch_size
96
+ self.vision_spatial_merge_size = vision_spatial_merge_size
97
+ self.vision_num_channels = vision_num_channels
98
+ self.vision_patch_dim = vision_patch_dim
99
+ self.vision_hidden_size = vision_hidden_size
100
+ self.vision_intermediate_size = vision_intermediate_size
101
+ self.vision_num_hidden_layers = vision_num_hidden_layers
102
+ self.vision_num_attention_heads = vision_num_attention_heads
103
+ self.vision_num_key_value_heads = vision_num_key_value_heads
104
+ self.vision_layer_types = vision_layer_types or [
105
+ "conv",
106
+ "conv",
107
+ "full_attention",
108
+ "conv",
109
+ "full_attention",
110
+ "conv",
111
+ "full_attention",
112
+ "conv",
113
+ "full_attention",
114
+ "conv",
115
+ "full_attention",
116
+ "conv",
117
+ "full_attention",
118
+ "conv",
119
+ ]
120
+ self.vision_rope_theta = vision_rope_theta
121
+ self.vision_max_position_embeddings = vision_max_position_embeddings
122
+ self.projector_hidden_size = projector_hidden_size
123
+ self.projector_layer_norm_eps = projector_layer_norm_eps
124
+ self.downsample_factor = downsample_factor
125
+ self.image_token_id = image_token_id
126
+ self.video_token_id = video_token_id
127
+ self.vision_start_token_id = vision_start_token_id
128
+ self.vision_end_token_id = vision_end_token_id
129
+ self.rms_norm_eps = rms_norm_eps
130
+ self.use_cache = use_cache
131
+ if len(self.layer_types) != self.num_hidden_layers:
132
+ raise ValueError("layer_types must contain one entry per language layer")
133
+ if len(self.vision_layer_types) != self.vision_num_hidden_layers:
134
+ raise ValueError("vision_layer_types must contain one entry per vision layer")
135
+ expected_patch_dim = vision_temporal_patch_size * vision_num_channels * vision_patch_size**2
136
+ if vision_patch_dim != expected_patch_dim:
137
+ raise ValueError(f"vision_patch_dim must be {expected_patch_dim}, got {vision_patch_dim}")
138
+ if downsample_factor != vision_spatial_merge_size:
139
+ raise ValueError("downsample_factor and vision_spatial_merge_size must match")
140
+ super().__init__(
141
+ pad_token_id=pad_token_id,
142
+ bos_token_id=bos_token_id,
143
+ eos_token_id=eos_token_id,
144
+ tie_word_embeddings=tie_word_embeddings,
145
+ **kwargs,
146
+ )
147
+
148
+
149
+ __all__ = ["Lfm3VlConfig"]
generation_config.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_from_model_config": true,
3
+ "bos_token_id": 124894,
4
+ "eos_token_id": 124900,
5
+ "output_attentions": false,
6
+ "output_hidden_states": false,
7
+ "pad_token_id": 124893,
8
+ "transformers_version": "5.20.0",
9
+ "use_cache": true
10
+ }
image_processing_lfm3_vl.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 The HuggingFace Team. All rights reserved.
2
+ # Licensed under the Apache License, Version 2.0
3
+ """Qwen-style dynamic-resolution image preprocessing for LFM3-VL."""
4
+
5
+ from transformers.models.qwen2_vl.image_processing_qwen2_vl import Qwen2VLImageProcessor
6
+
7
+
8
+ class Lfm3VlImageProcessor(Qwen2VLImageProcessor):
9
+ patch_size = 16
10
+ temporal_patch_size = 2
11
+ merge_size = 2
12
+ size = {"shortest_edge": 65_536, "longest_edge": 16_777_216}
13
+ image_mean = [0.5, 0.5, 0.5]
14
+ image_std = [0.5, 0.5, 0.5]
15
+
16
+
17
+ __all__ = ["Lfm3VlImageProcessor"]
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0b203ba09e586a19c594bb6d20f71f07763ef847c59ff6301969240533261c11
3
+ size 8436616
modeling_lfm3_vl.py ADDED
@@ -0,0 +1,499 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 The HuggingFace Team. All rights reserved.
2
+ # Licensed under the Apache License, Version 2.0
3
+ """Simple PyTorch LFM3-VL with a variable-shape Liquid vision encoder."""
4
+
5
+ from dataclasses import dataclass
6
+
7
+ import torch
8
+ import torch.nn.functional as F
9
+ from torch import nn
10
+
11
+ from transformers.generation import GenerationMixin
12
+ from transformers.modeling_outputs import CausalLMOutputWithPast
13
+ from transformers.modeling_utils import PreTrainedModel
14
+ from .configuration_lfm3_vl import Lfm3VlConfig
15
+
16
+
17
+ class Lfm3VlRMSNorm(nn.Module):
18
+ def __init__(self, hidden_size, eps):
19
+ super().__init__()
20
+ self.weight = nn.Parameter(torch.ones(hidden_size))
21
+ self.eps = eps
22
+
23
+ def forward(self, hidden_states):
24
+ dtype = hidden_states.dtype
25
+ normalized = hidden_states.float()
26
+ normalized = normalized * torch.rsqrt(normalized.square().mean(-1, keepdim=True) + self.eps)
27
+ return (normalized.to(dtype) * self.weight).to(dtype)
28
+
29
+
30
+ def _rope_1d(hidden_states, position_ids, theta, rotary_factor):
31
+ head_dim = hidden_states.shape[-1]
32
+ pairs = int(head_dim * rotary_factor) // 2
33
+ frequencies = theta ** (-(2.0 * torch.arange(pairs, device=hidden_states.device, dtype=torch.float32) / head_dim))
34
+ angles = position_ids.float()[..., None] * frequencies
35
+ cos = angles.cos().to(hidden_states.dtype)[:, None, :, :]
36
+ sin = angles.sin().to(hidden_states.dtype)[:, None, :, :]
37
+ first = hidden_states[..., :pairs]
38
+ second = hidden_states[..., head_dim // 2 : head_dim // 2 + pairs]
39
+ output = hidden_states.clone()
40
+ output[..., :pairs] = first * cos - second * sin
41
+ output[..., head_dim // 2 : head_dim // 2 + pairs] = first * sin + second * cos
42
+ return output
43
+
44
+
45
+ def _rope_2d(hidden_states, height, width, theta):
46
+ head_dim = hidden_states.shape[-1]
47
+ quarter = head_dim // 4
48
+ frequencies = theta ** (
49
+ -(2.0 * torch.arange(quarter, device=hidden_states.device, dtype=torch.float32) / (head_dim // 2))
50
+ )
51
+ rows = torch.arange(height, device=hidden_states.device).repeat_interleave(width)
52
+ cols = torch.arange(width, device=hidden_states.device).repeat(height)
53
+ angles = torch.cat((rows[:, None] * frequencies, cols[:, None] * frequencies), dim=-1)
54
+ cos = angles.cos().to(hidden_states.dtype)[:, None, :]
55
+ sin = angles.sin().to(hidden_states.dtype)[:, None, :]
56
+ first, second = hidden_states[..., : head_dim // 2], hidden_states[..., head_dim // 2 :]
57
+ return torch.cat((first * cos - second * sin, first * sin + second * cos), dim=-1)
58
+
59
+
60
+ class Lfm3VlCache:
61
+ def __init__(self, config, batch_size, dtype, device):
62
+ self.key_cache = [None] * config.num_hidden_layers
63
+ self.value_cache = [None] * config.num_hidden_layers
64
+ self.conv_cache = [
65
+ torch.zeros(batch_size, config.hidden_size, config.conv_L_cache - 1, dtype=dtype, device=device)
66
+ for _ in range(config.num_hidden_layers)
67
+ ]
68
+ self.layer_types = config.layer_types
69
+ self.is_decode = False
70
+
71
+ def get_seq_length(self):
72
+ for layer_type, key in zip(self.layer_types, self.key_cache):
73
+ if layer_type != "conv" and key is not None:
74
+ return key.shape[-2]
75
+ return 0
76
+
77
+ def reorder_cache(self, beam_idx):
78
+ for index in range(len(self.layer_types)):
79
+ if self.key_cache[index] is not None:
80
+ self.key_cache[index] = self.key_cache[index].index_select(0, beam_idx)
81
+ self.value_cache[index] = self.value_cache[index].index_select(0, beam_idx)
82
+ self.conv_cache[index] = self.conv_cache[index].index_select(0, beam_idx)
83
+
84
+
85
+ class Lfm3VlMLP(nn.Module):
86
+ def __init__(self, hidden_size, intermediate_size):
87
+ super().__init__()
88
+ self.gate_up = nn.Linear(hidden_size, 2 * intermediate_size, bias=False)
89
+ self.w2 = nn.Linear(intermediate_size, hidden_size, bias=False)
90
+
91
+ def forward(self, hidden_states):
92
+ gate, up = self.gate_up(hidden_states).chunk(2, dim=-1)
93
+ return self.w2(F.silu(gate) * up)
94
+
95
+
96
+ class Lfm3VlAttention(nn.Module):
97
+ def __init__(self, config, layer_idx, layer_type):
98
+ super().__init__()
99
+ self.layer_idx = layer_idx
100
+ self.layer_type = layer_type
101
+ self.num_heads = config.num_attention_heads
102
+ self.num_key_value_heads = config.num_key_value_heads
103
+ self.head_dim = config.head_dim or config.hidden_size // config.num_attention_heads
104
+ qkv_size = (self.num_heads + 2 * self.num_key_value_heads) * self.head_dim
105
+ self.qkv = nn.Linear(config.hidden_size, qkv_size, bias=False)
106
+ self.gate_proj = nn.Linear(config.hidden_size, self.num_heads, bias=False)
107
+ self.o_proj = nn.Linear(self.num_heads * self.head_dim, config.hidden_size, bias=False)
108
+ self.q_norm = Lfm3VlRMSNorm(self.head_dim, config.rms_norm_eps)
109
+ self.k_norm = Lfm3VlRMSNorm(self.head_dim, config.rms_norm_eps)
110
+ self.theta = (
111
+ config.full_attention_rope_theta if layer_type == "full_attention" else config.sliding_attention_rope_theta
112
+ )
113
+ self.rotary_factor = config.partial_rotary_factor if layer_type == "full_attention" else 1.0
114
+ self.sliding_window = config.sliding_window
115
+
116
+ def forward(self, hidden_states, position_ids, attention_mask, past_key_values):
117
+ batch_size, query_length, _ = hidden_states.shape
118
+ q_size = self.num_heads * self.head_dim
119
+ kv_size = self.num_key_value_heads * self.head_dim
120
+ q, k, v = self.qkv(hidden_states).split((q_size, kv_size, kv_size), dim=-1)
121
+ q = self.q_norm(q.view(batch_size, query_length, self.num_heads, self.head_dim)).transpose(1, 2)
122
+ k = self.k_norm(k.view(batch_size, query_length, self.num_key_value_heads, self.head_dim)).transpose(1, 2)
123
+ v = v.view(batch_size, query_length, self.num_key_value_heads, self.head_dim).transpose(1, 2)
124
+ q = _rope_1d(q, position_ids, self.theta, self.rotary_factor)
125
+ k = _rope_1d(k, position_ids, self.theta, self.rotary_factor)
126
+ if past_key_values is not None:
127
+ old_k = past_key_values.key_cache[self.layer_idx]
128
+ old_v = past_key_values.value_cache[self.layer_idx]
129
+ if old_k is not None:
130
+ k = torch.cat((old_k, k), dim=-2)
131
+ v = torch.cat((old_v, v), dim=-2)
132
+ past_key_values.key_cache[self.layer_idx] = k
133
+ past_key_values.value_cache[self.layer_idx] = v
134
+ groups = self.num_heads // self.num_key_value_heads
135
+ k = k.repeat_interleave(groups, dim=1)
136
+ v = v.repeat_interleave(groups, dim=1)
137
+ key_length = k.shape[-2]
138
+ query_positions = torch.arange(key_length - query_length, key_length, device=hidden_states.device)
139
+ key_positions = torch.arange(key_length, device=hidden_states.device)
140
+ allowed = key_positions[None, :] <= query_positions[:, None]
141
+ if self.layer_type == "sliding_attention":
142
+ allowed &= key_positions[None, :] > query_positions[:, None] - self.sliding_window
143
+ if attention_mask is not None:
144
+ allowed = allowed[None, None] & attention_mask[:, None, None, :key_length].bool()
145
+ else:
146
+ allowed = allowed[None, None]
147
+ additive_mask = torch.zeros(allowed.shape, dtype=q.dtype, device=q.device)
148
+ additive_mask.masked_fill_(~allowed, torch.finfo(q.dtype).min)
149
+ output = F.scaled_dot_product_attention(q, k, v, attn_mask=additive_mask)
150
+ gate = torch.sigmoid(self.gate_proj(hidden_states)).transpose(1, 2).unsqueeze(-1)
151
+ output = (output * gate).transpose(1, 2).reshape(batch_size, query_length, -1)
152
+ return self.o_proj(output)
153
+
154
+
155
+ class Lfm3VlShortConv(nn.Module):
156
+ def __init__(self, config, layer_idx):
157
+ super().__init__()
158
+ self.layer_idx = layer_idx
159
+ self.kernel_size = config.conv_L_cache
160
+ self.in_proj = nn.Linear(config.hidden_size, 3 * config.hidden_size, bias=False)
161
+ self.conv = nn.Parameter(torch.empty(config.hidden_size, 1, self.kernel_size))
162
+ self.out_proj = nn.Linear(config.hidden_size, config.hidden_size, bias=False)
163
+
164
+ def forward(self, hidden_states, attention_mask, past_key_values):
165
+ first, middle, third = self.in_proj(hidden_states).chunk(3, dim=-1)
166
+ gated = first * third
167
+ if attention_mask is not None:
168
+ gated = gated * attention_mask[:, -hidden_states.shape[1] :, None]
169
+ gated = gated.transpose(1, 2)
170
+ if past_key_values is not None and past_key_values.is_decode:
171
+ state = past_key_values.conv_cache[self.layer_idx]
172
+ full = torch.cat((state, gated), dim=-1)
173
+ output = (full * self.conv[:, 0, :][None]).sum(-1, keepdim=True)
174
+ past_key_values.conv_cache[self.layer_idx] = full[..., -self.kernel_size + 1 :]
175
+ else:
176
+ output = F.conv1d(gated, self.conv, padding=self.kernel_size - 1, groups=gated.shape[1])
177
+ output = output[..., : hidden_states.shape[1]]
178
+ if past_key_values is not None:
179
+ past_key_values.conv_cache[self.layer_idx] = F.pad(
180
+ gated, (max(0, self.kernel_size - 1 - gated.shape[-1]), 0)
181
+ )[..., -self.kernel_size + 1 :]
182
+ return self.out_proj((middle.transpose(1, 2) * output).transpose(1, 2))
183
+
184
+
185
+ class Lfm3VlLanguageLayer(nn.Module):
186
+ def __init__(self, config, layer_idx, layer_type):
187
+ super().__init__()
188
+ self.layer_type = layer_type
189
+ self.operator_norm = Lfm3VlRMSNorm(config.hidden_size, config.rms_norm_eps)
190
+ self.post_operator_norm = Lfm3VlRMSNorm(config.hidden_size, config.rms_norm_eps)
191
+ self.ffn_norm = Lfm3VlRMSNorm(config.hidden_size, config.rms_norm_eps)
192
+ self.post_ffn_norm = Lfm3VlRMSNorm(config.hidden_size, config.rms_norm_eps)
193
+ if layer_type == "conv":
194
+ self.conv = Lfm3VlShortConv(config, layer_idx)
195
+ else:
196
+ self.self_attn = Lfm3VlAttention(config, layer_idx, layer_type)
197
+ self.feed_forward = Lfm3VlMLP(config.hidden_size, config.intermediate_size)
198
+
199
+ def forward(self, hidden_states, position_ids, attention_mask, past_key_values):
200
+ operator_input = self.operator_norm(hidden_states)
201
+ if self.layer_type == "conv":
202
+ operator_output = self.conv(operator_input, attention_mask, past_key_values)
203
+ else:
204
+ operator_output = self.self_attn(operator_input, position_ids, attention_mask, past_key_values)
205
+ hidden_states = hidden_states + self.post_operator_norm(operator_output)
206
+ return hidden_states + self.post_ffn_norm(self.feed_forward(self.ffn_norm(hidden_states)))
207
+
208
+
209
+ class Lfm3VlBackbone(nn.Module):
210
+ def __init__(self, config):
211
+ super().__init__()
212
+ self.config = config
213
+ self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size, padding_idx=config.pad_token_id)
214
+ self.layers = nn.ModuleList(
215
+ [Lfm3VlLanguageLayer(config, index, layer_type) for index, layer_type in enumerate(config.layer_types)]
216
+ )
217
+ self.embedding_norm = Lfm3VlRMSNorm(config.hidden_size, config.rms_norm_eps)
218
+
219
+ def forward(self, hidden_states, attention_mask=None, position_ids=None, past_key_values=None):
220
+ if past_key_values is not None:
221
+ past_key_values.is_decode = past_key_values.get_seq_length() > 0
222
+ if position_ids is None:
223
+ past_length = past_key_values.get_seq_length() if past_key_values is not None else 0
224
+ position_ids = (
225
+ torch.arange(past_length, past_length + hidden_states.shape[1], device=hidden_states.device)
226
+ .unsqueeze(0)
227
+ .expand(hidden_states.shape[0], -1)
228
+ )
229
+ for layer in self.layers:
230
+ hidden_states = layer(hidden_states, position_ids, attention_mask, past_key_values)
231
+ return self.embedding_norm(hidden_states)
232
+
233
+
234
+ class Lfm3VlVisionAttention(nn.Module):
235
+ def __init__(self, config):
236
+ super().__init__()
237
+ self.num_heads = config.vision_num_attention_heads
238
+ self.num_key_value_heads = config.vision_num_key_value_heads
239
+ self.head_dim = config.vision_hidden_size // self.num_heads
240
+ self.theta = config.vision_rope_theta
241
+ self.q_proj = nn.Linear(config.vision_hidden_size, self.num_heads * self.head_dim, bias=False)
242
+ self.k_proj = nn.Linear(config.vision_hidden_size, self.num_key_value_heads * self.head_dim, bias=False)
243
+ self.v_proj = nn.Linear(config.vision_hidden_size, self.num_key_value_heads * self.head_dim, bias=False)
244
+ self.out_proj = nn.Linear(config.vision_hidden_size, config.vision_hidden_size, bias=False)
245
+ self.q_layernorm = Lfm3VlRMSNorm(self.head_dim, config.rms_norm_eps)
246
+ self.k_layernorm = Lfm3VlRMSNorm(self.head_dim, config.rms_norm_eps)
247
+
248
+ def forward(self, hidden_states, height, width):
249
+ tokens = hidden_states.shape[0]
250
+ q = self.q_layernorm(self.q_proj(hidden_states).view(tokens, self.num_heads, self.head_dim))
251
+ k = self.k_layernorm(self.k_proj(hidden_states).view(tokens, self.num_key_value_heads, self.head_dim))
252
+ v = self.v_proj(hidden_states).view(tokens, self.num_key_value_heads, self.head_dim)
253
+ q = _rope_2d(q, height, width, self.theta)
254
+ k = _rope_2d(k, height, width, self.theta)
255
+ groups = self.num_heads // self.num_key_value_heads
256
+ output = F.scaled_dot_product_attention(
257
+ q.transpose(0, 1).unsqueeze(0),
258
+ k.repeat_interleave(groups, dim=1).transpose(0, 1).unsqueeze(0),
259
+ v.repeat_interleave(groups, dim=1).transpose(0, 1).unsqueeze(0),
260
+ )
261
+ return self.out_proj(output.squeeze(0).transpose(0, 1).reshape(tokens, -1))
262
+
263
+
264
+ class Lfm3VlVisionConv2d(nn.Module):
265
+ def __init__(self, hidden_size):
266
+ super().__init__()
267
+ self.in_proj = nn.Linear(hidden_size, 3 * hidden_size, bias=False)
268
+ self.spatial_conv = nn.Parameter(torch.empty(hidden_size, 1, 3, 3))
269
+ self.out_proj = nn.Linear(hidden_size, hidden_size, bias=False)
270
+
271
+ def forward(self, hidden_states, height, width):
272
+ first, middle, third = self.in_proj(hidden_states).chunk(3, dim=-1)
273
+ gated = (first * third).transpose(0, 1).reshape(1, -1, height, width)
274
+ output = F.conv2d(gated, self.spatial_conv, padding=1, groups=gated.shape[1])
275
+ return self.out_proj(middle * output.reshape(-1, height * width).transpose(0, 1))
276
+
277
+
278
+ class Lfm3VlVisionLayer(nn.Module):
279
+ def __init__(self, config, layer_type):
280
+ super().__init__()
281
+ self.layer_type = layer_type
282
+ self.operator_norm = Lfm3VlRMSNorm(config.vision_hidden_size, config.rms_norm_eps)
283
+ self.ffn_norm = Lfm3VlRMSNorm(config.vision_hidden_size, config.rms_norm_eps)
284
+ if layer_type == "conv":
285
+ self.conv = Lfm3VlVisionConv2d(config.vision_hidden_size)
286
+ else:
287
+ self.self_attn = Lfm3VlVisionAttention(config)
288
+ self.feed_forward = Lfm3VlMLP(config.vision_hidden_size, config.vision_intermediate_size)
289
+
290
+ def forward(self, hidden_states, height, width):
291
+ operator_input = self.operator_norm(hidden_states)
292
+ operator_output = (
293
+ self.conv(operator_input, height, width)
294
+ if self.layer_type == "conv"
295
+ else self.self_attn(operator_input, height, width)
296
+ )
297
+ hidden_states = hidden_states + operator_output
298
+ return hidden_states + self.feed_forward(self.ffn_norm(hidden_states))
299
+
300
+
301
+ class Lfm3VlVisionEncoder(nn.Module):
302
+ def __init__(self, config):
303
+ super().__init__()
304
+ self.spatial_merge_size = config.vision_spatial_merge_size
305
+ self.patchifier = nn.Linear(config.vision_patch_dim, config.vision_hidden_size, bias=False)
306
+ self.embedding_norm = Lfm3VlRMSNorm(config.vision_hidden_size, config.rms_norm_eps)
307
+ self.layers = nn.ModuleList(
308
+ [Lfm3VlVisionLayer(config, layer_type) for layer_type in config.vision_layer_types]
309
+ )
310
+
311
+ @staticmethod
312
+ def _block_major_to_raster(hidden_states, temporal, height, width, merge_size):
313
+ return (
314
+ hidden_states.view(temporal, height // merge_size, width // merge_size, merge_size, merge_size, -1)
315
+ .permute(0, 1, 3, 2, 4, 5)
316
+ .reshape(temporal, height, width, -1)
317
+ )
318
+
319
+ def forward(self, pixel_values, grid_thw):
320
+ """Encode Qwen-preprocessed patches, one independent spatial sequence per temporal patch."""
321
+ outputs = []
322
+ offset = 0
323
+ for temporal, height, width in grid_thw.tolist():
324
+ temporal, height, width = int(temporal), int(height), int(width)
325
+ tokens = temporal * height * width
326
+ hidden_states = pixel_values[offset : offset + tokens]
327
+ offset += tokens
328
+ hidden_states = self._block_major_to_raster(
329
+ hidden_states, temporal, height, width, self.spatial_merge_size
330
+ )
331
+ for temporal_hidden_states in hidden_states:
332
+ temporal_hidden_states = self.embedding_norm(
333
+ self.patchifier(temporal_hidden_states.reshape(-1, hidden_states.shape[-1]))
334
+ )
335
+ for layer in self.layers:
336
+ temporal_hidden_states = layer(temporal_hidden_states, height, width)
337
+ outputs.append((temporal_hidden_states, height, width))
338
+ if offset != pixel_values.shape[0]:
339
+ raise ValueError("pixel_values and grid_thw disagree")
340
+ return outputs
341
+
342
+
343
+ class Lfm3VlProjector(nn.Module):
344
+ def __init__(self, config):
345
+ super().__init__()
346
+ self.spatial_merge_size = config.vision_spatial_merge_size
347
+ self.layer_norm = nn.LayerNorm(config.vision_hidden_size, eps=config.projector_layer_norm_eps)
348
+ self.linear_1 = nn.Linear(
349
+ self.spatial_merge_size**2 * config.vision_hidden_size,
350
+ config.projector_hidden_size,
351
+ bias=True,
352
+ )
353
+ self.linear_2 = nn.Linear(config.projector_hidden_size, config.hidden_size, bias=True)
354
+
355
+ def forward(self, hidden_states, height, width):
356
+ if height % self.spatial_merge_size or width % self.spatial_merge_size:
357
+ raise ValueError("projector requires even patch-grid dimensions")
358
+ hidden_states = self.layer_norm(hidden_states).view(height, width, -1)
359
+ merge = self.spatial_merge_size
360
+ hidden_states = hidden_states.view(height // merge, merge, width // merge, merge, -1)
361
+ hidden_states = hidden_states.permute(0, 2, 1, 3, 4).reshape(-1, merge**2 * hidden_states.shape[-1])
362
+ return self.linear_2(F.gelu(self.linear_1(hidden_states), approximate="none"))
363
+
364
+
365
+ @dataclass
366
+ class Lfm3VlCausalLMOutputWithPast(CausalLMOutputWithPast):
367
+ image_hidden_states: torch.FloatTensor | None = None
368
+ video_hidden_states: torch.FloatTensor | None = None
369
+
370
+
371
+ class Lfm3VlPreTrainedModel(PreTrainedModel):
372
+ config_class = Lfm3VlConfig
373
+ base_model_prefix = ""
374
+ input_modalities = ("image", "video", "text")
375
+ _no_split_modules = ["Lfm3VlLanguageLayer", "Lfm3VlVisionLayer"]
376
+
377
+
378
+ class Lfm3VlForConditionalGeneration(Lfm3VlPreTrainedModel, GenerationMixin):
379
+ def __init__(self, config):
380
+ super().__init__(config)
381
+ self.language_model = Lfm3VlBackbone(config)
382
+ self.vision_encoder = Lfm3VlVisionEncoder(config)
383
+ self.vision_projector = Lfm3VlProjector(config)
384
+ self.post_init()
385
+
386
+ def get_input_embeddings(self):
387
+ return self.language_model.embed_tokens
388
+
389
+ @classmethod
390
+ def _supports_default_dynamic_cache(cls):
391
+ return False
392
+
393
+ def set_input_embeddings(self, value):
394
+ self.language_model.embed_tokens = value
395
+
396
+ def get_output_embeddings(self):
397
+ return self.language_model.embed_tokens
398
+
399
+ def get_visual_features(self, pixel_values, grid_thw):
400
+ target_dtype = self.vision_encoder.patchifier.weight.dtype
401
+ encoded = self.vision_encoder(pixel_values.to(dtype=target_dtype), grid_thw)
402
+ return [self.vision_projector(hidden_states, height, width) for hidden_states, height, width in encoded]
403
+
404
+ def get_image_features(self, pixel_values, image_grid_thw):
405
+ return self.get_visual_features(pixel_values, image_grid_thw)
406
+
407
+ def get_video_features(self, pixel_values_videos, video_grid_thw):
408
+ return self.get_visual_features(pixel_values_videos, video_grid_thw)
409
+
410
+ def forward(
411
+ self,
412
+ input_ids=None,
413
+ attention_mask=None,
414
+ position_ids=None,
415
+ pixel_values=None,
416
+ image_grid_thw=None,
417
+ pixel_values_videos=None,
418
+ video_grid_thw=None,
419
+ past_key_values=None,
420
+ inputs_embeds=None,
421
+ labels=None,
422
+ use_cache=None,
423
+ logits_to_keep=0,
424
+ **kwargs,
425
+ ):
426
+ del kwargs
427
+ if inputs_embeds is None:
428
+ inputs_embeds = self.language_model.embed_tokens(input_ids)
429
+ image_hidden_states = None
430
+ if pixel_values is not None:
431
+ if image_grid_thw is None:
432
+ raise ValueError("image_grid_thw is required with pixel_values")
433
+ image_features = self.get_image_features(pixel_values, image_grid_thw)
434
+ image_hidden_states = torch.cat(image_features, dim=0).to(inputs_embeds.dtype)
435
+ image_mask = input_ids == self.config.image_token_id
436
+ if int(image_mask.sum()) != image_hidden_states.shape[0]:
437
+ raise ValueError("image token count does not match projected image features")
438
+ inputs_embeds = inputs_embeds.clone()
439
+ inputs_embeds[image_mask] = image_hidden_states
440
+ video_hidden_states = None
441
+ if pixel_values_videos is not None:
442
+ if video_grid_thw is None:
443
+ raise ValueError("video_grid_thw is required with pixel_values_videos")
444
+ video_features = self.get_video_features(pixel_values_videos, video_grid_thw)
445
+ video_hidden_states = torch.cat(video_features, dim=0).to(inputs_embeds.dtype)
446
+ video_mask = input_ids == self.config.video_token_id
447
+ if int(video_mask.sum()) != video_hidden_states.shape[0]:
448
+ raise ValueError("video token count does not match projected video features")
449
+ if image_hidden_states is None:
450
+ inputs_embeds = inputs_embeds.clone()
451
+ inputs_embeds[video_mask] = video_hidden_states
452
+ use_cache = self.config.use_cache if use_cache is None else use_cache
453
+ if use_cache and past_key_values is None:
454
+ past_key_values = Lfm3VlCache(
455
+ self.config, inputs_embeds.shape[0], inputs_embeds.dtype, inputs_embeds.device
456
+ )
457
+ hidden_states = self.language_model(inputs_embeds, attention_mask, position_ids, past_key_values)
458
+ hidden_for_logits = (
459
+ hidden_states[:, -logits_to_keep:] if isinstance(logits_to_keep, int) and logits_to_keep else hidden_states
460
+ )
461
+ logits = F.linear(hidden_for_logits, self.language_model.embed_tokens.weight)
462
+ loss = None
463
+ if labels is not None:
464
+ loss = F.cross_entropy(
465
+ logits[:, :-1].reshape(-1, logits.shape[-1]), labels[:, 1:].reshape(-1), ignore_index=-100
466
+ )
467
+ return Lfm3VlCausalLMOutputWithPast(
468
+ loss=loss,
469
+ logits=logits,
470
+ past_key_values=past_key_values,
471
+ image_hidden_states=image_hidden_states,
472
+ video_hidden_states=video_hidden_states,
473
+ )
474
+
475
+ def prepare_inputs_for_generation(
476
+ self, input_ids, past_key_values=None, attention_mask=None, position_ids=None, **kwargs
477
+ ):
478
+ if past_key_values is not None and past_key_values.get_seq_length() > 0:
479
+ input_ids = input_ids[:, -1:]
480
+ if position_ids is not None:
481
+ position_ids = position_ids[:, -1:]
482
+ kwargs["pixel_values"] = None
483
+ kwargs["image_grid_thw"] = None
484
+ kwargs["pixel_values_videos"] = None
485
+ kwargs["video_grid_thw"] = None
486
+ return {
487
+ "input_ids": input_ids,
488
+ "past_key_values": past_key_values,
489
+ "attention_mask": attention_mask,
490
+ "position_ids": position_ids,
491
+ "use_cache": kwargs.get("use_cache", True),
492
+ "pixel_values": kwargs.get("pixel_values"),
493
+ "image_grid_thw": kwargs.get("image_grid_thw"),
494
+ "pixel_values_videos": kwargs.get("pixel_values_videos"),
495
+ "video_grid_thw": kwargs.get("video_grid_thw"),
496
+ }
497
+
498
+
499
+ __all__ = ["Lfm3VlForConditionalGeneration", "Lfm3VlPreTrainedModel"]
processing_lfm3_vl.py ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 The HuggingFace Team. All rights reserved.
2
+ # Licensed under the Apache License, Version 2.0
3
+ """Processor for LFM3-VL using Qwen visual preprocessing and LFM3 text tokenization."""
4
+
5
+ import numpy as np
6
+
7
+ from transformers.feature_extraction_utils import BatchFeature
8
+ from transformers.image_utils import ImageInput
9
+ from transformers.processing_utils import ProcessingKwargs, ProcessorMixin, Unpack
10
+ from transformers.tokenization_utils_base import PreTokenizedInput, TextInput
11
+ from transformers.utils import logging
12
+ from transformers.video_utils import VideoInput
13
+
14
+
15
+ logger = logging.get_logger(__name__)
16
+
17
+ IMAGE_TOKEN = "<|image|>"
18
+ VIDEO_TOKEN = "<|video|>"
19
+ VISION_START_TOKEN = "<|vision_start|>"
20
+ VISION_END_TOKEN = "<|vision_end|>"
21
+ IMAGE_TOKEN_ID = 125030
22
+ VIDEO_TOKEN_ID = 125029
23
+ VISION_START_TOKEN_ID = 125009
24
+ VISION_END_TOKEN_ID = 125010
25
+
26
+
27
+ class Lfm3VlProcessorKwargs(ProcessingKwargs, total=False):
28
+ _defaults = {
29
+ "text_kwargs": {
30
+ "padding": False,
31
+ "return_token_type_ids": False,
32
+ "return_mm_token_type_ids": False,
33
+ },
34
+ "videos_kwargs": {"return_metadata": True},
35
+ }
36
+
37
+
38
+ class Lfm3VlProcessor(ProcessorMixin):
39
+ """Expand Qwen-style visual spans around Qwen-preprocessed visual inputs."""
40
+
41
+ def __init__(self, image_processor=None, tokenizer=None, video_processor=None, chat_template=None, **kwargs):
42
+ self.image_token = IMAGE_TOKEN
43
+ self.video_token = VIDEO_TOKEN
44
+ self.vision_start_token = VISION_START_TOKEN
45
+ self.vision_end_token = VISION_END_TOKEN
46
+ self.image_token_id = tokenizer.convert_tokens_to_ids(self.image_token)
47
+ self.video_token_id = tokenizer.convert_tokens_to_ids(self.video_token)
48
+ self.vision_start_token_id = tokenizer.convert_tokens_to_ids(self.vision_start_token)
49
+ self.vision_end_token_id = tokenizer.convert_tokens_to_ids(self.vision_end_token)
50
+ actual_token_ids = (
51
+ self.image_token_id,
52
+ self.video_token_id,
53
+ self.vision_start_token_id,
54
+ self.vision_end_token_id,
55
+ )
56
+ expected_token_ids = (IMAGE_TOKEN_ID, VIDEO_TOKEN_ID, VISION_START_TOKEN_ID, VISION_END_TOKEN_ID)
57
+ if actual_token_ids != expected_token_ids:
58
+ raise ValueError(
59
+ "LFM3-VL requires the released multimodal tokenizer with "
60
+ f"{IMAGE_TOKEN}={IMAGE_TOKEN_ID}, {VIDEO_TOKEN}={VIDEO_TOKEN_ID}, "
61
+ f"{VISION_START_TOKEN}={VISION_START_TOKEN_ID}, and {VISION_END_TOKEN}={VISION_END_TOKEN_ID}; "
62
+ f"got {actual_token_ids}."
63
+ )
64
+ super().__init__(image_processor, tokenizer, video_processor, chat_template=chat_template, **kwargs)
65
+
66
+ def __call__(
67
+ self,
68
+ images: ImageInput = None,
69
+ text: TextInput | PreTokenizedInput | list[TextInput] | list[PreTokenizedInput] = None,
70
+ videos: VideoInput = None,
71
+ **kwargs: Unpack[Lfm3VlProcessorKwargs],
72
+ ) -> BatchFeature:
73
+ output_kwargs = self._merge_kwargs(
74
+ Lfm3VlProcessorKwargs,
75
+ tokenizer_init_kwargs=self.tokenizer.init_kwargs,
76
+ **kwargs,
77
+ )
78
+ image_inputs = (
79
+ self.image_processor(images=images, **output_kwargs["images_kwargs"]) if images is not None else {}
80
+ )
81
+ video_inputs = (
82
+ self.video_processor(videos=videos, **output_kwargs["videos_kwargs"]) if videos is not None else {}
83
+ )
84
+ image_grid_thw = image_inputs.get("image_grid_thw")
85
+ video_grid_thw = video_inputs.get("video_grid_thw")
86
+ video_metadata = video_inputs.get("video_metadata")
87
+
88
+ if not isinstance(text, list):
89
+ text = [text]
90
+ text = text.copy()
91
+ if image_grid_thw is not None:
92
+ image_index = 0
93
+ for sample_index in range(len(text)):
94
+ while self.image_token in text[sample_index]:
95
+ token_count = int(image_grid_thw[image_index].prod()) // self.image_processor.merge_size**2
96
+ text[sample_index] = text[sample_index].replace(
97
+ self.image_token, "<|placeholder|>" * token_count, 1
98
+ )
99
+ image_index += 1
100
+ text[sample_index] = text[sample_index].replace("<|placeholder|>", self.image_token)
101
+
102
+ if video_grid_thw is not None:
103
+ video_index = 0
104
+ for sample_index in range(len(text)):
105
+ while self.video_token in text[sample_index]:
106
+ metadata = video_metadata[video_index]
107
+ if metadata.fps is None:
108
+ logger.warning_once("Video FPS is missing; defaulting to 24 FPS for timestamps.")
109
+ metadata.fps = 24
110
+ timestamps = self._calculate_timestamps(
111
+ metadata.frames_indices,
112
+ metadata.fps,
113
+ self.video_processor.temporal_patch_size,
114
+ )
115
+ frame_tokens = int(video_grid_thw[video_index][1:].prod()) // self.video_processor.merge_size**2
116
+ placeholder = "".join(
117
+ f"<{timestamp:.1f} seconds>"
118
+ + self.vision_start_token
119
+ + "<|placeholder|>" * frame_tokens
120
+ + self.vision_end_token
121
+ for timestamp in timestamps
122
+ )
123
+ text[sample_index] = text[sample_index].replace(self.video_token, placeholder, 1)
124
+ video_index += 1
125
+ text[sample_index] = text[sample_index].replace("<|placeholder|>", self.video_token)
126
+
127
+ return_tensors = output_kwargs["text_kwargs"].pop("return_tensors", None)
128
+ return_mm_token_type_ids = output_kwargs["text_kwargs"].pop("return_mm_token_type_ids", False)
129
+ text_inputs = self.tokenizer(text, **output_kwargs["text_kwargs"])
130
+ self._check_special_mm_tokens(text, text_inputs, modalities=["image", "video"])
131
+ if return_mm_token_type_ids:
132
+ input_ids = np.asarray(text_inputs["input_ids"])
133
+ mm_token_type_ids = np.zeros_like(input_ids)
134
+ mm_token_type_ids[input_ids == self.image_token_id] = 1
135
+ mm_token_type_ids[input_ids == self.video_token_id] = 2
136
+ text_inputs["mm_token_type_ids"] = mm_token_type_ids.tolist()
137
+ video_inputs.pop("video_metadata", None)
138
+ return BatchFeature(
139
+ data={**text_inputs, **image_inputs, **video_inputs},
140
+ tensor_type=return_tensors,
141
+ )
142
+
143
+ @staticmethod
144
+ def _calculate_timestamps(indices, video_fps, temporal_patch_size=2):
145
+ indices = indices.tolist() if not isinstance(indices, list) else indices.copy()
146
+ if len(indices) % temporal_patch_size:
147
+ indices.extend(indices[-1] for _ in range(temporal_patch_size - len(indices) % temporal_patch_size))
148
+ return [
149
+ (indices[index] + indices[index + temporal_patch_size - 1]) / (2 * video_fps)
150
+ for index in range(0, len(indices), temporal_patch_size)
151
+ ]
152
+
153
+
154
+ __all__ = ["Lfm3VlProcessor"]
processor_config.json ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "auto_map": {
3
+ "AutoProcessor": "processing_lfm3_vl.Lfm3VlProcessor"
4
+ },
5
+ "image_processor": {
6
+ "auto_map": {
7
+ "AutoImageProcessor": "image_processing_lfm3_vl.Lfm3VlImageProcessor",
8
+ "AutoProcessor": "processing_lfm3_vl.Lfm3VlProcessor"
9
+ },
10
+ "do_convert_rgb": true,
11
+ "do_normalize": true,
12
+ "do_rescale": true,
13
+ "do_resize": true,
14
+ "image_mean": [
15
+ 0.5,
16
+ 0.5,
17
+ 0.5
18
+ ],
19
+ "image_processor_type": "Lfm3VlImageProcessor",
20
+ "image_std": [
21
+ 0.5,
22
+ 0.5,
23
+ 0.5
24
+ ],
25
+ "merge_size": 2,
26
+ "patch_size": 16,
27
+ "resample": 3,
28
+ "rescale_factor": 0.00392156862745098,
29
+ "size": {
30
+ "longest_edge": 16777216,
31
+ "shortest_edge": 65536
32
+ },
33
+ "temporal_patch_size": 2
34
+ },
35
+ "processor_class": "Lfm3VlProcessor",
36
+ "video_processor": {
37
+ "auto_map": {
38
+ "AutoProcessor": "processing_lfm3_vl.Lfm3VlProcessor",
39
+ "AutoVideoProcessor": "video_processing_lfm3_vl.Lfm3VlVideoProcessor"
40
+ },
41
+ "do_convert_rgb": true,
42
+ "do_normalize": true,
43
+ "do_rescale": true,
44
+ "do_resize": true,
45
+ "do_sample_frames": true,
46
+ "fps": 2.0,
47
+ "image_mean": [
48
+ 0.5,
49
+ 0.5,
50
+ 0.5
51
+ ],
52
+ "image_std": [
53
+ 0.5,
54
+ 0.5,
55
+ 0.5
56
+ ],
57
+ "max_frames": 768,
58
+ "max_video_tokens": 768,
59
+ "merge_size": 2,
60
+ "min_frames": 4,
61
+ "patch_size": 16,
62
+ "resample": 3,
63
+ "rescale_factor": 0.00392156862745098,
64
+ "return_metadata": false,
65
+ "size": {
66
+ "longest_edge": 25165824,
67
+ "shortest_edge": 4096
68
+ },
69
+ "temporal_patch_size": 2,
70
+ "video_processor_type": "Lfm3VlVideoProcessor"
71
+ }
72
+ }
tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a39eed58008cbcbc09e5b629b81338a654269c64cd13348aaa6090f4db75c7e7
3
+ size 17908258
tokenizer_config.json ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "auto_map": {
3
+ "AutoProcessor": "processing_lfm3_vl.Lfm3VlProcessor"
4
+ },
5
+ "backend": "tokenizers",
6
+ "bos_token": "<|startoftext|>",
7
+ "eos_token": "<|im_end|>",
8
+ "extra_special_tokens": [],
9
+ "image_token": "<|image|>",
10
+ "is_local": true,
11
+ "local_files_only": false,
12
+ "model_max_length": 1000000000000000019884624838656,
13
+ "model_specific_special_tokens": {
14
+ "image_token": "<|image|>",
15
+ "video_token": "<|video|>",
16
+ "vision_end_token": "<|vision_end|>",
17
+ "vision_start_token": "<|vision_start|>"
18
+ },
19
+ "pad_token": "<|pad|>",
20
+ "processor_class": "Lfm3VlProcessor",
21
+ "tokenizer_class": "TokenizersBackend",
22
+ "video_token": "<|video|>",
23
+ "vision_end_token": "<|vision_end|>",
24
+ "vision_start_token": "<|vision_start|>"
25
+ }
video_processing_lfm3_vl.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 The HuggingFace Team. All rights reserved.
2
+ # Licensed under the Apache License, Version 2.0
3
+ """Qwen-style video preprocessing for LFM3-VL."""
4
+
5
+ from transformers.models.qwen3_vl.video_processing_qwen3_vl import Qwen3VLVideoProcessor
6
+
7
+
8
+ class Lfm3VlVideoProcessor(Qwen3VLVideoProcessor):
9
+ patch_size = 16
10
+ temporal_patch_size = 2
11
+ merge_size = 2
12
+ size = {"shortest_edge": 4_096, "longest_edge": 25_165_824}
13
+ fps = 2.0
14
+ min_frames = 4
15
+ max_frames = 768
16
+ image_mean = [0.5, 0.5, 0.5]
17
+ image_std = [0.5, 0.5, 0.5]
18
+
19
+
20
+ __all__ = ["Lfm3VlVideoProcessor"]