def __init__(
            self,
            bert_model_path: str = "shibing624/parrots-chinese-roberta-wwm-ext-large",
            hubert_model_path: str = "shibing624/parrots-chinese-hubert-base",
            sovits_model_path: str = None,
            gpt_model_path: str = None,
            speaker_model_path: str = "shibing624/parrots-gpt-sovits-speaker-maimai",
            speaker_name: Optional[str] = "MaiMai",
            device: Optional[str] = None,
            half: Optional[bool] = False,
    ):
        """
        Args:
            bert_model_path: str, path to the pretrained BERT model
            hubert_model_path: str, path to the pretrained HuBERT model
            sovits_model_path: str, path to the pretrained SoVITS, if None, use the speaker_model_path
            gpt_model_path: str, path to the pretrained GPT, if None, use the speaker_model_path
            speaker_model_path: str, path to the pretrained speaker model,
                if sovits_model_path and gpt_model_path are None, use this model, else ignore it
            speaker_name: str, name of the speaker, default is "MaiMai"
            device: str, device to run on, "cuda", "cpu" or "mps"
            half: bool, use half precision instead of float32
        """
        if device is None:
            if torch.cuda.is_available():
                device = "cuda"
            elif torch.backends.mps.is_available():
                device = "cpu"
            else:
                device = "cpu"
        self.device = device
        logger.debug("Use device: {}".format(self.device))
        self.half = half
        self.dtype = torch.float16 if half else torch.float32

        # BERT
        self.bert_tokenizer = AutoTokenizer.from_pretrained(bert_model_path)
        self.bert_model = AutoModelForMaskedLM.from_pretrained(bert_model_path)
        self.bert_model = self.to_device(self.bert_model)

        # Speaker
        if bool(sovits_model_path) != bool(gpt_model_path):  # Check if only one of them is provided
            raise ValueError("sovits_model_path and gpt_model_path must be provided together")
        if sovits_model_path is None and gpt_model_path is None:
            if speaker_model_path:
                logger.info("Load pretrained parrots speaker: {}".format(speaker_model_path))
                if os.path.exists(speaker_model_path):
                    # Load from path
                    model_path = speaker_model_path
                else:
                    # Load from huggingface model hub
                    model_path = snapshot_download(speaker_model_path)
                sovits_model_path = os.path.join(model_path, speaker_name, SOVITS_MODEL_NAME)
                gpt_model_path = os.path.join(model_path, speaker_name, GPT_MODEL_NAME)
                # Set ref_wav_path to the speaker's reference wav file
                config_file = os.path.join(model_path, speaker_name, CONFIG_NAME)
                if os.path.exists(config_file):
                    ref_config = load_json_file(config_file)
                    logger.debug(f"Reference speaker config: {ref_config}, loaded from {config_file}")
                    self.ref_wav_path = os.path.join(model_path, speaker_name, REF_WAV_NAME)
                    self.ref_prompt = ref_config.get("reference_prompt", "")
                    self.ref_language = ref_config.get("reference_language", "")
                else:
                    raise ValueError(f"Config file not found: {config_file}")
            else:
                raise ValueError("sovits_model_path, gpt_model_path or speaker_model_path must be provided")

        # SoVITS
        sovits_dict = torch.load(sovits_model_path, map_location="cpu")
        hps = DictToAttrRecursive(sovits_dict["config"])
        logger.debug(f"SoVITS config: {hps}")
        vq_model = SynthesizerModel(
            spec_channels=hps.data.filter_length // 2 + 1,
            segment_size=hps.train.segment_size // hps.data.hop_length,
            n_speakers=hps.data.n_speakers,
            **hps.model,
        )
        self.sampling_rate = hps.data.sampling_rate
        self.hps = hps
        vq_model = self.to_device(vq_model)
        vq_model.eval()
        vq_model.load_state_dict(sovits_dict["weight"], strict=False)
        self.vq_model = vq_model

        # GPT
        gpt_dict = torch.load(gpt_model_path, map_location="cpu")
        config = gpt_dict["config"]
        logger.debug(f"GPT config: {config}")
        t2s_model = Text2SemanticDecoder(config)
        # Convert Text2SemanticLightningModule to Text2SemanticDecoder model
        adjusted_state_dict = self.adjust_keys(gpt_dict["weight"])
        t2s_model.load_state_dict(adjusted_state_dict)
        t2s_model = self.to_device(t2s_model)
        t2s_model.eval()
        self.t2s_model = t2s_model
        self.t2s_max_sec = config["data"]["max_sec"]
        total = sum([param.nelement() for param in t2s_model.parameters()])
        logger.debug("Number of t2s model parameter: %.2fM" % (total / 1e6))

        # HuBERT
        self.ssl_model = self.to_device(cnhubert.get_model(hubert_model_path, self.sampling_rate))

        Args:
            bert_model_path: str, path to the pretrained BERT model
            hubert_model_path: str, path to the pretrained HuBERT model
            sovits_model_path: str, path to the pretrained SoVITS, if None, use the speaker_model_path
            gpt_model_path: str, path to the pretrained GPT, if None, use the speaker_model_path
            speaker_model_path: str, path to the pretrained speaker model,
                if sovits_model_path and gpt_model_path are None, use this model, else ignore it
            speaker_name: str, name of the speaker, default is "MaiMai"
            device: str, device to run on, "cuda", "cpu" or "mps"
            half: bool, use half precision instead of float32
        
parrots/tts.py