voice_audio_multi/tests/ming3_audio/python_talker_reference.py
Fabio c36510c21e
Some checks failed
Linux Build / Linux cpu (push) Has been cancelled
Linux Build / Linux cpu-clang (push) Has been cancelled
Linux Build / Linux vulkan (push) Has been cancelled
mac-build / macOS CPU (push) Has been cancelled
Nix Build / Nix (cpu) (push) Has been cancelled
Nix Build / Nix (vulkan) (push) Has been cancelled
windows-build / Windows CPU (push) Has been cancelled
Initial import: audio.cpp with Orange Pi documentation
2026-11-04 19:05:22 +01:00

188 lines
9.7 KiB
Python

#!/usr/bin/env python3
"""Run the official Talker; capture only its AR input and final latent/waveform boundaries."""
import argparse
from concurrent.futures import ThreadPoolExecutor
import json
import os
from pathlib import Path
import sys
import threading
import time
import numpy as np
import soundfile as sf
import torch
from safetensors.torch import load_file, save_file
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "tools/audiocpp_cli"))
from run_audiocpp_cli_path_tests import ResourceMonitor
class TalkerCapture:
def __init__(self, model):
self.model = model
self.tensors = {}
self.prompts = []
self.encoder_layer_calls = []
self.original = model.generate_latents
self.decode = model.audio_vae.decode
self.inputs = model.input_embeddings
def __enter__(self):
self.encoder_hooks = [layer.register_forward_hook(
lambda module, inputs, output, index=index: self.encoder_layer_calls.append(index))
for index, layer in enumerate(self.model.audio_vae.encoder.encoder.layers)]
self.encoder_boundary_hook = self.model.audio_vae.encoder.register_forward_hook(
lambda module, inputs, output: self.tensors.update({
"reference.encoder_parameters": output[0].detach().cpu().contiguous(),
"reference.waveform": output[1].detach().cpu().contiguous()}))
self.speaker_hook = self.model.spk_head.register_forward_pre_hook(
lambda module, inputs: self.tensors.update({
"reference.speaker_features": inputs[0].detach().cpu().contiguous()}))
def prompt(*args, **kwargs):
name = f"chunk.{len(self.prompts)}"
handle = self.model.model.get_input_embeddings().register_forward_pre_hook(
lambda module, inputs: self.tensors.update({
name + ".prompt_ids": inputs[0].detach().to(torch.int32).cpu().contiguous()}))
try:
result = self.inputs(*args, **kwargs)
finally:
handle.remove()
self.prompts.append({"text": args[0], "user_prompt": args[1], "instruction": args[2],
"reference_text": args[4]})
return result
def generate(embeddings, reference, max_steps, cfg, sigma, temperature):
name = f"chunk.{len(self.prompts) - 1}"
offset_words = torch.cuda.default_generators[0].get_offset()
self.tensors[name + ".initial_rng_offset_words"] = torch.tensor(offset_words, dtype=torch.int64)
self.tensors[name + ".initial_rng_offset_blocks"] = torch.tensor(offset_words // 4, dtype=torch.int64)
self.tensors[name + ".prompt_embeddings"] = embeddings.detach().cpu().contiguous()
if reference is not None:
self.tensors[name + ".reference_latents"] = reference.detach().cpu().contiguous()
result = self.original(embeddings, reference, max_steps, cfg, sigma, temperature)
self.tensors[name + ".generated_latents"] = result.detach().cpu().contiguous()
return result
def decode(latents, *args, **kwargs):
result = self.decode(latents, *args, **kwargs)
self.tensors[f"chunk.{len(self.prompts) - 1}.waveform"] = result[0].detach().cpu().contiguous()
return result
self.model.input_embeddings = prompt
self.model.generate_latents = generate
self.model.audio_vae.decode = decode
return self
def __exit__(self, *exc):
for handle in self.encoder_hooks:
handle.remove()
self.encoder_boundary_hook.remove()
self.speaker_hook.remove()
self.model.generate_latents = self.original
self.model.audio_vae.decode = self.decode
self.model.input_embeddings = self.inputs
@torch.inference_mode()
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--model-dir", type=Path, required=True)
parser.add_argument("--requests", type=Path, required=True)
parser.add_argument("--output-dir", type=Path, required=True)
parser.add_argument("--capture", action="store_true")
parser.add_argument("--decode-fixture", type=Path,
help="Teacher-force captured AR output through the official VAE only")
parser.add_argument("--vae-attention", choices=("eager", "sdpa"), default="eager")
parser.add_argument("--log", action="store_true", required=True)
args = parser.parse_args()
sys.path.insert(0, str(args.model_dir.resolve()))
from talker import MingTalker
model = MingTalker.from_pretrained(args.model_dir / "talker", dtype=torch.bfloat16, device="cuda:0")
model.audio_vae.decoder.decoder.set_attn_implementation(args.vae_attention)
if args.capture and args.decode_fixture:
model.audio_vae.decoder.upsampling.upsampler.register_forward_hook(
lambda module, inputs, output: print(json.dumps({"vae.upsampler.input_dtype": str(inputs[0].dtype),
"vae.upsampler.output_dtype": str(output.dtype)}), flush=True))
model.audio_vae.decoder.decoder.register_forward_pre_hook(
lambda module, inputs, kwargs: print(json.dumps({"vae.decoder.input_dtype": str(kwargs["inputs_embeds"].dtype),
"vae.rope.inv_freq_dtype": str(module.rotary_emb.inv_freq.dtype)}), flush=True),
with_kwargs=True)
args.output_dir.mkdir(parents=True, exist_ok=True)
manifest = {"torch": torch.__version__, "gpu": torch.cuda.get_device_name(0), "requests": []}
requests = json.loads(args.requests.read_text(encoding="utf-8"))
if isinstance(requests, dict):
requests = requests["requests"]
for request in requests:
request = {**request, **request.get("options", {})}
name = request.get("name", request.get("id"))
if not name:
raise ValueError("Each request requires a name or id")
reference = None
reference_audio = request.get("reference_audio", request.get("voice_ref"))
if reference_audio:
samples, sample_rate = sf.read(reference_audio, dtype="float32", always_2d=True)
reference = {"samples": np.mean(samples, axis=1), "sample_rate": sample_rate}
monitor = ResourceMonitor(os.getpid(), 0, 20)
stop = threading.Event()
def sample_resources():
while not stop.is_set():
monitor.sample()
stop.wait(0.02)
capture = TalkerCapture(model)
torch.cuda.synchronize()
torch.cuda.reset_peak_memory_stats()
with ThreadPoolExecutor(max_workers=1) as executor:
job = executor.submit(sample_resources)
try:
start = time.perf_counter()
if args.capture and not args.decode_fixture:
capture.__enter__()
if args.decode_fixture:
tensors = load_file(args.decode_fixture, device="cuda:0")
with torch.autocast(device_type="cuda", dtype=torch.bfloat16):
waveform = model.audio_vae.decode(tensors["chunk.0.generated_latents"], use_cache=False, last_chunk=True)[0]
result = {"audio": waveform.detach().float().cpu(), "sr": model.audio_vae.config.sample_rate}
else:
result = model.synthesize(
request["text"], ip_name=request.get("voice_id", "US_James"), reference=reference,
prompt_text=request.get("reference_text"), language=request.get("language"),
max_steps=request.get("max_audio_tokens", 1000), seed=request.get("seed", 42),
cfg=request.get("guidance_scale", 2), sigma=request.get("sigma", 0.25),
temperature=request.get("temperature", 0))
torch.cuda.synchronize()
wall_ms = (time.perf_counter() - start) * 1000
finally:
if args.capture and not args.decode_fixture:
capture.__exit__()
stop.set()
job.result()
monitor.close()
waveform = result["audio"].reshape(-1).numpy()
sample_rate = int(result["sr"])
sf.write(args.output_dir / f"{name}.wav", waveform, sample_rate, subtype="FLOAT")
if args.capture and not args.decode_fixture:
save_file(capture.tensors, str(args.output_dir / f"{name}.safetensors"))
duration = len(waveform) / sample_rate
record = {**request, "session.wall_ms": wall_ms, "audio_duration_sec": duration,
"rtf": wall_ms / (duration * 1000), "instrumented": args.capture,
"peak_allocated_vram_bytes": torch.cuda.max_memory_allocated(),
"peak_reserved_vram_bytes": torch.cuda.max_memory_reserved(),
**monitor.metrics.to_json(), "decode_only": bool(args.decode_fixture),
"vae_attention": args.vae_attention, "prompts": capture.prompts,
"reference_encoder_modules": len(model.audio_vae.encoder.encoder.layers),
"reference_encoder_config_layers": model.audio_vae.encoder.encoder.config.num_hidden_layers,
"reference_encoder_shared_config": model.audio_vae.encoder.encoder.config is model.audio_vae.encoder.aggregator.config,
"reference_encoder_executed_layers": capture.encoder_layer_calls}
manifest["requests"].append(record)
print(json.dumps(record, ensure_ascii=False), flush=True)
(args.output_dir / "metrics.json").write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
if __name__ == "__main__":
main()