E-Ink Dirty-Region-Generator — NPU-ready Modelle

Event-getriebene, E-Ink-artige Bildaktualisierung auf dem Snapdragon NPU. Das Modell sieht eine Miniatur des ganzen Bildschirms, entscheidet selbst, welche 128×128-Kacheln sich ändern müssen, und generiert nur diese neu. Dazwischen: null Rechenlast — kein Frame-Loop, keine GPU-Last bei statischem Bild.

Gebaut & kompiliert für: Samsung Galaxy S26 Ultra (Snapdragon 8 Elite Gen 5 for Galaxy, SM8850, Hexagon NPU v81) via Qualcomm AI Hub. Läuft genauso auf S25/S24 und anderen QNN-Geräten (Hexagon v68+).

🤗 Datensatz: KillerBoss/eink-dirty-region-dataset

Architektur (Conditional Computation, 871.290 Parameter)

Modell Parameter Input Output
Decision Head 94.473 (1, 7, 44, 96) — Screen-Thumbnail 64×30 + Touch-Heatmap + 5× Action-One-hot Logits (1, 1, 11, 12) — Dirty-Tile-Maske über ein 12×11-Gitter von 128×128-Kacheln
Patch Generator 776.817 (1, 2, 128, 128) Kachel-Kontext + (1, 1, 32, 32) Global-Context (1, 1, 128, 128) — neue Kachel-Pixels (linear, clamp 0..1)

Pipeline pro Event:

Statisches Bitmap (kein Frame-Loop!)
   │ Touch (x, y) + Aktionstyp  ──►  Decision Head  (0,11 ms)
   │                                   │ wählt Kacheln aus 12×11-Gitter
   │                                   ▼
   │                     Patch Generator je Kachel (0,24 ms)
   │                                   │ neue Kachel-Pixels
   │                                   ▼
   └── Delta-Compositing: nur Pixel mit |neu−alt| > 25 werden geschrieben
                                       └──► Canvas.drawBitmap(dirtyRect)

Messwerte (echtes Gerät, Qualcomm AI Hub Cloud-Device, FP16/HTP)

Modell NPU-Inferenz Peak-Speicher (Inferenz)
Decision Head 0,109 ms ~149 MB Kontext
Patch Generator 0,24 ms pro Kachel ~147 MB Kontext

→ Typischer Touch-Event (1 Decision + 3–8 Kacheln) kostet ≈ 1–2 ms NPU-Zeit. Selbst ein Vollbild-Update (66 Kacheln) läge bei ~16 ms. Quelle: npu/profile_results.json.

Dateien

models/                     PyTorch-Gewichte + verifizierte ONNX-Modelle (Opset 17)
  decision_head.pt/.onnx        Input (1,7,44,96) → Logits (1,1,11,12)
  patch_generator.pt/.onnx      Input (1,2,128,128)+(1,1,32,32) → (1,1,128,128)
npu/                        Kompilierte NPU-Artefakte (AI Hub, Galaxy S26 Ultra)
  decision_head_qnn.serialized  QNN Context Binary (NPU, FP16)
  patch_generator_qnn.serialized
  decision_head.tflite          TFLite-Fallback (QNN-Delegate nutzbar)
  patch_generator.tflite
  profile_results.json          Rohe Profiling-Daten vom Gerät
kotlin/EinkEngine.kt        Android-Integrationsgerüst (TFLite+QNN-Delegate & QNN-SDK-Skizze)
scripts/                    Reproduzierbare Pipeline (Dataset → Training → Eval → Compile → Profile)
eval/                       Test-Metriken + End-to-End-Pipeline-Bilder

Testergebnisse (150 frische, ungesehene Szenen, neuer Seed)

Metrik Wert
Decision Precision / Recall / F1 / IoU 0,543 / 0,309 / 0,394 / 0,246 (thr 0,60)
Generator L1 (delta-composited) 0,033 (≈ 8 Graustufen)
Generator PSNR 24,3 dB

Ehrliche Einordnung: Der Decision Head wählt zuverlässig die Kern-Kacheln (konservativ, Precision > Recall — gut fürs NPU-Budget; über thr kann die App Precision/Recall live justieren). Der Generator beherrscht den E-Ink-Stil (Grauabstufung, Dithering-Look, Text-/Box-Priors) und Ink/Erase-Updates gut; bei reveal/morph reproduziert er plausible statt pixel-exakter Inhalte — dafür wäre mehr Kapazität/Daten nötig. Das Delta-Compositing stellt sicher, dass sich unveränderte Pixel niemals verschlechtern.

Nutzung (ONNX, CPU-Referenz)

import numpy as np, onnxruntime as ort

# Decision Head: Thumbnail (7 Ebenen, /255-normalisiert) → Dirty-Tile-Logits
sess_d = ort.InferenceSession("models/decision_head.onnx")
logits = sess_d.run(None, {"input": screen_7ch.astype(np.float32)})[0]  # (1,1,11,12)
tiles  = logits[0, 0] > 0.60            # Threshold 0.60 (Precision-first)

# Patch Generator: gewählte Kacheln neu generieren
sess_g = ort.InferenceSession("models/patch_generator.onnx")
patch  = sess_g.run(None, {"tile_ctx": tile_ctx, "glob_ctx": glob_ctx})[0]
patch  = np.clip(patch, 0.0, 1.0)       # Clip nur auf Bild-Output!

Integrationshinweise (Android / NPU)

  • Schnellster Start: TFLite-Dateien + Qualcomm QNN Delegate (Hexagon). Alternativ QNN-SDK / SNPE mit den .serialized Context Binaries (C++ API, siehe kotlin/EinkEngine.kt).
  • Inputs sind /255-normalisiert (Float 0..1). Generator-Ausgabe linear → clamp(0,1).
  • Aktionstyp als Input: 5 One-hot-Ebenen (INK, ERASE, MORPH, REVEAL, NONE — Reihenfolge fix). Die App kennt die Aktion selbst; für autonomes Verhalten NONE + Timer alle 100–300 ms.
  • Delta-Compositing nicht vergessen — es ist die Pixelebene des „update only what changed“-Prinzips.
  • INT8 statt FP16: Artefakte sind FP16 (beste Qualität, HTP-nativ). Für INT8: AI-Hub-submit_quantize_job mit ~200 Kalibrierungs-Inputs, erwartet ~30–40 % weniger Latenz & Größe.

Reproduktion

pip install torch numpy onnx onnxruntime qai-hub
python scripts/gen_dataset.py     # synthetischer Datensatz → data/eink/*.npz
python scripts/train.py --stage dec
python scripts/train.py --stage gen
python scripts/eval_export.py     # 150 ungesehene Szenen, Metriken + PNGs
python scripts/ai_hub_compile.py  # QNN Context Binary + TFLite via Qualcomm AI Hub
python scripts/profile_npu.py     # On-Device-Latenz auf echter Hardware

Zitierweise

@misc{eink_dirty_region_2026,
  author = {KillerBoss},
  title = {E-Ink Dirty-Region-Generator: event-getriebene Bildaktualisierung auf dem Hexagon NPU},
  year = {2026},
  publisher = {Hugging Face},
  url = {https://huggingface.co/KillerBoss/eink-npu-dirty-region}
}
Downloads last month
-
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Dataset used to train KillerBoss/eink-npu-dirty-region