File size: 11,247 Bytes
ee560dd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
"""
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()