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 用户下载官方安装包:
- 访问 https://ollama.com/download/windows
- 下载
OllamaSetup.exe - 双击运行安装程序
- 安装完成后,打开 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": """请为以下代码生成文档:
- 函数/类的用途说明
- 参数说明
- 返回值说明
- 使用示例
- 注意事项
代码:
{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 最佳实践
- 选择合适的模型:根据任务选择模型,不要一味追求大模型
- 合理设置参数:温度、top_p 等参数会影响输出质量
- 使用系统提示词:好的系统提示词能显著提升输出质量
- 管理上下文窗口:过长的上下文会降低质量,定期清理
- 定期更新模型:开源模型更新很快,定期拉取新版本
- 监控资源使用:关注 GPU 显存和系统内存使用情况
- 备份重要配置:Modelfile 和自定义配置要版本管理
- 安全考虑:生产环境要配置认证和 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 本地大模型部署的方方面面:
- 基础概念:理解了为什么需要本地部署,以及 Ollama 的核心优势
- 安装配置:掌握了全平台的安装方法和环境变量配置
- 模型管理:学会了下载、运行、自定义 Modelfile 等操作
- 模型对比:了解了主流开源模型的特点和适用场景
- API 使用:深入掌握了 Generate、Chat、Embeddings 等 API
- OpenAI 兼容:实现了与现有 OpenAI 生态的无缝集成
- GPU 加速:配置了 CUDA、Metal、ROCm 等加速方案
- 模型导入:学会了 GGUF 格式转换和量化优化
- 生产部署:实现了 Nginx 代理、负载均衡、监控告警
- 实战项目:搭建了完整的本地 AI 编程助手
本地大模型部署不仅是技术能力的体现,更是数据安全和成本控制的重要手段。随着开源模型的不断进步,本地部署将成为越来越多企业和开发者的首选方案。
希望本教程能帮助你顺利开启本地大模型之旅!