Skip to content

Commit 289eb6c

Browse files
committed
bugfix: Fix memory clear bug
1 parent d7fb671 commit 289eb6c

6 files changed

Lines changed: 193 additions & 43 deletions

File tree

app/src/main/cpp/llama_context_wrapper.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,18 @@ void LlamaContextWrapper::generateStream(const std::string& prompt, TokenCallbac
194194
return;
195195
}
196196

197-
// KV cache is already empty for a new generation
198-
// No need to clear explicitly
197+
// Clear memory (KV cache) for new generation - new API uses llama_memory_clear
198+
llama_memory_t mem = llama_get_memory(context_);
199+
if (mem != nullptr) {
200+
llama_memory_clear(mem, true);
201+
LOGD("Memory cleared for new generation");
202+
}
203+
204+
// Reset sampler state for new generation
205+
if (sampler_ != nullptr) {
206+
llama_sampler_reset(sampler_);
207+
LOGD("Sampler reset for new generation");
208+
}
199209

200210
// Create batch for prompt processing
201211
llama_batch batch = llama_batch_init(cfg.batchSize, 0, 1);

app/src/main/cpp/llama_jni.cpp

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,47 @@ static jstring stringToJstring(JNIEnv* env, const std::string& str) {
4545
return env->NewStringUTF(str.c_str());
4646
}
4747

48-
// Helper to throw Java exception
48+
// Helper to throw Java exception - uses RuntimeException for safety
4949
static void throwException(JNIEnv* env, const char* className, const char* message) {
50+
// Try the specified class first
5051
jclass exClass = env->FindClass(className);
51-
if (exClass != nullptr) {
52+
if (exClass != nullptr && !env->ExceptionCheck()) {
5253
env->ThrowNew(exClass, message);
5354
env->DeleteLocalRef(exClass);
55+
} else {
56+
// Fall back to RuntimeException if class not found
57+
env->ExceptionClear();
58+
jclass runtimeEx = env->FindClass("java/lang/RuntimeException");
59+
if (runtimeEx != nullptr) {
60+
env->ThrowNew(runtimeEx, message);
61+
env->DeleteLocalRef(runtimeEx);
62+
}
63+
}
64+
}
65+
66+
// Helper to throw LlamaException.GenerationError
67+
static void throwGenerationError(JNIEnv* env, const char* message) {
68+
jclass exClass = env->FindClass("com/llamakotlin/android/exception/LlamaException$GenerationError");
69+
if (exClass != nullptr && !env->ExceptionCheck()) {
70+
// Find constructor
71+
jmethodID constructor = env->GetMethodID(exClass, "<init>", "(Ljava/lang/String;Ljava/lang/Throwable;)V");
72+
if (constructor != nullptr) {
73+
jstring jmsg = env->NewStringUTF(message);
74+
jobject ex = env->NewObject(exClass, constructor, jmsg, nullptr);
75+
if (ex != nullptr) {
76+
env->Throw((jthrowable)ex);
77+
env->DeleteLocalRef(ex);
78+
}
79+
env->DeleteLocalRef(jmsg);
80+
} else {
81+
// Fallback to RuntimeException
82+
env->ExceptionClear();
83+
throwException(env, "java/lang/RuntimeException", message);
84+
}
85+
env->DeleteLocalRef(exClass);
86+
} else {
87+
env->ExceptionClear();
88+
throwException(env, "java/lang/RuntimeException", message);
5489
}
5590
}
5691

@@ -176,7 +211,7 @@ Java_com_llamakotlin_android_LlamaNative_nativeLoadModel(
176211

177212
if (!success) {
178213
std::string error = context->getLastError();
179-
throwException(env, "com/llamakotlin/android/exception/LlamaException", error.c_str());
214+
throwGenerationError(env, error.c_str());
180215
return JNI_FALSE;
181216
}
182217

@@ -242,8 +277,7 @@ Java_com_llamakotlin_android_LlamaNative_nativeGenerate(
242277
std::string result = context->generate(promptStr, configPtr);
243278

244279
if (result.empty() && !context->getLastError().empty()) {
245-
throwException(env, "com/llamakotlin/android/exception/LlamaException",
246-
context->getLastError().c_str());
280+
throwGenerationError(env, context->getLastError().c_str());
247281
return nullptr;
248282
}
249283

@@ -318,10 +352,11 @@ Java_com_llamakotlin_android_LlamaNative_nativeGenerateStream(
318352
env->DeleteGlobalRef(globalCallback);
319353
env->DeleteLocalRef(callbackClass);
320354

321-
// Check for errors
322-
if (!context->getLastError().empty()) {
323-
throwException(env, "com/llamakotlin/android/exception/LlamaException",
324-
context->getLastError().c_str());
355+
// Check for errors - don't throw if already completed successfully
356+
std::string error = context->getLastError();
357+
if (!error.empty()) {
358+
LOGE("Generation error: %s", error.c_str());
359+
throwGenerationError(env, error.c_str());
325360
}
326361
}
327362

sample/src/main/java/com/llamakotlin/sample/MainActivity.kt

Lines changed: 123 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package com.llamakotlin.sample
22

3+
import android.content.Context
4+
import android.content.Intent
5+
import android.content.SharedPreferences
6+
import android.net.Uri
37
import android.os.Bundle
4-
import android.os.Environment
8+
import android.provider.OpenableColumns
59
import android.widget.Toast
610
import androidx.activity.result.contract.ActivityResultContracts
711
import androidx.appcompat.app.AppCompatActivity
812
import androidx.lifecycle.lifecycleScope
9-
import com.llamakotlin.android.LlamaConfig
1013
import com.llamakotlin.android.LlamaModel
1114
import com.llamakotlin.android.exception.LlamaException
1215
import com.llamakotlin.sample.databinding.ActivityMainBinding
@@ -16,20 +19,52 @@ import kotlinx.coroutines.flow.catch
1619
import kotlinx.coroutines.launch
1720
import kotlinx.coroutines.withContext
1821
import java.io.File
22+
import java.io.FileOutputStream
1923

2024
class MainActivity : AppCompatActivity() {
2125

2226
private lateinit var binding: ActivityMainBinding
27+
private lateinit var prefs: SharedPreferences
2328
private var model: LlamaModel? = null
2429
private var generationJob: Job? = null
2530

31+
companion object {
32+
private const val PREFS_NAME = "llama_prefs"
33+
private const val KEY_MODEL_PATH = "model_path"
34+
}
35+
2636
override fun onCreate(savedInstanceState: Bundle?) {
2737
super.onCreate(savedInstanceState)
28-
binding = ActivityMainBinding.inflate(layoutInflater)
29-
setContentView(binding.root)
38+
try {
39+
binding = ActivityMainBinding.inflate(layoutInflater)
40+
setContentView(binding.root)
41+
42+
prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
3043

31-
setupUI()
32-
showVersion()
44+
setupUI()
45+
showVersion()
46+
47+
// Auto-load last used model
48+
autoLoadModel()
49+
} catch (e: Exception) {
50+
e.printStackTrace()
51+
Toast.makeText(this, "Error: ${e.message}", Toast.LENGTH_LONG).show()
52+
}
53+
}
54+
55+
private fun autoLoadModel() {
56+
val savedPath = prefs.getString(KEY_MODEL_PATH, null)
57+
if (savedPath != null) {
58+
val modelFile = File(savedPath)
59+
if (modelFile.exists()) {
60+
binding.tvStatus.text = "Auto-loading last model..."
61+
loadModel(savedPath)
62+
} else {
63+
// Clear saved path if file no longer exists
64+
prefs.edit().remove(KEY_MODEL_PATH).apply()
65+
binding.tvStatus.text = "Previous model not found. Please select a new model."
66+
}
67+
}
3368
}
3469

3570
private fun setupUI() {
@@ -63,39 +98,94 @@ class MainActivity : AppCompatActivity() {
6398
}
6499
}
65100

66-
private val pickFileLauncher = registerForActivityResult(
101+
// File picker for GGUF models
102+
private val filePickerLauncher = registerForActivityResult(
67103
ActivityResultContracts.OpenDocument()
68-
) { uri ->
69-
uri?.let {
70-
// Copy to local storage or use content resolver
71-
val path = getModelPath(uri.path ?: "")
72-
if (path.isNotEmpty()) {
73-
loadModel(path)
74-
} else {
75-
Toast.makeText(this, "Invalid model file", Toast.LENGTH_SHORT).show()
76-
}
104+
) { uri: Uri? ->
105+
uri?.let { selectedUri ->
106+
// Take persistent permission
107+
contentResolver.takePersistableUriPermission(
108+
selectedUri,
109+
Intent.FLAG_GRANT_READ_URI_PERMISSION
110+
)
111+
copyAndLoadModel(selectedUri)
77112
}
78113
}
79114

80115
private fun pickModelFile() {
81-
// For simplicity, look for model in Downloads folder
82-
val downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
83-
val ggufFiles = downloadsDir.listFiles { file -> file.extension == "gguf" }
116+
binding.tvStatus.text = "Opening file picker...\nSelect a .gguf model file"
84117

85-
if (ggufFiles.isNullOrEmpty()) {
86-
Toast.makeText(this, "No .gguf files in Downloads. Please download a model first.", Toast.LENGTH_LONG).show()
87-
binding.tvStatus.text = "No model found. Download a .gguf model to Downloads folder."
88-
return
89-
}
118+
// Use Storage Access Framework - works on all Android versions
119+
filePickerLauncher.launch(arrayOf("*/*"))
120+
}
121+
122+
private fun copyAndLoadModel(uri: Uri) {
123+
binding.tvStatus.text = "Copying model to app storage..."
124+
binding.btnLoadModel.isEnabled = false
125+
126+
lifecycleScope.launch(Dispatchers.IO) {
127+
try {
128+
// Get file name
129+
val fileName = getFileName(uri) ?: "model.gguf"
130+
131+
if (!fileName.endsWith(".gguf", ignoreCase = true)) {
132+
withContext(Dispatchers.Main) {
133+
binding.tvStatus.text = "Please select a .gguf file"
134+
binding.btnLoadModel.isEnabled = true
135+
}
136+
return@launch
137+
}
138+
139+
// Copy to app's internal storage
140+
val modelFile = File(filesDir, fileName)
141+
142+
withContext(Dispatchers.Main) {
143+
binding.tvStatus.text = "Copying $fileName...\nThis may take a few minutes for large models."
144+
}
145+
146+
contentResolver.openInputStream(uri)?.use { input ->
147+
FileOutputStream(modelFile).use { output ->
148+
val buffer = ByteArray(8192)
149+
var bytesRead: Int
150+
var totalBytes = 0L
151+
while (input.read(buffer).also { bytesRead = it } != -1) {
152+
output.write(buffer, 0, bytesRead)
153+
totalBytes += bytesRead
154+
// Update progress every 10MB
155+
if (totalBytes % (10 * 1024 * 1024) < 8192) {
156+
withContext(Dispatchers.Main) {
157+
binding.tvStatus.text = "Copying $fileName...\n${totalBytes / (1024 * 1024)} MB copied"
158+
}
159+
}
160+
}
161+
}
162+
}
90163

91-
// Use the first found model
92-
val modelFile = ggufFiles.first()
93-
loadModel(modelFile.absolutePath)
164+
withContext(Dispatchers.Main) {
165+
binding.tvStatus.text = "Model copied. Loading..."
166+
binding.btnLoadModel.isEnabled = true
167+
loadModel(modelFile.absolutePath)
168+
}
169+
170+
} catch (e: Exception) {
171+
e.printStackTrace()
172+
withContext(Dispatchers.Main) {
173+
binding.tvStatus.text = "Error copying model: ${e.message}"
174+
binding.btnLoadModel.isEnabled = true
175+
}
176+
}
177+
}
94178
}
95179

96-
private fun getModelPath(uriPath: String): String {
97-
// Simple path extraction - in production use ContentResolver
98-
return uriPath
180+
private fun getFileName(uri: Uri): String? {
181+
var name: String? = null
182+
contentResolver.query(uri, null, null, null, null)?.use { cursor ->
183+
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
184+
if (cursor.moveToFirst() && nameIndex >= 0) {
185+
name = cursor.getString(nameIndex)
186+
}
187+
}
188+
return name ?: uri.lastPathSegment
99189
}
100190

101191
private fun loadModel(modelPath: String) {
@@ -114,6 +204,9 @@ class MainActivity : AppCompatActivity() {
114204
topK = 40
115205
}
116206

207+
// Save model path for auto-loading
208+
prefs.edit().putString(KEY_MODEL_PATH, modelPath).apply()
209+
117210
binding.tvStatus.text = "Model loaded: ${File(modelPath).name}"
118211
binding.btnGenerate.isEnabled = true
119212
Toast.makeText(this@MainActivity, "Model loaded successfully!", Toast.LENGTH_SHORT).show()

sample/src/main/res/layout/activity_main.xml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
android:layout_width="match_parent"
44
android:layout_height="match_parent"
55
android:orientation="vertical"
6-
android:padding="16dp">
6+
android:padding="16dp"
7+
android:fitsSystemWindows="true">
78

89
<!-- Status -->
910
<TextView
1011
android:id="@+id/tvStatus"
1112
android:layout_width="match_parent"
1213
android:layout_height="wrap_content"
14+
android:layout_marginTop="?attr/actionBarSize"
1315
android:text="No model loaded"
1416
android:textColor="?android:attr/textColorSecondary"
1517
android:padding="8dp"
@@ -21,7 +23,7 @@
2123
android:layout_width="match_parent"
2224
android:layout_height="wrap_content"
2325
android:layout_marginTop="8dp"
24-
android:text="Load Model from Downloads" />
26+
android:text="Select Model File (.gguf)" />
2527

2628
<!-- Prompt Input -->
2729
<EditText
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
3+
<background android:drawable="@color/purple_500"/>
4+
<foreground android:drawable="@color/white"/>
5+
</adaptive-icon>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
3+
<background android:drawable="@color/purple_500"/>
4+
<foreground android:drawable="@color/white"/>
5+
</adaptive-icon>

0 commit comments

Comments
 (0)