File size: 7,069 Bytes
969573c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | #!/usr/bin/env python3
"""AISHELL-4 批量推理(多 GPU 并行,兼容 MOSS-Speaker-RoPE 和 MOSS-Transcribe-Diarize)
用法示例:
python aishell4_eval.py \
--ckpt /wangshuai/moss/MOSS-Transcribe-Diarize/output_lr1e-4_spkw5/checkpoint-1207 \
--output_dir /wangshuai/moss/MOSS-Transcribe-Diarize/aishell4_eval_output_lr1e4_spkw5 \
--gpus 0 --workers_per_gpu 4
"""
import os, sys, time, json, re, argparse, multiprocessing as mp
from pathlib import Path
module_root = Path("/wangshuai/moss/MOSS_Speaker-RoPE/moss_speaker_rope").parent
if str(module_root) not in sys.path:
sys.path.insert(0, str(module_root))
AUDIO_DIR = "/F00120240032/Aishell-4/test/test/wav"
PROCESSOR_ID = "/wangshuai/moss/MOSS-Transcribe-Diarize/MOSS-Transcribe-Diarize"
# ─── model detection ─────────────────────────────────────────────────────────
def detect_model_type(ckpt: str) -> str:
"""Return "speaker_rope" or "moss". """
cj = json.loads((Path(ckpt) / "config.json").read_text())
mt = cj.get("model_type", "")
if "speaker_rope" in mt:
return "speaker_rope"
return "moss"
def load_model(ckpt: str, device, dtype, model_type: str):
if model_type == "speaker_rope":
sys.path.insert(0, "/taoye/lhy/czy/moss/MOSS_Speaker-RoPE")
from moss_speaker_rope.configuration_moss_speaker_rope import MossSpeakerRopeConfig
from moss_speaker_rope.modeling_moss_speaker_rope import MossSpeakerRopeForConditionalGeneration
cj = json.loads((Path(ckpt) / "config.json").read_text())
for k in ("architectures", "auto_map", "model_type", "dtype", "transformers_version"):
cj.pop(k, None)
cfg = MossSpeakerRopeConfig(**cj)
cfg._attn_implementation = "sdpa"
cfg.text_config._attn_implementation = "sdpa"
model = MossSpeakerRopeForConditionalGeneration.from_pretrained(
ckpt, config=cfg, trust_remote_code=True, dtype=dtype).to(device).eval()
model.model.speaker_encoder.float()
return model, True # has_speaker=True
else:
from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained(
ckpt, trust_remote_code=True, dtype="auto").to(dtype=dtype).to(device).eval()
return model, False # has_speaker=False
# ─── worker ──────────────────────────────────────────────────────────────────
def run_worker(gpu_id, ckpt, file_list, output_dir, max_new_tokens):
os.environ["CUDA_VISIBLE_DEVICES"] = str(gpu_id)
import torch, soundfile as sf, soxr
from moss_speaker_rope.inference_utils import build_transcription_messages
device = torch.device("cuda:0"); dtype = torch.bfloat16
model_type = detect_model_type(ckpt)
model, has_speaker = load_model(ckpt, device, dtype, model_type)
# Processor: use the one that matches the model type
if model_type == "speaker_rope":
from moss_speaker_rope.processing_moss_speaker_rope import MossSpeakerRopeProcessor
processor = MossSpeakerRopeProcessor.from_pretrained(PROCESSOR_ID, trust_remote_code=True)
else:
from moss_transcribe_diarize.processing_moss_transcribe_diarize import MossTranscribeDiarizeProcessor
processor = MossTranscribeDiarizeProcessor.from_pretrained(PROCESSOR_ID, trust_remote_code=True)
out_dir = Path(output_dir); out_dir.mkdir(parents=True, exist_ok=True)
for fname in file_list:
fpath = Path(AUDIO_DIR) / f"{fname}.wav"
dur = sf.info(str(fpath)).duration
audio, sr = sf.read(str(fpath), dtype="float32", always_2d=True)
audio = audio.mean(axis=1)
sfr = int(processor.feature_extractor.sampling_rate)
if sr != sfr:
audio = soxr.resample(audio, sr, sfr)
msgs = build_transcription_messages(str(fpath))
text = processor.apply_chat_template(msgs, tokenize=False, add_generation_prompt=True)
batch = processor(text=text, audio=[audio], max_length=81920, return_tensors="pt")
batch = {k: v.to(device) for k, v in batch.items()}
prompt_len = batch["attention_mask"].sum().item()
mnt = max_new_tokens if max_new_tokens > 0 else min(35000, int(dur * 13))
generate_kwargs = {
"input_ids": batch["input_ids"],
"attention_mask": batch["attention_mask"],
"input_features": batch["input_features"],
"audio_feature_lengths": batch["audio_feature_lengths"],
"audio_chunk_mapping": batch["audio_chunk_mapping"],
"max_new_tokens": mnt, "do_sample": False, "use_cache": True,
}
if has_speaker:
generate_kwargs["speaker_input_values"] = batch["speaker_input_values"]
generate_kwargs["speaker_chunk_mapping"] = batch["speaker_chunk_mapping"]
t0 = time.time()
with torch.inference_mode():
out = model.generate(**generate_kwargs)
elapsed = time.time() - t0
n_gen = out.shape[1] - prompt_len
txt = processor.tokenizer.decode(out[0][prompt_len:], skip_special_tokens=True)
ts = re.findall(r"\[(\d+\.\d+)\]", txt)
covered = f"{ts[0]}->{ts[-1]}" if ts else "?"
(out_dir / f"{fname}.txt").write_text(txt, encoding="utf-8")
print(f"[GPU{gpu_id}] {fname}: {elapsed:.0f}s {n_gen}tok eos={n_gen<mnt} cover={covered}", flush=True)
print(f"[GPU{gpu_id}] DONE", flush=True)
# ─── main ────────────────────────────────────────────────────────────────────
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--ckpt", required=True)
parser.add_argument("--output_dir", required=True)
parser.add_argument("--gpus", default="0")
parser.add_argument("--workers_per_gpu", type=int, default=1)
parser.add_argument("--max_new_tokens", type=int, default=0)
args = parser.parse_args()
gpu_list = [g.strip() for g in args.gpus.split(",")]
n_workers = len(gpu_list) * args.workers_per_gpu
all_files = sorted([f.stem for f in Path(AUDIO_DIR).glob("*.wav")])
chunk_size = (len(all_files) + n_workers - 1) // n_workers
procs = []
worker_idx = 0
for gpu in gpu_list:
for _ in range(args.workers_per_gpu):
chunk = all_files[worker_idx * chunk_size : (worker_idx + 1) * chunk_size]
if not chunk:
break
p = mp.Process(target=run_worker, args=(gpu, args.ckpt, chunk, args.output_dir, args.max_new_tokens))
p.start()
procs.append(p)
worker_idx += 1
for p in procs:
p.join()
print("ALL DONE")
if __name__ == "__main__":
mp.set_start_method("spawn", force=True)
main()
|