Skip to content

N100中使用docker部署Qwen3.5-0.8b-MNN

约 1753 字大约 6 分钟

AI

2026-03-04

准备在绿联上跑个Qwen的模型,N100的U,8G内存。查了半天发现了MNN

编译MNN

构建目录

root@85:/home/qwen# tree -a -L 1
|-- .dockerignore
|-- .env
|-- Dockerfile
|-- docker-compose.yml
|-- model
`-- src

根据官方文档的部署:

下载MNN框架,编译

MNN (Mobile Neural Network),阿里开源的轻量级深度学习推理框架
部署Qwen需要通过MNN-LLM,MNN-LLM是基于MNN开发的

#https://mnn-docs.readthedocs.io/en/latest/transformers/llm.html?highlight=llm_demo

cd /home/qwen
rm -rf MNN-3.4.0

#3.4无法跑Qwen3.5
#git clone --recurse-submodules https://github.com/alibaba/MNN.git -b 3.4.0 MNN-3.4.0

git clone --recurse-submodules https://github.com/alibaba/MNN.git -b mmaster MNN
cd MNN
rm -rf build && mkdir build && cd build

# 官方推荐参数 + N100 优化
cmake .. \
  -DCMAKE_BUILD_TYPE=Release \
  -DMNN_BUILD_LLM=true \
  -DMNN_SUPPORT_TRANSFORMER_FUSE=true \
  -DMNN_CPU_WEIGHT_DEQUANT_GEMM=true \
  -DMNN_LOW_MEMORY=true \
  -DMNN_AVX2=true \
  -DMNN_AVX512=false \
  -DMNN_SEP_BUILD=ON \        # ← 关键:改为 ON,使用动态库方式
  -DMNN_USE_SYSTEM_LIB=OFF \
  -DMNN_BUILD_TOOLS=true \
  -DMNN_USE_THREAD_POOL=true
  
cmake .. -DCMAKE_BUILD_TYPE=Release -DMNN_BUILD_LLM=true -DMNN_SUPPORT_TRANSFORMER_FUSE=true -DMNN_CPU_WEIGHT_DEQUANT_GEMM=true  -DMNN_LOW_MEMORY=true  -DMNN_AVX2=true  -DMNN_AVX512=false -DMNN_SEP_BUILD=ON   -DMNN_USE_SYSTEM_LIB=OFF  -DMNN_BUILD_TOOLS=true -DMNN_USE_THREAD_POOL=true

# 编译
make -j4

# 验证产物
ls -lh llm_demo libMNN.so libllm.so  

echo "你好,简单介绍下自己" > prompt.txt
./llm_demo ../../Qwen2.5-0.5B-Instruct-MNN/config.json prompt.txt

../mnn_runtime/bin/llm_demo config.json ../prompt.txt

../mnn_runtime/bin/llm_demo config.json

检查依赖

ldd /home/qwen/mnn_runtime/bin/llm_demo
root@2d4e1355223f:/app/bin# ldd llm_demo 
        linux-vdso.so.1 (0x00007ffec5f84000)
        libllm.so => /app/lib/libllm.so (0x00007f64c3b05000)
        libMNN_Express.so => /app/lib/libMNN_Express.so (0x00007f64c3a11000)
        libMNN.so => /app/lib/libMNN.so (0x00007f64c3727000)
        libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f64c3508000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f64c3326000)
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f64c3244000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f64c3c3b000)
        libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f64c3224000)

下载模型

https://modelscope.cn/models/MNN/Qwen2.5-0.5B-Instruct-MNN 可以运行 https://modelscope.cn/models/MNN/Qwen3.5-0.8B-MNN 一直遇到3.5运行时模型初始化错误。

坑了一天,最后发现 Qwen3.5-0.8B-MNN报错的原因是因为下载了MNN3.4.0版本,他不支持3.5的模型!!! 流程是没错的,下载master分支编译就可以了

运行

####编译打包镜像的机器 I7-12代 
root@85:/home/qwen/Qwen3.5-0.8B-MNN# ../MNN/build/llm_demo config.json ../prompt.txt 
The device supports: i8sdot:0, fp16:0, i8mm: 0, sve2: 0, sme2: 0
config path is config.json
main, 267, cost time: 1876.698975 ms
Prepare for tuning opt Begin
Prepare for tuning opt End
main, 275, cost time: 162.406998 ms
prompt file is ../prompt.txt
你好!很高兴认识我。我是 Qwen3.5 的最新版本,由阿里巴巴塔林实验室联合研发。

我的主要特点包括:
- **深度理解语言能力**:具备强大的语义理解和逻辑推理基础。
- **多领域知识积累**:涵盖历史事实、科学常识和伦理道德等多个方面。
- **高效协作能力**:能在代码编写、数据分析等方面提供具体建议。
- **安全合规与责任边界**:
   - 严格遵守国家法律法规
   - 保护用户隐私和信息安全(如处理个人数据)
   - 确保对话的合法性和道德性(避免生成违法、危险或有害内容)

希望这些问题能帮助你更好地理解本系统的功能!😊

#################################
prompt tokens num = 22
decode tokens num = 150
 vision time = 0.00 s
 pixels_mp = 0.00 MP
  audio process time = 0.00 s
  audio input time = 0.00 s
prefill time = 0.10 s
 decode time = 3.39 s
 sample time = 0.05 s
prefill speed = 217.60 tok/s 首屏响应
 decode speed = 44.18 tok/s 生成速度
 vision speed = 0.000 MP/s
 audio RTF = -nan 
##################################


###################### N100上 2B模型 #############################
root@ef1be15091d7:/app# ./bin/llm_demo ./model/config.json p.txt 
CPU Group: [ 2  0  3  1 ], 700000 - 3400000
The device supports: i8sdot:0, fp16:0, i8mm: 0, sve2: 0, sme2: 0
config path is ./model/config.json
main, 267, cost time: 9331.930664 ms
Prepare for tuning opt Begin
Prepare for tuning opt End
main, 275, cost time: 572.570984 ms
prompt file is p.txt
你好!我是**Qwen3.5**。

我是通义千问的进阶版本,拥有超大规模上下文窗口(最长256K -token)、视觉/数学等高级能力。在医疗、法律、科研、编程等领域表现强劲。比如我可以帮你拆解复杂的代码逻辑,或是分析专业学术论文的结论...

有什么特别想聊的吗?✨

#################################
prompt tokens num = 21
decode tokens num = 77
 vision time = 0.00 s
 pixels_mp = 0.00 MP
  audio process time = 0.00 s
  audio input time = 0.00 s
prefill time = 1.07 s
 decode time = 5.88 s
 sample time = 0.03 s
prefill speed = 19.70 tok/s
 decode speed = 13.10 tok/s
 vision speed = 0.000 MP/s
 audio RTF = -nan 
##################################


root@ef1be15091d7:/app# ./bin/llm_demo ./model/config.json       
CPU Group: [ 2  0  3  1 ], 700000 - 3400000
The device supports: i8sdot:0, fp16:0, i8mm: 0, sve2: 0, sme2: 0
config path is ./model/config.json
main, 267, cost time: 7778.529785 ms
Prepare for tuning opt Begin
Prepare for tuning opt End
main, 275, cost time: 818.870972 ms

User: 我分2次给你2个数字,你帮我结算他们的和
A: 请依次告诉我两个数字。
我会把这两个数的总和计算出来:`__ + __ = `
(请直接输入数字,例如:3 5)

User: 4
A: 4

User: 6
A: 4 + 6 = 10

User: 很棒!
A: 当然!如果你们接下来还有更多题目需要练习或者有其他问题需要帮忙,随时可以再来问我~加油!😊

User: 我是天天,你以后可以叫我天天  
A: 没问题,**天天���**
那我们就接着上一题的格式来:`4 + 6 = 10`
如果还需要继续做其他加法,随时吩咐~����

User: 我叫什么?
A: 你的名字是:**天天**

使用了python写了个api方便调用

测试健康:

  # 1. 健康检查
curl http://localhost:8456/health

# 2. 测试聊天接口
curl -X POST http://localhost:8456/chat \
 -H "Content-Type: application/json"  \ 
 -d '{"msg": "你好"}'

产物

dockerfile

FROM debian:12-slim

# 环境变量(解决交互/编码问题)
ENV DEBIAN_FRONTEND=noninteractive \
    PYTHONUNBUFFERED=1 \
    # 告诉系统加载 MNN 运行时库
    LD_LIBRARY_PATH=/app/lib:$LD_LIBRARY_PATH

# 安装python api运行时依赖
RUN apt update && apt install -y \
    python3 python3-pip libomp-dev curl \
    && rm -rf /var/lib/apt/lists/*

# 安装 Flask(OpenAI 接口依赖)
RUN python3 -m pip install --upgrade pip --break-system-packages
RUN pip install flask==2.3.3 --break-system-packages

# 复制 MNN 运行时库 (编译的)
COPY mnn_runtime/lib/libMNN.so /app/lib/
COPY mnn_runtime/lib/libllm.so /app/lib/
COPY mnn_runtime/lib/libMNN_Express.so /app/lib/

# 复制 llm_demo 二进制工具(核心)
COPY mnn_runtime/bin/llm_demo /app/bin/
RUN chmod +x /app/bin/llm_demo  # 确保有执行权限

# 创建工作目录 + 权限(避免日志/模型权限问题)
RUN mkdir -p /app/model && \
    mkdir -p /app/src && \
    mkdir -p /app/logs && \
    chmod 777 /app/logs

# 复制应用代码和配置
#COPY src/ /app/src/
COPY .env /app/

# 工作目录 + 暴露端口(你的测试端口 8456)
WORKDIR /app
EXPOSE 8456

# 启动 Flask 服务
CMD ["python3", "/app/src/api_server.py"]

docker compose

services:
  qwen-service:
    build: .
    container_name: qwen-mnn
    restart: always
    ports:
      - "8456:8456"
    volumes:
      - ./model:/app/model
      - ./src:/app/src
      - ./logs:/app/logs
    environment:
      - OMP_NUM_THREADS=${OMP_NUM_THREADS}
    deploy:
      resources:
        limits:
          cpus: 4.0
          memory: 2G
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8456/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s

python flash api

#!/usr/bin/env python3
import os
import time
import subprocess
from flask import Flask, request, jsonify

app = Flask(__name__)
app.config["JSON_AS_ASCII"] = False

# 核心配置
LLM_DEMO = "/app/bin/llm_demo"
CONFIG = "/app/model/config.json"

# 全局变量:仅维护交互式进程
llm_proc = None

# 启动交互式进程(只启动一次)
def start_llm():
    global llm_proc
    if not llm_proc or llm_proc.poll() is not None:
        # 启动交互式模式:llm_demo config.json
        llm_proc = subprocess.Popen(
            [LLM_DEMO, CONFIG],
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            encoding="utf-8",
            bufsize=1,  # 行缓冲
            universal_newlines=True
        )
        time.sleep(3)

# 初始化进程
start_llm()

# 核心对话接口
@app.route("/chat", methods=["POST"])
def chat():
    # 1. 接收用户输入
    data = request.get_json()
    prompt = data.get("msg", "").strip()
    if not prompt:
        return jsonify({"code": 1, "msg": "输入不能为空"})

    # 2. 确保进程存活
    if not llm_proc or llm_proc.poll() is not None:
        start_llm()

    # 3. 发送输入到 llm_demo
    llm_proc.stdin.write(prompt + "\n")
    llm_proc.stdin.flush()

    # 4. 读取回复(过滤启动日志,只取A:后的内容)
    response = ""
    while True:
        line = llm_proc.stdout.readline()
        if not line:
            break
        # 只处理回复行(A: 开头),跳过其他日志
        if line.startswith("A: "):
            response += line.replace("A: ", "").strip() + "\n"
        # 检测到下一个 User: 说明回复结束
        if line.startswith("User:"):
            break

    # 5. 返回结果
    return jsonify({
        "code": 0,
        "input": prompt,
        "reply": response.strip()
    })

# 健康检查
@app.route("/health")
def health():
    alive = llm_proc and llm_proc.poll() is None
    return jsonify({"alive": alive, "pid": llm_proc.pid if alive else None})

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8456, debug=False)