import os os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True") import spaces # MUST come before torch / any CUDA-touching import import torch import gradio as gr import numpy as np import librosa from transformers import ( Qwen2_5OmniForConditionalGeneration, Qwen2_5OmniProcessor, ) MODEL_ID = "umd-zhou-lab/AudioRubrics" processor = Qwen2_5OmniProcessor.from_pretrained(MODEL_ID) model = Qwen2_5OmniForConditionalGeneration.from_pretrained( MODEL_ID, torch_dtype=torch.bfloat16, attn_implementation="sdpa", ).to("cuda").eval() THINK_OPEN = "" THINK_CLOSE = "" ANSWER_OPEN = "" ANSWER_CLOSE = "" @spaces.GPU(duration=60) def answer_audio_question( audio_path: str, question: str, max_new_tokens: int = 768, temperature: float = 0.0, enable_thinking: bool = True, progress=gr.Progress(track_tqdm=True), ): """Answer an audio-grounded question using AudioRubrics (Qwen2.5-Omni-7B fine-tuned with evolving rubric rewards). Args: audio_path: Path to the input audio file (WAV/MP3/FLAC etc.). question: A text question about the audio content. max_new_tokens: Maximum number of tokens to generate. temperature: Sampling temperature (0.0 = greedy). enable_thinking: If True, the model reasons step-by-step before answering. """ import re if audio_path is None: return "Please upload an audio file.", "" if not question.strip(): return "Please enter a question about the audio.", "" # Build conversation messages matching the Qwen2.5-Omni chat template if enable_thinking: system_content = ( "You are an expert audio understanding assistant. " "Listen carefully and answer questions about the audio. " "Always think step by step inside " + THINK_OPEN + " tags, " "then give the final answer inside " + ANSWER_OPEN + " tags." ) user_text = ( f"Listen to the audio carefully and answer the following question.\n\n" f"Question: {question}\n\n" "First, reason step by step inside " + THINK_OPEN + " ... " + THINK_CLOSE + " tags.\n" "Then output your final answer inside " + ANSWER_OPEN + " ... " + ANSWER_CLOSE + " tags." ) else: system_content = ( "You are an expert audio understanding assistant. " "Listen carefully and answer questions about the audio. " "Give the final answer directly." ) user_text = ( f"Listen to the audio carefully and answer the following question.\n\n" f"Question: {question}" ) messages = [ {"role": "system", "content": [{"type": "text", "text": system_content}]}, { "role": "user", "content": [ {"type": "audio", "audio": audio_path}, {"type": "text", "text": user_text}, ], }, ] text = processor.apply_chat_template( messages, tokenize=False, add_generation_prompt=True ) # Load audio as numpy array (resampled to 16kHz for Whisper feature extractor) audio_data, sr = librosa.load(audio_path, sr=16000, mono=True) inputs = processor( text=text, audio=audio_data, return_tensors="pt", padding=True, ).to("cuda").to(model.dtype) with torch.no_grad(): output_ids = model.generate( **inputs, generation_mode="text", thinker_max_new_tokens=max_new_tokens, thinker_temperature=temperature if temperature > 0 else 1.0, thinker_do_sample=temperature > 0, ) # Strip the input tokens from the output input_len = inputs["input_ids"].shape[1] generated_ids = output_ids[0][input_len:] response = processor.decode(generated_ids, skip_special_tokens=True) # The think/answer tags may be decoded as special tokens (stripped by skip_special_tokens=True) # or as literal text. Try both approaches. # First try parsing with the known tag strings. think_pattern = re.escape(THINK_OPEN) + r"\s*(.*?)\s*" + re.escape(THINK_CLOSE) answer_pattern = re.escape(ANSWER_OPEN) + r"\s*(.*?)\s*" + re.escape(ANSWER_CLOSE) think_match = re.search(think_pattern, response, flags=re.DOTALL | re.IGNORECASE) answer_match = re.search(answer_pattern, response, flags=re.DOTALL | re.IGNORECASE) thinking_text = think_match.group(1).strip() if think_match else "" answer_text = answer_match.group(1).strip() if answer_match else "" # If tags not found with skip_special_tokens=True, try with False if not think_match and not answer_match: response_raw = processor.decode(generated_ids, skip_special_tokens=False) think_match = re.search(think_pattern, response_raw, flags=re.DOTALL | re.IGNORECASE) answer_match = re.search(answer_pattern, response_raw, flags=re.DOTALL | re.IGNORECASE) thinking_text = think_match.group(1).strip() if think_match else "" answer_text = answer_match.group(1).strip() if answer_match else "" if answer_text or thinking_text: response = response_raw # If still no tags found, return the full response as the answer if not answer_text and not thinking_text: answer_text = response.strip() thinking_text = "" elif not answer_text: answer_text = response.strip() # Format nicely if thinking_text: formatted_thinking = f"**Reasoning:**\n{thinking_text}" else: formatted_thinking = "" return answer_text, formatted_thinking CSS = """ #col-container { max-width: 1100px; margin: 0 auto; } .dark .gradio-container { color: var(--body-text-color); } """ with gr.Blocks() as demo: with gr.Column(elem_id="col-container"): gr.Markdown( "# AudioRubrics: Audio Reasoning with Evolving Rubric Rewards\n" "Upload an audio clip and ask a question about it. The model reasons step-by-step " "about what it hears.\n\n" "Based on [Reinforcement Learning with Evolving Rubrics as Rewards for Audio Reasoning](https://huggingface.co/papers/2608.02831) | " "a Qwen2.5-Omni-7B model fine-tuned with GRPO using self-evolving, audio-grounded rubric rewards." ) with gr.Row(): with gr.Column(scale=1): audio_input = gr.Audio( label="Audio Input", type="filepath", sources=["upload", "microphone"], ) question_input = gr.Textbox( label="Question", placeholder="e.g., What sound do you hear in the audio?", lines=3, ) run_btn = gr.Button("Run", variant="primary") with gr.Accordion("Advanced settings", open=False): max_tokens = gr.Slider( label="Max new tokens", minimum=64, maximum=1024, value=768, step=64, ) temp = gr.Slider( label="Temperature", minimum=0.0, maximum=1.5, value=0.0, step=0.1, ) think_checkbox = gr.Checkbox( label="Enable step-by-step thinking", value=True, ) with gr.Column(scale=1): answer_output = gr.Textbox( label="Answer", lines=4, interactive=False, ) thinking_output = gr.Markdown( label="Reasoning", ) with gr.Row(): gr.Examples( examples=[ [ "examples/bird_chirp.wav", "What animal is making the sound in the audio?\nChoices:\nA. dog\nB. bird\nC. cat\nD. frog", 512, 0.0, True, ], [ "examples/metro_sound.wav", "Where did the audio take place?\nChoices:\nA. train\nB. aquatic\nC. bus station\nD. Metro Station", 512, 0.0, True, ], [ "examples/alarm_sound.wav", "What's that noise?\nChoices:\nA. firecrackers\nB. Car sound\nC. tornado\nD. siren", 512, 0.0, True, ], ], inputs=[ audio_input, question_input, max_tokens, temp, think_checkbox, ], outputs=[answer_output, thinking_output], fn=answer_audio_question, cache_examples=True, cache_mode="lazy", ) gr.Markdown( "\n---\n" "**Model:** [umd-zhou-lab/AudioRubrics](https://huggingface.co/umd-zhou-lab/AudioRubrics) | " "**Paper:** [arXiv:2608.02831](https://arxiv.org/abs/2608.02831) | " "**Code:** [GitHub](https://github.com/tianyi-lab/AudioRubrics)" ) run_btn.click( fn=answer_audio_question, inputs=[audio_input, question_input, max_tokens, temp, think_checkbox], outputs=[answer_output, thinking_output], ) demo.launch(mcp_server=True, theme=gr.themes.Citrus(), css=CSS)