返回AI笔记
AI Day 51

AI Day 51: 实战(1):本地大模型部署全流程 — 从安装到API服务

AI Day 51: 实战(1):本地大模型部署全流程 — 从安装到API服务

2026-05-22

日期: 2026-05-22 | 阶段: 第五阶段 · 动手实战 (Day 51-60) | 主题: Local LLM Deployment

学习路径 / Learning Path

AI/LLM 深度技术学习 60天计划
├── 第一阶段:模型基础 (Day 1-15) ✅
│   ├── Day 1: Transformer与LLM基础 ✅
│   ├── Day 2: 量化与本地部署 ✅
│   ├── Day 3: 训练全流程 ✅
│   ├── Day 4: Prompt Engineering ✅
│   ├── Day 5: RAG架构 ✅
│   ├── Day 6: 向量数据库与Embedding ✅
│   ├── Day 7: 微调技术 ✅
│   ├── Day 8: 推理优化 ✅
│   ├── Day 9: 长上下文技术 ✅
│   ├── Day 10: 多模态模型 ✅
│   ├── Day 11: 推理模型 ✅
│   ├── Day 12: Agent框架 ✅
│   ├── Day 13: MCP协议 ✅
│   ├── Day 14: 模型评估 ✅
│   └── Day 15: 阶段一总结 ✅
├── 第二阶段:工程实践 (Day 16-30) ✅
│   ├── Day 16: LLM应用架构 ✅
│   ├── Day 17: 安全与护栏 ✅
│   ├── Day 18: 可观测性 ✅
│   ├── Day 19: 生产RAG·解析与分块 ✅
│   ├── Day 20: 生产RAG·检索与重排 ✅
│   ├── Day 21: 生产RAG·评估与迭代 ✅
│   ├── Day 22: Agent状态与恢复 ✅
│   ├── Day 23: Agent成本优化 ✅
│   ├── Day 24: 多Agent系统 ✅
│   ├── Day 25: Agent测试部署 ✅
│   ├── Day 26: LLM成本工程 ✅
│   ├── Day 27: 多模型编排 ✅
│   ├── Day 28: LLM应用测试 ✅
│   ├── Day 29: 企业LLM平台 ✅
│   └── Day 30: 阶段二总结 ✅
├── 第三阶段:金融零售AI应用 (Day 31-42) ✅
│   ├── Day 31: 金融AI风控 ✅
│   ├── Day 32: 智能投顾与量化 ✅
│   ├── Day 33: 合规与RegTech ✅
│   ├── Day 34: 信贷AI全链路 ✅
│   ├── Day 35: 金融AI总结 ✅
│   ├── Day 36: 零售AI推荐 ✅
│   ├── Day 37: 智能客服 ✅
│   ├── Day 38: 供应链AI ✅
│   ├── Day 39: 智能营销 ✅
│   ├── Day 40: 零售AI总结 ✅
│   ├── Day 41: CeFi×DeFi×AI融合 ✅
│   └── Day 42: AI融合案例与职业 ✅
├── 第四阶段:面试冲刺 (Day 43-50) ✅
│   ├── Day 43: 系统设计·LLM平台 ✅
│   ├── Day 44: 系统设计·RAG系统 ✅
│   ├── Day 45: 系统设计·Agent系统 ✅
│   ├── Day 46: 系统设计·推荐系统 ✅
│   ├── Day 47: 面试·产品AI ✅
│   ├── Day 48: 面试·架构AI ✅
│   ├── Day 49: 面试·行为AI ✅
│   └── Day 50: 学习总结 ✅
└── 第五阶段:动手实战 (Day 51-60)
    ├── Day 51: 本地大模型部署全流程 ← 你在这里
    ├── Day 52: RAG系统实战:从文档到问答
    ├── Day 53: RAG进阶:评估优化与生产化
    ├── Day 54: LoRA微调实战:训练你的专属模型
    ├── Day 55: Agent开发实战:构建工具调用Agent
    ├── Day 56: MCP Server开发:扩展AI能力边界
    ├── Day 57: 多模态应用:图文理解与文档分析
    ├── Day 58: AI应用全栈开发:前后端集成
    ├── Day 59: 性能调优与成本实战
    └── Day 60: 总结与作品集

核心概念 / Core Concepts

动手 = 最好的学习 / Hands-on = Best Learning

前50天的学习路径回顾:
Day 1-15:  理论基础 — 知道 "是什么"
Day 16-30: 工程实践 — 理解 "怎么做"
Day 31-42: 领域应用 — 明白 "做什么"
Day 43-50: 面试冲刺 — 能够 "讲出来"

Day 51-60: 动手实战 — 真正 "做出来" ← 现在开始!

为什么在最后才动手?

常见错误路径:
  Day 1 就开始装 Ollama → 玩两天觉得好酷 → 不知道下一步做什么 → 放弃

正确路径(我们走的):
  50天理论+工程+应用 → 带着明确目标动手 → 每一步都知道为什么

区别在于:
  - 你现在知道 Q4_K_M 量化意味着什么(Day 2 学的)
  - 你现在知道 KV Cache 为什么占显存(Day 8 学的)
  - 你现在知道 Embedding 和向量检索的原理(Day 6 学的)
  - 你现在知道 Prompt Engineering 的最佳实践(Day 4 学的)

理论50天 → 验证时刻 / 50 Days Theory → Verification Time

今天开始的10天,是对前50天学习的终极验证:

验证清单:
├── Day 51: 量化理论 → 实际部署不同量化模型,感受差异
├── Day 52: RAG架构 → 用自己的笔记构建真实RAG系统
├── Day 53: 评估方法 → 用RAGAS量化评估RAG效果
├── Day 54: 微调理论 → 实际LoRA微调一个模型
├── Day 55: Agent架构 → 构建可工作的Tool-Calling Agent
├── Day 56: MCP协议 → 开发真实的MCP Server
├── Day 57: 多模态理论 → 实际处理图片和文档
├── Day 58: 应用架构 → 前后端集成完整应用
├── Day 59: 优化理论 → 实测性能瓶颈并优化
└── Day 60: 全部整合 → 形成可展示的作品集

今天的核心目标

输入:一台 Windows 电脑(8GB+ 显存的 NVIDIA GPU)
输出:一个可通过 API 调用的本地 LLM 服务

具体目标:
1. Ollama 安装并运行第一个模型
2. 对比 4 个不同模型的表现
3. 搭建 OpenAI 兼容的 API 服务
4. 了解 llama.cpp 底层运行
5. 掌握显存管理技巧

知识点1:Ollama 安装与使用 / Ollama Installation & Usage

为什么选择 Ollama?/ Why Ollama?

本地LLM部署工具对比:

工具          | 易用性 | 性能   | 灵活性 | 生态
-------------|--------|--------|--------|--------
Ollama       | ★★★★★ | ★★★★  | ★★★   | ★★★★★  ← 首选
llama.cpp    | ★★    | ★★★★★ | ★★★★★ | ★★★★
vLLM         | ★★★   | ★★★★★ | ★★★★  | ★★★★
LocalAI      | ★★★   | ★★★★  | ★★★★  | ★★★
LM Studio    | ★★★★★ | ★★★★  | ★★    | ★★★

Ollama 胜出原因:
- 一行命令启动模型
- 内置 OpenAI 兼容 API
- 底层就是 llama.cpp(性能有保障)
- 生态丰富(Open WebUI / Dify / LangChain 都支持)

Windows 安装步骤 / Installation on Windows

# 方法1:官方安装包(推荐)
# 1. 访问 https://ollama.ai/download
# 2. 下载 Windows 安装包
# 3. 双击安装,一路 Next
# 4. 安装完成后自动启动服务

# 方法2:winget 安装
winget install Ollama.Ollama

# 方法3:如果你用 WSL2
curl -fsSL https://ollama.ai/install.sh | sh

安装验证

# 打开命令行/终端
ollama --version
# 输出类似: ollama version is 0.6.x

# 检查服务是否运行
curl http://localhost:11434
# 输出: Ollama is running

Windows 特别注意

常见问题与解决:

1. GPU 未被识别
   原因: NVIDIA 驱动版本太老
   解决: 更新到最新 Game Ready / Studio Driver
   验证: nvidia-smi 能正常显示 GPU 信息

2. 端口被占用
   原因: 其他服务占了 11434
   解决: 设置环境变量 OLLAMA_HOST=0.0.0.0:11435

3. 模型下载慢
   原因: 默认走国外 CDN
   解决: 设置 OLLAMA_MODELS 路径到大容量磁盘
         考虑使用代理加速下载

4. 显存不足
   原因: 模型太大 / 其他程序占用显存
   解决: 关闭浏览器GPU加速,先试小模型

常用命令 / Common Commands

# === 模型管理 ===

# 拉取模型(下载到本地)
ollama pull qwen2.5:7b-instruct-q4_K_M
ollama pull llama3.3:8b-instruct-q4_K_M

# 运行模型(自动下载 + 启动交互对话)
ollama run qwen2.5:7b

# 列出已下载的模型
ollama list

# 显示模型详情
ollama show qwen2.5:7b

# 删除模型
ollama rm qwen2.5:7b

# === 服务管理 ===

# 启动 Ollama 服务(Windows 通常自动启动)
ollama serve

# === 模型运行 ===

# 交互式对话
ollama run qwen2.5:7b
>>> 你好,介绍一下你自己

# 单次调用(管道输入)
echo "解释什么是Transformer" | ollama run qwen2.5:7b

# 从文件输入
ollama run qwen2.5:7b < prompt.txt

# 设置系统提示
ollama run qwen2.5:7b --system "你是一个金融分析师"

# === 复制和自定义 ===

# 基于已有模型创建副本
ollama cp qwen2.5:7b my-finance-model

模型库浏览 / Model Library

Ollama 模型库 (https://ollama.ai/library) 核心模型:

通用对话:
├── qwen2.5:7b          — 阿里通义,中文最强 ★★★★★
├── llama3.3:8b          — Meta,英文底座最强 ★★★★★
├── phi-4-mini:3.8b      — 微软,小而精 ★★★★
├── gemma2:9b            — Google,均衡 ★★★★
├── mistral:7b           — Mistral AI,欧洲血统 ★★★★
└── deepseek-r1:7b       — DeepSeek,推理强 ★★★★

代码专用:
├── qwen2.5-coder:7b     — 代码能力突出
├── codellama:7b         — Meta代码模型
└── deepseek-coder-v2:lite — DeepSeek代码

Embedding:
├── nomic-embed-text     — 通用文本Embedding
├── bge-m3               — 多语言Embedding
└── mxbai-embed-large    — 高质量Embedding

多模态(带视觉):
├── llava:7b             — 基础视觉对话
├── llama3.2-vision:11b  — Meta视觉模型
└── minicpm-v:8b         — 面壁智能视觉

模型命名规则:
  模型名:参数量-变体-量化
  例如: qwen2.5:7b-instruct-q4_K_M
  - 7b = 70亿参数
  - instruct = 指令微调版
  - q4_K_M = 4bit量化(K-quant Medium)

自定义 Modelfile / Custom Modelfile

# 文件名: Modelfile.finance-analyst
# 用途: 创建一个金融分析师模型

# 基础模型
FROM qwen2.5:7b-instruct-q4_K_M

# 系统提示
SYSTEM """你是一位资深金融分析师,专注于:
1. DeFi协议分析和评估
2. 传统金融与Web3的桥接
3. 风控和合规问题
4. 数据驱动的投资分析

回答时请:
- 用中文回答,关键术语附英文原文
- 提供数据支撑的分析
- 考虑风险因素
- 给出可执行的建议"""

# 模型参数
PARAMETER temperature 0.3
PARAMETER top_p 0.9
PARAMETER top_k 40
PARAMETER num_ctx 4096
PARAMETER repeat_penalty 1.1
PARAMETER stop "<|im_end|>"

# 对话模板(Qwen格式)
TEMPLATE """{{ if .System }}<|im_start|>system
{{ .System }}<|im_end|>
{{ end }}{{ if .Prompt }}<|im_start|>user
{{ .Prompt }}<|im_end|>
{{ end }}<|im_start|>assistant
{{ .Response }}<|im_end|>
"""
# 创建自定义模型
ollama create finance-analyst -f Modelfile.finance-analyst

# 运行自定义模型
ollama run finance-analyst
>>> 分析Aave V3的利率模型设计

# 验证模型信息
ollama show finance-analyst

知识点2:第一个本地模型 / Your First Local Model

为什么选 Qwen2.5-7B 作为第一个模型?

1. 中文能力最强 — 我们很多笔记是中英混合的
2. 7B 参数适中 — 8GB 显存刚好能跑
3. Q4 量化成熟 — 质量损失可接受
4. Instruct 版本 — 开箱即用,无需额外微调

Day 2 学的量化知识现在用上了:
- FP16: 7B × 2 bytes = 14GB → 显存不够
- Q8:   7B × 1 byte  = 7GB  → 勉强能跑
- Q4:   7B × 0.5 byte = 3.5GB → 舒适运行 ✓
- KV Cache 额外需要约 0.5-1GB
- 总计约 4-5GB 显存 → 8GB 显卡完全OK

下载与运行 / Download and Run

# Step 1: 下载模型(约 4.4GB)
ollama pull qwen2.5:7b-instruct-q4_K_M

# 下载过程观察:
# pulling manifest
# pulling 8de95da68dc4... 100% ▕████████████████▏ 4.4 GB
# pulling 62fbfd9ed093... 100% ▕████████████████▏  182 B
# pulling c156170b718e... 100% ▕████████████████▏  11 KB
# verifying sha256 digest
# writing manifest
# success

# Step 2: 运行模型
ollama run qwen2.5:7b-instruct-q4_K_M

# Step 3: 开始对话测试
>>> 你好!请用一段话介绍什么是DeFi

第一次对话测试 / First Conversation Tests

测试1: 基础中文对话
>>> 用简洁的语言解释什么是智能合约

预期观察:
- 首次加载需要几秒(模型加载到显存)
- 后续对话秒级响应
- 中文流畅度如何?

---

测试2: 英文能力
>>> Explain the difference between Optimistic Rollup and ZK Rollup in 3 sentences

预期观察:
- 英文是否流畅?
- 技术概念是否准确?

---

测试3: 代码能力
>>> 写一个Python函数,用aiohttp异步请求Ethereum JSON-RPC获取最新区块号

预期观察:
- 代码是否可运行?
- 是否处理了异常?
- 代码风格如何?

---

测试4: 推理能力
>>> 一个DeFi协议TVL从10亿跌到1亿,可能的原因有哪些?按可能性排序分析

预期观察:
- 是否有逻辑性?
- 分析是否全面?
- 是否有深度?

观察关键指标 / Key Metrics to Watch

# 在另一个终端监控 GPU
nvidia-smi -l 1   # 每秒刷新

# 关注这些指标:
GPU-Util:     生成时应该 80-100%
Memory-Usage: qwen2.5:7b Q4 约占 4.5-5GB
Temperature:  长时间运行注意温度
Power:        功耗变化
性能观察记录表:

指标               | 你的观测值 | 参考值
------------------|-----------|--------
模型加载时间       | ___ 秒    | 3-8秒
首Token延迟(TTFT) | ___ 秒    | 0.5-2秒
生成速度(tokens/s) | ___ t/s   | 20-40 t/s (Q4, RTX 3060/4060)
显存占用           | ___ GB    | 4.5-5.5GB
GPU利用率(生成时)  | ___ %     | 80-100%

Day 8 学过的指标,现在亲眼验证:
- TTFT (Time to First Token) — 用户感知的等待时间
- TPS (Tokens per Second) — 阅读体验的关键
- 这两个指标直接决定用户体验!

知识点3:模型对比实验 / Model Comparison Experiment

实验设计 / Experiment Design

目的:用同一套测试问题,对比不同模型的表现
方法:控制变量(同一台机器、同样的问题、同样的参数)

测试模型(4个):
1. Qwen2.5-7B-Instruct (Q4_K_M)     — 阿里,中文标杆
2. Llama3.3-8B-Instruct (Q4_K_M)     — Meta,英文标杆
3. Phi-4-mini (3.8B, Q4_K_M)         — 微软,小模型代表
4. Gemma2-9B-Instruct (Q4_K_M)       — Google,均衡选手
# 下载所有测试模型
ollama pull qwen2.5:7b-instruct-q4_K_M
ollama pull llama3.3:8b-instruct-q4_K_M
ollama pull phi4-mini:3.8b-q4_K_M
ollama pull gemma2:9b-instruct-q4_K_M

# 确认全部下载完成
ollama list

测试问题集 / Test Questions

准备 5 类问题,每类 1 题:

Q1 [中文理解]:
  "解释DeFi中的'无常损失'概念,用一个具体数字例子说明"

Q2 [英文理解]:
  "Compare the pros and cons of Account Abstraction (ERC-4337)
   vs native smart contract wallets"

Q3 [代码生成]:
  "Write a Python function that calculates the impermanent loss
   given initial and current price ratio. Include docstring and
   type hints."

Q4 [逻辑推理]:
  "一个DEX协议想要设计代币激励来吸引流动性提供者。
   但过高的代币奖励会导致抛压。请设计一个平衡方案,
   并分析其博弈均衡。"

Q5 [信息提取]:
  "从以下审计报告摘要中提取所有HIGH和CRITICAL风险项:
   [此处粘贴一段审计报告文本]"

对比执行脚本 / Comparison Script

"""
model_comparison.py
对比多个本地模型的表现
"""
import requests
import time
import json

OLLAMA_URL = "http://localhost:11434/api/generate"

MODELS = [
    "qwen2.5:7b-instruct-q4_K_M",
    "llama3.3:8b-instruct-q4_K_M",
    "phi4-mini:3.8b-q4_K_M",
    "gemma2:9b-instruct-q4_K_M",
]

QUESTIONS = [
    {
        "category": "中文理解",
        "prompt": "解释DeFi中的'无常损失'概念,用一个具体数字例子说明",
    },
    {
        "category": "English",
        "prompt": "Compare Account Abstraction (ERC-4337) vs native smart contract wallets in 5 bullet points",
    },
    {
        "category": "代码生成",
        "prompt": "Write a Python function that calculates impermanent loss given initial and current price ratio. Include docstring and type hints.",
    },
    {
        "category": "逻辑推理",
        "prompt": "一个DEX协议想设计代币激励吸引LP,但过高奖励会导致抛压。请设计一个平衡方案并分析博弈均衡。",
    },
]

def test_model(model: str, prompt: str) -> dict:
    """测试单个模型,返回响应和性能指标"""
    start_time = time.time()

    response = requests.post(OLLAMA_URL, json={
        "model": model,
        "prompt": prompt,
        "stream": False,
        "options": {
            "temperature": 0.7,
            "num_ctx": 2048,
        }
    })

    total_time = time.time() - start_time
    result = response.json()

    return {
        "model": model,
        "response": result.get("response", ""),
        "total_time": round(total_time, 2),
        "eval_count": result.get("eval_count", 0),
        "eval_duration_ns": result.get("eval_duration", 0),
        "tokens_per_second": round(
            result.get("eval_count", 0) /
            (result.get("eval_duration", 1) / 1e9), 1
        ) if result.get("eval_duration") else 0,
    }

def run_comparison():
    """运行完整对比测试"""
    results = []

    for q in QUESTIONS:
        print(f"\n{'='*60}")
        print(f"测试类别: {q['category']}")
        print(f"{'='*60}")

        for model in MODELS:
            print(f"\n  测试模型: {model}...")
            result = test_model(model, q["prompt"])
            result["category"] = q["category"]
            results.append(result)

            print(f"  耗时: {result['total_time']}s")
            print(f"  速度: {result['tokens_per_second']} tokens/s")
            print(f"  回答前100字: {result['response'][:100]}...")

    # 保存结果
    with open("comparison_results.json", "w", encoding="utf-8") as f:
        json.dump(results, f, ensure_ascii=False, indent=2)

    print("\n\n结果已保存到 comparison_results.json")
    return results

if __name__ == "__main__":
    run_comparison()

对比结果模板 / Comparison Results Template

模型对比结果表(请填入你的实测数据):

┌─────────────────┬──────────┬──────────┬──────────┬──────────┐
│ 指标            │ Qwen2.5  │ Llama3.3 │ Phi-4    │ Gemma2   │
│                 │ 7B       │ 8B       │ 3.8B     │ 9B       │
├─────────────────┼──────────┼──────────┼──────────┼──────────┤
│ 模型大小(GB)    │ ~4.4     │ ~4.9     │ ~2.2     │ ~5.4     │
│ 显存占用(GB)    │ ___      │ ___      │ ___      │ ___      │
│ 加载时间(s)     │ ___      │ ___      │ ___      │ ___      │
│ 生成速度(t/s)   │ ___      │ ___      │ ___      │ ___      │
├─────────────────┼──────────┼──────────┼──────────┼──────────┤
│ 中文质量 (1-5)  │ ___      │ ___      │ ___      │ ___      │
│ 英文质量 (1-5)  │ ___      │ ___      │ ___      │ ___      │
│ 代码质量 (1-5)  │ ___      │ ___      │ ___      │ ___      │
│ 推理能力 (1-5)  │ ___      │ ___      │ ___      │ ___      │
├─────────────────┼──────────┼──────────┼──────────┼──────────┤
│ 综合评分 (1-5)  │ ___      │ ___      │ ___      │ ___      │
│ 推荐场景        │ 中文对话 │ 英文任务 │ 轻量推理 │ 通用任务 │
└─────────────────┴──────────┴──────────┴──────────┴──────────┘

参考评分标准:
5 = 接近GPT-4水平
4 = 完全可用,偶有小问题
3 = 基本可用,需要优化prompt
2 = 勉强可用,频繁出错
1 = 不可用

预期结论(需要你的实测验证):
- 中文场景: Qwen2.5 >> 其他(中文训练数据多)
- 英文场景: Llama3.3 ≈ Gemma2 > Qwen2.5 > Phi-4
- 代码场景: Qwen2.5 ≈ Llama3.3 > Phi-4 > Gemma2
- 推理场景: Gemma2 ≈ Qwen2.5 > Llama3.3 > Phi-4
- 速度: Phi-4 >> 其他(参数量只有一半)

知识点4:API 服务搭建 / API Service Setup

Ollama REST API / Ollama REST API

Ollama 自带 REST API,启动服务后自动可用:

API 端点一览:
┌──────────────────────────────────────────────────────────┐
│ POST /api/generate     — 文本生成(单轮)               │
│ POST /api/chat         — 对话生成(多轮)               │
│ POST /api/embeddings   — 生成Embedding                  │
│ GET  /api/tags         — 列出已下载模型                 │
│ POST /api/show         — 显示模型信息                   │
│ POST /api/pull         — 拉取模型                       │
│ DELETE /api/delete     — 删除模型                       │
└──────────────────────────────────────────────────────────┘

OpenAI 兼容格式 / OpenAI-Compatible Format

Ollama 提供 OpenAI 兼容的 API:

端点: http://localhost:11434/v1/chat/completions
格式: 与 OpenAI API 完全一致

这意味着:
- 任何支持 OpenAI API 的库/工具都能直接使用
- LangChain / LlamaIndex / Dify 无缝对接
- 切换本地↔云端只需改一个 URL
# 使用 curl 测试 OpenAI 兼容 API
curl http://localhost:11434/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen2.5:7b-instruct-q4_K_M",
    "messages": [
      {"role": "system", "content": "你是一个Web3产品分析师"},
      {"role": "user", "content": "分析Uniswap V3的集中流动性设计"}
    ],
    "temperature": 0.7,
    "max_tokens": 500
  }'

Python 调用示例 / Python Examples

"""
ollama_api_demo.py
演示 Ollama API 的各种调用方式
"""

# === 方式1: 使用 requests 直接调用 ===

import requests
import json

def chat_basic(prompt: str, model: str = "qwen2.5:7b") -> str:
    """基础对话调用"""
    response = requests.post(
        "http://localhost:11434/api/generate",
        json={
            "model": model,
            "prompt": prompt,
            "stream": False,
            "options": {
                "temperature": 0.7,
                "num_ctx": 4096,
            }
        }
    )
    return response.json()["response"]

# 使用
result = chat_basic("什么是闪电贷攻击?")
print(result)


# === 方式2: 使用 OpenAI SDK(推荐) ===

from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:11434/v1",
    api_key="ollama",  # Ollama 不需要真实 key
)

def chat_openai_style(
    messages: list[dict],
    model: str = "qwen2.5:7b",
    temperature: float = 0.7,
) -> str:
    """OpenAI 兼容方式调用"""
    response = client.chat.completions.create(
        model=model,
        messages=messages,
        temperature=temperature,
        max_tokens=1000,
    )
    return response.choices[0].message.content

# 使用
messages = [
    {"role": "system", "content": "你是一个DeFi协议分析师"},
    {"role": "user", "content": "评估Aave V3的风险参数设计"},
]
result = chat_openai_style(messages)
print(result)


# === 方式3: 使用 ollama Python 库 ===

import ollama

def chat_ollama_lib(prompt: str) -> str:
    """使用 ollama 官方 Python 库"""
    response = ollama.chat(
        model="qwen2.5:7b",
        messages=[
            {"role": "user", "content": prompt},
        ]
    )
    return response["message"]["content"]

# 使用
result = chat_ollama_lib("解释AMM的x*y=k公式")
print(result)


# === 方式4: 生成 Embedding ===

def get_embedding(text: str) -> list[float]:
    """获取文本的Embedding向量"""
    response = requests.post(
        "http://localhost:11434/api/embeddings",
        json={
            "model": "nomic-embed-text",
            "prompt": text,
        }
    )
    return response.json()["embedding"]

# 使用
embedding = get_embedding("DeFi lending protocol risk assessment")
print(f"Embedding维度: {len(embedding)}")  # 768 for nomic-embed-text

流式响应处理 / Streaming Response

"""
streaming_demo.py
流式响应处理 — 实现打字机效果
"""

import requests
import json
import sys

def stream_chat(prompt: str, model: str = "qwen2.5:7b"):
    """流式对话 — 逐 Token 输出"""
    response = requests.post(
        "http://localhost:11434/api/generate",
        json={
            "model": model,
            "prompt": prompt,
            "stream": True,  # 关键:开启流式
        },
        stream=True,  # requests 也要开启流式
    )

    full_response = ""
    for line in response.iter_lines():
        if line:
            data = json.loads(line)
            token = data.get("response", "")
            full_response += token

            # 实时打印(打字机效果)
            sys.stdout.write(token)
            sys.stdout.flush()

            # 检查是否结束
            if data.get("done", False):
                print()  # 换行
                # 打印性能统计
                print(f"\n--- 性能统计 ---")
                print(f"总Token数: {data.get('eval_count', 'N/A')}")
                eval_ns = data.get('eval_duration', 0)
                if eval_ns:
                    tps = data['eval_count'] / (eval_ns / 1e9)
                    print(f"生成速度: {tps:.1f} tokens/s")
                break

    return full_response


# === OpenAI SDK 流式版本 ===

from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:11434/v1",
    api_key="ollama",
)

def stream_chat_openai(messages: list[dict], model: str = "qwen2.5:7b"):
    """OpenAI 兼容的流式调用"""
    stream = client.chat.completions.create(
        model=model,
        messages=messages,
        stream=True,  # 开启流式
    )

    full_response = ""
    for chunk in stream:
        if chunk.choices[0].delta.content:
            token = chunk.choices[0].delta.content
            full_response += token
            sys.stdout.write(token)
            sys.stdout.flush()

    print()
    return full_response


if __name__ == "__main__":
    print("=== Ollama 原生流式 ===")
    stream_chat("用3句话解释什么是Layer 2 Rollup")

    print("\n\n=== OpenAI 兼容流式 ===")
    stream_chat_openai([
        {"role": "user", "content": "用3句话解释什么是MEV"}
    ])
流式 vs 非流式的选择:

场景                    | 推荐     | 原因
-----------------------|----------|----------------------------------
聊天界面               | 流式 ✓   | 打字机效果,用户体验好
批量处理               | 非流式 ✓ | 代码简单,方便统计
RAG 系统               | 流式 ✓   | 用户等待时间感知更短
API 后端               | 都行     | 取决于前端是否需要流式
自动化脚本             | 非流式 ✓ | 需要完整响应做后处理

Day 16 讲的 LLM 应用架构中提到过:
  TTFT 决定用户等待体验
  流式输出让 TTFT 从"整体响应时间"降到"首Token时间"
  这就是为什么 ChatGPT 是逐字显示的

知识点5:llama.cpp 进阶 / llama.cpp Advanced

为什么还要学 llama.cpp?/ Why Learn llama.cpp?

Ollama 底层就是 llama.cpp,但 Ollama 做了很多封装。

直接用 llama.cpp 的场景:
1. 极致性能调优 — Ollama 的默认参数不一定最优
2. 自定义量化 — 用特定方法量化 HuggingFace 模型
3. 特殊模型格式 — 有些模型 Ollama 还没收录
4. 服务器部署 — llama.cpp 的 server 模式更灵活
5. 理解底层 — 面试时能讲清楚推理引擎原理

手动加载 GGUF 模型 / Loading GGUF Models

# Step 1: 安装 llama-cpp-python(带GPU加速)
# Windows + CUDA:
pip install llama-cpp-python \
  --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cu124

# 或者从源码编译(更灵活):
CMAKE_ARGS="-DGGML_CUDA=on" pip install llama-cpp-python

# Step 2: 下载 GGUF 模型(从 HuggingFace)
# 推荐模型: https://huggingface.co/Qwen/Qwen2.5-7B-Instruct-GGUF
# 下载 qwen2.5-7b-instruct-q4_k_m.gguf

# Step 3: Python 中使用
"""
llama_cpp_demo.py
直接使用 llama.cpp Python 绑定
"""

from llama_cpp import Llama

# 加载模型(关键参数)
llm = Llama(
    model_path="./models/qwen2.5-7b-instruct-q4_k_m.gguf",

    # 上下文长度 — 决定能处理多长的输入
    n_ctx=4096,            # 默认512太小,建议4096

    # GPU层数 — 决定多少层放GPU(剩余放CPU)
    n_gpu_layers=-1,       # -1 = 全部放GPU(推荐)
                           # 0 = 纯CPU(无GPU时)
                           # 20 = 部分放GPU(显存不够时)

    # CPU线程数 — 影响CPU计算部分的速度
    n_threads=8,           # 建议 = CPU物理核心数

    # 批处理大小 — 影响预填充速度
    n_batch=512,           # 默认512,一般不用改

    # 其他选项
    verbose=True,          # 显示加载详情
    seed=42,               # 随机种子(可复现)
)

# === 文本生成 ===
output = llm(
    "解释什么是预言机(Oracle)在区块链中的作用:",
    max_tokens=500,
    temperature=0.7,
    top_p=0.9,
    top_k=40,
    repeat_penalty=1.1,
    stop=["<|im_end|>", "\n\n\n"],
)
print(output["choices"][0]["text"])

# === Chat 格式 ===
output = llm.create_chat_completion(
    messages=[
        {"role": "system", "content": "你是Web3架构分析师"},
        {"role": "user", "content": "对比Optimistic和ZK Rollup的架构差异"},
    ],
    max_tokens=800,
    temperature=0.7,
)
print(output["choices"][0]["message"]["content"])

# === 获取 Embedding ===
embedding = llm.create_embedding("DeFi lending protocol")
print(f"Embedding shape: {len(embedding['data'][0]['embedding'])}")

参数调优详解 / Parameter Tuning

关键参数对性能的影响:

┌─────────────────┬───────────────────┬─────────────────────────────┐
│ 参数            │ 影响              │ 调优建议                     │
├─────────────────┼───────────────────┼─────────────────────────────┤
│ n_ctx           │ 上下文窗口长度    │ 越大越占显存                 │
│                 │ 决定输入+输出上限 │ KV Cache = 2×n_layers×      │
│                 │                   │   n_heads×head_dim×n_ctx    │
│                 │                   │ 4096是个好平衡点            │
├─────────────────┼───────────────────┼─────────────────────────────┤
│ n_gpu_layers    │ GPU卸载层数       │ -1全放GPU(最快)              │
│                 │ 更多层→更快       │ 显存不够时逐步减少           │
│                 │                   │ Qwen2.5-7B有32层            │
├─────────────────┼───────────────────┼─────────────────────────────┤
│ n_threads       │ CPU线程数         │ = 物理核心数(非逻辑核心)     │
│                 │ 影响CPU部分速度   │ 过多反而更慢(上下文切换)     │
├─────────────────┼───────────────────┼─────────────────────────────┤
│ n_batch         │ 批处理大小        │ 影响Prefill速度              │
│                 │ Prefill时并行度   │ 512是个好默认值              │
├─────────────────┼───────────────────┼─────────────────────────────┤
│ temperature     │ 生成随机性        │ 0.0=确定性, 1.0=高随机       │
│                 │                   │ 分析任务: 0.1-0.3            │
│                 │                   │ 创意任务: 0.7-0.9            │
├─────────────────┼───────────────────┼─────────────────────────────┤
│ top_p           │ 核采样阈值        │ 0.9 通常比 top_k 更好        │
│ top_k           │ 保留前K个Token    │ 40 是常用值                  │
├─────────────────┼───────────────────┼─────────────────────────────┤
│ repeat_penalty  │ 重复惩罚          │ 1.0=不惩罚, 1.1=轻微惩罚    │
│                 │                   │ 防止模型陷入重复循环         │
└─────────────────┴───────────────────┴─────────────────────────────┘

Ollama vs llama.cpp 性能对比 / Performance Comparison

"""
performance_comparison.py
对比 Ollama 和 llama.cpp 的性能差异
"""

import time
import requests
from llama_cpp import Llama

PROMPT = "详细解释以太坊的Gas机制,包括EIP-1559的改进"

def test_ollama(prompt: str, n_runs: int = 3) -> dict:
    """测试 Ollama 性能"""
    times = []
    tps_list = []

    for i in range(n_runs):
        start = time.time()
        resp = requests.post(
            "http://localhost:11434/api/generate",
            json={"model": "qwen2.5:7b", "prompt": prompt, "stream": False}
        ).json()
        elapsed = time.time() - start

        times.append(elapsed)
        if resp.get("eval_duration"):
            tps = resp["eval_count"] / (resp["eval_duration"] / 1e9)
            tps_list.append(tps)

    return {
        "avg_time": sum(times) / len(times),
        "avg_tps": sum(tps_list) / len(tps_list) if tps_list else 0,
    }

def test_llama_cpp(prompt: str, n_runs: int = 3) -> dict:
    """测试 llama.cpp 性能"""
    llm = Llama(
        model_path="./models/qwen2.5-7b-instruct-q4_k_m.gguf",
        n_ctx=4096,
        n_gpu_layers=-1,
        n_threads=8,
        verbose=False,
    )

    times = []
    tps_list = []

    for i in range(n_runs):
        start = time.time()
        output = llm(prompt, max_tokens=500, temperature=0.7)
        elapsed = time.time() - start

        times.append(elapsed)
        tokens = output["usage"]["completion_tokens"]
        tps_list.append(tokens / elapsed)

    return {
        "avg_time": sum(times) / len(times),
        "avg_tps": sum(tps_list) / len(tps_list),
    }

# 运行对比
print("Testing Ollama...")
ollama_result = test_ollama(PROMPT)

print("Testing llama.cpp...")
llamacpp_result = test_llama_cpp(PROMPT)

print(f"\n{'='*50}")
print(f"{'指标':<20} {'Ollama':<15} {'llama.cpp':<15}")
print(f"{'='*50}")
print(f"{'平均耗时(s)':<20} {ollama_result['avg_time']:<15.2f} {llamacpp_result['avg_time']:<15.2f}")
print(f"{'平均速度(t/s)':<20} {ollama_result['avg_tps']:<15.1f} {llamacpp_result['avg_tps']:<15.1f}")
预期对比结果:

指标            | Ollama        | llama.cpp (调优)
----------------|---------------|------------------
首次加载        | 自动管理      | 需手动加载
生成速度        | 略慢 (5-10%)  | 基准线
显存管理        | 自动          | 手动精确控制
多模型切换      | 自动卸载/加载 | 需要手动管理
API 兼容性      | OpenAI 兼容   | 需要额外配置
易用性          | ★★★★★        | ★★
生产部署        | 适合原型      | 适合性能敏感场景

结论:
- 开发和原型阶段 → Ollama(省时间)
- 生产和性能要求高 → llama.cpp 或 vLLM
- 学习和理解底层 → llama.cpp(必须了解)

知识点6:显存管理实战 / GPU Memory Management

8GB 显存下的最优配置 / Optimal Config for 8GB VRAM

8GB 显存预算分配(以 RTX 3060/4060 为例):

总显存: 8192 MB
├── 系统/驱动保留: ~500 MB
├── 模型权重: 模型大小决定
├── KV Cache: 上下文长度决定
└── 运行时缓冲: ~200-500 MB

可用显存 ≈ 7200 MB

不同模型的显存消耗:
┌───────────────────────┬──────────┬──────────┬──────────┐
│ 模型                  │ 权重     │ KV(4K)   │ 总占用   │
├───────────────────────┼──────────┼──────────┼──────────┤
│ Phi-4-mini 3.8B Q4    │ ~2.2 GB  │ ~0.3 GB  │ ~3.0 GB  │
│ Qwen2.5-7B Q4         │ ~4.4 GB  │ ~0.5 GB  │ ~5.5 GB  │
│ Llama3.3-8B Q4        │ ~4.9 GB  │ ~0.5 GB  │ ~6.0 GB  │
│ Gemma2-9B Q4          │ ~5.4 GB  │ ~0.6 GB  │ ~6.5 GB  │
│ Qwen2.5-14B Q4        │ ~8.1 GB  │ ~0.8 GB  │ ~9.5 GB  │ ← 超了!
└───────────────────────┴──────────┴──────────┴──────────┘

8GB 显存的最优组合:
- 主力模型: Qwen2.5-7B Q4(中文任务)
- 备选模型: Llama3.3-8B Q4(英文任务)
- 轻量模型: Phi-4-mini Q4(速度优先任务)
- Embedding: nomic-embed-text(768维,很小)

注意:不要同时运行两个大模型!
Ollama 会自动卸载不活跃的模型,但切换需要重新加载

nvidia-smi 监控 / nvidia-smi Monitoring

# 基础监控(每秒刷新)
nvidia-smi -l 1

# 只看显存使用
nvidia-smi --query-gpu=memory.used,memory.free,memory.total \
  --format=csv -l 1

# 详细进程信息
nvidia-smi --query-compute-apps=pid,name,used_memory \
  --format=csv

# 持续监控到文件(后台运行)
nvidia-smi --query-gpu=timestamp,memory.used,utilization.gpu,power.draw \
  --format=csv -l 5 > gpu_monitor.csv &
"""
gpu_monitor.py
实时GPU监控工具
"""

import subprocess
import time
import sys

def get_gpu_info() -> dict:
    """获取GPU信息"""
    result = subprocess.run(
        [
            "nvidia-smi",
            "--query-gpu=memory.used,memory.free,memory.total,"
            "utilization.gpu,temperature.gpu,power.draw",
            "--format=csv,noheader,nounits"
        ],
        capture_output=True, text=True
    )

    values = result.stdout.strip().split(", ")
    return {
        "memory_used_mb": int(values[0]),
        "memory_free_mb": int(values[1]),
        "memory_total_mb": int(values[2]),
        "gpu_util_pct": int(values[3]),
        "temperature_c": int(values[4]),
        "power_w": float(values[5]),
    }

def monitor_loop(interval: float = 1.0):
    """持续监控GPU状态"""
    print(f"{'Time':>8} | {'VRAM Used':>10} | {'VRAM Free':>10} | "
          f"{'GPU%':>5} | {'Temp':>5} | {'Power':>7}")
    print("-" * 65)

    while True:
        info = get_gpu_info()
        used_pct = info["memory_used_mb"] / info["memory_total_mb"] * 100

        # 显存使用率高时用红色提示
        vram_warning = " !!!" if used_pct > 90 else ""

        print(
            f"{time.strftime('%H:%M:%S'):>8} | "
            f"{info['memory_used_mb']:>6} MB | "
            f"{info['memory_free_mb']:>6} MB | "
            f"{info['gpu_util_pct']:>4}% | "
            f"{info['temperature_c']:>3}°C | "
            f"{info['power_w']:>5.1f} W"
            f"{vram_warning}"
        )
        time.sleep(interval)

if __name__ == "__main__":
    try:
        monitor_loop()
    except KeyboardInterrupt:
        print("\nMonitoring stopped.")

多模型切换策略 / Multi-Model Switching Strategy

Ollama 的自动管理机制:

默认行为:
1. 请求模型A → 加载模型A到显存
2. 请求模型B → 检查显存是否够
   ├── 够 → 同时保留A和B(如果你有16GB+)
   └── 不够 → 卸载A,加载B
3. 再请求模型A → 重新加载(有延迟)

关键环境变量:
OLLAMA_MAX_LOADED_MODELS=1     # 最多同时加载几个模型
OLLAMA_NUM_PARALLEL=1          # 单模型并发请求数
OLLAMA_KEEP_ALIVE=5m           # 空闲多久后卸载模型
"""
smart_model_router.py
智能模型路由:根据任务类型选择最优模型
"""

from enum import Enum
from openai import OpenAI

client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")

class TaskType(Enum):
    CHINESE_CHAT = "chinese_chat"
    ENGLISH_CHAT = "english_chat"
    CODE_GEN = "code_generation"
    QUICK_ANSWER = "quick_answer"
    EMBEDDING = "embedding"

# 任务→模型映射
MODEL_MAP = {
    TaskType.CHINESE_CHAT: "qwen2.5:7b",      # 中文最强
    TaskType.ENGLISH_CHAT: "llama3.3:8b",      # 英文最强
    TaskType.CODE_GEN: "qwen2.5-coder:7b",     # 代码专用
    TaskType.QUICK_ANSWER: "phi4-mini:3.8b",   # 速度最快
    TaskType.EMBEDDING: "nomic-embed-text",     # Embedding
}

def smart_chat(
    prompt: str,
    task_type: TaskType = TaskType.CHINESE_CHAT,
    temperature: float = 0.7,
) -> str:
    """根据任务类型自动选择模型"""
    model = MODEL_MAP[task_type]

    response = client.chat.completions.create(
        model=model,
        messages=[{"role": "user", "content": prompt}],
        temperature=temperature,
    )

    return response.choices[0].message.content

# === 使用示例 ===

# 中文分析
result = smart_chat(
    "分析Uniswap V3的集中流动性对LP的影响",
    task_type=TaskType.CHINESE_CHAT,
    temperature=0.3,  # 分析任务低温度
)

# 快速回答
result = smart_chat(
    "What is ERC-4337?",
    task_type=TaskType.QUICK_ANSWER,
)

# 代码生成
result = smart_chat(
    "Write a Solidity function for ERC20 token transfer with fee",
    task_type=TaskType.CODE_GEN,
)
显存优化技巧总结:

1. 减少上下文长度
   n_ctx: 4096 → 2048 可节省约 0.3-0.5GB
   只在需要长输入时才用大 n_ctx

2. 使用更小的量化
   Q4_K_M → Q3_K_M 可节省约 20% 显存
   但质量会有明显下降,不推荐

3. 部分CPU卸载
   n_gpu_layers: -1 → 28(32层放28层到GPU)
   4层放CPU,速度下降约15%,省约1GB

4. 关闭不必要的GPU程序
   浏览器GPU加速 → 关闭
   其他GPU程序 → 关闭
   Windows桌面合成 → 无法关闭但影响不大

5. 单模型策略
   OLLAMA_MAX_LOADED_MODELS=1
   确保只有一个模型在显存中

6. Flash Attention
   Ollama 和 llama.cpp 默认开启
   可以减少 KV Cache 的显存消耗

今日思考 / Today's Reflections

思考1:本地部署 vs 云端 API 的真正取舍 / Local vs Cloud Trade-offs

经过实际部署,重新审视本地 vs 云端的选择:

本地部署的真实优势:
├── 隐私: 数据完全不出本机(金融场景关键)
├── 成本: 长期使用几乎零边际成本
├── 延迟: 无网络开销(局域网场景)
└── 控制: 可精确控制模型参数和行为

本地部署的真实痛点:
├── 质量: 7B 模型 vs GPT-4/Claude 差距明显
├── 运维: 需要管理硬件、驱动、模型更新
├── 扩展: 单卡并发有限
└── 投资: 好显卡不便宜(RTX 4090 = 1万+)

实际决策框架:
┌─────────────────┬──────────────┬──────────────┐
│ 场景            │ 推荐方案     │ 原因         │
├─────────────────┼──────────────┼──────────────┤
│ 敏感数据处理    │ 本地         │ 合规要求     │
│ 高质量分析      │ 云端 API     │ 质量差距大   │
│ 开发调试        │ 本地         │ 免费无限调用 │
│ 生产服务        │ 云端/私有部署│ 可靠性+扩展  │
│ RAG 原型        │ 本地         │ 快速迭代     │
│ 面向用户产品    │ 云端         │ 质量决定体验 │
└─────────────────┴──────────────┴──────────────┘

最佳实践是混合架构(Day 27 多模型编排学过的):
- 简单任务 → 本地小模型(快+免费)
- 复杂任务 → 云端大模型(质量+可靠)
- 敏感数据 → 本地处理(合规)
- 非敏感数据 → 云端处理(效率)

思考2:模型选择的"够用就好"原则 / "Good Enough" Model Selection

今天对比了 4 个模型后的感悟:

错误思维: "一定要用最好的模型"
正确思维: "用最合适的模型"

实际案例:
- 文本分类任务 → Phi-4-mini 3.8B 就够了
  速度快 3 倍,准确率只差 2%

- 中文客服问答 → Qwen2.5-7B 完全够用
  不需要 GPT-4 级别

- 复杂金融分析 → 才需要 Claude/GPT-4
  本地模型确实不够

这就是 Day 26 "成本工程" 学到的:
  ROI = 任务价值 / 模型成本
  很多场景下,小模型的 ROI 更高

思考3:从"知道"到"会做"的质变 / From "Knowing" to "Doing"

Day 2 学量化时,我记住了:
  "Q4_K_M 将每个权重压缩到 4 bit"

今天实际部署后,我真正理解了:
  "Q4_K_M 意味着 7B 模型只占 4.4GB,能在我的 8GB 卡上跑"
  "同一个模型 Q4 vs Q8 速度差 30%,质量差感知不大"
  "KV Cache 随上下文线性增长,4K 和 8K 差距可量化"

这就是动手实战的价值:
  理论: "Transformer 的自注意力机制复杂度是 O(n²)"
  实践: "我把 n_ctx 从 4096 开到 8192,显存多了 0.5GB,速度也慢了"

  理论: "流式输出可以降低 TTFT"
  实践: "用户第一个字 0.8 秒就出来了,体验确实好很多"

后面 9 天的动手实践,每一天都会有这种"啊原来如此"的时刻。

学习资源 / Resources

官方文档

模型资源

社区工具

性能基准


明日预告 / Tomorrow's Preview

Day 52: RAG系统实战 — 让AI回答你的文档问题

明天我们要做一件真正有用的事:
用自己的 251 篇架构笔记 + 90 篇 Web3 笔记,构建一个 RAG 系统

你将能问:
  "Day 44 讲的 RAG 系统设计要点是什么?"
  "架构计划中关于支付系统的内容有哪些?"
  "DeFi风控和传统金融风控的差异总结?"

需要提前准备:
1. 确保 Ollama 正常运行(今天已完成)
2. 安装: pip install chromadb langchain sentence-transformers
3. 确认所有笔记文件在 docs/ 目录下

这将是前 51 天所有知识的综合运用:
- Day 5 的 RAG 架构理论
- Day 6 的向量数据库知识
- Day 19-21 的生产 RAG 实践
- Day 51 的本地模型部署(今天)

Day 51 完成! 从零到一,你现在拥有了一个可通过 API 调用的本地 LLM 服务。 这是后续所有实战项目的基础设施。明天开始构建真正有用的 RAG 系统!