kwajiehao commited on
Commit
890bc25
·
verified ·
1 Parent(s): 4c5f90f

Upload convert_reka_vlm_to_gguf.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. convert_reka_vlm_to_gguf.py +311 -0
convert_reka_vlm_to_gguf.py ADDED
@@ -0,0 +1,311 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Convert Reka Yasa2 checkpoints to GGUF (text decoder and optional mmproj for MTMD).
4
+
5
+ - Text: Llama-arch decoder + tiktoken/BPE vocab (bytes keys normalized for GGUF).
6
+ - Mmproj (--mmproj): ConvNeXt vision + language_projection for Yasa2 MTMD path.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import re
12
+ from typing import Iterable
13
+
14
+ import numpy as np
15
+ import torch
16
+ from transformers import AutoTokenizer
17
+
18
+ import convert_hf_to_gguf as base
19
+
20
+
21
+ def _get_2d_sincos_pos_embed_yasa2(embed_dim: int, image_size: int = 50, seq_len: int = 256) -> np.ndarray:
22
+ """Match HF get_2d_sincos_pos_embed(hidden, image_size=50) then slice [:seq_len]."""
23
+ assert embed_dim % 2 == 0
24
+
25
+ def _get_1d(embed_dim_1d: int, pos_2d: np.ndarray) -> np.ndarray:
26
+ assert embed_dim_1d % 2 == 0
27
+ omega = np.arange(embed_dim_1d // 2, dtype=np.float32)
28
+ omega /= embed_dim_1d / 2.0
29
+ omega = 1.0 / (10000.0**omega)
30
+ out = np.einsum("hw,d->hwd", pos_2d, omega)
31
+ return np.concatenate([np.sin(out), np.cos(out)], axis=-1)
32
+
33
+ grid_h = np.arange(image_size, dtype=np.float32)
34
+ grid_w = np.arange(image_size, dtype=np.float32)
35
+ grid = np.meshgrid(grid_w, grid_h)
36
+ grid = np.stack(grid, axis=0)
37
+ emb_h = _get_1d(embed_dim // 2, grid[0])
38
+ emb_w = _get_1d(embed_dim // 2, grid[1])
39
+ pos = np.concatenate([emb_h, emb_w], axis=-1).reshape(image_size * image_size, embed_dim)
40
+ return pos[:seq_len].astype(np.float32, copy=False)
41
+
42
+
43
+ class RekaYasa2TextDecoderModel(base.LlamaModel):
44
+ model_arch = base.gguf.MODEL_ARCH.LLAMA
45
+
46
+ def get_vocab_base(self) -> tuple[list[str], list[int], str]:
47
+ tokens: list[str] = []
48
+ toktypes: list[int] = []
49
+
50
+ tokenizer = AutoTokenizer.from_pretrained(self.dir_model, trust_remote_code=True)
51
+ vocab_dict = tokenizer.get_vocab()
52
+ vocab_size = self.hparams.get("vocab_size", len(vocab_dict))
53
+ assert max(vocab_dict.values()) < vocab_size
54
+
55
+ tokpre = self.get_vocab_base_pre(tokenizer)
56
+ reverse_vocab = {idx: tok for tok, idx in vocab_dict.items()}
57
+ added_vocab = tokenizer.get_added_vocab()
58
+ added_tokens_decoder = getattr(tokenizer, "added_tokens_decoder", {})
59
+ tiktoken_special = set(getattr(tokenizer, "tiktoken_special_tokens", {}).keys())
60
+
61
+ def token_to_str(tok: str | bytes) -> str:
62
+ if isinstance(tok, bytes):
63
+ return base.QwenModel.token_bytes_to_string(tok)
64
+ return tok
65
+
66
+ for i in range(vocab_size):
67
+ if i not in reverse_vocab:
68
+ tokens.append(f"[PAD{i}]")
69
+ toktypes.append(base.gguf.TokenType.UNUSED)
70
+ continue
71
+
72
+ token = reverse_vocab[i]
73
+ token_str = token_to_str(token)
74
+ if token_str in tiktoken_special:
75
+ toktypes.append(base.gguf.TokenType.CONTROL)
76
+ elif token in added_vocab or token_str in added_vocab:
77
+ if i in added_tokens_decoder and getattr(added_tokens_decoder[i], "special", False):
78
+ toktypes.append(base.gguf.TokenType.CONTROL)
79
+ else:
80
+ toktypes.append(base.gguf.TokenType.USER_DEFINED)
81
+ else:
82
+ toktypes.append(base.gguf.TokenType.NORMAL)
83
+ tokens.append(token_str)
84
+
85
+ return tokens, toktypes, tokpre
86
+
87
+ def set_vocab(self):
88
+ tokens, toktypes, tokpre = self.get_vocab_base()
89
+ self.gguf_writer.add_tokenizer_model("gpt2")
90
+ self.gguf_writer.add_tokenizer_pre(tokpre)
91
+ self.gguf_writer.add_token_list(tokens)
92
+ self.gguf_writer.add_token_types(toktypes)
93
+
94
+ tokenizer = AutoTokenizer.from_pretrained(self.dir_model, trust_remote_code=True)
95
+ mergeable_ranks = getattr(tokenizer, "mergeable_ranks", None)
96
+ if mergeable_ranks is None and hasattr(tokenizer, "tiktoken"):
97
+ mergeable_ranks = getattr(tokenizer.tiktoken, "_mergeable_ranks", None)
98
+
99
+ if mergeable_ranks:
100
+ merges: list[str] = []
101
+ for token, rank in mergeable_ranks.items():
102
+ if len(token) == 1:
103
+ continue
104
+ merged = base.QwenModel.bpe(mergeable_ranks, token, max_rank=rank)
105
+ if len(merged) == 2:
106
+ merges.append(" ".join(map(base.QwenModel.token_bytes_to_string, merged)))
107
+ self.gguf_writer.add_token_merges(merges)
108
+
109
+ special_vocab = base.gguf.SpecialVocab(self.dir_model, load_merges=False)
110
+ special_vocab.add_to_gguf(self.gguf_writer)
111
+
112
+ # SpecialVocab finds no eos_token in the tokenizer config and writes nothing,
113
+ # causing llama.cpp to fall back to the gpt2 default of token 11 (comma) as EOS.
114
+ # Explicitly write the correct values: <|endoftext|> as EOS, <sep> as EOT.
115
+ self.gguf_writer.add_eos_token_id(tokenizer.tiktoken.encode_single_token("<|endoftext|>"))
116
+ self.gguf_writer.add_eot_token_id(tokenizer.tiktoken.encode_single_token("<sep>"))
117
+
118
+ # Tiktoken/BPE typically does not add BOS; avoid shifting logits.
119
+ self.gguf_writer.add_add_bos_token(False)
120
+
121
+ def modify_tensors(
122
+ self, data_torch, name: str, bid: int | None
123
+ ) -> Iterable[tuple[str, object]]:
124
+ if name.startswith("model.language_model."):
125
+ name = "model." + name[len("model.language_model.") :]
126
+ elif name.startswith("language_model."):
127
+ name = name[len("language_model.") :]
128
+ elif name.startswith("model.vision_model.") or name.startswith("model.connector."):
129
+ return
130
+ elif name.startswith("vision_model.") or name.startswith("connector."):
131
+ return
132
+
133
+ if not (
134
+ name == "lm_head.weight"
135
+ or name == "model.embed_tokens.weight"
136
+ or name == "model.norm.weight"
137
+ or name.startswith("model.layers.")
138
+ ):
139
+ return
140
+
141
+ yield from super().modify_tensors(data_torch, name, bid)
142
+
143
+
144
+ class RekaYasa2VisionMmprojModel(base.MmprojModel):
145
+ """Vision backbone + language_projection tensors for MTMD Yasa2 clip graph."""
146
+
147
+ model_arch = base.gguf.MODEL_ARCH.MMPROJ
148
+ has_vision_encoder = True
149
+ has_audio_encoder = False
150
+
151
+ def get_vision_config(self) -> dict[str, object] | None:
152
+ cfg = self.global_config.get("vision_config")
153
+ if not isinstance(cfg, dict):
154
+ return cfg
155
+
156
+ out = dict(cfg)
157
+ depths = out.get("depths")
158
+ if isinstance(depths, list) and "num_hidden_layers" not in out:
159
+ out["num_hidden_layers"] = int(sum(int(x) for x in depths))
160
+ return out
161
+
162
+ def set_gguf_parameters(self):
163
+ self.gguf_writer.add_file_type(self.ftype)
164
+ self.gguf_writer.add_clip_has_vision_encoder(True)
165
+ self.gguf_writer.add_clip_projector_type("yasa2")
166
+ self.gguf_writer.add_vision_projection_dim(self.n_embd_text)
167
+
168
+ vcfg = self.global_config.get("vision_config", {})
169
+ self.gguf_writer.add_vision_image_size(int(vcfg.get("image_size", 512)))
170
+ self.gguf_writer.add_vision_patch_size(int(vcfg.get("patch_size", 4)))
171
+ self.gguf_writer.add_vision_embedding_length(int(vcfg.get("hidden_size", 2816)))
172
+ self.gguf_writer.add_vision_feed_forward_length(int(vcfg.get("hidden_size", 2816)))
173
+ self.gguf_writer.add_vision_block_count(0)
174
+ self.gguf_writer.add_vision_head_count(int(vcfg.get("num_attention_heads", 1) or 1))
175
+ self.gguf_writer.add_vision_attention_layernorm_eps(float(vcfg.get("layer_norm_eps", 1e-6)))
176
+ self.gguf_writer.add_vision_use_gelu(True)
177
+
178
+ mean = self.preprocessor_config.get("image_mean", [0.485, 0.456, 0.406])
179
+ std = self.preprocessor_config.get("image_std", [0.229, 0.224, 0.225])
180
+ self.gguf_writer.add_vision_image_mean(mean)
181
+ self.gguf_writer.add_vision_image_std(std)
182
+ use_vision_pos = self.global_config.get("use_vision_pos_embed", True)
183
+ if use_vision_pos:
184
+ hidden = int(vcfg.get("hidden_size", 2816))
185
+ self._yasa_vision_pos_embed = _get_2d_sincos_pos_embed_yasa2(hidden, 50, 256)
186
+ else:
187
+ self._yasa_vision_pos_embed = None
188
+ self._yasa_pos_embed_yielded = False
189
+
190
+ def _ensure_yasa_pos_embed(self) -> None:
191
+ if hasattr(self, "_yasa_vision_pos_embed"):
192
+ return
193
+ self._yasa_pos_embed_yielded = False
194
+ vcfg = self.global_config.get("vision_config", {})
195
+ use_vision_pos = self.global_config.get("use_vision_pos_embed", True)
196
+ if use_vision_pos:
197
+ hidden = int(vcfg.get("hidden_size", 2816))
198
+ self._yasa_vision_pos_embed = _get_2d_sincos_pos_embed_yasa2(hidden, 50, 256)
199
+ else:
200
+ self._yasa_vision_pos_embed = None
201
+
202
+ def modify_tensors(
203
+ self, data_torch, name: str, bid: int | None
204
+ ) -> Iterable[tuple[str, object]]:
205
+ del bid
206
+
207
+ if name.startswith("model.vision_model.") or name.startswith("model.language_projection."):
208
+ self._ensure_yasa_pos_embed()
209
+ short = name[len("model.") :]
210
+ if not self._yasa_pos_embed_yielded and self._yasa_vision_pos_embed is not None and "vision_model." in short:
211
+ self._yasa_pos_embed_yielded = True
212
+ yield "v.vision_pos_embed", torch.from_numpy(self._yasa_vision_pos_embed.copy())
213
+ out_name = self._map_mmproj_name(short)
214
+ if out_name is not None:
215
+ yield out_name, data_torch
216
+ return
217
+
218
+ if name.startswith("vision_model.") or name.startswith("language_projection."):
219
+ self._ensure_yasa_pos_embed()
220
+ if not self._yasa_pos_embed_yielded and self._yasa_vision_pos_embed is not None and "vision_model." in name:
221
+ self._yasa_pos_embed_yielded = True
222
+ yield "v.vision_pos_embed", torch.from_numpy(self._yasa_vision_pos_embed.copy())
223
+ out_name = self._map_mmproj_name(name)
224
+ if out_name is not None:
225
+ yield out_name, data_torch
226
+ return
227
+
228
+ return
229
+
230
+ @staticmethod
231
+ def _map_mmproj_name(name: str) -> str | None:
232
+ if name.startswith("language_projection."):
233
+ mapping = {
234
+ "language_projection.0.weight": "mm.0.weight",
235
+ "language_projection.0.bias": "mm.0.bias",
236
+ "language_projection.2.weight": "mm.2.weight",
237
+ "language_projection.2.bias": "mm.2.bias",
238
+ }
239
+ return mapping.get(name)
240
+
241
+ simple = {
242
+ "vision_model.backbone.embeddings.patch_embeddings.weight": "v.patch_embd.weight",
243
+ "vision_model.backbone.embeddings.patch_embeddings.bias": "v.patch_embd.bias",
244
+ "vision_model.backbone.embeddings.layernorm.weight": "v.patch_ln.weight",
245
+ "vision_model.backbone.embeddings.layernorm.bias": "v.patch_ln.bias",
246
+ "vision_model.backbone.layernorm.weight": "v.backbone_ln.weight",
247
+ "vision_model.backbone.layernorm.bias": "v.backbone_ln.bias",
248
+ }
249
+ if name in simple:
250
+ return simple[name]
251
+
252
+ m = re.match(
253
+ r"vision_model\.backbone\.encoder\.stages\.(\d+)\.downsampling_layer\.(0|1)\.(weight|bias)$",
254
+ name,
255
+ )
256
+ if m:
257
+ stage = int(m.group(1))
258
+ layer_idx = int(m.group(2))
259
+ wb = m.group(3)
260
+ if layer_idx == 0:
261
+ return f"v.stage.{stage}.down.ln.{wb}"
262
+ return f"v.stage.{stage}.down.conv.{wb}"
263
+
264
+ m = re.match(
265
+ r"vision_model\.backbone\.encoder\.stages\.(\d+)\.layers\.(\d+)\.(dwconv|layernorm|pwconv1|grn|pwconv2)\.(weight|bias)$",
266
+ name,
267
+ )
268
+ if m:
269
+ stage = int(m.group(1))
270
+ blk = int(m.group(2))
271
+ part = m.group(3)
272
+ wb = m.group(4)
273
+ part_map = {
274
+ "dwconv": "dw",
275
+ "layernorm": "ln",
276
+ "pwconv1": "pw1",
277
+ "grn": "grn",
278
+ "pwconv2": "pw2",
279
+ }
280
+ return f"v.stage.{stage}.blk.{blk}.{part_map[part]}.{wb}"
281
+
282
+ return None
283
+
284
+ def tensor_force_quant(
285
+ self, name: str, new_name: str, bid: int | None, n_dims: int
286
+ ) -> base.gguf.GGMLQuantizationType | bool:
287
+ del name, new_name, bid
288
+ if n_dims > 1:
289
+ return base.gguf.GGMLQuantizationType.F16
290
+ return False
291
+
292
+
293
+ def register_reka_architectures() -> None:
294
+ model_classes = base.ModelBase._model_classes[base.ModelType.TEXT]
295
+ mmproj_classes = base.ModelBase._model_classes[base.ModelType.MMPROJ]
296
+ for arch_name in (
297
+ "Yasa2ForConditionalGeneration",
298
+ "Yasa2Model",
299
+ "YasaCausalLM",
300
+ ):
301
+ model_classes[arch_name] = RekaYasa2TextDecoderModel
302
+ mmproj_classes["Yasa2ForConditionalGeneration"] = RekaYasa2VisionMmprojModel
303
+
304
+
305
+ def main() -> None:
306
+ register_reka_architectures()
307
+ base.main()
308
+
309
+
310
+ if __name__ == "__main__":
311
+ main()