DeepSeek-R1 推理模型实战:5 步搭建本地深度思考 AI 助手

阿里云上云采购季 2核2G云服务器99元/年 续费同价

DeepSeek-R1 发布后,”深度思考”成了大模型圈最火的关键词。本文带你从零搭建一个本地运行的 DeepSeek-R1 推理助手,支持多轮对话、思维链可视化,全程只需 5 步。

为什么选择 DeepSeek-R1?

2025 年初,DeepSeek-R1 的发布在 AI 圈引发了一场地震。不同于传统的大语言模型,R1 系列模型主打 推理能力——它能像人类一样”思考”,在数学证明、代码调试、逻辑推理等复杂任务上表现惊人。

DeepSeek-R1 vs 其他推理模型对比

特性 DeepSeek-R1 OpenAI o3 Claude 3.5 QwQ-32B
开源 ✅ Apache 2.0 ❌ 闭源 ❌ 闭源 ✅ 开源
本地部署 ✅ 支持 ❌ ❌ ✅ 支持
推理链可视化 ✅ 完整 部分 ❌ ✅ 完整
最低显存要求 8GB(量化版) – – 16GB
数学推理能力 顶尖 顶尖 优秀 优秀
中文能力 原生优秀 优秀 优秀 原生优秀
API 价格(输入) ¥1/M tokens $4/M tokens $3/M tokens ¥2/M tokens

核心优势:开源可本地部署、推理链完全透明、中文原生支持、成本极低。

第一步:环境准备

硬件要求

根据你的显卡选择合适的模型版本:

模型版本 参数量 量化方式 最低显存 推荐显存
R1-Distill-Qwen-1.5B 1.5B Q4_K_M 4GB 6GB
R1-Distill-Qwen-7B 7B Q4_K_M 8GB 12GB
R1-Distill-Qwen-14B 14B Q4_K_M 12GB 16GB
R1-Distill-Qwen-32B 32B Q4_K_M 20GB 24GB
R1-Distill-Qwen-70B 70B Q4_K_M 40GB 48GB

💡 没有显卡也能跑!CPU 推理虽然慢一些,但 7B 模型在 16GB 内存的机器上完全可行。

安装 Ollama(最简单的部署方式)

# Linux / macOS(一键安装)
curl -fsSL https://ollama.com/install.sh | sh

# Windows:去 https://ollama.com/download 下载安装包
# 安装完成后,Ollama 会自动以系统服务方式运行

安装完成后验证:

ollama --version
# 输出:ollama version is 0.x.x

第二步:下载 DeepSeek-R1 模型

使用 Ollama 一键拉取模型:

# 推荐大多数人使用 7B 版本(平衡性能与速度)
ollama pull deepseek-r1:7b

# 如果显卡较好(RTX 4090 / A100),可以用 14B
ollama pull deepseek-r1:14b

# 轻量级机器(集成显卡 / 8G 显存),用 1.5B
ollama pull deepseek-r1:1.5b

验证模型加载:

ollama run deepseek-r1:7b "你好,请介绍一下自己"

你会看到模型在回答前会先展示一段 <think >...</think > 的思维链过程——这就是 R1 的核心特征。

第三步:用 Python 构建 API 服务

接下来,我们用 FastAPI 包装一个本地推理服务,支持多轮对话和思维链提取。

"""
DeepSeek-R1 本地推理 API 服务
功能:多轮对话、思维链可视化、流式输出
"""

import json
import time
from typing import AsyncGenerator
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
import httpx

app = FastAPI(title="DeepSeek-R1 本地推理服务")

# 跨域配置
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_methods=["*"],
    allow_headers=["*"],
)

# Ollama 本地 API 地址
OLLAMA_BASE_URL = "http://localhost:11434"
MODEL_NAME = "deepseek-r1:7b"

# 对话历史存储(简单实现,生产环境建议用 Redis)
chat_histories: dict[str, list[dict]] = {}


class ChatRequest(BaseModel):
    """聊天请求"""
    message: str
    session_id: str = "default"
    temperature: float = 0.6
    max_tokens: int = 4096


class ChatResponse(BaseModel):
    """聊天响应"""
    thinking: str      # 思维链内容
    answer: str        # 最终回答
    duration: float    # 耗时(秒)
    model: str         # 模型名称


def extract_thinking_and_answer(raw_text: str) -> tuple[str, str]:
    """
    从原始输出中提取思维链和最终回答
    DeepSeek-R1 的输出格式:<think >思维过程</think >最终回答
    """
    thinking = ""
    answer = raw_text

    # 提取 <think >...</think > 中的内容
    if "<think >" in raw_text:
        parts = raw_text.split("</think >", 1)
        think_part = parts[0]
        if "<think >" in think_part:
            thinking = think_part.split("<think >", 1)[1].strip()
        answer = parts[1].strip() if len(parts) > 1 else raw_text
    # 兼容没有闭合标签的情况
    elif raw_text.startswith("<think >"):
        thinking = raw_text[7:].strip()
        answer = ""

    return thinking, answer


@app.post("/api/chat", response_model=ChatResponse)
async def chat(request: ChatRequest):
    """普通聊天接口(非流式)"""
    # 获取或创建对话历史
    if request.session_id not in chat_histories:
        chat_histories[request.session_id] = []

    history = chat_histories[request.session_id]

    # 构建消息列表
    messages = history + [{"role": "user", "content": request.message}]

    start_time = time.time()

    try:
        async with httpx.AsyncClient(timeout=300.0) as client:
            response = await client.post(
                f"{OLLAMA_BASE_URL}/api/chat",
                json={
                    "model": MODEL_NAME,
                    "messages": messages,
                    "stream": False,
                    "options": {
                        "temperature": request.temperature,
                        "num_predict": request.max_tokens,
                    }
                }
            )
            response.raise_for_status()
            result = response.json()

    except httpx.ConnectError:
        raise HTTPException(
            status_code=503,
            detail="无法连接到 Ollama 服务,请确保 Ollama 正在运行"
        )

    raw_text = result.get("message", {}).get("content", "")
    thinking, answer = extract_thinking_and_answer(raw_text)
    duration = time.time() - start_time

    # 更新对话历史(只保存最终回答,不保存思维链,节省上下文空间)
    history.append({"role": "user", "content": request.message})
    history.append({"role": "assistant", "content": answer})

    # 限制历史长度(防止显存溢出)
    if len(history) > 20:
        history[:] = history[-20:]

    return ChatResponse(
        thinking=thinking,
        answer=answer,
        duration=duration,
        model=result.get("model", MODEL_NAME)
    )


@app.post("/api/chat/stream")
async def chat_stream(request: ChatRequest):
    """流式聊天接口(SSE)"""
    if request.session_id not in chat_histories:
        chat_histories[request.session_id] = []

    history = chat_histories[request.session_id]
    messages = history + [{"role": "user", "content": request.message}]

    async def generate() -> AsyncGenerator[str, None]:
        full_text = ""
        try:
            async with httpx.AsyncClient(timeout=300.0) as client:
                async with client.stream(
                    "POST",
                    f"{OLLAMA_BASE_URL}/api/chat",
                    json={
                        "model": MODEL_NAME,
                        "messages": messages,
                        "stream": True,
                        "options": {
                            "temperature": request.temperature,
                            "num_predict": request.max_tokens,
                        }
                    }
                ) as response:
                    response.raise_for_status()
                    async for line in response.aiter_lines():
                        if line.strip():
                            chunk = json.loads(line)
                            content = chunk.get("message", {}).get("content", "")
                            full_text += content
                            # 发送 SSE 格式数据
                            yield f"data: {json.dumps({'content': content})}\n\n"
        except Exception as e:
            yield f"data: {json.dumps({'error': str(e)})}\n\n"

        # 流结束后更新历史
        thinking, answer = extract_thinking_and_answer(full_text)
        history.append({"role": "user", "content": request.message})
        history.append({"role": "assistant", "content": answer})
        if len(history) > 20:
            history[:] = history[-20:]
        yield "data: [DONE]\n\n"

    return StreamingResponse(generate(), media_type="text/event-stream")


@app.get("/api/sessions/{session_id}/clear")
async def clear_session(session_id: str):
    """清空指定会话历史"""
    if session_id in chat_histories:
        del chat_histories[session_id]
    return {"status": "ok", "message": f"会话 {session_id} 已清空"}


if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

安装依赖:

pip install fastapi uvicorn httpx pydantic

启动服务:

python deepseek_r1_server.py

测试接口:

curl -X POST http://localhost:8000/api/chat \
  -H "Content-Type: application/json" \
  -d '{"message": "请证明根号2是无理数", "session_id": "test"}'

第四步:构建 Web 聊天界面

用一个简单的 HTML 页面即可实现思维链可视化的聊天界面:

"""
DeepSeek-R1 Web 聊天界面
直接运行后访问 http://localhost:8001
"""

from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI()

HTML_PAGE = """
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>DeepSeek-R1 深度思考助手</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: -apple-system, sans-serif; background: #f5f5f5; }
        .container { max-width: 900px; margin: 0 auto; padding: 20px; }
        h1 { text-align: center; color: #333; margin-bottom: 20px; font-size: 24px; }
        .chat-box {
            background: white; border-radius: 12px;
            box-shadow: 0 2px 12px rgba(0,0,0,0.1);
            height: 500px; overflow-y: auto; padding: 20px; margin-bottom: 20px;
        }
        .message { margin-bottom: 16px; }
        .user-msg {
            background: #1890ff; color: white; padding: 12px 16px;
            border-radius: 12px 12px 0 12px; max-width: 70%; margin-left: auto;
        }
        .thinking-box {
            background: #fffbe6; border-left: 3px solid #faad14;
            padding: 12px 16px; border-radius: 8px; margin-bottom: 8px;
            font-size: 13px; color: #666; cursor: pointer;
        }
        .thinking-box:hover { background: #fff7cc; }
        .thinking-content {
            max-height: 0; overflow: hidden; transition: max-height 0.3s;
            white-space: pre-wrap;
        }
        .thinking-box.expanded .thinking-content { max-height: 2000px; }
        .thinking-label { font-weight: bold; color: #faad14; }
        .answer-box {
            background: #f6f8fa; padding: 12px 16px;
            border-radius: 0 12px 12px 12px; max-width: 85%;
            white-space: pre-wrap; line-height: 1.6;
        }
        .input-area { display: flex; gap: 12px; }
        .input-area textarea {
            flex: 1; padding: 12px 16px; border: 2px solid #e8e8e8;
            border-radius: 12px; font-size: 14px; resize: none;
            height: 50px; outline: none; transition: border-color 0.3s;
        }
        .input-area textarea:focus { border-color: #1890ff; }
        .input-area button {
            background: #1890ff; color: white; border: none;
            padding: 12px 24px; border-radius: 12px; cursor: pointer;
            font-size: 14px; white-space: nowrap;
        }
        .input-area button:hover { background: #40a9ff; }
        .input-area button:disabled { background: #d9d9d9; cursor: not-allowed; }
        .meta { text-align: center; color: #999; font-size: 12px; margin-top: 8px; }
        .loading { display: inline-block; animation: spin 1s linear infinite; }
        @keyframes spin { to { transform: rotate(360deg); } }
    </style>
</head>
<body>
    <div class="container">
        <h1>DeepSeek-R1 深度思考助手</h1>
        <div class="chat-box" id="chatBox"></div>
        <div class="input-area">
            <textarea id="userInput" placeholder="输入你的问题..." onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();send()}"></textarea>
            <button id="sendBtn" onclick="send()">发送</button>
        </div>
        <div class="meta">本地运行 · 数据不离开你的电脑 · DeepSeek-R1</div>
    </div>
    <script>
        const chatBox = document.getElementById('chatBox');
        const userInput = document.getElementById('userInput');
        const sendBtn = document.getElementById('sendBtn');
        const API_URL = 'http://localhost:8000/api/chat';
        const sessionId = 'web_' + Date.now();

        async function send() {
            const message = userInput.value.trim();
            if (!message) return;
            userInput.value = '';
            sendBtn.disabled = true;

            // 显示用户消息
            addMessage('user', message);

            try {
                const resp = await fetch(API_URL, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ message, session_id: sessionId })
                });
                const data = await resp.json();

                // 显示思维链(可折叠)
                if (data.thinking) {
                    addThinking(data.thinking);
                }
                // 显示回答
                addMessage('assistant', data.answer, data.duration);
            } catch (e) {
                addMessage('error', '请求失败: ' + e.message);
            }
            sendBtn.disabled = false;
        }

        function addMessage(role, content, duration) {
            const div = document.createElement('div');
            div.className = 'message';
            if (role === 'user') {
                div.innerHTML = '<div class="user-msg">' + escapeHtml(content) + '</div>';
            } else if (role === 'error') {
                div.innerHTML = '<div style="color:red;padding:8px">' + escapeHtml(content) + '</div>';
            } else {
                div.innerHTML = '<div class="answer-box">' + escapeHtml(content) +
                    (duration ? '</div><div class="meta">耗时 ' + duration.toFixed(1) + 's</div>' : '</div>');
            }
            chatBox.appendChild(div);
            chatBox.scrollTop = chatBox.scrollHeight;
        }

        function addThinking(content) {
            const div = document.createElement('div');
            div.className = 'message';
            div.innerHTML =
                '<div class="thinking-box" onclick="this.classList.toggle(\'expanded\')">' +
                '<span class="thinking-label">💭 思考过程(点击展开)</span>' +
                '<div class="thinking-content">' + escapeHtml(content) + '</div></div>';
            chatBox.appendChild(div);
            chatBox.scrollTop = chatBox.scrollHeight;
        }

        function escapeHtml(text) {
            const d = document.createElement('div');
            d.textContent = text;
            return d.innerHTML;
        }
    </script>
</body>
</html>
"""

@app.get("/", response_class=HTMLResponse)
async def index():
    return HTML_PAGE

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8001)

第五步:测试与优化

性能测试脚本

"""
DeepSeek-R1 推理性能测试
"""

import time
import httpx
import asyncio

OLLAMA_URL = "http://localhost:11434"
MODEL = "deepseek-r1:7b"
TEST_QUESTIONS = [
    "1+1等于几?",
    "用Python写一个快速排序",
    "解释相对论的核心概念",
    "请证明:对于任意正整数n,n³ - n 一定能被6整除",
]

async def test_question(client: httpx.AsyncClient, question: str) -> dict:
    """测试单个问题"""
    start = time.time()
    response = await client.post(
        f"{OLLAMA_URL}/api/chat",
        json={"model": MODEL, "messages": [{"role": "user", "content": question}], "stream": False}
    )
    elapsed = time.time() - start
    result = response.json()
    content = result.get("message", {}).get("content", "")

    # 统计字数
    total_chars = len(content)
    # 提取思维链和回答
    think_start = content.find("<think >")
    think_end = content.find("</think >")
    think_chars = think_end - think_start - 7 if think_start != -1 and think_end != -1 else 0

    return {
        "question": question[:20] + "...",
        "total_chars": total_chars,
        "thinking_chars": think_chars,
        "time_seconds": round(elapsed, 2),
        "chars_per_sec": round(total_chars / elapsed, 1),
    }

async def run_benchmark():
    """运行完整测试"""
    print(f"DeepSeek-R1 推理性能测试")
    print(f"模型:{MODEL}")
    print("=" * 70)

    async with httpx.AsyncClient(timeout=300.0) as client:
        results = []
        for q in TEST_QUESTIONS:
            result = await test_question(client, q)
            results.append(result)
            print(f"问题:{result['question']}")
            print(f"  总字数:{result['total_chars']}  思考过程:{result['thinking_chars']}字")
            print(f"  耗时:{result['time_seconds']}s  速度:{result['chars_per_sec']} 字/秒")
            print()

        # 汇总
        avg_time = sum(r['time_seconds'] for r in results) / len(results)
        avg_speed = sum(r['chars_per_sec'] for r in results) / len(results)
        print("=" * 70)
        print(f"平均耗时:{avg_time:.2f}s  平均速度:{avg_speed:.1f} 字/秒")

if __name__ == "__main__":
    asyncio.run(run_benchmark())

性能优化技巧

优化手段 效果 操作方式
使用 GGUF Q4_K_M 量化 速度提升 2-3x,质量损失 < 3% 默认已量化
开启 GPU 加速 速度提升 5-10x Ollama 自动检测 GPU
调低 temperature 减少推理时间 设置 temperature=0.3
限制 num_predict 避免超长输出 设置 max_tokens=2048
使用 num_ctx 控制上下文 降低显存占用 根据需要设置
# 指定 GPU 和上下文长度运行
ollama run deepseek-r1:7b --num-gpu-layers 99 --num-ctx 2048

生产部署建议

如果你想把推理服务部署到云服务器上对外提供服务:

配置项 最低配置 推荐配置
CPU 4 核 8 核+
内存 16GB 32GB+
GPU NVIDIA T4 16GB NVIDIA A10 24GB
系统 Ubuntu 22.04 Ubuntu 22.04
模型 7B Q4_K_M 14B Q4_K_M
# 在云服务器上设置 Ollama 监听所有网卡
export OLLAMA_HOST=0.0.0.0:11434

# 设置模型并行(多 GPU)
ollama run deepseek-r1:14b --num-gpu 2

总结

DeepSeek-R1 让本地部署”会思考”的 AI 成为现实。相比依赖云端 API,本地部署有三个核心优势:

  1. 数据安全:敏感数据不离开你的服务器
  2. 零调用成本:一次部署,无限使用
  3. 思维透明:完整的推理过程可审计、可优化

从 Ollama 一键安装到 FastAPI 服务封装,再到 Web 界面搭建,整个过程不超过 30 分钟。如果你在云服务器方面有需求,欢迎联系我(腾讯云/阿里云/火山云都有优惠),帮你选到性价比最高的配置。


👤 作者简介

一枚在大中原腹地(河南)卖公有云的从业者,主营腾讯云/阿里云/火山云,曾踩坑无数,现专注AI大模型应用落地。关注公众号「公有云cloud」,围观AI前沿动态~

博客:y1cloud.cn

发表评论