Skip to content

feat(SemanticEmojiFilter): embedding filter with a Laya decision verifier - #37

Merged
michalharakal merged 4 commits into
developfrom
feature/semantic-emoji-filter
Oct 15, 2026
Merged

michalharakal merged 4 commits into
developfrom
feature/semantic-emoji-filter

Conversation

@michalharakal

Copy link
Copy Markdown
Contributor

New example SemanticEmojiFilter/: type what you mean, get the emoji. A Compose Multiplatform app (Android, Desktop, Web) that ranks a bilingual catalog of 208 emoji by the meaning of a free-text query with on-device sentence embeddings, plus an opt-in second stage on Desktop/Android where the Laya decision model re-checks the top hits through sk.ainet.decisions.

System 1 — embedding filter, every keystroke. MongoDB LEAF (mdbr-leaf-ir, 23M params, Apache-2.0) on the SKaiNET-transformers BERT runtime: engine WordPiece tokenizer, mean pooling, the model's 2_Dense projection to 768-d, L2. Catalog embeddings computed once (shipped precomputed for wasm, 639 KB); a sweep is a dot product over 208 rows. Calibrated logistic confidence, top tier capped at 12.

System 2 — typed decisions, when the query settles (500 ms). One BooleanProbability question per candidate ("is this emoji a good answer to the search?"), one request per item so every ~2.5 s decision is a cancellation point; verdicts stream into the badges next to the vector score, ? for an abstention. 🍕/🥓/🍩 drop for "healthy things to eat", "feeling sick" gets four confident yes.

Measured (12-core laptop, Panama Vector, DIRECT): LEAF load 345 ms, catalog embed 112 ms/item, query embed p50 81 ms / p95 98 ms (with LEAF's retrieval prompt — without it the ranking is useless), sweep 0.28 ms, RSS 512 MB. Laya: load 9.0 s, 1.8–3.6 s per decision, RSS 4.8 GB. Details and the honest findings (conservative base checkpoint, asymmetric policy 0.2/0.6, LEAF is English-first) in the README.

Build

  • Pins SKaiNET 0.58.0 / transformers 0.58.0 from Central (the other examples are on 0.40.x). Kotlin 2.4.10, Compose MP 1.11.1, AGP 9.3.1 as the siblings.
  • -PlayaVerifier=true adds the :verifier module and resolves skainet-transformers-{decisions-api,decisions-runtime,inference-laya} as 0.59.0-SNAPSHOT from mavenLocal. Draft until transformers 0.59.0 is on Maven Central; then the three coordinates move under the BOM and the flag only toggles the module.
  • scripts/fetch-model.sh + fetchModel task bundle LEAF (92 MB) into the wasm Compose resources only (gitignored); Desktop/Android download per file at first run. scripts/generate-catalog.py builds catalog.json from CLDR 47 annotations (en + de) and the curated selection. CLI: bench | embed-catalog | calibrate | golden | query | tokens | verify.
  • Tests: shared 11 (jvmTest + testAndroidHostTest): calibration fit, index/bin round trip, view model debounce + stable-query trigger, catalog schema + digest, ranking goldens (no model needed), model-gated parity; verifier 2 on a scripted adapter. CI workflow semanticemojifilter-ci.yml mirrors KernelRace (no verifier).
  • No iOS: bert/laya/decisions publish no Apple targets. Screenshots still to be taken (docs/screenshots/grid.png referenced).

The verifier's model source is one interface (DecisionAdapterSource); a SKaiNET decision cartridge replaces it later without touching UI or view model.

…fier

New example: a Compose Multiplatform app (Android, Desktop, Web) that ranks 208
emoji by the meaning of a typed query with on-device sentence embeddings, and an
opt-in second stage on Desktop/Android where the Laya decision model re-checks
the top hits through sk.ainet.decisions.

System 1: MongoDB LEAF (mdbr-leaf-ir, Apache-2.0) on the SKaiNET-transformers
BERT runtime — engine WordPiece tokenizer, mean pooling, 2_Dense projection to
768-d, L2; the query carries LEAF's retrieval prompt. Catalog embeddings are
computed once (shipped precomputed for wasm); the sweep is a dot product.
Calibrated logistic confidence (balanced fit, F1-optimal threshold), top tier
capped at 12. Debounced 120 ms, mapLatest.

System 2 (-PlayaVerifier=true, transformers 0.59.0 / 0.59.0-SNAPSHOT from
mavenLocal): one BooleanProbability question per candidate, one request per
item so every decision is a cancellation point, verdicts stream into the badges,
"?" for an abstention. Policy: reject below 0.2, accept from 0.6 (the base
checkpoint is conservative). The model source is one interface
(DecisionAdapterSource) a decision cartridge can replace later.

Catalog from CLDR 47 annotations (en + de) via scripts/generate-catalog.py and
a curated selection; LEAF fetched by scripts/fetch-model.sh into the wasm
Compose resources only. CLI: bench | embed-catalog | calibrate | golden | query
| tokens | verify. Measured numbers in the README. Tests: shared 11 (jvm +
Android host), verifier 2. CI workflow mirrors KernelRace. No iOS (bert, laya
and decisions publish no Apple targets).
…erifier in CI

The decisions and laya coordinates move under the BOM, the mavenLocal gate goes, and the
CI workflow builds and tests the verifier module behind -PlayaVerifier=true.
@michalharakal
michalharakal marked this pull request as ready for review October 13, 2026 21:32
Engine 0.59.0 adds erf and gelu(approximate) as op metadata; transformers
0.60.0 runs the Laya verifier's exact GELU through that op instead of a
host-side loop per FFN per layer. Catalog, BuildInfo and README follow.
Base targets and the -PlayaVerifier=true set build and test green.
@michalharakal
michalharakal merged commit ca97c6e into develop Oct 15, 2026
1 of 3 checks passed
@michalharakal
michalharakal deleted the feature/semantic-emoji-filter branch October 15, 2026 14:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant