Skip to content

Commit d05fe71

Browse files
committed
Enable swappable Android kernel packages
1 parent e3fe3c8 commit d05fe71

13 files changed

Lines changed: 2587 additions & 92 deletions

File tree

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Changelog
2+
3+
## v1.1.c21b45 - 2026-05-28
4+
5+
- Added a dedicated Kernels screen for GitHub release discovery, compressed kernel downloads, local archive import, activation, and deletion.
6+
- Added kernel version metadata, persistence, bundled fallback registration, package validation, and missing-library warnings.
7+
- Updated downloaded kernel extraction to accept both `llama-server` and `libllama-server.so`, storing executables as `bin/libllama-server.so`.
8+
- Fixed Android `error=13` launch failures by launching downloaded `.so` executables through `/system/bin/linker64` and launching bundled/reverted executables from Android's native library directory.
9+
- Reworked kernel activation to clear stale active binaries/libraries before switching and to re-apply executable permissions whenever a kernel is activated or prepared.
10+
- Fixed Hexagon startup by using semicolon-separated `ADSP_LIBRARY_PATH` entries and including app, native library, RFSA, and DSP search paths.
11+
- Added a one-shot CPU fallback for Hexagon session-open failures so the server can recover when CDSP/HTP access fails.
12+
- Updated runtime detection for newer llama-server logs that report `server is listening on ...`.
13+
- Updated documentation for the new kernel packaging, activation, launch, and DSP library-path behavior.
14+
15+
### Verification
16+
17+
- Built signed release APK with `llama-android-builder:latest`.
18+
- Installed and launched on device `3B15AV008YH00000`.
19+
- Verified downloaded and bundled kernel launch paths no longer fail with `error=13`.
20+
- Verified HTP0 startup after the ADSP path fix: model loaded and `llama-server` listened on `http://127.0.0.1:8080`.

README.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ It also copies `bin/llama-server` as:
8383
app/src/main/jniLibs/arm64-v8a/libllama-server.so
8484
```
8585

86-
This rename is intentional: Android automatically packages and extracts `.so` files from `jniLibs`, so the executable is stored with a library-style filename during packaging and restored to `llama-server` at runtime.
86+
This rename is intentional: Android automatically packages and extracts `.so` files from `jniLibs`, so the executable is stored with a library-style filename during packaging. Bundled kernels launch directly from Android's native library directory; downloaded kernels keep the same `libllama-server.so` name and are launched through Android's dynamic linker.
8787

8888
## Building the APK
8989

@@ -160,6 +160,7 @@ At app startup, `MainActivity` opens the Compose UI:
160160
MainActivity
161161
└── MainScreen
162162
├── Config tab -> AllConfigScreen
163+
├── Kernels tab -> KernelScreen
163164
└── Runtime tab -> RuntimeScreen
164165
```
165166

@@ -169,27 +170,28 @@ When the user starts the server:
169170
RuntimeScreen
170171
└── RuntimeViewModel.startServer()
171172
└── ServerProcessManager.startServer(config)
173+
├── KernelRepository.prepareActiveVersion()
172174
├── BinaryExtractor.ensureAllAvailable(context)
173175
├── build llama-server command line
174176
├── configure process environment
175177
├── ProcessBuilder(...).start()
176178
└── read stdout/stderr for server status
177179
```
178180

179-
`BinaryExtractor` restores runtime files under app-private storage:
181+
`BinaryExtractor` stages active runtime files under app-private storage:
180182

181183
```text
182-
filesDir/bin/llama-server
183-
filesDir/lib/*.so
184+
filesDir/bin/libllama-server.so # only for downloaded kernels
185+
filesDir/lib/*.so # active kernel libraries
184186
```
185187

186-
It first attempts to create symlinks from Android's extracted native library directory. If symlinking fails, it copies the files. The executable is marked with `chmod 755`.
188+
For bundled kernels, the process launches `libllama-server.so` directly from Android's extracted native library directory. For downloaded kernels, Android does not reliably allow direct `execve` of app-private downloaded files, so the app launches `/system/bin/linker64 filesDir/bin/libllama-server.so ...`. The staged executable is still marked with `chmod 755` each time a kernel is activated or prepared.
187189

188190
`ServerProcessManager` configures library search paths before launching the process:
189191

190192
```text
191-
LD_LIBRARY_PATH=<filesDir>:<filesDir>/lib:/system/lib64:/system/vendor/lib64:/vendor/lib64:/vendor/dsp/cdsp
192-
ADSP_LIBRARY_PATH=<filesDir>/lib
193+
LD_LIBRARY_PATH=<filesDir>/bin:<filesDir>/lib:<nativeLibraryDir>:<filesDir>:/system/lib64:/system/vendor/lib64:/vendor/lib64:/vendor/dsp/cdsp
194+
ADSP_LIBRARY_PATH=<filesDir>/lib;<nativeLibraryDir>;/vendor/lib/rfsa/adsp;/system/vendor/lib/rfsa/adsp;/vendor/lib64/rfs/dsp;/vendor/dsp/cdsp;/dsp
193195
GGML_HEXAGON_EXPERIMENTAL=1 # for HTP devices
194196
```
195197

@@ -220,7 +222,7 @@ The active configuration is persisted as JSON at runtime by `ConfigRepositoryImp
220222
The generated command has this general shape:
221223

222224
```text
223-
filesDir/bin/llama-server
225+
<nativeLibraryDir>/libllama-server.so
224226
--model <model-path>
225227
--poll 1000
226228
--ctx-size <context-size>
@@ -240,6 +242,8 @@ filesDir/bin/llama-server
240242
[--predict <tokens>]
241243
```
242244

245+
Downloaded kernels use the same arguments with `/system/bin/linker64 <filesDir>/bin/libllama-server.so` as the command prefix.
246+
243247
The app treats stdout/stderr as the process status channel. It marks the server as running when output contains messages such as `HTTP server is listening` or `llama server listening`.
244248

245249
## Notes and known sharp edges

app/proguard-rules.pro

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,9 @@
55
# For more details, see
66
# http://developer.android.com/guide/developing/tools/proguard.html
77

8-
# If your project uses WebView with JS, uncomment the following
9-
# and specify the fully qualified class name to the JavaScript interface
10-
# class:
11-
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12-
# public *;
13-
#}
14-
15-
# Uncomment this to preserve the line number information for
16-
# debugging stack traces.
17-
#-keepattributes SourceFile,LineNumberTable
18-
19-
# If you keep the line number information, uncomment this to
20-
# hide the original source file name.
21-
#-renamesourcefileattribute SourceFile
8+
# Keep line number information for debugging stack traces
9+
-keepattributes SourceFile,LineNumberTable
10+
-renamesourcefileattribute SourceFile
2211

2312
# Hilt
2413
-keep class dagger.hilt.** { *; }
@@ -41,5 +30,24 @@
4130

4231
# Compose
4332
-dontnote kotlinx.coroutines.DebugKt
44-
-keep,includedescriptorclasses class com.example.llamaserver.ui.theme.** { *; }
45-
-keep,includedescriptorclasses class com.example.llamaserver.ui.components.** { *; }
33+
-keep,includedescriptorclasses class tridefender.llama.snapdragon.ui.theme.** { *; }
34+
35+
# kotlinx.serialization
36+
-keepattributes *Annotation*, InnerClasses
37+
-dontnote kotlinx.serialization.AnnotationsKt
38+
-keepclassmembers class kotlinx.serialization.json.** { *** Companion; }
39+
-keepclasseswithmembers class kotlinx.serialization.json.** { kotlinx.serialization.KSerializer serializer(...); }
40+
-keep,includedescriptorclasses class tridefender.llama.snapdragon.**$$serializer { *; }
41+
-keepclassmembers class tridefender.llama.snapdragon.** {
42+
*** Companion;
43+
}
44+
-keepclasseswithmembers class tridefender.llama.snapdragon.** {
45+
kotlinx.serialization.KSerializer serializer(...);
46+
}
47+
48+
# Keep model classes for serialization
49+
-keep class tridefender.llama.snapdragon.model.** { *; }
50+
-keep class tridefender.llama.snapdragon.model.KernelSource { *; }
51+
-keep class tridefender.llama.snapdragon.model.KernelVersion { *; }
52+
-keep class tridefender.llama.snapdragon.model.KernelConfig { *; }
53+
-keep class tridefender.llama.snapdragon.model.ServerConfig { *; }

app/src/main/java/tridefender/llama/snapdragon/di/RepositoryModule.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package tridefender.llama.snapdragon.di
22

33
import tridefender.llama.snapdragon.repository.ConfigRepository
4+
import tridefender.llama.snapdragon.repository.KernelRepository
45
import tridefender.llama.snapdragon.repository.PresetRepository
56
import tridefender.llama.snapdragon.repository.impl.ConfigRepositoryImpl
7+
import tridefender.llama.snapdragon.repository.impl.KernelRepositoryImpl
68
import tridefender.llama.snapdragon.repository.impl.PresetRepositoryImpl
79
import dagger.Binds
810
import dagger.Module
@@ -17,7 +19,11 @@ abstract class RepositoryModule {
1719
@Binds
1820
@Singleton
1921
abstract fun bindConfigRepository(impl: ConfigRepositoryImpl): ConfigRepository
20-
22+
23+
@Binds
24+
@Singleton
25+
abstract fun bindKernelRepository(impl: KernelRepositoryImpl): KernelRepository
26+
2127
@Binds
2228
@Singleton
2329
abstract fun bindPresetRepository(impl: PresetRepositoryImpl): PresetRepository
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package tridefender.llama.snapdragon.model
2+
3+
import kotlinx.serialization.Serializable
4+
5+
@Serializable
6+
enum class KernelSource {
7+
BUNDLED,
8+
GITHUB_RELEASE,
9+
LOCAL_IMPORT
10+
}
11+
12+
@Serializable
13+
data class KernelVersion(
14+
val name: String,
15+
val isBundled: Boolean = false,
16+
val installedAt: Long = System.currentTimeMillis(),
17+
val source: KernelSource = KernelSource.BUNDLED,
18+
val missingLibraries: List<String> = emptyList()
19+
)
20+
21+
@Serializable
22+
data class KernelConfig(
23+
val activeVersion: String = BUNDLED_NAME,
24+
val versions: List<KernelVersion> = emptyList()
25+
) {
26+
companion object {
27+
const val BUNDLED_NAME = "Optimized Defaults"
28+
}
29+
}
30+
31+
enum class DownloadState {
32+
IDLE,
33+
DOWNLOADING,
34+
EXTRACTING,
35+
VALIDATING,
36+
DONE,
37+
ERROR
38+
}
39+
40+
data class GitHubRelease(
41+
val tagName: String,
42+
val name: String,
43+
val assetName: String,
44+
val assetUrl: String,
45+
val assetSize: Long
46+
)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package tridefender.llama.snapdragon.repository
2+
3+
import android.net.Uri
4+
import tridefender.llama.snapdragon.model.DownloadState
5+
import tridefender.llama.snapdragon.model.GitHubRelease
6+
import tridefender.llama.snapdragon.model.KernelConfig
7+
import tridefender.llama.snapdragon.model.KernelVersion
8+
import kotlinx.coroutines.flow.Flow
9+
import kotlinx.coroutines.flow.StateFlow
10+
11+
interface KernelRepository {
12+
val kernelConfig: StateFlow<KernelConfig>
13+
val downloadState: StateFlow<DownloadState>
14+
val downloadProgress: StateFlow<Float>
15+
16+
suspend fun ensureBundledExtracted()
17+
suspend fun prepareActiveVersion(): Boolean
18+
fun getActiveVersion(): String
19+
fun getInstalledVersions(): List<KernelVersion>
20+
suspend fun switchVersion(name: String): Boolean
21+
suspend fun importLocalArchive(uri: Uri, name: String): ImportResult
22+
suspend fun deleteVersion(name: String): Boolean
23+
fun canDelete(name: String): Boolean
24+
suspend fun fetchGitHubReleases(repoOwner: String, repoName: String): List<GitHubRelease>
25+
suspend fun downloadFromGitHub(release: GitHubRelease): Flow<DownloadState>
26+
suspend fun checkAndRevertIfNeeded(): String?
27+
}
28+
29+
sealed class ImportResult {
30+
data class Success(val missingLibraries: List<String>) : ImportResult()
31+
data class Error(val message: String) : ImportResult()
32+
}

0 commit comments

Comments
 (0)