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# 查看当前运行中的模型ollamaps2.3 命令行直接对话(验证是否正常)
ollama run qwen3.5:9b# 进入交互模式,直接输入问题# 输入 /bye 退出2.4 常见问题
问题:Error: 500 Internal Server Error: memory layout cannot be allocated
排查顺序:
# 1. 查看显存占用nvidia-smi# 2. 查看内存占用Get-CimInstanceWin32_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. 模型文件损坏,重新下载ollamarmqwen3.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 转成概率
importmathdefsoftmax(logits):exp_values=[math.exp(x)forxinlogits]# 对每个值取 e^xtotal=sum(exp_values)return[x/totalforxinexp_values]# 归一化,加起来 = 1softmax([2.1,1.8,1.2,0.9])# → [0.38, 0.28, 0.16, 0.12] 加起来 = 1.0第三步:temperature 在 softmax 之前介入
操作很简单,就是把 logits除以 T,再做 softmax:
defsoftmax_with_temperature(logits,T):scaled=[x/Tforxinlogits]# 先除以 temperaturereturnsoftmax(scaled)# 再转概率为什么除法能控制尖锐程度
关键在e^x这个函数对数值差距极其敏感:
# 原始 logits,"9" 和 "5" 差距是 0.3logits=[2.1,1.8]# 直接做 e^x,比值 = 1.35math.exp(2.1)=8.17math.exp(1.8)=6.05# ── temperature = 0.1,差距放大 10 倍 ──scaled=[21.0,18.0]math.exp(21.0)=1,318,815,734math.exp(18.0)=65,659,969# 比值 = 20.1,差距被指数级拉开 → 分布尖锐# ── temperature = 2.0,差距压缩到一半 ──scaled=[1.05,0.90]math.exp(1.05)=2.86math.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 已启动,且已拉取对应模型 """importrefromopenaiimportOpenAI# ==================== 初始化客户端 ====================client_ollama=OpenAI(api_key="ollama",# 本地不验证,随便填但不能为空base_url="http://localhost:11500/v1"# 改成你的 ollama 端口)DEFAULT_MODEL="qwen3.5:9b"# 默认使用的模型# ==================== 工具函数 ====================defparse_think(content:str,show_think:bool=True)->str:""" 解析 deepseek-r1 的思考链输出 deepseek-r1 会在回答前输出 <think>...</think> 思考过程 这个函数把思考过程和最终答案分离 """if"<think>"notincontent:returncontent think_match=re.search(r"<think>(.*?)</think>",content,re.DOTALL)answer=re.sub(r"<think>.*?</think>","",content,flags=re.DOTALL).strip()ifshow_thinkandthink_match:print(f"💭 思考过程:\n{'-'*40}")print(think_match.group(1).strip())print(f"{'-'*40}\n")returnanswer# ==================== 单轮对话 ====================defask(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.contentreturnparse_think(content,show_think)# ==================== 多轮对话 ====================classChatSession:""" 多轮对话会话 维护对话历史,实现上下文记忆 模型本身没有记忆,"记住"上下文的原因是: 每次调用都把完整的历史对话传给模型(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}]defchat(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})returnreplydefclear(self):"""清空对话历史,保留 system prompt"""system_msg=self.history[0]self.history=[system_msg]defshow_history(self):"""打印当前对话历史"""print("\n📋 当前对话历史:")fori,msginenumerate(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)whileTrue:user_input=input("\n👤 你:").strip()ifuser_input.lower()in["quit","exit","退出","q"]:print("对话结束")breakifnotuser_input:continuereply=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 浮点运算的微小误差会在思考链中累积,导致最终答案措辞不同。对于需要稳定输出的场景,推理模型反而不如普通模型合适。
七、参考资料
- Ollama 官网
- Ollama 模型库
- OpenAI Python 库文档
- deepseek-r1 官方说明
- Qwen3 模型介绍
