开源项目介绍:Native-LLM-for-Android

项目地址:Native-LLM-for-Android
创作活动时间:2025年

支持在 Android 设备上运行大型语言模型 (LLM) ,具体支持的模型包括:
DeepSeek-R1-Distill-Qwen: 1.5B
Qwen2.5-Instruct: 0.5B, 1.5B
Qwen2/2.5VL: 2B, 3B
MiniCPM-DPO/SFT: 1B, 2.7B
Gemma2-it: 2B
Phi3.5-mini-instruct: 3.8B
Llama-3.2-Instruct: 1B

Native-LLM-for-Android项目主要提供2个参考点,1、将LLM模型导出为onnx模型,2、在安卓端实现LLL模型的运行,本博文主要关注将llm导出为onnx推理(对现有的llm模型进行局部修改并导出),并以miniCPM模型为例进行测试。同时,Native-LLM-for-Android项目还有一些列模型量化代码可以学习。

1、模型运行性能

运行最快的模型是Llama3.2-1B-Instruct q8f32,达到25 token每秒,相应的硬件与os为 Nubia Z50(Android 13、8_Gen2-CPU);其次是Distill-Qwen-1.5B q8f32,达到22 token每秒,相应的硬件与os为Xiaomi-14T-Pro (HyperOS 2、MediaTek_9300±CPU);

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

2、核心功能

这里主要关注将llm导出为onnx脱离torch环境运行,因此对Android运行代码不予理会

2.1、分词器

分词器也就是Tokenizer ,一共两个功能:
1、将输入模型的文本,分为多个短词,并转换为token,
2、将模型输出的token转换为文本
需要注意的是,不同的llm模型分词规则是不一样的,同时对于的token编码规则也不同

一般运行onnx模型,都是基于Transformer库中的Tokenizer,这无法脱离torch环境。应该自行实现。

Native-LLM-for-Android项目中Tokenizer 依赖的是mnn-llm仓库中的实现.
具体代码链接为:
https://github.com/wangzhaode/mnn-llm/blob/master/src/tokenizer.cpp,是纯c++代码,与torch环境毫无关联

同时,在每一种模型的Android-onnx代码路径下,都有对于的Tokenizer的c++实现代码
在这里插入图片描述

2.2、导出onnx模型

在Native-LLM-for-Android项目下Export_ONNX目录中,每一个模型都有单独的导出代码
在这里插入图片描述
如Gemma模型的导出,分别执行A-B-C步骤,导出3个模型,在最后的导出代码中含onnx推理代码
在这里插入图片描述

其中关于QwenVL模型的导出较为复杂,需要对transformers库中modeling_qwen2_vl.py文件进行改写覆盖,将单个模型拆分为5个模型运行。其中A模型是VIT的主体部分,E模型是llm的主体部分,BCD模型是一些切片索引操作,被单独导出为模型。关于E模型导出有报错,可以参考https://github.com/DakeQQ/Native-LLM-for-Android/issues/10
在这里插入图片描述

如果导出模型报错

RuntimeError: The serialized model is larger than the 2GiB limit imposed by the protobuf library. Therefore the output file must be a file path, so that the ONNX external data can be written to the same directory. Please specify the output file name.

则尝试将torch版本降低到2.4.1
pip install torch2.4.1 torchvision0.19.1 torchaudio==2.4.1 --index-url https://download.pytorch.org/whl/cu121

2.3、onnx模型量化

关于onnx模型量化,可以参考:https://blog.csdn.net/m0_63642362/article/details/124741589,根据介绍,onnx量化可以分为动态量化与静态量化,动态量化在推理时根据输入数据动态计算缩放因子与零点;静态量化,使用校准数据集离线计算缩放因子(Scale)和零点(Zero Point)。通常,建议对 RNN 和基于 Transformer 的模型使用动态量化,对 CNN 模型使用静态量化

在Native-LLM-for-Android-main\Do_Quantize\Dynamic_Quant 目录下有多个模型量化代码,具体如下图所示
在这里插入图片描述
q8_f16的量化代码如下所示,可以看到对于大尺寸的模型的量化有一个关键参数项 is_large_model

import os
import gc
import glob
import sys
import onnx
import torch
import subprocess
import onnx.version_converter
from onnxsim import simplify
from onnxslim import slim
from onnxruntime.quantization import QuantType, quantize_dynamic
from onnxruntime.transformers.optimizer import optimize_model
from transformers import AutoModelForCausalLM# Path Setting
original_folder_path = r"C:\Users\Downloads\Model_ONNX"                          # The original folder.
quanted_folder_path = r"C:\Users\Downloads\Model_ONNX_Optimized"                 # The optimized folder.
model_path = os.path.join(original_folder_path, "Model.onnx")                    # The original fp32 model path.
quanted_model_path = os.path.join(quanted_folder_path, "Model_Optimized.onnx")   # The optimized model stored path.
download_path = r'C:\Users\Downloads\Qwen2-1.5B-Instruct'                        # Set the folder path where the LLM whole project downloaded, otherwise set "NONE".
use_gpu = True                                                                   # If true, the transformers.optimizer will remain the FP16 processes.
provider = 'CPUExecutionProvider'                                                # ['CPUExecutionProvider', 'CUDAExecutionProvider']
use_low_memory_mode_in_Android = False                                           # If you need to use low memory mode on Android, please set it to True.# Preprocess, it also cost alot of memory during preprocess, you can close this command and keep quanting. Call subprocess may get permission failed on Windows system.
# (optional process)
# subprocess.run([f'python -m onnxruntime.quantization.preprocess --auto_merge --all_tensors_to_one_file --input {model_path} --output {quanted_folder_path}'], shell=True)# Start Quantize
quantize_dynamic(model_input=model_path,model_output=quanted_model_path,per_channel=True,                                        # True for model accuracy but cost a lot of time during quanting process.reduce_range=False,                                      # True for some x86_64 platform.weight_type=QuantType.QInt8,                            # It is recommended using uint8 + Symmetric Falseextra_options={'ActivationSymmetric': False,             # True for inference speed. False may keep more accuracy.'WeightSymmetric': False,                 # True for inference speed. False may keep more accuracy.'EnableSubgraph': True,                   # True for more quant.'ForceQuantizeNoInputCheck': False,       # True for more quant.'MatMulConstBOnly': True                  # False for more quant. Sometime, the inference speed may get worse.},nodes_to_exclude=None,                                   # Specify the node names to exclude quant process. Example: nodes_to_exclude={'/Gather'}use_external_data_format=True                            # Save the model into two parts.
)model_size_bytes = sys.getsizeof(onnx.load(model_path).SerializeToString())
model_size_gb = model_size_bytes * 9.31322575e-10            # 1 / (1024 * 1024 * 1024)
if model_size_gb > 2.0:is_large_model = True
else:is_large_model = True if use_low_memory_mode_in_Android else False# ONNX Model Optimizer
slim(model=quanted_model_path,output_model=quanted_model_path,no_shape_infer=False,   # True for more optimize but may get errors.skip_fusion_patterns=False,no_constant_folding=False,save_as_external_data=is_large_model,verbose=False
)if download_path == "NONE":num_heads = 0    # defaulthidden_size = 0  # default
else:if ('vl' in download_path.lower()) & ('qwen' in download_path.lower()):if "2.5" in download_path or "3b" in download_path.lower():from transformers import Qwen2_5_VLForConditionalGenerationmodel = Qwen2_5_VLForConditionalGeneration.from_pretrained(download_path, torch_dtype=torch.float16, device_map='cpu', trust_remote_code=True, low_cpu_mem_usage=True).eval()else:from transformers import Qwen2VLForConditionalGenerationmodel = Qwen2VLForConditionalGeneration.from_pretrained(download_path, torch_dtype=torch.float16, device_map='cpu', trust_remote_code=True, low_cpu_mem_usage=True).eval()else:model = AutoModelForCausalLM.from_pretrained(download_path, torch_dtype=torch.float16, device_map='cpu', trust_remote_code=True, low_cpu_mem_usage=True).eval()num_heads = model.config.num_attention_headshidden_size = model.config.hidden_sizedel modelgc.collect()# transformers.optimizer
model = optimize_model(quanted_model_path,use_gpu=use_gpu,opt_level=2,num_heads=num_heads,hidden_size=hidden_size,provider=provider,verbose=False,model_type='bert')
model.convert_float_to_float16(keep_io_types=True,force_fp16_initializers=True,use_symbolic_shape_infer=True,  # True for more optimize but may get errors.op_block_list=['DynamicQuantizeLinear', 'DequantizeLinear', 'DynamicQuantizeMatMul', 'Range', 'MatMulIntegerToFloat']
)
model.save_model_to_file(quanted_model_path, use_external_data_format=is_large_model)
del model
gc.collect()# onnxslim 2nd
slim(model=quanted_model_path,output_model=quanted_model_path,no_shape_infer=False,   # True for more optimize but may get errors.skip_fusion_patterns=False,no_constant_folding=False,save_as_external_data=is_large_model,verbose=False
)# Upgrade the Opset version. (optional process)
model = onnx.load(quanted_model_path)
model = onnx.version_converter.convert_version(model, 21)
onnx.save(model, quanted_model_path, save_as_external_data=is_large_model)if is_large_model:pattern = os.path.join(quanted_folder_path, '*.data')files_to_delete = glob.glob(pattern)for file_path in files_to_delete:try:os.remove(file_path)except Exception as e:print(f"Error deleting {file_path}: {e}")# It is not recommended to convert an FP16 ONNX model to the ORT format because this process adds a Cast operation to convert the FP16 process back to FP32.

3、导出minicpm模型onnx推理

3.1 下载模型

pip install modelscope

基于modelscope 库可以下载MiniCPM-2B-dpo-fp16模型

from modelscope import snapshot_download
model_dir = snapshot_download('OpenBMB/MiniCPM-2B-dpo-fp16',cache_dir=".cache_dir")

3.2 导出onnx模型

这里以MiniCPM-2B-split导出方式为例

先在命令行进入 F:\Native-LLM-for-Android-main\Export_ONNX\MiniCPM\MiniCPM-2B-split 目录

然后创建,model_a,model_b两个目录,用于存储2个onnx模型,并将代码修改为以下形式
在这里插入图片描述

最后在命令行中执行 python .\MiniCPM_Export.py 即可实现模型导出为onnx,并进行推理测试
在这里插入图片描述
这里可以发现代码的推理速度居然为0.375token/s,简直巨慢。

按照单个模型导出,并进行推理测试,发现效果如下所示,可以发现性能有6倍的提升,这表明数据通信也占据了大量的耗时。
在这里插入图片描述

3.3 单独运行onnx

基于以下代码可以运行onnx模型,但无法脱离transformers库,除非手写tokenizer实现分词,并实现token与文本的对应关系。


import numpy as np
import onnxruntime
from transformers import AutoModelForCausalLM, AutoTokenizer
import timepath = 'F:\DMT\.cache_dir\OpenBMB\MiniCPM-2B-dpo-fp16'  # Set the folder path where the MiniCPM whole project downloaded.
onnx_model_A = r'F:\Native-LLM-for-Android-main\Export_ONNX\MiniCPM\MiniCPM-2B-single\model_q8_f16\MiniCPM_part_A_Optimized.onnx'  # Assign a path where the exported MiniCPM_part_A stored.# Run the exported model by ONNX Runtime
query = "山东省最高的山是哪座山, 它比黄山高还是矮?差距多少?"
max_seq_len = 1024  # Please modify the same variable, which declared in the modified modeling_minicpm.py on line 1008, at the same time.
num_heads = 36
head_dim = 64
num_key_value_heads = 36
num_layers = 40
hidden_size = 2304max_single_chat_length = 341  # It a adjustable value, but must less than max_seq_len.
tokenizer = AutoTokenizer.from_pretrained(path, trust_remote_code=True)# ONNX Runtime settings
session_opts = onnxruntime.SessionOptions()
session_opts.log_severity_level = 3  # error level, it a adjustable value.
session_opts.inter_op_num_threads = 0  # Run different nodes with num_threads. Set 0 for auto.
session_opts.intra_op_num_threads = 0  # Under the node, execute the operators with num_threads. Set 0 for auto.
session_opts.enable_cpu_mem_arena = True  # True for execute speed; False for less memory usage.
session_opts.execution_mode = onnxruntime.ExecutionMode.ORT_SEQUENTIAL
session_opts.graph_optimization_level = onnxruntime.GraphOptimizationLevel.ORT_ENABLE_ALL
session_opts.add_session_config_entry("session.intra_op.allow_spinning", "1")
session_opts.add_session_config_entry("session.inter_op.allow_spinning", "1")ort_session_A = onnxruntime.InferenceSession(onnx_model_A, sess_options=session_opts, providers=['CPUExecutionProvider'])
in_name_A = ort_session_A.get_inputs()
out_name_A = ort_session_A.get_outputs()
in_name_A0 = in_name_A[0].name
in_name_A1 = in_name_A[1].name
in_name_A2 = in_name_A[2].name
in_name_A3 = in_name_A[3].name
in_name_A4 = in_name_A[4].name
in_name_A5 = in_name_A[5].name
out_name_A0 = out_name_A[0].name
out_name_A1 = out_name_A[1].name
out_name_A2 = out_name_A[2].name# Pre-process inputs
prompt = tokenizer.apply_chat_template([{"role": 'user', "content": query}], tokenize=False, add_generation_prompt=False)
token = tokenizer(prompt, return_tensors='pt')['input_ids']
ids_len = token.shape[1] + np.zeros(1, dtype=np.int64)
input_ids = np.zeros(max_seq_len, dtype=np.int32)
input_ids[:ids_len[0]] = token[0, :]
attention_mask = np.array([-65504.0], dtype=np.float32)
history_len = np.zeros(1, dtype=np.int64)
past_key_states_A = np.zeros((num_layers, num_key_value_heads, max_seq_len, head_dim), dtype=np.float16)
past_values_states_A = past_key_states_A
num_decode = 0
print('\nTest Question: ' + query + "\n\nMiniCPM Answering:\n")# Start to run LLM
start_time = time.time()
while history_len < max_single_chat_length:token_id, past_key_states_A, past_values_states_A = ort_session_A.run([out_name_A0, out_name_A1, out_name_A2],{in_name_A0: input_ids,in_name_A1: attention_mask,in_name_A2: past_key_states_A,in_name_A3: past_values_states_A,in_name_A4: history_len,in_name_A5: ids_len})if token_id == 2:  # the stop_id in MiniCPM is "2"breakelse:history_len[0] += ids_len[0]ids_len[0] = 1num_decode += 1attention_mask[0] = 0.0input_ids[0] = token_idprint(tokenizer.decode(token_id), end="", flush=True)
end_time = time.time()
print(f"\n\nDecode: {(num_decode / (end_time - start_time)):.3f} token/s")

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/29987.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

STM32标准库代码详解之GPIO

GPIO的初始化代码如下&#xff1a; /*开启时钟*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟&#xff0c;使用外设必须开启/*GPIO初始化*/ GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量 GPIO_InitStructure.GPIO_Mode GPIO_Mo…

实现Django和Transformers 构建智能客服大模型(模拟订单系统)

一、环境安装准备 #git拉取 bert-base-chinese 文件#创建 虚拟运行环境python -m venv myicrplatenv#刷新source myicrplatenv/bin/activate#python Django 集成nacospip install nacos-sdk-python#安装 Djangopip3 install Django5.1#安装 pymysql settings.py 里面需要 # 强制…

Apache Kafka单节点极速部署指南:10分钟搭建开发单节点环境

Apache Kafka单节点极速部署指南&#xff1a;10分钟搭建开发单节点环境 Kafka简介&#xff1a; Apache Kafka是由LinkedIn开发并捐赠给Apache基金会的分布式流处理平台&#xff0c;现已成为实时数据管道和流应用领域的行业标准。它基于高吞吐、低延迟的设计理念&#xff0c;能够…

浅论数据库聚合:合理使用LambdaQueryWrapper和XML

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、数据库聚合替代内存计算&#xff08;关键优化&#xff09;二、批量处理优化四、区域特殊处理解耦五、防御性编程增强 前言 技术认知点&#xff1a;使用 XM…

【C++设计模式】第四篇:建造者模式(Builder)

注意&#xff1a;复现代码时&#xff0c;确保 VS2022 使用 C17/20 标准以支持现代特性。 分步骤构造复杂对象&#xff0c;实现灵活装配 1. 模式定义与用途 核心目标&#xff1a;将复杂对象的构建过程分离&#xff0c;使得同样的构建步骤可以创建不同的表示形式。 常见场景&am…

uploadlabs经验总结

目录 一、基础上传漏洞&#xff08;太过简单目前环境不可能存在&#xff09; 1、抓包然后改后缀进行绕过 2、抓包然后改上传文件类型进行绕过 3、改后缀大小写绕过&#xff0c;以及收尾加空格&#xff0c;加::$DATA,加点等等 4、黑名单不完整绕过&#xff0c;复习后缀绕过&…

若依ry-vue分离板(完整版)前后端部署

目录 1.目标 2.准备工作 3.源码下载 4.整理前后端目录 5.先部署后端 &#xff08;1&#xff09;导入数据库 &#xff08;2&#xff09;改代码数据库配置 &#xff08;3&#xff09;运行redis &#xff08;4&#xff09;运行执行文件 &#xff08;5&#xff09;后端启…

重构谷粒商城09:人人开源框架的快速入门

谷粒商城09——人人开源框架的快速入门 前言&#xff1a;这个系列将使用最前沿的cursor作为辅助编程工具&#xff0c;来快速开发一些基础的编程项目。目的是为了在真实项目中&#xff0c;帮助初级程序员快速进阶&#xff0c;以最快的速度&#xff0c;效率&#xff0c;快速进阶…

Linux | Vim 鼠标不能右键粘贴、跨系统复制粘贴

注&#xff1a;本文为 “ Vim 中鼠标右键粘贴、跨系统复制粘贴问题解决方案” 相关文章合辑。 未整理去重。 Linux 入门&#xff1a;vim 鼠标不能右键粘贴、跨系统复制粘贴 foryouslgme 发布时间 2016 - 09 - 28 10:24:16 Vim基础 命令模式(command-mode)插入模式(insert-m…

【JavaWeb】Web基础概念

文章目录 1、服务器与客户端2、服务器端应用程序3、请求和响应4、项目的逻辑构成5、架构5.1 概念5.2 发展演变历程单一架构分布式架构 5.3 单一架构技术体系 6、本阶段技术体系 1、服务器与客户端 ①线下的服务器与客户端 ②线上的服务器与客户端 2、服务器端应用程序 我…

基于云的内容中台核心优势是什么?

弹性云架构赋能资源整合 现代企业通过弹性云架构实现多源数据资源的深度整合&#xff0c;其动态扩展能力可自动适配业务流量波动。基于分布式存储与容器化部署&#xff0c;系统能够无缝对接CRM、ERP等企业软件集成&#xff0c;实现跨平台数据实时同步。值得注意的是&#xff0…

数据库基础练习1

目录 1.创建数据库和表 2.插入数据 创建一个数据库&#xff0c;在数据库种创建一张叫heros的表&#xff0c;在表中插入几个四大名著的角色&#xff1a; 1.创建数据库和表 #创建表 CREATE DATABASE db_test;#查看创建的数据库 show databases; #使用db_test数据库 USE db_te…

亲测解决笔记本触摸板使用不了Touchpad not working

这个问题可以通过FnFxx来解决&#xff0c;笔记本键盘上Fxx会有一个触摸板图标。如果不行应该玉藻设置中关了&#xff0c;打开即可。 解决办法 在蓝牙&#xff0c;触摸板里打开即可。 Turn it on in settings。

Vue23Web 基礎性拉滿的面試題(2025版)還沒更新完...

Vue2&3 基礎性1. 關於Vue2和Vue3生命週期的差別2. Vue2&3組件之間傳參不同點Vue2 傳遞與接收Vue3 傳遞與接收 (使用script setup語法糖)Vue3 傳遞與接收 (不使用script setup語法糖) 3. Vue2&3 keep-alive 組件Vue2 keep-aliveVue3 keep-alive 進階性爲什麽POST請求…

动态ip和静态ip适用于哪个场景?有何区别

在数字化浪潮席卷全球的今天&#xff0c;IP地址作为网络世界的“门牌号”&#xff0c;其重要性不言而喻。然而&#xff0c;面对动态IP与静态IP这两种截然不同的IP分配方式&#xff0c;许多用户往往感到困惑&#xff1a;它们究竟有何区别&#xff1f;又分别适用于哪些场景呢&…

深度学习模型Transformer核心组件—自注意力机制

第一章&#xff1a;人工智能之不同数据类型及其特点梳理 第二章&#xff1a;自然语言处理(NLP)&#xff1a;文本向量化从文字到数字的原理 第三章&#xff1a;循环神经网络RNN&#xff1a;理解 RNN的工作机制与应用场景(附代码) 第四章&#xff1a;循环神经网络RNN、LSTM以及GR…

FreeRTOS 源码结构解析与 STM32 HAL 库移植实践(任务创建、删除篇)

1. FreeRTOS源码结构介绍 1.1 下载源码 ​ 点击官网地址&#xff0c;选择 FreeRTOS 202212.01非 LTS 版本&#xff08;非长期支持版&#xff09;&#xff0c;因为这个版本有着最全的历程和更多型号处理器支持。 1.2 文件夹结构介绍 ​ 下载后主文件 FreeRTOSv202212.01 下包…

【uniapp】图片添加canvas水印

目录 需求&背景实现地理位置添加水印 ios补充 需求&背景 需求&#xff1a;拍照后给图片添加水印, 水印包含经纬度、用户信息、公司logo等信息。 效果图&#xff1a; 方案&#xff1a;使用canvas添加水印。 具体实现&#xff1a;上传图片组件是项目里现有的&#xff…

基于Windows11的DockerDesktop安装和布署方法简介

基于Windows11的DockerDesktop安装和布署方法简介 一、下载安装Docker docker 下载地址 https://www.docker.com/ Download Docker Desktop 选择Download for Winodws AMD64下载Docker Desktop Installer.exe 双点击 Docker Desktop Installer.exe 进行安装 测试Docker安装是…

AI自习室渐兴:人工智能赋能教育新场景的深度剖析

在数字化浪潮席卷全球的今天&#xff0c;教育领域也迎来了前所未有的变革。近年来&#xff0c;AI自习室作为人工智能技术与传统教育融合的产物&#xff0c;在河北等多地悄然兴起&#xff0c;成为学生们的新宠。这一新兴的学习场所&#xff0c;不仅引发了社会的广泛关注&#xf…