Qwen在今年3月份的时候发布了号称能媲美DeepSeek-R1-671B的32B模型:QwQ(这名字卖萌是故意的,还是不小心?)

32B模型如果做一个4bit量化的话,大约只需要占16G的显存,这样的话,一块24G显存的消费级显卡(如NVIDIA GeForce RTX 3090)也能把这个模型部署起来。拥有一个本地部署的性能匹配满血DeepSeek-R1的模型岂不美哉?于是,我紧锣密鼓地一顿部署,很快便搞定了。

下载模型

模型的官方链接位于Qwen/QwQ-32B,不过我这里选择了另一个HuggingFace 上的由bartowski提供的 该模型的 llama.cpp/ggml 格式量化版仓库:bartowski/Qwen_QwQ-32B-GGUF

找到文件:Qwen_QwQ-32B-IQ4_XS.gguf,用wget下到本地即可。

安装 llama.cpp

这是一个用cpp写的大模型推理框架,链接如下:

仓库给了一个编译文档,不过我直接对着文档操作失败了,所以在这里记录一下我的操作。

在编译之前,需要确保已经安装好了一些基本的组件:

sudo apt update
sudo apt install -y build-essential libcurl4-openssl-dev
sudo snap install cmake --classic

另外如果编译CUDA版,还需要装好cuda-toolkit:

$ nvcc --version
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2024 NVIDIA Corporation
Built on Tue_Feb_27_16:19:38_PST_2024
Cuda compilation tools, release 12.4, V12.4.99
Build cuda_12.4.r12.4/compiler.33961263_0

增加CUDA路径

export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH

Clone仓库

git clone https://github.com/ggml-org/llama.cpp
cd llama.cpp

开始编译

env -i \
  PATH="/snap/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/bin:/usr/local/cuda/bin" \
  CC=/usr/bin/gcc CXX=/usr/bin/g++ \
  CUDACXX=/usr/local/cuda/bin/nvcc \
  cmake -B build -DGGML_CUDA=ON \
        -DCMAKE_C_FLAGS="-pthread" \
        -DCMAKE_CXX_FLAGS="-pthread" \
        -DCMAKE_EXE_LINKER_FLAGS="-pthread" \
        -DCUDAToolkit_ROOT=/usr/local/cuda

cmake --build build -j4  # 4核足以,开太多会把我ssh挤掉

编译完成后,将编译出的文件移动到系统上:

sudo cmake --install build --prefix /usr/local

这时直接运行llama-server仍可能会报错:

llama-server: error while loading shared libraries: libmtmd.so: cannot open shared object file: No such file or directory

运行一下下面的命令即可:

# 确保 /usr/local/lib 在动态库配置里,并刷新缓存
echo '/usr/local/lib' | sudo tee /etc/ld.so.conf.d/llama.conf
sudo ldconfig

部署 QwQ-32B

创建一个jinja模板./chat_templates/QwQ.jinja

{%- if tools %}
    {{- '<|im_start|>system\n' }}
    {%- if messages[0]['role'] == 'system' %}
        {{- messages[0]['content'] }}
    {%- else %}
        {{- '' }}
    {%- endif %}
    {{- "\n\n# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>" }}
    {%- for tool in tools %}
        {{- "\n" }}
        {{- tool | tojson }}
    {%- endfor %}
    {{- "\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{\"name\": <function-name>, \"arguments\": <args-json-object>}\n</tool_call><|im_end|>\n" }}
{%- else %}
    {%- if messages[0]['role'] == 'system' %}
        {{- '<|im_start|>system\n' + messages[0]['content'] + '<|im_end|>\n' }}
  {%- endif %}
{%- endif %}
{%- for message in messages %}
    {%- if (message.role == "user") or (message.role == "system" and not loop.first) %}
        {{- '<|im_start|>' + message.role + '\n' + message.content + '<|im_end|>' + '\n' }}
    {%- elif message.role == "assistant" and not message.tool_calls %}
        {%- set content = (((message.content | default('', true))).split('</think>')|last).lstrip('\n') %}
        {{- '<|im_start|>' + message.role + '\n' + content + '<|im_end|>' + '\n' }}
    {%- elif message.role == "assistant" %}
        {%- set content = (((message.content | default('', true))).split('</think>')|last).lstrip('\n') %}
        {{- '<|im_start|>' + message.role }}
        {%- if message.content %}
            {{- '\n' + content }}
        {%- endif %}
        {%- for tool_call in message.tool_calls %}
            {%- if tool_call.function is defined %}
                {%- set tool_call = tool_call.function %}
            {%- endif %}
            {{- '\n<tool_call>\n{"name": "' }}
            {{- tool_call.name }}
            {{- '", "arguments": ' }}
            {{- tool_call.arguments | tojson }}
            {{- '}\n</tool_call>' }}
        {%- endfor %}
        {{- '<|im_end|>\n' }}
    {%- elif message.role == "tool" %}
        {%- if (loop.index0 == 0) or (messages[loop.index0 - 1].role != "tool") %}
            {{- '<|im_start|>user' }}
        {%- endif %}
        {{- '\n<tool_response>\n' }}
        {{- message.content }}
        {{- '\n</tool_response>' }}
        {%- if loop.last or (messages[loop.index0 + 1].role != "tool") %}
            {{- '<|im_end|>\n' }}
        {%- endif %}
    {%- endif %}
{%- endfor %}
{%- if add_generation_prompt %}
    {{- '<|im_start|>assistant\n<think>\n' }}
{%- endif %}

创建启动命令文件llama_deploy.sh:

llama-server \
    --model "./model/bartowski/Qwen_QwQ-32B-GGUF/Qwen_QwQ-32B-IQ4_XS.gguf" \
    --alias "QwQ-32B" \
    --n-gpu-layers 65 \
    --ctx-size 32768 \
    --parallel 1 \
    --cache-type-k q8_0 \
    --cache-type-v q8_0 \
    --threads 16 \
    --flash-attn \
    --mlock \
    --n-predict -1 \
    --jinja \
    --chat-template-file ./chat_templates/QwQ.jinja \
    --host 0.0.0.0 \
    --port 8000

然后运行llama_deploy.sh,即可完成部署。

测试

在Cherry Studio中还能轻松调用工具: