AI学习 - 大模型基础入门
AI学习 - 大模型基础入门
从零开始:Ollama 安装 → 本地模型运行 → Python 代码接入 → 理解核心概念
摘要
本文记录了在 Windows 上使用 Ollama 部署本地大模型、并通过 Python 代码接入调用的完整过程。内容涵盖:Ollama 安装与模型拉取、大模型基础概念科普(Token / Context / Context Window / Temperature / Role)、三种调用方式对比,以及多轮对话的完整实现代码。
跟着做完,你能在自己电脑上跑起一个本地大模型,并用代码与它对话。
一、环境准备
1.1 硬件建议
| 配置 | 能跑的模型 | 体验 |
|---|---|---|
| 8GB 显存独显 + 16GB 内存 | 7b-9b 模型 | 流畅 |
| 无独显 / 核显 | 3b 以下模型 | 慢,但能跑 |
| 16GB 显存 | 14b-30b 模型 | 良好 |
模型文件大小 ≈ 显存需求。qwen3.5:9b 文件 6.6GB,需要约 7GB 显存。
1.2 安装 Ollama
推荐安装官方版本:
# 方式一:winget 安装(推荐)
winget install Ollama.Ollama
# 方式二:官网下载安装包
# https://ollama.com/download 选 Windows
安装完成后验证:
ollama --version
# 应输出类似:ollama version 0.9.x
二、拉取并运行本地模型
2.1 推荐的入门模型
| 模型 | 大小 | 显存需求 | 特点 |
|---|---|---|---|
qwen3.5:9b |
6.6GB | ~7GB | 中文友好,速度快,推荐入门 |
deepseek-r1:7b |
4.7GB | ~5GB | 有思考链,适合观察推理过程 |
qwen3-coder:30b |
18GB | ~20GB | 代码能力强,需要大显存 |
2.2 拉取模型
# 拉取(第一次会下载,之后直接用缓存)
ollama pull qwen3.5:9b
# 查看已安装的模型
ollama list
# 查看当前运行中的模型
ollama ps
2.3 命令行直接对话(验证是否正常)
ollama run qwen3.5:9b
# 进入交互模式,直接输入问题
# 输入 /bye 退出
2.4 常见问题
问题:Error: 500 Internal Server Error: memory layout cannot be allocated
排查顺序:
# 1. 查看显存占用
nvidia-smi
# 2. 查看内存占用
Get-CimInstance Win32_OperatingSystem | Select-Object `
@{N='总内存(GB)';E={[math]::Round($_.TotalVisibleMemorySize/1MB,1)}}, `
@{N='可用内存(GB)';E={[math]::Round($_.FreePhysicalMemory/1MB,1)}}
# 3. 停止已加载的模型释放显存
ollama stop deepseek-r1:7b
# 4. 版本太旧导致不兼容,尝试升级
winget upgrade Ollama.Ollama
# 5. 模型文件损坏,重新下载
ollama rm qwen3.5:9b
ollama pull qwen3.5:9b
三、大模型基础概念
在写代码之前,先把几个核心概念搞清楚,后面看代码会顺很多。
3.1 Token:模型处理文字的最小单位
模型不认识"字"或"词",只认识 token。可以理解为把文字切成碎片:
"你好北京" → ["你", "好", "北", "京"] → 4 个 token
"hello" → ["hello"] → 1 个 token
"unhappy" → ["un", "happy"] → 2 个 token
为什么要了解 token:
- API 按 token 计费(调云端模型时)
- 模型有最大处理量限制(context window)
max_tokens参数控制的就是最多生成多少 token
经验换算:1000 个中文字符 ≈ 500-700 token。
3.2 Context:模型这次推理能看到的全部内容
模型没有记忆,每次调用都是全新的。它能"记住"上下文,是因为你把历史对话都传进去了。
一次 API 调用时,context 包含:
┌─────────────────────────────┐
│ system prompt │ → 角色设定
│ 第1轮用户消息 │ → 历史
│ 第1轮模型回复 │ → 历史
│ 第2轮用户消息 │ → 历史
│ 第2轮模型回复 │ → 历史
│ 当前用户消息(最新) │ → 当前问题
└─────────────────────────────┘
模型基于以上全部内容生成回复
思考题: 如果对话进行了 100 轮,每次都要把 100 轮历史传进去,token 消耗会越来越大。这就是为什么长对话会越来越慢、越来越贵。
3.3 Context Window:模型一次能处理的 token 上限
Context Window = 128,000 token(以 Claude 为例)
意思是:system prompt + 所有历史对话 + 当前问题
加起来不能超过 128,000 token
超出的部分模型直接看不到,就像被裁掉了
这也是 RAG(检索增强生成)存在的核心原因:当你有一本 500 页的书,塞不进 context window,就只能先检索最相关的几段塞进去,而不是全文。
3.4 Temperature:控制输出的随机程度
第一步:模型输出的是 logits,不是概率
模型最后一层输出的是原始分数,叫 logits,数值没有限制,可以是任意实数,不能直接当概率用:
# 模型对"下一个词是什么"给出的原始分数
logits = [2.1, 1.8, 1.2, 0.9]
# "9" "5" "3" "7"
第二步:softmax 把 logits 转成概率
import math
def softmax(logits):
exp_values = [math.exp(x) for x in logits] # 对每个值取 e^x
total = sum(exp_values)
return [x / total for x in exp_values] # 归一化,加起来 = 1
softmax([2.1, 1.8, 1.2, 0.9])
# → [0.38, 0.28, 0.16, 0.12] 加起来 = 1.0
第三步:temperature 在 softmax 之前介入
操作很简单,就是把 logits 除以 T,再做 softmax:
def softmax_with_temperature(logits, T):
scaled = [x / T for x in logits] # 先除以 temperature
return softmax(scaled) # 再转概率
为什么除法能控制尖锐程度
关键在 e^x 这个函数对数值差距极其敏感:
# 原始 logits,"9" 和 "5" 差距是 0.3
logits = [2.1, 1.8]
# 直接做 e^x,比值 = 1.35
math.exp(2.1) = 8.17
math.exp(1.8) = 6.05
# ── temperature = 0.1,差距放大 10 倍 ──
scaled = [21.0, 18.0]
math.exp(21.0) = 1,318,815,734
math.exp(18.0) = 65,659,969
# 比值 = 20.1,差距被指数级拉开 → 分布尖锐
# ── temperature = 2.0,差距压缩到一半 ──
scaled = [1.05, 0.90]
math.exp(1.05) = 2.86
math.exp(0.90) = 2.46
# 比值 = 1.16,差距被压缩 → 分布平坦
完整数字对比
原始 logits:[2.1, 1.8, 1.2, 0.9]
token: "9" "5" "3" "7"
T = 1.0(不变):
scaled: [2.1, 1.8, 1.2, 0.9]
概率: [38%, 28%, 16%, 12%] 有差距,其他词仍有机会
T = 0.1(极低,差距放大 10 倍):
scaled: [21.0, 18.0, 12.0, 9.0]
概率: [95%, 4%, ~0%, ~0%] "9" 几乎必然被选,输出稳定
T = 0.5(适中,差距放大 2 倍):
scaled: [4.2, 3.6, 2.4, 1.8]
概率: [63%, 27%, 7%, 3%] "9" 概率提升,"5" 还有机会
T = 2.0(极高,差距压缩一半):
scaled: [1.05, 0.90, 0.60, 0.45]
概率: [30%, 27%, 23%, 20%] 接近均匀,什么都可能选
T → 0(趋近于零):
→ 等价于直接取最大值(greedy decoding)
→ [100%, 0%, 0%, 0%],必然选 "9"
用图形理解
概率分布的"山峰"形状:
temperature 低(0.1): temperature 高(2.0):
█ █ █ █ █
█ █ █ █ █ █
█ █ █ █ █ █ █ █
█ █ █ █ █ █ █ █ █ █ █
9 5 3 7 9 5 3 7
尖锐,"9" 一枝独秀 平坦,每个词机会差不多
→ 输出稳定 → 输出多样
一句话总结
temperature 通过缩放 logits 的数值差距
利用 e^x 对差距极其敏感的特性
在 softmax 之后产生尖锐或平坦的概率分布
T 小 → 差距放大 → e^x 把差距指数级拉开 → 分布尖锐 → 输出稳定
T 大 → 差距压缩 → e^x 的放大效果减弱 → 分布平坦 → 输出多样
T→0 → 差距无限大 → 最高分词概率→100% → greedy decoding
实际使用建议
| 场景 | temperature 推荐值 |
|---|---|
| 分类、判断、提取(需要稳定) | 0 - 0.2 |
| 问答、总结(平衡) | 0.3 - 0.7 |
| 写作、头脑风暴(需要创意) | 0.7 - 1.0 |
| deepseek-r1 推理模型 | 0.6(官方推荐) |
注意: deepseek-r1 这类推理模型即使 temperature=0,输出也可能不固定。原因是它先生成
<think>思考链(几百次采样),思考路径的微小差异会影响最终答案的措辞。
3.5 Role:对话里的角色标识
发给模型的消息必须标明是谁说的,三个固定角色:
{"role": "system", "content": "..."} # 系统指令,定角色和规则
{"role": "user", "content": "..."} # 人类说的话
{"role": "assistant", "content": "..."} # 模型自己说过的话
这三个值是训练时就固化的,不能随意更改。模型内部会把消息拼接成:
<|system|>你是一个助手
<|user|>我叫张三
<|assistant|>你好张三!
<|user|>我叫什么名字?
<|assistant|> ← 模型从这里续写
assistant 历史为什么不能省: 没有 assistant 的历史,对话上下文就断了,模型不知道自己之前说过什么。
3.6 API Key:身份验证凭证,不是模型的一部分
云端服务(Claude/GPT):
请求 → [业务层验证 Key → 计费 → 限速] → 模型推理 → 返回
本地 Ollama:
请求 → 模型推理 → 返回
(没有业务层,不需要 Key)
用 openai 库调本地 ollama 时,api_key 填任意非空字符串都行,因为 openai 库在代码层面要求这个字段不能为空,但 ollama 收到请求后直接忽略它。
# 这三种写法效果完全相同
OpenAI(api_key="ollama", base_url="http://localhost:11500/v1")
OpenAI(api_key="任意字符串", base_url="http://localhost:11500/v1")
OpenAI(api_key="helloworld", base_url="http://localhost:11500/v1")
如果要对外提供服务并做鉴权,需要在 ollama 前面加反向代理(如 FastAPI 或 one-api),自己实现 Key 的生成和验证逻辑。
四、三种调用方式
方式对比
| anthropic 库 | openai 库(调GPT) | openai 库(调ollama) | |
|---|---|---|---|
| 目标模型 | Claude | GPT 系列 | 本地模型 |
| 需要 Key | 是(付费) | 是(付费) | 不需要 |
| 认证 Header | X-Api-Key |
Authorization: Bearer |
忽略 |
| 额外 Header | anthropic-version |
无 | 无 |
| 费用 | 按 token 计费 | 按 token 计费 | 免费 |
| 数据隐私 | 发到云端 | 发到云端 | 本地,不出网 |
学习阶段推荐方式三(openai 库 + 本地 ollama):不花钱、数据不出网、openai 的接口格式是行业标准,学会后换 GPT/Claude 只改两行代码。
五、完整项目代码
"""
本地大模型接入示例
依赖:pip install openai
运行前确保:ollama 已启动,且已拉取对应模型
"""
import re
from openai import OpenAI
# ==================== 初始化客户端 ====================
client_ollama = OpenAI(
api_key="ollama", # 本地不验证,随便填但不能为空
base_url="http://localhost:11500/v1" # 改成你的 ollama 端口
)
DEFAULT_MODEL = "qwen3.5:9b" # 默认使用的模型
# ==================== 工具函数 ====================
def parse_think(content: str, show_think: bool = True) -> str:
"""
解析 deepseek-r1 的思考链输出
deepseek-r1 会在回答前输出 <think>...</think> 思考过程
这个函数把思考过程和最终答案分离
"""
if "<think>" not in content:
return content
think_match = re.search(r"<think>(.*?)</think>", content, re.DOTALL)
answer = re.sub(r"<think>.*?</think>", "", content, flags=re.DOTALL).strip()
if show_think and think_match:
print(f"💭 思考过程:\n{'-' * 40}")
print(think_match.group(1).strip())
print(f"{'-' * 40}\n")
return answer
# ==================== 单轮对话 ====================
def ask(
question: str,
system: str = "你是一个助手,请用中文回答",
temperature: float = 0.7,
model: str = DEFAULT_MODEL,
show_think: bool = True
) -> str:
"""
单轮问答
每次调用都是独立的,不保留上下文
Args:
question: 用户问题
system: 系统提示词,定义模型角色和行为规则
temperature: 随机程度,0=稳定,1=多样,推理模型建议0.6
model: 使用的模型名称
show_think: 是否打印思考过程(deepseek-r1 专用)
"""
resp = client_ollama.chat.completions.create(
model=model,
temperature=temperature,
messages=[
{"role": "system", "content": system},
{"role": "user", "content": question},
]
)
content = resp.choices[0].message.content
return parse_think(content, show_think)
# ==================== 多轮对话 ====================
class ChatSession:
"""
多轮对话会话
维护对话历史,实现上下文记忆
模型本身没有记忆,"记住"上下文的原因是:
每次调用都把完整的历史对话传给模型(history 列表)
随着对话轮数增加,传入的 token 也越来越多
"""
def __init__(
self,
system: str = "你是一个助手,请用中文回答",
model: str = DEFAULT_MODEL,
temperature: float = 0.7,
show_think: bool = False
):
self.model = model
self.temperature = temperature
self.show_think = show_think
self.history = [
{"role": "system", "content": system}
]
def chat(self, user_input: str) -> str:
"""
发送一条消息,返回模型回复
自动维护 history,实现多轮对话
"""
# 把用户消息加入历史
self.history.append({"role": "user", "content": user_input})
# 把完整历史传给模型
resp = client_ollama.chat.completions.create(
model=self.model,
temperature=self.temperature,
messages=self.history # 关键:传完整历史
)
reply = resp.choices[0].message.content
reply = parse_think(reply, self.show_think)
# 把模型回复也加入历史(下一轮需要用到)
self.history.append({"role": "assistant", "content": reply})
return reply
def clear(self):
"""清空对话历史,保留 system prompt"""
system_msg = self.history[0]
self.history = [system_msg]
def show_history(self):
"""打印当前对话历史"""
print("\n📋 当前对话历史:")
for i, msg in enumerate(self.history):
role_icon = {"system": "⚙️", "user": "👤", "assistant": "🤖"}.get(msg["role"], "?")
print(f"{role_icon} [{msg['role']}]: {msg['content'][:80]}...")
print(f"共 {len(self.history)} 条消息\n")
# ==================== 演示代码 ====================
if __name__ == "__main__":
# ---------- 演示1:temperature 对比 ----------
print("=" * 60)
print("演示1:temperature 对比(使用 qwen3.5:9b)")
print("=" * 60)
print("\n--- temperature=0(稳定,每次结果应该相同)---")
print(ask("随机给我一个1-10之间的数字,只回答数字",
temperature=0, model="qwen3.5:9b", show_think=False))
print(ask("随机给我一个1-10之间的数字,只回答数字",
temperature=0, model="qwen3.5:9b", show_think=False))
print("\n--- temperature=1(多样,每次结果可能不同)---")
print(ask("随机给我一个1-10之间的数字,只回答数字",
temperature=1, model="qwen3.5:9b", show_think=False))
print(ask("随机给我一个1-10之间的数字,只回答数字",
temperature=1, model="qwen3.5:9b", show_think=False))
# ---------- 演示2:system prompt 的效果 ----------
print("\n" + "=" * 60)
print("演示2:system prompt 角色限定")
print("=" * 60)
print("\n--- 没有角色限定 ---")
print(ask("帮我写首诗", model="qwen3.5:9b", show_think=False))
print("\n--- 限定为客服角色 ---")
print(ask(
"帮我写首诗",
system="你是一家叫「星河科技」的公司的客服,只回答产品相关问题,其他问题礼貌拒绝",
model="qwen3.5:9b",
show_think=False
))
# ---------- 演示3:deepseek-r1 思考链 ----------
print("\n" + "=" * 60)
print("演示3:deepseek-r1 思考链(观察推理过程)")
print("=" * 60)
print(ask(
"9.9 和 9.11 哪个数字更大?",
model="deepseek-r1:7b",
temperature=0.6,
show_think=True
))
# ---------- 演示4:多轮对话记忆 ----------
print("\n" + "=" * 60)
print("演示4:多轮对话(验证上下文记忆)")
print("=" * 60)
session = ChatSession(
system="你是一个友好的助手",
model="qwen3.5:9b",
show_think=False
)
print("👤 我叫张三")
print(f"🤖 {session.chat('我叫张三')}\n")
print("👤 我在学习 Python 和大模型开发")
print(f"🤖 {session.chat('我在学习 Python 和大模型开发')}\n")
print("👤 我叫什么名字?在学什么?")
print(f"🤖 {session.chat('我叫什么名字?在学什么?')}\n")
# 查看传给模型的完整历史
session.show_history()
# ---------- 演示5:交互式对话 ----------
print("\n" + "=" * 60)
print("演示5:交互式对话(输入 quit 退出)")
print("=" * 60)
interactive_session = ChatSession(
system="你是一个耐心的 AI 学习助手,用简单易懂的方式解释概念",
model="qwen3.5:9b",
show_think=False
)
while True:
user_input = input("\n👤 你:").strip()
if user_input.lower() in ["quit", "exit", "退出", "q"]:
print("对话结束")
break
if not user_input:
continue
reply = interactive_session.chat(user_input)
print(f"🤖 助手:{reply}")
六、思考与延伸
Q:模型越大越好吗?
不一定。9b 的模型在日常问答、写作、代码辅助上已经够用,而且速度更快。30b+ 的模型在复杂推理、长文档理解上有明显优势,但需要更强的硬件。实际项目里先用小模型跑通,再按需升级。
Q:本地模型和云端模型的选择?
本地模型:数据不出网(适合隐私数据)、免费、受硬件限制
云端模型:能力更强、随用随取、按量计费、数据上传云端
学习阶段用本地,生产环境根据数据敏感程度和能力需求决定。
Q:history 一直增长怎么办?
对话很长时,history 会越来越大,超出 context window 后模型就会"失忆"。实际项目里的处理方式:
- 只保留最近 N 轮对话
- 把早期对话摘要压缩后放入 system prompt
- 用向量数据库存历史,按需检索(RAG 思路)
Q:为什么 deepseek-r1 的 temperature=0 仍然不稳定?
因为推理模型先生成 <think> 思考链(几百次采样),GPU 浮点运算的微小误差会在思考链中累积,导致最终答案措辞不同。对于需要稳定输出的场景,推理模型反而不如普通模型合适。
七、参考资料
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)