canary-speechlm-mlx / benchmark.py
Santosh-bold's picture
Upload benchmark.py with huggingface_hub
c11c129 verified
Raw
History Blame Contribute Delete
6.13 kB
import time
import os
import glob
import sys
import numpy as np
import mlx.core as mx
import librosa
from transformers import AutoTokenizer
# Import the model and setup helpers from generate
sys.path.insert(0, "/Users/santosh/Desktop/ASR-Playground/mlx_canary")
from generate import load_llm, load_encoder, get_embed_tokens, extract_mel
from mlx_lm.models.cache import make_prompt_cache
def run_benchmark(audio_path: str, qwen_base_dir: str):
print(f"Loading audio file: {audio_path}")
# 1. Measure audio duration
y, sr = librosa.load(audio_path, sr=None)
duration = len(y) / sr
print(f"Audio duration: {duration:.2f} seconds")
# Load models
print("\nLoading models into memory...")
t_start_load = time.perf_counter()
canary = load_encoder()
llm = load_llm(qwen_base_dir)
tokenizer = AutoTokenizer.from_pretrained(qwen_base_dir)
embed_tokens = get_embed_tokens(llm)
# Warmup
print("\nRunning warm-up pass...")
warmup_features = extract_mel(audio_path)
warmup_embeds, _ = canary.encode_audio(warmup_features)
mx.eval(warmup_embeds)
# Warmup LLM prefill compilation
warmup_t1 = [151644, 872, 198, 3167, 3114, 279, 2701, 25, 220]
warmup_t2 = [151645, 198, 151644, 77091, 198]
warmup_emb1 = embed_tokens(mx.array([warmup_t1]))
warmup_emb2 = embed_tokens(mx.array([warmup_t2]))
warmup_embeds_bf16 = warmup_embeds.astype(embed_tokens.weight.dtype)
warmup_full_embeds = mx.concatenate([warmup_emb1, warmup_embeds_bf16, warmup_emb2], axis=1)
mx.eval(warmup_full_embeds)
warmup_cache = make_prompt_cache(llm)
warmup_dummy = mx.zeros((1, warmup_full_embeds.shape[1]), dtype=mx.int32)
warmup_logits = llm(warmup_dummy, cache=warmup_cache, input_embeddings=warmup_full_embeds)
warmup_next = mx.argmax(warmup_logits[:, -1, :], axis=-1)
mx.eval(warmup_next)
# Warmup LLM decode loop compilation (first few steps)
warmup_token_id = warmup_next.item()
for _ in range(5):
warmup_logits = llm(mx.array([[warmup_token_id]]), cache=warmup_cache)
warmup_next = mx.argmax(warmup_logits[:, -1, :], axis=-1)
mx.eval(warmup_next)
warmup_token_id = warmup_next.item()
# 2. Benchmark Audio Feature Extraction + Conformer Encoding
print("\nBenchmarking Audio Encoder...")
t_start_enc = time.perf_counter()
audio_features = extract_mel(audio_path)
audio_embeds, _ = canary.encode_audio(audio_features)
# Evaluate MLX graph to block until computation completes
mx.eval(audio_embeds)
t_end_enc = time.perf_counter()
enc_time = t_end_enc - t_start_enc
print(f"Feature Extraction + Encoder execution time: {enc_time:.4f} seconds")
# 3. Prompt Setup
t1 = [151644, 872, 198, 3167, 3114, 279, 2701, 25, 220]
t2 = [151645, 198, 151644, 77091, 198]
emb1 = embed_tokens(mx.array([t1]))
emb2 = embed_tokens(mx.array([t2]))
audio_embeds_bf16 = audio_embeds.astype(embed_tokens.weight.dtype)
full_embeds = mx.concatenate([emb1, audio_embeds_bf16, emb2], axis=1)
mx.eval(full_embeds)
# 4. Benchmark LLM Decoding (Autoregressive Generation)
print("\nBenchmarking LLM Text Generation...")
cache = make_prompt_cache(llm)
dummy = mx.zeros((1, full_embeds.shape[1]), dtype=mx.int32)
# Prefill/First token time
t_start_prefill = time.perf_counter()
logits = llm(dummy, cache=cache, input_embeddings=full_embeds)
y = mx.argmax(logits[:, -1, :], axis=-1)
mx.async_eval(y)
mx.eval(y)
token_id = y.item()
prefill_time = time.perf_counter() - t_start_prefill
def step(token):
logits = llm(token, cache=cache)
return mx.argmax(logits[:, -1, :], axis=-1)
generated = []
im_end_id = tokenizer.convert_tokens_to_ids("<|im_end|>")
# Decode loop benchmark
t_start_decode = time.perf_counter()
for _ in range(200):
if token_id in (tokenizer.eos_token_id, im_end_id):
break
generated.append(token_id)
# Enqueue next token computation on GPU
next_y = step(mx.array([[token_id]]))
mx.async_eval(next_y)
# Fetch current token ID (blocks CPU while next_y runs in parallel on GPU)
token_id = next_y.item()
decode_time = time.perf_counter() - t_start_decode
total_gen_time = prefill_time + decode_time
num_tokens = len(generated)
# Calculate separate metrics
prompt_tokens = full_embeds.shape[1]
prefill_tps = prompt_tokens / prefill_time
# Generated tokens excluding the first token (which was generated by prefill)
decode_tokens = max(0, num_tokens - 1)
decode_tps = decode_tokens / decode_time if decode_time > 0 else 0.0
rtf = (enc_time + total_gen_time) / duration
result_text = tokenizer.decode(generated)
print("\n" + "="*50)
print(" BENCHMARK RESULTS ")
print("="*50)
print(f"Transcription: '{result_text}'")
print(f"Audio Duration : {duration:.2f}s")
print(f"Prompt Size : {prompt_tokens} tokens")
print(f"Time to First Token : {prefill_time:.4f}s")
print(f"Prompt Processing Speed : {prefill_tps:.2f} tok/s")
print(f"Decode Loop Time : {decode_time:.4f}s")
print(f"Tokens Decoded : {decode_tokens}")
print(f"Decode Generation Speed : {decode_tps:.2f} tok/s (excl. TTFT)")
print(f"Total Gen Time : {total_gen_time:.4f}s")
print(f"Total Processing : {enc_time + total_gen_time:.4f}s")
print(f"Real-Time Factor : {rtf:.4f}x ({(1/rtf):.1f}x faster than real-time)")
print("="*50)
if __name__ == "__main__":
audio = sys.argv[1] if len(sys.argv) > 1 else "/Users/santosh/Desktop/ASR-Playground/test.wav"
candidates = glob.glob(os.path.expanduser(
"~/.cache/huggingface/hub/models--Qwen--Qwen3-1.7B/snapshots/*/"))
if not candidates:
print("ERROR: Qwen3-1.7B not found.")
sys.exit(1)
qwen_dir = candidates[0].rstrip("/")
run_benchmark(audio, qwen_dir)