Skip to content

Commit 307e709

Browse files
committed
Remove model memory UI, add AGENTS.md, bump version
Add AGENTS.md with repository guidance and build/runtime notes. Remove the model memory estimation UI and related helpers: delete ModelMemoryCard, MemoryInfoRow, formatSignedBytes, and associated imports/props from AllConfigScreen.kt; remove related strings from res/values/strings.xml. Bump app versionCode to 7 and versionName to 1.3 in app/build.gradle.kts. Files changed: AGENTS.md (new), app/build.gradle.kts, app/src/main/java/.../AllConfigScreen.kt, app/src/main/res/values/strings.xml.
1 parent 03a655c commit 307e709

4 files changed

Lines changed: 146 additions & 126 deletions

File tree

‎AGENTS.md‎

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
# AGENTS.md
2+
3+
This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
4+
5+
## Overview
6+
7+
This is a **Jetpack Compose Android GUI wrapper** for llama.cpp. The app does NOT compile llama.cpp source code - it only packages and launches prebuilt native binaries (specifically `llama-server` and supporting libraries) on Android devices. The wrapper is designed for Snapdragon-oriented builds with CPU, OpenCL, and Hexagon/HTP acceleration support.
8+
9+
**Architecture principle**: The Android app is a thin UI layer that manages native process lifecycle via `ProcessBuilder`. All actual inference happens in the prebuilt `llama-server` executable.
10+
11+
## Key Architecture Components
12+
13+
- **Native Integration**: Packages prebuilt binaries from `../pkg-snapdragon/llama.cpp/` into `app/src/main/jniLibs/arm64-v8a/`, then extracts/symlinks them to app-private storage at runtime
14+
- **GUI Layer**: Jetpack Compose with Material 3 and Navigation Component
15+
- **Dependency Injection**: Hilt (`LlamaServerApplication` with `@HiltAndroidApp`)
16+
- **Persistence**: DataStore for configuration storage, JSON serialization via `ConfigRepositoryImpl`
17+
- **Process Management**: `ServerProcessManager` launches `llama-server` as a child process and monitors stdout/stderr for status
18+
19+
## Critical Integration Points
20+
21+
### Binary Preparation (`util/BinaryExtractor.kt`)
22+
- Packages `libllama-server.so` (renamed from `bin/llama-server`) and libraries in `jniLibs/arm64-v8a/`
23+
- At runtime, symlinks (or copies if symlinking fails) binaries to `filesDir/bin/` and `filesDir/lib/`
24+
- Marks executable with `chmod 755`
25+
26+
### Server Process Launch (`service/ServerProcessManager.kt`)
27+
- Builds `llama-server` command line with flags from `ServerConfig`
28+
- Configures environment variables for library loading:
29+
- `LD_LIBRARY_PATH`: includes `filesDir`, `filesDir/lib`, and system paths
30+
- `ADSP_LIBRARY_PATH`: `<filesDir>/lib`
31+
- `GGML_HEXAGON_EXPERIMENTAL=1` (for HTP devices)
32+
- Monitors stdout/stderr for `HTTP server is listening` or `llama server listening` to mark server as running
33+
- **Real-time logging**: Uses `realtimeOutput` StateFlow to emit each line as read, parsed by `RuntimeViewModel` for display
34+
35+
### Configuration Model (`model/ServerConfig.kt`)
36+
Comprehensive data class that maps to `llama-server` CLI flags:
37+
- Model selection, embedding mode, context size, batch size
38+
- Device types: `cpu`, `opencl`, `htp0`-`htp4`
39+
- KV cache types, KV offload, flash attention, auto-fit settings
40+
- HTTP server config (port, bind, API key), timeout, threads, continuous batching
41+
- `extraParams`: String for passing additional command-line arguments
42+
43+
## Build Commands
44+
45+
### Prerequisites
46+
Native artifacts must exist at:
47+
```text
48+
../pkg-snapdragon/llama.cpp/
49+
├── bin/llama-server
50+
└── lib/*.so
51+
```
52+
53+
### Development Workflow
54+
55+
1. **Copy native libraries**:
56+
```bash
57+
./copy-native-libs.sh
58+
```
59+
Copies artifacts to `app/src/main/jniLibs/arm64-v8a/` and renames `llama-server` to `libllama-server.so`
60+
61+
2. **Build with Docker** (recommended):
62+
```bash
63+
docker run --rm \
64+
-v /d/llama.cpp/android-gui-wrapper:/source \
65+
-w /source \
66+
llama-android-builder:latest \
67+
bash -c "rm -rf app/build && export ANDROID_HOME=/opt/android-sdk && gradle assembleDebug --no-daemon --parallel"
68+
```
69+
70+
3. **Build release APK** (signed):
71+
```bash
72+
docker run --rm \
73+
-v /d/llama.cpp/android-gui-wrapper:/source \
74+
-w /source \
75+
llama-android-builder:latest \
76+
bash -c "rm -rf app/build && export ANDROID_HOME=/opt/android-sdk && gradle assembleRelease --no-daemon"
77+
```
78+
79+
4. **Install on device**:
80+
```bash
81+
adb install app/build/outputs/apk/debug/app-debug.apk
82+
# or for release
83+
adb install app/build/outputs/apk/release/app-release.apk
84+
```
85+
86+
### Output
87+
- Debug APK: `app/build/outputs/apk/debug/app-debug.apk`
88+
- Release APK: `app/build/outputs/apk/release/app-release.apk`
89+
90+
### Signing
91+
Release builds require a keystore. See `keystore.properties.example` and `README.md` (Release signing section) for setup instructions. Signing config is optional — debug builds work without it.
92+
93+
## Runtime Flow
94+
95+
```
96+
MainActivity
97+
└── MainScreen
98+
├── Config tab -> AllConfigScreen (ui/launcher/)
99+
└── Runtime tab -> RuntimeScreen (ui/runtime/)
100+
```
101+
102+
Server startup sequence:
103+
```
104+
RuntimeScreen
105+
└── RuntimeViewModel.startServer()
106+
└── ServerProcessManager.startServer(config)
107+
├── BinaryExtractor.ensureAllAvailable(context)
108+
├── build llama-server command line
109+
├── configure LD_LIBRARY_PATH and other env vars
110+
├── ProcessBuilder(...).start()
111+
└── read stdout/stderr for server status (real-time via realtimeOutput)
112+
```
113+
114+
## Common Modification Points
115+
116+
- **Add UI settings**: Edit `AllConfigScreen.kt`, `ModelConfigViewModel.kt`, and `ServerConfig.kt`
117+
- **Change llama-server flags**: Modify `ServerProcessManager.kt`
118+
- **Modify binary/library preparation**: Update `BinaryExtractor.kt` and `copy-native-libs.sh`
119+
- **Change runtime controls or logs**: Edit `RuntimeScreen.kt` and `RuntimeViewModel.kt`
120+
- **Adjust Gradle configuration**: Modify `app/build.gradle.kts`
121+
122+
## Important Constraints
123+
124+
- **Only supports `arm64-v8a` ABI** - other architectures require native builds and Gradle changes
125+
- **Model files** use Android's document picker URI system (`UriUtils` resolves `content://` URIs)
126+
- **Binary compatibility**: The Android app can only use features supported by the prebuilt `llama-server` binary
127+
- **Package name mismatch**: Code package is `tridefender.llama.snapdragon`, but namespace/applicationId is `tridefender.llama.snapdragon`. Always use full class names in `AndroidManifest.xml` (e.g., `android:name="tridefender.llama.snapdragon.LlamaServerApplication"` instead of `android:name=".LlamaServerApplication"`)
128+
- **Resource imports**: Kotlin files importing R must use `import tridefender.llama.snapdragon.R`
129+
130+
## Multi-User/Clone App Considerations
131+
132+
On some devices (e.g., OnePlus with MultiApp feature), the app may be installed in multiple user profiles. To fully uninstall:
133+
```bash
134+
adb uninstall --user 0 tridefender.llama.snapdragon
135+
adb uninstall --user 999 tridefender.llama.snapdragon # MultiApp profile
136+
# or
137+
adb shell pm uninstall --user all tridefender.llama.snapdragon
138+
```
139+
140+
## Reference
141+
142+
- Parent repo: [llama.cpp](../)
143+
- Server docs: [tools/server/README.md](../tools/server/README.md)
144+
- Build docs: [docs/build.md](../docs/build.md)

‎app/build.gradle.kts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ android {
1616
applicationId = "tridefender.llama.snapdragon"
1717
minSdk = 24
1818
targetSdk = 34
19-
versionCode = 6
20-
versionName = "1.2"
19+
versionCode = 7
20+
versionName = "1.3"
2121

2222
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
2323
vectorDrawables {

‎app/src/main/java/tridefender/llama/snapdragon/ui/launcher/AllConfigScreen.kt‎

Lines changed: 0 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import androidx.compose.ui.graphics.Color
3131
import androidx.compose.ui.graphics.vector.ImageVector
3232
import androidx.compose.ui.platform.LocalContext
3333
import androidx.compose.ui.res.stringResource
34-
import androidx.compose.ui.text.font.FontFamily
3534
import androidx.compose.ui.text.font.FontWeight
3635
import androidx.compose.ui.text.style.TextOverflow
3736
import androidx.compose.ui.unit.dp
@@ -41,7 +40,6 @@ import tridefender.llama.snapdragon.model.CacheType
4140
import tridefender.llama.snapdragon.model.DeviceType
4241
import tridefender.llama.snapdragon.model.FlashAttentionMode
4342
import tridefender.llama.snapdragon.model.MemoryFitState
44-
import tridefender.llama.snapdragon.model.ModelMemoryEstimate
4543
import tridefender.llama.snapdragon.model.PoolingType
4644
import tridefender.llama.snapdragon.viewmodel.ModelCatalogEntry
4745
import tridefender.llama.snapdragon.viewmodel.ModelConfigViewModel
@@ -121,8 +119,6 @@ fun AllConfigScreen(
121119
ModelSection(
122120
modelPath = config.modelPath,
123121
modelCatalog = analysis.catalog,
124-
selectedEstimate = analysis.selectedEstimate,
125-
freeBytes = analysis.freeBytes,
126122
isEmbedding = config.isEmbedding,
127123
poolingType = config.poolingType,
128124
onModelSelect = { viewModel.updateModelPath(it) },
@@ -193,8 +189,6 @@ fun AllConfigScreen(
193189
fun ModelSection(
194190
modelPath: String,
195191
modelCatalog: List<ModelCatalogEntry>,
196-
selectedEstimate: ModelMemoryEstimate?,
197-
freeBytes: Long,
198192
isEmbedding: Boolean,
199193
poolingType: PoolingType,
200194
onModelSelect: (String) -> Unit,
@@ -309,14 +303,6 @@ fun ModelSection(
309303
Text(stringResource(R.string.browse))
310304
}
311305
}
312-
313-
Spacer(modifier = Modifier.height(12.dp))
314-
315-
ModelMemoryCard(
316-
estimate = selectedEstimate,
317-
freeBytes = freeBytes,
318-
modifier = Modifier.fillMaxWidth()
319-
)
320306

321307
Spacer(modifier = Modifier.height(16.dp))
322308

@@ -753,102 +739,6 @@ fun ExtraParamsSection(
753739
}
754740
}
755741

756-
@Composable
757-
private fun ModelMemoryCard(
758-
estimate: ModelMemoryEstimate?,
759-
freeBytes: Long,
760-
modifier: Modifier = Modifier
761-
) {
762-
Surface(
763-
modifier = modifier,
764-
shape = RoundedCornerShape(12.dp),
765-
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)
766-
) {
767-
Column(
768-
modifier = Modifier
769-
.fillMaxWidth()
770-
.padding(16.dp),
771-
verticalArrangement = Arrangement.spacedBy(10.dp)
772-
) {
773-
Row(
774-
modifier = Modifier.fillMaxWidth(),
775-
horizontalArrangement = Arrangement.SpaceBetween,
776-
verticalAlignment = Alignment.CenterVertically
777-
) {
778-
Column(modifier = Modifier.weight(1f)) {
779-
Text(
780-
text = stringResource(R.string.model_memory_calculator),
781-
style = MaterialTheme.typography.bodyLarge,
782-
fontWeight = FontWeight.Medium
783-
)
784-
Text(
785-
text = stringResource(R.string.unified_memory_note),
786-
style = MaterialTheme.typography.bodySmall,
787-
color = MaterialTheme.colorScheme.onSurfaceVariant
788-
)
789-
}
790-
791-
if (estimate != null) {
792-
AssistChip(
793-
onClick = {},
794-
label = { Text(fitLabel(estimate.fitState)) },
795-
colors = AssistChipDefaults.assistChipColors(
796-
labelColor = estimate.fitState.fitColor()
797-
)
798-
)
799-
}
800-
}
801-
802-
if (estimate == null) {
803-
Text(
804-
text = stringResource(
805-
R.string.model_memory_waiting,
806-
formatBytes(freeBytes)
807-
),
808-
style = MaterialTheme.typography.bodySmall,
809-
color = MaterialTheme.colorScheme.onSurfaceVariant
810-
)
811-
} else {
812-
MemoryInfoRow(stringResource(R.string.available_memory), formatBytes(estimate.freeBytes))
813-
MemoryInfoRow(stringResource(R.string.model_weights), formatBytes(estimate.modelBytes))
814-
MemoryInfoRow(stringResource(R.string.kv_cache_estimate), formatBytes(estimate.kvCacheBytes))
815-
MemoryInfoRow(stringResource(R.string.runtime_overhead), formatBytes(estimate.overheadBytes))
816-
MemoryInfoRow(stringResource(R.string.total_memory_estimate), formatBytes(estimate.totalBytes))
817-
MemoryInfoRow(
818-
label = stringResource(R.string.memory_after_load),
819-
value = formatSignedBytes(estimate.remainingBytes),
820-
valueColor = estimate.fitState.fitColor()
821-
)
822-
}
823-
}
824-
}
825-
}
826-
827-
@Composable
828-
private fun MemoryInfoRow(
829-
label: String,
830-
value: String,
831-
valueColor: Color = MaterialTheme.colorScheme.onSurface
832-
) {
833-
Row(
834-
modifier = Modifier.fillMaxWidth(),
835-
horizontalArrangement = Arrangement.SpaceBetween,
836-
verticalAlignment = Alignment.CenterVertically
837-
) {
838-
Text(
839-
text = label,
840-
style = MaterialTheme.typography.bodySmall,
841-
color = MaterialTheme.colorScheme.onSurfaceVariant
842-
)
843-
Text(
844-
text = value,
845-
style = MaterialTheme.typography.bodySmall,
846-
fontFamily = FontFamily.Monospace,
847-
color = valueColor
848-
)
849-
}
850-
}
851-
852742
@Composable
853743
private fun PoolingType.localizedName(): String = when (this) {
854744
PoolingType.NONE -> stringResource(R.string.none)
@@ -883,11 +773,6 @@ private fun formatBytes(bytes: Long): String {
883773
}
884774
}
885775

886-
private fun formatSignedBytes(bytes: Long): String {
887-
val prefix = if (bytes > 0) "+" else ""
888-
return prefix + formatBytes(bytes)
889-
}
890-
891776
@Composable
892777
fun ConfigSectionCard(
893778
title: String,

‎app/src/main/res/values/strings.xml‎

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -170,15 +170,6 @@
170170
<string name="cached_models_count">%1$d cached GGUF model(s)</string>
171171
<string name="model_menu_memory_line">%1$s - %2$s</string>
172172
<string name="model_metadata_unavailable">Metadata unavailable</string>
173-
<string name="model_memory_calculator">Model Memory Calculator</string>
174-
<string name="unified_memory_note">Phone RAM is unified; model weights, KV cache, and runtime overhead use the same pool.</string>
175-
<string name="model_memory_waiting">Select a readable GGUF model to estimate memory use. Current free memory: %1$s.</string>
176-
<string name="available_memory">Available memory</string>
177-
<string name="model_weights">Model weights</string>
178-
<string name="kv_cache_estimate">KV cache estimate</string>
179-
<string name="runtime_overhead">Runtime overhead</string>
180-
<string name="total_memory_estimate">Total estimate</string>
181-
<string name="memory_after_load">After load</string>
182173
<string name="fit_wont_fit">Won\'t fit</string>
183174
<string name="fit_tight_fit">Tight fit</string>
184175
<string name="fit_good_fit">Good fit</string>

0 commit comments

Comments
 (0)