博客 / 詳情

返回

Windows 環境下 llama.cpp 編譯 + Qwen 模型本地部署全指南

在大模型落地場景中,本地輕量化部署因低延遲、高隱私性、無需依賴雲端算力等優勢,成為開發者與 AI 愛好者的熱門需求。本文聚焦 Windows 10/11(64 位)環境,詳細拆解 llama.cpp 工具的編譯流程(支持 CPU/GPU 雙模式,GPU 加速需依賴 NVIDIA CUDA),並指導如何通過 modelscope 下載 GGUF 格式的 Qwen-7B-Chat 模型,最終實現模型本地啓動與 API 服務搭建。

1.打開管理員權限的 PowerShell/CMD,執行以下命令克隆代碼:

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

2.基礎編譯(僅 CPU 支持)或者選用GPU 加速編譯(已安裝 CUDA Toolkit)

如果只使用CPU則執行如下配置

cmake .. -G "Visual Studio 18 2026" -A x64 -DLLAMA_CURL=OFF
cmake --build . --config Release

如果已安裝 CUDA Toolkit,添加 -DLLAMA_CUDA=ON 開啓 GPU 支持

cmake .. -G "Visual Studio 18 2026" -A x64 -DLLAMA_CUDA=ON
cmake --build . --config Release

3、下載 GGUF 格式的 Qwen 模型(以 7B 為例)

https://www.modelscope.cn/models

pip install modelscope
modelscope download --model Xorbits/Qwen-7B-Chat-GGUF

下載後的保存位置為 \modelscope\hub\models\Xorbits

4、運行模型啓動 API 服務(支持 HTTP 調用)

# 命令行啓動
chcp 65001
llama-cli.exe -m qwen.gguf -i -c 4096

# CPU 版
llama-server.exe -m qwen.gguf --host 127.0.0.1 --port 11433 -c 4096

# GPU 加速版
llama-server.exe -m qwen-7b-chat.Q4_0.gguf -c 4096 --n-gpu-layers -1

5、服務啓動後默認監聽 http://localhost:8080,可通過 curl 測試調用效果。

curl http://localhost:8080/completion -H "Content-Type: application/json" -d '{
  "prompt": "你好,介紹一下通義千問",
  "temperature": 0.7,
  "max_tokens": 512
}'

6、工具測試,通過代碼調用大模型測試效果。

基礎非流式調用(completion 端點)

import requests
import json

url = "http://localhost:8080/completion"
headers = {"Content-Type": "application/json"}
data = {
    "model": "qwen.gguf",
    "prompt": "你好,請用100字介紹一下通義千問",
    "temperature": 0.7,  # 回答隨機性(越低越保守)
    "max_tokens": 512,  # 最大生成token數
    "ctx_size": 4096,  # 上下文窗口(與服務啓動時一致)
    "stop": ["<|im_end|>"]  # 停止符(適配Qwen的對話格式)
}

try:
    response = requests.post(url, headers=headers, data=json.dumps(data), timeout=60)
    response.raise_for_status()
    result = response.json()

    print("生成結果:")
    print(result["content"])
except Exception as e:
    print(f"調用失敗:{e}")

多輪對話示例(基於 chat/completions)

import requests
import json

chat_history = []
url = "http://localhost:8080/chat/completions"
headers = {"Content-Type": "application/json"}

def chat_with_model(prompt):
    # 添加當前用户消息到歷史
    chat_history.append({"role": "user", "content": prompt})

    data = {
        "model": "qwen.gguf",
        "messages": chat_history,
        "temperature": 0.7,
        "max_tokens": 512
    }

    try:
        response = requests.post(url, headers=headers, data=json.dumps(data), timeout=60)
        response.raise_for_status()
        result = response.json()
        answer = result["choices"][0]["message"]["content"]

        # 添加助手回答到歷史
        chat_history.append({"role": "assistant", "content": answer})
        return answer
    except Exception as e:
        return f"調用失敗:{e}"

# 多輪對話示例
print("開始多輪對話(輸入'退出'結束):")
while True:
    user_input = input("你:")
    if user_input == "退出":
        break
    answer = chat_with_model(user_input)
    print(f"助手:{answer}\n")

帶有對話記憶功能測試

import requests
import json
import re

# 初始化對話歷史(包含系統提示,引導模型記上下文)
chat_history = [
    {"role": "system", "content": "你是一個有幫助的助手,必須記住之前的對話內容,基於上下文回答用户問題。"}
]
# 你的服務實際地址(保持你原來的 11433 端口和 OpenAI 兼容路徑)
url = "http://localhost:11433/chat/completions"
headers = {"Content-Type": "application/json"}

def clean_pad_content(content):
    """過濾模型返回的 [PAD...] 垃圾字符"""
    return re.sub(r'\[PAD\d+\]', '', content).strip()

def chat_with_model(prompt):
    global chat_history

    # 添加當前用户消息到歷史(關鍵:上下文靠這個列表傳遞)
    chat_history.append({"role": "user", "content": prompt})

    data = {
        "model": "qwen.gguf",  # 保持你原來的模型名(你的服務識別這個名字)
        "messages": chat_history,  # 傳遞完整對話歷史
        "temperature": 0.7,
        "max_tokens": 512,
        "stream": False,  # 關閉流式輸出,適配你的返回格式
        "stop": ["[PAD"]  # 提前終止 PAD 字符的輸出
    }

    try:
        response = requests.post(url, headers=headers, data=json.dumps(data), timeout=60)
        response.raise_for_status()  # 觸發 HTTP 錯誤(比如 404、500)

        result = response.json()
        print(f"調試:模型原始返回 = {json.dumps(result, ensure_ascii=False)[:500]}")  # 可選:查看原始返回

        # 適配你的 OpenAI 兼容格式:從 choices[0].message.content 提取內容
        if "choices" in result and len(result["choices"]) > 0:
            choice = result["choices"][0]
            if "message" in choice and "content" in choice["message"]:
                raw_answer = choice["message"]["content"]
                answer = clean_pad_content(raw_answer)  # 過濾 PAD 垃圾字符

                # 關鍵:將助手回覆加入歷史,下次請求會帶上
                chat_history.append({"role": "assistant", "content": answer})
                return answer
            else:
                return f"返回格式異常:缺少 message/content 字段,原始返回:{json.dumps(result, ensure_ascii=False)[:300]}"
        else:
            return f"返回格式異常:缺少 choices 字段,原始返回:{json.dumps(result, ensure_ascii=False)[:300]}"

    except requests.exceptions.ConnectionError:
        return "連接失敗:請檢查本地服務是否在 11433 端口運行"
    except requests.exceptions.Timeout:
        return "請求超時:模型響應過慢"
    except Exception as e:
        return f"調用失敗:{str(e)},原始返回:{response.text[:300] if 'response' in locals() else '無'}"

# 多輪對話測試(重點測試上下文記憶)
print("開始多輪對話(輸入'退出'結束):")
print("提示:先發送 '我的名字是李四',再發送 '我叫什麼名字' 測試記憶功能\n")
while True:
    user_input = input("你:")
    if user_input.strip() == "退出":
        break
    if not user_input.strip():
        print("助手:請輸入有效內容!\n")
        continue
    answer = chat_with_model(user_input)
    print(f"助手:{answer}\n")

函數工具調用測試

import requests
import json
import re
from datetime import datetime

# ====================== 1. 定義可用工具集 ======================
# 工具1:獲取當前時間
def get_current_time():
    """獲取當前的本地時間,格式為 年-月-日 時:分:秒"""
    current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    return f"當前時間為:{current_time}"


# 工具2:加法計算
def calculate_add(a: float, b: float):
    """計算兩個數的加法結果"""
    return f"{a} + {b} = {a + b}"


# 工具註冊表(核心:映射工具名到函數和描述,供模型識別)
tool_registry = {
    "get_current_time": {
        "function": get_current_time,
        "description": "獲取當前的本地時間,無需參數",
        "parameters": {}  # 無參數
    },
    "calculate_add": {
        "function": calculate_add,
        "description": "計算兩個數字的加法,需要兩個參數:a(數字)、b(數字)",
        "parameters": {
            "a": {"type": "float", "required": True, "description": "加數1"},
            "b": {"type": "float", "required": True, "description": "加數2"}
        }
    }
}

# ====================== 2. 初始化對話歷史和基礎配置 ======================
chat_history = [
    {"role": "system", "content": """你是一個有幫助的助手,必須記住之前的對話內容,基於上下文回答用户問題。
你可以調用以下工具來輔助回答:
1. get_current_time:獲取當前的本地時間,無需參數
2. calculate_add:計算兩個數字的加法,需要參數a和b(均為數字)

如果需要調用工具,請嚴格按照以下JSON格式返回(僅返回JSON,不要加其他內容):
{"name": "工具名", "parameters": {"參數名": 參數值}}

如果不需要調用工具,直接回答用户問題即可,不要返回JSON格式。"""}
]

# 本地LLM服務地址
url = "http://localhost:11433/chat/completions"
headers = {"Content-Type": "application/json"}


# ====================== 3. 工具調用相關輔助函數 ======================
def clean_pad_content(content):
    """過濾模型返回的 [PAD...] 垃圾字符"""
    return re.sub(r'\[PAD\d+\]', '', content).strip()


def parse_tool_call(content):
    """解析模型返回的內容,提取工具調用指令(JSON格式)"""
    try:
        # 提取JSON部分(兼容模型返回時可能帶的多餘文字)
        json_match = re.search(r'\{[\s\S]*\}', content)
        if not json_match:
            return None
        tool_call = json.loads(json_match.group())
        # 驗證必要字段
        if "name" in tool_call and "parameters" in tool_call:
            return tool_call
        return None
    except (json.JSONDecodeError, Exception):
        return None


def execute_tool(tool_call):
    """執行工具調用,返回執行結果"""
    tool_name = tool_call["name"]
    parameters = tool_call.get("parameters", {})

    # 檢查工具是否存在
    if tool_name not in tool_registry:
        return f"錯誤:不存在名為 {tool_name} 的工具,可用工具:{list(tool_registry.keys())}"

    tool_info = tool_registry[tool_name]
    tool_func = tool_info["function"]
    tool_params = tool_info["parameters"]

    # 驗證必填參數
    missing_params = []
    for param_name, param_info in tool_params.items():
        if param_info.get("required") and param_name not in parameters:
            missing_params.append(param_name)
    if missing_params:
        return f"錯誤:調用 {tool_name} 缺少必填參數:{', '.join(missing_params)}"

    # 轉換參數類型(比如字符串轉數字)
    try:
        for param_name, param_info in tool_params.items():
            if param_name in parameters:
                param_type = param_info.get("type", "str")
                if param_type == "float":
                    parameters[param_name] = float(parameters[param_name])
                elif param_type == "int":
                    parameters[param_name] = int(parameters[param_name])
    except ValueError as e:
        return f"錯誤:參數類型轉換失敗 - {str(e)}"

    # 執行工具函數
    try:
        result = tool_func(**parameters)
        return f"工具調用成功({tool_name}):{result}"
    except Exception as e:
        return f"錯誤:執行 {tool_name} 失敗 - {str(e)}"


# ====================== 4. 核心對話函數(支持工具調用) ======================
def chat_with_model(prompt):
    global chat_history

    # 添加當前用户消息到歷史
    chat_history.append({"role": "user", "content": prompt})

    # 第一步:發送請求,判斷是否需要調用工具
    data = {
        "model": "qwen.gguf",
        "messages": chat_history,
        "temperature": 0.7,
        "max_tokens": 512,
        "stream": False,
        "stop": ["[PAD"]
    }

    try:
        # 第一次調用模型:獲取是否需要工具調用的響應
        response = requests.post(url, headers=headers, data=json.dumps(data), timeout=60)
        response.raise_for_status()
        result = response.json()

        # 解析模型原始返回
        if "choices" in result and len(result["choices"]) > 0 and "message" in result["choices"][0]:
            raw_answer = result["choices"][0]["message"]["content"]
            clean_answer = clean_pad_content(raw_answer)
        else:
            return f"返回格式異常:{json.dumps(result, ensure_ascii=False)[:300]}"

        # 解析是否包含工具調用指令
        tool_call = parse_tool_call(clean_answer)
        if tool_call:
            print(f"📢 檢測到工具調用:{json.dumps(tool_call, ensure_ascii=False)}")

            # 執行工具並獲取結果
            tool_result = execute_tool(tool_call)
            print(f"🔧 工具執行結果:{tool_result}")

            # 將工具執行結果加入對話歷史(讓模型感知結果)
            chat_history.append({
                "role": "assistant",
                "content": f"工具調用結果:{tool_result}"
            })

            # 第二步:基於工具結果,再次調用模型生成最終回答
            second_response = requests.post(url, headers=headers, data=json.dumps(data), timeout=60)
            second_response.raise_for_status()
            second_result = second_response.json()

            # 解析第二次調用的結果
            if "choices" in second_result and len(second_result["choices"]) > 0 and "message" in \
                    second_result["choices"][0]:
                final_answer = clean_pad_content(second_result["choices"][0]["message"]["content"])
                chat_history.append({"role": "assistant", "content": final_answer})
                return final_answer
            else:
                return f"工具調用後二次請求異常:{json.dumps(second_result, ensure_ascii=False)[:300]}"
        else:
            # 無需調用工具,直接返回模型回答
            chat_history.append({"role": "assistant", "content": clean_answer})
            return clean_answer

    except requests.exceptions.ConnectionError:
        return "連接失敗:請檢查本地服務是否在 11433 端口運行"
    except requests.exceptions.Timeout:
        return "請求超時:模型響應過慢"
    except Exception as e:
        return f"調用失敗:{str(e)},原始返回:{response.text[:300] if 'response' in locals() else '無'}"


# ====================== 5. 多輪對話測試(含工具調用) ======================
if __name__ == "__main__":
    print("開始多輪對話(輸入'退出'結束):")
    print("📌 測試工具調用示例:")
    print("   1. 現在幾點了?(調用獲取時間工具)")
    print("   2. 計算123+456等於多少?(調用加法工具)")
    print("   3. 我的名字是李四,我叫什麼?(測試上下文記憶)\n")

    while True:
        user_input = input("你:")
        if user_input.strip() == "退出":
            break
        if not user_input.strip():
            print("助手:請輸入有效內容!\n")
            continue
        answer = chat_with_model(user_input)
        print(f"助手:{answer}\n")
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.