iPhone, Android und Edge AI

Wie man ein neuronales Netz plattformübergreifend betreibt

iPhone und Android-Phones können beide neuronale Netze lokal ausführen. Die Hardware ist aber verschieden. Der richtige Ansatz ist deshalb nicht ein einziger Beschleuniger, sondern eine gemeinsame Modellstrategie mit plattformspezifischen Inferenzpfaden.

Das Problem: gleiche App-Idee, verschiedene Hardware

Auf dem iPhone laufen Modelle typischerweise über Core ML, Metal/GPU und Apple Neural Engine. Auf Android hängt die Beschleunigung vom Gerät ab: Qualcomm Snapdragon kann Hexagon NPU und Adreno GPU nutzen, andere Hersteller bringen eigene NPUs und Treiber mit. Dazu kommen CPU- Fallbacks, unterschiedliche Android-Versionen und verschiedene Runtime-APIs.

Eine App sollte deshalb nicht davon ausgehen, dass "NPU" überall dasselbe bedeutet. Besser ist eine stabile App-Schnittstelle: predict(strokes) -> topK labels. Darunter darf die Implementierung je nach Plattform unterschiedlich sein.

Empfohlene Strategie für ein kleines QuickDraw-CNN

Für DrawLa ist die Aufgabe überschaubar: Strokes werden in ein 1 x 96 x 96-Bild gerastert, ein CNN berechnet 345 Logits, und die App zeigt die wahrscheinlichsten Begriffe. Dieses Modell ist klein genug, um auf beiden Mobilplattformen lokal zu laufen.

PyTorch training checkpoint
  -> neutraler Export: ONNX
  -> iOS: Core ML / .mlpackage
  -> Android: TFLite, LiteRT oder ONNX Runtime Mobile
  -> gemeinsame App-API: predict(strokes)

Der Kern bleibt gleich: Das Modell wurde einmal trainiert und getestet. Für die Auslieferung entstehen aber zwei oder mehr optimierte Artefakte. Das ist normal und meist besser als eine Minimal-Lösung, die auf beiden Plattformen läuft, aber keine davon gut ausnutzt.

Option A: ONNX Runtime Mobile als gemeinsamer Nenner

ONNX Runtime Mobile kann ONNX-Modelle auf iOS und Android ausführen. Das ist attraktiv, wenn man ein einheitliches Modellformat behalten möchte. Für ein kleines CNN ist ONNX besonders naheliegend, weil die Operatoren einfach sind: Faltungen, Normalisierung, Aktivierung, Pooling und lineare Schicht.

Vorteil: ein neutraler Exportpfad aus PyTorch, eine Runtime-Familie für beide Plattformen. Nachteil: maximale Hardwarebeschleunigung und Binary-Größe müssen trotzdem pro Plattform getestet werden.

Quelle: ONNX Runtime Mobile.

Option B: Native Artefakte für beste Plattformpassung

Für iPhone ist Core ML der native Weg. Mit coremltools lassen sich PyTorch-Modelle direkt in Core-ML-Formate konvertieren. Apple kann dann CPU, GPU und Neural Engine passend einsetzen, sofern das Modell und die Operatoren geeignet sind.

Auf Android sind TensorFlow Lite, LiteRT oder ONNX Runtime Mobile typische Wege. Für Qualcomm-Geräte können zusätzliche SDKs oder Delegates interessant sein. Diese Variante erzeugt mehrere Artefakte, ist aber in Produktapps oft die robusteste Performance-Strategie.

Quellen: Core ML Tools, TensorFlow Lite Android.

Option C: ExecuTorch für PyTorch-nahe Deployments

ExecuTorch ist PyTorchs Deployment-Weg für On-Device-AI. Es zielt auf Mobiltelefone, Embedded-Systeme und Edge-Geräte und kann für Teams attraktiv sein, die Training, Export und Deployment möglichst lange im PyTorch-Oekosystem halten wollen.

Für DrawLa wäre ExecuTorch technisch plausibel, aber man müsste prüfen, ob der Overhead gegenüber ONNX Runtime Mobile oder Core ML / TFLite gerechtfertigt ist. Bei einem kleinen CNN ist die einfachere Runtime oft die bessere Wahl.

Quelle: ExecuTorch Documentation.

Wo NexaSDK hineinpasst

NexaSDK existiert weiterhin und bietet Android- sowie iOS/macOS-SDKs. Nach aktueller Doku ist der Android-Teil stark auf Qualcomm/Snapdragon, NPU/GPU/CPU und Modelle wie LLMs/VLMs ausgerichtet. Die iOS/macOS-Doku beschreibt NexaML, iOS 17/macOS 15 als Mindestziel und ANE-Support derzeit vor allem für ASR und Embeddings; andere Module laufen über GPU/CPU.

Für ein kleines QuickDraw-CNN ist Nexa deshalb eher ein interessanter Kontext als die erste Wahl. Wenn man LLM/VLM-Funktionen in derselben App plant, kann Nexa relevant werden. Für reine Skizzenklassifikation ist Core ML, TFLite/LiteRT, ONNX Runtime Mobile oder ExecuTorch direkter.

Quellen: Nexa Android SDK, Nexa iOS/macOS SDK.

Das eigentliche Problem ist nicht das Modell

Wer eine solche Portierung plant, unterschätzt regelmäßig, wo die Arbeit liegt. Das Modell zu konvertieren ist ein Aufruf von coremltools oder ein Export nach ONNX. Die Vorverarbeitung ist der schwierige Teil.

In DrawLa liegt die Rasterung heute in Python: Bounding Box bestimmen, zentrieren, mit acht Prozent Rand skalieren, Segmente mit zwei Pixeln Breite zeichnen. Auf dem Gerät müsste dieselbe Vorschrift in Dart, Swift oder Kotlin existieren — und zwar pixelgenau.

Genau hier entstehen Fehler, die niemand bemerkt. Rundet eine Sprache anders, weicht ein Rand um einen Pixel ab oder interpoliert eine Linienziehung minimal anders, bekommt das Modell Eingaben, wie sie im Training nie vorkamen. Es stürzt nicht ab und meldet keinen Fehler — es wird einfach schlechter. Ohne Vergleichsmessung gegen die Server-Implementierung fällt so etwas monatelang nicht auf.

Faustregel: Für jede Zeile Modellkonvertierung sollte man zehn Zeilen Test der Vorverarbeitung einplanen.

Wie man Gleichstand absichert

Das Mittel der Wahl sind Referenzdateien. Man legt eine Handvoll Zeichnungen fest, speichert für jede das auf dem Server gerasterte Bild und die zugehörigen Wahrscheinlichkeiten, und liefert beides als Testdaten mit der App aus.

fixtures/
  katze_01.strokes.json      # Eingabe
  katze_01.raster.png        # erwartetes 96×96-Bild
  katze_01.logits.json       # erwartete Ausgabe

Der Test auf jeder Plattform prüft dann zwei Dinge getrennt: Stimmt das gerasterte Bild exakt mit dem Referenzbild überein? Und liegen die Ausgaben innerhalb einer kleinen Toleranz — bei quantisierten Modellen etwas großzügiger als bei fp32?

Die Trennung ist wichtig. Weicht nur die Ausgabe ab, liegt es an Modellkonvertierung oder Quantisierung. Weicht schon das Bild ab, ist die Vorverarbeitung schuld — und dann ist jede Suche im Modell verlorene Zeit.

Der Flutter-Umweg

DrawLa ist in Flutter geschrieben, und das fügt eine Schicht hinzu, die in plattformneutralen Betrachtungen meist fehlt: Dart kann Core ML oder LiteRT nicht direkt ansprechen. Der Aufruf führt entweder über einen Platform Channel zu nativem Swift- beziehungsweise Kotlin-Code oder per FFI in eine C-Bibliothek.

Beides kostet. Ein Platform Channel serialisiert die Daten bei jedem Aufruf — bei achtmal pro Sekunde ist das nicht dramatisch, aber es ist auch nicht nichts, und es bedeutet nativen Code für zwei Plattformen zu pflegen. FFI vermeidet die Serialisierung, verlangt aber eine C-Schnittstelle und sorgfältigen Umgang mit Speicher.

Für eine Flutter-App verschiebt das die Abwägung spürbar in Richtung ONNX Runtime Mobile: ein Modellformat, eine Integration, ein Satz Tests. Der letzte Prozentpunkt Hardwarebeschleunigung, den Core ML zusätzlich herausholt, wiegt den doppelten Integrationsaufwand bei zwei Megabyte Modellgröße nicht auf.

Pragmatische Zielarchitektur für DrawLa

Die sauberste Architektur trennt drei Dinge: Training, Modellartefakte und App-Schnittstelle. Training bleibt in PyTorch. Der Export erzeugt getestete Artefakte für iOS und Android. Die App selbst kennt nur eine kleine Inferenzschnittstelle und muss nicht wissen, ob darunter Core ML, ONNX Runtime oder TFLite läuft.

shared Dart/Swift/Kotlin API
  rasterize(strokes) -> Float32/UInt8 tensor
  predict(tensor) -> topK labels

iOS backend
  Core ML model

Android backend
  ONNX Runtime Mobile or TFLite/LiteRT model

So bleibt die Produktlogik plattformübergreifend, während jede Plattform ihren besten Inferenzpfad nutzen kann. Für eine schnelle Zeichenerkennung ist das meist wichtiger als ein einzelnes Framework, das auf dem Papier alles kann.