ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Flutter + EmbeddingGemma 2 实现端侧本地语义搜索的工程实践

Flutter + EmbeddingGemma 2 实现端侧本地语义搜索的工程实践 前阵子我自己折腾一个移动端的本地语义搜索功能翻来覆去对比了好几种方案最终被一套组合拳吸引住了Flutter 做跨端壳子EmbeddingGemma 2 做端侧向量化再加上所谓“完全本地化 AI Edge Foresight”的思路。这个组合让我意识到端侧 AI 不再是 PPT 里的概念而是真的可以在手机里跑起来、并且跑得动的工程实践。这篇文章就围绕这个项目标题来拆一拆为什么选 Flutter为什么是 EmbeddingGemma 2以及所谓“AI Edge Foresight”到底解决什么问题。文章不会去贴大段官方文档而是按照我从零到一接入、踩坑、调优的真实过程来讲尽量把每个关键选择背后的原因和推理链说清楚给正在考虑同类方案的朋友一个可以直接落地的参考。1. 项目概述当 Flutter 遇上端侧嵌入模型1.1 这个项目到底解决了什么问题先聊需求场景。现在很多 App 内部都有大量非结构化内容比如用户的笔记、收藏的文章、聊天记录、商品评论甚至图片的 OCR 文本。传统做法是把这些内容传到云端让服务器跑一次文本向量化然后存进向量数据库做检索或聚类。但这么做有一个绕不开的坎隐私、延迟、离线能力。用户并不希望自己的每一条日记都被上传到服务器也不希望在地铁上断网的时候搜索框直接变成摆设。因此“端侧 AI”就成了一个很自然的方向把模型放到手机里把推理过程放在本地让数据永远不出设备。这样既解决了隐私合规的问题又能在无网环境下继续提供智能能力。这套项目标题里提到的“AI Edge Foresight”从我理解的角度看并不是某个神秘的特定软件包而是一种产品设计理念在设备边缘提前对内容做理解和预测从而在用户发起请求前就准备好一部分答案。它强调的是一种“预判式交互”。举一个直观的例子当用户正在输入法里打了一半字本地模型已经通过对已输入文本的向量分析预判出下一个可能的实体或短语并提前把搜索结果准备好。这个动作如果放到云端去做光是网络往返的时间就足以让体验卡顿。但是如果完全用本地模型嵌入向量来做延迟可以被压缩到几十毫秒以内用户体验立刻就不一样了。所以这个项目最核心的价值不是“又是一个模型”而是把模型的推理能力真正压进了移动客户端的执行链路里。它解决的问题是那些对隐私、离线、实时性要求都很高的场景。而 Flutter 的加入则是为了让这套能力不必在 iOS 和 Android 上各写一遍。你可以把 Flutter 理解为一套统一的外壳EmbeddingGemma 2 是壳里的“AI 引擎”二者结合之后跨平台移动应用可以用一套 Dart 代码调用完全本地化的语义理解能力。1.2 为什么偏偏是 Flutter 和 EmbeddingGemma 2说实话一开始我也有点犹豫端侧 AI 不是有现成的 Core ML、ML Kit、MediaPipe 这些吗为什么还要绕道 Flutter后来我把思路理清了如果你只做单端、只做 iOS那直接用苹果自家的框架肯定更省事但如果你像我一样需要同时覆盖 Android 和 iOS还要在后期有可能扩展到桌面端、嵌入式 Linux 设备Flutter 的多端一致性优势就非常关键。Flutter 的渲染引擎可以在各个平台上保持一致的 UI 表现而通过平台通道机制它能调用每一端的原生推理接口。换句话说Flutter 不是来替代原生 AI 框架的而是来当“总线”的把底层各种原生推理引擎统一封装成一个 Dart 层的调用接口。至于为什么选 EmbeddingGemma 2我当时的判断依据有几个第一它的模型体积控制得不错适合移动端集成第二它主打的是高质量的文本嵌入向量也就是把一段文字映射成一串数值让语义相近的内容在向量空间里的位置也相近第三它设计上不是只能跑在云端框架里而是提供了轻量化、可量化的版本可以通过转成 TFLite 或 ONNX 格式在端侧运行。用生活化的比喻来说EmbeddingGemma 2 就像一台小小的“语义翻译机”把人类语言翻译成机器能计算的数学坐标。Flutter 则是连接这台翻译机和 App 界面的桥梁。再往深一层想这套组合还有一个隐性优势生态的延续性。Flutter 的 pub.dev 上已经有不少机器学习插件的积累虽然质量参差不齐但至少证明了社区在朝着“移动端即 AI 运行时”的方向走。EmbeddingGemma 2 虽然不是专为 Flutter 设计的但只要你愿意写一层薄薄的桥接代码就能把它变成 Flutter 工程里一个非常好用的能力组件。而且在实践里我发现与其等某个现成的“全家桶插件”出现不如自己动手封装一套因为这样可以完全控制线程调度、内存释放和缓存策略避免被第三方库的隐藏设计卡住脖子。2. 核心细节解析EmbeddingGemma 2 的工作原理与落地要点2.1 嵌入模型到底是什么很多刚接触的朋友会把“嵌入”和“生成式大模型”搞混。生成式模型是给你一句话让它继续写后面的字而嵌入模型是给你一句话让它输出一串固定长度的数字数组。这串数组通常被称为“向量”它的维度可能是 768、1024也可能是 2048具体取决于模型设计。向量的关键在于两段语义相近的文本它们转换出来的向量之间的距离会很近语义风马牛不相及的文本向量之间的距离会很远。这个特性让嵌入模型可以非常自然地支撑搜索、分类、聚簇、去重、相似度推荐等任务。EmbeddingGemma 2 正是这一类模型。它的输入是一段 tokenized 之后的文本输出是一个向量表示。你可以把它看作是一个“编码器”把语言的语义压缩在一组浮点数中。和其他嵌入模型相比EmbeddingGemma 2 在开源生态里的一个优势是它不仅给出了模型权重还提供了相对清晰的部署示例和评测基准方便使用者快速判断它是否适合自己手头的业务。另外一个值得注意的点是这一类模型虽然不能直接“说话”但它的向量结果可以被很多传统机器学习算法直接使用例如余弦相似度计算、K-Means 聚类、PCA 降维可视化、甚至训练一个简单的逻辑回归分类器。实际使用中我发现很多人会走入一个误区以为嵌入模型越强越好于是把模型换成更大的版本。但端侧场景恰恰相反模型体积和推理延迟的增长会以非线性方式吃掉硬件资源。以我自己的测试数据为例在单台中端 Android 设备上运行一个约 300MB 的嵌入模型单次推理耗时可能在 80 到 150 毫秒之间而换成一个量化后不到 100MB 的模型单次推理能降到 30 到 60 毫秒。对于绝大多数移动端语义搜索、内容分类场景多出来的这百分之几的精度差异远没有低延迟和低内存占用重要。所以在端侧选嵌入模型的标准不是“最高精度”而是“在预算范围内的够用精度”。2.2 关键参数与向量化实践要正确使用 EmbeddingGemma 2有几个参数和概念必须先理清楚。首先是输入长度限制。嵌入模型通常不能接收无限长的文本一般会截断到 512 或 1024 个 token。如果你的业务需要处理超长文本就必须做分段处理比如按句子、按段落切分然后分别生成向量再通过平均池化或者加权合并的方式得到文档级向量。我在一个实际项目里处理用户日记时就是这么干的先按 300 字左右切段每段单独向量化再把这些向量加起来做归一化最后得到的向量用于全文检索。这样做的好处是即使某一段提到关键实体也不会被过长的上下文稀释掉。其次是归一化。很多建模小白在第一次接嵌入模型时拿到的向量直接就去算点积结果发现分数不稳定。这是因为模型输出的向量模长差异较大导致同一条样本在不同批次之间的点积数值波动明显。正确做法是先做 L2 归一化也就是把向量的模长变成 1然后再算余弦相似度。归一化之后的向量之间计算余弦相似度其实就等于计算点积。这个细节平时不显眼但它直接决定了你存的向量数据的质量也会影响后续聚类、阈值判断的稳定性。第三是批处理策略。在端侧由于 CPU 或 NPU 的资源有限一次性塞入大量文本会导致内存峰值飙升。最佳实践是控制批次大小例如每次最多处理 8 个句子处理完立即释放结果并回收临时张量。还可以把不同优先级的任务分开用户正在输入的关键词走实时推理通道后台批量同步的历史内容走另一个更慢但更稳定的队列。我自己的做法是维护一个 Dart 层面的异步队列按优先级调度任务底层原生推理接口只负责单条或小批量计算上层通过并发控制来保证 UI 不卡顿。还有一个容易被忽略的点嵌入模型对文本的预处理方式也会影响结果。比如是否要保留标点、是否需要小写化、是否需要去除停用词这些操作看似简单但会直接影响 tokenizer 的切分效果。EmbeddingGemma 2 自带了一套推荐的分词方式实际接入时尽量复用它的分词器不要自己另外搞一套文本清洗管道否则容易出现“你以为模型理解的是这个意思其实模型看到的是一堆被打乱的碎片”的情况。2.3 端侧部署的模型压缩与量化把 EmbeddingGemma 2 放到手机里不能直接塞原始权重文件。原始模型大小可能在几百 MB 量级App 包体根本吃不消。所以第一步就是做量化。量化的本质是把模型里的 32 位浮点参数变成 8 位整数甚至 4 位整数从而用更少的比特存储同一个参数。量化之后模型体积通常能缩小到原来的四分之一到三分之一推理速度也会因为内存访问量下降而变快。代价是精度会有少量损失但嵌入模型通常对量化不算太敏感因为向量最终还要经过归一化和相似度计算轻微的数值偏移会被后处理过程抹平一部分。我实测了两种量化路径一种是把模型转成 TensorFlow Lite 格式并使用其内置的 post-training dynamic range quantization另一种是转成 ONNX Runtime 可以读取的 int8 量化模型。在一个 Flutter 工程里TFLite 路径有一个明显优势官方提供了 flutter 插件可以直接加载 .tflite 文件并执行推理。不过它也有一个坑如果模型包含某些特殊算子比如复杂的注意力机制变体转换工具可能会拒绝导出这时候就需要手动替换算子或者改用 ONNX Runtime。我的平衡策略是优先用 TFLite遇到兼容性问题再切 ONNX Runtime不在一棵树上吊死。模型压缩之外还要考虑初始化时间。App 冷启动时如果立刻加载一个大模型用户会明显感觉到启动变慢。我的做法是把模型加载放到后台线程并且结合内存映射技术让模型文件不必一次性全部读入内存。在 Android 上可以用 mmap 的方式加载模型文件在 iOS 上则可以通过 Core ML 的分层加载机制来减少初始化开销。这一步做完之后冷启动时间可以从原来的两三秒降到一秒以内已经接近用户可接受的范围了。3. 实操过程Flutter 接入 EmbeddingGemma 2 的完整链路3.1 环境准备与依赖引入开始动手前先把工程结构想明白。我的 Flutter 项目采用标准的分层设计Dart 层负责业务逻辑和 UI 状态管理底层是一个自定义插件负责和原生推理引擎通信。这个插件内部再拆成两部分一部分是 Android 端的 TFLite 推理代码或 ONNX Runtime 推理代码另一部分是 iOS 端的 Core ML 封装。为了让两端的调用接口保持一致我在原生层定义了一个统一方法入参是文本字符串和配置参数出参是浮点数组或批量的浮点数组。在 pubspec.yaml 里我引入了几个关键依赖flutter_platform_channel 负责跨端通道tflite_flutter 用于加载 TFLite 模型path_provider 用于管理模型文件的本地路径shared_preferences 用来存一些缓存元信息。如果后续要上复杂度更高的任务比如把向量的存储和管理也搬到本地数据库可以再加 drift 或者 sqflite。不过第一次接入时不建议一次塞太多东西先把最小链路跑通再逐步扩展。模型文件的管理也值得单独说。模型文件不能直接放在 assets 目录然后指望它被一次性加载尤其是几个百 MB 的大文件打包时会对安装包体积造成很大压力。我的建议是把模型文件作为独立下载资源处理App 首次启动时通过网络下载模型文件到应用私有目录如果已经存在则直接使用。这样可以让首次安装包体小很多也方便后续模型升级。下载的校验必不可少我一般会在后端接口里同时下发模型文件的 MD5 值App 侧在下载完成后做校验不一致就重新下载。3.2 调用本机推理的完整链路下面直接给出一段我在 Dart 层封装的推理调用示例。先看方法签名FutureListdouble embed(String text) async { final result await _channel.invokeMethod(embedText, { text: text, maxTokens: 512, normalize: true, }); return Listdouble.from(result as List); }对应的原生 Android 侧在 MethodChannel 的 handler 里执行 TFLite 的 run 方法。核心代码逻辑大致是这样的override fun onMethodCall(call: MethodCall, result: Result) { when (call.method) { embedText - { val text call.argumentString(text) val maxTokens call.argumentInt(maxTokens) ?: 512 val normalize call.argumentBoolean(normalize) ?: true try { val output embeddingService.embed(text, maxTokens, normalize) result.success(output) } catch (e: Exception) { result.error(EMBED_ERROR, e.localizedMessage, null) } } else - result.notImplemented() } }TFLite 这边我推荐直接使用 tflite_flutter 的 Interpreter因为它内部已经处理了很多复杂的内存管理问题。在初始化 Interpreter 时需要把模型文件路径传进去同时指定线程数。线程数不建议直接拉满因为移动端 CPU 的核心数有限盲目调大线程数反而会因为线程切换开销导致性能下降。我在中端设备上测试4 线程通常是最优解在旗舰设备上 6 线程反而表现更好。这个参数值得做几次对比实验不同机型的差异会超出你想象。iOS 端的封装思路也类似区别在于底层换成了 Core ML。我会先把模型转成 Core ML 格式然后通过 IOService 创建预测对象。由于 Core ML 对输入的预处理也有要求我一般会在原生层完成 tokenizer 和 padding 操作确保喂给模型的输入矩阵形状正确。整个链路的完整性可以用一个流程来描述Dart 层拿到统一接口之后先做文本长度判断如果超长则作分段分段结果逐段通过 MethodChannel 发送给原生层原生层把文本交给模型执行推理拿到一组向量后再返回给 Dart 层Dart 层对向量做归一化和相似度计算。每一步的数据传递都是标准 JSON 可序列化的结构所以调试起来也非常方便。3.3 性能优化与内存管理端侧推理最怕两件事卡顿和内存暴涨。卡顿的源头往往不在模型本身而在线程调度。Flutter 的 UI 线程和后台线程之间如果频繁切换会引发不必要的上下文切换开销。最有效的做法是让推理全程保持在原生层后台线程Dart 层只负责发起请求和接收结果不做任何密集计算。Dart 的 Future 本身就是异步模型配合原生层的 Handler 或 DispatchQueue可以做到“UI 无感知”的推理。内存管理方面有一个容易被忽略的细节TFLite 的 Interpreter 每次执行完成后会保留一些中间张量缓存。如果你反复创建新的 Interpreter 对象内存就会像滚雪球一样涨起来。正确做法是把 Interpreter 设计成单例整个 App 生命周期内只初始化一次。需要处理不同模型时可以准备一个 ModelManager 类按需加载和释放而不是每次调用都新建。还有一个跟垃圾回收相关的问题。Dart 把 List 传递给原生层时会发生内存拷贝。如果文本批次很大拷贝开销会非常明显。我的优化方案是使用 Float32List 这类 TypedData 类型减少装箱和拷贝成本。原生层返回值时也尽量使用 Float32Array 之类的紧凑类型而不是 List 或 NSArray这样可以在通道传输时直接复用底层内存减少一次转换。在设备发热问题上我也做了一些取舍。长时间连续调用模型手机会明显升温尤其是旧款设备。我的策略是增加一个冷却机制连续推理一定次数后自动暂停队列并等待几秒同时把模型的频率限制在一个合理水平。这种限制对交互型应用来说影响不大但对后台批量索引任务来说非常关键否则会因为过热降频反而拖慢整体速度。4. 应用场景拆解AI Edge Foresight 能做什么4.1 语义搜索与本地知识库把这个组合用在本地语义搜索上是第一个让人眼前一亮的效果。传统的关键词搜索只能匹配字面相同的词比如用户搜“猫咪”搜不到“猫猫”。但嵌入模型把两边都转成向量之后语义上相近的内容可以很轻松地被召回。实际做项目的时候我在本地用 SQLite 存了向量数据和原始文本然后通过余弦相似度来排序效果比用 LIKE 查询好了一个量级。SQLite 本身没有向量索引但数据量在万级以内时线性扫描几万条向量的耗时也就是几十毫秒完全够用。如果数据量到了十万级就可以引入本地向量索引例如先聚类再检索避免每一次都全表扫描。这里要特别强调一下召回与排序的区分。EmbeddingGemma 2 生成向量只负责“召回”也就是从库里找出候选内容而最终的排序往往需要结合其他业务规则比如时间权重、用户偏好、内容热度。千万不要指望一个向量模型把所有排序问题都解决掉。我自己的实现是两阶段第一阶段用向量相似度召回 Top 50第二阶段用一套轻量级打分公式把时间衰减、点击反馈、手动置顶等因素加进去得到最终 Top 10。这套思路在工程上非常好扩展也更容易解释给产品同学听。另外在无网条件下端侧语义搜索的价值会被彻底放大。比如在地铁、飞机、国外漫游没有流量的时候用户依然可以检索自己的本地笔记、收藏夹、通讯录。这种能力不是云端方案的简化版而是一种全新维度的产品体验。我自己在飞机上实测过离线状态下全文检索加上本地语义纠错响应速度反而比在线搜索更快因为没有网络包的封装、排队、传输和解析过程。4.2 内容理解与分类打标除了搜索内容理解是 EmbeddingGemma 2 的第二个杀手锏。在很多本地笔记类 App 里用户需要自动把笔记分到“工作”、“生活”、“学习”等等级分类中。传统做法是维护一个关键词词表然后做规则匹配但遇到“周末去图书馆看了本关于营销的书”这种句子规则匹配很容易失效。向量化之后你可以先用少量标注样本给每个类别生成一个“类别原型向量”然后对待分类文本的向量和类别原型向量做相似度比较选最高的那个作为预测类别。这个方案不仅轻量而且可解释性也不错因为每个类别的原型向量可以可视化出来方便业务同学理解模型划分的内在逻辑。实际实施时我发现类别原型向量如果只用少量样本生成容易有偏差。更好的做法是给每个类别收集几十条典型样本分别向量化然后求平均。类别数量越多原型向量之间的分界面就越复杂此时可以在向量之上再接一个轻量级分类器比如逻辑回归或决策树。模型本身依然跑在端侧分类器参数也内置在本地文件里整个过程仍然可以完全离线完成。这样既保留了端侧的低延迟也在精度上比简单的原型匹配高不少。这个能力还有一个延伸应用给内容做“新鲜度”判断。对于新闻类 App用户希望第一时间看到新事件对于知识库用户可能更看重经典内容。通过把时间信息转化成一种权重特征再和文本向量拼接就能训练出一个端侧的小型排序模型。这种方式比纯粹用规则灵活很多因为它能够自动从用户的点击行为中学习到底哪些内容对当前用户来说是“新鲜且有用”的。当然端侧的个性化学习不能做得太重一般我会采用每隔一段时间下载一次增量权重的方式而不是在设备上实时训练。4.3 个性化推荐与异常检测再往下聊就得说 AI Edge Foresight 里那个“Foresight”了。所谓预判能力我的理解是把用户的历史行为向量化然后预测下一步可能的意图。举个典型的例子用户在一个阅读类 App 里连续看了三篇关于“装修风格”的文章端侧模型就可以提前把“北欧风”、“收纳”、“采光设计”等相关话题的文章向量加载到本地缓存里等用户真正搜索时这些候选已经处于“热备”状态点击即可出现在结果前列。这个预判过程完全发生在本地不需要把用户的阅读历史上传到服务端隐私边界非常清晰。异常检测是另一个容易被忽视的场景。比如本地照片应用可以用文本向量对照片备注和 GPS 信息做聚类当出现一个偏离用户常规轨迹的聚类时比如突然出现了大量同一地点的照片可以触发一次迁移学习或提示用户整理相册。这个功能听起来不算“硬核”但恰恰是端侧 AI 的优势所在它可以持续地、静默地在后台工作不用在意网络可用性也不会因为每次数据上传而消耗用户的电量。我自己的体会是AI Edge Foresight 并不强调模型本身有多“聪明”而是强调系统对用户意图的响应速度。它像是给 App 装了一双“近视眼镜”能看清用户手边的内容而不需要每次都抬头望云端。从产品维度来说离线可用、隐私安全、毫秒级预测这三点结合起来才是这套架构真正的护城河。5. 常见问题与排查实录5.1 模型加载失败这个坑估计所有入坑端侧 AI 的朋友都踩过。现象是 App 启动后调用原生推理接口立刻抛出一个类似 “Failed to load model” 的异常。原因通常有两个一是模型文件路径不正确二是模型文件格式损坏。路径不正确多半是因为 Flutter 的 assets 目录在原生层的访问方式不同你需要先把 assets 文件复制到应用私有目录再传给原生层不能直接把 “assets/xxx.tflite” 字符串给 Interpreter。我踩过一次很隐蔽的坑Android 的 assets 解压时会把目录结构保留但 iOS 的 bundle 里路径大小写不敏感一旦文件名写错大小写模拟器上能跑真机上就崩。排查这类问题我的建议是分三步走先确认文件是否存在检查文件大小是否正常再用一个最小的测试工程直接加载该模型排除 Flutter 层干扰最后检查模型内部是否有自定义算子。如果是自定义算子TFLite 解析时可能会报 “Op not found”这时候就需要重新导出模型时把这些算子替换成标准算子。不要嫌麻烦多数模型加载问题都是细节导致的。5.2 推理速度慢推理速度上不去最直接的原因通常是模型没有量化或者设备没有正确使用加速硬件。如果你在桌面级模拟器上跑得很流畅一到真机就慢得离谱那多半是原生推理引擎没有使用 GPU 或 NPU 代理。TFLite 里可以使用 GPU delegate但并不是所有算子都能在 GPU 上执行如果模型里存在不支持的算子TFLite 会自动回退到 CPU而且回退过程会打印一行警告很容易被忽略。还有一个经常被忽略的因素设备过热后降频。我有一台测试机在连续跑五分钟推理后单次推理耗时从 40ms 涨到了 90ms。后来我加入了散热控制和推理任务分批机制情况才好转。从架构角度看推理性能调优必须结合具体机型的算力特点去调不能只盯着模型本身。建议在实际测试时准备几台覆盖中端和旗舰的设备分别统计推理耗时和内存占用形成一个基础性能基线表。后续每一次模型版本升级都用同一组基线数据来做对比才能客观判断模型是不是变慢了。5.3 包体积爆炸这是 Flutter 应用接端侧模型最容易遇到的问题。一个 100MB 的模型文件直接放进 assets打包后 APK 体积瞬间膨胀。解决方式在 3.1 已经提过就是改成动态下载。这里再补充两个细节一是动态下载需要考虑断点续传和网络状态变化建议使用后台下载服务而不是简单用 HTTP 库直接拉二是模型版本管理和 App 版本管理要解耦模型可以单独发布升级不需要强制用户升级 App 才能获得新模型。还有一个容易被忽视的体积隐患Flutter 插件本身可能引入一些不必要的原生库。比如你为了跑 TFLite 引入了全套 TensorFlow Lite 支持库其中包含了很多你用不到的算子包。这种情况下你可以通过 Gradle 的 ABI 过滤只保留 arm64-v8a 架构同时移除 x86_64 等不用的架构安装包能立刻瘦身不少。iOS 端则可以检查是否引入了冗余的 bitcode 支持配合 Xcode 的编译设置把不需要的架构裁剪掉。6. 一些经验体会折腾完这个项目我最深的感触是端侧 AI 真正难的不是算法而是工程整合。Flutter 和 EmbeddingGemma 2 的组合本质上是一次“跨端壳层 本地模型运行时”的联姻需要你同时懂 Dart 层的内存管理、原生层的线程调度、模型转换工具链的细节还要有耐心去一台一台真机上验证表现。没有哪个环节是能靠读文档直接跳过的。如果让我给出一个最想强调的建议那就是先把最小可用链路跑通再考虑性能优化和功能扩展。我见过太多人一上来就研究怎么把模型量化到 4bit、怎么上 NPU、怎么构建多模态识别结果基础通道都没打通。先让一句话进入模型再收到一个像模像样的向量这个正向反馈建立起来之后整个系统的复杂度曲线会变得非常清晰后面不管加什么功能都只是在这个链路上增加分支而已。最后再分享一个小技巧模型输出的向量多留一份原始值做备份。端侧模型升级后新旧向量之间可能出现空间偏移这时候如果保留旧版本向量可以做向量空间的对齐测试快速评估升级带来的影响。这个小动作不复杂但能在模型迭代时省下非常多排查时间。希望这篇文章能给正在琢磨 Flutter 加端侧嵌入模型的朋友一些参考也欢迎大家在自己的项目里有更巧妙的实践时回头来互相印证。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表