Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ name: CI
# under the SAME command a developer runs: no bypass flags, both reproducibility
# gates (dependency verification + strict dependency locking) enforced.
#
# STATUS: green. All four SDK artifacts are live on Maven Central at 0.20.17
# STATUS: green. All four SDK artifacts are live on Maven Central at 0.20.18
# runanywhere-sdk, runanywhere-llamacpp, runanywhere-onnx and
# runanywhere-qhexrt-android.

Expand Down
37 changes: 34 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@ Or open the project in Android Studio and run the **app** configuration, or inst

## SDK dependency

All SDK artifacts come from Maven Central under the group `io.github.sanchitmonga22`, pinned by the single `runanywhere` version in `gradle/libs.versions.toml` (currently **0.20.17**). Nothing is declared as a local AAR or project path:
All SDK artifacts come from Maven Central under the group `io.github.sanchitmonga22`, pinned by the single `runanywhere` version in `gradle/libs.versions.toml` (currently **0.20.18**). Nothing is declared as a local AAR or project path:

```kotlin
// gradle/libs.versions.toml
runanywhere = "0.20.17"
runanywhere = "0.20.18"

// app/build.gradle.kts
implementation(libs.runanywhere.sdk) // io.github.sanchitmonga22:runanywhere-sdk
Expand All @@ -75,7 +75,7 @@ implementation(libs.runanywhere.onnx) // io.github.sanchitmonga22:runanywhe
implementation(libs.runanywhere.qhexrt) // io.github.sanchitmonga22:runanywhere-qhexrt-android
```

| Coordinate | Role | On Maven Central at 0.20.17 |
| Coordinate | Role | On Maven Central at 0.20.18 |
|---|---|---|
| `runanywhere-sdk` | Core SDK + commons native libraries | ✅ |
| `runanywhere-llamacpp` | LlamaCPP backend (LLM, VLM) | ✅ |
Expand Down Expand Up @@ -118,6 +118,37 @@ Then confirm the result the same way CI will, with no bypass flags:
CI=true ./gradlew :app:assembleDebug # + LockMode.STRICT
```

### Testing an unreleased SDK build (developer-only)

To try a change from a [`runanywhere-sdks`](https://github.com/RunanywhereAI/runanywhere-sdks)
checkout before it is on Maven Central, publish it to `~/.m2` and point this repo at it:

```bash
# In the monorepo — publishes io.github.sanchitmonga22:*:<core/VERSION>
(cd path/to/runanywhere-sdks/bindings/kotlin && ./gradlew publishToMavenLocal)

# Here — opt in per invocation, and relax verification for that one run
./gradlew :app:assembleDebug \
-Prunanywhere.useLocalSdkAars=true \
--dependency-verification=lenient
```

`-Prunanywhere.useLocalSdkAars=true` adds `mavenLocal()` ahead of Google and Maven
Central, scoped by `content { includeGroup("io.github.sanchitmonga22") }` so a stale
`~/.m2` copy of any *other* dependency cannot shadow the verified one.

`--dependency-verification=lenient` is **required** alongside it, and is not a bypass
being smuggled in. A locally published AAR has the same coordinates as the released one
but different bytes, so it can never match the sha256 in `gradle/verification-metadata.xml`.
Without the flag the build stops with `artifacts failed verification`, which is the gate
working correctly. Relax it per invocation like this; do **not** add a
`<trust group="io.github.sanchitmonga22"/>` entry to the committed metadata, because that
would permanently un-pin the four artifacts the gate exists to pin.

Both flags are per-invocation only. Never commit `runanywhere.useLocalSdkAars` to
`gradle.properties` and never set it in CI: `ci.yml` exists to prove a clean clone
resolves the SDK from Maven Central, and a local AAR would make that proof vacuous.

---

## Continuous integration
Expand Down
8 changes: 4 additions & 4 deletions app/gradle.lockfile
Original file line number Diff line number Diff line change
Expand Up @@ -233,10 +233,10 @@ com.tom-roush:pdfbox-android:2.0.27.0=debugAndroidTestCompileClasspath,debugAndr
commons-codec:commons-codec:1.17.1=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
commons-io:commons-io:2.16.1=androidLintTool,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action
commons-logging:commons-logging:1.2=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
io.github.sanchitmonga22:runanywhere-llamacpp:0.20.17=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
io.github.sanchitmonga22:runanywhere-onnx:0.20.17=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
io.github.sanchitmonga22:runanywhere-qhexrt-android:0.20.17=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
io.github.sanchitmonga22:runanywhere-sdk:0.20.17=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
io.github.sanchitmonga22:runanywhere-llamacpp:0.20.18=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
io.github.sanchitmonga22:runanywhere-onnx:0.20.18=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
io.github.sanchitmonga22:runanywhere-qhexrt-android:0.20.18=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
io.github.sanchitmonga22:runanywhere-sdk:0.20.18=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
io.grpc:grpc-api:1.57.2=unified-test-platform-core
io.grpc:grpc-api:1.69.1=unified-test-platform-android-test-plugin-host-emulator-control
io.grpc:grpc-context:1.57.2=unified-test-platform-core
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ internal object ModelCatalog {
SingleFileModel("cosmos3_edge_vlm", "Cosmos3-Edge Vision (HNPU)", "https://huggingface.co/runanywhere/cosmos3_edge_HNPU/cosmos3-edge-vlm.json", QHEXRT, MULTIMODAL, 3505000000L, contextLength = 2_048),
// InternVL3.5
SingleFileModel("internvl3_5_1b", "InternVL3.5 1B (HNPU)", "https://huggingface.co/runanywhere/internvl3_5_1b_HNPU", QHEXRT, MULTIMODAL, 3_067_933_894L, contextLength = 512),
SingleFileModel("lfm2_5_vl_3b", "LFM2.5-VL 3B (HNPU)", "https://huggingface.co/runanywhere/lfm2_5_vl_3b_HNPU/lfm2-5-vl-3b.json", QHEXRT, MULTIMODAL, 4_168_394_864L, contextLength = 512),

// --- QHexRT / HNPU: Embeddings ----------------------------------------
// Gemma
Expand Down Expand Up @@ -515,7 +516,7 @@ internal object ModelCatalog {
),
),
),
// LFM2-VL (Liquid AI)
// LFM2-VL / LFM2.5-VL (Liquid AI)
MultiFileModel(
"lfm2-vl-450m-q8_0", "LFM2-VL 450M", LLAMA, MULTIMODAL, 600_000_000,
files = listOf(
Expand All @@ -529,6 +530,51 @@ internal object ModelCatalog {
),
),
),
// Q4_K_M, matching the Qwen2.5-VL 3B row beside it and the LFM2.5 2.6B LLM
// row this VLM is built on: one quantization per model, and Q4_K_M is what
// every other 3B-class GGUF row in this catalog ships. The mmproj is Q8_0
// because the vision tower is quantization-sensitive; that is the same
// Q4_K_M-weights + Q8_0-mmproj pairing the Qwen2-VL, Qwen2.5-VL and Gemma 4
// E4B rows use, and the only mmproj quant LiquidAI publishes below F16.
//
// Pinned to a revision rather than `main` because the per-file sizeBytes
// below are exact (verified by Content-Range against this sha, 1_674_454_240
// + 583_109_120): declared bytes feed the post-download size guard and the
// progress bar's denominator, so a silent upstream re-upload would break
// both. Every row in this file that declares exact sizes pins its sha.
//
// DISTINCT from the `lfm2_5_vl_3b` QHEXRT row in npuCatalog: different
// framework, different artifact. Not a duplicate; do not merge them.
//
// memoryBytes is RAM, not transfer: 2.26 GB of weights plus KV cache and
// the image-encoder activations, so it is stated separately from the exact
// downloadBytes rather than defaulting to it.
MultiFileModel(
"lfm2.5-vl-3b-q4_k_m",
"LFM2.5-VL 3B Q4_K_M",
LLAMA,
MULTIMODAL,
memoryBytes = 3L * 1_024L * 1_024L * 1_024L,
downloadBytes = 2_257_563_360L,
files = listOf(
ModelFile(
"https://huggingface.co/LiquidAI/LFM2.5-VL-3B-GGUF/resolve/" +
"3e0e828198e2abb75a957ad823f5d691c13f0f28/LFM2.5-VL-3B-Q4_K_M.gguf",
"LFM2.5-VL-3B-Q4_K_M.gguf",
1_674_454_240,
),
ModelFile(
"https://huggingface.co/LiquidAI/LFM2.5-VL-3B-GGUF/resolve/" +
"3e0e828198e2abb75a957ad823f5d691c13f0f28/mmproj-LFM2.5-VL-3B-Q8_0.gguf",
"mmproj-LFM2.5-VL-3B-Q8_0.gguf",
583_109_120,
),
),
),
// NOTE: LFM2.5-VL-3B-MLX-4bit is intentionally NOT registered. MLX is an
// Apple-silicon runtime; there is no MLX engine on Android (see the
// Ternary-Bonsai note in `llm` above, and HuggingFaceHubClient's GGUF-only
// search sets). The GGUF row above is the runnable variant on this platform.
// Gemma
MultiFileModel(
"gemma-4-e2b-it-q8_0", "Gemma 4 E2B IT Q8_0 (Experimental)", LLAMA, MULTIMODAL, 3_000_000_000,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.runanywhere.runanywhereai

import ai.runanywhere.proto.v1.InferenceFramework
import ai.runanywhere.proto.v1.ModelCategory
import ai.runanywhere.proto.v1.ModelFileRole
import ai.runanywhere.proto.v1.ModelInfo
import ai.runanywhere.proto.v1.ModelSource
import com.runanywhere.runanywhereai.data.ModelCatalog
Expand Down Expand Up @@ -159,6 +160,7 @@ class ModelCatalogTest {
assertEquals(1_024, byId.getValue("qwen3_5_2b").contextLength)
assertEquals(1_024, byId.getValue("qwen3_5_4b").contextLength)
assertEquals(512, byId.getValue("internvl3_5_1b").contextLength)
assertEquals(512, byId.getValue("lfm2_5_vl_3b").contextLength)
}

@Test
Expand Down Expand Up @@ -221,6 +223,59 @@ class ModelCatalogTest {
}
}

/**
* The llama.cpp LFM2.5-VL 3B row is a VLM, so it is only usable if BOTH the
* weights and the separately published mmproj vision projector are declared —
* with just the weights it loads text-only and fails silently on image input.
*
* Its per-file `sizeBytes` are exact (verified by HTTP Content-Range against
* the pinned revision) and feed both the post-download size guard and the
* progress bar's denominator, so the revision pin, the two sizes and their sum
* are asserted rather than left to drift against `main`.
*/
@Test
fun lfm25Vl3bShipsWeightsAndMmprojAtExactPinnedSizes() {
val model =
ModelCatalog.models.single { it.id == "lfm2.5-vl-3b-q4_k_m" } as MultiFileModel

assertEquals(InferenceFramework.INFERENCE_FRAMEWORK_LLAMA_CPP, model.framework)
assertEquals(ModelCategory.MODEL_CATEGORY_MULTIMODAL, model.category)
assertEquals(3L * 1_024L * 1_024L * 1_024L, model.memoryBytes)
assertEquals(2_257_563_360L, model.downloadBytes)
assertEquals(model.downloadBytes, model.files.sumOf { it.sizeBytes ?: 0 })

val pin =
"https://huggingface.co/LiquidAI/LFM2.5-VL-3B-GGUF/resolve/" +
"3e0e828198e2abb75a957ad823f5d691c13f0f28"

val descriptors = model.descriptors()
assertEquals(2, descriptors.size)

val weights = descriptors.first()
assertEquals("LFM2.5-VL-3B-Q4_K_M.gguf", weights.filename)
assertEquals("$pin/LFM2.5-VL-3B-Q4_K_M.gguf", weights.url)
assertEquals(1_674_454_240L, weights.size_bytes)
assertEquals(ModelFileRole.MODEL_FILE_ROLE_PRIMARY_MODEL, weights.role)

val mmproj = descriptors.last()
assertEquals("mmproj-LFM2.5-VL-3B-Q8_0.gguf", mmproj.filename)
assertEquals("$pin/mmproj-LFM2.5-VL-3B-Q8_0.gguf", mmproj.url)
assertEquals(583_109_120L, mmproj.size_bytes)
assertEquals(ModelFileRole.MODEL_FILE_ROLE_COMPANION, mmproj.role)

// Distinct from the QHexRT/HNPU bundle of the same model — different
// framework, different artifact, different id. Not a duplicate.
assertTrue(ModelCatalog.npuCatalog.any { it.id == "lfm2_5_vl_3b" })

// MLX is Apple-silicon only; there is no MLX engine on Android, so the
// LiquidAI/LFM2.5-VL-3B-MLX-4bit repo must never be referenced by any row.
assertFalse(
"LFM2.5-VL-3B MLX has no Android runtime and must not be registered",
ModelCatalog.models.filterIsInstance<MultiFileModel>()
.any { row -> row.files.any { it.url.contains("MLX", ignoreCase = true) } },
)
}

@Test
fun pickerShowsOnlyQhexrtRowsReturnedByNativeRegistration() {
val cpu = ModelInfo(
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ kotlin = "2.4.0"
# RunAnywhere SDK — consumed entirely from Maven Central
# (group io.github.sanchitmonga22). One version drives the core SDK and every
# backend module; they are published together and must never be mixed.
runanywhere = "0.20.17"
runanywhere = "0.20.18"

# AndroidX core
coreKtx = "1.19.0"
Expand Down
32 changes: 32 additions & 0 deletions gradle/verification-metadata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2145,6 +2145,14 @@
<sha256 value="3dfeee073c2795bfb6c323ec0ac672864c9160c2c67fc064b043792880f86f99" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.github.sanchitmonga22" name="runanywhere-llamacpp" version="0.20.18">
<artifact name="runanywhere-llamacpp-0.20.18.aar">
<sha256 value="6d2d9504fc3124692d24dec91bb7c9132d2cf1903c1646b2db9792eaf1333fa8" origin="Generated by Gradle"/>
</artifact>
<artifact name="runanywhere-llamacpp-0.20.18.module">
<sha256 value="b75e3391902dd7a221067226ee245ec7c2ffe937f354422edd791500324efc96" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.github.sanchitmonga22" name="runanywhere-onnx" version="0.20.17">
<artifact name="runanywhere-onnx-0.20.17.aar">
<sha256 value="0e6c4a13784ee4b6a8f335b7d5065a7a4ec387b44096acba7188d91f533e3ac5" origin="Generated by Gradle"/>
Expand All @@ -2153,6 +2161,14 @@
<sha256 value="7da5d29c3a9023b071003089ec9554c6798ef8b439f08395aed881c6aaa5bbd6" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.github.sanchitmonga22" name="runanywhere-onnx" version="0.20.18">
<artifact name="runanywhere-onnx-0.20.18.aar">
<sha256 value="44d28d165ba23a9ef5f76dd2cf81d999cde415017dd52579f3ee69b424f1245f" origin="Generated by Gradle"/>
</artifact>
<artifact name="runanywhere-onnx-0.20.18.module">
<sha256 value="aafa54ac3062c5343a6c8a738ff62043772c7d4af6dc25f50b3e4fa21e9ccb74" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.github.sanchitmonga22" name="runanywhere-qhexrt-android" version="0.20.17">
<artifact name="runanywhere-qhexrt-android-0.20.17.aar">
<sha256 value="0c3bce03db3d95a0c3c0911f1312555f5538b97afd0ad7595d19cc86b7ba5a5a" origin="Generated by Gradle"/>
Expand All @@ -2161,6 +2177,14 @@
<sha256 value="69a6561ccafbc898c870f7be7d8dd47b6fd87a880b9ed91ded2426eec0fdf0bc" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.github.sanchitmonga22" name="runanywhere-qhexrt-android" version="0.20.18">
<artifact name="runanywhere-qhexrt-android-0.20.18.aar">
<sha256 value="b4311fc685aa05761d7b73a26d71b47482d45d43a514a8c83d09ab7b49ca92e2" origin="Generated by Gradle"/>
</artifact>
<artifact name="runanywhere-qhexrt-android-0.20.18.module">
<sha256 value="2688a8a12de20f696edca2606862e58b57cf3d03017554bfda3bd216ac13e28b" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.github.sanchitmonga22" name="runanywhere-sdk" version="0.20.17">
<artifact name="runanywhere-sdk-0.20.17.aar">
<sha256 value="aabf59e748b56ea9be24b80d2540836e9890263f1bc6bc9546233a8836d7829e" origin="Generated by Gradle"/>
Expand All @@ -2169,6 +2193,14 @@
<sha256 value="748ac4142b796fad2ce7fe31dd42fc42bf12afc67a79224370ad177ea937ca12" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.github.sanchitmonga22" name="runanywhere-sdk" version="0.20.18">
<artifact name="runanywhere-sdk-0.20.18.aar">
<sha256 value="e30cd062295aae4b9c8e1d9045e5e802f6a75b3d59a183acaf607def4cd406ac" origin="Generated by Gradle"/>
</artifact>
<artifact name="runanywhere-sdk-0.20.18.module">
<sha256 value="a101ea70603e46595c562827fb413732266a97d43ca0e32adfdef6440efc546e" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.grpc" name="grpc-api" version="1.57.2">
<artifact name="grpc-api-1.57.2.jar">
<sha256 value="42b72e6572c084055ac3ce03e6efe433eb05ef620b3daf5136a4359fc72cc3e1" origin="Generated by Gradle"/>
Expand Down
Loading