Back to blog
    llama.cpp Android 集成:Kotlin 开发指南
    llama.cppAndroidKotlinintegrationon-device AIVulkansegment:mobile-builder

    llama.cpp Android 集成:Kotlin 开发指南

    将 llama.cpp 集成到 Android 应用的分步指南(Kotlin)。JNI 绑定、Vulkan GPU 加速、模型加载以及跨 Android 设备范围的内存管理。

    EErtas Team·

    llama.cpp 在 Android 设备上使用 CPU 多线程和 Vulkan GPU 加速运行 GGUF 语言模型。llama.android 项目通过 JNI 提供预构建的 Kotlin 绑定,使 Kotlin 开发者的集成非常简单。

    本指南涵盖从项目设置到生产部署的完整集成路径。

    集成选项

    选项 1:llama.android 库(推荐)

    llama.cpp 仓库包含 llama.android,一个带有 Kotlin 绑定的预构建 Android 库。这是实现端侧 AI 最快的途径。

    将其添加到您的项目:

    1. 从 llama.cpp 仓库克隆或下载 llama.android 模块
    2. 将其作为模块包含在您的 Android 项目中
    3. 在应用的 build.gradle.kts 中添加依赖
    dependencies {
        implementation(project(":llama"))
    }

    选项 2:从源码构建

    如需更多控制权,使用 Android NDK 将 llama.cpp 构建为原生库:

    mkdir build-android && cd build-android
    cmake .. \
        -DCMAKE_TOOLCHAIN_FILE=$NDK_PATH/build/cmake/android.toolchain.cmake \
        -DANDROID_ABI=arm64-v8a \
        -DANDROID_PLATFORM=android-26 \
        -DLLAMA_VULKAN=ON \
        -DBUILD_SHARED_LIBS=ON
    cmake --build . --config Release

    这会生成 libllama.so,将其放入 jniLibs 目录即可。

    选项 3:预构建 AAR

    一些社区项目将 llama.cpp 发布为 AAR(Android Archive),您可以作为 Maven 依赖项包含。请检查最新且维护中的版本。

    项目设置

    最低要求

    • Android API 26+(Android 8.0)
    • ARM64(arm64-v8a)目标架构
    • NDK r25+ 用于构建原生代码
    • 目标设备 4GB+ RAM(用于 1B 模型)

    构建配置

    // app/build.gradle.kts
    android {
        defaultConfig {
            ndk {
                abiFilters += "arm64-v8a" // 仅 64 位 ARM
            }
        }
    }

    权限

    推理无需特殊权限。模型下载需要:

    <uses-permission android:name="android.permission.INTERNET" />

    加载模型

    class LlamaEngine(private val context: Context) {
        private var model: Long = 0 // 原生指针
        private var ctx: Long = 0   // 原生上下文指针
    
        suspend fun loadModel(modelPath: String) = withContext(Dispatchers.Default) {
            // 使用 GPU 加速加载模型
            model = LlamaNative.loadModel(
                modelPath = modelPath,
                nGpuLayers = 99,    // 将所有层卸载到 Vulkan
            )
            require(model != 0L) { "Failed to load model" }
    
            // 创建推理上下文
            ctx = LlamaNative.createContext(
                model = model,
                nCtx = 2048,        // 上下文窗口
                nThreads = 4,       // CPU 线程数
                nBatch = 512,       // 批次大小
            )
            require(ctx != 0L) { "Failed to create context" }
        }
    
        fun unload() {
            if (ctx != 0L) {
                LlamaNative.freeContext(ctx)
                ctx = 0
            }
            if (model != 0L) {
                LlamaNative.freeModel(model)
                model = 0
            }
        }
    }

    Vulkan GPU 加速

    Vulkan 是 Android 的 GPU 计算 API。llama.cpp 使用它来加速推理过程中的矩阵运算。通过将 nGpuLayers 设置为模型的层数来启用。

    Vulkan 支持取决于设备:

    • 骁龙 8 Gen 2+:完整 Vulkan 计算支持,性能最佳
    • Tensor G3/G4:良好的 Vulkan 支持
    • 骁龙 7 Gen 3+:支持 Vulkan,中等加速效果
    • 旧款/入门级设备:可能缺少 Vulkan 计算支持。自动回退到 CPU。

    运行时检查 Vulkan 可用性:

    fun isVulkanAvailable(): Boolean {
        return try {
            val vk = android.hardware.HardwareBuffer::class.java
            android.os.Build.VERSION.SDK_INT >= 26
            // 更可靠的方式:尝试创建 Vulkan 实例
        } catch (e: Exception) {
            false
        }
    }

    生成文本

    class LlamaEngine(private val context: Context) {
        // ... 上面的 loadModel 和 unload
    
        suspend fun generate(
            prompt: String,
            maxTokens: Int = 256,
            temperature: Float = 0.7f,
            onToken: (String) -> Unit = {}
        ): String = withContext(Dispatchers.Default) {
            val result = StringBuilder()
    
            LlamaNative.generate(
                context = ctx,
                prompt = prompt,
                maxTokens = maxTokens,
                temperature = temperature,
            ) { token ->
                result.append(token)
                // 分派到主线程更新 UI
                withContext(Dispatchers.Main) {
                    onToken(token)
                }
            }
    
            result.toString()
        }
    }

    ViewModel 集成

    class AiViewModel(application: Application) : AndroidViewModel(application) {
        private val engine = LlamaEngine(application)
        private val _response = MutableStateFlow("")
        val response: StateFlow<String> = _response
        private val _isGenerating = MutableStateFlow(false)
        val isGenerating: StateFlow<Boolean> = _isGenerating
    
        fun loadModel(path: String) {
            viewModelScope.launch {
                engine.loadModel(path)
            }
        }
    
        fun generate(prompt: String) {
            viewModelScope.launch {
                _isGenerating.value = true
                _response.value = ""
    
                engine.generate(prompt, maxTokens = 256) { token ->
                    _response.value += token
                }
    
                _isGenerating.value = false
            }
        }
    
        override fun onCleared() {
            engine.unload()
            super.onCleared()
        }
    }

    Jetpack Compose UI

    @Composable
    fun AiChatScreen(viewModel: AiViewModel = viewModel()) {
        val response by viewModel.response.collectAsState()
        val isGenerating by viewModel.isGenerating.collectAsState()
        var input by remember { mutableStateOf("") }
    
        Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
            // 回复区域
            Text(
                text = response,
                modifier = Modifier.weight(1f).verticalScroll(rememberScrollState())
            )
    
            // 输入区域
            Row(modifier = Modifier.fillMaxWidth()) {
                TextField(
                    value = input,
                    onValueChange = { input = it },
                    modifier = Modifier.weight(1f),
                    enabled = !isGenerating
                )
                Button(
                    onClick = {
                        viewModel.generate(input)
                        input = ""
                    },
                    enabled = !isGenerating
                ) {
                    Text("Send")
                }
            }
        }
    }

    内存管理

    Android 的内存管理非常激进。系统会杀死后台进程以释放 RAM,并在内存压力下终止您的应用。

    可用内存检查

    fun getAvailableMemoryMb(): Long {
        val memInfo = ActivityManager.MemoryInfo()
        val activityManager = getSystemService(ACTIVITY_SERVICE) as ActivityManager
        activityManager.getMemoryInfo(memInfo)
        return memInfo.availMem / (1024 * 1024)
    }
    
    fun canLoadModel(modelSizeMb: Long): Boolean {
        val available = getAvailableMemoryMb()
        // 为应用和系统预留 500MB
        return available > modelSizeMb + 500
    }

    生命周期管理

    class AiService : LifecycleObserver {
        private var engine: LlamaEngine? = null
    
        @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
        fun onResume() {
            // 如果 AI 界面处于活动状态,考虑加载模型
        }
    
        @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
        fun onPause() {
            // 卸载模型以释放内存
            engine?.unload()
        }
    }

    onTrimMemory 处理

    override fun onTrimMemory(level: Int) {
        super.onTrimMemory(level)
        if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW) {
            engine?.unload()
        }
    }

    模型分发

    Asset Delivery(Play Feature Delivery)

    对于超过 150MB 的模型,使用 Play Asset Delivery 以避免 APK 大小限制:

    // build.gradle.kts
    assetPacks += ":model_pack"
    
    // model_pack/build.gradle.kts
    plugins {
        id("com.android.asset-pack")
    }
    assetPack {
        packName.set("model_pack")
        dynamicDelivery {
            deliveryType.set("install-time") // 或 "fast-follow" 或 "on-demand"
        }
    }

    安装后下载

    安装后下载模型:

    suspend fun downloadModel(url: String, destination: File): Boolean {
        return withContext(Dispatchers.IO) {
            val client = OkHttpClient()
            val request = Request.Builder().url(url).build()
            val response = client.newCall(request).execute()
    
            response.body?.let { body ->
                val totalBytes = body.contentLength()
                destination.outputStream().use { output ->
                    body.byteStream().use { input ->
                        val buffer = ByteArray(8192)
                        var bytesRead: Long = 0
                        var read: Int
    
                        while (input.read(buffer).also { read = it } != -1) {
                            output.write(buffer, 0, read)
                            bytesRead += read
                            val progress = bytesRead.toFloat() / totalBytes
                            // 更新进度 UI
                        }
                    }
                }
                true
            } ?: false
        }
    }

    对于可以在应用重启后继续的后台下载,使用 WorkManager

    跨设备测试

    Android 设备种类繁多。需要在以下设备上测试:

    1. 旗舰机(SD 8 Gen 3, 12GB):验证最佳性能
    2. 中端机(SD 7 Gen 3, 6-8GB):验证 1B 模型流畅运行
    3. 入门机(SD 6 Gen 3, 4GB):验证模型无法加载时的优雅降级
    4. 旧款旗舰(SD 8 Gen 1, 8GB):验证两年前的旗舰机能正常工作

    使用 Firebase Test Lab 或 BrowserStack 进行设备覆盖测试,无需拥有每台设备。

    生产清单

    1. 模型在最低配置设备上正确加载并生成
    2. 检测到 Vulkan 时使用 Vulkan 加速
    3. Vulkan 不可用时 CPU 回退正常工作
    4. 内存检查防止在低 RAM 设备上加载
    5. 在 onPause/onTrimMemory 时卸载模型
    6. 安装后下载的进度正确显示
    7. 下载后验证模型完整性(SHA256)
    8. 用户可以取消生成
    9. 模型不可用时应用正常运行

    GGUF 模型决定了输出质量。在您的领域数据上微调的模型(通过 Ertas 等平台)会生成针对您应用目的的特定回复。无论使用哪个模型,llama.cpp 的集成方式都是一样的。

    Ship AI that runs on your users' devices.

    Early bird pricing starts at $14.50/mo — locked in for life. Plans for builders and agencies.

    Keep reading