"""
    #################################################################################################################################################################################################
    # This code library is an adaptation of the original Transformers and was designed, developed and programmed by Sapiens Technology®.                                                            #
    # Any alteration and/or disclosure of this code without prior authorization is strictly prohibited and is subject to legal action that will be forwarded by the Sapiens Technology® legal team. #
    # This set of algorithms aims to download, train, fine-tune and/or infer large language models from various sources and slopes.                                                                 #
    #################################################################################################################################################################################################
"""
import json
import os
import shutil
from argparse import ArgumentParser
from collections import OrderedDict
import torch
from nemo.collections.common.tokenizers.huggingface.auto_tokenizer import AutoTokenizer
from nemo.collections.nlp.models.language_modeling.megatron_gpt_model import MegatronGPTModel
from nemo.collections.nlp.parts.nlp_overrides import NLPDDPStrategy
from nemo.utils import logging
from pytorch_lightning import Trainer
from sapiens_transformers import LlamaTokenizer, PreTrainedTokenizerFast
from sapiens_transformers.convert_slow_tokenizer import LlamaConverter
def get_args():
    parser = ArgumentParser()
    parser.add_argument("--input_name_or_path", type=str, default=None, required=True, help="Path to .nemo file or extracted folder")
    parser.add_argument("--output_path", type=str, default=None, required=False, help="Path to HF .bin file")
    parser.add_argument("--hf_input_path", type=str, default=None, help="A HF model path, " "e.g. a folder containing https://huggingface.co/nvidia/Minitron-8B-Base")
    parser.add_argument("--hf_output_path", type=str, default=None, help="Output HF model path, " "with the same format as above but user's own weights")
    parser.add_argument("--precision", type=str, default=None, help="Precision of output weights." "Defaults to precision of the input nemo weights (model.cfg.trainer.precision)")
    parser.add_argument("--cpu-only", action="store_true", help="Load model in cpu only. Useful if the model cannot fit in GPU memory, " "but this option makes the conversion script significantly slower.")
    args = parser.parse_args()
    return args
def convert_hf_config(nemo_config, tokenizer, vocab_size, dtype, hf_output_path, hf_url="nvidia/Minitron-8B-Base"):
    NEMO_ACT2HF = {'squared-relu': 'relu2', 'fast-swiglu': 'silu'}
    DTYPE2HF = {torch.bfloat16: "bfloat16", torch.float16: "float16", torch.float32: "float32"}
    hf_config = {"_name_or_path": hf_url, "architectures": ["NemotronForCausalLM"], "bos_token_id": tokenizer.bos_id, "eos_token_id": tokenizer.eos_id, "hidden_act": NEMO_ACT2HF[nemo_config.activation],
    "hidden_size": nemo_config.hidden_size, "initializer_range": nemo_config.init_method_std, "intermediate_size": nemo_config.ffn_hidden_size, "max_position_embeddings": nemo_config.max_position_embeddings,
    "model_type": "nemotron", "num_attention_heads": nemo_config.num_attention_heads, "num_hidden_layers": nemo_config.num_layers, "num_key_value_heads": nemo_config.get("num_query_groups", nemo_config.num_attention_heads),
    "norm_eps": nemo_config.layernorm_epsilon, "rope_theta": nemo_config.get("rotary_base", 10000), "partial_rotary_factor": nemo_config.get("rotary_percentage", 1.0),
    "tie_word_embeddings": False, "torch_dtype": DTYPE2HF[dtype], "sapiens_transformers_version": "4.32.0.dev0", "use_cache": True, "vocab_size": vocab_size}
    if nemo_config.kv_channels is not None: hf_config["kv_channels"] = nemo_config.kv_channels
    json.dump(hf_config, open(f"{hf_output_path}/config.json", "w"), indent=2)
def convert(input_nemo_file, output_hf_file, precision=None, cpu_only=False) -> None:
    dummy_trainer = Trainer(devices=1, accelerator="cpu", strategy=NLPDDPStrategy())
    model_config = MegatronGPTModel.restore_from(input_nemo_file, trainer=dummy_trainer, return_config=True)
    model_config.tensor_model_parallel_size = 1
    model_config.pipeline_model_parallel_size = 1
    model_config.sequence_parallel = False
    model_config.transformer_engine = True
    if cpu_only:
        map_location = torch.device("cpu")
        model_config.use_cpu_initialization = True
        model_config.dist_ckpt_load_on_device = False
    else: map_location = None
    if cpu_only: logging.info("******** Loading model on CPU. This will take a significant amount of time.")
    model = MegatronGPTModel.restore_from(input_nemo_file, trainer=dummy_trainer, override_config_path=model_config, map_location=map_location)
    vocab_size = model.padded_vocab_size
    if precision is None: precision = model.cfg.precision
    if precision in [32, "32"]: dtype = torch.float32
    elif precision in [16, "16", "16-mixed"]: dtype = torch.float16
    elif precision in ["bf16", "bf16-mixed"]: dtype = torch.bfloat16
    else:
        logging.warning(f"Precision string {precision} is not recognized, falling back to fp32")
        dtype = torch.float32
    logging.info(f"Using precision {dtype}")
    def param_to_weights(param): return param.to(dtype)
    checkpoint = OrderedDict()
    hidden_size = model.cfg.hidden_size
    head_num = model.cfg.num_attention_heads
    num_layers = model.cfg.num_layers
    ffn_hidden_size = model.cfg.ffn_hidden_size
    num_query_groups = model.cfg.get("num_query_groups", head_num)
    if num_query_groups is None: num_query_groups = head_num
    heads_per_group = head_num // num_query_groups
    qkv_total_dim = head_num + 2 * num_query_groups
    embed_weight = model.state_dict()["model.embedding.word_embeddings.weight"]
    embed_weights_base_name = "model.embed_tokens.weight"
    checkpoint[embed_weights_base_name] = param_to_weights(embed_weight)
    for l in range(int(num_layers)):
        print(f"converting layer {l}")
        qkv_weights = model.state_dict()[f"model.decoder.layers.{l}.self_attention.linear_qkv.weight"]
        qkv_weights = qkv_weights.reshape([qkv_total_dim, -1, hidden_size])
        q_slice = torch.cat([torch.arange((heads_per_group + 2) * i, (heads_per_group + 2) * i + heads_per_group) for i in range(num_query_groups)])
        k_slice = torch.arange(heads_per_group, qkv_total_dim, (heads_per_group + 2))
        v_slice = torch.arange(heads_per_group + 1, qkv_total_dim, (heads_per_group + 2))
        q_weights_base_name = f"model.layers.{l}.self_attn.q_proj.weight"
        k_weights_base_name = f"model.layers.{l}.self_attn.k_proj.weight"
        v_weights_base_name = f"model.layers.{l}.self_attn.v_proj.weight"
        checkpoint[q_weights_base_name] = param_to_weights(qkv_weights[q_slice].reshape(-1, hidden_size))
        checkpoint[k_weights_base_name] = param_to_weights(qkv_weights[k_slice].reshape(-1, hidden_size))
        checkpoint[v_weights_base_name] = param_to_weights(qkv_weights[v_slice].reshape(-1, hidden_size))
        o_weight = model.state_dict()[f"model.decoder.layers.{l}.self_attention.linear_proj.weight"]
        o_weight_base_name = f"model.layers.{l}.self_attn.o_proj.weight"
        checkpoint[o_weight_base_name] = param_to_weights(o_weight)
        mlp_weights = model.state_dict()[f"model.decoder.layers.{l}.mlp.linear_fc1.weight"]
        mlp_up_proj_weight = model.state_dict()[f"model.decoder.layers.{l}.mlp.linear_fc2.weight"]
        if mlp_weights.shape[0] != mlp_up_proj_weight.shape[1]:
            logging.warning("Gated projection layers detected in NeMo checkpoint. Currently Nemotron HF does not support gated MLP.")
            assert mlp_weights.shape[0] == 2 * mlp_up_proj_weight.shape[1]
            mlp_down_proj_weight = mlp_weights[:ffn_hidden_size, :]
            mlp_gate_proj_weight = mlp_weights[ffn_hidden_size:, :]
            mlp_down_proj_base_name = f"model.layers.{l}.mlp.gate_proj.weight"
            mlp_gate_proj_base_name = f"model.layers.{l}.mlp.up_proj.weight"
            checkpoint[mlp_down_proj_base_name] = param_to_weights(mlp_down_proj_weight)
            checkpoint[mlp_gate_proj_base_name] = param_to_weights(mlp_gate_proj_weight)
        else:
            mlp_down_proj_weight = mlp_weights
            mlp_down_proj_base_name = f"model.layers.{l}.mlp.up_proj.weight"
            checkpoint[mlp_down_proj_base_name] = param_to_weights(mlp_down_proj_weight)
        mlp_up_proj_base_name = f"model.layers.{l}.mlp.down_proj.weight"
        checkpoint[mlp_up_proj_base_name] = param_to_weights(mlp_up_proj_weight)
        input_ln_weight = model.state_dict()[f"model.decoder.layers.{l}.self_attention.linear_qkv.layer_norm_weight"]
        input_ln_base_name = f"model.layers.{l}.input_layernorm.weight"
        checkpoint[input_ln_base_name] = param_to_weights(input_ln_weight)
        if (model.state_dict().get(f"model.decoder.layers.{l}.self_attention.linear_qkv.layer_norm_bias", None) is not None):
            input_ln_bias = model.state_dict()[f"model.decoder.layers.{l}.self_attention.linear_qkv.layer_norm_bias"]
            input_ln_bias_name = f"model.layers.{l}.input_layernorm.bias"
            checkpoint[input_ln_bias_name] = param_to_weights(input_ln_bias)
        post_attn_ln_weight = model.state_dict()[f"model.decoder.layers.{l}.mlp.linear_fc1.layer_norm_weight"]
        post_attn_ln_base_name = f"model.layers.{l}.post_attention_layernorm.weight"
        checkpoint[post_attn_ln_base_name] = param_to_weights(post_attn_ln_weight)
        if model.state_dict().get(f"model.decoder.layers.{l}.mlp.linear_fc1.layer_norm_bias", None) is not None:
            post_attn_ln_bias = model.state_dict()[f"model.decoder.layers.{l}.mlp.linear_fc1.layer_norm_bias"]
            post_attn_ln_bias_name = f"model.layers.{l}.post_attention_layernorm.bias"
            checkpoint[post_attn_ln_bias_name] = param_to_weights(post_attn_ln_bias)
        print(f"done layer {l}")
    final_ln_weight = model.state_dict()["model.decoder.final_layernorm.weight"]
    final_ln_base_name = "model.norm.weight"
    checkpoint[final_ln_base_name] = param_to_weights(final_ln_weight)
    if model.state_dict().get("model.decoder.final_layernorm.bias", None) is not None:
        final_ln_bias = model.state_dict()["model.decoder.final_layernorm.bias"]
        final_ln_bias_name = "model.norm.bias"
        checkpoint[final_ln_bias_name] = param_to_weights(final_ln_bias)
    output_layer_weight = model.state_dict()["model.output_layer.weight"]
    output_layer_base_name = "lm_head.weight"
    checkpoint[output_layer_base_name] = param_to_weights(output_layer_weight)
    os.makedirs(os.path.dirname(output_hf_file), exist_ok=True)
    torch.save(checkpoint, output_hf_file)
    logging.info(f"Weights saved to {output_hf_file}")
    return model_config, model.tokenizer, dtype, vocab_size
def extract_nemotron_tokenizer(nemo_file, model_config, output_hf_path, nemo_tokenizer):
    tokenizer_cfg = model_config.tokenizer
    if tokenizer_cfg.library == "sentencepiece":
        tokenizer_fn = tokenizer_cfg.model[5:]
        output_tokenizer = f"{output_hf_path}/tokenizer.model"
        if nemo_file.endswith(".nemo"):
            import tarfile
            archive = tarfile.open(nemo_file, "r")
            tokenizer_filename = "./" + tokenizer_fn
            archive.extract(tokenizer_filename, output_hf_path)
            archive.close()
            os.rename(f"{output_hf_path}/{tokenizer_fn}", output_tokenizer)
        elif os.path.isdir(nemo_file): shutil.copy(f"{nemo_file}/{tokenizer_fn}", output_tokenizer)
        tokenizer = LlamaTokenizer.from_pretrained(output_hf_path, legacy=False)
        tokenizer = PreTrainedTokenizerFast(tokenizer_object=LlamaConverter(tokenizer).converted(), model_input_names=["input_ids", "token_type_ids"])
        tokenizer.save_pretrained(output_hf_path)
        logging.info(f"Setencepiece tokenizer has been saved to {output_tokenizer}")
    elif isinstance(nemo_tokenizer, AutoTokenizer):
        nemo_tokenizer.tokenizer.save_pretrained(output_hf_path)
        logging.info(f"HF AutoTokenizer has been saved to {output_hf_path}")
    else: raise ValueError(f"Unsupported tokenizer type: library: {tokenizer_cfg.library}, type: {tokenizer_cfg.type}")
if __name__ == "__main__":
    args = get_args()
    if not args.hf_output_path: assert args.output_path is not None, "Need to provide either output_path or hf_output_path"
    else:
        args.output_path = f"{args.hf_output_path}/pytorch_model.bin"
        logging.info(f"weight will be saved to {args.output_path}")
    nemo_config, nemo_tokenizer, dtype, vocab_size = convert(args.input_name_or_path, args.output_path, precision=args.precision, cpu_only=args.cpu_only)
    if args.hf_input_path and args.hf_output_path:
        convert_hf_config(nemo_config, nemo_tokenizer, vocab_size, dtype, args.hf_output_path, args.hf_input_path)
        extract_nemotron_tokenizer(args.input_name_or_path, nemo_config, args.hf_output_path, nemo_tokenizer)
    else:
        logging.info("`hf_input_path` and/or `hf_output_path` not provided, not generating full HF model.")
        logging.info(f".bin file is saved to {args.output_path}")
"""
    #################################################################################################################################################################################################
    # This code library is an adaptation of the original Transformers and was designed, developed and programmed by Sapiens Technology®.                                                            #
    # Any alteration and/or disclosure of this code without prior authorization is strictly prohibited and is subject to legal action that will be forwarded by the Sapiens Technology® legal team. #
    # This set of algorithms aims to download, train, fine-tune and/or infer large language models from various sources and slopes.                                                                 #
    #################################################################################################################################################################################################
"""
