| """Convert RVC WebUI's fairseq HuBERT checkpoint to Transformers format.""" |
|
|
| from __future__ import annotations |
|
|
| import argparse |
| from pathlib import Path |
|
|
| import torch |
| from transformers import HubertConfig, HubertModel |
|
|
|
|
| VOICECHANGER_ROOT = Path(__file__).resolve().parents[1] |
|
|
|
|
| def resolve_path(path: str) -> Path: |
| value = Path(path) |
| if value.is_absolute(): |
| return value |
| return VOICECHANGER_ROOT / value |
|
|
|
|
| def map_state_dict(fairseq_state: dict[str, torch.Tensor], hf_state: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: |
| mapped: dict[str, torch.Tensor] = {} |
|
|
| def put(hf_key: str, fs_key: str) -> None: |
| value = fairseq_state[fs_key] |
| if tuple(value.shape) != tuple(hf_state[hf_key].shape): |
| raise ValueError( |
| f"Shape mismatch: {fs_key} {tuple(value.shape)} -> " |
| f"{hf_key} {tuple(hf_state[hf_key].shape)}" |
| ) |
| mapped[hf_key] = value |
|
|
| if "masked_spec_embed" in hf_state and "mask_emb" in fairseq_state: |
| put("masked_spec_embed", "mask_emb") |
|
|
| for idx in range(7): |
| put(f"feature_extractor.conv_layers.{idx}.conv.weight", f"feature_extractor.conv_layers.{idx}.0.weight") |
| put("feature_extractor.conv_layers.0.layer_norm.weight", "feature_extractor.conv_layers.0.2.weight") |
| put("feature_extractor.conv_layers.0.layer_norm.bias", "feature_extractor.conv_layers.0.2.bias") |
|
|
| put("feature_projection.layer_norm.weight", "layer_norm.weight") |
| put("feature_projection.layer_norm.bias", "layer_norm.bias") |
| put("feature_projection.projection.weight", "post_extract_proj.weight") |
| put("feature_projection.projection.bias", "post_extract_proj.bias") |
|
|
| put("encoder.pos_conv_embed.conv.bias", "encoder.pos_conv.0.bias") |
| put("encoder.pos_conv_embed.conv.parametrizations.weight.original0", "encoder.pos_conv.0.weight_g") |
| put("encoder.pos_conv_embed.conv.parametrizations.weight.original1", "encoder.pos_conv.0.weight_v") |
| put("encoder.layer_norm.weight", "encoder.layer_norm.weight") |
| put("encoder.layer_norm.bias", "encoder.layer_norm.bias") |
|
|
| for idx in range(12): |
| prefix_hf = f"encoder.layers.{idx}" |
| prefix_fs = f"encoder.layers.{idx}" |
| for proj in ("k", "v", "q", "out"): |
| put(f"{prefix_hf}.attention.{proj}_proj.weight", f"{prefix_fs}.self_attn.{proj}_proj.weight") |
| put(f"{prefix_hf}.attention.{proj}_proj.bias", f"{prefix_fs}.self_attn.{proj}_proj.bias") |
| put(f"{prefix_hf}.layer_norm.weight", f"{prefix_fs}.self_attn_layer_norm.weight") |
| put(f"{prefix_hf}.layer_norm.bias", f"{prefix_fs}.self_attn_layer_norm.bias") |
| put(f"{prefix_hf}.feed_forward.intermediate_dense.weight", f"{prefix_fs}.fc1.weight") |
| put(f"{prefix_hf}.feed_forward.intermediate_dense.bias", f"{prefix_fs}.fc1.bias") |
| put(f"{prefix_hf}.feed_forward.output_dense.weight", f"{prefix_fs}.fc2.weight") |
| put(f"{prefix_hf}.feed_forward.output_dense.bias", f"{prefix_fs}.fc2.bias") |
| put(f"{prefix_hf}.final_layer_norm.weight", f"{prefix_fs}.final_layer_norm.weight") |
| put(f"{prefix_hf}.final_layer_norm.bias", f"{prefix_fs}.final_layer_norm.bias") |
|
|
| missing = sorted(set(hf_state) - set(mapped)) |
| if missing: |
| raise ValueError(f"Unmapped Transformers keys: {missing}") |
| return mapped |
|
|
|
|
| def convert(checkpoint_path: Path, output_dir: Path) -> None: |
| checkpoint = torch.load(checkpoint_path, map_location="cpu", weights_only=False) |
| fairseq_state = checkpoint["model"] |
| config = HubertConfig( |
| hidden_dropout=0.0, |
| activation_dropout=0.0, |
| attention_dropout=0.0, |
| feat_proj_dropout=0.0, |
| final_dropout=0.0, |
| mask_time_prob=0.0, |
| layerdrop=0.0, |
| ) |
| model = HubertModel(config) |
| mapped = map_state_dict(fairseq_state, model.state_dict()) |
| model.load_state_dict(mapped, strict=True) |
| model.eval() |
| output_dir.mkdir(parents=True, exist_ok=True) |
| model.save_pretrained(output_dir) |
|
|
|
|
| def main() -> None: |
| parser = argparse.ArgumentParser() |
| parser.add_argument("--input", default="assets/hubert/hubert_base.pt") |
| parser.add_argument("--output", default="assets/hubert/hubert_base_transformers") |
| args = parser.parse_args() |
| convert(resolve_path(args.input), resolve_path(args.output)) |
| print(f"saved Transformers HuBERT to {args.output}") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|