开源项目介绍: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.mzph.cn/bicheng/72847.shtml

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

相关文章

深入理解 Java 虚拟机内存区域

Java 虚拟机&#xff08;JVM&#xff09;是 Java 程序运行的核心环境&#xff0c;它通过内存管理为程序提供高效的执行支持。JVM 在运行时将内存划分为多个区域&#xff0c;每个区域都有特定的作用和生命周期。本文将详细介绍 JVM 的运行时数据区域及其功能&#xff0c;并探讨与…

PDF转JPG(并去除多余的白边)

首先&#xff0c;手动下载一个软件&#xff08;poppler for Windows&#xff09;&#xff0c;下载地址&#xff1a;https://github.com/oschwartz10612/poppler-windows/releases/tag/v24.08.0-0 否则会出现以下错误&#xff1a; PDFInfoNotInstalledError: Unable to get pag…

深入剖析MyBatis缓存机制:原理、源码与实战指南

引言 MyBatis作为一款优秀的ORM框架,其缓存机制能显著提升数据库查询性能。但许多开发者仅停留在“知道有缓存”的层面,对其实现原理和细节知之甚少。本文将结合可运行的代码示例和源码分析,手把手带您彻底掌握MyBatis缓存机制。 一、MyBatis缓存分类 MyBatis提供两级缓存…

Vue 使用 vue-router 时,多级嵌套路由缓存问题处理

Vue 使用 vue-router 时&#xff0c;多级嵌套路由缓存问题处理 对于三级菜单&#xff08;或多级嵌套路由&#xff09;&#xff0c;vue 都是 通过 keep-alive 组件来实现路由组件的缓存。 有时候三级或者多级路由时&#xff0c;会出现失效情况。以下是三级菜单缓存的例子。 最…

QSplitter保存和读取

官方文档提供的方案 保存 connect(ui->splitter, &QSplitter::splitterMoved, [](){settings.setValue("splitterSizes", ui->splitter->saveState()); });读取 ui->splitter->restoreState(settings.value("splitterSizes").toByteA…

VanillaVueSvelteReactSolidAngularPreact前端框架/库的简要介绍及其优势

VanillaVueSvelteReactSolidAngularPreact前端框架/库的简要介绍及其优势。以下是这些前端框架/库的简要介绍及其优势&#xff1a; 1. Vanilla 定义&#xff1a;Vanilla 并不是一个框架&#xff0c;而是指 原生 JavaScript&#xff08;即不使用任何框架或库&#xff09;。优势…

Java多线程与高并发专题——关于CopyOnWrite 容器特点

引入 在 CopyOnWriteArrayList 出现之前&#xff0c;我们已经有了 ArrayList 和 LinkedList 作为 List 的数组和链表的实现&#xff0c;而且也有了线程安全的 Vector 和Collections.synchronizedList() 可以使用。 首先我们来看看Vector是如何实现线程安全的 &#xff0c;还是…

Jmeter接口测试详解

今天笔者呢&#xff0c;想给大家聊聊Jmeter接口测试流程详解&#xff0c;废话不多说直接进入正题。 一、jmeter简介 Jmeter是由Apache公司开发的java开源项目&#xff0c;所以想要使用它必须基于java环境才可以&#xff1b; Jmeter采用多线程&#xff0c;允许通过多个线程并…

DeepSeek开启AI办公新模式,WPS/Office集成DeepSeek-R1本地大模型!

从央视到地方媒体&#xff0c;已有多家媒体机构推出AI主播&#xff0c;最近杭州文化广播电视集团的《杭州新闻联播》节目&#xff0c;使用AI主持人进行新闻播报&#xff0c;且做到了0失误率&#xff0c;可见AI正在逐渐取代部分行业和一些重复性的工作&#xff0c;这一现象引发很…

通过Golang的container/list实现LRU缓存算法

文章目录 力扣&#xff1a;146. LRU 缓存主要结构 List 和 Element常用方法1. 初始化链表2. 插入元素3. 删除元素4. 遍历链表5. 获取链表长度使用场景注意事项 源代码阅读 在 Go 语言中&#xff0c;container/list 包提供了一个双向链表的实现。链表是一种常见的数据结构&#…

【大学生体质】智能 AI 旅游推荐平台(Vue+SpringBoot3)-完整部署教程

智能 AI 旅游推荐平台开源文档 项目前端地址 ☀️项目介绍 智能 AI 旅游推荐平台&#xff08;Intelligent AI Travel Recommendation Platform&#xff09;是一个利用 AI 模型和数据分析为用户提供个性化旅游路线推荐、景点评分、旅游攻略分享等功能的综合性系统。该系统融合…

【渗透测试】基于时间的盲注(Time-Based Blind SQL Injection)

发生ERROR日志告警 查看系统日志如下&#xff1a; java.lang.IllegalArgumentException: Illegal character in query at index 203: https://api.weixin.qq.com/sns/jscode2session?access_token90_Vap5zo5UTJS4jbuvneMkyS1LHwHAgrofaX8bnIfW8EHXA71IRZwsqzJam9bo1m3zRcSrb…

redis数据类型以及底层数据结构

redis数据类型以及底层数据结构 String&#xff1a;字符串类型&#xff0c;底层就是动态字符串&#xff0c;使用sds数据结构 Map:有两种数据结构&#xff1a;1.压缩列表&#xff1a;当hash结构中存储的元素个数小于了512个。并且元 …

DeepSeek R1-32B医疗大模型的完整微调实战分析(全码版)

DeepSeek R1-32B微调实战指南 ├── 1. 环境准备 │ ├── 1.1 硬件配置 │ │ ├─ 全参数微调:4*A100 80GB │ │ └─ LoRA微调:单卡24GB │ ├── 1.2 软件依赖 │ │ ├─ PyTorch 2.1.2+CUDA │ │ └─ Unsloth/ColossalAI │ └── 1.3 模…

window下的docker内使用gpu

Windows 上使用 Docker GPU需要进行一系列的配置和步骤。这是因为 Docker 在 Windows 上的运行环境与 Linux 有所不同,需要借助 WSL 2(Windows Subsystem for Linux 2)和 NVIDIA Container Toolkit 来实现 GPU 的支持。以下是详细的流程: 一、环境准备 1.系统要求 Window…

Ubuntu 下 nginx-1.24.0 源码分析 - cycle->modules[i]->type

Nginx 中主要有以下几种模块类型 类型 含义 NGX_CORE_MODULE 核心模块&#xff08;如进程管理、错误日志、配置解析&#xff09;。 NGX_EVENT_MODULE 事件模块&#xff08;如 epoll、kqueue 等 IO 多路复用机制的实现&#xff09;。 NGX_HTTP_MODULE HTTP 模块&#xf…

八、排序算法

一些简单的排序算法 8.1 冒泡排序 void Bubble_sort(int a[] , int len){int i,j,flag,tmp;for(i=0 ; i < len-1 ; i++){flag = 1;for(j=0 ; j < len-1-i ; j++){if(a[j] > a[j+1]){tmp = a[j];a[j] = a[j+1];a[j+1] = tmp;flag = 0;}}if(flag == 1){break;}}…

Sqlserver安全篇之_手工创建TLS用到的pfx证书文件

Sqlserver官方提供的Windows Powershell脚本 https://learn.microsoft.com/zh-cn/sql/database-engine/configure-windows/configure-sql-server-encryption?viewsql-server-ver16 # Define parameters $certificateParams {Type "SSLServerAuthentication"Subje…

npm install -g @vue/cli 方式已经无法创建VUE3项目

采用该方式&#xff0c;启动VUE3项目&#xff0c;运行命令&#xff0c;出现报错&#xff1a; npm install -g vue/cli PS D:\> npm install -g vue/cli npm warn deprecated inflight1.0.6: This module is not supported, and leaks memory. Do not use it. Check out lr…

3.8[a]cv

函数核心目标 实现屏幕空间内三角形的光栅化&#xff0c;将三角形覆盖的像素点颜色填充到帧缓冲区&#xff0c;同时处理深度测试&#xff08;Z-Buffer&#xff09;。这是渲染管线中几何阶段到像素阶段的关键步骤 包围盒计算&#xff08;Bounding Box&#xff09;​** ​功能&…