☰
端侧AI错题辅导落地实践:架构选型与推理优化
2026/10/20 16:50:08 网站建设 项目流程

去年我刚接手某个本地AI项目时,甲方提的第一句话是:“做错题辅导,直接调大模型API不就行了,为什么非要端侧?”我没急着反驳。跑了一个月真机测试,拍了三百多道错题,在课后辅导班、卧室、上下学地铁这种真实场景里测,答案很清楚:调用云端API的方案,在很多学生手上是撑不住日常使用的。信号不稳定、课堂集中提交导致的排队、每道题几秒甚至十几秒的转圈,再加上家长对照片上传的天然警惕,这些都不是“换个更大的模型”能解决的。于是我们转向了“本地AI错题辅导”这条路线:识别、出题、解析、讲题,尽量全部在设备端完成。这篇内容,就是把当时从架构选型到端侧推理落地过程中踩过的坑和最终的选择,原原本本拆给你看。

我默认的读者是有一定客户端开发经验、理解“小程序”只是轻量宿主、想了解如何把真正的AI推理放到本地的人。如果你还在纠结“小程序到底能不能本地推理”,我也会把这个问题一并回答掉。

1. 为什么先谈“端侧”而不是“大模型”:三个现实约束让我改了架构方案

先说结论:错题辅导这个场景,端侧推理是刚需,不是炫技。我越测越确信,三个约束死死地卡住了“全部上云”的方案。

第一个约束是网络环境。学生用错题App的高频时段,恰好是网络质量最差的时段。晚自习教室几十台设备同时挤一个信号源,校园图书馆、地下自习室、回家路上的地铁车厢,这些地方要么信号弱,要么干脆没网。我们的实测数据里,光失败重试就占了请求总量的两成多。一个辅导工具,学生正卡在哪道题上不去了,结果它先转圈再报错,这个体验基本等于劝退。端侧推理完全离线工作,网络再烂,识别和讲解都照常出结果。

第二个约束是隐私。错题照片和普通风景照完全不同,它天然包含了学生姓名、学校、班级、试卷来源这些信息。家长对“孩子作业照片被传到一个不知道藏在哪的服务器”这件事,接受度越来越低。我接触的几所合作学校里,有班主任直接把“不能用上传照片的App”写进班级公约。本地推理意味着照片文件不离开手机,OCR识别和解题都在本机跑完,这才是真正能拿出来和家长谈的隐私方案。

第三个约束是成本。云端按次计费的模型,单价看着不高,但错题场景的使用量非常吓人。一个学生每天拍十道题,一个班四十人,一个年级八个班,日调用量就是几千次。高峰期再叠加并发,账单会成一个持续膨胀的无底洞。端侧模型是一次性部署,跑一万次和跑一次的成本几乎一样。

当然,端侧也有替代不起的觉悟。我一开始就怕末尾,本地小模型的数学推理能力确实不如云端大模型,尤其面对多步证明题,容易“一本正经地胡说”。所以设计目标不是“本地模型全知全会”,而是把识别和标配讲解做扎实,覆盖日常错题里七八成的高频题型,难题才考虑做云端增强。把边界划清楚后,端侧方案才真正立得住。

2. 三道工序拆解:拍照、读懂题意、生成讲解,分别该选什么模型

很多人做错题辅导,第一反应是“找个多模态大模型,图像输入,答案输出”。我在评估过几类端侧视觉语言模型后,果断拆成了三条独立的工序。拆的好处是:每一步可以单独优化、单独替换,出问题也好定位。具体拆成三道。

2.1 工序一:OCR把照片变成题干原文

第一步是让设备“看懂”照片里写着什么。错题场景的OCR难点不在于单行文字,而在于版面结构:题目可能跨页、可能有配图、可能有手写批注、还可能有表格。我们最终采用的做法是“检测加识别”两段式:先做文本区域的版面检测,再做具体文字识别。

这一道工序直接决定后面所有环节的上限,OCR出错,后面解析再准也没用。所以我把OCR质量卡得很严。实测标准是:对印刷体题目文字,识别准确率必须能达到95%以上;对包含公式的题,允许括号和上下标偶有偏差,但核心数字和符号不能错。

2.2 工序二:语义理解把题干变成结构化信息

拿到题干原文后,模型要回答几个问题:这是什么科目的题?对应教材哪个知识模块(一元二次方程、平行四边形、浮力、动量)?题目类型是选择、填空还是解答?已知条件有哪些,目标是什么?

这步我建议单独用一个轻量文本模型来做,不需要很大的参数规模,因为任务非常聚焦——它不是写作文,而是做信息抽取和归类。相比视觉模型,纯文本走的算子更简单,在端侧的速度优势也更大。经过量化处理之后,这一步在绝大多数手机上都能在半秒内完成。

2.3 工序三:讲解生成把结构化信息变成解题过程

最后才是生成解题思路和步骤。很多人在这步犯了一个错误,让模型“从图片直接写讲解”,中间没有结构化信息约束,输出经常牛头不对马嘴。我坚持先出结构化结果,再喂给生成环节。

给生成模型的输入是一段拼装好的模板Prompt,包含:题干文本、题目类型、知识点标签、学生年级。模型要做的是按步骤推导。为了控制质量和长度,我会限制最大输出词元数,并强制模型按“已知—求解—步骤—验证”的结构输出。生成模型是三者里参数规模最大的一个,但也不需要顶到云端那种几千亿参数的体量。一亿到三四十亿参数区间、4比特量化后的模型已经能胜任绝大多数初中理科题和部分高中数学题的讲解。

我把三道工序的参数定位整理成一张表,方便你做选型时的参考:

工序模型形态参数量级量化后模型体积单次推理目标耗时
文字检测与识别轻量视觉模型千万级60MB内0.5-1秒
语义理解与分类小型文本模型0.5-1.5亿200MB内0.2-0.4秒
解题与讲解生成中规模文本模型1-3亿300-800MB0.5-1.5秒

三段式结构的另一个好处是容错清晰。OCR错了,看检测结果就知道是拍摄角度问题还是模型问题;讲解错了,可以回看结构化抽取结果是否丢信息。要是只用一个端侧多模态大模型,所有错误混在一起,排查起来会非常痛苦。

3. 整体架构与数据流:六步链路里,模型推理被放在哪个位置

架构设计上,我先把“小程序”这个概念摆正:纯Web页面承载不了本地推理。端侧AI要走原生能力,需要拿到CPU、NPU、内存的直接调度权。实际落地时,我采用的是“轻量应用壳+原生推理内核”的混合结构:外层是类小程序的轻量界面,内层是一个原生模块,专门干AI推理和数据库存储。两套之间通过桥接层做异步通信。

在这个结构下,一次完整的错题辅导操作要经过六步链路,每一步都有自己的任务和坑。

第一步是图像采集。这一步往往被初学者忽略,但它是体验的关键。我们做了自动取景框,用边缘检测算法识别拍到的题目区域,自动裁掉多余背景。手抖、反光、倾斜,都在这一层做好预处理。第二步是图像预处理,包括透视矫正、灰度化、对比度增强。这一步不求视觉上多好看,只求OCR能吃得更准。

第三步就是OCR识别。处理完的图像交给本地OCR模块,产出题干原文和版面结构信息。第四步是关键的结构化理解:把原文喂给理解模型,产出学科、题型、知识点、已知条件、求解目标。第五步是讲解生成,把理解模型产出的结构化信息和模板Prompt拼装后,喂给生成模型,流式产出解题步骤。

第六步是落库与呈现。生成结果连同原图、OCR文本、结构化标签一起写入本地数据库,同时根据知识点归属更新错题统计。界面上以“题干信息+逐步解答+知识点标签”的卡片形式呈现,学生可以直接保存、标记和回顾。

架构模块上我坚持了四层分离:

  • UI层:只管展示、交互,不知道模型存在。
  • 任务编排层:负责六步链路的调度、超时处理、失败回退。
  • 服务层:模型管理器、OCR服务、推理桥、本地存储接口。
  • 基础设施层:推理引擎、SQLite数据库、文件系统。

这种分层最直观的收益是,我可以单独替换OCR模块而不影响其他层,也可以把某个步骤的模型从本地方案切换成云端增强方案,UI完全无感知。

整个链路中,推理桥层是最需要花心思的。它负责模型实例的加载与释放、控制并发、管理预热状态。错题辅导这个场景的并发量其实很低,同一时刻最多一到两个推理任务,但模型预热成本高、显存内存占用大,管理不好就会出现“拍了一道题然后App直接被系统杀死”的惨剧。我把推理桥设计成单例模式,所有模型实例由它统一管理,启动时只加载小模型,大模型按需加载,用完即释放。

4. 端侧推理工程落地:引擎选型、量化取舍与上下文管理

说到端侧推理,很多人第一反应是“模型有多大、手机装不装得下”。实际上跑过一轮真机后你会发现,最大的拦路虎根本不是体积,而是推理引擎对算子、量化格式和内存回收的支持。这里把我的工程判断拆开讲。

4.1 推理引擎选型看四点,不是看跑分

我评估移动端推理框架时,不看官方在高端机上刷出来的跑分,只看四个更实际的标准:

第一,算子覆盖完整性。端侧模型里除了常规矩阵乘法和激活函数,还有归一化、旋转位置编码、注意力计算这类结构。框架算子不全,就意味着模型跑不起来或者要拆成几段算,延迟直接翻倍。第二,动态形状支持。错题文本长度变化很大,模型输入不是固定尺寸,框架必须支持动态张量形状,这一点能筛掉一半候选。第三,量化支持。必须是能跑的4比特量化推理,不是只支持导出格式但真机上被反复回退成浮点。第四,内存回收能力。推理结束后能不能立刻把池化内存归还给系统,直接决定App会不会被杀后台。

收尾一句:选型阶段就该带着目标模型去框架里跑真机压测,别只看示例程序里的Benchmark。

4.2 量化取舍:我为什么坚持“4比特为主,敏感层例外”

量化是端侧推理绕不开的坎。模型从32比特浮点位压到4比特整型,体积剪到八分之一,推理速度也能快不少,但换来的可能是精度的轻微下降。错题辅导是教育场景,讲解步骤错一个符号、错一个顺序都可能误导学生,量化策略必须保守。

我采用“感知量化和混合精度”结合的做法:整体按4比特量化,但对影响推理质量的少数层——特别是注意力输出的投影层和最后输出的计算层——保留更高精度。实际操作时先做一次全模型浮点推理收集各层权重分布,找出离群值较多的层,再做逐层量化的敏感度测试。这一步比较费时间,但值得做。

4.3 上下文管理:错题场景不需要长篇对话

很多端侧模型跑得慢,不是因为模型大,而是上下文开得太长。错题辅导的本质是单轮任务,不是聊天。不存在的多轮对话,所以我会把上下文窗口限制在一个很小的范围,比如512词元,足够容纳题干加一小段历史。这会让预填充阶段的计算量大幅下降,生成阶段的键值缓存占用也直线减少。

Prompt模板也要精修。我的是这样一个结构:学科与年级定位、题干原文、结构化信息(题型、知识点、已知条件)、输出格式约束、语气约束。Prompt模板看似简单,但它是本地模型输出质量提升最明显的杠杆之一。同一个模型,模板写得好坏,答案可读性差别很大。

这里放一段我在推理桥层的TypeScript伪代码,展示一次标准调用的骨架:

class InferenceBridge { static instance: InferenceBridge; private ocrModel?: any; private textModel?: any; private genModel?: any; async warmUp() { // 启动时只加载小模型,生成模型按需加载 this.ocrModel = await MobileInference.load("ocr_quant_v1", { precision: "int8" }); this.textModel = await MobileInference.load("text_struct_v1", { precision: "int4" }); } async handleWrongQuestion(photoPath: string) { const taskRunner = new TaskChain([ this.preprocess.bind(this, photoPath), this.runOCR.bind(this), this.understand.bind(this), this.generate.bind(this), this.saveToLocalDB.bind(this), ]); return taskRunner.run(); } private async runOCR(image: any) { // 返回题目文本 + 版面结构 const result = await this.ocrModel.run(image); return result.text; } private async understand(text: string) { const prompt = buildPrompt({ task: "struct", text }); return this.textModel.run(prompt, { maxTokens: 128 }); } private async generate(structInfo: any) { const prompt = buildPrompt({ task: "solve", structInfo }); const engine = await this.loadGenModelLazy(); return engine.generate(prompt, { maxTokens: 512, stream: true }); } }

这里loadGenModelLazy是懒加载,只有在需要生成讲解时才加载生成模型,避免一进App就把几百MB模型全怼进内存,导致低端机直接黑屏。

生成过程中的流式输出也很关键。端侧模型单次完整生成要几百毫秒到一秒以上,让我告诉你是什么体验:如果必须全量生成完才显示,学生面前就是一片空白加一个小圈圈转半秒以上。流式输出让第一个字在几百毫秒内就蹦出来,然后一个字一个字地滚动显示,用户感知到的延迟会少很多,焦虑感也低了。这个体验投资非常值。

5. 错题本的数据模型:让“知识错误”真正可查询、可回顾

错题辅导的核心资产不是模型,而是积累下来的错题数据。学生持续用App两周,本地就攒了几百道错题。如果没有一套好用的数据模型,这些错题就是一堆死照片。这个模块必须一开始就设计对,后面补数据模型是真的痛苦。

我采用的是一张主表加多张关联表的结构。主表存每道错题的基本信息:原始照片路径、OCR提取文本、题型、学科、知识点ID、错误原因类型、讲解内容、时间戳和状态。知识点和错因单独立表,主表和它们多对多关联。

一段核心的表结构设计建议,直接用SQL表达:

CREATE TABLE wrong_questions ( id INTEGER PRIMARY KEY AUTOINCREMENT, photo_path TEXT NOT NULL, ocr_text TEXT, subject TEXT, problem_type TEXT, knowledge_point_id INTEGER, wrong_reason TEXT, solution_text TEXT, source_file_id INTEGER, status INTEGER DEFAULT 0, create_time INTEGER ); CREATE TABLE knowledge_points ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE, chapter TEXT, difficulty INTEGER DEFAULT 1 ); CREATE TABLE wrong_reason_tags ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE, description TEXT );

为什么知识点必须单独建表而不是直接存字符串?因为只有结构化之后才能做统计。有了这张表,我可以轻易回答“这个学生哪些知识点错得最多”这类问题,再根据错误频率给复习建议。而如果只存一段文本标签,做统计时每次都要去分词和归一化,痛苦不堪。

关键还有个字段是status。我把它设为整型,0表示待复习,1表示已掌握,2表示再刷一次。它驱动的是错题回顾机制。本地不需要做多复杂的间隔重复算法,我实际用的只是一个简化版:每道题录入后三天推一次复习提醒,答对就置为已掌握,答错就把状态重置回0。效果稳定,代码也不复杂。对一个小程序端的工具来说,够用了。

数据导出的能力也不可少。本地数据是学生的资产,我留了一个“导出错题本”的入口,可以把结构化数据打包成通用格式发给老师。这是家教场景里很受欢迎的功能,也让家长知道数据在本地、可控——不仅是隐私承诺,而且是实际操作。

6. 性能优化和设备兼容:我的目标不是跑通,而是感知不到延迟

端侧推理有个反常识的地方,单看每一项都挺快,串在一起就容易让人等得不耐烦。OCR一秒钟,理解三百毫秒,生成八百毫秒,加起来两秒多。两秒听起来不长,但在一个高频操作里,每道题都要两秒,学生很快会烦躁。

所以我把优化目标定得非常明确:完整链路控制在1.5秒左右,且用户第一眼看到反馈的时间不能超过500毫秒。按这个目标,拆解后的各项预算如下:

链路环节预算耗时优化手段
拍照与预处理100-200ms自动裁切加灰度化一次性完成
OCR识别400-600ms轻量检测模型加小批量预测
结构化理解200-300ms小模型加短上下文
讲解生成400-600ms限制输出长度,流式首字加速

要把这些数字兑现,我做了四件事。第一,模型预热策略:用户进入拍照页的瞬间,后台就把OCR模型和文本理解模型加载好,拍完照片直接推理,省掉加载时间。第二,图像预处理轻量化,透视矫正直接抛给硬件加速,之后立刻转灰度图,减少后续算子的计算量。第三,生成限制输出词数,把默认回答死死压在200词元之内,还设了停止符,模型判断“步骤已完整”就会早停,不再废话。第四,内存的显式释放,每次推理完成后立刻释放中间张量,避免连续拍三四道题后内存水位飙升。

设备兼容是另一个大坑。市面上的安卓设备从旗舰到千元机差异巨大,同一个模型在不同机上的表现天差地别。我的处理方式是分档策略:高端机型跑完整三段式推理,中端机型跑OCR加小模型理解,生成环节退化为模板填充;低端机型则是只保OCR和结构化标签,讲解改为预设模板按公式库填充。分档逻辑不是写死在代码里的,而是通过一个远程开关来控制按需回退,模型更新也不会导致老设备直接不可用。

更底层的保障是崩溃兜底。推理引擎偶尔会碰到不支持的算子或资源不足,在入口处做一层异常捕获,一旦推理失败,直接提示用户“当前设备暂不支持本地讲解,可尝试云端增强”而不是干等着闪退。这个兜底教会了我很多次:给用户一条退路,比让他在loading圈里等一辈子强。

7. 落地顺序、踩坑复盘和一些实在建议

这类项目最忌讳一上来就做大而全的“全科错题AI助手”。我实际跑下来的顺序建议是这样。

先做一个超级窄的MVP:只支持初中数学,只支持一元二次方程、函数单调性等十个高频错题题型,模型只在高端机型上开放完整推理,其他机型走本地OCR加云端API。为什么是初中数学?因为初中理科题的解题步骤相对固定,端侧小模型应付得了,准确率容易达标。先定义这个范围,后面的模型训练、模板设计和验收标准都有了一个清晰边界。

MVP跑通后会遇到一个很有意思的现象:用户反馈里有“这道题怎么解错了”的占比会明显下降,取而代之的是“这道题步骤太简略,我看不懂为什么这么做”。这时候才真正体现出错题辅导的核心需求——学生需要的不是标准答案,而是“我错在哪、为什么错”。结构化数据里存放的错因标签就在这时候派上用场。录入错题时,让学生顺手点一下错因:“粗心算错”“概念混淆”“方法不当”“不会变通”。积累一段时间后,按错因汇总给出复习建议,比单纯按知识点汇总更有引导性。

再回到本地和云端的边界问题。我最后的方案是“默认全部本地,疑难自动提示”。本地模型对每道题产出一个置信分,置信分高的直接展示讲解;置信分低的提示“本机算力有限,建议联网获取更权威解析”。这样既保住了大多数场景的离线能力和隐私,也给真正的难题留了一条出路。用户不会觉得被一个笨模型耽误了。

最后分享一个我反复吃的亏:量化模型不是压得越小越好。我曾把生成模型压到3比特,模型体积少了三成,但讲解正确率掉了近二十个百分点,尤其是涉及符号推导的题,几乎全线崩溃。后来又花了大把时间回退到4比特混合精度方案。压缩带来的收益要放在整条体验链路中看,省下的几百兆空间,远远抵消不了错误率上升带来的口碑损失。

端侧AI错题辅导,真正的难点不在“端侧”,而在想清楚“哪些必须端侧、哪些可以放云端、哪些干脆不做”。把这个问题想透,模型选型、架构设计、性能优化都会自然有答案。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询