基于 Flutter 实现跨平台离线大模型对话应用
1、背景介绍
ChatGPT 的爆火让证明了大模型的可行,让各大公司趋之若鹜,疯狂拥抱。公司基于行业数据库训练的大模型在进行 4bit 量化后压缩为 4GB,尝试利用端的 CPU 能力离线部署运行在 Android、iOS、Mac、Windows、Linux。
考虑到跨平台的开发效率我们使用了 Google 推出的 Flutter。
2、项目实现
项目的功能很简单,UI 上一个最简单的 IM 聊天页面,左侧显示模型生成答案,右侧显示输入问题,底部是输入框和发送按钮。输入内容点击发送时,将文本内容传给模型,模型生成答案后流式返回结果展示。
这里聊天 UI 基于开源项目flyerhq/flutter_chat_ui 二次开发,UI 效果如下:
2.1 Flutter 调用 C/C++代码
加载模型使用 C/C++代码,Flutter 提供了dart:ffi 实现本地代码的调用。「FFI」 代表外部功能接口类似功能的其他术语包括「本地接口」和「语言绑定」。
不同的平台调用本地代码实现略有不同,但是 Flutter 已经封装的足够通用了。
「本地代码编译」
我们将 C/C++源代码添加到 ios
文件夹,或者添加到一个单独目录再软链到ios/Classes
下,因为 CocoaPods 不允许源码处于比 podspec 文件更高的目录层级,但是 Gradle 允许你指向 ios
文件夹。
FFI 库只能与 C 符号绑定,因此在 C++ 中,这些符号添加 extern C
标记。还应该添加属性来表明符号是需要被 Dart 引用的,以防止链接器在优化链接时会丢弃符号。
放置好代码后,在 Android 下面创建 CMakeLists.txt 目录,配置编译源文件和 target,然后在 build.gradle 中配置:
path 指向 CmakeLists.txt 路径,这样在编译 Android 项目是会自动将 C/C++代码编译成动态库。
对于 iOS,放置到ios/Classes
下后编译时会自动编译这部分代码。对于 Mac,和 iOS 类似。
对于 Windows,直接在 windows 目录下创建 CMakeLists.txt 文件,可以在 CMakeLists.txt 中直接添加编译信息:
也可以添加子目录方式添加对应模块:
2.2 Flutter 文件路径问题
由于模型比较大,有 4GB,如果直接打包到应用程序包中,替换和更新比较费劲,所以让应用程序读取固定路径下一个文件加载。在 Mac、Linux 下,直接定位当前应用路径即可:Directory.current.path
,对于 Android,直接定位应用目录返回是/
,我们需要使用对应路径,我们使用 Google 官方维护的插件 「path_provider」。
「path_provider」提供了 8 个方法获取不同的文件路径:
「getTemporaryDirectory」 :临时目录,适用于下载的缓存文件,此目录随时可以清除,此目录为应用程序私有目录,其他应用程序无法访问此目录。Android 上对应
getCacheDir
。iOS 上对应NSCachesDirectory
。「getApplicationSupportDirectory」:应用程序可以在其中放置应用程序支持文件的目录的路径。在 iOS 上,对应
NSApplicationSupportDirectory
,如果此目录不存在,则会自动创建。在 Android 上,对应getFilesDir
。「getLibraryDirectory」:应用程序可以在其中存储持久性文件,备份文件以及对用户不可见的文件的目录路径,例如 storage.sqlite.db。在 Android 上,此函数抛出[UnsupportedError]异常,没有等效项路径存在。
「getApplicationDocumentsDirectory」:应用程序可能在其中放置用户生成的数据或应用程序无法重新创建的数据的目录路径。在 iOS 上,对应
NSDocumentDirectory
API。 如果数据不是用户生成的,使用[getApplicationSupportDirectory]。在 Android 上,对应getDataDirectory
API。 如果要让用户看到数据,改用[getExternalStorageDirectory]。「getExternalStorageDirectory」:应用程序可以访问顶级存储的目录的路径。由于此功能仅在 Android 上可用,因此应在发出此函数调用之前确定当前操作系统。在 iOS 上,此功能会引发[UnsupportedError]异常,因为无法在应用程序的沙箱外部访问。在 Android 上,对应
getExternalFilesDir(null)
。「getExternalCacheDirectories」:存储特定于应用程序的外部缓存数据的目录的路径。 这些路径通常位于外部存储(如单独的分区或 SD 卡)上。 这里返回的是一个列表。该方法仅在 Android 上可用,在 iOS 上,此功能会抛出 UnsupportedError,因为这是不可能的在应用程序的沙箱外部访问。在 Android 上,对应
Context.getExternalCacheDirs()
或 API Level 低于 19 的Context.getExternalCacheDir()
。「getExternalStorageDirectories」:可以存储应用程序特定数据的目录的路径。 这些路径通常位于外部存储(如单独的分区或 SD 卡)上。此功能仅在 Android 上可用,在 iOS 上,此功能会抛出 UnsupportedError,因为这是不可能的在应用程序的沙箱外部访问。在 Android 上,对应
Context.getExternalFilesDirs(String type)
或 API Level 低于 19 的Context.getExternalFilesDir(String type)
。「getDownloadsDirectory」:存储下载文件的目录的路径,这通常仅与台式机操作系统有关。在 Android 和 iOS 上,此函数将引发[UnsupportedError]异常。
Andorid 上为了省去动态权限的申请,我们直接放在getExternalCacheDirectories
下:
iOS 无法读取沙盒外的数据,所以我们只能将模型打包到应用程序,放置在assets
下,在 pubspec.yaml 下进行配置。然后启动应用时将 assets 下的模型文件释放到临时目录下:
3. 运行效果展示
Android 运行效果:
https://www.bilibili.com/video/BV1xs4y127i2/
设备:vivo iQoo neo 7 3.2GHz 天玑 9000+ 八核 12+8GB 内存 独立显示芯片模型:约 4GB
GPU 加速:未开启效果:最开始出字慢,后面整体还可以,手机会发烫
性能数据(内从从最开始 2.5G 增长到 6.5GB):
其他手机都没有 vivo 这块效果好,华为 Mate Xs 处理器 HUAWEIKirin9905G、运行内存 8.0GB 性能数据:
4、遇到问题
iOS 编译运行到 iphone14 时一直报签名失败,xcode 版本问题,切换手机后正常;
iphone12 ProMax 运行时报 out of memory 错误,应为 iphone 不支持 swap 内存,并且最大内存 3G,无法全量加载模型,现在正在继续对模型裁剪。
5、参考资料
6、总结
本文介绍了离线大模型对话应用的跨平台实现,包括 Flutter 调用本地代码,Flutter 跨平台路径问题,展示了离线大模型效果及性能指标。
版权声明: 本文为 InfoQ 作者【轻口味】的原创文章。
原文链接:【http://xie.infoq.cn/article/7cff94c53c61227f01ea79b1a】。文章转载请联系作者。
评论