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
.serializedContext Binaries (C++ API, siehekotlin/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_jobmit ~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
- -