| package com.blaze.agent.ai |
|
|
| import android.content.Context |
| import android.util.Log |
| import kotlinx.coroutines.* |
|
|
| class BonsaiInference(private val context: Context) { |
|
|
| companion object { |
| private const val TAG = "BlazeBonsai" |
| private const val LIB_NAME = "blaze_llama" |
| private const val N_CTX = 512 |
| private const val N_THREADS = 4 |
| private const val MAX_TOKENS = 512 |
| private const val UNLOAD_TIMEOUT_MS = 30_000L |
| private var libLoaded = false |
|
|
| fun tryLoadLibrary(): Boolean { |
| if (libLoaded) return true |
| return try { System.loadLibrary(LIB_NAME); libLoaded = true; true } |
| catch (e: UnsatisfiedLinkError) { false } |
| } |
| } |
|
|
| private val inferenceDispatcher = Dispatchers.IO.limitedParallelism(1) |
| private val scope = CoroutineScope(inferenceDispatcher + SupervisorJob()) |
| private var unloadJob: Job? = null |
| private var modelPath: String? = null |
|
|
| private external fun loadModel(modelPath: String, nCtx: Int, nThreads: Int): Int |
| private external fun complete(prompt: String, maxTokens: Int): String |
| private external fun unloadModel() |
| private external fun isLoaded(): Boolean |
|
|
| suspend fun complete(prompt: String): String = withContext(inferenceDispatcher) { |
| if (!tryLoadLibrary()) throw IllegalStateException("Bonsai native library not loaded") |
| ensureModelLoaded() |
| scheduleUnload() |
| Log.d(TAG, "Running Bonsai inference (${prompt.length} chars)") |
| complete(prompt, MAX_TOKENS) |
| } |
|
|
| fun preloadIfReady() { |
| if (BonsaiDownloader.modelExists && tryLoadLibrary()) { |
| scope.launch { ensureModelLoaded(); scheduleUnload() } |
| } |
| } |
|
|
| fun forceUnload() { |
| scope.launch { if (libLoaded && isLoaded()) { unloadModel(); Log.d(TAG, "Force unloaded") } } |
| } |
|
|
| private fun ensureModelLoaded() { |
| if (!libLoaded || isLoaded()) return |
| val path = BonsaiDownloader.getModelFile(context).absolutePath |
| val result = loadModel(path, N_CTX, N_THREADS) |
| if (result != 1) throw IllegalStateException("Failed to load Bonsai model (JNI=$result)") |
| modelPath = path |
| Log.d(TAG, "Bonsai model loaded into memory") |
| } |
|
|
| private fun scheduleUnload() { |
| unloadJob?.cancel() |
| unloadJob = scope.launch { |
| delay(UNLOAD_TIMEOUT_MS) |
| if (libLoaded && isLoaded()) { Log.d(TAG, "Unloading Bonsai after inactivity"); unloadModel() } |
| } |
| } |
| } |
|
|