news 2026/4/29 2:49:21

Memoria-智能影记创新实训博客(四):Qwen3.5-0.8B 模型的端侧部署与跑通

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Memoria-智能影记创新实训博客(四):Qwen3.5-0.8B 模型的端侧部署与跑通

Memoria-智能影记创新实训博客(四):Qwen3.5-0.8B 模型的端侧部署与跑通

博客主题:技术基础
时间跨度:2026.04.23 - 2026.04.26(第8周)
进度总结:上一篇博客中提到了故事生成功能提供了两种故事生成方式,一种是直接调deepseek根据标签和时空数据来生成,一种是本地vlm打caption,再交由deepseek生成。本博客实现了本地vlm的端侧部署与成功调用。

1. 目标

把项目里的Qwen3.5-0.8B本地模型真正部署到手机侧,并让它在 App 内稳定跑起来。当前项目采用一条更适合工程落地的路径:把llama.cpp的可执行文件、Qwen GGUF 模型和mmproj一起准备好,打进 APK 或外部部署目录,由 Android 侧通过MethodChannel拉起常驻llama-server,再让 Flutter 通过127.0.0.1的 OpenAI 兼容接口发起请求。这样做的目标有三个:第一,保证图片可以在手机本地完成理解,不把原图直接上传云端;第二,把模型加载和推理留在 Android 原生层,避免 Dart 层直接承受本地模型运行成本;第三,为后面的本地 caption、本地故事、多图理解等功能提供统一底座。

2. 部署前提

项目真正依赖的是三类文件:llama-serverllama-mtmd-cli两个可执行文件,libggml / libllama / libmtmd等共享库,以及Qwen3.5-0.8B-Q4_K_M.gguf + mmproj-F16.gguf这组模型文件。代码里对模型和投影文件的命名是写死匹配的,尤其会优先寻找checkpoints/qwen/Qwen3.5-0.8B-Q4_K_M.ggufcheckpoints/qwen/mmproj-F16.gguf,所以文件名和目录结构不能随意改。

3. 目录组织与打包方式

项目里对本地模型资源的打包路径已经约定好了,Gradle 会在构建阶段自动把本地资源同步到 APK 资产目录中。核心逻辑在android/app/build.gradle.kts

from("../../third_party/llama.cpp/install-android-baseline/bin"){include("llama-server","llama-mtmd-cli")into("local_llm/install-android-baseline/bin")}from("../../third_party/llama.cpp/install-android-baseline/lib"){include("libggml-base.so","libggml-cpu.so","libggml.so","libllama.so","libmtmd.so")into("local_llm/install-android-baseline/lib")}from("../../checkpoints/qwen"){include("Qwen3.5-0.8B-Q4_K_M.gguf","mmproj-F16.gguf")into("local_llm/checkpoints/qwen")}

这段配置说明了两件事:第一,项目默认把third_party/llama.cpp/install-android-baseline视为本地运行时来源,把checkpoints/qwen视为模型来源;第二,真正打进 APK 之后,这些文件都会统一落到assets/local_llm/...下面。

4. 安装与运行

Android 侧在OnDeviceInternvlBridge.kt里做了两层转移:先把打包进 APK 的资源解压到noBackupFilesDir/local_llm,再把真正需要执行的llama-serverllama-mtmd-cli和共享库复制到noBackupFilesDir/internvl_runtime。这样做的原因很实际:资产目录只适合读取,不适合直接作为可执行运行时目录;而私有目录既能持久保存,又可以给二进制文件设置执行权限。

代码里对应的关键逻辑是:

privatefunensureBundledAssetsInstalledIfNeeded(){copyAssetTree("$packagedAssetRoot/install-android-baseline/bin",File(bundledInstallRoot,"bin"))copyAssetTree("$packagedAssetRoot/install-android-baseline/lib",bundledLibDir)copyAssetTree("$packagedAssetRoot/checkpoints/qwen",bundledCheckpointsRoot)}

以及:

privatefunensureRuntimeServerStaged(){copyFileIfChanged(sourceServer,appRuntimeServerFile)sourceLibFiles.forEach{sourceLib->valtargetLib=File(appRuntimeLibDir,sourceLib.name)copyFileIfChanged(sourceLib,targetLib)}}

所以,真正的运行路径不是 APK 内部,而是:

  • 安装目录:noBackupFilesDir/local_llm
  • 运行目录:noBackupFilesDir/internvl_runtime

5. 拉起本地服务

Flutter 侧并不直接操作模型文件,而是通过MethodChannel("memoria/on_device_internvl")调 Android 原生桥。桥接入口在MainActivity.kt,真正处理逻辑在OnDeviceInternvlBridge.kt。Dart 侧最关键的几个方法是:

  • getServerDeploymentStatus():检查本地依赖是否齐全
  • getServerStatus():查看当前服务有没有运行、能不能连通
  • ensureServerStarted():真正启动或复用本地常驻服务
  • stopServer():停止服务

启动时,Android 侧会拼出一条标准llama-server命令:

valcommand=mutableListOf(linkerPath,appRuntimeServerFile.absolutePath,"-m",modelFile.path,"--mmproj",mmprojFile.path,"--host",serverHost,"--port",serverPort.toString(),"--threads",threads.toString(),"--ctx-size",contextSize.toString(),"--alias",serverModelAlias,"--no-webui","--no-mmproj-offload",)

这里几个参数很重要:

  • 模型路径和mmproj路径都来自部署状态检查结果
  • 服务固定监听127.0.0.1:8080
  • 模型别名固定为local-qwen3.5-0.8b-vl
  • 线程数和上下文大小由设备画像动态推荐

也就是说,这条部署方案本质上是“App 内自启动一个本地 OpenAI 兼容服务”,而不是“每次调用都临时起一个进程”。

6. 验证部署成功

在这个项目里,“文件存在”不等于“部署成功”。代码对成功的判断分了三层。

第一层是依赖完整。getServerDeploymentStatus()会检查:

  • llama-server是否存在
  • lib目录是否存在
  • Qwen GGUF是否存在
  • mmproj是否存在
  • linker64是否存在

只要这些项目里有缺失,就会返回missingItems,同时isRunnable = false

第二层是端口可达。即使服务已经拉起,如果127.0.0.1:8080还连不上,也不能说明服务真的可用。

第三层是推理预热成功。项目不会只检查端口,而是会主动向/v1/chat/completions发一个带 1x1 测试图的 warmup 请求。只有端口可达并且 warmup 成功,ready才会变成true。也就是说,当前代码里真正意义上的“部署成功”是:

  • isRunnable = true
  • running = true
  • reachable = true
  • ready = true

这比“看到进程 PID”更严格,也更贴近真实可用状态。

7. 总结

当前项目里,Qwen3.5-0.8B的本地部署已经形成了一条完整链路:先在仓库中准备llama.cpp运行时和 Qwen 模型文件,再由 Gradle 在构建阶段把这些资源打进 APK;应用首次运行时把资产解压到私有目录,再把服务可执行文件和共享库复制到运行目录;随后通过MethodChannel拉起常驻llama-server,用127.0.0.1/v1/chat/completions承接推理请求,并通过测试页验证部署、启动、预热和推理结果是否全部正常。它的价值不只是“本地能跑一个模型”,而是为后面的本地 caption、多图故事、本地 VLM 辅助生成等功能提供了稳定、统一、可复用的本地推理底座。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/29 2:46:26

数据结构——栈和队列的相互模拟

栈:只能一端进行插入和删除,具有先进后出的特点队列:一端进行插入一端进行删除,具有先进先出的特点1.两个栈来模拟一个队列:此时我们将第一个栈称为S1,将第二个栈称为S2。思路:入队:…

作者头像 李华
网站建设 2026/4/29 2:45:22

向量数据库全面对比分析

向量数据库全面对比分析:从国产到开源,如何选择最适合的方案随着AI应用的快速发展,向量数据库作为处理高维数据的关键技术,已经成为现代AI架构中不可或缺的组成部分。本文将对主流向量数据库进行全面对比分析。📊 向量…

作者头像 李华
网站建设 2026/4/29 2:43:25

有源晶振选择方法:适配设备性能的评估要点

在选择合适的有源晶振时,有几个重要因素需要关注。首先,频率稳定性是确保设备能够在各种条件下正常工作的关键。其次,晶振的功耗不仅影响设备的运行效率,还直接关系到续航能力。此外,根据不同的应用场景,还…

作者头像 李华
网站建设 2026/4/29 2:43:22

边缘智能中的轻量级视觉模型STResNet与STYOLO解析

1. 边缘智能时代的轻量级视觉模型革新在嵌入式视觉领域,我们正面临一个关键的技术转折点。随着智能摄像头、工业质检设备和可穿戴设备的普及,市场对能在微控制器(MCU)上高效运行的深度学习模型需求激增。传统方案往往需要在模型精度和硬件资源之间做出艰…

作者头像 李华
网站建设 2026/4/29 2:40:48

仅剩3家头部厂商掌握的Java低代码内核技术:基于Eclipse JDT+自定义Annotation Processor的零侵入式元编程体系

更多请点击: https://intelliparadigm.com 第一章:Java低代码内核技术演进与行业格局洞察 Java 低代码平台的内核已从早期的可视化表单引擎,演进为融合元数据驱动、AST 编译时增强、运行时字节码插桩与动态类加载的复合型架构。其核心突破在…

作者头像 李华
网站建设 2026/4/29 2:38:01

【研报402】智能底盘技术研究:i-Dynamics的实车验证与落地路径

本报告提供限时下载,请查看文后提示以下仅为报告部分内容:摘要:高级别自动驾驶产业化背景下,传统底盘动力学模型已无法适配复杂场景的控制需求,智能底盘成为核心突破方向。本文详解可实时进化的i-Dynamics底盘控制模型…

作者头像 李华