当前位置: 首页 > news >正文

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 辅助生成等功能提供了稳定、统一、可复用的本地推理底座。

http://www.jsqmd.com/news/716958/

相关文章:

  • [特殊字符]【AI Infra 核心】告别黑盒调参:手把手教你搭建深度学习模型的可视化监控系统
  • 基于改进雷达图模型的热电联供型微网系统多目标优化配置(Matlab代码实现)
  • 热镀锌螺栓为什么更适合户外工程?防腐原理与应用场景解析_FES上海紧固件展
  • 别再手动造数据了!Halcon 3D建模:用gen_object_model_3d_from_points快速生成点云模型(附Python/C++调用示例)
  • COMSOL与Matlab联调避坑指南:如何正确使用‘createselection’自动生成选择集
  • HBuilderX里搞定uview-plus和Pinia:一个Vue3版uni-app项目的完整配置流程
  • 我做了一个很长的梦,醒来让GPT-5.5帮我解,它说的话让我坐了一上午
  • 无人机巡检光伏板深度学习故障检测系统实现【附代码】
  • 从故障工单到OEE监控,TPM实战体系拆解与落地参数
  • 别再死记梅森公式了!用MATLAB手把手带你玩转信号流图与系统函数(附实战代码)
  • VS Code MCP插件发布倒计时!GitHub Marketplace审核通过率提升300%的6项元数据优化与签名签名实践
  • 小米MiMo-V2.5系列大模型发布:AI智能体再进化,硬核技术直达全球第一梯队
  • 如何通过LinkSwift实现网盘直链下载:技术原理与实战应用指南
  • Arm编译器浮点支持与C99环境控制详解
  • 别把 async 当银弹:在 CPU 密集型图像处理服务中,优秀工程师为什么要敢于说“不”
  • 告别桥接芯片!聊聊MIPI A-PHY如何重塑车载摄像头与屏幕的连接(附2024量产展望)
  • 2026年值得关注的AI大模型API中转站推荐
  • c++中的内存管理
  • 小白必看!10 秒分清 360 全景和 720 全景,别再被商家忽悠
  • 2026上海紧固件专业展为何更具权威性?全国协会与国家级行业支持!
  • ChatGLM-6B企业培训应用:员工知识问答平台搭建
  • 【WebStorm】运行报错:env: node: No such file or directory
  • 撕开 CPython 的底裤:从巨大的 Switch/Case 到协程调度,一文彻底搞懂 Python 运行机制
  • 2026年热门会议记录语音转文字工具实测对比,准确率比拼差距竟然这么大,真香款才是隐藏王者
  • 计算机专业生打 CTF 全流程详解:零基础小白快速入门、赛事高效拿分、实战踩坑避坑完整版手册
  • SUSE以“数字主权“为旗帜,却难掩60亿美元出售传闻的尴尬
  • Python边缘轻量化终极瓶颈在哪?IEEE IoT Journal最新论文证实:93.6%的性能损失源于动态图转静态图时的梯度残留——附可复现修复方案
  • 从咖啡豆烘焙到芯片良率:Xbar控制图在制造业之外的3个硬核应用场景
  • 非易失性可编程光子集成电路的创新架构与应用
  • 【VRP问题】基于狼群算法求解带时间窗车辆路径动态规划问题Matlab代码