Safetensors
gemma4
pnevskaiaan commited on
Commit
558e02e
·
verified ·
1 Parent(s): b4c3425

Update README.md

Browse files
Files changed (1) hide show
  1. README.md +177 -46
README.md CHANGED
@@ -3,68 +3,199 @@ license: apache-2.0
3
  ---
4
 
5
  ```python
6
- from transformers import (
7
- AutoTokenizer,
8
- Gemma4Config,
9
- Gemma4ForConditionalGeneration,
10
- Gemma4TextConfig,
11
- Gemma4ForCausalLM,
12
- Gemma4VisionConfig,
13
- Gemma4AudioConfig,
14
- )
15
-
16
-
17
- def generate_vlm_model(output_dir="./tiny-random-gemma4"):
18
- model_tr = Gemma4ForConditionalGeneration.from_pretrained("google/gemma-4-E2B-it")
19
- config = model_tr.config
20
-
21
- config.audio_config.hidden_size = 8
22
- config.audio_config.num_attention_heads = 2
23
- config.audio_config.num_hidden_layers = 1
24
- config.audio_config.output_proj_dims = 8
25
-
26
- config.text_config.global_head_dim = 4
27
- config.text_config.head_dim = 4
28
- config.text_config.hidden_size = 8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  config.text_config.hidden_size_per_layer_input = 1
30
- config.text_config.intermediate_size = 32
31
- config.text_config.num_attention_heads = 2
32
- config.text_config.num_hidden_layers = 3
 
33
  config.text_config.layer_types = ["sliding_attention", "full_attention", "full_attention"]
34
  config.text_config.num_kv_shared_layers = 1
35
  config.text_config.dtype = "float32"
36
 
37
  config.vision_config.default_output_length = 70
38
- config.vision_config.head_dim = 4
39
- config.vision_config.hidden_size = 8
40
- config.vision_config.intermediate_size = 32
41
- config.vision_config.num_attention_heads = 2
42
- config.vision_config.num_hidden_layers = 1
43
- config.vision_config.num_key_value_heads = 2
44
- config.vision_config.patch_size = 2
 
45
 
46
  model = Gemma4ForConditionalGeneration(config)
47
  model.eval()
48
 
49
- model.save_pretrained(output_dir)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
- # Copy tokenizer from google/gemma-4-E2B-it
52
- tokenizer = AutoTokenizer.from_pretrained("google/gemma-4-E2B-it")
53
- tokenizer.save_pretrained(output_dir)
54
 
55
- # Estimate safetensors size
56
- import os
 
 
 
 
 
 
 
 
57
 
58
- safetensors_path = os.path.join(output_dir, "model.safetensors")
59
- if os.path.exists(safetensors_path):
60
- size_mb = os.path.getsize(safetensors_path) / (1024 * 1024)
61
- print(f" model.safetensors size: {size_mb:.1f} MB")
62
 
63
- print(f" VLM model saved to {output_dir}")
64
- return model
 
 
 
 
 
 
65
 
66
 
67
  if __name__ == "__main__":
68
- generate_vlm_model()
69
 
70
  ```
 
3
  ---
4
 
5
  ```python
6
+
7
+ import argparse
8
+ from pathlib import Path
9
+
10
+
11
+ DEFAULT_SOURCE_MODEL = "google/gemma-4-E2B-it"
12
+
13
+
14
+ def parse_args() -> argparse.Namespace:
15
+ parser = argparse.ArgumentParser(
16
+ description="Generate and optionally export a configurable tiny-random Gemma4 model.",
17
+ )
18
+ parser.add_argument("--output-dir", type=Path, required=True)
19
+ parser.add_argument("--source-model", default=DEFAULT_SOURCE_MODEL)
20
+ parser.add_argument("--hidden-size", type=int, default=32)
21
+ parser.add_argument("--head-dim", type=int, default=8)
22
+ parser.add_argument("--num-attention-heads", type=int, default=4)
23
+ parser.add_argument("--intermediate-size", type=int, default=64)
24
+ parser.add_argument("--text-layers", type=int, default=3)
25
+ parser.add_argument("--audio-layers", type=int, default=1)
26
+ parser.add_argument("--vision-layers", type=int, default=1)
27
+ parser.add_argument("--seed", type=int, default=0)
28
+ parser.add_argument("--export-dir", type=Path)
29
+ parser.add_argument("--smoke-test", action="store_true")
30
+ parser.add_argument("--device", default="CPU")
31
+ parser.add_argument("--attention-backend", choices=("PA", "SDPA"), default="SDPA")
32
+ return parser.parse_args()
33
+
34
+
35
+ def validate_args(args: argparse.Namespace) -> None:
36
+ if args.hidden_size != args.head_dim * args.num_attention_heads:
37
+ raise ValueError(
38
+ "hidden-size must equal head-dim * num-attention-heads: "
39
+ f"{args.hidden_size} != {args.head_dim} * {args.num_attention_heads}"
40
+ )
41
+ if args.num_attention_heads < 2:
42
+ raise ValueError("num-attention-heads must be at least 2")
43
+ if args.text_layers != 3:
44
+ raise ValueError("text-layers must remain 3 for the model-card layer pattern")
45
+ if args.intermediate_size < args.hidden_size:
46
+ raise ValueError("intermediate-size must be at least hidden-size")
47
+ if args.smoke_test and args.export_dir is None:
48
+ raise ValueError("--smoke-test requires --export-dir")
49
+
50
+
51
+ def generate_model(args: argparse.Namespace) -> None:
52
+ import torch
53
+ from transformers import AutoProcessor, Gemma4Config, Gemma4ForConditionalGeneration
54
+
55
+ torch.manual_seed(args.seed)
56
+ config = Gemma4Config.from_pretrained(args.source_model)
57
+
58
+ config.audio_config.hidden_size = args.hidden_size
59
+ config.audio_config.num_attention_heads = args.num_attention_heads
60
+ config.audio_config.num_hidden_layers = args.audio_layers
61
+ config.audio_config.output_proj_dims = args.hidden_size
62
+ config.audio_config.dtype = "float32"
63
+
64
+ config.text_config.global_head_dim = args.head_dim
65
+ config.text_config.head_dim = args.head_dim
66
+ config.text_config.hidden_size = args.hidden_size
67
  config.text_config.hidden_size_per_layer_input = 1
68
+ config.text_config.intermediate_size = args.intermediate_size
69
+ config.text_config.num_attention_heads = args.num_attention_heads
70
+ config.text_config.num_key_value_heads = max(1, args.num_attention_heads // 2)
71
+ config.text_config.num_hidden_layers = args.text_layers
72
  config.text_config.layer_types = ["sliding_attention", "full_attention", "full_attention"]
73
  config.text_config.num_kv_shared_layers = 1
74
  config.text_config.dtype = "float32"
75
 
76
  config.vision_config.default_output_length = 70
77
+ config.vision_config.head_dim = args.head_dim
78
+ config.vision_config.hidden_size = args.hidden_size
79
+ config.vision_config.intermediate_size = args.intermediate_size
80
+ config.vision_config.num_attention_heads = args.num_attention_heads
81
+ config.vision_config.num_hidden_layers = args.vision_layers
82
+ config.vision_config.num_key_value_heads = args.num_attention_heads
83
+ config.vision_config.patch_size = 16
84
+ config.vision_config.dtype = "float32"
85
 
86
  model = Gemma4ForConditionalGeneration(config)
87
  model.eval()
88
 
89
+ args.output_dir.mkdir(parents=True, exist_ok=True)
90
+ model.save_pretrained(args.output_dir)
91
+ processor = AutoProcessor.from_pretrained(args.source_model, padding_side="left", truncation_side="left")
92
+ processor.save_pretrained(args.output_dir)
93
+
94
+ parameter_count = sum(parameter.numel() for parameter in model.parameters())
95
+ print(f"Saved {parameter_count:,}-parameter model to {args.output_dir}")
96
+
97
+ from transformers import AutoProcessor, Gemma4ForConditionalGeneration
98
+
99
+ messages = [
100
+ {
101
+ "role": "user", "content": [
102
+ {"type": "image",
103
+ "url": "https://raw.githubusercontent.com/google-gemma/cookbook/refs/heads/main/apps/sample-data/GoldenGate.png"},
104
+ {"type": "text", "text": "What is shown in this image?"}
105
+ ]
106
+ }
107
+ ]
108
+
109
+ processor = AutoProcessor.from_pretrained("google/gemma-4-E2B-it")
110
+ model = Gemma4ForConditionalGeneration.from_pretrained(
111
+ args.output_dir,
112
+ dtype="auto",
113
+ device_map="auto"
114
+ )
115
+
116
+ # Process input
117
+ inputs = processor.apply_chat_template(
118
+ messages,
119
+ tokenize=True,
120
+ return_dict=True,
121
+ return_tensors="pt",
122
+ add_generation_prompt=True,
123
+ ).to(model.device)
124
+ input_len = inputs["input_ids"].shape[-1]
125
+
126
+ # Generate output
127
+ outputs = model.generate(**inputs, max_new_tokens=512)
128
+ print("VLM infer OK")
129
+
130
+
131
+ def export_model(model_dir: Path, export_dir: Path) -> None:
132
+ import openvino
133
+ import openvino_tokenizers
134
+ from optimum.intel.openvino import OVModelForVisualCausalLM
135
+ from transformers import AutoProcessor, Gemma4ForConditionalGeneration
136
+
137
+
138
+
139
+ processor = AutoProcessor.from_pretrained(model_dir, padding_side="left", truncation_side="left")
140
+ ov_model = OVModelForVisualCausalLM.from_pretrained(
141
+ model_dir,
142
+ compile=False,
143
+ device="CPU",
144
+ export=True,
145
+ load_in_8bit=False,
146
+ )
147
+
148
+ processor.image_processor.size ={
149
+ "height": 32,
150
+ "width": 32
151
+ }
152
+
153
+ export_dir.mkdir(parents=True, exist_ok=True)
154
+ ov_model.save_pretrained(export_dir)
155
+ processor.save_pretrained(export_dir)
156
+ ov_tokenizer, ov_detokenizer = openvino_tokenizers.convert_tokenizer(
157
+ processor.tokenizer,
158
+ with_detokenizer=True,
159
+ )
160
+ openvino.save_model(ov_tokenizer, export_dir / "openvino_tokenizer.xml")
161
+ openvino.save_model(ov_detokenizer, export_dir / "openvino_detokenizer.xml")
162
+ print(f"Exported OpenVINO model to {export_dir}")
163
+
164
+
165
+ def smoke_test(export_dir: Path, device: str, attention_backend: str) -> None:
166
+ import numpy as np
167
+ import openvino
168
+ from openvino_genai import VLMPipeline
169
+ from transformers import Gemma4ForConditionalGeneration
170
+
171
 
172
+ pipeline = VLMPipeline(export_dir, device, ATTENTION_BACKEND=attention_backend)
173
+ text_result = pipeline.generate("Hello", max_new_tokens=1, do_sample=False)
174
+ print(f"Text smoke test passed: {text_result.texts!r}")
175
 
176
+ sampling_rate = 16_000
177
+ timestamps = np.arange(sampling_rate, dtype=np.float32) / sampling_rate
178
+ audio = 0.5 * np.sin(2 * np.pi * 440 * timestamps) + 0.25 * np.sin(2 * np.pi * 880 * timestamps)
179
+ audio_result = pipeline.generate(
180
+ "Describe this audio.<|audio|>",
181
+ audios=[openvino.Tensor(audio.astype(np.float32))],
182
+ max_new_tokens=1,
183
+ do_sample=False,
184
+ )
185
+ print(f"Audio smoke test passed: {audio_result.texts!r}")
186
 
 
 
 
 
187
 
188
+ def main() -> None:
189
+ args = parse_args()
190
+ validate_args(args)
191
+ generate_model(args)
192
+ if args.export_dir is not None:
193
+ export_model(args.output_dir, args.export_dir)
194
+ if args.smoke_test:
195
+ smoke_test(args.export_dir, args.device, args.attention_backend)
196
 
197
 
198
  if __name__ == "__main__":
199
+ main()
200
 
201
  ```