ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MTK平台跑Qwen2.5实战:从GGUF到真机推理全攻略

MTK平台跑Qwen2.5实战:从GGUF到真机推理全攻略 前阵子我把一台吃灰的天玑平板翻了出来试着在上面跑Qwen2.5。一开始我以为像在PC上一样把GGUF文件丢给llama.cpp就完事结果被现实狠狠教育了一课模型转换、量化格式、推理引擎、大小核调度、上下文长度每个环节都能翻车。这篇文章就把MTK联发科平台上部署Qwen2.5的完整链路拆开讲清楚从Hugging Face上的原始权重一路做到MTK真机推理中间穿插我在天玑平台上实际踩过的坑和最终沉淀下来的解决方案。文章适合几类人想在自己的天玑手机/平板上跑本地大模型的折腾党、做边缘AI设备的嵌入式工程师、以及准备把Qwen2.5集成进Android App的应用开发者。我会尽量把为什么这样做也讲明白而不是只丢给你一串能跑的命令。1. MTK平台的硬件底牌为什么手机跑模型和PC完全是两码事1.1 统一内存架构这里没有显存可以搬运PC上跑大模型显卡有独立显存模型权重可以一股脑塞进VRAMCPU内存只负责加载和调度。但MTK平台无论是天玑手机芯片还是Genio嵌入式平台采用的都是统一内存架构Unified Memory ArchitectureCPU、GPU、NPU、DSP共享同一块DDR内存。这意味着Qwen2.5-7B的GGUF文件一旦加载进内存权重就老老实实占着系统RAM的一部分没有显存这个概念。这也是很多从PC思维迁移过来的开发者最容易忽略的点。PC上能用--n-gpu-layers把层塞进显存MTK的集成GPUMali/Immortalis系列虽然也能做通用计算但开源推理引擎对它的算子支持非常有限绝大多数人实际走的还是CPU推理路线。既然走CPU内存带宽就成了第一瓶颈。Qwen2.5-7B用Q4_K_M量化后权重接近4.4GB如果系统内存只有8GB跑起来之前就要掂量一下留给系统和后台应用的空间够不够否则加载到一半进程被Android的LMKLow Memory Killer杀掉是常有的事。1.2 天玑和Genio同为MTK平台差距却很大MTK的产品线跨度很大不能一概而论。天玑系列Dimensity面向手机和平板高端型号用LPDDR5X内存中低端还在用LPDDR4XGenio系列面向物联网和嵌入式场景比如Genio 1200、Genio 700/750内存带宽更是千差万别。同一个Qwen2.5模型在高端天玑上可能只是能用在Genio入门板上直接就变成数学题算不完。这里有个关键认知大模型端侧推理的速度很大程度上取决于内存带宽而不是CPU算力。LPDDR4X 4266Mbps和LPDDR5X 8533Mbps之间的带宽差异直接体现在每秒钟能生成多少个token上。选平台的时候别只看CPU主频和核心数要先看内存规格。这个道理放在高通平台上也成立但MTK因为覆盖了大量中低端设备问题暴露得更明显。MTK和高通的另外一个差别是软件生态高通在移动端AI推理的驱动和SDK材料相对齐全而MTK的NeuroPilot APU工具链对普通开发者并不友好后面第5章会专门说这个事。1.3 Qwen2.5系列规格怎么选不是越大越好Qwen2.5官方开源了多个规格0.5B、1.5B、3B、7B、14B、32B、72B。端侧部署通常只考虑前四档7B已经是很多中端设备的极限。我按Q4_K_M量化后的重量做了一个粗略估算模型规格Q4_K_M文件大小运行时内存占用含KV Cache和开销端侧体验Qwen2.5-0.5B约0.4GB约1GB很流畅适合关键词抽取、意图识别Qwen2.5-1.5B约0.9GB约2GB流畅适合文本总结、简单对话Qwen2.5-3B约1.8GB约3.5GB中等速度日常问答可接受Qwen2.5-7B约4.4GB约6-7GB需要旗舰级内存带宽还得接受延迟选型原则就一句话不是参数越大越好而是你设备的DDR带宽能喂饱的那个规格才是最好的。如果生成速度低于每秒2个token用户交互体验会非常糟糕再聪明的模型也白搭。Qwen2.5官方默认支持32K上下文通过YaRN还能扩展但端侧部署时通常要把上下文压到4096甚至2048否则KV Cache会吃掉大量内存。这个矛盾到第6章还会再回来折磨你。写到这里你应该已经意识到MTK平台上跑Qwen2.5不是一个下载即运行的事。下一个大坑出现在模型文件本身Hugging Face上下来的PyTorch权重不能直接喂给推理引擎必须先做转换。2. 从Hugging Face权重到GGUF模型转换的完整链路2.1 先把原始权重拉下来转换的第一步是拿到原始模型。Qwen2.5各规格在Hugging Face上都有官方仓库名字类似Qwen/Qwen2.5-7B-Instruct。国内直连很慢我习惯设置HF_ENDPOINT环境变量走镜像pip install huggingface_hub export HF_ENDPOINThttps://hf-mirror.com huggingface-cli download Qwen/Qwen2.5-7B-Instruct --local-dir ./Qwen2.5-7B-Instruct如果不习惯命令行也可以直接git clone但模型仓库里可能有大文件指针需要先装git-lfs。无论哪种方式下载完都建议核对一下config.json里的model_type是否为qwen2再看看tokenizer_config.json里的eos_token是不是|im_end|。这些字段看起来不起眼但如果后面转换出的模型输出重复、停不下来返回头查这里大概率能找到原因。2.2 用convert_hf_to_gguf.py做初次转换llama.cpp仓库里自带转换脚本路径是convert_hf_to_gguf.py。转换前先装好依赖git clone https://github.com/ggerganov/llama.cpp cd llama.cpp pip install -r requirements.txt然后执行转换这一步把原始的混合精度PyTorch权重统一成F16或F32的GGUF。端侧建议F16因为F32体积大一圈精度上又没有肉眼可见的收益python3 convert_hf_to_gguf.py ../Qwen2.5-7B-Instruct \ --outfile qwen2.5-7b-instruct-f16.gguf \ --outtype f16转换过程会打印每一层的张量名、形状、精度最后生成一个可能高达15GB左右的F16文件。这里有个容易踩的坑转换脚本的版本要和llama.cpp主程序版本尽量同步否则可能出现量化格式版本号对不上、运行时报unknown GGML model magic的兼容性错误。我一般直接拉最新release版本的源码整套工具链保持在同一个提交上能省掉很多莫名其妙的兼容性排查。另外说一句闲话其实社区里已经有人放出了Qwen2.5各规格的GGUF量化文件不想折腾的话直接下载就行。但我还是建议亲手转换一遍因为只有亲手做一次你才能理解量化是什么、GGUF文件内部长什么样、后续排查问题的时候心里才有底。2.3 量化把模型从15GB压到4.4GBF16 GGUF体积太大手机存储和内存都吃不消接下来要用llama.cpp自带的量化工具cmake --build build --config Release -j4 ./build/bin/llama-quantize qwen2.5-7b-instruct-f16.gguf \ qwen2.5-7b-instruct-q4_k_m.gguf \ Q4_K_M量化类型一堆K系列量化支持对不同部分用不同位数处理attention层、feed-forward层分开对待Q4_K_M是体积和质量权衡得最好的一档。Q8_0质量损失更小但体积接近翻倍Q2_K体积最小但生成质量下降明显快到不可用的程度。我自己在多个文本任务上的对比是Q4_K_M跑出来的结果和原始F16差距很小日常对话、翻译、摘要基本无感Q8_0效果确实好一点但代价是体积大、内存带宽压力大在MTK端侧并不划算。所以默认就选Q4_K_M。唯一例外是0.5B这种小模型内存充足时可以上Q8_0因为小模型自身的容量小量化误差对敏感细节的影响更明显用Q8_0更稳妥。如果你最终用的不是llama.cpp而是MNN或自研框架转换路径就不一样了MNN通常从ONNX转得先把PyTorch权重导出成ONNX再用MNNConvert转成MNN格式和GGUF是两条路。这个后面第3章会展开。3. 推理引擎选型同一台MTK设备不同引擎体验是两个世界3.1 主流引擎横向对比模型文件就绪接下来选推理引擎。我把目前在MTK平台Android/Linux可用的主流方案拉了一张表引擎支持格式MTK平台部署难度NPU支持适合场景llama.cppGGUF低Termux可编译暂无端侧CPU推理首选OllamaGGUF中官方无Android版暂无本地/服务器调试MNNONNX/MNN中格式转换链路长部分支持集成进Android AppMediaPipe LLM InferenceTFLite中部分支持移动端App快速集成llama.cpp是目前最主流的选择纯C无外部依赖Termux里能直接编译Android NDK也能交叉编译成so库嵌进App。MNN是国内团队维护的推理框架在Android上集成更顺手对部分MTK平台有NPU层面的尝试性支持但要把PyTorch权重转成ONNX再转MNN链路更长中间任何一个算子不支持都可能卡住。MediaPipe走TFLite路线Google官方支持不少移动端模型但对Qwen2.5这种新模型的适配要看版本更新进度。Ollama在桌面端很香封装了模型管理、API服务、上下文长度设置但官方没有Android版本想在MTK真机上跑要么用Termux折腾要么装一个Linux环境整体不如llama.cpp直接。3.2 为什么我默认推荐llama.cpp选llama.cpp当主力有三个原因。第一可控。线程数、上下文长度、内存映射、采样参数全部命令行可控这几个参数在MTK端侧推理里缺一不可。Ollama封装得太好出问题时反而难定位llama.cpp是透明的你设置什么就是什么。第二部署路径短。Termux里pkg install几乎就能满足依赖交叉编译也只要一个Android NDK没有复杂的运行时环境。第三社区活跃。Qwen2.5发布后llama.cpp很快就适配了新量化格式、bug修复都能第一时间跟进。你在MTK平台上遇到的绝大多数报错基本都能在GitHub issue里找到对应讨论。如果你只想快速验证模型效果不想折腾编译可以在PC上先用Ollama跑通再到MTK真机上用llama.cpp部署。这两者用的是同一个GGUF生态模型文件甚至可以复用属于互补关系。3.3 关于MNN和NPU说点大实话MNN在移动端App集成场景有优势尤其是要用到自定义前后处理算子的时候。但大模型推理目前90%以上的算子在llama.cpp里已经优化得很好了换到MNN不一定有性能优势反而要额外处理ONNX导出时的算子对齐问题。比如Qwen2.5用的GQAGrouped Query Attention转ONNX时如果用了太新的算子版本老版本MNN可能直接不认。所以我的建议是纯模型推理用llama.cpp要做完整App且需要图形化交互界面时再用MNN或直接把llama.cpp通过JNI封装进App。至于NPU我必须泼一盆冷水。MTK的NeuroPilot APU理论算力不低但大语言模型的算子结构很复杂LayerNorm、RoPE、Attention、GQA这些算子在NPU上的映射并不是开箱即用。目前开源生态里你编译出来的llama.cpp不会去调APUMNN对NPU的支持也主要集中在特定厂商定制芯片或小模型上。别被NPU算力十几TOPS的宣传带偏在大多数MTK平台上Qwen2.5的端侧推理实际就是CPU在扛优化的关键是把CPU线程和内存带宽用到极致。4. 真机实操在MTK平台上把Qwen2.5跑起来4.1 Termux环境搭建我用的是一台天玑8200平台的Android平板系统版本Android 13。先在应用商店或GitHub release装好Termux然后执行termux-setup-storage pkg update pkg upgrade -y pkg install git cmake ninja clang python -yTermux的包仓库自带clang和cmake不需要root。安装过程中如果提示某个包冲突先pkg upgrade保持整个环境一致再回来装依赖。这里提醒一句Android系统对Termux的数据目录有权限限制模型文件我一般放在~/storage/downloads或者直接放在Termux内部目录路径里不要带中文否则后面命令容易踩编码类的低级错误。4.2 编译llama.cpp的两种方式方式一Termux本地编译简单直接git clone https://github.com/ggerganov/llama.cpp cd llama.cpp cmake -B build -DCMAKE_BUILD_TYPERelease -DLLAMA_NATIVEOFF cmake --build build -j4注意这里我特意关了LLAMA_NATIVE。MTK的CPU指令集和x86不同如果开着Native优化编译时会主动启用一些平台相关的指令集反倒在真实运行环境上不稳定。另外-j4是我在手机上限制并发数防止编译时过热。手机本地编译会比较久整套代码编译大约要20到30分钟期间平板发热明显建议在散热条件好的桌面环境操作或者用个风扇对着吹。方式二NDK交叉编译适合要把推理引擎集成到App里的情况。先下载Android NDK然后cmake -B build-android \ -DCMAKE_TOOLCHAIN_FILE$ANDROID_NDK/build/cmake/android.toolchain.cmake \ -DANDROID_ABIarm64-v8a \ -DANDROID_PLATFORMandroid-28 \ -DCMAKE_BUILD_TYPERelease \ -DLLAMA_NATIVEOFF cmake --build build-android -j8生成的llama-cli和llama-server可以直接push到设备上用。实际项目里我更推荐先用方式一验证效果等确认模型、参数都合适再切到NDK交叉编译去做App集成避免在长链路上反复折腾。4.3 第一条真正的生成命令把GGUF文件放到设备上之后运行./build/bin/llama-cli -m qwen2.5-7b-instruct-q4_k_m.gguf \ -c 4096 \ -t 6 \ --temp 0.7 \ --repeat-penalty 1.1 \ -p 用一句话介绍自己几个参数的含义-c 4096上下文长度。Qwen2.5本身支持32K但端侧内存有限7B模型开32K时KV Cache会吃掉大量内存4096是起步值。-t 6推理线程数。MTK平台一般是8核但拉满8核不一定最快因为内存带宽会被争抢发热也会导致降频6线程往往是最优值。--temp 0.7采样温度。值越大输出越随机越小越保守0.7适合日常对话。--repeat-penalty 1.1重复惩罚防止模型陷入循环文本。首次加载时llama.cpp会走mmap内存映射感觉像是加载很快因为模型文件没有全部读进内存而是按需读页。此时用free -h看内存实际占用会小于权重文件大小这是正常的。真正跑起来后内存里才会有完整的权重页和KV Cache。4.4 除了CLI还能直接起一个本地API服务如果想把模型能力接到自己的程序里用llama-server更合适./build/bin/llama-server -m qwen2.5-7b-instruct-q4_k_m.gguf \ -c 4096 -t 6 --host 127.0.0.1 --port 8080然后在同一台设备上访问http://127.0.0.1:8080/v1/chat/completions接口兼容OpenAI格式。手机浏览器直接访问本机地址就能测试。这个模式对后面做App封装特别友好应用层走HTTP就可以不用把自己绑死在C API上。我在调试阶段几乎都用这种形式因为可以在电脑上用curl直接打到平板上调试效率高很多。5. 性能实测与MTK调优线程、内存、功耗、NPU都要照顾5.1 实测数据什么样的生成速度算能用先给参考数据。我在天玑8200平板上用Q4_K_M量化模型实测结果大致如下模型上下文长度线程首token延迟生成速度Qwen2.5-0.5B Q4_K_M409641秒内30-40 token/sQwen2.5-1.5B Q4_K_M40966约1秒12-18 token/sQwen2.5-3B Q4_K_M40966约2秒6-9 token/sQwen2.5-7B Q4_K_M40966约3-5秒3-5 token/s3B以下属于可交互范围7B就只能接受偏慢的体验了。如果你部署到Genio嵌入式板内存带宽更低7B的生成速度可能掉到每秒1到2个token基本只能做后台异步任务。我对能用的划分线是聊天场景至少5 token/s批量文本处理2 token/s也能忍。根据这条线反推MTK中端平台优先选1.5B或3B不要盲目上7B。这也就是为什么我在第1章反复强调选型模型一旦选错后面所有优化都是在补救。5.2 大小核调度与散热降频MTK平台普遍采用三簇架构超大核、大核、小核混在一起操作系统调度器默认并不了解当前哪个核心最省电、哪个核心性能最强。Termux里没有root权限不能随便绑核但可以借助nice调整进程优先级让调度器尽量优先给它分大核nice -n -10 ./build/bin/llama-cli -m ...没有root时负Nice不一定完全生效但值得一试。另外长时间推理会让芯片发热MTK温控策略一介入就会降频生成速度明显下滑。我实测天玑8200平板连续推理10分钟后温度上来速度可能掉20%左右。应对方式有两个要么给设备加散热背夹要么故意把线程数从6降到5。后者单看峰值速度略低但能维持更长时间不降频总体吞吐反而更高。5.3 为什么NPU暂时指望不上前面提到MTK有NeuroPilot APU要把Qwen2.5跑到NPU上得满足三个条件模型量化成INT8且每个算子都被NPU工具链支持、有对应的模型转换插件、用户有权限调用NPU驱动。在Android消费者设备上Google NN API对LLM的支持还在演进MTK的NeuroPilot SDK主要面向OEM厂商普通开发者很难拿到完整的、适配Qwen2.5的模型转换工具链。所以当前最现实的路线就是CPU 量化 合理线程数等MNN或MediaPipe把NPU算子覆盖做好之后再考虑也不迟。有人可能会问MTK的GPU能不能帮忙答案是Mali GPU跑通用计算不是强项而且开源推理引擎对它的支持很有限。你可以自己写OpenCL算子去加速某个矩阵乘法但要把整个Transformer的算子都搬过去工作量巨大且收益不确定。与其折腾GPU不如把DDR带宽吃透。5.4 内存带宽才是真瓶颈这里有一个反直觉的点Qwen2.5-7B生成时CPU算力并没有吃满但速度就是上不去。原因在于自回归生成是访存密集型任务每一步都要把模型权重从DDR搬到寄存器里计算内存带宽决定了下限。我用一个很粗的公式估算7B模型Q4_K_M权重约4.4GB生成一个token需要把所有权重读一遍如果设备有效内存带宽是20GB/s理论上最快的生成速度就是20除以4.4约4.5 token/s。这个估算和实测的3-5 token/s基本吻合。这也是为什么把线程从4提到8速度可能不升反降——多核竞争内存控制器DDR带宽被瓜分还会引入额外的cache一致性开销。我一般建议在MTK平台上先用6线程起步再用-t 5和-t 7各测一轮选最高分。不同设备的内存控制器策略不一样别迷信某一个线程数实测数据永远是最可靠的。6. 高频翻车现场上下文丢失、加载慢、输出乱码6.1 上下文记不住99%是上下文长度设置太小有个热搜词我太有共鸣了我本地ollama部署的qwen2.5:7b记不住上下文怎么办。绝大多数情况下模型根本没坏是上下文长度默认值太小。Ollama默认上下文只有2048意味着超过2048个token的历史信息会被截断模型看起来就像失忆。解决办法是在Modelfile里显式声明FROM qwen2.5:7b PARAMETER num_ctx 8192然后重新创建模型ollama create qwen2.5-7b-8k -f Modelfile ollama run qwen2.5-7b-8k如果是调API记得在请求体里传num_ctx{ model: qwen2.5:7b, messages: [], options: { num_ctx: 8192 } }注意把上下文调大不是没有代价的。KV Cache的大小和上下文长度成正比Qwen2.5-7B在8192上下文下KV Cache会额外占用500MB以上内存如果开满32K额外内存接近2GB。设备内存紧张的话会出现加载模型后直接OOM或者生成到一半进程被杀。所以在MTK端侧上下文长度不是越大越好要在记得住和跑得动之间找平衡。6.2 模型加载慢、首次生成卡顿如果你发现llama-cli启动后要等很久才开始输出先确认两点模型文件是不是放在慢速存储上比如SD卡以及有没有开启mmap。llama.cpp默认用mmap按需读页首次生成会有一阵明显的卡顿因为要边推理边补页。如果设备内存足够可以换成--mlock把模型锁进内存换取生成过程更稳定./build/bin/llama-cli -m qwen2.5-3b-instruct-q4_k_m.gguf \ -c 4096 -t 6 --mlock ...--mlock的要求是内存足够否则会直接失败。此外把模型放在内部存储比放SD卡快得多。如果模型文件是从网盘或微信传过来的建议先校验一下文件完整性llama-cli加载时如果有CRC错误会直接报错退出你还要排查半天是文件损坏还是模型转换有问题。6.3 输出乱码、重复、停不下来转换工具正常的话Qwen2.5很少出现彻底乱码但容易遇到生成重复内容或停不下来的情况。这通常是采样参数没调好而不是模型或转换的问题。推荐一组比较稳的参数--temp 0.7 --top-k 40 --top-p 0.9 --repeat-penalty 1.1repeat-penalty是重点Qwen2.5在端侧低精度下偶尔会陷入好的我会继续这类循环稍微加大到1.15能明显改善。如果是在llama-server里通过OpenAI接口调用这些参数可以放到sampler字段里。另外检查一下模型文件里的chat模板是否正常。Qwen2.5的聊天格式是|im_start|system...|im_end|如果模板不对模型会把指令当普通文本继续续写表现就是输出里带出一堆莫名其妙的前缀。这种情况和你推理引擎无关回到第2章检查tokenizer_config.json更高效。现在回想整个部署过程我最想对准备入坑的人说的一句话是先拿0.5B模型把转换、量化、编译、推理、调参全链路跑通再逐步换更大的模型。全链路跑通后的每一步调优都有明确的参照系问题定位会快得多而不是一上来就啃7B最后被内存不足和速度太慢两个问题同时卡住连问题出在哪都分不清。部署这件事慢就是快。
返回列表