ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

MTK平台部署Qwen2.5大模型:模型转换、量化与推理优化实战

MTK平台部署Qwen2.5大模型:模型转换、量化与推理优化实战 1. 为什么要在MTK平台上跑Qwen2.5把Qwen2.5这类大模型塞进MTK平台很多人第一反应是这不是自找麻烦吗。毕竟MTK芯片的主战场一直是移动终端、智能座舱、边缘盒子这类功耗敏感、算力有限的场景而Qwen2.5哪怕是0.5B的版本参数量摆在那里跟传统CNN模型完全不是一个量级。但实际项目做下来我发现这条路不但走得通而且在特定场景下比上云更划算——数据不出设备、响应延迟稳定、没有网络抖动这些优势在工业质检、车载语音助手、离线翻译笔这类产品上是刚需。我自己第一次接触这个需求是给一个做智能会议终端的客户做方案。他们的设备用的是MTK Genio系列平台原本只跑一些语音唤醒和降噪算法后来产品经理拍脑袋要加会议纪要自动生成要求本地推理、不联网。当时团队里没人相信MTK能扛得住结果折腾了两个月从模型转换到推理部署全流程跑通实测Qwen2.5-0.5B-Instruct在Genio 1200上首token延迟控制在800ms以内生成速度大概12 tokens/s虽然不算快但完全可用。这篇文章就是把这套流程完整拆开讲。核心关键词是MTK平台、Qwen2.5、模型转换、推理部署我会从模型格式转换、量化策略、推理引擎选型、内存与线程调优、实测性能数据这几个维度展开适合正在做边缘AI部署的工程师、对端侧大模型感兴趣的开发者以及需要给MTK平台选型做技术评估的架构师。文章里涉及的操作步骤和参数配置都是我在实际项目中验证过的不是纸上谈兵。需要提前说明的是MTK平台型号差异很大从入门的Genio 350到高端的Genio 1200、天玑系列NPU算力和内存带宽差了好几倍。我下面讲的方法论是通用的但具体参数需要根据你手上的芯片型号做调整。另外Qwen2.5有0.5B、1.5B、3B、7B等多个尺寸MTK平台实际能跑得舒服的主要是0.5B和1.5B3B以上就要看具体芯片的NPU能力了。2. Qwen2.5模型转换的核心链路2.1 从HuggingFace原始权重到可部署格式Qwen2.5在HuggingFace上发布的是PyTorch格式的权重包含config.json、model.safetensors、tokenizer.json等文件。MTK平台的推理引擎通常不直接吃PyTorch权重需要先转成ONNX或者MTK自家的格式。这里有个关键决策点走ONNX路线还是走MTK NeuroPilot路线。ONNX路线的好处是通用性强转换工具链成熟torch.onnx.export或者optimum库都能干这活。缺点是ONNX Runtime在MTK平台上的NPU加速支持有限很多时候只能跑CPU性能上不去。MTK NeuroPilot路线是官方推荐的能把模型编译成NPU能执行的格式性能提升明显但工具链相对封闭文档也没那么友好。我实际项目里两条路都走过。第一版用ONNX在Genio 1200上CPU推理Qwen2.5-0.5B生成速度只有3 tokens/s基本没法用。后来切到NeuroPilot用mtk_nn_stream工具链编译速度提到12 tokens/s翻了四倍。所以如果你的项目对性能有要求强烈建议走NeuroPilot路线。转换的第一步是导出ONNX。这里有个坑Qwen2.5用了RoPE位置编码和GQA分组查询注意力直接torch.onnx.export可能会报错。我的做法是用HuggingFace的optimum库它已经内置了对Qwen系列的支持from optimum.onnxruntime import ORTModelForCausalLM from transformers import AutoTokenizer model_id Qwen/Qwen2.5-0.5B-Instruct model ORTModelForCausalLM.from_pretrained(model_id, exportTrue) tokenizer AutoTokenizer.from_pretrained(model_id) model.save_pretrained(./qwen2.5_onnx) tokenizer.save_pretrained(./qwen2.5_onnx)这段代码跑完你会得到一个ONNX模型文件通常是model.onnx加上model.onnx_data外部权重。注意Qwen2.5-0.5B的ONNX文件大概1GB左右1.5B版本大概3GB转换过程中需要保证磁盘空间充足。2.2 量化策略INT8还是INT4ONNX模型直接部署到MTK平台内存占用和带宽压力都很大。量化是必须的。MTK NeuroPilot支持INT8和INT4两种量化精度选择哪个要看你的场景。INT8量化的精度损失很小Qwen2.5-0.5B量化后模型大小从1GB降到500MB左右实测生成质量几乎无损。INT4量化后模型只有250MB但精度损失明显尤其是长文本生成时容易出现重复、逻辑断裂的问题。我的经验是如果内存够用优先INT8如果内存紧张且对生成质量要求不高再考虑INT4。MTK的量化工具是mtk_quantize基本用法mtk_quantize --input_model qwen2.5_onnx/model.onnx \ --output_model qwen2.5_int8.onnx \ --quant_type int8 \ --calibration_dataset calibration_data.txt这里的关键是校准数据集。量化不是简单地把FP32截断成INT8而是需要一批代表性数据来统计激活值的分布范围。校准数据选得不好量化后的模型精度会崩。我的做法是从实际业务场景里抽200-500条文本覆盖各种长度和主题作为校准集。别用随机生成的文本那样统计出来的分布跟真实推理差太远。2.3 编译成MTK NPU可执行格式量化完的ONNX模型还不能直接在NPU上跑需要用MTK的编译器转成.dlc或者.nb格式。这一步用的是NeuroPilot SDK里的mtk_nn_compilermtk_nn_compiler --model qwen2.5_int8.onnx \ --target genio1200 \ --output qwen2.5_int8.dlc \ --optimize_level 3--target参数指定目标芯片型号不同芯片的NPU指令集不一样编译出来的格式不通用。--optimize_level控制优化等级3是最高编译时间会长一些但推理性能最好。编译过程中最常见的报错是unsupported operator。Qwen2.5里有些算子MTK NPU不支持比如某些变体的LayerNorm或者自定义的激活函数。遇到这种情况有两个解法一是用MTK提供的算子替换工具把不支持的算子映射到支持的实现二是把不支持的层切出来放到CPU上跑其余部分在NPU上跑。第二种方案性能会打折扣但胜在简单。3. 推理引擎选型与集成3.1 MTK NeuroPilot Runtime的接入方式模型编译成.dlc之后下一步是在应用层调用MTK的推理Runtime。NeuroPilot提供了C和Python两套API实际产品里用C居多因为要跟Android或者Linux应用集成。C接入的核心流程是加载模型、创建推理上下文、准备输入输出Tensor、执行推理、解析结果。下面是一个简化版的代码框架#include mtk_nn_runtime.h // 加载模型 MtkModel* model mtkModelCreate(qwen2.5_int8.dlc); MtkContext* ctx mtkContextCreate(model); // 准备输入 MtkTensor* input_ids mtkTensorCreate(ctx, input_ids, MTK_INT32, {1, seq_len}); MtkTensor* attention_mask mtkTensorCreate(ctx, attention_mask, MTK_INT32, {1, seq_len}); // 执行推理 mtkContextSetInput(ctx, input_ids, 0); mtkContextSetInput(ctx, attention_mask, 1); mtkContextRun(ctx); // 获取输出 MtkTensor* logits mtkContextGetOutput(ctx, 0);实际项目里这段代码要封装成一个类管理模型生命周期、处理多轮对话的KV Cache、做tokenizer的编解码。KV Cache这块特别重要Qwen2.5是自回归生成每生成一个token都要重新计算注意力如果不缓存KV生成100个token就要算100次完整的前向性能会差到没法用。3.2 Tokenizer的端侧适配Qwen2.5用的是BPE tokenizer词表大小15万左右。在MTK平台上跑tokenizer有两个选择用HuggingFace的tokenizers库C版本或者用MTK提供的轻量级tokenizer实现。tokenizers库功能全但依赖Rust运行时在嵌入式环境里编译比较麻烦。MTK的轻量级实现只支持基本的BPE编码特殊token的处理需要自己补。我实际项目里用的是后者因为编译简单、内存占用小。代价是要自己处理Qwen2.5的chat template把对话历史拼成模型期望的格式|im_start|system You are a helpful assistant.|im_end| |im_start|user 你好|im_end| |im_start|assistant这个模板如果拼错了模型输出会完全乱掉。我踩过一次坑把|im_end|写成了|endoftext|结果模型一直重复输出用户的问题排查了半天才发现是模板问题。3.3 多线程与NPU核心调度MTK平台的NPU通常有多个核心比如Genio 1200的NPU有4个核心。推理时怎么调度这些核心对性能影响很大。NeuroPilot Runtime默认是单核心推理要开多核心需要显式设置mtkContextSetNumCores(ctx, 4);但多核心不是越多越好。Qwen2.5的推理是串行的每个token的生成依赖前一个token的结果多核心并行只能加速单个token内部的计算。实测下来4核心比单核心大概快2.5倍不是线性的4倍因为核心间的同步有开销。另外CPU和NPU的协同也很关键。tokenizer的编解码、采样策略top-k、top-p这些操作是在CPU上跑的如果CPU线程数不够会成为瓶颈。我的配置是NPU用4核心跑模型推理CPU开2个线程做tokenizer和采样整体吞吐能提升30%左右。4. 内存与性能调优实战4.1 内存占用分析与优化Qwen2.5-0.5B INT8量化后模型文件250MB但实际运行时内存占用远不止这些。推理过程中需要分配输入输出Tensor、KV Cache、中间激活值。实测下来Genio 1200上跑Qwen2.5-0.5B峰值内存占用大概600MB。KV Cache是大头。Qwen2.5-0.5B有24层每层KV Cache大小是2 * hidden_size * seq_len * batch_size * sizeof(dtype)。假设hidden_size是896seq_len是512batch_size是1INT8存储那KV Cache就是2 * 896 * 512 * 1 * 1 917KB每层24层就是22MB。看起来不大但如果seq_len拉到2048就是88MB内存压力就上来了。优化KV Cache有几个方向一是限制最大生成长度别让模型无限生成二是用PagedAttention之类的技术做内存分页但MTK平台支持有限三是INT4存储KV Cache精度损失可接受。我实际项目里用的是第一种把max_new_tokens限制在256配合业务场景足够用。4.2 首token延迟与生成速度的平衡首token延迟TTFT和生成速度TPS是两个关键指标但它们的优化方向有时候是矛盾的。TTFT主要受prefill阶段影响输入prompt越长TTFT越大。TPS受decode阶段影响跟模型大小、NPU算力、内存带宽都相关。实测数据Genio 1200上Qwen2.5-0.5B INT8prompt长度128TTFT约800msTPS约12 tokens/s。如果把prompt长度拉到512TTFT会涨到2.5s左右TPS基本不变。优化TTFT的手段一是用chunked prefill把长prompt切成小块分批处理降低单次计算量二是用NPU的并行能力加速prefill阶段的矩阵运算。优化TPS的手段一是减少采样开销用greedy search代替top-p采样二是用投机解码speculative decoding用小模型预测、大模型验证但MTK平台对投机解码的支持还在完善中。4.3 温度控制与功耗管理MTK平台大多是功耗敏感设备大模型推理会让NPU和CPU满载发热明显。Genio 1200在持续推理时芯片温度能到70度以上如果不做温控会触发降频性能直接腰斩。我的做法是在推理循环里加温度检测超过阈值就降低推理频率或者暂停推理。MTK提供了mtk_thermal_get_temp接口可以读取芯片温度。另外NPU的频率也可以动态调整mtk_npu_set_freq可以设置NPU工作频率低频省电但性能下降高频性能好但发热大。实际项目里我设的是中档频率平衡性能和功耗。还有一个容易被忽略的点内存带宽。大模型推理是内存带宽密集型任务NPU算力再强如果内存带宽不够也会成为瓶颈。MTK平台的LPDDR带宽有限跑大模型时要尽量减少内存拷贝输入输出Tensor尽量复用避免频繁分配释放。5. 实测性能数据与场景适配5.1 不同MTK芯片的实测对比我在三款MTK芯片上跑过Qwen2.5-0.5B INT8数据如下芯片型号NPU算力TTFT (prompt128)TPS峰值内存Genio 3501 TOPS3.2s3.5 tokens/s580MBGenio 7004 TOPS1.5s7 tokens/s600MBGenio 12008 TOPS0.8s12 tokens/s620MBGenio 350跑起来比较吃力TTFT超过3秒用户体验不好适合对实时性要求不高的场景比如离线文档摘要。Genio 700是甜点级TTFT 1.5秒TPS 7能满足大部分语音助手场景。Genio 1200性能最好但成本也最高适合高端会议终端、车载座舱这类产品。5.2 Qwen2.5-1.5B的可行性评估Qwen2.5-1.5B参数量是0.5B的三倍INT8量化后模型大小约1.5GB内存占用峰值大概1.8GB。Genio 1200的NPU算力够但内存带宽是瓶颈。实测TTFT约2.5sTPS约5 tokens/s比0.5B慢了一倍多。如果业务场景对生成质量要求高1.5B是值得的它的逻辑推理和长文本生成能力明显强于0.5B。但如果只是做简单的意图识别、槽位填充0.5B完全够用没必要上1.5B。我的建议是先用0.5B跑通流程验证业务价值再根据效果决定是否升级到1.5B。5.3 典型应用场景的配置建议不同场景对延迟、吞吐、精度的要求不一样配置策略也要调整语音助手要求低延迟TTFT控制在1秒内TPS 8以上。建议用Genio 700以上芯片Qwen2.5-0.5B INT8max_new_tokens限制在128。会议纪要对延迟不敏感但要求生成质量高。可以用Genio 1200Qwen2.5-1.5B INT8max_new_tokens放到512。离线翻译要求双向翻译输入输出都比较长。建议用Genio 1200Qwen2.5-1.5B INT8配合chunked prefill优化TTFT。工业质检报告生成输入是结构化数据输出是固定格式文本。Qwen2.5-0.5B足够重点优化TPS用greedy search减少采样开销。6. 踩过的坑与排查经验6.1 模型转换时的算子不兼容第一次转换Qwen2.5-0.5B到MTK格式时编译器报了一堆unsupported operator主要集中在RoPE位置编码和GQA注意力上。MTK NPU对这两个算子的支持是分芯片的Genio 1200支持Genio 350不支持。排查方法先用mtk_nn_compiler --list_ops查看目标芯片支持的算子列表然后对照ONNX模型的算子清单找出不支持的。对于不支持的算子要么用MTK提供的替换工具改写要么把相关层切到CPU上。我当时的做法是把RoPE层切到CPU其余部分在NPU上跑性能损失大概15%但至少能跑起来。6.2 量化后精度崩塌的定位有一次量化完Qwen2.5-0.5B模型输出全是乱码重复同一个词。排查了半天发现是校准数据集的问题。我当时图省事用了一段英文技术文档做校准结果模型对中文的激活值分布统计完全偏了量化后中文生成能力直接崩掉。教训校准数据集必须覆盖实际业务场景的语言和主题分布。如果是中英混合场景校准集里中英文比例要跟实际使用一致。另外校准集的数量也有讲究太少统计不准太多浪费时间200-500条是比较合适的范围。6.3 推理时的内存泄漏在Android应用里集成MTK推理Runtime时遇到了内存泄漏问题。每轮对话结束后内存占用都会涨一点跑几十轮之后OOM。用valgrind排查发现是KV Cache没有正确释放。MTK的Runtime API里mtkTensorDestroy和mtkContextDestroy要成对调用而且顺序不能错。我当时的代码是先销毁Context再销毁Tensor结果Tensor的引用计数没减到零内存一直不释放。改成先销毁Tensor再销毁Context问题解决。6.4 多轮对话的上下文管理Qwen2.5支持多轮对话但MTK平台内存有限不能无限累积上下文。我的做法是维护一个滑动窗口保留最近N轮对话超过就丢弃最早的。N的取值要看内存预算Genio 1200上N5比较安全Genio 350上N2。另外system prompt也会占用上下文长度。如果system prompt很长留给对话历史的空间就少了。建议把system prompt精简到100字以内把关键指令放在user message里。7. 从Demo到产品的工程化建议7.1 模型版本管理与OTA升级产品化之后模型不可能一成不变。业务需求变了、Qwen发布了新版本、量化策略优化了都需要更新模型。MTK平台的模型文件通常放在/vendor/etc/或者应用私有目录OTA升级时要考虑模型文件的版本管理和回滚机制。我的做法是模型文件带版本号应用启动时检查版本不匹配就触发下载。下载用差分更新只传变化的权重减少流量。回滚机制是保留上一个版本的模型文件新版本加载失败时自动切回旧版本。7.2 推理服务的稳定性保障端侧推理不像云端有完善的监控和容错出问题了只能靠本地日志。建议在推理Runtime里加详细的日志记录每次推理的输入长度、输出长度、耗时、内存占用。出问题时能快速定位。另外要加超时机制。如果某次推理超过预期时间比如TTFT超过5秒直接中断返回兜底结果。别让用户对着卡死的界面等。7.3 与云端方案的混合部署纯端侧推理有局限性模型能力受限于芯片算力。实际产品里我建议做混合部署简单任务端侧处理复杂任务上云。比如语音助手唤醒词识别、简单指令在端侧跑Qwen2.5-0.5B复杂的知识问答上云跑更大的模型。混合部署的关键是任务路由。可以根据输入长度、任务类型、网络状态来决定走端侧还是云端。网络不好时全走端侧保证基本可用网络好时复杂任务上云提升体验。7.4 性能监控与持续优化产品上线后要持续监控端侧推理的性能数据。MTK平台可以通过mtk_perf接口采集NPU利用率、内存带宽、功耗等指标。这些数据回传后能帮你发现性能瓶颈指导后续优化。我实际项目里上线第一个月就发现Genio 350机型上TTFT波动很大有时候3秒有时候5秒。排查后发现是后台有其他进程占用NPU导致推理排队。后来加了NPU资源锁保证推理进程优先问题解决。这套流程走下来MTK平台跑Qwen2.5从技术验证到产品落地大概需要2-3个月。难点不在单点技术而在工程化的细节模型转换的算子兼容、量化的精度保持、推理的内存管理、多场景的性能调优。每个环节都有坑但踩过之后回头看这条路是通的而且随着MTK NPU算力的提升和工具链的完善端侧大模型的门槛会越来越低。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进