CoolFace
Modelpublic

Felipe97/llama-cpp-compiled

sourceHugging Faceupdated 4d agoView on Hugging Face
0likes1.1kdownloads
hunyuan.py473 linesDownload Raw Back to conversion
1from __future__ import annotations2 3import json4import re5 6from pathlib import Path7from typing import Callable, Iterable, TYPE_CHECKING8 9import torch10 11if TYPE_CHECKING:12    from torch import Tensor13 14from .base import MmprojModel, ModelBase, TextModel, gguf, logger15 16from .qwen import QwenModel17 18 19@ModelBase.register("HunYuanMoEV1ForCausalLM")20@ModelBase.example("tencent/Hunyuan-A13B-Instruct")21class HunYuanMoEModel(TextModel):22    model_arch = gguf.MODEL_ARCH.HUNYUAN_MOE23 24    def set_vocab(self):25        from transformers import AutoTokenizer26        tokenizer = AutoTokenizer.from_pretrained(self.dir_model, trust_remote_code=True)27 28        # 1. Get the pre-tokenizer identifier hash29        tokpre = self.get_vocab_base_pre(tokenizer)30 31        # 2. Reverse-engineer the merges list from mergeable_ranks32        merges = []33        vocab = {}34        mergeable_ranks = tokenizer.mergeable_ranks  # ty: ignore[unresolved-attribute]35        for token, rank in mergeable_ranks.items():36            vocab[QwenModel.token_bytes_to_string(token)] = rank37            if len(token) == 1:38                continue39            merged = QwenModel.bpe(mergeable_ranks, token, max_rank=rank)40            if len(merged) == 2: # todo this is an assert in Qwen, why?41                merges.append(' '.join(map(QwenModel.token_bytes_to_string, merged)))42 43        # 3. Generate the tokens and toktypes lists44        vocab_size = self.hparams["vocab_size"]45        assert tokenizer.vocab_size == vocab_size  # ty: ignore[unresolved-attribute]46        special_tokens = tokenizer.special_tokens  # ty: ignore[unresolved-attribute]47        reverse_vocab = {id_ : encoded_tok for encoded_tok, id_ in {**vocab, **special_tokens}.items()}48        tokens: list[str] = []49        toktypes: list[int] = []50        for i in range(vocab_size):51            if i not in reverse_vocab:52                tokens.append(f"[PAD{i}]")53                toktypes.append(gguf.TokenType.UNUSED)54            else:55                token = reverse_vocab[i]56                tokens.append(token)57                if i in special_tokens.values():58                    toktypes.append(gguf.TokenType.CONTROL)59                else:60                    toktypes.append(gguf.TokenType.NORMAL)61 62        # 4. Write all vocab-related fields to the GGUF writer63        self.gguf_writer.add_tokenizer_model("gpt2")64        self.gguf_writer.add_tokenizer_pre(tokpre)65        self.gguf_writer.add_token_list(tokens)66        self.gguf_writer.add_token_types(toktypes)67        self.gguf_writer.add_token_merges(merges)68 69        # 5. Add special tokens and chat templates70        special_vocab = gguf.SpecialVocab(self.dir_model, load_merges=False)71        special_vocab.add_to_gguf(self.gguf_writer)72        # FIX for BOS token: Overwrite incorrect id read from config.json73        self.gguf_writer.add_bos_token_id(127959) # <|bos|>74 75    def set_gguf_parameters(self):76        super().set_gguf_parameters()77        hparams = self.hparams78 79        self.gguf_writer.add_expert_shared_feed_forward_length(hparams["intermediate_size"])80 81        moe_intermediate_size = hparams["moe_intermediate_size"]82        assert all(n == moe_intermediate_size[0] for n in moe_intermediate_size)83        self.gguf_writer.add_expert_feed_forward_length(moe_intermediate_size[0])84 85        moe_topk = hparams["moe_topk"]86        assert all(topk == moe_topk[0] for topk in moe_topk)87        self.gguf_writer.add_expert_used_count(moe_topk[0])88 89        moe_shared_expert = hparams["num_shared_expert"]90        assert all(n == moe_shared_expert[0] for n in moe_shared_expert)91        self.gguf_writer.add_expert_shared_count(moe_shared_expert[0])92 93        # Rope94        if self.rope_parameters.get("rope_type") == "dynamic":95            # HunYuan uses NTK Aware Alpha based scaling. Original implementation: https://www.reddit.com/r/LocalLLaMA/comments/14lz7j5/ntkaware_scaled_rope_allows_llama_models_to_have/96            # 1000 corresponds to a usable context length of 256k (https://github.com/Tencent-Hunyuan/Hunyuan-A13B/blob/main/report/Hunyuan_A13B_Technical_Report.pdf)97            alpha = self.rope_parameters.get("alpha", 1000)98            base = self.rope_parameters.get("rope_theta", 10000.0)99            dim = (hparams["hidden_size"] // hparams["num_attention_heads"]) # 128100            scaled_base = base * (alpha ** (dim / (dim - 2))) # 10000 * (1000 ** (128 / 126)) = 11158839.9251101            self.gguf_writer.add_rope_freq_base(scaled_base)102            self.gguf_writer.add_rope_scaling_type(gguf.RopeScalingType.NONE)103            self.gguf_writer.add_rope_scaling_factor(1)104            # There is no consistent way to calculate ctx from alpha, and the config is incorrectly set to 32k105            self.gguf_writer.add_rope_scaling_orig_ctx_len(256 * 1024) # 256k context length106            self.gguf_writer.add_context_length(256 * 1024) # 256k context length107 108            # if any of our assumptions about the values are wrong, something has changed and this may need to be updated109            assert alpha == 1000 and base == 10000.0 and dim == 128 and self.hparams["max_position_embeddings"] in [32 * 1024, 256 * 1024] , \110                "HunYuan dynamic RoPE scaling assumptions changed, please update the logic or context length manually"111 112    _experts: list[dict[str, Tensor]] | None = None113 114    def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:115        if name == "lm_head.weight":116            if self.hparams.get("tie_word_embeddings", False):117                logger.info("Skipping tied output layer 'lm_head.weight'")118                return119 120        if name.find("mlp.experts") != -1:121            n_experts = self.find_hparam(["num_local_experts", "num_experts"])122            assert bid is not None123 124            if self._experts is None:125                self._experts = [{} for _ in range(self.block_count)]126 127            self._experts[bid][name] = data_torch128 129            if len(self._experts[bid]) >= n_experts * 3:130                # merge the experts into a single 3d tensor131                for w_name in ["down_proj", "gate_proj", "up_proj"]:132                    datas: list[Tensor] = []133 134                    for xid in range(n_experts):135                        ename = f"model.layers.{bid}.mlp.experts.{xid}.{w_name}.weight"136                        datas.append(self._experts[bid][ename])137                        del self._experts[bid][ename]138 139                    data_torch = torch.stack(datas, dim=0)140                    merged_name = f"model.layers.{bid}.mlp.experts.{w_name}.weight"141 142                    yield from super().modify_tensors(data_torch, merged_name, bid)143                return144            else:145                return146 147        yield from super().modify_tensors(data_torch, name, bid)148 149    def prepare_tensors(self):150        super().prepare_tensors()151        if self._experts is not None:152            experts = [k for d in self._experts for k in d.keys()]153            if len(experts) > 0:154                raise ValueError(f"Unprocessed experts: {experts}")155 156 157@ModelBase.register("HunYuanDenseV1ForCausalLM")158@ModelBase.example("tencent/Hunyuan-4B-Instruct")159class HunYuanModel(TextModel):160    model_arch = gguf.MODEL_ARCH.HUNYUAN_DENSE161 162    def _get_eod_token_id(self) -> int | None:163        """Get the actual end-of-generation token from config (eod_token_id)."""164        return self.hparams.get("eod_token_id")165 166    def _get_eot_token_id(self) -> int | None:167        """Get the end-of-turn token from generation_config.json.168        This is the first entry in eos_token_id when it's a list."""169        gen_cfg_path = self.dir_model / "generation_config.json"170        if gen_cfg_path.is_file():171            with open(gen_cfg_path, encoding="utf-8") as f:172                gen_cfg = json.load(f)173            eos = gen_cfg.get("eos_token_id")174            if isinstance(eos, list) and len(eos) >= 2:175                return eos[0]176        return None177 178    def _fix_special_tokens(self):179        """Fix EOS/EOT tokens that are incorrect in upstream configs."""180        eod_id = self._get_eod_token_id()181        if eod_id is not None:182            self.gguf_writer.add_eos_token_id(eod_id)183        eot_id = self._get_eot_token_id()184        if eot_id is not None:185            self.gguf_writer.add_eot_token_id(eot_id)186 187    def set_vocab(self):188        if (self.dir_model / "tokenizer.json").is_file():189            tokens, toktypes, tokpre = self.get_vocab_base()190            self.gguf_writer.add_tokenizer_model("gpt2")191            self.gguf_writer.add_tokenizer_pre(tokpre)192            self.gguf_writer.add_token_list(tokens)193            self.gguf_writer.add_token_types(toktypes)194 195            # Some HunYuanVL variants (e.g. OCR-style configs) have pad_token_id=-1;196            # guard SpecialVocab so it doesn't try to emit an invalid pad id.197            token_types = None198            if (self.hparams.get("pad_token_id") or 0) < 0:199                token_types = ('bos', 'eos', 'unk', 'sep', 'cls', 'mask')200            special_vocab = gguf.SpecialVocab(self.dir_model, load_merges=True, special_token_types=token_types)201            special_vocab.add_to_gguf(self.gguf_writer)202            self._fix_special_tokens()203        else:204            from transformers import AutoTokenizer205            tokenizer = AutoTokenizer.from_pretrained(self.dir_model, trust_remote_code=True)206 207            # 1. Get the pre-tokenizer identifier hash208            tokpre = self.get_vocab_base_pre(tokenizer)209 210            # 2. Reverse-engineer the merges list from mergeable_ranks211            merges = []212            vocab = {}213            mergeable_ranks = tokenizer.mergeable_ranks  # ty: ignore[unresolved-attribute]214            for token, rank in mergeable_ranks.items():215                vocab[QwenModel.token_bytes_to_string(token)] = rank216                if len(token) == 1:217                    continue218                merged = QwenModel.bpe(mergeable_ranks, token, max_rank=rank)219                if len(merged) == 2:220                    merges.append(' '.join(map(QwenModel.token_bytes_to_string, merged)))221 222            # 3. Generate the tokens and toktypes lists223            vocab_size = self.hparams["vocab_size"]224            assert tokenizer.vocab_size == vocab_size  # ty: ignore[unresolved-attribute]225            special_tokens = tokenizer.special_tokens  # ty: ignore[unresolved-attribute]226            reverse_vocab = {id_ : encoded_tok for encoded_tok, id_ in {**vocab, **special_tokens}.items()}227            tokens: list[str] = []228            toktypes: list[int] = []229            for i in range(vocab_size):230                if i not in reverse_vocab:231                    tokens.append(f"[PAD{i}]")232                    toktypes.append(gguf.TokenType.UNUSED)233                else:234                    token = reverse_vocab[i]235                    tokens.append(token)236                    if i in special_tokens.values():237                        toktypes.append(gguf.TokenType.CONTROL)238                    else:239                        toktypes.append(gguf.TokenType.NORMAL)240 241            # 4. Write all vocab-related fields to the GGUF writer242            self.gguf_writer.add_tokenizer_model("gpt2")243            self.gguf_writer.add_tokenizer_pre(tokpre)244            self.gguf_writer.add_token_list(tokens)245            self.gguf_writer.add_token_types(toktypes)246            self.gguf_writer.add_token_merges(merges)247 248            # 5. Add special tokens and chat templates249            special_vocab = gguf.SpecialVocab(self.dir_model, load_merges=False)250            special_vocab.add_to_gguf(self.gguf_writer)251            # FIX for BOS token: Overwrite incorrect id read from config.json252            if self.hparams['hidden_size'] == 4096:253                self.gguf_writer.add_bos_token_id(127958) # only for 7b dense, fix <|bos|> token254            self._fix_special_tokens()255 256    def set_gguf_parameters(self):257        # Some HunYuanVL variants set num_experts=1 (not real MoE);258        # prevent the parent class from emitting expert_count metadata in that case.259        saved_num_experts = self.hparams.pop("num_experts", None)260        super().set_gguf_parameters()261        if saved_num_experts is not None and saved_num_experts > 1:262            self.hparams["num_experts"] = saved_num_experts263        hparams = self.hparams264 265        # Rope266        if self.rope_parameters.get("rope_type") in ("dynamic", "xdrope"):267            # HunYuan uses NTK Aware Alpha based scaling. Original implementation: https://www.reddit.com/r/LocalLLaMA/comments/14lz7j5/ntkaware_scaled_rope_allows_llama_models_to_have/268            # 1000 corresponds to a usable context length of 256k (https://github.com/Tencent-Hunyuan/Hunyuan-A13B/blob/main/report/Hunyuan_A13B_Technical_Report.pdf)269            alpha = self.rope_parameters.get("alpha", 50)270            base = self.rope_parameters.get("rope_theta", 10000.0)271            dim = hparams["head_dim"]272            scaled_base = base * (alpha ** (dim / (dim - 2)))273            self.gguf_writer.add_rope_freq_base(scaled_base)274            self.gguf_writer.add_rope_scaling_type(gguf.RopeScalingType.NONE)275            self.gguf_writer.add_rope_scaling_factor(1)276            if self.rope_parameters.get("rope_type") == "dynamic":277                # There is no consistent way to calculate ctx from alpha, and the config is incorrectly set to 32k278                self.gguf_writer.add_rope_scaling_orig_ctx_len(256 * 1024) # 256k context length279                self.gguf_writer.add_context_length(256 * 1024) # 256k context length280 281                # if any of our assumptions about the values are wrong, something has changed and this may need to be updated282                assert base == 10000.0 and self.hparams["max_position_embeddings"] in [32 * 1024, 256 * 1024] , \283                    "HunYuan dynamic RoPE scaling assumptions changed, please update the logic or context length manually"284 285    def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:286        if name == "lm_head.weight":287            if self.hparams.get("tie_word_embeddings", False):288                logger.info("Skipping tied output layer 'lm_head.weight'")289                return290 291        yield from super().modify_tensors(data_torch, name, bid)292 293 294@ModelBase.register("HunYuanVLForConditionalGeneration")295@ModelBase.example("tencent/HunyuanOCR")296class HunyuanVLVisionModel(MmprojModel):297    def __init__(self, *args, **kwargs):298        super().__init__(*args, **kwargs)299        assert self.hparams_vision is not None300        # HunyuanVL uses max_image_size instead of image_size301        if "image_size" not in self.hparams_vision:302            self.hparams_vision["image_size"] = self.hparams_vision.get("max_image_size", 2048)303 304    def set_gguf_parameters(self):305        super().set_gguf_parameters()306        assert self.hparams_vision is not None307        vcfg = self.hparams_vision308        self.gguf_writer.add_clip_projector_type(gguf.VisionProjectorType.HUNYUANVL)309        self.gguf_writer.add_vision_use_gelu(True)310        self.gguf_writer.add_vision_attention_layernorm_eps(vcfg.get("rms_norm_eps", 1e-5))311        self.gguf_writer.add_vision_spatial_merge_size(vcfg.get("spatial_merge_size", 2))312        self.gguf_writer.add_vision_min_pixels(int(self.preprocessor_config["min_pixels"]))313        self.gguf_writer.add_vision_max_pixels(int(self.preprocessor_config["max_pixels"]))314 315    @classmethod316    def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:317        name, gen = item318 319        if not name.startswith("vit."):320            return None321 322        return super().filter_tensors(item)323 324    def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:325        # strip CLS token (row 0) from position embeddings so resize_position_embeddings works326        if "position_embedding" in name:327            data_torch = data_torch[1:]  # [n_patches+1, n_embd] -> [n_patches, n_embd]328        yield from super().modify_tensors(data_torch, name, bid)329 330    def tensor_force_quant(self, name, new_name, bid, n_dims):331        # force conv weights to F32 or F16 to avoid BF16 IM2COL issues on Metal332        # HunyuanVL emit the ViT -> LLM projection as mm.0/mm.2.333        if ("mm.0." in new_name or "mm.2." in new_name) and new_name.endswith(".weight"):334            return gguf.GGMLQuantizationType.F16 if self.ftype == gguf.LlamaFileType.MOSTLY_F16 else gguf.GGMLQuantizationType.F32335        return super().tensor_force_quant(name, new_name, bid, n_dims)336 337 338@ModelBase.register("HunYuanVLForConditionalGeneration")339@ModelBase.example("tencent/HunyuanOCR")340class HunyuanVLTextModel(HunYuanModel):341    model_arch = gguf.MODEL_ARCH.HUNYUAN_VL342 343    def __init__(self, dir_model: Path, *args, **kwargs):344        super().__init__(dir_model, *args, **kwargs)345        # transformers 5.13.0 encodes HunyuanVL XD-RoPE as dynamic + mrope_section.346        # Normalize it to avoid the HunYuan dynamic-RoPE context assertion.347        if self.rope_parameters.get("rope_type") == "dynamic" and "mrope_section" in self.rope_parameters:348            self.rope_parameters["rope_type"] = "xdrope"349            self.rope_parameters["type"] = "xdrope"350            self.rope_parameters["xdrope_section"] = list(self.rope_parameters["mrope_section"])351 352    def set_gguf_parameters(self):353        super().set_gguf_parameters()354 355        # XD-RoPE metadata for the HunyuanVL;356        if self.rope_parameters.get("rope_type") != "xdrope":357            return358 359        self.gguf_writer.add_rope_freq_base(float(self.rope_parameters["rope_theta"]))360        self.gguf_writer.add_rope_scaling_alpha(float(self.rope_parameters["alpha"]))361        self.gguf_writer.add_rope_scaling_type(gguf.RopeScalingType.NONE)362        self.gguf_writer.add_rope_scaling_factor(float(self.rope_parameters.get("factor", 1)))363 364        ctx_len = int(self.hparams["max_position_embeddings"])365        self.gguf_writer.add_rope_scaling_orig_ctx_len(ctx_len)366        self.gguf_writer.add_context_length(ctx_len)367 368        self.gguf_writer.add_rope_dimension_sections(list(self.rope_parameters["xdrope_section"]))369 370 371@ModelBase.register("HYV3ForCausalLM")372@ModelBase.example("tencent/Hy3")373class HYV3Model(TextModel):374    model_arch = gguf.MODEL_ARCH.HY_V3375    supports_mtp_export = True376 377    # Trunk layer count, stashed before indexing so the classmethod378    # filter_tensors can identify the appended MTP block(s) (mirrors379    # Step35Model).380    _n_main_layers: int | None = None381 382    def __init__(self, *args, **kwargs):383        super().__init__(*args, **kwargs)384        # NextN/MTP layers are appended past num_hidden_layers; extend the385        # tensor map so the MTP block's tensors resolve to blk.<n>.* names.386        n_nextn = int(self.hparams.get("num_nextn_predict_layers", 0))387        if n_nextn > 0 and not self.no_mtp:388            self.block_count += n_nextn389            self.tensor_map = gguf.get_tensor_name_map(self.model_arch, self.block_count)390 391    def index_tensors(self, remote_hf_model_id: str | None = None):392        type(self)._n_main_layers = self.hparams["num_hidden_layers"]393        return super().index_tensors(remote_hf_model_id=remote_hf_model_id)394 395    def set_vocab(self):396        self._set_vocab_gpt2()397 398    def set_gguf_parameters(self):399        super().set_gguf_parameters()400        self.gguf_writer.add_expert_feed_forward_length(self.hparams["moe_intermediate_size"])401        self.gguf_writer.add_expert_shared_feed_forward_length(402            self.hparams["moe_intermediate_size"] * self.hparams.get("num_shared_experts", 1)403        )404        self.gguf_writer.add_expert_weights_norm(self.hparams.get("route_norm", True))405        self.gguf_writer.add_expert_weights_scale(float(self.hparams.get("router_scaling_factor", 1.0)))406        # sigmoid router with expert selection bias407        self.gguf_writer.add_expert_gating_func(gguf.ExpertGatingFuncType.SIGMOID)408 409        n_nextn = int(self.hparams.get("num_nextn_predict_layers", 0))410        if n_nextn > 0 and not self.no_mtp:411            self.gguf_writer.add_nextn_predict_layers(n_nextn)412 413    @classmethod414    def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:415        if (titem := super().filter_tensors(item)) is None:416            return None417        name, gen = titem418 419        # HY V3 appends the MTP block(s) past num_hidden_layers.420        assert cls._n_main_layers is not None421        is_mtp = (m := re.match(r"model\.layers\.(\d+)\.", name)) is not None and int(m.group(1)) >= cls._n_main_layers422 423        # --no-mtp: drop the appended MTP block(s) entirely.424        if is_mtp and cls.no_mtp:425            return None426        # --mtp: keep ONLY MTP-block tensors plus the shared embeddings/norm/427        # lm_head (so the resulting GGUF carries just the draft head).428        if cls.mtp_only and not is_mtp and name not in (429            "model.embed_tokens.weight", "model.norm.weight", "lm_head.weight",430        ):431            return None432 433        # The MTP block's trailing final_layernorm (applied after the decoder434        # block, before the shared LM head) maps to nextn.shared_head_norm.435        if is_mtp:436            name = name.replace(".final_layernorm.", ".shared_head.norm.")437 438        return name, gen439 440    _experts: list[dict[str, Tensor]] | None = None441 442    def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:443        # merge the per-expert tensors into stacked 3d tensors444        if name.startswith("model.layers.") and ".mlp.experts." in name:445            n_experts = self.find_hparam(["num_local_experts", "num_experts"])446            assert bid is not None447 448            if self._experts is None:449                self._experts = [{} for _ in range(self.block_count)]450 451            self._experts[bid][name] = data_torch452 453            if len(self._experts[bid]) >= n_experts * 3:454                for w_name in ("down_proj", "gate_proj", "up_proj"):455                    datas: list[Tensor] = []456                    for xid in range(n_experts):457                        ename = f"model.layers.{bid}.mlp.experts.{xid}.{w_name}.weight"458                        datas.append(self._experts[bid][ename])459                        del self._experts[bid][ename]460 461                    merged = torch.stack(datas, dim=0)462                    yield from super().modify_tensors(merged, f"model.layers.{bid}.mlp.experts.{w_name}.weight", bid)463            return464 465        yield from super().modify_tensors(data_torch, name, bid)466 467    def prepare_tensors(self):468        super().prepare_tensors()469        if self._experts is not None:470            experts = [k for d in self._experts for k in d.keys()]471            if experts:472                raise ValueError(f"Unprocessed experts: {experts}")473