""" 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( """ """ ) if __name__ == "__main__": demo.queue() demo.launch()