Download runtime/test_onnxruntime.py from happyme531/muscriptor-medium-onnx: direct link, hf CLI and curl.
- Browser
- Download file 11.1 kB
-
https://huggingface.co/happyme531/muscriptor-medium-onnx/resolve/main/runtime/test_onnxruntime.py
- Command line
-
hf download hf://happyme531/muscriptor-medium-onnx/runtime/test_onnxruntime.py
-
curl -L -o test_onnxruntime.py https://huggingface.co/happyme531/muscriptor-medium-onnx/resolve/main/runtime/test_onnxruntime.py
11.1 kB
| #!/usr/bin/env python3 | |
| """Run cached autoregressive MuScriptor inference with ONNX Runtime.""" | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import time | |
| from pathlib import Path | |
| import numpy as np | |
| # Importing PyTorch first preloads the CUDA/cuDNN shared libraries shipped in | |
| # the uv environment when available; CPU-only users do not need PyTorch. | |
| try: | |
| import torch # noqa: F401 | |
| except ModuleNotFoundError: | |
| torch = None # type: ignore[assignment] | |
| import onnxruntime as ort | |
| from muscriptor_onnx.audio import SAMPLE_RATE, load_audio_16k, log_mel_spectrogram | |
| def device_value( | |
| array: np.ndarray, | |
| device_type: str, | |
| device_id: int, | |
| ) -> ort.OrtValue: | |
| return ort.OrtValue.ortvalue_from_numpy(array, device_type, device_id) | |
| def run_shared_gqa( | |
| decoder: ort.InferenceSession, | |
| feeds: dict[str, ort.OrtValue], | |
| caches: dict[str, ort.OrtValue], | |
| layers: int, | |
| device_type: str, | |
| device_id: int, | |
| ) -> np.ndarray: | |
| """Run fused GQA while aliasing every present cache to its past cache.""" | |
| binding = decoder.io_binding() | |
| for name, value in feeds.items(): | |
| binding.bind_ortvalue_input(name, value) | |
| for name, value in caches.items(): | |
| binding.bind_ortvalue_input(name, value) | |
| binding.bind_output("logits", device_type, device_id) | |
| for layer_index in range(layers): | |
| binding.bind_ortvalue_output( | |
| f"present_key.{layer_index}", caches[f"past_key.{layer_index}"] | |
| ) | |
| binding.bind_ortvalue_output( | |
| f"present_value.{layer_index}", caches[f"past_value.{layer_index}"] | |
| ) | |
| decoder.run_with_iobinding(binding) | |
| return binding.get_outputs()[0].numpy() | |
| def providers(requested: str, device_id: int) -> list: | |
| available = ort.get_available_providers() | |
| if requested == "cuda" or (requested == "auto" and "CUDAExecutionProvider" in available): | |
| if "CUDAExecutionProvider" not in available: | |
| raise RuntimeError(f"CUDA EP unavailable; installed providers: {available}") | |
| return [("CUDAExecutionProvider", {"device_id": device_id}), "CPUExecutionProvider"] | |
| return ["CPUExecutionProvider"] | |
| def session( | |
| path: Path, | |
| selected_providers: list, | |
| profile_prefix: str | None = None, | |
| intra_op_threads: int = 0, | |
| inter_op_threads: int = 0, | |
| ) -> ort.InferenceSession: | |
| options = ort.SessionOptions() | |
| options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL | |
| if intra_op_threads > 0: | |
| options.intra_op_num_threads = intra_op_threads | |
| if inter_op_threads > 0: | |
| options.inter_op_num_threads = inter_op_threads | |
| if profile_prefix is not None: | |
| options.enable_profiling = True | |
| options.profile_file_prefix = profile_prefix | |
| result = ort.InferenceSession(path, sess_options=options, providers=selected_providers) | |
| requested_cuda = bool(selected_providers) and ( | |
| selected_providers[0] == "CUDAExecutionProvider" | |
| or ( | |
| isinstance(selected_providers[0], tuple) | |
| and selected_providers[0][0] == "CUDAExecutionProvider" | |
| ) | |
| ) | |
| if requested_cuda and "CUDAExecutionProvider" not in result.get_providers(): | |
| raise RuntimeError("CUDA EP was requested but session creation fell back to CPU") | |
| return result | |
| def activation_numpy_dtype(metadata: dict) -> np.dtype: | |
| dtype_name = metadata.get("activation_dtype", metadata.get("dtype")) | |
| try: | |
| return { | |
| "float32": np.dtype(np.float32), | |
| "float16": np.dtype(np.float16), | |
| }[dtype_name] | |
| except KeyError as error: | |
| raise ValueError(f"unsupported activation dtype: {dtype_name!r}") from error | |
| def causal_mask( | |
| query_length: int, | |
| past_length: int, | |
| dtype: np.dtype = np.dtype(np.float16), | |
| ) -> np.ndarray: | |
| query_positions = past_length + np.arange(query_length)[:, None] | |
| key_positions = np.arange(past_length + query_length)[None, :] | |
| masked_value = -65504.0 if dtype == np.dtype(np.float16) else -1.0e9 | |
| return np.where(key_positions <= query_positions, 0.0, masked_value).astype(dtype) | |
| def main() -> None: | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("--model-dir", type=Path, required=True) | |
| parser.add_argument("--audio", type=Path) | |
| parser.add_argument("--provider", choices=("auto", "cuda", "cpu"), default="auto") | |
| parser.add_argument("--device-id", type=int, default=0) | |
| parser.add_argument("--max-new-tokens", type=int, default=8) | |
| parser.add_argument("--max-cache-length", type=int, default=2504) | |
| parser.add_argument("--intra-op-threads", type=int, default=0) | |
| parser.add_argument("--inter-op-threads", type=int, default=1) | |
| parser.add_argument( | |
| "--instrument-id", | |
| type=int, | |
| action="append", | |
| help="Optional MT3_FULL_PLUS group ID; repeat for multiple groups", | |
| ) | |
| args = parser.parse_args() | |
| metadata = json.loads((args.model_dir / "config.json").read_text()) | |
| activation_dtype = activation_numpy_dtype(metadata) | |
| selected = providers(args.provider, args.device_id) | |
| conditioner = session( | |
| args.model_dir / "conditioner.onnx", | |
| selected, | |
| intra_op_threads=args.intra_op_threads, | |
| inter_op_threads=args.inter_op_threads, | |
| ) | |
| decoder = session( | |
| args.model_dir / "decoder.onnx", | |
| selected, | |
| intra_op_threads=args.intra_op_threads, | |
| inter_op_threads=args.inter_op_threads, | |
| ) | |
| if args.audio: | |
| audio = load_audio_16k(args.audio) | |
| source = str(args.audio) | |
| else: | |
| # Deterministic smoke-test input; it only tests execution, not quality. | |
| time_axis = np.arange(5 * SAMPLE_RATE, dtype=np.float32) / SAMPLE_RATE | |
| audio = (0.1 * np.sin(2 * np.pi * 440.0 * time_axis)).astype(np.float32) | |
| source = "generated 440 Hz sine" | |
| mel = log_mel_spectrogram(audio).astype(activation_dtype) | |
| instrument_ids = np.asarray( | |
| [args.instrument_id if args.instrument_id is not None else [-1]], dtype=np.int64 | |
| ) | |
| dataset_ids = np.asarray([[-1]], dtype=np.int64) | |
| start = time.perf_counter() | |
| condition = conditioner.run( | |
| ["condition_embeddings"], | |
| { | |
| "log_mel": mel, | |
| "instrument_ids": instrument_ids, | |
| "dataset_ids": dataset_ids, | |
| }, | |
| )[0] | |
| condition_seconds = time.perf_counter() - start | |
| layers = metadata["num_layers"] | |
| heads = metadata["num_heads"] | |
| head_dim = metadata["head_dim"] | |
| fused_gqa = "past_key.0" in {value.name for value in decoder.get_inputs()} | |
| active = decoder.get_providers() | |
| device_type = "cuda" if active[0] == "CUDAExecutionProvider" else "cpu" | |
| if fused_gqa: | |
| cache_bytes = ( | |
| layers | |
| * 2 | |
| * heads | |
| * args.max_cache_length | |
| * head_dim | |
| * activation_dtype.itemsize | |
| ) | |
| caches = { | |
| f"past_{kind}.{layer}": device_value( | |
| np.zeros( | |
| (1, heads, args.max_cache_length, head_dim), | |
| dtype=activation_dtype, | |
| ), | |
| device_type, | |
| args.device_id, | |
| ) | |
| for layer in range(layers) | |
| for kind in ("key", "value") | |
| } | |
| else: | |
| past_key = np.zeros( | |
| (layers, 1, heads, 0, head_dim), dtype=activation_dtype | |
| ) | |
| past_value = np.zeros_like(past_key) | |
| input_ids = np.asarray([[metadata["initial_token_id"]]], dtype=np.int64) | |
| generated: list[int] = [] | |
| decode_times: list[float] = [] | |
| for step in range(args.max_new_tokens): | |
| prefix = condition if step == 0 else np.empty( | |
| (1, 0, heads * head_dim), activation_dtype | |
| ) | |
| query_length = prefix.shape[1] + input_ids.shape[1] | |
| tick = time.perf_counter() | |
| if fused_gqa: | |
| past_length = 0 if step == 0 else condition.shape[1] + step | |
| total_length = past_length + query_length | |
| if total_length > args.max_cache_length: | |
| raise ValueError( | |
| f"sequence length {total_length} exceeds --max-cache-length " | |
| f"{args.max_cache_length}" | |
| ) | |
| feeds = { | |
| "input_ids": device_value(input_ids, device_type, args.device_id), | |
| "condition_embeddings": device_value(prefix, device_type, args.device_id), | |
| "position_ids": device_value( | |
| np.arange(past_length, total_length, dtype=np.int64)[None, :], | |
| device_type, | |
| args.device_id, | |
| ), | |
| "seqlens_k": device_value( | |
| np.asarray([total_length - 1], dtype=np.int32), | |
| device_type, | |
| args.device_id, | |
| ), | |
| "total_sequence_length": device_value( | |
| np.asarray(total_length, dtype=np.int32), | |
| device_type, | |
| args.device_id, | |
| ), | |
| } | |
| logits = run_shared_gqa( | |
| decoder, feeds, caches, layers, device_type, args.device_id | |
| ) | |
| else: | |
| mask = causal_mask(query_length, past_key.shape[3], activation_dtype) | |
| logits, past_key, past_value = decoder.run( | |
| ("logits", "present_key", "present_value"), | |
| { | |
| "input_ids": input_ids, | |
| "condition_embeddings": prefix, | |
| "past_key": past_key, | |
| "past_value": past_value, | |
| "attention_mask": mask, | |
| }, | |
| ) | |
| decode_times.append(time.perf_counter() - tick) | |
| if not np.isfinite(logits).all(): | |
| raise RuntimeError("decoder produced non-finite logits") | |
| logits[:, metadata["first_reserved_token_id"] :] = -np.inf | |
| token = int(logits.argmax(axis=-1)[0]) | |
| generated.append(token) | |
| input_ids = np.asarray([[token]], dtype=np.int64) | |
| if token == metadata["eos_token_id"]: | |
| break | |
| sizes = { | |
| path.name: path.stat().st_size | |
| for path in args.model_dir.iterdir() | |
| if path.is_file() and (path.suffix == ".onnx" or path.name.endswith(".onnx.data")) | |
| } | |
| sizes["total"] = sum(sizes.values()) | |
| print(f"model: {args.model_dir}") | |
| print(f"audio: {source}") | |
| print(f"providers: {active}") | |
| print(f"mel shape: {mel.shape}") | |
| print(f"condition shape: {condition.shape}") | |
| if fused_gqa: | |
| print( | |
| "shared KV cache: " | |
| f"{args.max_cache_length} positions, {cache_bytes / 2**20:.1f} MiB" | |
| ) | |
| else: | |
| print(f"final KV shape: {past_key.shape}") | |
| print(f"generated tokens: {generated}") | |
| print(f"condition latency: {condition_seconds * 1000:.1f} ms") | |
| print(f"decode latency: {[round(x * 1000, 1) for x in decode_times]} ms") | |
| print(f"ONNX file sizes: {sizes}") | |
| if __name__ == "__main__": | |
| main() | |