DeepSeek最近几个月很火热,很多产品以及企业都在接入DeepSeek,比如微信搜索接入,可以搜索公众号信息并总结,这个对于查一些资料还挺好用,因为现在很多都会在公众号上写一些东西,进行宣传,毕竟手机才是用户用的最多的。
既然谈到了手机,那么DeepSeek能否部署于手机之上呢,在不联网的情况下就能使用?基于这个问题,查询了相关资料,发现android端部署DeepSeek两种方法:
第一种是使用Termux。Termux 是一款终端模拟器应用程序,专为 Android 系统设计,提供完整的 Linux 环境,允许用户在 Android 设备上运行 Linux 命令和工具,无需 root 权限。然后使用ollama相关命令下载部署模型即可。目前网上基本上都是这种方式,这种方式其实跟在服务端部署差不多,所以为啥不直接用服务端部署的模型呢。
第二种就是直接将模型文件下载到手机中,应用内直接加载模型文件并运行,这种方式好处在于,可结合自身业务做一些基于大模型的离线本地私有化应用,耗费基本为0。
本文主要采用第二种方法,基于阿里MNN库进行部署。
1 准备环境
1.1 MNN转换工具
bash
cd MNN
mkdir build && cd build
cmake .. -DMNN_BUILD_CONVERTER=ON
make -j8
这个主要是得到MNNConvert转换工具,可用来转换onnx格式为mnn格式
1.2 大模型转换工具
安装大模型转换工具需要的一些依赖,可用conda来进行环境管理
bash
cd path/MNN/transformers/llm/export
pip install -r requirements.txt
如果只是想试试大模型android端部署,把这些依赖下载下来,然后使用如下方式一转换模型就行了
1.3 模型下载
bash
git lfs install
git clone https://www.modelscope.cn/deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B.
1.4 模型转换
方式一:直接使用llmexport.py直接转换得到mnn格式的模型文件
bash
python llmexport.py --path path/model/DeepSeek-R1-Distill-Qwen-1.5B --export mnn
方式二:可先转换为onnx,然后再使用MNNConvert转换为mnn格式模型
bash
python llmexport.py --path path/model/DeepSeek-R1-Distill-Qwen-1.5B --export onnx ./MNNConvert -f ONNX --modelFile llm.onnx --MNNModel llm.mnn --bizCode biz
两种方式都尝试过,转换出来的模型都没啥问题,方式二主要可以进行其他bits数的量化
成功:
产物解释:
1.5 编译android依赖库
bash
cd project/android
mkdir build_64 && cd build_64
../build_64.sh "-DMNN_LOW_MEMORY=true -DMNN_CPU_WEIGHT_DEQUANT_GEMM=true -DMNN_BUILD_LLM=true -DMNN_SUPPORT_TRANSFORMER_FUSE=true -DMNN_ARM82=true -DMNN_OPENCL=true -DMNN_USE_LOGCAT=true"
mkdir build_32 && cd build_32
../build_32.sh "-DMNN_LOW_MEMORY=true -DMNN_CPU_WEIGHT_DEQUANT_GEMM=true -DMNN_BUILD_LLM=true -DMNN_SUPPORT_TRANSFORMER_FUSE=true -DMNN_ARM82=true -DMNN_OPENCL=true -DMNN_USE_LOGCAT=true"
这个主要是用于编译android需要用的mnn库,编译完成后将*.so文件将其放在android工程中
⚠️:如果需要调试mnn库,需要将build_64/32.sh文件中的如下参数设置为true
2 android工程
主要介绍一下其中一些关键的点:CMakeList文件的编写、JNI文件的编写,以及简要说一下android native的实现
2.1 头文件导入
将MNN库中的头文件(要包含llm.hpp头文件),以及1.5编译的android依赖库放入android工程中,目录如下:
2.2 CMakeList
bash
cmake_minimum_required(VERSION 3.10.2)
project("my_deep_seek")
aux_source_directory(./ SRC_LIST)
add_library(my_deep_seek SHARED ${SRC_LIST})
find_library(log-lib log)
find_library(android-lib android)
include_directories(${CMAKE_SOURCE_DIR}/mnn/include)
include_directories(${CMAKE_SOURCE_DIR}/mnn/include/expr/)
add_library(libMNN STATIC IMPORTED)
add_library(libMNN_CL STATIC IMPORTED)
add_library(libMNN_Express STATIC IMPORTED)
add_library(libllm STATIC IMPORTED)
set_target_properties(
libMNN PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/mnn/${CMAKE_ANDROID_ARCH_ABI}/libMNN.so
)
set_target_properties(
libMNN_CL PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/mnn/${CMAKE_ANDROID_ARCH_ABI}/libMNN_CL.so
)
set_target_properties(
libMNN_Express PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/mnn/${CMAKE_ANDROID_ARCH_ABI}/libMNN_Express.so
)
set_target_properties(
libllm PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/mnn/${CMAKE_ANDROID_ARCH_ABI}/libllm.so
)
message("===>>>> abi is : ${CMAKE_ANDROID_ARCH_ABI} <<<<===")
target_link_libraries(
my_deep_seek
${log-lib}
${android-lib}
libMNN
libMNN_CL
libMNN_Express
libllm
)
2.3 JNI
kotlin
class Chat : Serializable {
companion object {
init {
System.loadLibrary("my_deep_seek")
}
}
external fun Init(modelDir: String): Boolean // 加载模型
external fun Submit(input: String): String // 输入请求
external fun Respose(): ByteArray // 模型输出
external fun Done()
external fun Reset()
}
c++
#include <android/asset_manager_jni.h>
#include <android/bitmap.h>
#include <android/log.h>
#include <jni.h>
#include <string>
#include <vector>
#include <sstream>
#include <thread>
#include "MNN/llm.hpp"
#ifndef LOG_TAG
#define LOG_TAG "MyDeepSeek"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG ,__VA_ARGS__) // 定义LOGD类型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG ,__VA_ARGS__) // 定义LOGI类型
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG ,__VA_ARGS__) // 定义LOGW类型
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG ,__VA_ARGS__) // 定义LOGE类型
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,LOG_TAG ,__VA_ARGS__) // 定义LOGF类型
#endif
static std::unique_ptr<MNN::Transformer::Llm> llm(nullptr);
static std::stringstream response_buffer;
extern "C" {
// 模型加载
JNIEXPORT jboolean JNICALL
Java_com_example_mydeepseek_Chat_Init(JNIEnv *env, jobject thiz, jstring modelDir) {
const char* model_dir = env->GetStringUTFChars(modelDir, 0);
if (!llm.get()) {
llm.reset(MNN::Transformer::Llm::createLLM(model_dir));
try {
llm->load();
} catch (const std::exception& e) {
LOGI("=== 异常:%s ====", e.what());
return JNI_FALSE;
}
}
return JNI_TRUE;
}
// 将问题输入模型
JNIEXPORT jstring JNICALL
Java_com_example_mydeepseek_Chat_Submit(JNIEnv *env, jobject thiz, jstring inputStr) {
if (!llm.get()) {
return env->NewStringUTF("Failed, Chat is not ready!");
}
const char* input_str = env->GetStringUTFChars(inputStr, 0);
auto chat = [&](std::string str) {
llm->response(str, &response_buffer, "<eop>");
};
std::thread chat_thread(chat, input_str); //子线程运行
chat_thread.detach();
jstring result = env->NewStringUTF("Submit success!");
return result;
}
// 取出模型输出
JNIEXPORT jbyteArray JNICALL
Java_com_example_mydeepseek_Chat_Respose(JNIEnv *env, jobject thiz) {
auto len = response_buffer.str().size();
jbyteArray res = env->NewByteArray(len);
env->SetByteArrayRegion(res, 0, len, (const jbyte*)response_buffer.str().c_str());
return res;
}
JNIEXPORT void JNICALL
Java_com_example_mydeepseek_Chat_Done(JNIEnv *env, jobject thiz) {
response_buffer.str("");
}
JNIEXPORT void JNICALL
Java_com_example_mydeepseek_Chat_Reset(JNIEnv *env, jobject thiz) {
llm->reset();
}
} // extern "C"
2.4 android native
app实现方面比较简单,使用recycleview来显示与模型的对话,其他就调用jni接口即可
(1)加载模型
将1.4节模型转换得到的那些文件将其放到手机的/data/local/tmp/DeepSeek-R1-Distill-Qwen-1.5B目录下,然后mModelDir就为/data/local/tmp/DeepSeek-R1-Distill-Qwen-1.5B/llm.mnn
kotlin
Thread {
mChat = Chat()
if (mChat?.Init(mModelDir) == true) {
runOnUiThread {
mIntent?.putExtra("chat", mChat)
startActivityForResult(mIntent, 100)
}
} else {
Toast.makeText(this,"加载模型失败", Toast.LENGTH_SHORT).show()
}
}.start()
(2)向模型输入
kotlin
mChat?.Submit(input)
(3)得到模型输出
kotlin
Thread {
mChat?.Submit(input) // 输入
var lastResponse = ""
while (!lastResponse.contains("<eop>")) { // 模型输出结束标志"<eop>"
try {
Thread.sleep(50) // 等模型输出一点信息
val response: String = String(mChat?.Respose() ?: ByteArray(0))
if (response != lastResponse) {
lastResponse = response
lifecycleScope.launch {
updateBotResponse(
response.replaceFirst(
"<eop>".toRegex(),
""
)
)
}
}
} catch (e: InterruptedException) {
Thread.currentThread().interrupt()
}
}
mChat?.Done()
}
3 总结
1.5B模型,运行内存最高1.5G,推理时1G,占用内存还挺多,且模型性能相比云端模型还是差很多。但需向前看,最近阿里的QwQ-32B模型不是达到了DeepSeek 671B模型的性能了吗,甚至某些方面还超越DeepSeek,发展还是很快的。说不定后面不到1B参数的模型,性能可比肩671B模型,狠狠期待一下。
做为一个小小的开发,或许只能积累以及积极传递技术,让更多的人参与进来,这样技术和应用才能更加繁荣,或许才能在浪潮之下享受技术带来的福利。
参考资料
1\] [github.com/alibaba/MNN...](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Falibaba%2FMNN%2Ftree%2Fmaster "https://github.com/alibaba/MNN/tree/master")