Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
9c69b3b
feat: runtime LoRA control, batch embeddings, UTF-8-safe JNI strings
claude Jul 5, 2026
b605f8b
Add NativeServer attach mode, in-JVM router mode, and GGUF quantizer
claude Jul 5, 2026
3fa7c79
langchain4j: tool calling, JSON mode, multimodal user input
claude Jul 5, 2026
b4f78bc
Android AAR (llama-android) + Kotlin coroutines facade (llama-kotlin)
claude Jul 5, 2026
3edfc3e
README: complete the Similar Projects section with session research l…
claude Jul 5, 2026
1ff8b5f
Typed Java API for router mode (RouterClient + RouterModel)
claude Jul 5, 2026
1f57537
RouterClient: generate equals/hashCode via Lombok (project convention)
claude Jul 5, 2026
6434b63
GGUF inspector, streaming tool calls (langchain4j), Session fork/rewind
claude Jul 5, 2026
725f570
Android x86_64 ABI + on-emulator CI (multi-ABI AAR, runtime-tested)
claude Jul 5, 2026
0e7ab86
CI fixes: REUSE headers for new files + skip-safe router test teardown
claude Jul 5, 2026
25d3a3e
RouterClient: replace deprecated URL(String) with URI.create(...).toU…
claude Jul 5, 2026
f77262b
CI fixes: R8 dontwarn for compile-only refs + emulator script-per-line
claude Jul 5, 2026
3502e2e
Fix Android dlopen failure: drop libomp.so/libc++_shared.so DT_NEEDED
claude Jul 5, 2026
99359a0
Upgrade llama.cpp from b9870 to b9873
claude Jul 5, 2026
a4a0b52
Upgrade llama.cpp from b9873 to b9876
claude Jul 5, 2026
a18376f
Mark the PIT audio-fixture gotcha as resolved (gate is hermetic)
claude Jul 5, 2026
260ddb0
Add audio sample.wav
bernardladenthin Jul 5, 2026
a843ea8
Audio fixture wiring + REUSE coverage; promote emulator job to releas…
claude Jul 5, 2026
4b457f2
Clean TODO.md: remove completed items from the Open sections
claude Jul 5, 2026
ad48793
Upgrade llama.cpp from b9876 to b9878
claude Jul 5, 2026
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
26 changes: 26 additions & 0 deletions .github/android-consumer-test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!--
SPDX-FileCopyrightText: 2026 Bernard Ladenthin <bernard.ladenthin@gmail.com>

SPDX-License-Identifier: MIT
-->
# llama-android consumer-test fixture (CI only)

Minimal AGP app that consumes the `net.ladenthin:llama-android` AAR from `mavenLocal`.
Not a shipped project — it exists so CI proves Android Studio consumption end to end.

Two CI jobs drive it (`.github/workflows/publish.yml`):

1. **`package-android-aar`** — `assembleRelease` with R8: validates AAR format, manifest
minSdk merge, `jni/{arm64-v8a,x86_64}` packaging, and the shipped consumer ProGuard
rules (asserts the APK still carries the binding and both `.so` files).
2. **`test-android-emulator`** — boots a KVM-accelerated **x86_64** emulator, adb-pushes a
small GGUF (the already-cached draft model) to
`/data/local/tmp/jllama-test-model.gguf`, and runs `connectedDebugAndroidTest`:
`OnDeviceInferenceTest` loads the binding via `System.loadLibrary` from the AAR's
`jni/x86_64/libjllama.so`, reads the GGUF with the pure-Java `GgufInspector`, and runs
real native inference on the emulator. Tests self-skip when the model was not pushed,
so a local `connectedAndroidTest` against a bare emulator stays green.

What the emulator job deliberately does NOT cover: arm64 kernels (real-device ABI — the
example app on hardware covers that) and the Adreno/OpenCL flavor (no OpenCL ICD in the
emulator).
52 changes: 52 additions & 0 deletions .github/android-consumer-test/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: 2026 Bernard Ladenthin <bernard.ladenthin@gmail.com>
//
// SPDX-License-Identifier: MIT

plugins {
id("com.android.application")
}

// Injected by CI (-PjllamaVersion=<reactor version>) so the fixture always tests
// the AAR that was just built and published to mavenLocal.
val jllamaVersion: String = providers.gradleProperty("jllamaVersion").get()

android {
namespace = "net.ladenthin.llama.consumertest"
compileSdk = 35

defaultConfig {
applicationId = "net.ladenthin.llama.consumertest"
minSdk = 28
targetSdk = 35
versionCode = 1
versionName = "1.0"
// arm64 = real devices; x86_64 = the CI emulator (and x86_64 Android hardware).
ndk {
abiFilters += listOf("arm64-v8a", "x86_64")
}
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
// Full R8 pass so the AAR's consumer proguard.txt is actually exercised:
// if the shipped rules were broken, the binding would be stripped or the
// build would fail here.
isMinifyEnabled = true
signingConfig = signingConfigs.getByName("debug")
}
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}

dependencies {
implementation("net.ladenthin:llama-android:$jllamaVersion")

// On-emulator instrumentation (test-android-emulator CI job).
androidTestImplementation("androidx.test.ext:junit:1.2.1")
androidTestImplementation("androidx.test:runner:1.6.2")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// SPDX-FileCopyrightText: 2026 Bernard Ladenthin <bernard.ladenthin@gmail.com>
//
// SPDX-License-Identifier: MIT

package net.ladenthin.llama.consumertest;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import net.ladenthin.llama.GgufInspector;
import net.ladenthin.llama.LlamaModel;
import net.ladenthin.llama.parameters.InferenceParameters;
import net.ladenthin.llama.parameters.ModelParameters;
import net.ladenthin.llama.value.GgufMetadata;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
* On-emulator smoke of the shipped AAR: the binding must load via
* {@code System.loadLibrary("jllama")} from the APK's native-lib dir (the AAR's
* {@code jni/x86_64/} on the CI emulator), and both the pure-Java GGUF inspector and real
* native inference must work on Android/bionic. The model file is adb-pushed by the
* {@code test-android-emulator} CI job; every test self-skips when it is absent so the
* fixture stays green on a bare emulator.
*/
@RunWith(AndroidJUnit4.class)
public class OnDeviceInferenceTest {

/** Where the CI job adb-pushes the test GGUF (world-readable in /data/local/tmp). */
private static final String MODEL_PATH = "/data/local/tmp/jllama-test-model.gguf";

private static void assumeModelPresent() {
assumeTrue("test model not pushed to " + MODEL_PATH, new File(MODEL_PATH).canRead());
}

@Test
public void ggufInspectorReadsTheModelOnDevice() throws IOException {
assumeModelPresent();

GgufMetadata meta = GgufInspector.read(Paths.get(MODEL_PATH));

assertTrue("tensor count must be positive, was " + meta.getTensorCount(), meta.getTensorCount() > 0);
assertTrue("architecture key must be present: " + meta, meta.getArchitecture().isPresent());
}

@Test
public void nativeInferenceGeneratesTokensOnDevice() {
assumeModelPresent();

// Loading LlamaModel exercises System.loadLibrary("jllama") + JNI_OnLoad's
// FindClass resolution against the D8-dexed classes — the load-time surface
// the build-only CI jobs cannot reach.
try (LlamaModel model = new LlamaModel(
new ModelParameters().setModel(MODEL_PATH).setCtxSize(512).setGpuLayers(0))) {
String generated = model.complete(new InferenceParameters("Hello").withNPredict(8));

assertFalse("expected generated tokens, got empty output", generated.isEmpty());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
SPDX-FileCopyrightText: 2026 Bernard Ladenthin <bernard.ladenthin@gmail.com>

SPDX-License-Identifier: MIT
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:label="jllama consumer test" />
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: 2026 Bernard Ladenthin <bernard.ladenthin@gmail.com>
//
// SPDX-License-Identifier: MIT

package net.ladenthin.llama.consumertest;

import net.ladenthin.llama.LlamaModel;
import net.ladenthin.llama.parameters.InferenceParameters;
import net.ladenthin.llama.parameters.ModelParameters;

/**
* Compile-time consumer check: references the binding's API surface exactly the way an app
* would, so the AGP build fails if the AAR's classes.jar is broken or its POM dependencies
* (Jackson etc.) do not resolve. No code here runs in CI — the fixture is never installed on a
* device; on-device inference is covered by the example app (separate project).
*/
public final class ConsumerSmoke {

private ConsumerSmoke() {}

/**
* Opens a model from an absolute on-device path.
*
* @param modelPath absolute path to a GGUF file on device storage
* @return the loaded model (caller closes)
*/
public static LlamaModel open(String modelPath) {
return new LlamaModel(new ModelParameters().setModel(modelPath).setCtxSize(512));
}

/**
* Runs a short completion.
*
* @param model the loaded model
* @param prompt the prompt text
* @return the generated text
*/
public static String prompt(LlamaModel model, String prompt) {
return model.complete(new InferenceParameters(prompt).withNPredict(8));
}
}
5 changes: 5 additions & 0 deletions .github/android-consumer-test/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# SPDX-FileCopyrightText: 2026 Bernard Ladenthin <bernard.ladenthin@gmail.com>
#
# SPDX-License-Identifier: MIT
org.gradle.jvmargs=-Xmx2g
android.useAndroidX=true
30 changes: 30 additions & 0 deletions .github/android-consumer-test/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: 2026 Bernard Ladenthin <bernard.ladenthin@gmail.com>
//
// SPDX-License-Identifier: MIT

// CI fixture (NOT a shipped project): a minimal AGP app that consumes the
// net.ladenthin:llama-android AAR from mavenLocal and runs a full R8 release
// build — proving Android Studio consumption end-to-end (AAR format, manifest
// minSdk merge, jni packaging, consumer proguard rules) without an emulator.
// Driven by the package-android-aar job in .github/workflows/publish.yml.
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
plugins {
id("com.android.application") version "8.7.3"
}
}

dependencyResolutionManagement {
repositories {
mavenLocal() // the freshly built llama-android AAR is published here first
google()
mavenCentral()
}
}

rootProject.name = "llama-android-consumer-test"
include(":app")
Loading
Loading