Skip to content

Vulkan vs XNNPACK: per model divergences across devices #1488

Open

Description

@msluszniak

Tracking issue for cases where the Vulkan vs XNNPACK verdict changes between devices, so one per-platform registry default cannot be right for the whole fleet.

One comment per device pair. Each carries the full paired dataset, the models that flip, and what they imply for defaults.

A Vulkan vs XNNPACK ratio is a GPU/CPU quotient, so it does not transfer across devices: a phone with a strong CPU relative to its GPU reads as "Vulkan loses" on models where another phone reads the opposite. The ratio also moves with input length, because Vulkan carries a fixed per-inference dispatch cost. Peak memory is so far the only axis that holds steady across devices.

Measured with apps/benchmarks (#1363) so every device runs the same protocol and the same synthetic inputs.

Device pairs

Activity

  1. added
    platform: androidIssues and tasks related to Android
    performanceRelated to all issues and tasks focused on improving performance
    on Sep 23, 2026
  2. changed the title [-]Vulkan vs XNNPACK diverges per device: 7 of 16 models flip verdict between Mali-G76 and Mali-G77[/-] [+]Vulkan vs XNNPACK: per model divergences across devices[/+] on Sep 23, 2026
  3. added theissue type on Sep 23, 2026
  4. msluszniak commented on Sep 23, 2026

    @msluszniak
    MemberAuthor

    Mali-G76 (Galaxy S10+) vs Mali-G77 MP11 (Galaxy S20+)

    7 of 16 models flip verdict between these two phones.

    Root cause

    Sorting all 44 measurements by backend:

    backend S10+ / S20+ time reading
    Vulkan 0.61 to 1.17, median ~0.90 older GPU is as fast or faster
    XNNPACK 0.99 to 1.85, median ~1.38 older CPU is consistently slower

    Mali-G76 is not meaningfully worse than Mali-G77. The Exynos 9820 CPU is much worse than the 990's. Only the denominator moved. The S20+ is therefore the worst case for Vulkan in our fleet, not a broken device.

    The CPU gap is not uniform: tiny models are identical across the two phones (minilm 22.6 vs 22.2 ms, clip-text 74.1 vs 74.9), and the gap only opens on sustained work (whisper 1.37 to 1.74, supertonic 1.85). That is sustained-clock behaviour, so short-input benchmarks understate how well Vulkan does on older hardware.

    Per model

    model Vulkan vs XNNPACK, S10+ (G76) Vulkan vs XNNPACK, S20+ (G77) diverges
    all-minilm-l6-v2 1.50x slower 2.52x slower
    multi-qa-minilm-l6-cos-v1 1.47x slower 2.50x slower
    paraphrase-multilingual-minilm-l12-v2 1.74x slower 2.25x slower
    distiluse-base-multilingual-cased-v2 1.07x slower 1.73x slower
    clip-vit-base-patch32-text 1.58x slower 1.72x slower
    all-mpnet-base-v2 1.05x slower 2.01x slower
    multi-qa-mpnet-base-dot-v1 tie 2.07x slower yes
    clip-vit-base-patch32 (image) 1.04x faster 2.09x slower yes
    paddle-ppocrv6-small 1.24x slower 1.18x faster yes
    whisper-tiny 1.88x faster 1.95x faster
    whisper-base 2.28x faster 1.45x faster
    whisper-small 2.07x faster 1.69x faster
    whisper-en-tiny 1.12x faster 1.15x slower yes
    whisper-en-base 1.43x faster 1.11x slower yes
    whisper-en-small tie 1.25x slower yes
    supertonic 1.84x faster 1.12x slower yes

    Whisper is split because the baseline differs: the EN sizes have an XNNPACK_INT8 export and are compared against it, the multilingual sizes only have fp32. Against fp32 Vulkan wins everywhere. "Vulkan vs XNNPACK" has no single answer without naming the XNNPACK precision.

    Two exceptions to the root cause

    paddle-ppocrv6-small Vulkan is 1.61x slower on S10+ while its XNNPACK sibling is only 1.10x. The one place in 44 measurements where the G76 GPU genuinely underperforms. Vulkan is the current Android default, chosen on the S20+, so it regresses on G76.

    Profiled since: no shader pathology. G76/Adreno on detect is a flat 8.2x at 640, 960 and 1280 (134.0/16.1, 284.5/35.0, 494.8/60.8 ms), and the shipped conv2d_pw_tiled tile shape is already optimal on G76 (TILE_M=4 0.79x, TILE_N4=2 0.24x, both 0.18x, all byte-identical output). Remaining lead is kernel selection: on G76 30 convs fall back to conv2d_im2col_buffer + conv2d_gemm_buffer (215 ms) where Adreno uses conv2d_clamp (19.9 ms), and 60 dw convs pick conv2d_dw_output_tile_3x3_b1x1 (33.6 ms) over _3x3 (1.9 ms). Needs an S20+ to confirm.

    Profiling caveat for anyone repeating this: the Vulkan query pool costs Mali +269% and Adreno +13%, so query-pool timings are not comparable across vendors.

    whisper-tiny multilingual reads 1.82x on Vulkan and 1.78x on XNNPACK, so both arms moved together. Decode-length difference between devices, not a backend effect. Its cross-device numbers are void; the within-device ratio holds.

    What this implies for defaults

    Peak memory is the only device-independent axis: mpnet 0.61 on both, whisper 0.79/0.81, minilm 0.84/0.87, supertonic 0.36/0.45, clip image 0.41/0.45. Vulkan is roughly half the XNNPACK footprint at nearly the same ratio on both phones.

    supertonic is the strongest candidate for a change: 1.84x on G76, 2.12x on Adreno 840, 0.36 to 0.45 of the memory, losing only on the S20+. Current Android default is XNNPACK.

    all-minilm / multi-qa-minilm default to VULKAN_FP16 today, chosen on a 254 token benchmark where Vulkan was 2.4x faster. At this harness's ~62 tokens it is 1.50x slower on G76 and 2.52x on G77. Same phone, opposite verdict, so the fix is probably a length-aware choice rather than a flip.

    mpnet VULKAN_INT8 is 7.3x slower than fp16 on G76 and 8.2x on G77, confirming across two GPU generations that ET Vulkan's quantized tiled kernels are Adreno-tuned and pathological on Mali (they are 1.34x faster than fp16 on Adreno 840). Accuracy was never the issue, 0.9983 to 0.9994 cosine. Correctly shipped as explicit opt-in. Falsified fix, do not retry: raising TILE_M4 1 to 4 kept output bit-identical and was 1.6x slower, workgroups dropped 288 to 72, so the bottleneck is occupancy not the bitfieldExtract unpack.

    whisper-en-small VULKAN_FP16 peaks at 1466 MB on S10+ for no gain over int8 (6012 vs 5830 ms). Heaviest arm measured and an OOM risk on 4 GB devices.

    Full dataset

    case S10+ ms S20+ ms S10/S20 S10+ MB S20+ MB S10+ IQR
    image-embeddings/clip-vit-base-patch32-vulkan-fp16 152.2 236.3 0.64 308 423 1
    image-embeddings/clip-vit-base-patch32-xnnpack-fp32 157.5 113.1 1.39 749 944 19
    ocr/paddle-ppocrv6-small-vulkan 578.2 359.7 1.61 324 556 12
    ocr/paddle-ppocrv6-small-xnnpack 468.2 425.5 1.10 534 801 23
    ocr/paddle-ppocrv6-small-xnnpack-fp32 597.5 534.7 1.12 652 888 55
    speech-to-text/whisper-base-vulkan-fp16 1656.4 1527.0 1.08 853 715 6
    speech-to-text/whisper-base-vulkan-int8 1598.8 1470.9 1.09 653 578 16
    speech-to-text/whisper-base-xnnpack-fp32 3639.6 2129.2 1.71 957 865 101
    speech-to-text/whisper-en-base-vulkan-fp16 1620.4 1481.0 1.09 839 710 7
    speech-to-text/whisper-en-base-vulkan-int8 1577.4 1434.1 1.10 647 571 7
    speech-to-text/whisper-en-base-xnnpack-fp32 3387.9 2113.5 1.60 956 862 155
    speech-to-text/whisper-en-base-xnnpack-int8 2248.3 1289.7 1.74 798 720 245
    speech-to-text/whisper-en-small-vulkan-fp16 6012.1 5124.4 1.17 1466 1335 7
    speech-to-text/whisper-en-small-vulkan-int8 5829.6 5017.8 1.16 1079 940 9
    speech-to-text/whisper-en-small-xnnpack-fp32 12837.0 7785.5 1.65 1757 1655 2524
    speech-to-text/whisper-en-small-xnnpack-int8 5950.7 4019.0 1.48 1118 1006 784
    speech-to-text/whisper-en-tiny-vulkan-fp16 722.7 674.3 1.07 631 556 7
    speech-to-text/whisper-en-tiny-vulkan-int8 701.6 660.8 1.06 472 474 7
    speech-to-text/whisper-en-tiny-xnnpack-fp32 1170.4 838.0 1.40 727 658 134
    speech-to-text/whisper-en-tiny-xnnpack-int8 786.7 574.2 1.37 620 603 52
    speech-to-text/whisper-small-vulkan-fp16 6163.4 5289.6 1.17 1469 1339 8
    speech-to-text/whisper-small-vulkan-int8 5991.9 5153.7 1.16 1091 940 16
    speech-to-text/whisper-small-xnnpack-fp32 12428.7 8701.3 1.43 1739 1661 741
    speech-to-text/whisper-tiny-vulkan-fp16 747.2 411.2 1.82 632 557 4
    speech-to-text/whisper-tiny-vulkan-int8 722.6 391.0 1.85 478 471 7
    speech-to-text/whisper-tiny-xnnpack-fp32 1358.6 762.7 1.78 730 644 136
    text-embeddings/all-minilm-l6-v2-vulkan-fp16 33.9 56.0 0.61 320 346 0
    text-embeddings/all-minilm-l6-v2-xnnpack-fp32 22.6 22.2 1.02 369 412 3
    text-embeddings/all-mpnet-base-v2-vulkan-fp16 169.0 247.8 0.68 538 471 1
    text-embeddings/all-mpnet-base-v2-vulkan-int8 1237.6 2038.3 0.61 399 390 3
    text-embeddings/all-mpnet-base-v2-xnnpack-fp32 161.4 123.5 1.31 879 770 7
    text-embeddings/clip-vit-base-patch32-text-vulkan-fp16 117.0 128.5 0.91 374 473 0
    text-embeddings/clip-vit-base-patch32-text-xnnpack-fp32 74.1 74.9 0.99 709 657 2
    text-embeddings/distiluse-base-multilingual-cased-v2-vulkan-fp16 92.4 127.3 0.73 543 492 0
    text-embeddings/distiluse-base-multilingual-cased-v2-xnnpack-fp32 86.2 73.8 1.17 1006 958 15
    text-embeddings/multi-qa-minilm-l6-cos-v1-vulkan-fp16 34.1 55.8 0.61 320 301 1
    text-embeddings/multi-qa-minilm-l6-cos-v1-xnnpack-fp32 23.1 22.3 1.04 404 451 3
    text-embeddings/multi-qa-mpnet-base-dot-v1-vulkan-fp16 168.6 248.3 0.68 541 474 1
    text-embeddings/multi-qa-mpnet-base-dot-v1-vulkan-int8 1237.2 2039.6 0.61 309 395 3
    text-embeddings/multi-qa-mpnet-base-dot-v1-xnnpack-fp32 165.3 120.1 1.38 907 805 9
    text-embeddings/paraphrase-multilingual-minilm-l12-v2-vulkan-fp16 92.9 110.1 0.84 612 524 20
    text-embeddings/paraphrase-multilingual-minilm-l12-v2-xnnpack-fp32 53.4 49.0 1.09 1045 989 4
    text-to-speech/supertonic-vulkan-fp16 5093.5 5662.0 0.90 454 590 6
    text-to-speech/supertonic-xnnpack-fp32 9349.0 5064.3 1.85 1246 1300 2231

    Method and caveats

    RNE @ms/perf-benchmarks (#1363), nativeLibsVersion 0.10.0, release build, 20 timed iterations after 3 warmups, median of the whole pipeline, host thermal gate 37C, one repeat, synthetic deterministic inputs. Only ratios beyond ~1.4x are outside this harness's spread.

    S10+ (SM-G975F, Android 12, Exynos 9820, Mali-G76) run 2026-09-23: 44 ok, 0 failed, 0 skipped.
    S20+ (SM-G986B, Android 13, Exynos 990, Mali-G77 MP11) from #1363 (comment)

    S10+ peak is total PSS on a device with ~151 MB MemFree, so mmap'd .pte pages get evicted and the S10+ MB column is a floor, not a measurement.

    Unstable rows are all XNNPACK on long work (whisper-en-small-xnnpack-fp32 IQR 2524 ms, supertonic-xnnpack-fp32 2231 ms), which if anything understates Vulkan there. Device was charging throughout, raising the thermal floor equally for both arms.

    Two --only ids matched nothing and were silently dropped by selectCases: both xnnpack-8da4w text-embedding arms. Harness bug, and it leaves the distiluse and paraphrase 8da4w comparisons incomplete.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    performanceRelated to all issues and tasks focused on improving performanceplatform: androidIssues and tasks related to Android

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions