Android端部署DeepSeek的详细教程
作者:技术野侠客
DeepSeek最近几个月很火热,很多产品以及企业都在接入DeepSeek,比如微信搜索接入,可以搜索公众号信息并总结,这个对于查一些资料还挺好用,因为现在很多都会在公众号上写一些东西,进行宣传,毕竟手机才是用户用的最多的。
既然谈到了手机,那么DeepSeek能否部署于手机之上呢,在不联网的情况下就能使用?基于这个问题,查询了相关资料,发现android端部署DeepSeek两种方法:
第一种是使用Termux。Termux 是一款终端模拟器应用程序,专为 Android 系统设计,提供完整的 Linux 环境,允许用户在 Android 设备上运行 Linux 命令和工具,无需 root 权限。然后使用ollama相关命令下载部署模型即可。目前网上基本上都是这种方式,这种方式其实跟在服务端部署差不多,所以为啥不直接用服务端部署的模型呢。
第二种就是直接将模型文件下载到手机中,应用内直接加载模型文件并运行,这种方式好处在于,可结合自身业务做一些基于大模型的离线本地私有化应用,耗费基本为0。
本文主要采用第二种方法,基于阿里MNN库进行部署。
1 准备环境
1.1 MNN转换工具
cd MNN mkdir build && cd build cmake .. -DMNN_BUILD_CONVERTER=ON make -j8
这个主要是得到MNNConvert转换工具,可用来转换onnx格式为mnn格式
1.2 大模型转换工具
安装大模型转换工具需要的一些依赖,可用conda来进行环境管理
cd path/MNN/transformers/llm/export pip install -r requirements.txt
如果只是想试试大模型android端部署,把这些依赖下载下来,然后使用如下方式一转换模型就行了
1.3 模型下载
git lfs install git clone https://www.modelscope.cn/deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B.
1.4 模型转换
方式一:直接使用llmexport.py直接转换得到mnn格式的模型文件
python llmexport.py --path path/model/DeepSeek-R1-Distill-Qwen-1.5B --export mnn
方式二:可先转换为onnx,然后再使用MNNConvert转换为mnn格式模型
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依赖库
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
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
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() }
#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
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)向模型输入
mChat?.Submit(input)
(3)得到模型输出
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模型,狠狠期待一下。
以上就是Android端部署DeepSeek的详细教程的详细内容,更多关于Android端部署DeepSeek的资料请关注脚本之家其它相关文章!