Ollama 本地大模型部署完全教程

教程简介

零基础Ollama本地大模型部署完全教程,涵盖Ollama安装配置、模型管理、主流模型对比、API详解、OpenAI兼容接口、GPU加速优化、自定义模型导入、生产部署等核心技能,配有搭建本地AI编程助手实战项目,适合AI开发者和运维人员系统学习。

Ollama 本地大模型部署完全教程

第一章 Ollama 概述:为什么需要本地大模型

1.1 云端大模型的局限性

近年来,以 ChatGPT、Claude 为代表的云端大模型席卷了整个科技行业。然而,随着应用的深入,云端方案的局限性也日益凸显:

隐私与数据安全:企业内部的代码、文档、客户数据一旦发送到云端,就脱离了自身的安全边界。对于金融、医疗、政务等行业,数据合规是硬性要求,很多场景根本无法使用云端 API。

成本压力:按 Token 计费的模式在大规模应用时成本惊人。一个中等规模的开发团队,每月仅代码补全功能就可能产生数千美元的 API 费用。而本地部署的边际成本几乎为零。

网络依赖:云端服务需要稳定的网络连接,在离线环境、内网隔离、弱网场景下完全无法使用。对于需要 7×24 小时可用的生产系统,网络波动就是可用性风险。

延迟与吞吐:所有请求都需要经过网络传输,首 Token 延迟(TTFT)通常在 500ms-2s 之间。对于实时交互场景(如代码补全、实时翻译),这个延迟是不可接受的。

1.2 Ollama 是什么

Ollama 是一个开源的本地大模型运行框架,它的核心设计理念是"让大模型像 Docker 一样简单"。只需一行命令就能下载并运行一个大语言模型,无需复杂的环境配置。

Ollama 的核心优势:

  • 极简安装:一个二进制文件,开箱即用
  • 模型仓库:内置模型仓库,一键拉取主流开源模型
  • 硬件加速:自动检测并利用 GPU(CUDA/Metal/ROCm)
  • API 兼容:提供 OpenAI 兼容的 REST API
  • 跨平台:支持 Windows、macOS、Linux
  • Modelfile:类似 Dockerfile 的模型配置语法

1.3 本地部署的适用场景

场景 云端方案 本地方案
代码补全 高延迟,隐私风险 低延迟,数据不出内网
文档处理 大文档受 Token 限制 无 Token 限制
客服系统 按量付费成本高 一次部署长期使用
学术研究 API 调用受限 完全自主可控
敏感数据分析 合规风险 数据本地闭环

第二章 Ollama 安装与配置

2.1 系统要求

在安装之前,先确认硬件配置:

最低配置

  • CPU:4 核以上
  • 内存:8GB(运行 7B 模型)
  • 磁盘:20GB 可用空间
  • 操作系统:Windows 10+、macOS 12+、Ubuntu 20.04+

推荐配置

  • CPU:8 核以上
  • 内存:32GB(可运行 13B-70B 模型)
  • GPU:NVIDIA RTX 3060 12GB 以上
  • 磁盘:100GB SSD

GPU 显存与模型大小对应关系

显存 可运行的模型(量化后)
6GB 7B Q4
8GB 7B Q5/Q6
12GB 13B Q4
16GB 13B Q5/Q6
24GB 34B Q4
48GB 70B Q4

2.2 macOS 安装

macOS 是最简单的安装平台:

# 方式一:使用 Homebrew(推荐)
brew install ollama

# 方式二:下载安装包
# 访问 https://ollama.com/download/mac
# 下载 .dmg 文件,双击安装

# 验证安装
ollama --version

安装完成后,Ollama 会作为后台服务自动运行。macOS 上自动使用 Apple Silicon 的 Metal 加速。

2.3 Linux 安装

Linux 平台支持一键安装脚本:

# 一键安装(支持 Ubuntu、Debian、CentOS、Fedora 等)
curl -fsSL https://ollama.com/install.sh | sh

# 或者手动安装二进制
curl -L https://ollama.com/download/ollama-linux-amd64 -o /usr/local/bin/ollama
chmod +x /usr/local/bin/ollama

# 创建系统用户
useradd -r -s /bin/false -m -d /usr/share/ollama ollama

# 创建 systemd 服务文件
cat > /etc/systemd/system/ollama.service << 'EOF'
[Unit]
Description=Ollama Service
After=network-online.target

[Service]
ExecStart=/usr/local/bin/ollama serve
User=ollama
Group=ollama
Restart=always
RestartSec=3
Environment="HOME=/usr/share/ollama"
Environment="OLLAMA_HOST=0.0.0.0"

[Install]
WantedBy=default.target
EOF

# 启动服务
systemctl daemon-reload
systemctl enable ollama
systemctl start ollama

# 验证
ollama --version
systemctl status ollama

2.4 Windows 安装

Windows 用户下载官方安装包:

  1. 访问 https://ollama.com/download/windows
  2. 下载 OllamaSetup.exe
  3. 双击运行安装程序
  4. 安装完成后,打开 PowerShell 或 CMD 验证:
ollama --version

Windows 上 Ollama 会自动检测 NVIDIA GPU 并启用 CUDA 加速。

2.5 Docker 安装

对于服务器环境,Docker 是更推荐的方式:

# CPU 版本
docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama

# GPU 版本(需要 nvidia-container-toolkit)
docker run -d --gpus=all -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama

# 运行模型
docker exec -it ollama ollama run llama3

# 使用 Docker Compose
cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
  ollama:
    image: ollama/ollama
    container_name: ollama
    restart: unless-stopped
    ports:
      - "11434:11434"
    volumes:
      - ollama_data:/root/.ollama
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all
              capabilities: [gpu]

volumes:
  ollama_data:
EOF

docker-compose up -d

2.6 环境变量配置

Ollama 通过环境变量进行高级配置:

# 服务监听地址(默认 127.0.0.1:11434)
export OLLAMA_HOST=0.0.0.0:11434

# 模型存储路径
export OLLAMA_MODELS=/data/ollama/models

# GPU 层数(控制哪些层在 GPU 上运行)
export OLLAMA_NUM_GPU=33

# 并发请求数
export OLLAMA_MAX_QUEUE=512

# 模型空闲卸载时间(默认 5 分钟)
export OLLAMA_KEEP_ALIVE=10m

# 日志级别
export OLLAMA_DEBUG=1

# 在 Linux systemd 中配置
# 编辑 /etc/systemd/system/ollama.service
# 在 [Service] 段添加:
# Environment="OLLAMA_HOST=0.0.0.0"
# Environment="OLLAMA_MODELS=/data/ollama/models"

第三章 模型管理

3.1 模型下载与运行

Ollama 的模型管理类似 Docker 的镜像管理:

# 下载模型(不运行)
ollama pull llama3
ollama pull qwen2.5:7b
ollama pull deepseek-coder-v2:16b

# 运行模型(自动下载)
ollama run llama3

# 列出已下载的模型
ollama list

# 查看模型详情
ollama show llama3

# 删除模型
ollama rm llama3

# 复制模型(用于创建自定义版本)
ollama cp llama3 my-llama3

# 查看正在运行的模型
ollama ps

3.2 模型标签系统

Ollama 使用标签来区分同一模型的不同版本:

# 默认标签(通常是 latest)
ollama pull llama3

# 指定参数大小
ollama pull llama3:8b
ollama pull llama3:70b

# 指定量化级别
ollama pull llama3:8b-q4_0
ollama pull llama3:8b-q5_K_M
ollama pull llama3:8b-q8_0

# 指定特定版本
ollama pull llama3.1:8b
ollama pull qwen2.5:7b-instruct-q4_K_M

3.3 Modelfile:自定义模型配置

Modelfile 是 Ollama 的模型定义文件,类似 Dockerfile:

# 基础模型
FROM llama3:8b

# 系统提示词
SYSTEM """
你是一个专业的Python编程助手。你擅长编写清晰、高效、符合PEP8规范的Python代码。
在回答问题时,你会:
1. 先分析需求
2. 给出代码实现
3. 解释关键逻辑
4. 提供测试用例
"""

# 参数设置
PARAMETER temperature 0.7
PARAMETER top_p 0.9
PARAMETER top_k 40
PARAMETER num_ctx 4096
PARAMETER repeat_penalty 1.1
PARAMETER stop "<|eot_id|>"

# 对话模板
TEMPLATE """{{- if .System }}<|start_header_id|>system<|end_header_id|>

{{ .System }}<|eot_id|>
{{ end }}{{- if .Prompt }}<|start_header_id|>user<|end_header_id|>

{{ .Prompt }}<|eot_id|>
{{ end }}<|start_header_id|>assistant<|end_header_id|>

{{ .Response }}<|eot_id|>"""

# 添加文件(如知识库)
# FILE ./knowledge.txt

使用 Modelfile 创建自定义模型:

# 创建模型
ollama create python-assistant -f Modelfile

# 测试运行
ollama run python-assistant "写一个快速排序算法"

# 查看模型信息
ollama show python-assistant

3.4 常用模型推荐

# 通用对话
ollama pull llama3.1:8b          # Meta 最新开源,综合能力强
ollama pull qwen2.5:7b           # 阿里通义,中文优秀
ollama pull gemma2:9b            # Google 出品,推理能力强

# 代码生成
ollama pull deepseek-coder-v2:16b  # 代码能力顶尖
ollama pull codellama:13b          # Meta 代码专用
ollama pull qwen2.5-coder:7b      # 代码补全专用

# 数学推理
ollama pull deepseek-coder-v2:16b  # 数学推理能力强
ollama pull qwen2.5:7b-instruct   # 数学能力不错

# 轻量级(适合边缘设备)
ollama pull phi3:mini              # 3.8B,微软出品
ollama pull gemma2:2b              # 2B,Google 轻量版
ollama pull qwen2.5:3b             # 3B,中文轻量版

第四章 主流模型深度对比

4.1 Llama 3/3.1 系列

Meta 的 Llama 系列是目前最流行的开源大模型:

模型规模:8B、70B、405B

核心优势

  • 128K 上下文窗口(3.1 版本)
  • 多语言支持(8 种语言)
  • 工具调用能力
  • 丰富的社区生态

中文能力:★★★☆☆(一般,需要微调提升)

代码能力:★★★★☆(较强)

推理能力:★★★★☆(较强)

适用场景:英文为主的通用任务、需要长上下文的场景

4.2 Qwen 2.5 系列

阿里云通义千问的开源版本,中文能力最强的开源模型之一:

模型规模:0.5B、1.5B、3B、7B、14B、32B、72B

核心优势

  • 中文能力顶尖
  • 代码能力优秀(Qwen2.5-Coder 专用版本)
  • 数学推理强
  • 支持 29 种语言
  • 128K 上下文

中文能力:★★★★★(顶尖)

代码能力:★★★★★(顶尖,特别是 Coder 版本)

推理能力:★★★★☆(强)

适用场景:中文场景首选、代码生成、数学问题

4.3 Gemma 2 系列

Google 的开源模型,以高效著称:

模型规模:2B、9B、27B

核心优势

  • 同参数量下性能最优
  • 推理效率高
  • 支持多种精度
  • 安全性设计良好

中文能力:★★★☆☆(一般)

代码能力:★★★★☆(较强)

推理能力:★★★★★(顶尖)

适用场景:资源受限环境、需要高效推理的场景

4.4 Mistral/Mixtral 系列

法国 Mistral AI 的开源模型:

模型规模:7B、8x7B(MoE)、8x22B(MoE)

核心优势

  • MoE 架构效率高
  • 函数调用能力强
  • 多语言支持好
  • 32K 上下文

中文能力:★★★☆☆(一般)

代码能力:★★★★☆(较强)

推理能力:★★★★☆(较强)

适用场景:需要函数调用的场景、多语言任务

4.5 DeepSeek 系列

深度求索的开源模型,以代码和推理见长:

模型规模:7B、16B、67B(V2)、V3(MoE)

核心优势

  • 代码能力顶尖
  • 数学推理能力极强
  • 中文能力优秀
  • 128K 上下文
  • DeepSeek-Coder 专注代码

中文能力:★★★★☆(优秀)

代码能力:★★★★★(顶尖)

推理能力:★★★★★(顶尖)

适用场景:代码生成、数学推理、中文场景

4.6 模型选择决策树

需要模型?
├── 主要处理中文?
│   ├── 是 → Qwen2.5 系列
│   └── 否 → 继续判断
├── 主要写代码?
│   ├── 是 → DeepSeek-Coder-V2 或 Qwen2.5-Coder
│   └── 否 → 继续判断
├── 需要数学推理?
│   ├── 是 → DeepSeek 系列
│   └── 否 → 继续判断
├── 资源受限?
│   ├── 是 → Gemma2:2B 或 Phi3:mini
│   └── 否 → Llama3.1 或 Mistral
└── 不确定 → Qwen2.5:7b(综合最佳平衡)

第五章 Ollama API 详解

5.1 Generate API

Generate API 是最基础的文本生成接口:

# 基础调用
curl http://localhost:11434/api/generate -d '{
  "model": "llama3",
  "prompt": "为什么天空是蓝色的?",
  "stream": false
}'

# 流式调用
curl http://localhost:11434/api/generate -d '{
  "model": "llama3",
  "prompt": "写一首关于春天的诗",
  "stream": true
}'

# 带上下文的调用(多轮对话)
curl http://localhost:11434/api/generate -d '{
  "model": "llama3",
  "prompt": "继续上面的话题",
  "context": [1, 2, 3, 4, 5],
  "stream": false
}'

请求参数详解

{
  "model": "llama3",           // 模型名称
  "prompt": "你的问题",         // 输入提示
  "stream": true,              // 是否流式输出
  "system": "系统提示词",       // 系统角色设定
  "template": "自定义模板",     // 覆盖默认模板
  "context": [],               // 上下文 token 列表
  "options": {
    "temperature": 0.7,        // 温度(0-2)
    "top_p": 0.9,              // 核采样
    "top_k": 40,               // Top-K 采样
    "num_predict": 128,        // 最大生成 token 数
    "repeat_penalty": 1.1,     // 重复惩罚
    "seed": 42,                // 随机种子
    "num_ctx": 4096,           // 上下文窗口大小
    "num_gpu": 33,             // GPU 层数
    "num_thread": 8,           // CPU 线程数
    "stop": ["\n\n"]           // 停止词
  }
}

5.2 Chat API

Chat API 提供了类似 OpenAI 的对话接口:

# 基础对话
curl http://localhost:11434/api/chat -d '{
  "model": "llama3",
  "messages": [
    {
      "role": "system",
      "content": "你是一个友好的助手"
    },
    {
      "role": "user",
      "content": "你好,介绍一下你自己"
    }
  ],
  "stream": false
}'

# 多轮对话
curl http://localhost:11434/api/chat -d '{
  "model": "llama3",
  "messages": [
    {"role": "user", "content": "什么是机器学习?"},
    {"role": "assistant", "content": "机器学习是人工智能的一个分支..."},
    {"role": "user", "content": "能举个具体的例子吗?"}
  ],
  "stream": false
}'

# 带工具调用的对话
curl http://localhost:11434/api/chat -d '{
  "model": "llama3.1",
  "messages": [
    {"role": "user", "content": "北京今天天气怎么样?"}
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "获取指定城市的天气信息",
        "parameters": {
          "type": "object",
          "properties": {
            "city": {
              "type": "string",
              "description": "城市名称"
            }
          },
          "required": ["city"]
        }
      }
    }
  ],
  "stream": false
}'

5.3 Embeddings API

用于生成文本的向量表示,适用于 RAG、语义搜索等场景:

# 生成单个文本的 embedding
curl http://localhost:11434/api/embed -d '{
  "model": "nomic-embed-text",
  "input": "Ollama 是一个本地大模型运行框架"
}'

# 批量生成 embeddings
curl http://localhost:11434/api/embed -d '{
  "model": "nomic-embed-text",
  "input": [
    "第一段文本",
    "第二段文本",
    "第三段文本"
  ]
}'

Python 实现 RAG 检索

import requests
import numpy as np
from typing import List

class OllamaEmbeddings:
    def __init__(self, base_url: str = "http://localhost:11434", model: str = "nomic-embed-text"):
        self.base_url = base_url
        self.model = model
    
    def embed(self, texts: List[str]) -> np.ndarray:
        """批量生成文本向量"""
        response = requests.post(f"{self.base_url}/api/embed", json={
            "model": self.model,
            "input": texts
        })
        data = response.json()
        return np.array(data["embeddings"])
    
    def similarity(self, text1: str, text2: str) -> float:
        """计算两段文本的相似度"""
        embeddings = self.embed([text1, text2])
        return float(np.dot(embeddings[0], embeddings[1]) / 
                     (np.linalg.norm(embeddings[0]) * np.linalg.norm(embeddings[1])))

# 使用示例
embedder = OllamaEmbeddings()

# 构建知识库
documents = [
    "Ollama 是一个开源的本地大模型运行框架",
    "Llama3 是 Meta 发布的开源大语言模型",
    "Qwen2.5 是阿里云发布的中文大模型",
    "Python 是一种广泛使用的编程语言"
]

# 查询相似文档
query = "什么是 Ollama?"
query_embedding = embedder.embed([query])[0]
doc_embeddings = embedder.embed(documents)

# 计算相似度并排序
similarities = np.dot(doc_embeddings, query_embedding)
top_indices = np.argsort(similarities)[::-1][:2]

print(f"查询:{query}")
for idx in top_indices:
    print(f"  相似度 {similarities[idx]:.4f}:{documents[idx]}")

5.4 模型管理 API

# 列出本地模型
curl http://localhost:11434/api/tags

# 查看模型详情
curl http://localhost:11434/api/show -d '{"name": "llama3"}'

# 拉取模型
curl http://localhost:11434/api/pull -d '{"name": "llama3"}'

# 删除模型
curl http://localhost:11434/api/delete -d '{"name": "llama3"}'

# 复制模型
curl http://localhost:11434/api/copy -d '{"source": "llama3", "destination": "my-llama3"}'

# 创建模型(从 Modelfile)
curl http://localhost:11434/api/create -d '{
  "name": "my-model",
  "modelfile": "FROM llama3\nSYSTEM \"你是一个助手\""
}'

5.5 健康检查与版本

# 健康检查
curl http://localhost:11434/

# 获取版本
curl http://localhost:11434/api/version

# 获取运行中的模型信息
curl http://localhost:11434/api/ps

第六章 OpenAI 兼容接口

6.1 接口说明

Ollama 提供了与 OpenAI API 完全兼容的接口,这意味着你可以用 OpenAI 的 SDK 直接调用本地模型:

# Chat Completions 接口
curl http://localhost:11434/v1/chat/completions -d '{
  "model": "llama3",
  "messages": [
    {"role": "system", "content": "你是一个助手"},
    {"role": "user", "content": "你好"}
  ],
  "stream": false
}'

# Completions 接口
curl http://localhost:11434/v1/completions -d '{
  "model": "llama3",
  "prompt": "从前有座山",
  "max_tokens": 100
}'

# Embeddings 接口
curl http://localhost:11434/v1/embeddings -d '{
  "model": "nomic-embed-text",
  "input": "测试文本"
}'

# Models 接口
curl http://localhost:11434/v1/models

6.2 Python OpenAI SDK 集成

from openai import OpenAI

# 创建客户端,指向本地 Ollama
client = OpenAI(
    base_url="http://localhost:11434/v1",
    api_key="ollama"  # 随便填,Ollama 不验证
)

# Chat Completions
response = client.chat.completions.create(
    model="llama3",
    messages=[
        {"role": "system", "content": "你是一个Python编程专家"},
        {"role": "user", "content": "写一个装饰器实现重试功能"}
    ],
    temperature=0.7,
    max_tokens=1000
)
print(response.choices[0].message.content)

# 流式输出
stream = client.chat.completions.create(
    model="llama3",
    messages=[{"role": "user", "content": "讲个故事"}],
    stream=True
)
for chunk in stream:
    if chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="")

# Embeddings
embedding = client.embeddings.create(
    model="nomic-embed-text",
    input="测试文本"
)
print(f"向量维度:{len(embedding.data[0].embedding)}")

# 列出模型
models = client.models.list()
for model in models.data:
    print(model.id)

6.3 Node.js OpenAI SDK 集成

import OpenAI from 'openai';

const client = new OpenAI({
  baseURL: 'http://localhost:11434/v1',
  apiKey: 'ollama'
});

// Chat Completions
const response = await client.chat.completions.create({
  model: 'llama3',
  messages: [
    { role: 'system', content: '你是一个助手' },
    { role: 'user', content: '用JavaScript写一个防抖函数' }
  ],
  temperature: 0.7
});
console.log(response.choices[0].message.content);

// 流式输出
const stream = await client.chat.completions.create({
  model: 'llama3',
  messages: [{ role: 'user', content: '解释什么是闭包' }],
  stream: true
});

for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content || '');
}

6.4 LangChain 集成

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

# 创建 LLM 实例
llm = ChatOpenAI(
    base_url="http://localhost:11434/v1",
    api_key="ollama",
    model="qwen2.5:7b",
    temperature=0.7
)

# 创建提示模板
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个{role}专家,请用中文回答问题"),
    ("user", "{question}")
])

# 创建处理链
chain = prompt | llm | StrOutputParser()

# 运行
result = chain.invoke({
    "role": "Python",
    "question": "如何优化数据库查询性能?"
})
print(result)

第七章 GPU 加速配置

7.1 NVIDIA CUDA 配置

NVIDIA GPU 是最常用的加速方案:

# 1. 安装 NVIDIA 驱动
# Ubuntu
sudo apt update
sudo apt install nvidia-driver-535
sudo reboot

# 验证驱动
nvidia-smi

# 2. 安装 CUDA Toolkit(如果使用 Docker 方式则不需要)
# 访问 https://developer.nvidia.com/cuda-downloads
# 下载并安装对应版本

# 3. 验证 Ollama GPU 使用
ollama run llama3 "你好"
# 启动时会显示 GPU 信息

# 4. 查看 GPU 使用情况
nvidia-smi -l 1

# 5. 控制 GPU 使用
# 只使用 GPU
export OLLAMA_NUM_GPU=33

# GPU + CPU 混合
export OLLAMA_NUM_GPU=20  # 20层在GPU,其余在CPU

# 只使用 CPU
export OLLAMA_NUM_GPU=0

CUDA 常见问题排查

# 检查 CUDA 是否可用
nvidia-smi

# 检查 Ollama 是否识别到 GPU
ollama ps

# 查看 Ollama 日志
journalctl -u ollama -f

# 手动指定 GPU(多卡环境)
CUDA_VISIBLE_DEVICES=0 ollama serve  # 使用第一块 GPU
CUDA_VISIBLE_DEVICES=1 ollama serve  # 使用第二块 GPU
CUDA_VISIBLE_DEVICES=0,1 ollama serve  # 使用所有 GPU

7.2 Apple Silicon Metal 配置

macOS 上的 Apple Silicon 芯片使用 Metal 加速:

# Metal 是自动启用的,无需额外配置
# M1/M2/M3 芯片的统一内存架构使得 GPU 可以访问全部内存

# 验证 Metal 是否启用
ollama run llama3 "你好"
# 日志中会显示 "using Metal"

# Apple Silicon 内存与模型对应关系
# M1 (8GB)  → 7B Q4 模型
# M1 Pro (16GB) → 13B Q4 模型
# M2 (8GB) → 7B Q4 模型
# M2 Pro (16GB) → 13B Q4 模型
# M2 Max (32GB) → 34B Q4 模型
# M3 (8GB) → 7B Q4 模型
# M3 Pro (18GB) → 13B Q5 模型
# M3 Max (36GB) → 34B Q4 模型

7.3 AMD ROCm 配置

AMD GPU 用户需要安装 ROCm:

# 1. 安装 ROCm
# 参考 https://rocm.docs.amd.com/

# Ubuntu
sudo apt install rocm-dkms

# 2. 设置用户组
sudo usermod -a -G video,render $USER

# 3. 配置环境变量
export ROCM_PATH=/opt/rocm
export PATH=$ROCM_PATH/bin:$PATH

# 4. 验证
rocm-smi

# 5. Ollama 会自动检测 ROCm GPU
# 支持的 GPU:RX 6000 系列、RX 7000 系列、Instinct 系列

7.4 性能优化技巧

# 1. 使用量化模型减少显存占用
ollama pull llama3:8b-q4_K_M  # Q4_K_M 是性价比最高的量化

# 2. 调整上下文窗口
# 默认 2048,按需调整
export OLLAMA_NUM_CTX=4096

# 3. 并发请求优化
export OLLAMA_MAX_QUEUE=512
export OLLAMA_NUM_PARALLEL=4

# 4. 预加载模型(避免首次请求延迟)
ollama run llama3 "" --keepalive 24h

# 5. 使用 mmap 优化大模型加载
export OLLAMA_MMAP=1

# 6. 调整线程数(CPU 推理时)
export OLLAMA_NUM_THREAD=8

第八章 自定义模型导入

8.1 GGUF 格式简介

GGUF(GPT-Generated Unified Format)是目前最流行的模型量化格式:

  • 支持多种量化精度(Q2_K 到 Q8_0)
  • 自包含元数据,无需额外配置文件
  • 支持 mmap 加载,启动速度快
  • 兼容多种推理框架

8.2 从 Hugging Face 导入

# 方式一:直接从 Hugging Face 拉取已量化的 GGUF 模型
# Ollama 支持直接导入 safetensors 格式
ollama pull hf.co/bartowski/Llama-3.1-8B-Instruct-GGUF:Q4_K_M

# 方式二:使用 Modelfile 导入本地 GGUF 文件
# 1. 下载 GGUF 文件
# 从 Hugging Face 下载 .gguf 文件

# 2. 创建 Modelfile
cat > Modelfile << 'EOF'
FROM ./Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf

TEMPLATE """{{- if .System }}<|start_header_id|>system<|end_header_id|>

{{ .System }}<|eot_id|>
{{ end }}{{- if .Prompt }}<|start_header_id|>user<|end_header_id|>

{{ .Prompt }}<|eot_id|>
{{ end }}<|start_header_id|>assistant<|end_header_id|>

{{ .Response }}<|eot_id|>"""

PARAMETER stop "<|eot_id|>"
PARAMETER num_ctx 8192
EOF

# 3. 创建模型
ollama create my-llama3 -f Modelfile

# 4. 测试
ollama run my-llama3 "你好"

8.3 从 PyTorch 模型转换

# 1. 安装转换工具
pip install llama-cpp-python
pip install transformers

# 2. 下载原始模型
from transformers import AutoModelForCausalLM, AutoTokenizer

model_name = "your-model-name"
model = AutoModelForCausalLM.from_pretrained(model_name)
tokenizer = AutoTokenizer.from_pretrained(model_name)

# 3. 转换为 GGUF
# 使用 llama.cpp 的转换脚本
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp

# 转换为 GGUF
python convert_hf_to_gguf.py /path/to/model --outfile model.gguf

# 4. 量化
./llama-quantize model.gguf model-q4_K_M.gguf Q4_K_M

# 5. 导入 Ollama
cat > Modelfile << 'EOF'
FROM ./model-q4_K_M.gguf
EOF
ollama create my-model -f Modelfile

8.4 量化精度选择指南

量化类型 大小比例 质量损失 推荐场景
Q2_K ~30% 明显 极端资源受限
Q3_K_S ~35% 较大 资源受限
Q3_K_M ~40% 中等 轻度资源受限
Q4_0 ~45% 较小 一般用途
Q4_K_M ~50% 很小 推荐默认
Q5_K_M ~60% 极小 质量优先
Q6_K ~70% 几乎无 高质量需求
Q8_0 ~100% 最高质量

推荐:大多数场景使用 Q4_K_M,它在大小和质量之间取得了最佳平衡。


第九章 生产部署

9.1 Nginx 反向代理

# /etc/nginx/conf.d/ollama.conf
upstream ollama_backend {
    # 单机部署
    server 127.0.0.1:11434;
    
    # 多机部署(负载均衡)
    # server 192.168.1.101:11434;
    # server 192.168.1.102:11434;
    # server 192.168.1.103:11434;
}

server {
    listen 80;
    server_name ollama.example.com;

    # HTTP 重定向到 HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name ollama.example.com;

    ssl_certificate /etc/nginx/ssl/ollama.crt;
    ssl_certificate_key /etc/nginx/ssl/ollama.key;

    # 请求大小限制(模型上传需要)
    client_max_body_size 10G;

    # 超长超时(大模型推理需要时间)
    proxy_read_timeout 600s;
    proxy_send_timeout 600s;
    proxy_connect_timeout 60s;

    # 基础认证(可选)
    # auth_basic "Ollama API";
    # auth_basic_user_file /etc/nginx/.htpasswd;

    location / {
        proxy_pass http://ollama_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        # WebSocket 支持(流式输出需要)
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        
        # 缓冲设置
        proxy_buffering off;
        proxy_cache off;
    }

    # 健康检查端点
    location /health {
        proxy_pass http://ollama_backend/;
        access_log off;
    }
}

9.2 Docker Compose 生产部署

# docker-compose.prod.yml
version: '3.8'

services:
  ollama:
    image: ollama/ollama
    container_name: ollama
    restart: unless-stopped
    ports:
      - "11434:11434"
    volumes:
      - ollama_data:/root/.ollama
    environment:
      - OLLAMA_HOST=0.0.0.0
      - OLLAMA_KEEP_ALIVE=1h
      - OLLAMA_MAX_QUEUE=512
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all
              capabilities: [gpu]
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:11434/"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s

  nginx:
    image: nginx:alpine
    container_name: ollama-nginx
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./ssl:/etc/nginx/ssl:ro
    depends_on:
      ollama:
        condition: service_healthy

  monitoring:
    image: prom/prometheus
    container_name: ollama-prometheus
    restart: unless-stopped
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro

volumes:
  ollama_data:

9.3 监控与告警

# monitor.py - Ollama 监控脚本
import requests
import time
import json
from datetime import datetime

class OllamaMonitor:
    def __init__(self, base_url: str = "http://localhost:11434"):
        self.base_url = base_url
    
    def check_health(self) -> dict:
        """健康检查"""
        try:
            start = time.time()
            response = requests.get(f"{self.base_url}/", timeout=5)
            latency = time.time() - start
            return {
                "status": "healthy" if response.status_code == 200 else "unhealthy",
                "latency_ms": round(latency * 1000, 2),
                "timestamp": datetime.now().isoformat()
            }
        except Exception as e:
            return {
                "status": "unhealthy",
                "error": str(e),
                "timestamp": datetime.now().isoformat()
            }
    
    def get_running_models(self) -> list:
        """获取运行中的模型"""
        try:
            response = requests.get(f"{self.base_url}/api/ps")
            data = response.json()
            return data.get("models", [])
        except Exception as e:
            return []
    
    def get_model_list(self) -> list:
        """获取所有模型"""
        try:
            response = requests.get(f"{self.base_url}/api/tags")
            data = response.json()
            return data.get("models", [])
        except Exception as e:
            return []
    
    def test_inference(self, model: str = "llama3", prompt: str = "hi") -> dict:
        """测试推理延迟"""
        try:
            start = time.time()
            response = requests.post(f"{self.base_url}/api/generate", json={
                "model": model,
                "prompt": prompt,
                "stream": False
            }, timeout=60)
            latency = time.time() - start
            data = response.json()
            return {
                "model": model,
                "latency_seconds": round(latency, 2),
                "eval_count": data.get("eval_count", 0),
                "eval_duration_ms": data.get("eval_duration", 0) / 1e6,
                "tokens_per_second": round(
                    data.get("eval_count", 0) / (data.get("eval_duration", 1) / 1e9), 2
                ),
                "timestamp": datetime.now().isoformat()
            }
        except Exception as e:
            return {"error": str(e)}
    
    def generate_report(self) -> str:
        """生成监控报告"""
        health = self.check_health()
        running = self.get_running_models()
        models = self.get_model_list()
        
        report = f"""
=== Ollama 监控报告 ===
时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}

健康状态:{health['status']}
响应延迟:{health.get('latency_ms', 'N/A')}ms

已下载模型数量:{len(models)}
运行中模型数量:{len(running)}

运行中的模型:
"""
        for model in running:
            report += f"  - {model['name']} (到期时间:{model.get('expires_at', 'N/A')})\n"
        
        return report

# 使用示例
if __name__ == "__main__":
    monitor = OllamaMonitor()
    print(monitor.generate_report())
    
    # 性能测试
    result = monitor.test_inference("llama3", "写一个 hello world")
    print(f"\n推理性能:{json.dumps(result, indent=2, ensure_ascii=False)}")

9.4 负载均衡策略

# load_balancer.py - 简单的负载均衡器
import random
import time
from typing import List
import requests

class OllamaLoadBalancer:
    def __init__(self, servers: List[str]):
        self.servers = servers
        self.health_status = {s: True for s in servers}
        self.request_count = {s: 0 for s in servers}
    
    def health_check(self):
        """定期健康检查"""
        for server in self.servers:
            try:
                response = requests.get(f"{server}/", timeout=5)
                self.health_status[server] = response.status_code == 200
            except:
                self.health_status[server] = False
    
    def get_healthy_servers(self) -> List[str]:
        return [s for s in self.servers if self.health_status[s]]
    
    def select_server(self, strategy: str = "round_robin") -> str:
        """选择服务器"""
        healthy = self.get_healthy_servers()
        if not healthy:
            raise Exception("没有可用的服务器")
        
        if strategy == "round_robin":
            # 轮询
            server = min(healthy, key=lambda s: self.request_count[s])
        elif strategy == "random":
            # 随机
            server = random.choice(healthy)
        elif strategy == "least_connections":
            # 最少连接
            server = min(healthy, key=lambda s: self.request_count[s])
        else:
            server = healthy[0]
        
        self.request_count[server] += 1
        return server
    
    def chat(self, model: str, messages: list, **kwargs) -> dict:
        """负载均衡的聊天请求"""
        server = self.select_server()
        response = requests.post(f"{server}/v1/chat/completions", json={
            "model": model,
            "messages": messages,
            **kwargs
        })
        return response.json()

# 使用示例
lb = OllamaLoadBalancer([
    "http://192.168.1.101:11434",
    "http://192.168.1.102:11434",
    "http://192.168.1.103:11434",
])

# 定期健康检查
import threading
def periodic_health_check():
    while True:
        lb.health_check()
        time.sleep(30)

threading.Thread(target=periodic_health_check, daemon=True).start()

# 发送请求
result = lb.chat("llama3", [{"role": "user", "content": "你好"}])
print(result)

第十章 实战项目:搭建本地 AI 编程助手

10.1 项目概述

我们将搭建一个功能完整的本地 AI 编程助手,具备以下能力:

  • 代码生成与补全
  • 代码解释与重构
  • Bug 查找与修复
  • 代码审查
  • 技术问答
  • 项目文档生成

10.2 技术架构

┌─────────────────────────────────────────┐
│           前端界面 (Web UI)              │
│   React/Vue + Monaco Editor             │
└─────────────┬───────────────────────────┘
              │ HTTP/WebSocket
┌─────────────▼───────────────────────────┐
│           后端服务 (FastAPI)              │
│   - 会话管理                            │
│   - 提示词工程                          │
│   - 上下文管理                          │
└─────────────┬───────────────────────────┘
              │ API
┌─────────────▼───────────────────────────┐
│         Ollama 本地模型                  │
│   - DeepSeek-Coder (代码)               │
│   - Qwen2.5 (中文对话)                  │
└─────────────────────────────────────────┘

10.3 后端实现

# main.py - AI 编程助手后端
from fastapi import FastAPI, WebSocket, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import List, Optional
import httpx
import json
import asyncio

app = FastAPI(title="本地 AI 编程助手")

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_methods=["*"],
    allow_headers=["*"],
)

OLLAMA_BASE = "http://localhost:11434"

# 提示词模板
PROMPTS = {
    "code_generate": """你是一个专业的编程助手。请根据用户的需求生成代码。
要求:
1. 代码清晰、高效、符合最佳实践
2. 添加必要的注释
3. 如果涉及错误处理,请包含异常处理
4. 给出代码的使用示例

编程语言:{language}
需求:{prompt}""",

    "code_explain": """请详细解释以下代码:
1. 代码的整体功能
2. 关键逻辑的解释
3. 可能的改进点

代码:
```{language}
{code}
```""",

    "code_review": """请审查以下代码,从以下角度给出建议:
1. 代码质量
2. 性能优化
3. 安全性
4. 可维护性
5. 潜在的 bug

代码:
```{language}
{code}
```""",

    "bug_fix": """以下代码存在问题,请找出 bug 并修复:
1. 描述问题所在
2. 解释为什么这是 bug
3. 给出修复后的代码

代码:
```{language}
{code}

错误信息:""",

"doc_generate": """请为以下代码生成文档:
  1. 函数/类的用途说明
  2. 参数说明
  3. 返回值说明
  4. 使用示例
  5. 注意事项

代码:

{code}
```"""
}

class ChatRequest(BaseModel):
    message: str
    mode: str = "chat"  # chat, code_generate, code_explain, code_review, bug_fix, doc_generate
    language: str = "python"
    code: Optional[str] = None
    error: Optional[str] = None
    model: str = "deepseek-coder-v2:16b"
    history: List[dict] = []

class ChatResponse(BaseModel):
    response: str
    model: str
    mode: str

async def stream_ollama(model: str, prompt: str):
    """流式调用 Ollama"""
    async with httpx.AsyncClient(timeout=300) as client:
        async with client.stream(
            "POST",
            f"{OLLAMA_BASE}/api/generate",
            json={"model": model, "prompt": prompt, "stream": True}
        ) as response:
            async for line in response.aiter_lines():
                if line:
                    data = json.loads(line)
                    if "response" in data:
                        yield data["response"]
                    if data.get("done"):
                        break

@app.post("/api/chat", response_model=ChatResponse)
async def chat(request: ChatRequest):
    """聊天接口"""
    if request.mode == "chat":
        # 普通对话
        prompt = request.message
    elif request.mode in PROMPTS:
        # 特定模式
        prompt = PROMPTS[request.mode].format(
            language=request.language,
            prompt=request.message,
            code=request.code or "",
            error=request.error or ""
        )
    else:
        raise HTTPException(status_code=400, detail=f"未知模式:{request.mode}")
    
    # 构建完整提示词(包含历史)
    full_prompt = ""
    for h in request.history[-5:]:  # 保留最近5轮
        role = "用户" if h["role"] == "user" else "助手"
        full_prompt += f"{role}:{h['content']}\n"
    full_prompt += f"用户:{prompt}\n助手:"
    
    # 调用模型
    response_text = ""
    async for chunk in stream_ollama(request.model, full_prompt):
        response_text += chunk
    
    return ChatResponse(
        response=response_text,
        model=request.model,
        mode=request.mode
    )

@app.websocket("/ws/chat")
async def websocket_chat(websocket: WebSocket):
    """WebSocket 流式聊天"""
    await websocket.accept()
    
    try:
        while True:
            data = await websocket.receive_json()
            model = data.get("model", "deepseek-coder-v2:16b")
            prompt = data.get("message", "")
            mode = data.get("mode", "chat")
            language = data.get("language", "python")
            code = data.get("code", "")
            error = data.get("error", "")
            
            if mode in PROMPTS:
                full_prompt = PROMPTS[mode].format(
                    language=language,
                    prompt=prompt,
                    code=code,
                    error=error
                )
            else:
                full_prompt = prompt
            
            # 流式返回
            async for chunk in stream_ollama(model, full_prompt):
                await websocket.send_json({"type": "chunk", "content": chunk})
            
            await websocket.send_json({"type": "done"})
    except Exception as e:
        await websocket.send_json({"type": "error", "message": str(e)})

@app.get("/api/models")
async def list_models():
    """列出可用模型"""
    async with httpx.AsyncClient() as client:
        response = await client.get(f"{OLLAMA_BASE}/api/tags")
        return response.json()

@app.get("/api/health")
async def health():
    """健康检查"""
    try:
        async with httpx.AsyncClient() as client:
            response = await client.get(f"{OLLAMA_BASE}/")
            return {"status": "healthy", "ollama": response.status_code == 200}
    except:
        return {"status": "unhealthy", "ollama": False}

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

10.4 前端界面

<!-- index.html - AI 编程助手前端 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>本地 AI 编程助手</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #1a1a2e; color: #eee; height: 100vh; display: flex; }
        
        .sidebar {
            width: 280px; background: #16213e; padding: 20px; display: flex; flex-direction: column;
            border-right: 1px solid #0f3460;
        }
        .sidebar h2 { color: #e94560; margin-bottom: 20px; font-size: 1.3em; }
        
        .mode-btn {
            padding: 12px 16px; margin: 4px 0; background: #0f3460; border: none;
            color: #eee; border-radius: 8px; cursor: pointer; text-align: left;
            transition: all 0.2s;
        }
        .mode-btn:hover, .mode-btn.active { background: #e94560; }
        .mode-btn .icon { margin-right: 8px; }
        
        .model-select {
            padding: 10px; background: #0f3460; border: 1px solid #533483;
            color: #eee; border-radius: 8px; margin: 10px 0; width: 100%;
        }
        
        .main { flex: 1; display: flex; flex-direction: column; }
        
        .header {
            padding: 16px 24px; background: #16213e; border-bottom: 1px solid #0f3460;
            display: flex; justify-content: space-between; align-items: center;
        }
        .header h1 { font-size: 1.2em; color: #e94560; }
        .status { display: flex; align-items: center; gap: 8px; }
        .status-dot { width: 8px; height: 8px; border-radius: 50%; background: #4ade80; }
        
        .chat-area {
            flex: 1; overflow-y: auto; padding: 24px;
            display: flex; flex-direction: column; gap: 16px;
        }
        
        .message {
            max-width: 80%; padding: 16px; border-radius: 12px;
            line-height: 1.6; font-size: 0.95em;
        }
        .message.user {
            align-self: flex-end; background: #e94560; color: white;
        }
        .message.assistant {
            align-self: flex-start; background: #16213e; border: 1px solid #0f3460;
        }
        .message pre {
            background: #0d1117; padding: 12px; border-radius: 8px;
            overflow-x: auto; margin: 8px 0;
        }
        .message code {
            font-family: 'Fira Code', 'Consolas', monospace; font-size: 0.9em;
        }
        
        .input-area {
            padding: 16px 24px; background: #16213e; border-top: 1px solid #0f3460;
        }
        .input-row { display: flex; gap: 12px; }
        .input-row textarea {
            flex: 1; padding: 12px; background: #0f3460; border: 1px solid #533483;
            color: #eee; border-radius: 8px; resize: none; font-size: 0.95em;
            font-family: inherit;
        }
        .input-row textarea:focus { outline: none; border-color: #e94560; }
        
        .send-btn {
            padding: 12px 24px; background: #e94560; border: none; color: white;
            border-radius: 8px; cursor: pointer; font-size: 0.95em; font-weight: 600;
            transition: background 0.2s;
        }
        .send-btn:hover { background: #c73650; }
        .send-btn:disabled { background: #666; cursor: not-allowed; }
        
        .code-input {
            margin-bottom: 12px; display: none;
        }
        .code-input textarea {
            width: 100%; height: 120px; padding: 12px; background: #0f3460;
            border: 1px solid #533483; color: #eee; border-radius: 8px;
            font-family: 'Fira Code', monospace; font-size: 0.9em; resize: vertical;
        }
    </style>
</head>
<body>
    <div class="sidebar">
        <h2>🤖 AI 编程助手</h2>
        
        <button class="mode-btn active" data-mode="chat">
            <span class="icon">💬</span> 智能对话
        </button>
        <button class="mode-btn" data-mode="code_generate">
            <span class="icon">⚡</span> 代码生成
        </button>
        <button class="mode-btn" data-mode="code_explain">
            <span class="icon">📖</span> 代码解释
        </button>
        <button class="mode-btn" data-mode="code_review">
            <span class="icon">🔍</span> 代码审查
        </button>
        <button class="mode-btn" data-mode="bug_fix">
            <span class="icon">🐛</span> Bug 修复
        </button>
        <button class="mode-btn" data-mode="doc_generate">
            <span class="icon">📝</span> 文档生成
        </button>
        
        <select class="model-select" id="modelSelect">
            <option value="deepseek-coder-v2:16b">DeepSeek Coder V2 (16B)</option>
            <option value="qwen2.5-coder:7b">Qwen2.5 Coder (7B)</option>
            <option value="llama3.1:8b">Llama 3.1 (8B)</option>
        </select>
        
        <div style="margin-top: auto; padding-top: 20px; border-top: 1px solid #0f3460;">
            <p style="font-size: 0.85em; color: #888;">本地运行 · 数据不出内网</p>
            <p style="font-size: 0.85em; color: #888; margin-top: 4px;">模型:Ollama</p>
        </div>
    </div>
    
    <div class="main">
        <div class="header">
            <h1 id="modeTitle">💬 智能对话</h1>
            <div class="status">
                <div class="status-dot" id="statusDot"></div>
                <span id="statusText">已连接</span>
            </div>
        </div>
        
        <div class="chat-area" id="chatArea">
            <div class="message assistant">
                你好!我是你的本地 AI 编程助手。我可以帮你:
                <ul style="margin-top: 8px; padding-left: 20px;">
                    <li>生成代码</li>
                    <li>解释代码逻辑</li>
                    <li>审查代码质量</li>
                    <li>修复 Bug</li>
                    <li>生成文档</li>
                </ul>
                <br>请选择左侧的功能模式,或者直接开始对话!
            </div>
        </div>
        
        <div class="input-area">
            <div class="code-input" id="codeInput">
                <textarea id="codeTextarea" placeholder="在此粘贴代码..."></textarea>
            </div>
            <div class="input-row">
                <textarea id="messageInput" rows="2" placeholder="输入你的问题..."></textarea>
                <button class="send-btn" id="sendBtn">发送</button>
            </div>
        </div>
    </div>

    <script>
        let currentMode = 'chat';
        let chatHistory = [];
        
        const modeTitles = {
            'chat': '💬 智能对话',
            'code_generate': '⚡ 代码生成',
            'code_explain': '📖 代码解释',
            'code_review': '🔍 代码审查',
            'bug_fix': '🐛 Bug 修复',
            'doc_generate': '📝 文档生成'
        };
        
        const modesNeedingCode = ['code_explain', 'code_review', 'bug_fix', 'doc_generate'];
        
        // 模式切换
        document.querySelectorAll('.mode-btn').forEach(btn => {
            btn.addEventListener('click', () => {
                document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active'));
                btn.classList.add('active');
                currentMode = btn.dataset.mode;
                document.getElementById('modeTitle').textContent = modeTitles[currentMode];
                document.getElementById('codeInput').style.display = 
                    modesNeedingCode.includes(currentMode) ? 'block' : 'none';
            });
        });
        
        // 发送消息
        async function sendMessage() {
            const input = document.getElementById('messageInput');
            const message = input.value.trim();
            if (!message) return;
            
            const code = document.getElementById('codeTextarea').value;
            
            // 添加用户消息
            addMessage('user', message);
            input.value = '';
            
            // 构建请求
            const requestData = {
                message,
                mode: currentMode,
                model: document.getElementById('modelSelect').value,
                language: 'python',
                code: code || null,
                history: chatHistory.slice(-10)
            };
            
            // 禁用发送按钮
            const sendBtn = document.getElementById('sendBtn');
            sendBtn.disabled = true;
            sendBtn.textContent = '思考中...';
            
            try {
                const response = await fetch('/api/chat', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify(requestData)
                });
                
                const data = await response.json();
                addMessage('assistant', data.response);
                
                chatHistory.push(
                    { role: 'user', content: message },
                    { role: 'assistant', content: data.response }
                );
            } catch (error) {
                addMessage('assistant', `错误:${error.message}`);
            } finally {
                sendBtn.disabled = false;
                sendBtn.textContent = '发送';
            }
        }
        
        function addMessage(role, content) {
            const chatArea = document.getElementById('chatArea');
            const div = document.createElement('div');
            div.className = `message ${role}`;
            
            // 简单的 Markdown 渲染
            let html = content
                .replace(/```(\w*)\n([\s\S]*?)```/g, '<pre><code class="language-$1">$2</code></pre>')
                .replace(/`([^`]+)`/g, '<code>$1</code>')
                .replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
                .replace(/\n/g, '<br>');
            
            div.innerHTML = html;
            chatArea.appendChild(div);
            chatArea.scrollTop = chatArea.scrollHeight;
            
            // 高亮代码
            div.querySelectorAll('pre code').forEach(block => {
                hljs.highlightElement(block);
            });
        }
        
        // 事件绑定
        document.getElementById('sendBtn').addEventListener('click', sendMessage);
        document.getElementById('messageInput').addEventListener('keydown', (e) => {
            if (e.key === 'Enter' && !e.shiftKey) {
                e.preventDefault();
                sendMessage();
            }
        });
    </script>
</body>
</html>

10.5 启动与测试

# 1. 安装依赖
pip install fastapi uvicorn httpx

# 2. 确保 Ollama 运行中
ollama serve

# 3. 下载模型
ollama pull deepseek-coder-v2:16b
ollama pull qwen2.5-coder:7b

# 4. 启动后端
python main.py

# 5. 访问前端
# 打开浏览器访问 http://localhost:8000

10.6 进阶功能

# extensions.py - 扩展功能

# 1. 项目上下文感知
class ProjectContext:
    def __init__(self, project_path: str):
        self.project_path = project_path
        self.file_cache = {}
    
    def scan_project(self):
        """扫描项目结构"""
        import os
        structure = []
        for root, dirs, files in os.walk(self.project_path):
            # 跳过常见的非源码目录
            dirs[:] = [d for d in dirs if d not in ['node_modules', '.git', '__pycache__', 'venv']]
            level = root.replace(self.project_path, '').count(os.sep)
            indent = ' ' * 2 * level
            structure.append(f"{indent}{os.path.basename(root)}/")
            file_indent = ' ' * 2 * (level + 1)
            for file in files:
                if file.endswith(('.py', '.js', '.ts', '.java', '.go', '.rs')):
                    structure.append(f"{file_indent}{file}")
        return '\n'.join(structure[:100])  # 限制行数
    
    def read_file(self, file_path: str) -> str:
        """读取文件内容"""
        full_path = os.path.join(self.project_path, file_path)
        if full_path not in self.file_cache:
            with open(full_path, 'r', encoding='utf-8') as f:
                self.file_cache[full_path] = f.read()
        return self.file_cache[full_path]

# 2. 代码片段提取
def extract_code_blocks(text: str) -> list:
    """从模型输出中提取代码块"""
    import re
    pattern = r'```(\w*)\n(.*?)```'
    matches = re.findall(pattern, text, re.DOTALL)
    return [(lang, code.strip()) for lang, code in matches]

# 3. Git 集成
class GitHelper:
    def __init__(self, repo_path: str):
        self.repo_path = repo_path
    
    def get_diff(self) -> str:
        """获取当前修改"""
        import subprocess
        result = subprocess.run(
            ['git', 'diff'],
            cwd=self.repo_path,
            capture_output=True,
            text=True
        )
        return result.stdout
    
    def get_recent_commits(self, n: int = 10) -> str:
        """获取最近的提交"""
        import subprocess
        result = subprocess.run(
            ['git', 'log', f'-{n}', '--oneline'],
            cwd=self.repo_path,
            capture_output=True,
            text=True
        )
        return result.stdout

第十一章 常见问题与最佳实践

11.1 常见问题解答

Q1:模型下载太慢怎么办?

# 使用代理
export HTTPS_PROXY=http://your-proxy:port
ollama pull llama3

# 或使用国内镜像(如果有)
export OLLAMA_REGISTRY=https://mirror.example.com

Q2:内存不足怎么办?

# 使用更小的量化版本
ollama pull llama3:8b-q4_0  # 比 Q4_K_M 更小

# 或使用更小的模型
ollama pull phi3:mini  # 只需要 2GB 内存

# 减少上下文窗口
export OLLAMA_NUM_CTX=2048

Q3:如何提高推理速度?

# 1. 使用 GPU
nvidia-smi  # 确认 GPU 可用

# 2. 使用量化模型
ollama pull llama3:8b-q4_K_M

# 3. 减少上下文
export OLLAMA_NUM_CTX=2048

# 4. 增加 GPU 层数
export OLLAMA_NUM_GPU=33

Q4:如何让 Ollama 开机自启?

# Linux (systemd)
sudo systemctl enable ollama

# macOS
# Ollama 安装后自动配置了 LaunchAgent

11.2 最佳实践

  1. 选择合适的模型:根据任务选择模型,不要一味追求大模型
  2. 合理设置参数:温度、top_p 等参数会影响输出质量
  3. 使用系统提示词:好的系统提示词能显著提升输出质量
  4. 管理上下文窗口:过长的上下文会降低质量,定期清理
  5. 定期更新模型:开源模型更新很快,定期拉取新版本
  6. 监控资源使用:关注 GPU 显存和系统内存使用情况
  7. 备份重要配置:Modelfile 和自定义配置要版本管理
  8. 安全考虑:生产环境要配置认证和 HTTPS

11.3 安全加固

# 1. 限制监听地址
export OLLAMA_HOST=127.0.0.1  # 只监听本地

# 2. 使用 Nginx 添加认证
# 见第九章 Nginx 配置

# 3. 防火墙规则
sudo ufw allow from 192.168.1.0/24 to any port 11434

# 4. 使用 API Key(通过 Nginx)
# 在 Nginx 中添加 header 验证

总结

本教程从零开始,完整介绍了 Ollama 本地大模型部署的方方面面:

  1. 基础概念:理解了为什么需要本地部署,以及 Ollama 的核心优势
  2. 安装配置:掌握了全平台的安装方法和环境变量配置
  3. 模型管理:学会了下载、运行、自定义 Modelfile 等操作
  4. 模型对比:了解了主流开源模型的特点和适用场景
  5. API 使用:深入掌握了 Generate、Chat、Embeddings 等 API
  6. OpenAI 兼容:实现了与现有 OpenAI 生态的无缝集成
  7. GPU 加速:配置了 CUDA、Metal、ROCm 等加速方案
  8. 模型导入:学会了 GGUF 格式转换和量化优化
  9. 生产部署:实现了 Nginx 代理、负载均衡、监控告警
  10. 实战项目:搭建了完整的本地 AI 编程助手

本地大模型部署不仅是技术能力的体现,更是数据安全和成本控制的重要手段。随着开源模型的不断进步,本地部署将成为越来越多企业和开发者的首选方案。

希望本教程能帮助你顺利开启本地大模型之旅!

内容声明

本文内容为AI技术学习教程,仅供学习参考。如涉及技术问题,欢迎通过 xurj005@163.com 与我们交流。

目录