Dados e ML no dispositivo¶
O tempestroid roda CPython de verdade no aparelho (não um subset). Isso abre
uma porta que apps Android normais não têm: rodar a stack científica de Python —
numpy, scikit-learn, polars, inferência ONNX — dentro do app, no
mesmo interpretador que constrói a UI.
Esta página mostra o que já roda, como você habilita cada peça, e onde estão os limites. É o Trilho G do roadmap.
Onde isso foi provado
Tudo aqui é device-verificado num emulador x86_64 (a stack de teste hardware-free; veja Rodar no dispositivo). O alvo de ship é arm64 — o caminho é o mesmo (as receitas são por-ABI), mas as wheels arm64 das libs pesadas ainda estão pendentes. O status de cada peça está na tabela no fim.
Dois caminhos¶
Há duas formas de uma lib nativa rodar no device, e o tempestroid usa as duas:
- Wheel CPython cross-compilada — a lib é compilada como uma wheel Android
(mesmo padrão do
pydantic-core) e roda no interpretador embarcado. É o caminho denumpy,scipy,scikit-learnepolars. - Biblioteca nativa + ponte — a lib roda como código nativo (AAR Kotlin/C++)
e o Python fala com ela pela ponte JNI. É o caminho da inferência ONNX
(
onnxruntime-android), que evita compilar a wheel C++ pesada.
Por que isso importa
Cross-compilar uma wheel resolve import x puro; a ponte nativa evita o peso
de compilar engines C++ gigantes. A decisão é por-lib, registrada em
docs/research/.
numpy¶
numpy é o caminho crítico — quase toda a stack depende dele. A wheel Android é
cross-compilada com cibuildwheel (receita toolchain/build_numpy_x86.sh).
import numpy as np
arr = np.arange(1, 11, dtype=np.float64)
total = float(arr.sum()) # 55
dot = float(np.dot(arr, arr)) # 385
Rode no emulador com o exemplo pronto:
make stage-x86 # estaga o CPython x86_64 + base (numpy incluso)
make apk-x86 # builda o APK do emulador
tempest serve examples/onnxspike/app.py # mostra "numpy OK" no device
Polars — o DataFrame do device¶
Para dados tabulares, use Polars, não pandas. Polars é um core em Rust
(classe pydantic-core), cross-compila para uma wheel abi3 (uma wheel serve
todo CPython ≥3.10), tem core sem dependências e lê/escreve CSV/JSON/Parquet
nativamente — sem numpy/pyarrow obrigatórios.
import io
import polars as pl
frame = pl.DataFrame({"team": ["a", "b", "a"], "points": [10, 7, 3]})
totals = frame.group_by("team").agg(pl.col("points").sum())
# Reading/writing: round-trip por CSV, tudo em memória
csv_text = frame.write_csv()
restored = pl.read_csv(io.StringIO(csv_text))
Habilite (opt-in — o core Rust é grande):
make stage-polars # estaga a wheel polars-runtime-32 (abi3) + o wrapper
make apk-x86
tempest serve examples/polarsspike/app.py
pandas é desencorajado
Se o seu app importa pandas, o loader emite um aviso orientando a usar
Polars (tempestroid/cli/advisories.py). pandas arrasta extensões Cython/C
pesadas + deps científicas pro APK e é chato de cross-compilar; Polars é a
escolha que cabe no device. O import ainda roda no simulador — é um aviso,
não um erro.
Receita do build
toolchain/build_polars_x86.sh cross-compila o polars-runtime-32 via
maturin. Os detalhes (features Android-safe, strip, o blocker do clipboard)
estão em docs/research/g-polars-feasibility.md.
scikit-learn + scipy¶
ML clássico roda no device. scipy e scikit-learn cross-compilam só com
clang, zero Fortran (o "calcanhar" histórico sumiu upstream: OpenBLAS em C +
scipy fortran-free), com OpenMP via a libomp do NDK.
import numpy as np
from sklearn.linear_model import LogisticRegression
x = np.arange(0, 10, dtype=np.float64).reshape(-1, 1)
y = (x.ravel() >= 5).astype(np.int64)
model = LogisticRegression().fit(x, y)
preds = model.predict(np.array([[2.0], [8.0]])) # [0, 1]
Habilite (opt-in — scipy + sklearn + deps são pesados):
make stage-science # scipy + scikit-learn + joblib/threadpoolctl/narwhals
make apk-x86
tempest serve examples/sklearnspike/app.py
Inferência ONNX (visão)¶
Para rodar modelos .onnx (classificação, detecção, segmentação) use o
ort-vision-sdk com o
backend nativo do tempestroid: o SDK roda a inferência pelo AAR
onnxruntime-android pela ponte (caminho 2), com o pré/pós em numpy no
Python.
from ort_vision_sdk import Classifier
from tempestroid.native.inference import AarBackend
clf = Classifier("squeezenet1.1.onnx", backend=AarBackend())
result = clf.predict("banana.jpg")[0] # top-1 no device
O caminho de imagem não precisa de OpenCV: tempestroid.native.image.decode_image
decodifica via BitmapFactory do host → ndarray. Modelos podem ser embutidos
ou baixados+cacheados (tempestroid.native.model_store.ensure_model, com
verificação sha256, fora da UI thread). tempest optimize model.onnx -q int8
quantiza + converte pra .ort no host (build time).
Receitas de staging (resumo)¶
As libs pesadas são opt-in — o build padrão não carrega nenhuma. Cada uma tem uma receita por-ABI:
| Lib | Habilitar | Wheel/recipe |
|---|---|---|
| numpy | make stage-x86 (base) |
toolchain/build_numpy_x86.sh |
| polars | make stage-polars |
toolchain/build_polars_x86.sh |
| scipy + sklearn | make stage-science |
toolchain/build_{openblas,scipy,sklearn}_x86.sh |
| onnxruntime | feature vision no build |
AAR onnxruntime-android (sem wheel) |
Tamanho do APK¶
A stack científica é pesada. O Trilho G7 corta o que dá com segurança:
noCompress("so")— as.sode assets não são comprimidas (o compressor do AGP crasha em.sogrande; elas são extraídas em runtime de qualquer forma).- strip — as
.soRust/C saem stripadas (a do polars cai de ~2.4 GB pra ~200 MB). - ABI única — só a
.soda ABI alvo entra (o build não vaza a outra ABI). - trim do numpy —
numpy/tests,f2py, stubs*.pyi(runtime-dead) saem.
Status por peça¶
| Peça | x86_64 (emulador) | arm64 (ship) |
|---|---|---|
| numpy | ✅ import + compute | ⏳ rebuild |
| scipy + scikit-learn | ✅ import + fit/predict |
⏳ rebuild |
| Polars | ✅ build + import (op-path PySeries pendente) |
⏳ rebuild |
| ONNX (ort-vision-sdk via AAR) | ✅ Classifier real (squeezenet) |
⏳ device físico |
| pandas | 🚫 desencorajado → Polars | 🚫 |
Recap¶
- O tempestroid roda CPython real no device → a stack científica de Python roda dentro do app.
- Polars é o DataFrame do device (Rust, abi3, leve); pandas é desencorajado (aviso automático).
numpy,scipy/scikit-learne a inferência ONNX (via AAR) rodam no emulador hoje; cada lib pesada é opt-in por uma receitamake stage-*.- O Trilho G7 corta o APK (noCompress/strip/ABI-única/trim).
- Tudo provado no emulador x86_64; arm64 (o ship real) é o próximo passo.