Download base_runtime.py from laion/Humaneness-Voice-Large-NotebookLM-Edition: direct link, hf CLI and curl.
- Browser
- Download file 7.46 kB
-
https://huggingface.co/laion/Humaneness-Voice-Large-NotebookLM-Edition/resolve/main/base_runtime.py
- Command line
-
hf download hf://laion/Humaneness-Voice-Large-NotebookLM-Edition/base_runtime.py
-
curl -L -o base_runtime.py https://huggingface.co/laion/Humaneness-Voice-Large-NotebookLM-Edition/resolve/main/base_runtime.py
7.46 kB
| """Humaneness Voice Large 1.3 Alpha: portable full-model inference. | |
| Use a downloaded model directory, or a Hugging Face model ID. This file has no | |
| JUPITER-specific runtime dependency. Twelve MOSS-v2 codebooks,48kHz mono. | |
| """ | |
| import argparse | |
| import json | |
| from pathlib import Path | |
| LANGUAGES = {'en': 'English', 'de': 'German', 'nl': 'Dutch', 'fr': 'French', 'es': 'Spanish'} | |
| def load_model(directory, device='cuda:0'): | |
| import torch | |
| from transformers import AutoModel, AutoTokenizer | |
| from transformers.dynamic_module_utils import get_class_from_dynamic_module | |
| model = AutoModel.from_pretrained(str(directory), trust_remote_code=True, | |
| dtype=torch.bfloat16, attn_implementation='sdpa').to(device).eval() | |
| Processor = get_class_from_dynamic_module('processing_moss_tts.MossTTSLocalProcessor', str(directory)) | |
| tokenizer = AutoTokenizer.from_pretrained(str(directory), trust_remote_code=True) | |
| proc = Processor(tokenizer=tokenizer, audio_tokenizer=None, model_config=model.config) | |
| return model, proc | |
| def load_codec(proc, path, device='cuda:0'): | |
| from transformers import AutoModel | |
| codec = AutoModel.from_pretrained(str(path), trust_remote_code=True, codec_weight_dtype='fp32') | |
| if hasattr(codec, 'set_compute_dtype'): | |
| codec.set_compute_dtype('bf16') | |
| proc.audio_tokenizer = codec.to(device).eval() | |
| return proc | |
| def build_user(proc, *, text, prompt, frames, language, reference=None, alias=None): | |
| if not text.strip() or not prompt.strip() or language not in LANGUAGES: | |
| raise ValueError('Nonempty literal text, English directions and a supported language required') | |
| if reference is not None and (reference.ndim != 2 or reference.shape[1] != 12 | |
| or not 63 <= len(reference) <= 187): | |
| raise ValueError('Reference codes must contain12codebooks over5.04–14.96seconds') | |
| if alias is not None: | |
| if reference is not None: | |
| raise ValueError('Choose either a reference recording or a registered voice name, not both') | |
| catalog = json.loads(Path(__file__).with_name('voices.json').read_text()) | |
| names = {v['speaker_alias'] for v in catalog['voices']} | |
| if alias not in names: | |
| raise ValueError('Unknown voice name; see VOICE_ALIASES.md and voices.json') | |
| user = proc.build_user_message(text=text, instruction=prompt, tokens=int(frames), | |
| language=LANGUAGES[language], reference=[reference] if reference is not None else None) | |
| if alias is not None: | |
| # Exact alias representation used by training/legacy/acting_data.py. | |
| # It is not an invented prefix in the spoken text or a new special token. | |
| marker = '- Reference(s):\nNone\n' | |
| if user['content'].count(marker) != 1 or user.get('audio_codes_list'): | |
| raise RuntimeError('Processor wrapper no longer matches the trained voice-name format') | |
| user['content'] = user['content'].replace(marker, f'- Reference(s):\nSpeaker: {alias}\n', 1) | |
| return user | |
| def generate(model, proc, user, *, seed=777, max_frames=625, device='cuda:0'): | |
| import torch | |
| batch = proc([[user]], mode='generation') | |
| torch.manual_seed(seed) | |
| torch.cuda.manual_seed_all(seed) | |
| with torch.inference_mode(), torch.autocast('cuda', dtype=torch.bfloat16): | |
| output = model.generate(input_ids=batch['input_ids'].to(device), | |
| attention_mask=batch['attention_mask'].to(device), max_new_frames=max_frames, | |
| do_sample=True, audio_temperature=1., audio_top_p=.95, audio_top_k=50, | |
| audio_repetition_penalty=1., use_kv_cache=True) | |
| prefix, sequence = output[0] | |
| if prefix != 0 or sequence.ndim != 2 or sequence.shape[1] != 13: | |
| raise RuntimeError('Unexpected MOSS frame layout') | |
| codes = sequence[1:, 1:] | |
| codes = codes[(codes[:, 0] >= 0) & (codes[:, 0] != model.config.audio_pad_token_id)] | |
| if len(codes) < 2 or int(codes.min()) < 0 or int(codes.max()) > 1023: | |
| raise RuntimeError('Too few frames or corrupt codec indices') | |
| return codes.cpu() | |
| def decode(proc, codes, device='cuda:0'): | |
| import numpy as np | |
| import torch | |
| with torch.inference_mode(): | |
| wave = proc.decode_audio_codes([codes.to(device)], return_stereo=False)[0] | |
| wave = wave.float().cpu().numpy().reshape(-1) | |
| rate = int(proc.model_config.sampling_rate) | |
| if not np.isfinite(wave).all() or abs(len(wave) / len(codes) - rate / 12.5) > .01 * rate / 12.5: | |
| raise RuntimeError('Codec frame-rate/sample-rate canary failed') | |
| return wave, rate | |
| def main(): | |
| p = argparse.ArgumentParser(description=__doc__) | |
| p.add_argument('--model', default='laion/Humaneness-Voice-Large-1.3-Alpha') | |
| p.add_argument('--codec', default='OpenMOSS-Team/MOSS-Audio-Tokenizer-v2') | |
| p.add_argument('--text', required=True, help='Literal spoken words, without directing tags') | |
| p.add_argument('--prompt', help='GENERAL/SCRIPT directions; literal \\n accepted') | |
| p.add_argument('--prompt-file', type=Path) | |
| p.add_argument('--language', choices=LANGUAGES, default='en') | |
| voice = p.add_mutually_exclusive_group() | |
| voice.add_argument('--reference-wav', type=Path) | |
| voice.add_argument('--voice-name', help='Registered fictional voice alias; see VOICE_ALIASES.md') | |
| p.add_argument('--seconds', type=float, default=8.) | |
| p.add_argument('--seed', type=int, default=777) | |
| p.add_argument('--output', type=Path, required=True) | |
| args = p.parse_args() | |
| if bool(args.prompt) == bool(args.prompt_file): | |
| p.error('Supply exactly one of --prompt or --prompt-file') | |
| if not 1 <= args.seconds <= 60: | |
| p.error('Target seconds must be between1and60') | |
| import numpy as np | |
| import soundfile as sf | |
| import torch | |
| if not torch.cuda.is_available(): | |
| raise RuntimeError('The tested path requires a CUDA GPU') | |
| device = 'cuda:0' | |
| model, proc = load_model(args.model, device) | |
| load_codec(proc, args.codec, device) | |
| reference = None | |
| if args.reference_wav: | |
| wave, sr = sf.read(args.reference_wav, dtype='float32', always_2d=True) | |
| if not np.isfinite(wave).all() or len(wave) / sr < 5.04: | |
| raise ValueError('Use at least5.04seconds of clean single-speaker reference speech') | |
| # Never reinstate37frames/2.96s. Crop waveform BEFORE encoding. | |
| wave = wave[:int(sr * 14.96)] | |
| with torch.inference_mode(): | |
| reference = proc.encode_audios_from_wav([torch.from_numpy(wave.T.copy())], int(sr), n_vq=12)[0][:187] | |
| prompt = args.prompt_file.read_text() if args.prompt_file else args.prompt.replace('\\n', '\n') | |
| frames = round(args.seconds * 12.5) | |
| user = build_user(proc, text=args.text, prompt=prompt, frames=frames, | |
| language=args.language, reference=reference, alias=args.voice_name) | |
| codes = generate(model, proc, user, seed=args.seed, max_frames=min(1000, frames + 190)) | |
| wave, sr = decode(proc, codes) | |
| args.output.parent.mkdir(parents=True, exist_ok=True) | |
| if args.output.exists(): | |
| raise FileExistsError('Refusing to overwrite ' + str(args.output)) | |
| sf.write(args.output, wave, sr, subtype='PCM_16') | |
| print(json.dumps({'output': str(args.output), 'duration_s': len(wave) / sr, | |
| 'requested_s': args.seconds, 'frames': len(codes), 'language': args.language, | |
| 'reference_frames': len(reference) if reference is not None else 0, | |
| 'voice_name': args.voice_name, 'seed': args.seed})) | |
| if __name__ == '__main__': | |
| main() | |