Brunobkr/llama.cpp_AlgMor24_github
ΩFFFΣLLIa • llama.cpp • AlgMor24 ██████╗ ███████╗███████╗███████╗██╗ ██╗ ██╗ █████╗ ██╔═══██╗██╔════╝██╔════╝██╔════╝██║ ██║ ██║██╔══██╗ ██║ ██║█████╗ █████╗ █████╗ ██║ ██║ ██║███████║ ██║ ██║██╔══╝ ██╔══╝ ██╔══╝ ██║ ██║ ██║██╔══██║ ╚██████╔╝██║ ██║ ███████╗███████╗███████╗██║██║ ██║ ╚═════╝ ╚═╝ ╚═╝ ╚══════╝╚══════╝╚══════╝╚═╝╚═╝ ╚═╝ High-Performance LLM / VLM Inference & Autonomous Agentic Ecosystem… See the full description on the dataset page: https://huggingface.co/datasets/Brunobkr/llama.cpp_AlgMor24_github.
03.1k
1from __future__ import annotations2 3import json4import re5 6from typing import Any, Callable, Iterable, TYPE_CHECKING7 8import torch9 10if TYPE_CHECKING:11 from torch import Tensor12 13from .base import MmprojModel, ModelBase, TextModel, gguf14 15 16@ModelBase.register("MiMoV2FlashForCausalLM", "MiMoV2ForCausalLM")17class MimoV2Model(TextModel):18 model_arch = gguf.MODEL_ARCH.MIMO219 20 # MiMo V2-Flash, V2.5 and V2.5-Pro all ship 3 trained MTP layers under model.mtp.layers.{0,1,2}.21 # The HF config does not expose the count, so it's hardcoded to match the count found in the safetensors.22 _n_nextn = 323 24 def __init__(self, *args, **kwargs):25 super().__init__(*args, **kwargs)26 27 self.block_count = self.hparams["num_hidden_layers"] + self._n_nextn28 self.tensor_map = gguf.get_tensor_name_map(self.model_arch, self.block_count)29 30 @staticmethod31 def _tp_aware_qkv_dequant(weight: Tensor, scale_inv: Tensor,32 n_q: int, n_kv: int, hd: int, vhd: int,33 bs: int = 128) -> Tensor:34 # MiMo-V2.5 (TP=4) and V2.5-Pro (TP=8) ship qkv_proj sharded across TP35 # ranks; per rank, rows are stacked as [Q_per | K_per | V_per].36 # weight_scale_inv has ceil(rows_per_rank/bs) block-rows per rank (last37 # may extend past rows_per_rank with phantom rows not in the weight).38 # Naive repeat_interleave aligns rank 0 only and mis-applies scales to39 # later ranks once rows_per_rank isn't a multiple of bs.40 # Re-group the per-rank [Q_per|K_per|V_per] rows into a single fused41 # [Q | K | V] tensor matching the un-sharded original layout.42 q_size = n_q * hd43 k_size = n_kv * hd44 v_size = n_kv * vhd45 total_rows = q_size + k_size + v_size46 if weight.shape[0] != total_rows:47 raise ValueError(f"qkv_proj weight rows {weight.shape[0]} != q+k+v {total_rows}")48 49 # detect TP from scale_inv block count, descending order so larger matches first50 tp = None51 for cand in (8, 4):52 if total_rows % cand != 0:53 continue54 rpr = total_rows // cand55 bpr = (rpr + bs - 1) // bs56 if scale_inv.shape[0] == cand * bpr:57 tp = cand58 break59 if tp is None:60 raise ValueError(61 f"qkv_proj: cannot detect TP - scale_inv rows {scale_inv.shape[0]}, "62 f"q+k+v {total_rows}")63 64 q_per = q_size // tp65 k_per = k_size // tp66 v_per = v_size // tp67 rows_per_rank = q_per + k_per + v_per68 blocks_per_rank = (rows_per_rank + bs - 1) // bs69 70 scale_inv = scale_inv.float()71 # per-row scale-row index: rank * blocks_per_rank + (rr_in_rank // bs)72 row_idx = torch.arange(total_rows)73 rr = row_idx % rows_per_rank74 rank = row_idx // rows_per_rank75 scale_row_idx = rank * blocks_per_rank + (rr // bs)76 # gather: (total_rows, n_col_blocks)77 scale_per_row_block = scale_inv[scale_row_idx]78 # expand col-blocks -> cols: each block-col covers `bs` weight cols79 scale_full = scale_per_row_block.repeat_interleave(bs, dim=1)80 # crop to weight col count (in case last col-block isn't full)81 scale_full = scale_full[:, : weight.shape[1]]82 dequant = weight.float() * scale_full83 84 if tp == 1:85 return dequant86 87 # Re-group per-rank [Q_per|K_per|V_per] rows into unified [Q | K | V]88 qs, ks, vs = [], [], []89 for r in range(tp):90 base = r * rows_per_rank91 qs.append(dequant[base : base + q_per])92 ks.append(dequant[base + q_per : base + q_per + k_per])93 vs.append(dequant[base + q_per + k_per : base + rows_per_rank])94 return torch.cat(qs + ks + vs, dim=0)95 96 def dequant_model(self):97 # Capture raw FP8 (weight, scale_inv) lambdas for qkv_proj BEFORE super98 # rewrites them with the existing dequant. Replace super's lambda after99 # it runs so scale_inv removal still happens via the standard path.100 qkv_overrides: dict[str, tuple[Callable, Callable, int]] = {}101 qc = self.hparams.get("quantization_config")102 if isinstance(qc, dict) and qc.get("quant_method") == "fp8":103 pat = re.compile(r"^model\.layers\.(\d+)\.self_attn\.qkv_proj\.weight_scale_inv$")104 for name in list(self.model_tensors.keys()):105 m = pat.match(name)106 if not m:107 continue108 weight_name = name.removesuffix("_scale_inv")109 if weight_name not in self.model_tensors:110 continue111 qkv_overrides[weight_name] = (112 self.model_tensors[weight_name],113 self.model_tensors[name],114 int(m.group(1)),115 )116 117 super().dequant_model()118 119 if not qkv_overrides:120 return121 122 n_q = self.hparams["num_attention_heads"]123 hd = self.hparams["head_dim"]124 vhd = self.hparams["v_head_dim"]125 hybrid = self.hparams["hybrid_layer_pattern"]126 n_layer_text = self.hparams["num_hidden_layers"]127 for weight_name, (w_fn, s_fn, bid) in qkv_overrides.items():128 # MTP layers (bid >= n_layer_text) use SWA-style attention dims129 is_swa = True if bid >= n_layer_text else hybrid[bid] == 1130 n_kv = self.hparams["swa_num_key_value_heads" if is_swa else "num_key_value_heads"]131 self.model_tensors[weight_name] = (132 lambda w_fn=w_fn, s_fn=s_fn, n_q=n_q, n_kv=n_kv, hd=hd, vhd=vhd:133 MimoV2Model._tp_aware_qkv_dequant(w_fn(), s_fn(), n_q, n_kv, hd, vhd)134 )135 136 def set_gguf_parameters(self):137 super().set_gguf_parameters()138 139 assert self.hparams["swa_head_dim"] == self.hparams["head_dim"]140 assert self.hparams["swa_num_attention_heads"] == self.hparams["num_attention_heads"]141 assert self.hparams["swa_v_head_dim"] == self.hparams["v_head_dim"]142 assert self.hparams["topk_method"] == "noaux_tc"143 144 n_head_kv = self.hparams["num_key_value_heads"]145 n_head_kv_swa = self.hparams["swa_num_key_value_heads"]146 # Extend the per-layer pattern with SWA entries for the MTP blocks so the147 # runtime arrays (sized to extended block_count) are fully populated.148 hybrid = list(self.hparams["hybrid_layer_pattern"]) + [1] * self._n_nextn149 n_head_kv_arr = [n_head_kv_swa if use_swa == 1 else n_head_kv for use_swa in hybrid]150 self.gguf_writer.add_head_count_kv(n_head_kv_arr)151 152 self.gguf_writer.add_sliding_window(self.hparams["sliding_window"])153 self.gguf_writer.add_sliding_window_pattern(hybrid)154 self.gguf_writer.add_value_length(self.hparams["v_head_dim"])155 self.gguf_writer.add_expert_count(self.hparams["n_routed_experts"])156 self.gguf_writer.add_expert_feed_forward_length(self.hparams["moe_intermediate_size"])157 158 rope_dim = int(self.hparams["head_dim"] * self.rope_parameters["partial_rotary_factor"])159 self.gguf_writer.add_rope_dimension_count(rope_dim)160 161 self.gguf_writer.add_layer_norm_rms_eps(self.hparams.get("layernorm_epsilon", 1e-5))162 163 v_scale = self.hparams.get("attention_value_scale")164 if v_scale is not None:165 self.gguf_writer.add_attn_value_scale(float(v_scale))166 167 self.gguf_writer.add_nextn_predict_layers(self._n_nextn)168 169 _experts: list[dict[str, Tensor]] | None = None170 171 @classmethod172 def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:173 name, gen = item174 175 if "attention_sink" in name and not name.endswith(".weight"):176 name += ".weight"177 178 return super().filter_tensors((name, gen))179 180 def modify_tensors(self, data_torch, name, bid):181 # Remap MTP/NextN tensors to additional layer slots so the standard tensor map handles them.182 # HF: model.mtp.layers.{i}.foo -> model.layers.{n_layer_text + i}.foo183 m = re.match(r"^model\.mtp\.layers\.(\d+)\.(.*)$", name)184 if m is not None:185 mtp_idx = int(m.group(1))186 assert mtp_idx < self._n_nextn, f"MTP layer index {mtp_idx} >= _n_nextn ({self._n_nextn})"187 rest = m.group(2)188 n_layer_text = self.hparams["num_hidden_layers"]189 new_bid = n_layer_text + mtp_idx190 name = f"model.layers.{new_bid}.{rest}"191 bid = new_bid192 193 # process the experts separately194 if name.find("mlp.experts") != -1:195 n_experts = self.hparams["n_routed_experts"]196 assert bid is not None197 198 if self._experts is None:199 self._experts = [{} for _ in range(self.block_count)]200 201 self._experts[bid][name] = data_torch202 203 if len(self._experts[bid]) >= n_experts * 3:204 # merge the experts into a single 3d tensor205 for w_name in ["gate_proj", "up_proj", "down_proj"]:206 datas: list[Tensor] = []207 208 for xid in range(n_experts):209 ename_to_retrieve = f"model.layers.{bid}.mlp.experts.{xid}.{w_name}.weight"210 datas.append(self._experts[bid][ename_to_retrieve])211 del self._experts[bid][ename_to_retrieve]212 213 data_torch = torch.stack(datas, dim=0)214 merged_name = f"model.layers.{bid}.mlp.experts.{w_name}.weight"215 216 yield from super().modify_tensors(data_torch, merged_name, bid)217 return218 else:219 return220 yield from super().modify_tensors(data_torch, name, bid)221 222 def prepare_tensors(self):223 super().prepare_tensors()224 225 if self._experts is not None:226 # flatten `list[dict[str, Tensor]]` into `list[str]`227 experts = [k for d in self._experts for k in d.keys()]228 if len(experts) > 0:229 raise ValueError(f"Unprocessed experts: {experts}")230 231 232@ModelBase.register("MiMoV2ForCausalLM")233class MiMoV2VisionAudioModel(MmprojModel):234 has_audio_encoder = True235 236 _audio_tok_hparams: dict[str, Any] | None = None237 _rvq_codebook_sizes: list[int] | None = None238 _code_embd: dict[int, Tensor] | None = None239 240 def __init__(self, *args, **kwargs):241 super().__init__(*args, **kwargs)242 assert self.hparams_vision is not None243 hp = self.hparams_vision244 245 hp["image_size"] = hp.get("image_size", 560)246 hp["num_attention_heads"] = hp.get("num_heads", 32)247 hp["num_hidden_layers"] = hp.get("depth", 28)248 249 self.n_q_heads = int(hp["num_heads"])250 self.num_kv_heads = int(hp.get("num_key_value_heads", 8))251 self.head_dim = int(hp.get("qk_channels", 64))252 self.spatial_merge_size = int(hp["spatial_merge_size"])253 # MiMoV2 vision RMSNorm: HF uses getattr(config, "rms_norm_eps", 1e-6) and the254 # field is absent from MiMo-V2.5's vision_config255 self.rms_norm_eps = float(hp.get("rms_norm_eps", 1e-6))256 257 # fullatt_block_indexes are also reflected in vit_window_attn_types as -1258 self.fullatt_block_indexes = list(hp.get("fullatt_block_indexes") or [])259 self.vit_window_attn_types = list(hp.get("vit_window_attn_types") or [])260 self.visual_token_window_size = int(hp.get("visual_token_window_size", -1))261 self.use_sink = bool(hp.get("use_sink", False))262 263 def get_audio_config(self) -> dict[str, Any] | None:264 if self._audio_tok_hparams is None:265 path = self.dir_model / "audio_tokenizer" / "config.json"266 with open(path, "r", encoding="utf-8") as f:267 cfg = json.load(f)268 # aliases so MmprojModel.find_aparam() / n_block_keys can resolve them269 cfg["hidden_size"] = cfg["d_model"]270 cfg["intermediate_size"] = cfg["encoder_ffn_dim"]271 cfg["num_attention_heads"] = cfg["encoder_attention_heads"]272 self._audio_tok_hparams = cfg273 return self._audio_tok_hparams274 275 def set_gguf_parameters(self):276 super().set_gguf_parameters()277 278 self.gguf_writer.add_clip_vision_projector_type(gguf.VisionProjectorType.MIMOVL)279 self.gguf_writer.add_vision_use_silu(True)280 self.gguf_writer.add_vision_head_count_kv(self.num_kv_heads)281 self.gguf_writer.add_vision_spatial_merge_size(self.spatial_merge_size)282 self.gguf_writer.add_uint32(gguf.Keys.ClipVision.WINDOW_SIZE, self.visual_token_window_size)283 self.gguf_writer.add_vision_wa_pattern_mode(self.vit_window_attn_types)284 self.gguf_writer.add_vision_attention_layernorm_eps(self.rms_norm_eps)285 self.gguf_writer.add_vision_min_pixels(int(self.preprocessor_config["min_pixels"]))286 self.gguf_writer.add_vision_max_pixels(int(self.preprocessor_config["max_pixels"]))287 288 assert self.hparams_audio is not None289 self.gguf_writer.add_clip_audio_projector_type(gguf.VisionProjectorType.MIMO_AUDIO)290 self.gguf_writer.add_audio_num_mel_bins(self.hparams_audio["n_mels"])291 self.gguf_writer.add_audio_attention_layernorm_eps(self.hparams_audio.get("layer_norm_eps", 1e-5))292 293 assert self._rvq_codebook_sizes is not None294 self.gguf_writer.add_audio_rvq_num_quantizers(len(self._rvq_codebook_sizes))295 self.gguf_writer.add_audio_rvq_codebook_size(self._rvq_codebook_sizes)296 297 n_layer = self.hparams_audio["encoder_layers"]298 swa_per_block = self.hparams_audio.get("swa_per_block", 1)299 if self.hparams_audio.get("hybrid_attention") and swa_per_block > 1:300 wa_pattern = [0 if i % swa_per_block < swa_per_block - 1 else -1 for i in range(n_layer)]301 else:302 wa_pattern = [-1] * n_layer303 self.gguf_writer.add_audio_wa_pattern_mode(wa_pattern)304 self.gguf_writer.add_audio_window_size(int(self.hparams_audio["encoder_attn_window_size"][0]))305 306 audio_cfg = self.global_config["audio_config"]307 self.gguf_writer.add_audio_local_block_count(int(audio_cfg["input_local_layers"]))308 self.gguf_writer.add_audio_local_group_size(int(audio_cfg["group_size"]))309 310 def tensor_force_quant(self, name, new_name, bid, n_dims):311 # for audio encoder: keep codebook in F32312 if new_name in (313 gguf.TENSOR_NAMES[gguf.MODEL_TENSOR.A_ENC_RVQ_CODEBOOK] + ".weight",314 gguf.TENSOR_NAMES[gguf.MODEL_TENSOR.A_MM_CODE_EMBD] + ".weight",315 ):316 return gguf.GGMLQuantizationType.F32317 if ("encoder.conv" in name or "encoder.down_sample_layer" in name) and name.endswith(".weight"):318 return gguf.GGMLQuantizationType.F32319 return super().tensor_force_quant(name, new_name, bid, n_dims)320 321 @classmethod322 def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:323 name, _ = item324 if name.startswith("visual.") or name.startswith("speech_embeddings.") or name.startswith("audio_encoder."):325 return super().filter_tensors(item)326 return None327 328 def modify_tensors(self, data_torch, name, bid):329 # Conv3D patch embed: split along the temporal axis (kt=2) into two Conv2D330 # weights that the existing qwen2vl-style two-Conv2D path consumes.331 if name == "visual.patch_embed.proj.weight":332 _, _, kt, _, _ = data_torch.shape333 if kt != 2:334 raise ValueError(f"unexpected temporal_patch_size: {kt}")335 embd_name = gguf.TENSOR_NAMES[gguf.MODEL_TENSOR.V_ENC_EMBD_PATCH]336 yield (embd_name + ".weight", data_torch[:, :, 0, ...])337 yield (embd_name + ".weight.1", data_torch[:, :, 1, ...])338 return339 340 if m := re.match(r"^speech_embeddings\.(\d+)\.weight$", name):341 if self._code_embd is None:342 self._code_embd = {}343 self._code_embd[int(m.group(1))] = data_torch344 345 n_channels = int(self.global_config["audio_config"]["audio_channels"])346 if len(self._code_embd) < n_channels:347 return348 merged = torch.stack([self._code_embd.pop(i) for i in range(n_channels)], dim=0)349 yield (self.format_tensor_name(gguf.MODEL_TENSOR.A_MM_CODE_EMBD), merged)350 return351 352 if "conv1.bias" in name or "conv2.bias" in name:353 # transpose conv1/conv2 bias so it broadcasts against [n_frames, C_out, 1]354 data_torch = data_torch.unsqueeze(-1)355 356 if name == "audio_encoder.projection.mlp.0.weight":357 yield (self.format_tensor_name(gguf.MODEL_TENSOR.A_MMPROJ, 1), data_torch)358 return359 if name == "audio_encoder.projection.mlp.2.weight":360 yield (self.format_tensor_name(gguf.MODEL_TENSOR.A_MMPROJ, 2), data_torch)361 return362 363 yield from super().modify_tensors(data_torch, name, bid)364 365 def generate_extra_tensors(self) -> Iterable[tuple[str, Tensor]]:366 # note: audio encoder is in its own subdir "audio_tokenizer"367 from safetensors.torch import load_file368 369 tok_dir = self.dir_model / "audio_tokenizer"370 state_dict = load_file(tok_dir / "model.safetensors")371 372 codebook_re = re.compile(r"^encoder\.quantizer\.vq\.layers\.(\d+)\._codebook\.embed$")373 codebooks: dict[int, Tensor] = {}374 375 # EMA/training-only RVQ buffers - not needed for inference (nearest-codebook376 # lookup only reads "_codebook.embed")377 skip_suffixes = (378 "_codebook.cluster_size",379 "_codebook.embed_avg",380 "_codebook.inited",381 )382 for name, tensor in state_dict.items():383 if name.endswith(skip_suffixes):384 continue385 if m := codebook_re.match(name):386 codebooks[int(m.group(1))] = tensor387 continue388 yield name, tensor389 390 # gather codebooks and merge into 3D tensor, similar to MoE MLP tensors391 n_q = len(codebooks)392 ordered = [codebooks[i] for i in range(n_q)]393 self._rvq_codebook_sizes = [int(cb.shape[0]) for cb in ordered]394 max_bins = max(self._rvq_codebook_sizes)395 dim = ordered[0].shape[1]396 merged = ordered[0].new_zeros(n_q, max_bins, dim)397 for i, cb in enumerate(ordered):398 merged[i, : cb.shape[0], :] = cb399 400 yield (self.format_tensor_name(gguf.MODEL_TENSOR.A_ENC_RVQ_CODEBOOK), merged)401 