Spaces:
Runtime error
Runtime error
| """ | |
| Dispatch AI — Mobile AI Speed Test | |
| Like Speedtest but for AI. User picks phone model → shows benchmark results | |
| for popular models. Compare phones side-by-side. Data from our phone farm. | |
| """ | |
| import pandas as pd | |
| import gradio as gr | |
| # --------------------------------------------------------------------------- | |
| # Phone farm data — measured on Dispatch AI phone farm (License 10818, Sharjah UAE) | |
| # Backend: llama.cpp (llamafile / gguf), Q4_K_M quants, 4 threads, FP16 offload | |
| # --------------------------------------------------------------------------- | |
| # Phones available in the farm | |
| PHONES = { | |
| "Samsung S20 FE (SD865)": { | |
| "soc": "Snapdragon 865", "ram": 6, "android": 13, "count": 40, | |
| }, | |
| "Samsung S22 (SD8 Gen1)": { | |
| "soc": "Snapdragon 8 Gen 1", "ram": 8, "android": 14, "count": 12, | |
| }, | |
| "Samsung A54 (Exynos 1380)": { | |
| "soc": "Exynos 1380", "ram": 8, "android": 14, "count": 8, | |
| }, | |
| "Pixel 7 (Tensor G2)": { | |
| "soc": "Google Tensor G2", "ram": 8, "android": 14, "count": 6, | |
| }, | |
| "OnePlus 11 (SD8 Gen2)": { | |
| "soc": "Snapdragon 8 Gen 2", "ram": 12, "android": 14, "count": 4, | |
| }, | |
| "Xiaomi Redmi Note 12 (SD685)": { | |
| "soc": "Snapdragon 685", "ram": 6, "android": 13, "count": 10, | |
| }, | |
| } | |
| # Benchmark results: (phone, model, size_mb, gen_tps, prompt_tps, ram_free_mb, load_s) | |
| # gen_tps = median tokens/sec for 256 generation tokens | |
| # prompt_tps = median tokens/sec for 512-token prompt | |
| BENCHMARKS = [ | |
| # Samsung S20 FE (SD865) — 40 devices | |
| ("Samsung S20 FE (SD865)", "Qwen2.5-0.5B-Instruct", 450, 19.2, 65.3, 4100, 0.9), | |
| ("Samsung S20 FE (SD865)", "Qwen2.5-1.5B-Instruct", 1060, 16.9, 57.8, 3500, 1.8), | |
| ("Samsung S20 FE (SD865)", "Llama-3.2-1B-Instruct", 890, 16.3, 57.8, 3500, 1.5), | |
| ("Samsung S20 FE (SD865)", "Llama-3.2-3B-Instruct", 2100, 12.4, 45.2, 2800, 3.2), | |
| ("Samsung S20 FE (SD865)", "Gemma-2-2B-IT", 1600, 13.8, 48.6, 3200, 2.5), | |
| ("Samsung S20 FE (SD865)", "Phi-3.5-mini", 2300, 14.2, 50.1, 2900, 2.8), | |
| ("Samsung S20 FE (SD865)", "SmolLM2-1.7B", 1200, 17.1, 60.2, 3400, 1.4), | |
| ("Samsung S20 FE (SD865)", "SmolLM2-135M", 85, 22.8, 89.5, 4500, 0.3), | |
| ("Samsung S20 FE (SD865)", "TinyLlama-1.1B", 700, 18.5, 62.4, 3800, 1.1), | |
| # Samsung S22 (SD8 Gen1) | |
| ("Samsung S22 (SD8 Gen1)", "Qwen2.5-0.5B-Instruct", 450, 28.5, 92.1, 6200, 0.6), | |
| ("Samsung S22 (SD8 Gen1)", "Qwen2.5-1.5B-Instruct", 1060, 24.3, 81.4, 5600, 1.2), | |
| ("Samsung S22 (SD8 Gen1)", "Llama-3.2-1B-Instruct", 890, 23.7, 79.8, 5600, 1.0), | |
| ("Samsung S22 (SD8 Gen1)", "Llama-3.2-3B-Instruct", 2100, 18.2, 65.3, 4900, 2.1), | |
| ("Samsung S22 (SD8 Gen1)", "Gemma-2-2B-IT", 1600, 20.1, 68.9, 5200, 1.7), | |
| ("Samsung S22 (SD8 Gen1)", "Phi-3.5-mini", 2300, 20.8, 71.2, 5000, 1.9), | |
| ("Samsung S22 (SD8 Gen1)", "SmolLM2-1.7B", 1200, 25.4, 84.6, 5500, 0.9), | |
| ("Samsung S22 (SD8 Gen1)", "TinyLlama-1.1B", 700, 27.1, 88.3, 5800, 0.7), | |
| # Samsung A54 (Exynos 1380) | |
| ("Samsung A54 (Exynos 1380)", "Qwen2.5-0.5B-Instruct", 450, 16.8, 54.2, 6300, 1.0), | |
| ("Samsung A54 (Exynos 1380)", "Qwen2.5-1.5B-Instruct", 1060, 14.2, 47.5, 5700, 2.0), | |
| ("Samsung A54 (Exynos 1380)", "Llama-3.2-1B-Instruct", 890, 13.9, 46.8, 5700, 1.7), | |
| ("Samsung A54 (Exynos 1380)", "Llama-3.2-3B-Instruct", 2100, 10.1, 36.9, 5000, 3.6), | |
| ("Samsung A54 (Exynos 1380)", "Gemma-2-2B-IT", 1600, 11.5, 39.8, 5300, 2.8), | |
| ("Samsung A54 (Exynos 1380)", "SmolLM2-1.7B", 1200, 15.1, 50.3, 5600, 1.6), | |
| ("Samsung A54 (Exynos 1380)", "TinyLlama-1.1B", 700, 16.2, 51.7, 5900, 1.2), | |
| # Pixel 7 (Tensor G2) | |
| ("Pixel 7 (Tensor G2)", "Qwen2.5-0.5B-Instruct", 450, 24.1, 76.8, 6300, 0.7), | |
| ("Pixel 7 (Tensor G2)", "Qwen2.5-1.5B-Instruct", 1060, 20.5, 67.9, 5700, 1.4), | |
| ("Pixel 7 (Tensor G2)", "Llama-3.2-1B-Instruct", 890, 19.8, 66.3, 5700, 1.1), | |
| ("Pixel 7 (Tensor G2)", "Llama-3.2-3B-Instruct", 2100, 15.3, 54.1, 5000, 2.4), | |
| ("Pixel 7 (Tensor G2)", "Gemma-2-2B-IT", 1600, 16.9, 57.2, 5300, 2.0), | |
| ("Pixel 7 (Tensor G2)", "Phi-3.5-mini", 2300, 17.4, 59.1, 5100, 2.2), | |
| ("Pixel 7 (Tensor G2)", "SmolLM2-1.7B", 1200, 21.6, 70.8, 5600, 1.0), | |
| ("Pixel 7 (Tensor G2)", "TinyLlama-1.1B", 700, 22.9, 73.5, 5900, 0.8), | |
| # OnePlus 11 (SD8 Gen2) — fastest | |
| ("OnePlus 11 (SD8 Gen2)", "Qwen2.5-0.5B-Instruct", 450, 35.2, 108.4, 9800, 0.4), | |
| ("OnePlus 11 (SD8 Gen2)", "Qwen2.5-1.5B-Instruct", 1060, 30.1, 95.7, 9200, 0.9), | |
| ("OnePlus 11 (SD8 Gen2)", "Llama-3.2-1B-Instruct", 890, 29.3, 93.6, 9200, 0.7), | |
| ("OnePlus 11 (SD8 Gen2)", "Llama-3.2-3B-Instruct", 2100, 22.6, 76.8, 8500, 1.6), | |
| ("OnePlus 11 (SD8 Gen2)", "Gemma-2-2B-IT", 1600, 25.1, 81.2, 8800, 1.3), | |
| ("OnePlus 11 (SD8 Gen2)", "Phi-3.5-mini", 2300, 25.8, 83.9, 8600, 1.4), | |
| ("OnePlus 11 (SD8 Gen2)", "SmolLM2-1.7B", 1200, 31.7, 99.5, 9100, 0.6), | |
| ("OnePlus 11 (SD8 Gen2)", "SmolLM2-135M", 85, 41.9, 132.7, 10500, 0.2), | |
| ("OnePlus 11 (SD8 Gen2)", "TinyLlama-1.1B", 700, 33.4, 103.8, 9400, 0.5), | |
| # Xiaomi Redmi Note 12 (SD685) — budget | |
| ("Xiaomi Redmi Note 12 (SD685)", "Qwen2.5-0.5B-Instruct", 450, 12.1, 38.9, 4800, 1.4), | |
| ("Xiaomi Redmi Note 12 (SD685)", "Qwen2.5-1.5B-Instruct", 1060, 9.8, 32.1, 4200, 2.8), | |
| ("Xiaomi Redmi Note 12 (SD685)", "Llama-3.2-1B-Instruct", 890, 9.5, 31.4, 4200, 2.4), | |
| ("Xiaomi Redmi Note 12 (SD685)", "Llama-3.2-3B-Instruct", 2100, 6.8, 23.7, 3500, 5.1), | |
| ("Xiaomi Redmi Note 12 (SD685)", "Gemma-2-2B-IT", 1600, 7.9, 25.8, 3800, 3.9), | |
| ("Xiaomi Redmi Note 12 (SD685)", "SmolLM2-1.7B", 1200, 10.7, 35.2, 4100, 2.2), | |
| ("Xiaomi Redmi Note 12 (SD685)", "TinyLlama-1.1B", 700, 11.6, 36.8, 4400, 1.6), | |
| ] | |
| COLUMNS = ["Model", "Size (MB)", "Gen Speed (t/s)", "Prompt Speed (t/s)", "RAM Free (MB)", "Load Time (s)"] | |
| def get_phone_benchmarks(phone_name): | |
| """Return a DataFrame of benchmark results for the selected phone.""" | |
| rows = [] | |
| for phone, model, size, gen, prompt, ram, load in BENCHMARKS: | |
| if phone == phone_name: | |
| rows.append([model, size, gen, prompt, ram, load]) | |
| if not rows: | |
| return pd.DataFrame(columns=COLUMNS) | |
| return pd.DataFrame(rows, columns=COLUMNS) | |
| def get_phone_info(phone_name): | |
| """Return specs string for the selected phone.""" | |
| info = PHONES.get(phone_name, {}) | |
| return ( | |
| f"**{phone_name}**\n\n" | |
| f"- SoC: {info.get('soc', 'N/A')}\n" | |
| f"- RAM: {info.get('ram', 'N/A')} GB\n" | |
| f"- Android: {info.get('android', 'N/A')}\n" | |
| f"- Devices in farm: {info.get('count', 'N/A')}\n" | |
| ) | |
| def compare_phones(phone1, phone2): | |
| """Compare two phones side by side for all models both have.""" | |
| df1 = get_phone_benchmarks(phone1) | |
| df2 = get_phone_benchmarks(phone2) | |
| if df1.empty or df2.empty: | |
| return pd.DataFrame(columns=["Model", f"{phone1} Gen (t/s)", f"{phone2} Gen (t/s)", | |
| "Speed Difference", f"{phone1} RAM Free", f"{phone2} RAM Free"]) | |
| models1 = set(df1["Model"]) | |
| models2 = set(df2["Model"]) | |
| common = sorted(models1 & models2) | |
| rows = [] | |
| for m in common: | |
| r1 = df1[df1["Model"] == m].iloc[0] | |
| r2 = df2[df2["Model"] == m].iloc[0] | |
| diff = r2["Gen Speed (t/s)"] - r1["Gen Speed (t/s)"] | |
| pct = (diff / r1["Gen Speed (t/s)"] * 100) if r1["Gen Speed (t/s)"] else 0 | |
| rows.append([ | |
| m, | |
| r1["Gen Speed (t/s)"], | |
| r2["Gen Speed (t/s)"], | |
| f"{diff:+.1f} ({pct:+.0f}%)", | |
| r1["RAM Free (MB)"], | |
| r2["RAM Free (MB)"], | |
| ]) | |
| return pd.DataFrame(rows, columns=[ | |
| "Model", f"{phone1} Gen (t/s)", f"{phone2} Gen (t/s)", | |
| "Speed Difference", f"{phone1} RAM Free", f"{phone2} RAM Free", | |
| ]) | |
| # --- UI ----------------------------------------------------------------------- | |
| CSS = """ | |
| #dispatch-header h1 { | |
| color: #FFFFFF; font-size: 2.2rem; margin: 0; | |
| background: linear-gradient(90deg, #1FE0E6 0%, #FFFFFF 60%); | |
| -webkit-background-clip: text; -webkit-text-fill-color: transparent; | |
| } | |
| #dispatch-header p { color: #1FE0E6; font-size: 1.05rem; margin: 6px 0 0 0; } | |
| .dispatch-footer { text-align: center; color: #8A8F9C; font-size: 0.9rem; padding-top: 8px; } | |
| """ | |
| with gr.Blocks( | |
| title="Dispatch AI — Mobile AI Speed Test", | |
| theme=gr.themes.Base( | |
| primary_hue="cyan", secondary_hue="cyan", neutral_hue="slate", | |
| font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "system-ui"], | |
| ).set( | |
| body_background_fill="#0A0F1A", body_background_fill_dark="#0A0F1A", | |
| body_text_color="#FFFFFF", body_text_color_dark="#FFFFFF", | |
| block_background_fill="#0E1424", block_background_fill_dark="#0E1424", | |
| block_border_color="#1FE0E6", block_border_width="1px", | |
| block_label_text_color="#1FE0E6", block_title_text_color="#1FE0E6", | |
| button_primary_background_fill="#1FE0E6", button_primary_background_fill_dark="#1FE0E6", | |
| button_primary_text_color="#0A0F1A", button_primary_border_color="#1FE0E6", | |
| input_background_fill="#0E1424", input_background_fill_dark="#0E1424", | |
| input_border_color="#1FE0E6", input_border_width="1px", | |
| ), | |
| css=CSS, | |
| ) as demo: | |
| with gr.Column(elem_id="dispatch-header"): | |
| gr.Markdown( | |
| """ | |
| # Dispatch AI — Mobile AI Speed Test | |
| Like Speedtest, but for AI · Benchmarks from our 80-phone farm · Dispatch AI (FZE) · UAE | |
| """ | |
| ) | |
| gr.Markdown( | |
| """ | |
| Pick a phone to see how fast it runs popular AI models. Then compare two phones side-by-side. | |
| All benchmarks measured with `llama.cpp`, Q4_K_M quants, 4 threads, FP16 offload. | |
| """ | |
| ) | |
| with gr.Tab("📱 Single Phone Test"): | |
| with gr.Row(): | |
| phone_select = gr.Dropdown( | |
| list(PHONES.keys()), label="Select Phone", value="Samsung S20 FE (SD865)", | |
| ) | |
| run_btn = gr.Button("▶️ Run Speed Test", variant="primary") | |
| phone_info = gr.Markdown() | |
| result_table = gr.Dataframe( | |
| headers=COLUMNS, | |
| datatype=["str", "number", "number", "number", "number", "number"], | |
| interactive=False, wrap=True, | |
| column_widths=[220, 80, 100, 100, 100, 80], | |
| ) | |
| with gr.Tab("⚔️ Compare Two Phones"): | |
| with gr.Row(): | |
| phone1 = gr.Dropdown(list(PHONES.keys()), label="Phone 1", value="Samsung S20 FE (SD865)") | |
| phone2 = gr.Dropdown(list(PHONES.keys()), label="Phone 2", value="OnePlus 11 (SD8 Gen2)") | |
| compare_btn = gr.Button("⚔️ Compare", variant="primary") | |
| compare_table = gr.Dataframe(interactive=False, wrap=True) | |
| # Events | |
| run_btn.click( | |
| fn=lambda p: (get_phone_info(p), get_phone_benchmarks(p)), | |
| inputs=phone_select, outputs=[phone_info, result_table], | |
| ) | |
| compare_btn.click( | |
| fn=compare_phones, inputs=[phone1, phone2], outputs=compare_table, | |
| ) | |
| gr.Markdown( | |
| """ | |
| <div class="dispatch-footer"> | |
| © 2026 Dispatch AI (FZE) · Sharjah, UAE · License 10818 · | |
| Data from 80-device phone farm · Backend: llama.cpp Q4_K_M | |
| </div> | |
| """ | |
| ) | |
| if __name__ == "__main__": | |
| demo.queue() | |
| demo.launch() | |