import os import json from concurrent.futures import ThreadPoolExecutor import gradio as gr import spaces from huggingface_hub import InferenceClient @spaces.GPU def _ensure_gpu_functions_present_for_zerogpu(): """ZeroGPU startup probe: required for zero-a10g spaces detection.""" return None MODEL_MATRIX = [ { "key": "gemma4-31b", "label": "Gemma 4 31B-IT", "model_id": "unsloth/gemma-4-31B-it-unsloth-bnb-4bit", }, { "key": "gemma4-26b", "label": "Gemma 4 26B-IT", "model_id": "unsloth/gemma-4-26B-A4B-it", }, { "key": "qwen3.6-35b", "label": "Qwen 3.6 35B-IT", "model_id": "unsloth/Qwen3.6-35B-A3B-GGUF", }, { "key": "qwen3.6-27b", "label": "Qwen 3.6 27B", "model_id": "unsloth/Qwen3.6-27B-GGUF", }, ] HF_TOKEN = os.getenv("HF_API_TOKEN") or os.getenv("HUGGINGFACE_HUB_TOKEN") or os.getenv("HF_TOKEN") HF_TIMEOUT = int(os.getenv("HF_TIMEOUT", "120")) _SYSTEM_PROMPT = ( "You are a useful assistant. Answer briefly and directly unless the user asks for " "long-form detail. Keep responses focused and practical." ) def _init_client() -> InferenceClient: return InferenceClient(token=HF_TOKEN) def _extract_chat_text(response): if response is None: return "No response from model." if isinstance(response, dict): if "choices" in response and response["choices"]: choice = response["choices"][0] if isinstance(choice, dict) and "message" in choice and isinstance(choice["message"], dict): return (choice["message"].get("content") or "").strip() if isinstance(choice, dict) and "text" in choice: return str(choice["text"]).strip() if "generated_text" in response: return str(response["generated_text"]).strip() if "text" in response: return str(response["text"]).strip() if hasattr(response, "choices"): choices = getattr(response, "choices") if choices: first = choices[0] if hasattr(first, "message") and getattr(first.message, "content", None) is not None: return str(first.message.content).strip() if hasattr(first, "text"): return str(first.text).strip() text = str(response) if text and text != "{}": return text.strip() return "Model returned an empty response." def _build_messages(history, user_message): messages = [{"role": "system", "content": _SYSTEM_PROMPT}] for pair in history: if not pair: continue user_turn, assistant_turn = pair if user_turn: messages.append({"role": "user", "content": str(user_turn)}) if assistant_turn: messages.append({"role": "assistant", "content": str(assistant_turn)}) messages.append({"role": "user", "content": user_message}) return messages def _chat_completion(model_id, messages, max_new_tokens, temperature, top_p): client = _init_client() try: response = client.chat_completion( model=model_id, messages=messages, max_tokens=max_new_tokens, temperature=temperature, top_p=top_p, timeout=HF_TIMEOUT, ) return _extract_chat_text(response) except Exception as chat_error: fallback_prompt = "\n".join([f"{m['role']}: {m['content']}" for m in messages]) + "\nassistant:" try: response = client.text_generation( prompt=fallback_prompt, max_new_tokens=max_new_tokens, temperature=temperature, top_p=top_p, timeout=HF_TIMEOUT, ) return str(response).strip() except Exception as text_error: return ( f"Could not query model '{model_id}'." f" Chat error: {chat_error.__class__.__name__}" f"\nFallback error: {text_error.__class__.__name__}" ) def _safe_append(history, user_message, assistant_message): updated = list(history or []) updated.append((user_message, assistant_message)) return updated def _query_model(model_id, messages, max_new_tokens, temperature, top_p): return _chat_completion(model_id, messages, max_new_tokens, temperature, top_p) def run_compare( user_message, hist1, hist2, hist3, hist4, temperature, top_p, max_tokens, ): if not user_message or not str(user_message).strip(): return hist1, hist2, hist3, hist4, "", "", "", "" user_message = str(user_message).strip() m1, m2, m3, m4 = MODEL_MATRIX model_ids = [m1["model_id"], m2["model_id"], m3["model_id"], m4["model_id"]] histories = [ list(hist1 or []), list(hist2 or []), list(hist3 or []), list(hist4 or []), ] messages = [ _build_messages(histories[i], user_message) for i in range(4) ] with ThreadPoolExecutor(max_workers=4) as executor: futures = [ executor.submit(_query_model, model_ids[idx], messages[idx], max_tokens, temperature, top_p) for idx in range(4) ] answers = [f.result() for f in futures] new_hist1 = _safe_append(histories[0], user_message, answers[0]) new_hist2 = _safe_append(histories[1], user_message, answers[1]) new_hist3 = _safe_append(histories[2], user_message, answers[2]) new_hist4 = _safe_append(histories[3], user_message, answers[3]) return ( new_hist1, new_hist2, new_hist3, new_hist4, m1["label"], m2["label"], m3["label"], m4["label"], ) def run_clear(): return [], [], [], [], "", "", "", "" css = """ .gradio-container { max-width: 100%; } .column { border: 1px solid #d7dee8; border-radius: 12px; padding: 10px; background: linear-gradient(160deg, #f8fbff, #f2f4ff); } """ with gr.Blocks(css=css) as demo: gr.Markdown( "# Side-by-side Unsloth model comparison\n" "All generations run through Hugging Face hosted inference (zero local GPU)." ) with gr.Row(): status1 = gr.Textbox(label="Gemma4 31B-IT", value="", interactive=False) status2 = gr.Textbox(label="Gemma4 26B-IT", value="", interactive=False) status3 = gr.Textbox(label="Qwen 3.6 35B-IT", value="", interactive=False) status4 = gr.Textbox(label="Qwen 3.6 27B", value="", interactive=False) with gr.Row(equal_height=True): with gr.Column(elem_classes=["column"]): gr.Markdown("### Gemma4 31B-IT") chat1 = gr.Chatbot(height=470, label="Gemma4 31B-IT") with gr.Column(elem_classes=["column"]): gr.Markdown("### Gemma4 26B-IT") chat2 = gr.Chatbot(height=470, label="Gemma4 26B-IT") with gr.Column(elem_classes=["column"]): gr.Markdown("### Qwen 3.6 35B-IT") chat3 = gr.Chatbot(height=470, label="Qwen 3.6 35B-IT") with gr.Column(elem_classes=["column"]): gr.Markdown("### Qwen 3.6 27B") chat4 = gr.Chatbot(height=470, label="Qwen 3.6 27B") with gr.Accordion("Advanced generation settings", open=False): temperature = gr.Slider(0.1, 1.2, value=0.7, step=0.05, label="Temperature") top_p = gr.Slider(0.2, 1.0, value=0.9, step=0.05, label="Top-p") max_tokens = gr.Slider(64, 2048, value=512, step=16, label="Max new tokens") with gr.Row(): prompt = gr.Textbox( label="User prompt", placeholder="Ask the same question to all models...", lines=2, scale=3, ) send = gr.Button("Compare", variant="primary") clear = gr.Button("Clear") send.click( run_compare, inputs=[ prompt, chat1, chat2, chat3, chat4, temperature, top_p, max_tokens, ], outputs=[chat1, chat2, chat3, chat4, status1, status2, status3, status4], ) prompt.submit( run_compare, inputs=[ prompt, chat1, chat2, chat3, chat4, temperature, top_p, max_tokens, ], outputs=[chat1, chat2, chat3, chat4, status1, status2, status3, status4], ) clear.click( run_clear, outputs=[chat1, chat2, chat3, chat4, status1, status2, status3, status4], ) if __name__ == "__main__": demo.queue() demo.launch()