pnevskaiaan commited on
Commit
1b96d67
·
verified ·
1 Parent(s): 9c7e971

Update README.md

Browse files
Files changed (1) hide show
  1. README.md +189 -0
README.md CHANGED
@@ -1,3 +1,192 @@
1
  ---
2
  license: apache-2.0
3
  ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  license: apache-2.0
3
  ---
4
+
5
+ ```python
6
+
7
+ import json
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ import torch
12
+ from transformers import (
13
+ AutoConfig,
14
+ AutoProcessor,
15
+ GenerationConfig,
16
+ )
17
+ from transformers.models.qwen3_5.modeling_qwen3_5 import Qwen3_5ForConditionalGeneration
18
+
19
+
20
+ SEED = 42
21
+ MODEL_ID = "openbmb/MiniCPM-OCR-Jev"
22
+ OUTPUT_DIR = Path(__file__).resolve().parent / "tiny-random-minicpm-ocr-jev"
23
+
24
+ # Marker written into the cached config so a stale/incompatible cache is rebuilt
25
+ # instead of silently reused.
26
+ CACHE_MARKER = "tiny-minicpm-ocr-jev-v1"
27
+
28
+ MAX_PARAMS = 40_000_000
29
+ MAX_BYTES = 180 * 1024 * 1024
30
+
31
+
32
+ def build_tiny_config():
33
+ """Load the real config and shrink every scale parameter in place.
34
+
35
+ The full ``vocab_size`` (248320) is preserved so the real tokenizer and the
36
+ vision/image special-token ids (image/video/vision-start/vision-end) stay
37
+ valid for the image-text-to-text path.
38
+ """
39
+ cfg = AutoConfig.from_pretrained(MODEL_ID)
40
+
41
+ tc = cfg.text_config
42
+ tc.hidden_size = 64
43
+ tc.intermediate_size = 128
44
+ tc.num_hidden_layers = 4
45
+ tc.num_attention_heads = 4
46
+ tc.num_key_value_heads = 2
47
+ tc.head_dim = 32
48
+ # Preserve the real linear/full attention mix (gated-delta-net + attention),
49
+ # with one full_attention layer like the full_attention_interval=4 pattern.
50
+ tc.layer_types = ["linear_attention", "linear_attention", "linear_attention", "full_attention"]
51
+ tc.linear_num_key_heads = 2
52
+ tc.linear_num_value_heads = 4
53
+ tc.linear_key_head_dim = 16
54
+ tc.linear_value_head_dim = 16
55
+ # keep tc.linear_conv_kernel_dim (4)
56
+ tc.max_position_embeddings = 4096
57
+ # keep tc.use_cache (original ships False; generation still works at this scale)
58
+
59
+ # partial_rotary_factor 0.25 * head_dim(32) -> rotary dim 8 -> freqs len 4;
60
+ # interleaved mrope_section must sum to rotary_dim // 2 == 4.
61
+ if isinstance(tc.rope_parameters, dict):
62
+ tc.rope_parameters["mrope_section"] = [2, 1, 1]
63
+ tc.rope_parameters["partial_rotary_factor"] = 0.25
64
+
65
+ vc = cfg.vision_config
66
+ vc.depth = 2
67
+ vc.hidden_size = 64
68
+ vc.intermediate_size = 128
69
+ vc.num_heads = 4
70
+ # out_hidden_size must match the text hidden_size (image embeds merge into
71
+ # the decoder embedding space).
72
+ vc.out_hidden_size = tc.hidden_size
73
+ # num_position_embeddings must be a perfect square (num_grid_per_side =
74
+ # int(sqrt(num_position_embeddings))); 256 -> 16 per side.
75
+ vc.num_position_embeddings = 256
76
+ # keep patch_size(16), spatial_merge_size(2), temporal_patch_size(2), in_channels(3)
77
+
78
+ # Force float32 at every effective precision level for a robust CPU forward.
79
+ cfg.dtype = "float32"
80
+ tc.dtype = "float32"
81
+ vc.dtype = "float32"
82
+
83
+ return cfg
84
+
85
+
86
+ def _count_and_measure(model):
87
+ n_params = 0
88
+ n_bytes = 0
89
+ for p in model.parameters():
90
+ n_params += p.numel()
91
+ n_bytes += p.numel() * p.element_size()
92
+ for b in model.buffers():
93
+ n_bytes += b.numel() * b.element_size()
94
+ return n_params, n_bytes
95
+
96
+
97
+ def _cache_is_valid():
98
+ cfg_path = OUTPUT_DIR / "config.json"
99
+ weights = (OUTPUT_DIR / "model.safetensors").exists() or (OUTPUT_DIR / "pytorch_model.bin").exists()
100
+ if not cfg_path.exists() or not weights:
101
+ return False
102
+ try:
103
+ data = json.loads(cfg_path.read_text())
104
+ except Exception:
105
+ return False
106
+ if data.get("_tiny_cache_marker") != CACHE_MARKER:
107
+ return False
108
+ if data.get("model_type") != "qwen3_5":
109
+ return False
110
+ if data.get("architectures") != ["Qwen3_5ForConditionalGeneration"]:
111
+ return False
112
+ return True
113
+
114
+
115
+ def create_tiny_model():
116
+ if _cache_is_valid():
117
+ print(f"Reusing valid cache at {OUTPUT_DIR}")
118
+ return OUTPUT_DIR
119
+
120
+ torch.manual_seed(SEED)
121
+
122
+ config = build_tiny_config()
123
+
124
+ # Instantiate the real architecture class with random weights.
125
+ with torch.device("cpu"):
126
+ model = Qwen3_5ForConditionalGeneration(config)
127
+ model = model.to(torch.float32)
128
+ model.eval()
129
+
130
+ n_params, n_bytes = _count_and_measure(model)
131
+ print(f"params={n_params:,} bytes={n_bytes / 1024 / 1024:.1f}MB")
132
+ if n_params > MAX_PARAMS:
133
+ raise RuntimeError(f"Parameter budget exceeded: {n_params:,} > {MAX_PARAMS:,}")
134
+ if n_bytes > MAX_BYTES:
135
+ raise RuntimeError(f"Memory budget exceeded: {n_bytes:,} > {MAX_BYTES:,}")
136
+
137
+ OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
138
+ model.save_pretrained(OUTPUT_DIR, safe_serialization=True)
139
+
140
+ # Save processor assets (tokenizer, image processor, chat template).
141
+ processor = AutoProcessor.from_pretrained(MODEL_ID)
142
+ processor.save_pretrained(OUTPUT_DIR)
143
+
144
+ # Save a usable generation config.
145
+ try:
146
+ gen = GenerationConfig.from_pretrained(MODEL_ID)
147
+ except Exception:
148
+ gen = GenerationConfig()
149
+ gen.save_pretrained(OUTPUT_DIR)
150
+
151
+ # Stamp the cache marker and assert float32 precision fields survived.
152
+ cfg_path = OUTPUT_DIR / "config.json"
153
+ data = json.loads(cfg_path.read_text())
154
+ data["_tiny_cache_marker"] = CACHE_MARKER
155
+ data["dtype"] = "float32"
156
+ if isinstance(data.get("text_config"), dict):
157
+ data["text_config"]["dtype"] = "float32"
158
+ if isinstance(data.get("vision_config"), dict):
159
+ data["vision_config"]["dtype"] = "float32"
160
+ # ``Qwen3_5VisionConfig.model_type`` class attribute is "qwen3_5", so
161
+ # ``to_dict()`` drops the real "qwen3_5_vision" marker; restore it.
162
+ data["vision_config"]["model_type"] = "qwen3_5_vision"
163
+ cfg_path.write_text(json.dumps(data, indent=2))
164
+
165
+ print(f"Saved tiny model to {OUTPUT_DIR}")
166
+ return OUTPUT_DIR
167
+
168
+
169
+ def verify_identity():
170
+ """Assert the saved config preserves the real architecture identity."""
171
+ orig = AutoConfig.from_pretrained(MODEL_ID)
172
+ tiny = AutoConfig.from_pretrained(OUTPUT_DIR)
173
+
174
+ assert tiny.model_type == orig.model_type == "qwen3_5", tiny.model_type
175
+ assert tiny.architectures == orig.architectures == ["Qwen3_5ForConditionalGeneration"]
176
+ assert tiny.text_config.model_type == orig.text_config.model_type == "qwen3_5_text"
177
+ assert tiny.vision_config.model_type == orig.vision_config.model_type == "qwen3_5_vision"
178
+ assert tiny.image_token_id == orig.image_token_id
179
+ assert tiny.vision_start_token_id == orig.vision_start_token_id
180
+ assert tiny.text_config.vocab_size == orig.text_config.vocab_size
181
+ # linear + full attention mix preserved
182
+ assert "linear_attention" in tiny.text_config.layer_types
183
+ assert "full_attention" in tiny.text_config.layer_types
184
+ print("Architecture identity OK")
185
+
186
+
187
+ if __name__ == "__main__":
188
+ out = create_tiny_model()
189
+ verify_identity()
190
+ print(out)
191
+
192
+ ```