1、布景介绍

ChatGPT的爆火让证明了大模型的可行,让各大公司趋之若鹜,张狂拥抱。公司根据行业数据库训练的大模型在进行4bit量化后压缩为4GB,尝试运用端的CPU才能离线部署运转在Android、iOS、Mac、Windows、Linux。

考虑到跨渠道的开发功率咱们运用了Google推出的Flutter。

2、项目完成

项目的功用很简略,UI上一个最简略的IM聊天页面,左边显现模型生成答案,右侧显现输入问题,底部是输入框和发送按钮。输入内容点击发送时,将文本内容传给模型,模型生成答案后流式回来结果展现。

这儿聊天UI根据开源项目flyerhq/flutter_chat_ui 二次开发,UI作用如下:

基于Flutter实现跨平台离线大模型对话应用

2.1 Flutter调用C/C++代码

加载模型运用C/C++代码,Flutter供给了dart:ffi 完成本地代码的调用。FFI代表外部功用接口相似功用的其他术语包含本地接口语言绑定

int loadLibrary() {
    var libraryPath;
    if (Platform.isLinux) {
      libraryPath = path.join(modelLibDir, 'lib$modelLibName.so');
      modelLib = DynamicLibrary.open(libraryPath);
    }
    if (Platform.isAndroid) {
      libraryPath = 'lib$modelLibName.so';
      modelLib = DynamicLibrary.open(libraryPath);
    }
    if (Platform.isMacOS) {
      modelLib = DynamicLibrary.process();
    }
    if (Platform.isWindows) {
      libraryPath = path.join(modelLibDir, 'Debug', '$modelLibName.dll');
      modelLib = DynamicLibrary.open(libraryPath);
    }
    if (Platform.isIOS) {
      modelLib = DynamicLibrary.process();
    }
    funcModelInit = modelLib.lookupFunction<VoidPtrFuncCharPtr, VoidPtrFuncCharPtr>('xxx_init');
    funcModelGenerate = modelLib.lookupFunction<VoidFuncVoidPtrCharPtrCallbackPtr, VoidFuncVoidPtrCharPtrCallbackPtrDart>('xxx_generate');
    return 0;
  }

不同的渠道调用本地代码完成略有不同,可是Flutter已经封装的满足通用了。

本地代码编译

咱们将C/C++源代码增加到ios文件夹,或许增加到一个单独目录再软链到ios/Classes下,由于 CocoaPods 不答应源码处于比 podspec 文件更高的目录层级,可是 Gradle 答应你指向ios文件夹。

FFI 库只能与 C 符号绑定,因而在 C++ 中,这些符号增加extern C标记。还应该增加特点来表明符号是需求被 Dart 引证的,以防止链接器在优化链接时会丢弃符号。

放置好代码后,在Android下面创立CMakeLists.txt目录,装备编译源文件和target,然后在build.gradle中装备:

externalNativeBuild {
        // Encapsulates your CMake build configurations.
        cmake {
            // Provides a relative path to your CMake build script.
            path "CMakeLists.txt"
        }
    }

path指向CmakeLists.txt途径,这样在编译Android项目是会主动将C/C++代码编译成动态库。

关于iOS,放置到ios/Classes下后编译时会主动编译这部分代码。关于Mac,和iOS相似。

关于Windows,直接在windows目录下创立CMakeLists.txt文件,能够在CMakeLists.txt中直接增加编译信息:

cmake_minimum_required(VERSION 3.10)
# 项目名称
set(PROJECT_NAME "libNativeAdd")
project(${PROJECT_NAME} LANGUAGES CXX)
# 源文件
add_library(${PROJECT_NAME} SHARED
    "./native_add.cpp"
)
# 动态库的输出目录
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/$<$<CONFIG:DEBUG>:Debug>$<$<CONFIG:RELEASE>:Release>")
# 装置动态库的方针目录
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
# 装置动态库,到履行目录
install(FILES "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${PROJECT_NAME}.dll" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime)

也能够增加子目录方法增加对应模块:

add_subdirectory("../libs/native_add" native_add)

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下:

Future<String> _getTemporaryDirectory() async {
    List<Directory>? dirs = await getExternalCacheDirectories();
    return dirs!.first.path;
  }

iOS无法读取沙盒外的数据,所以咱们只能将模型打包到运用程序,放置在assets下,在pubspec.yaml下进行装备。然后发动运用时将assets下的模型文件释放到暂时目录下:

  Future<String> readAndWriteModel() async {
    String fileName = "xxx.bin";
    String dir = (await getTemporaryDirectory()).path;
    String filePath = "$dir/$fileName";
    print("readAndWriteModel path = $filePath");
    File file = await new File(filePath);
    if(!await file.exists()){
      var bytes = await rootBundle.load("assets/xxx.bin");
      ByteBuffer buffer =  bytes.buffer;
      file.writeAsBytes(buffer.asUint8List(bytes.offsetInBytes,
        bytes.lengthInBytes));
    }
    return filePath;
  }

3. 运转作用展现

Android运转作用:

www.bilibili.com/video/BV1xs…

设备:vivo iQoo neo 7 3.2GHz 天玑9000+ 八核 12+8GB 内存 独立显现芯片 ​模型:约4GB

GPU加快:未敞开 ​作用:最开端出字慢,后边全体还能够,手机会发烫

功能数据(内从从最开端2.5G增长到6.5GB):

基于Flutter实现跨平台离线大模型对话应用

基于Flutter实现跨平台离线大模型对话应用

其他手机都没有vivo这块作用好,华为Mate Xs 处理器HUAWEIKirin9905G、运转内存8.0GB功能数据:

基于Flutter实现跨平台离线大模型对话应用

基于Flutter实现跨平台离线大模型对话应用

4、遇到问题

  1. iOS编译运转到iphone14时一直报签名失利,xcode版别问题,切换手机后正常;
  2. iphone12 ProMax运转时报out of memory过错,应为iphone不支撑swap内存,并且最大内存3G,无法全量加载模型,现在正在继续对模型裁剪。

5、参考资料

  • Dart 开发语言概览
  • Flutter中文开发文档
  • Flutter实战电子书

6、总结

本文介绍了离线大模型对话运用的跨渠道完成,包含Flutter调用本地代码,Flutter跨渠道途径问题,展现了离线大模型作用及功能指标。