Ollama 命令行与对话指令全解

两套命令、一条生命周期:系统终端里管模型,对话框里调模型,弄混了就会一直报「command not found」。

30″30 秒看懂 Ollama 的两套命令

Ollama 像一台傻瓜料理机:以前要自己买食材、调火候、洗锅,现在插上电、选好菜谱,按一个键就开做。但它有两套完全不同的按钮——机器外壳上的旋钮,和打开盖子之后里面的调节钮。

外壳上的旋钮是系统终端里敲的命令ollama pull 下载菜谱、ollama list 看有哪些菜谱、ollama rm 扔掉不要的。盖子里面的调节钮是进入对话之后才能敲的斜杠指令/set 调火候、/clear 倒掉锅里的、/bye 关盖子。

把这两套弄混,是新手最常见的第一个坑。在系统终端里敲 /show,shell 会告诉你「没有这个命令」;在对话框里敲 ollama list,模型会一本正经地给你编一段解释。

图① 系统终端里敲的命令与进对话后才能敲的指令
图① 系统终端里敲的命令与进对话后才能敲的指令
比喻里的动作对应的命令在哪里敲
从菜谱网站下载菜谱ollama pull qwen2:1.5b系统终端
看看家里有哪些菜谱ollama list系统终端
看看现在灶上炖着什么ollama ps系统终端
打开料理机开始做ollama run qwen2:1.5b系统终端(敲完就进对话了)
调小火候/set parameter temperature 0.3对话里
倒掉锅里的、重新开始/clear对话里
关盖子走人/bye对话里
⛔ 这一讲的铁律 ollama 前缀的在系统终端敲,带 / 前缀的在对话里敲。看到提示符是 $ 还是 >>>,就知道自己现在站在哪一边。这条分不清,后面所有命令都会时灵时不灵。

01概念:装完之后,机器上多了什么

先搞清楚 Ollama 到底是个什么东西,命令才不会瞎敲

1.1 两套命令,别弄混

Ollama 装完之后,你手上其实有两样东西:一个叫 ollama 的命令行工具,和一个在后台常驻的服务进程。它们的关系是:

ollama 命令你敲的这个,是客户端
HTTP 请求发到 127.0.0.1:11434
ollama 服务常驻后台,真正干活的

这个结构解释了两件常让人困惑的事:

  • 为什么有时候敲命令会报「connection refused」。因为客户端在,服务没起来。命令本身没问题,是后面那半截断了。
  • 为什么关掉终端窗口,模型还占着显存。因为模型是加载在服务进程里的,和你那个终端窗口没关系。要卸载得靠 keep_alive 到期,或者重启服务。
对比项系统终端命令对话内斜杠指令
提示符长什么样$#>>>
前缀都以 ollama 开头都以 / 开头
管什么模型文件、服务状态当前这次对话的行为
作用范围全局,重启后仍然有效只在本次会话内,退出就没了
敲错了会怎样shell 报 command not found模型把它当成普通问题,一本正经地瞎答
⚠️ 最坑的是第二种错法 在系统终端敲 /show,shell 直接报错,你立刻就知道错了。但在对话框里敲 ollama list模型不会报错——它会当成一个普通问题,给你编一段像模像样的「本地模型列表」。那个列表是假的。看到输出是自然语言而不是表格,就说明敲错地方了。

1.2 装完之后发生了什么

不同系统的安装方式不一样,但装完之后的结果是一致的:

系统常见安装方式装完之后
Windows / macOS下载安装包,双击安装后台服务自动起来,托盘里有图标,开机自启
Linux官方一键脚本,或下载压缩包解压一键脚本会顺带装好 systemd 服务;手动解压的不会,得自己配

装完立刻验证,别急着拉模型:

ollama --version        # 能打印版本号,说明命令装好了
ollama list             # 能返回列表(哪怕是空的),说明服务通了

这两条都通过,才算装成功。只有第一条通过,说明命令有了但服务没起来——这是 Linux 手动解压安装最常见的状态。

课堂和内网机房都不通外网,走的是离线包。离线装比一键脚本多出三件事,每一件被漏掉都会表现成「装好了但用不了」

要做的事一键脚本会替你做吗漏了会怎样
二进制放进 PATHollama -v 报 command not found
写 systemd 服务会;解压包不会得手动 serve,关终端服务就没了
把模型文件也搬过来都不会服务起来了,list 是空的,又拉不了
ollama_offline_install.sh —— 断网环境的完整安装路径离线安装
#!/bin/bash
# 离线安装:机器不通外网(课堂环境、内网机房)时怎么把 Ollama 装起来。
#
# 联网机器上一条 curl 就完事,离线环境要自己做三件事:
#   ① 把二进制放进 PATH
#   ② 手写 systemd 服务(一键脚本才会替你写,解压包不会)
#   ③ 把模型文件也搬过来 —— 这一步最容易被忘,装好了却拉不到模型
#
# 用法:把 ollama-linux-amd64.tgz 和模型目录拷到本机后执行
#   sudo bash ollama_offline_install.sh
set -euo pipefail

PKG="${PKG:-./ollama-linux-amd64.tgz}"      # 离线安装包路径
MODELS_DIR="${MODELS_DIR:-/data/ollama/models}"   # 模型存放目录,指到数据盘
MODELS_SRC="${MODELS_SRC:-}"                # 别的机器拷来的模型目录,可以为空

echo "===== ① 解压二进制 ====="
if [ ! -f "$PKG" ]; then
  echo "找不到安装包 $PKG,先把它拷到当前目录"
  exit 1
fi
tar -xzf "$PKG" -C /usr
# 验证:打得出版本号才算装上。报 command not found 就是解压路径不对
ollama -v

echo
echo "===== ② 准备模型目录 ====="
# 模型动辄几十 G,一定要放数据盘,不能留在系统盘
mkdir -p "$MODELS_DIR"

if [ -n "$MODELS_SRC" ] && [ -d "$MODELS_SRC" ]; then
  echo "从 $MODELS_SRC 拷模型过来(这一步可能很久)"
  # -a 保留权限和时间戳,模型目录里的 blobs 和 manifests 结构必须原样保留
  cp -a "$MODELS_SRC"/. "$MODELS_DIR"/
else
  echo "没指定 MODELS_SRC,跳过;稍后需要联网 pull 或再手动拷贝"
fi

# 服务以哪个用户跑,目录就得属于谁;不然服务起来了却读不到模型
chown -R root:root "$MODELS_DIR"

echo
echo "===== ③ 写 systemd 服务 ====="
# 注意:这里是新建一个服务文件。如果机器上已经有 /etc/systemd/system/ollama.service,
# 先备份再改,不要直接覆盖别人配好的参数。
if [ -f /etc/systemd/system/ollama.service ]; then
  cp -a /etc/systemd/system/ollama.service \
        "/etc/systemd/system/ollama.service.bak.$(date +%Y%m%d%H%M%S)"
  echo "已备份原有服务文件"
fi

cat > /etc/systemd/system/ollama.service <<EOF
[Unit]
Description=Ollama Service
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=/usr/bin/ollama serve
User=root
Group=root
Restart=always
RestartSec=3
Environment="OLLAMA_MODELS=${MODELS_DIR}"
Environment="OLLAMA_HOST=127.0.0.1:11434"
Environment="OLLAMA_KEEP_ALIVE=30m"

[Install]
WantedBy=default.target
EOF

echo
echo "===== ④ 启动并设开机自启 ====="
# 改完服务文件必须 daemon-reload,否则 systemd 还在用旧的那份
systemctl daemon-reload
systemctl enable ollama
systemctl restart ollama
sleep 2
systemctl status ollama --no-pager | head -6

echo
echo "===== ⑤ 验证 ====="
ss -lntp | grep 11434 || echo "11434 没在监听,看 journalctl -u ollama -n 30"
# 环境变量有没有真的吃进去,看进程实际拿到的环境最可靠
PID=$(systemctl show -p MainPID --value ollama)
[ "$PID" != "0" ] && tr '\0' '\n' < "/proc/$PID/environ" | grep '^OLLAMA_' || true
ollama list
⚠️ 拷模型目录要用 cp -a 模型目录里的 blobsmanifests 结构必须原样保留,权限和时间戳也要带上。拷完还要确认目录属主和服务运行用户一致——服务起来了却读不到模型,十有八九是这里出的问题。
模型存哪儿,装之前就要想好 默认路径在系统盘(Linux 一般在 ~/.ollama/models)。模型动辄几个到几十 GB,拉上三五个系统盘就满了。生产机器务必在启动服务前把 OLLAMA_MODELS 指到数据盘。改路径之后已经拉过的模型不会自动搬家,要么手动移过去,要么重新拉。

1.3 serve 到底要不要手动敲

ollama serve 是启动服务的命令,但大多数情况下你不需要敲它。判断标准很简单:

情况要不要手动 serve怎么确认
Windows / macOS 安装包装的不用托盘有图标,ollama list 能返回
Linux 一键脚本装的不用systemctl is-active ollama 返回 active
Linux 手动解压装的敲了才有服务;但这种起法关掉终端就没了
生产服务器不该手动敲必须做成 systemd 服务,见《Linux 企业级部署》
⚠️ 服务已经在跑时再敲 serve,会报端口占用 错误信息大意是 address already in use,指的是 11434 端口已经被现有的服务占着。这不是故障——恰恰说明服务是好的。此时应该直接用,而不是去杀掉重起。真要重启,用 systemctl restart ollama,别用「敲一遍 serve」这种方式。

02原理:模型从哪里来,到哪里去

搞清这条生命周期,命令就不用背了

2.1 模型的三个落脚点

一个模型在你机器上只会处在三种状态之一,所有命令都是在这三者之间搬东西:

图② 模型的生命周期:远程仓库、本地磁盘、内存显存
图② 模型的生命周期:远程仓库、本地磁盘、内存显存
落脚点谁能看见它说明
远程模型仓库浏览器 / ollama pull模型还没下载,在 ollama.com 上放着
本地磁盘ollama list已经下载好的模型文件,占磁盘不占显存
内存 / 显存ollama ps正在运行的模型,占显存;用完一段时间后自动卸载
list 和 ps 一字之差,查的是两件事 ollama list磁盘上有什么ollama ps显存里正跑着什么。「模型明明 list 里有,为什么 ps 里没有」——因为它还没被加载,正常现象。反过来,排查显存占用只能看 ps,看 list 没有任何意义。

2.2 pullrun 的区别

两条命令都能把模型弄到本地,区别在于「下载完之后做什么」:

命令没下载过时已经下载过时用在什么场景
ollama pull 模型下载,然后停检查更新,通常很快结束提前备料、批量准备、写进部署脚本
ollama run 模型先下载,再直接进对话加载进显存,直接进对话手动测试、交互式使用

所以在部署脚本里应该用 pull——用 run 的话,脚本会停在对话界面上等你输入,整个自动化流程就卡死在那儿了。

run 也能不进对话run 后面直接跟上问题,它就只回答一次然后退出:ollama run qwen2:1.5b "用一句话介绍你自己"。这个用法很适合写进脚本做健康检查,或者和管道配合,例如 cat 文件 | ollama run 模型 "总结这段内容"

2.3 keep_alive 与自动卸载

模型加载进显存要花几秒到几十秒,所以 Ollama 用完之后不会立刻卸载,而是先留着,等一段时间没人用再释放。这个时长默认是 5 分钟

场景建议设置理由
高频线上服务设长,例如 30m-1(永不卸载)省掉反复加载的几十秒,响应稳定
显存紧张、多模型轮换设短,例如 1m让不用的模型尽快腾出显存
开发机偶尔用默认 5 分钟就行不用操心

设置方式有两种:全局设 OLLAMA_KEEP_ALIVE 环境变量,或者在单次 API 请求里带 keep_alive 字段。要立刻卸载某个模型,把它的 keep_alive 设成 0 再发一次请求即可。

⚠️ 「我明明没在用,显存怎么还占着」 十有八九就是 keep_alive 还没到期。这是设计行为,不是内存泄漏。确认方法是 ollama ps,输出里会显示这个模型还有多久过期。等着、或者手动卸载,都可以;但不要因此去重装 Ollama。

2.4 对话里的斜杠指令

ollama run 之后,提示符变成 >>>,这时候能用的是另一套指令:

指令作用什么时候用
/?列出所有可用指令忘了有哪些指令时,第一个敲它
/show info看当前模型的参数量、量化、上下文长度确认自己用的到底是哪一档
/set parameter 名 值临时调生成参数试火候,比如把 temperature 调低
/set system "..."临时设人设试提示词效果,不用改代码
/load 模型在对话里切换模型对比两个模型的回答
/clear清空上下文模型被之前的对话带偏了,或者要换话题
/bye退出对话回到系统终端
⚠️ /set 改的东西退出就没了 斜杠指令的作用范围只有当前这次会话/bye 之后再 run 一次,所有设置都回到默认值。想让设置长期生效,有两条路:写进 Modelfile 固化成一个新模型(见 4.3),或者在每次 API 请求里传 options(见《Ollama API 与客户端接入》)。别指望 /set 能存住。

2.5 四个生成参数

调参本质上是在几个滑块之间找平衡,每个滑块往两端拨各有代价:

图③ 四个生成参数各自往哪个方向拨
图③ 四个生成参数各自往哪个方向拨
参数默认值调低会怎样调高会怎样
temperature0.8保守、稳定,但容易重复发散、有创意,但容易胡编
top_k40每步只从最稳的几个词里挑候选词变多,表达更多样也更容易跑偏
top_p0.9只在高概率词里选,输出更集中纳入更多低概率词,输出更多样
num_ctx4096记得少,省显存记得多,显存占用同步上涨

上面这张表记不住也没关系——自己跑一遍十秒钟就懂了。对话里的 /set parameter 一次只能试一个值、退出还会丢,下面这个脚本把整条梯度一次跑完,并把几轮回答的相似度量化出来

param_explore.py —— 同一个问题扫一遍参数梯度,看稳定性怎么变调参实验台
"""调参实验台:同一个问题,在不同参数下各跑几遍,把差异摆到眼前。

为什么需要它:
    「temperature 调低会更稳定」这句话,看文档记不住,
    自己跑一遍、看着同一个问题在 0.0 下三次回答一模一样、
    在 1.2 下三次各说各话,十秒钟就理解了。

对话里的 /set parameter 一次只能试一个值,退出还会丢。
这个脚本一次把整条梯度跑完,并把结果并排打印出来。

用法:
    python3 param_explore.py --model qwen2:1.5b
    python3 param_explore.py --model qwen2:1.5b --param top_p --values 0.1,0.5,0.9
    python3 param_explore.py --model qwen2:1.5b --prompt "给这段日志写一句故障摘要"

依赖:只用标准库。
"""
import argparse
import difflib
import json
import os
import sys
import urllib.request

BASE = os.environ.get("OLLAMA_BASE", "http://127.0.0.1:11434").rstrip("/")

# 默认问一个「有标准答案但允许不同表述」的问题:
# 太开放(写首诗)看不出稳定性,太封闭(1+1)所有参数都一样。
DEFAULT_PROMPT = "用一句话说明什么是私有化部署。"


def ask(model, prompt, options, timeout=180):
    payload = {
        "model": model,
        "messages": [{"role": "user", "content": prompt}],
        "stream": False,
        "options": options,
    }
    req = urllib.request.Request(
        BASE + "/api/chat",
        data=json.dumps(payload).encode("utf-8"),
        headers={"Content-Type": "application/json"},
    )
    with urllib.request.urlopen(req, timeout=timeout) as resp:
        return json.loads(resp.read().decode("utf-8"))["message"]["content"].strip()


def similarity(answers):
    """两两比对答案的相似度,用来量化「稳不稳定」。

    用标准库的 difflib 做字符级比对就够了:
    这里要的不是语义相似度,而是「是不是几乎逐字一样」。
    返回 0~1,越接近 1 表示几轮回答越像。
    """
    if len(answers) < 2:
        return 1.0
    scores = []
    for i in range(len(answers)):
        for j in range(i + 1, len(answers)):
            scores.append(difflib.SequenceMatcher(None, answers[i], answers[j]).ratio())
    return sum(scores) / len(scores)


def run_sweep(model, prompt, param, values, repeat):
    print("模型:%s" % model)
    print("问题:%s" % prompt)
    print("扫描参数:%s    每档重复 %d\n" % (param, repeat))

    summary = []
    for v in values:
        # 注意这里每一档都是独立的一次次请求,互相之间没有上下文,
        # 所以差异完全来自参数本身,不会被前面的对话影响。
        options = {param: v}
        print("=" * 60)
        print("%s = %s" % (param, v))
        print("-" * 60)

        answers = []
        for k in range(repeat):
            try:
                a = ask(model, prompt, options)
            except Exception as exc:
                print("  第 %d 次失败:%s" % (k + 1, exc))
                continue
            answers.append(a)
            print("  [%d] %s" % (k + 1, a))

        if answers:
            sim = similarity(answers)
            avg_len = sum(len(a) for a in answers) / len(answers)
            summary.append((v, sim, avg_len))
            print("  —— 几次回答的平均相似度 %.2f,平均长度 %.0f 字" % (sim, avg_len))
        print()

    if summary:
        print("=" * 60)
        print("汇总(相似度越高=越稳定、越可复现)")
        print("-" * 60)
        print("%-12s %-12s %s" % (param, "相似度", "平均长度"))
        for v, sim, ln in summary:
            bar = "█" * int(sim * 20)
            print("%-12s %-12.2f %-6.0f %s" % (v, sim, ln, bar))
        print()
        print("解读:")
        print("  · 相似度接近 1.00 —— 几乎每次都给同样的话,适合分类、抽取这类任务")
        print("  · 相似度明显下降 —— 表达开始发散,适合创作,但也更容易跑偏")
        print("  · 注意看平均长度:有些参数不改变稳定性,只改变啰嗦程度")


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--model", required=True, help="模型全名,例如 qwen2:1.5b")
    ap.add_argument("--prompt", default=DEFAULT_PROMPT)
    ap.add_argument("--param", default="temperature",
                    help="要扫描的参数名:temperature / top_p / top_k")
    ap.add_argument("--values", default="",
                    help="逗号分隔的取值;不给就按参数名用内置梯度")
    ap.add_argument("--repeat", type=int, default=3,
                    help="每档重复几次;至少 2 次才看得出稳定性")
    args = ap.parse_args()

    # 不同参数的取值范围不一样,内置梯度按参数名分别给
    presets = {
        "temperature": [0.0, 0.3, 0.8, 1.2],
        "top_p": [0.1, 0.5, 0.9, 1.0],
        "top_k": [1, 10, 40, 100],
    }
    if args.values:
        raw = [x.strip() for x in args.values.split(",") if x.strip()]
        values = [int(x) if args.param == "top_k" else float(x) for x in raw]
    else:
        values = presets.get(args.param)
        if values is None:
            print("参数 %s 没有内置梯度,请用 --values 指定" % args.param)
            return 1

    run_sweep(args.model, args.prompt, args.param, values, args.repeat)
    return 0


if __name__ == "__main__":
    sys.exit(main())
看到的现象说明什么
相似度接近 1.00几乎每次都给同样的话,适合分类、抽取这类要求可复现的任务
相似度明显下降表达开始发散,适合创作,但也更容易跑偏
相似度没变、长度变了这个参数对当前任务只改变啰嗦程度,不改变稳定性
扫参数要选对问题 脚本默认问的是一个有标准答案、但允许不同表述的问题。这是故意的:问「写首诗」太开放,每档都不一样,看不出稳定性差异;问「1+1 等于几」太封闭,所有参数下答案都一样。换成你自己的业务问题再跑一遍,得出的参数才是你能用的。
✅ 实用的调参口径 确定性任务(分类、抽取、改写)把 temperature 调到 0.1~0.3,创作类任务保持默认或往上调。top_ktop_p 一般不用动——先把 temperature 调对,绝大多数问题就解决了。num_ctx 只在确实需要处理长文档时才调大,而且调之前先算显存账。

03最小代码:从一台干净机器到第一次对话

五步,每步都带一个验证动作

整个上手过程只有五步。关键不在于命令本身,而在于每一步后面那条验证命令——上一步没验证通过就往下走,最后出了问题根本不知道断在哪一层。

① 装ollama -v 打得出版本号
② 起服务ss -lntp 看到 11434 在监听
③ 拉模型ollama list 里能看到它
④ 对话模型真的回了一句人话
⑤ 收尾确认ollama ps 看跑的是 GPU 还是 CPU
ollama_first_run.sh —— 首次部署的最小可复现路径,每步带验证上手脚本
#!/bin/bash
# 最小可复现路径:从一台干净的 Linux 机器,到能跟模型说上话。
# 每一步都带验证动作,上一步没验证通过就别走下一步。
set -euo pipefail

# ---------- ① 安装 ----------
# 方式 A:离线包(课堂环境用这个,安装包已经在本地)
#   tar -xzf ollama-linux-amd64.tgz -C /usr
# 方式 B:联网一键脚本(工作中常用,会顺带把 systemd 服务也装好)
#   curl -fsSL https://ollama.com/install.sh | sh
ollama -v      # 打得出版本号才算装上;报 command not found 就是 PATH 没生效

# ---------- ② 起服务 ----------
# 前台起(调试用,关掉终端服务就没了):ollama serve
# 后台托管(生产用,开机自启):
systemctl start ollama
systemctl status ollama --no-pager | head -5   # 要看到 active (running)

# 端口真的在监听吗?这一步最容易被跳过
ss -lntp | grep 11434 || echo "11434 没在监听,先看 journalctl -u ollama"

# ---------- ③ 拉模型 ----------
# 模型名必须写全「名字:版本」;不写版本默认拉 latest,体积可能大得多
ollama pull qwen2:1.5b

ollama list    # 拉下来的模型在这里,看 NAME 和 SIZE 两列

# ---------- ④ 对话 ----------
# 交互式:进去后用 /bye 退出
#   ollama run qwen2:1.5b
# 一次性问答:带上提示词,答完自动退出,适合写进脚本
ollama run qwen2:1.5b "用一句话说明什么是私有化部署"

# ---------- ⑤ 收尾确认 ----------
ollama ps      # 看模型是不是还占着内存;PROCESSOR 列显示 GPU 还是 CPU
这一步失败了说明断在哪先查什么
ollama -v 报 command not found命令没装上或 PATH 没生效确认解压目录、重开一个终端
11434 没在监听服务没起来journalctl -u ollama -n 30
pull 卡住或很慢网络问题换网络或用离线包;模型几个 G,慢是正常的
对话能进去但答得极慢在用 CPU 跑ollama psPROCESSOR
⚠️ 第 ③ 步的模型名必须写全 ollama pull qwen2ollama pull qwen2:1.5b 拉到的不是同一个东西。前者拉的是仓库默认标签,体积可能大得多,而且不同时间拉到的可能不一样。凡是写进脚本和文档的模型名,一律带上冒号版本号,否则同事照着你的文档跑出来的结果和你对不上。
第 ④ 步的两种用法 ollama run 模型 会进入交互式对话,用 /bye 退出;ollama run 模型 "问题" 则是答完就退出。写进脚本的一律用后者——前者会让脚本停在对话界面上等输入,整个自动化流程就卡死了。

04完整案例:三件真会遇到的事

上手、运维、固化人设——这三件做完,命令行这一关就过了

4.1 案例一 · 从零到第一次对话

就是上一节那个脚本。这里补充一遍各步骤的正常输出长什么样,好让你知道自己是不是走对了。

命令正常输出的特征不正常的样子
ollama -v一行版本号command not found:PATH 没生效
ollama list表头 NAME SIZE MODIFIED,可以没有内容报连接错误:服务没起
ollama pull进度条走到 success长时间不动:网络问题
ollama run提示符变成 >>>卡在加载:模型太大、显存不够
ollama psPROCESSOR 列显示 100% GPU显示 CPU 或混合:溢出了
空的 list 也是好消息 刚装完 ollama list 只打印一行表头、没有任何模型,这是正确的。它证明客户端和服务之间通了。很多人看到「什么都没有」以为装失败了,又去重装一遍——白折腾。

4.2 案例二 · 日常模型运维

场景:机器磁盘快满了,要在不影响线上服务的前提下腾空间。顺序是盘点 → 体检 → 看底细 → 清理 → 复核,一步都不能跳。

model_ops.sh —— 模型仓库的日常运维:盘点、体检、清理、复核运维脚本
#!/bin/bash
# 模型仓库的日常运维:盘点 → 体检 → 清理 → 复核。
# 场景:机器磁盘快满了,要在不影响线上服务的前提下腾空间。
set -euo pipefail

KEEP_MODEL="${KEEP_MODEL:-qwen2:1.5b}"   # 线上正在用的,绝对不能删

echo "===== ① 盘点:本地一共有哪些模型 ====="
ollama list

echo
echo "===== ② 体检:谁正占着内存/显存 ====="
# PROCESSOR 列是关键:100% GPU 才是全进显存;出现 CPU 说明显存不够溢出了
# UNTIL 列是 keep_alive 到期时间,到点模型自动卸载
ollama ps

echo
echo "===== ③ 看清楚线上这个模型的底细 ====="
# 不用把模型跑起来就能看信息
ollama show "$KEEP_MODEL"                  # 参数量、量化等级、上下文长度
ollama show "$KEEP_MODEL" --parameters     # 内置的停止词等参数
ollama show "$KEEP_MODEL" --template       # 提示词模板,排查「答非所问」时要看它

echo
echo "===== ④ 清理:删掉不再需要的模型 ====="
# 注意:ollama rm 只认模型全名,不能像 docker 那样用 ID 前缀
for m in $(ollama list | awk 'NR>1 {print $1}'); do
  if [ "$m" = "$KEEP_MODEL" ]; then
    echo "跳过线上模型:$m"
    continue
  fi
  echo "待删除:$m"
  # 真要删就去掉下面这行的注释;保持注释状态时这个脚本是只读的
  # ollama rm "$m"
done

echo
echo "===== ⑤ 复核:确认线上模型还在、还能答 ====="
ollama list | grep -q "^${KEEP_MODEL}" && echo "线上模型仍在:$KEEP_MODEL"
# 让它立刻卸载,把显存还回来(keep_alive 传 0)
curl -s http://127.0.0.1:11434/api/generate \
  -d "{\"model\":\"${KEEP_MODEL}\",\"keep_alive\":0}" > /dev/null
echo "已请求卸载模型,再看一次 ps:"
ollama ps
步骤命令为什么不能跳
盘点ollama list先知道有什么,才知道能删什么
体检ollama ps正在跑的模型不能删,这一步就是防止误删
看底细ollama show确认线上那个到底是哪一档,别删错了同名不同版本的
清理ollama rm真正腾空间的一步
复核list + 发一次请求确认线上模型还在、还能答,才算收工
⚠️ ollama rm 只认全名,而且删了就没了 不能像 Docker 那样用 ID 前缀。更要紧的是:删掉之后要重新下载才能恢复,几个 G 的模型可能要拉很久。所以脚本里那行 ollama rm注释掉的——先跑一遍看清楚要删哪些,确认无误再放开注释。这个习惯值得保留到所有批量删除脚本里。

机器上模型一多,人要在脑子里把 listps 两边的信息拼起来,这个拼接开始出错——删掉了正在用的,或者留着一个半年没人碰的 20G 模型占着数据盘。把两边合成一张表就清楚了:

model_inventory.py —— 合并 list 与 ps,给出只读的清理建议盘点工具
"""模型盘点:磁盘上有什么、显存里跑着什么、哪些可以删。

`ollama list` 和 `ollama ps` 各给一半信息,人要自己在脑子里拼。
机器上模型一多,这个拼接就开始出错 —— 删掉了正在用的,
或者留着一个半年没人碰的 20G 模型占着数据盘。

这个脚本把两边的信息合到一张表里,并给出清理建议。
它**只读**,不会删任何东西:删除命令打印出来,由你自己复制执行。

用法:
    python3 model_inventory.py
    python3 model_inventory.py --keep qwen2:1.5b,deepseek-r1:7b

依赖:只用标准库,走 HTTP 接口,不依赖 ollama 命令是否在 PATH 里。
"""
import argparse
import json
import os
import sys
import urllib.error
import urllib.request

BASE = os.environ.get("OLLAMA_BASE", "http://127.0.0.1:11434").rstrip("/")


def get(path):
    """GET 一个接口,拿不到就抛出带说明的错误。"""
    try:
        with urllib.request.urlopen(BASE + path, timeout=15) as resp:
            return json.loads(resp.read().decode("utf-8"))
    except urllib.error.URLError as exc:
        raise RuntimeError(
            "连不上 %s%s —— 先确认服务在跑:systemctl is-active ollama" % (BASE, path)
        ) from exc


def human(n):
    """字节数转成人看得懂的单位。"""
    for unit in ("B", "KB", "MB", "GB", "TB"):
        if n < 1024 or unit == "TB":
            return "%.1f%s" % (n, unit)
        n /= 1024


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--keep", default="",
                    help="逗号分隔的保留清单,这些模型不会出现在清理建议里")
    args = ap.parse_args()
    keep = set(x.strip() for x in args.keep.split(",") if x.strip())

    # /api/tags 对应 ollama list:磁盘上有哪些
    disk = get("/api/tags").get("models", [])
    # /api/ps 对应 ollama ps:显存里跑着哪些
    loaded = {m["name"]: m for m in get("/api/ps").get("models", [])}

    if not disk:
        print("磁盘上一个模型都没有。先 ollama pull 一个。")
        return 0

    total = sum(m.get("size", 0) for m in disk)
    print("=" * 74)
    print("本地模型盘点   共 %d 个,合计占用 %s" % (len(disk), human(total)))
    print("=" * 74)
    print("%-28s %-10s %-12s %-10s %s"
          % ("模型", "大小", "量化", "参数量", "状态"))
    print("-" * 74)

    # 按体积从大到小排:要腾空间时,先看最上面几行就够了
    for m in sorted(disk, key=lambda x: -x.get("size", 0)):
        name = m["name"]
        det = m.get("details", {}) or {}
        state = "显存中" if name in loaded else "仅磁盘"
        if name in keep:
            state += " · 保留"
        print("%-28s %-10s %-12s %-10s %s"
              % (name,
                 human(m.get("size", 0)),
                 det.get("quantization_level", "-"),
                 det.get("parameter_size", "-"),
                 state))

    # 正在显存里的单独列一遍,带上到期时间
    if loaded:
        print()
        print("当前占着内存/显存的模型:")
        for name, m in loaded.items():
            # expires_at 就是 keep_alive 到期的时刻,到点自动卸载
            print("  · %-26s 占用 %-10s 到期 %s"
                  % (name, human(m.get("size", 0)), m.get("expires_at", "未知")))
    else:
        print()
        print("当前没有模型占用内存/显存。")

    # 清理建议:只挑「不在保留清单、且当前没在跑」的
    candidates = [m for m in disk
                  if m["name"] not in keep and m["name"] not in loaded]
    print()
    if candidates:
        freeable = sum(m.get("size", 0) for m in candidates)
        print("可清理候选(不在保留清单、且当前没在跑),共可腾出 %s:" % human(freeable))
        for m in sorted(candidates, key=lambda x: -x.get("size", 0)):
            print("  ollama rm %s        # %s" % (m["name"], human(m.get("size", 0))))
        print()
        print("⚠️  上面只是打印,脚本不会替你删。")
        print("    删之前再确认一遍:这个模型有没有被别的服务或定时任务用着。")
    else:
        print("没有可清理的候选:要么都在保留清单里,要么都正在跑。")

    return 0


if __name__ == "__main__":
    try:
        sys.exit(main())
    except RuntimeError as exc:
        print(exc)
        sys.exit(1)
✅ 为什么它只打印不删 脚本把 ollama rm 命令打印出来让你自己复制执行,而不是直接跑。这不是偼懒——模型删了要重新下载,几个 G 拉很久,而「这个模型有没有被别的服务或定时任务用着」脚本判断不了。所有批量删除工具都应该守这个规矩。
立刻释放显存的办法 不想等 keep_alive 到期,就发一次 keep_alive0 的请求:
curl -s http://127.0.0.1:11434/api/generate -d '{"model":"qwen2:1.5b","keep_alive":0}'
发完再 ollama ps,这个模型就从列表里消失了。这是唯一不用重启服务就能卸载单个模型的办法。

4.3 案例三 · 用 Modelfile 固化人设

前面说过,/set 改的东西一退出就没了。要让「人设 + 参数」长期生效,正确做法是写一个 Modelfile,把它烤成一个新模型

Modelfile.skeleton —— 把人设与默认参数固化成私有模型骨架模板
# 骨架模板:把一个通用模型固化成「带人设、带默认参数」的私有模型。
#
# 用法:
#   1. 复制成 Modelfile,按 TODO 改
#   2. ollama create 你的模型名 -f Modelfile
#   3. ollama run 你的模型名
#   4. ollama list 里会多出这一条,像普通模型一样使用
#
# 为什么要做这个:把「人设 + 参数」写死在模型里,业务代码就不必每次都传一长串
# system 提示词和 options,换模型时也只改这一个文件。

# TODO: 换成本地已经拉好的基座模型全名(ollama list 里能查到的那个)
FROM qwen2:1.5b

# ---------- 人设 ----------
# TODO: 改成你自己的业务角色设定。写得越具体,跑偏的概率越低。
SYSTEM """
你是某公司内部的运维助手。
回答只依据用户提供的信息,不确定就直接说不确定,不要编造命令和参数。
回答用中文,先给结论,再给步骤。
"""

# ---------- 默认生成参数 ----------
# 这些值等价于对话里敲 /set parameter,区别是这里写死、重启也在

# TODO: 确定性任务(分类、抽取)调低到 0.1~0.3;创作类才往上调
PARAMETER temperature 0.3

# TODO: 上下文长度。调大更能记事,但显存占用同步上涨,并发时成倍放大
PARAMETER num_ctx 4096

# TODO: 单次回答最多生成多少 token,防止模型长篇大论刷屏
PARAMETER num_predict 512

# TODO: 遇到这些串就停止生成,按你的输出格式来定;不需要就整行删掉
# PARAMETER stop "###"

# ---------- 提示词模板 ----------
# 一般不用动:模板由基座模型自带,改错会导致模型答非所问。
# 确实要改时,先用 `ollama show 基座模型 --template` 把原版抄出来再改。

三步走:

① 写 ModelfileFROM 基座 + SYSTEM 人设 + PARAMETER
② createollama create 新名字 -f Modelfile
③ 当普通模型用ollama run 新名字,list 里也能看到
指令作用要不要改
FROM基于哪个已有模型必改,写本地已拉好的全名
SYSTEM固化的人设提示词必改,写得越具体跑偏越少
PARAMETER默认生成参数按任务类型调 temperaturenum_ctx
TEMPLATE提示词模板一般不要动,改错会导致答非所问

骨架看完还是不知道「写多具体算具体」,对照一份填好的就清楚了。下面这份是一个制造企业内部知识库助手,人设里身份、边界、输出格式三件事都写死了——缺任何一条,模型都会自由发挥:

Modelfile.kb-assistant —— 填好的实例:内部知识库问答助手实例
# 填好的实例:一个内部知识库问答助手。
# 对照 Modelfile.skeleton 看,能直观看出「该写多具体」。
#
# 落地:
#   ollama create kb-assistant -f Modelfile.kb-assistant
#   ollama run kb-assistant
#   ollama list      # 它会像普通模型一样出现在列表里

FROM qwen2:1.5b

# 人设写得越具体,跑偏概率越低。
# 注意三件事都写死了:身份、边界、输出格式 —— 缺任何一条模型都会自由发挥。
SYSTEM """
你是某制造企业的内部知识库助手,服务对象是一线生产和质检人员。

回答规则:
1. 只依据用户在问题中提供的信息回答。用户没给的内容,直接说「资料里没有,需要人工确认」,不要推测。
2. 涉及安全操作、设备参数、质量判定标准时,必须提醒对方以现行作业指导书为准。
3. 回答用中文,先给结论,再给依据,最后给下一步动作。总长度控制在五句话以内。
4. 不回答与生产业务无关的问题,礼貌拒绝即可。
"""

# 知识库问答属于确定性任务:同一个问题应该每次给同样的答案。
# 温度压到 0.2,配合上面的「不要推测」,能显著减少编造。
PARAMETER temperature 0.2

# top_p 一起收紧,进一步压住发散。
PARAMETER top_p 0.7

# 用户会把整段作业指导书贴进来,上下文要够。
# 但注意:这个值直接决定 KV 缓存大小,并发场景下要重新算显存账。
PARAMETER num_ctx 8192

# 回答限制在五句话以内,生成上限给 400 就够,防止模型长篇大论。
PARAMETER num_predict 400
这份实例里的选择为什么这么定
temperature 0.2知识库问答是确定性任务,同一个问题应该每次给同样的答案
top_p 0.7和低温度配合,进一步压住发散;这是少数值得动 top_p 的场景
num_ctx 8192用户会把整段作业指导书贴进来,4096 不够装
num_predict 400人设要求五句话以内,给上限防止模型啰嗦
「资料里没有,需要人工确认」把「不知道」给出固定说法,比写「不要编造」有效得多
✅ 固化的好处不只是省事 把人设写死在模型里,业务代码就不必每次都传一长串 system 提示词和 options。更重要的是可复现:同事拿到的是同一个模型名,行为就是一致的,不会因为谁忘了传参数而结果不同。换模型时也只改这一个文件。
⚠️ create 出来的不是副本,是引用 它不会把基座模型再复制一份,所以几乎不额外占磁盘。但反过来说,基座模型被 rm 掉之后,你这个自定义模型也会跟着不能用。清理磁盘时要留意这层依赖关系。

05骨架模板:三份脚本怎么配合用

一份管上手,一份管日常,一份管固化

前面三个案例各自对应一份可复用的文件。它们的分工是这样的:

文件什么时候跑核心价值
ollama_first_run.sh拿到新机器的第一天每步带验证,断在哪一层一目了然
model_ops.sh磁盘告警、显存告急时默认只读(删除命令是注释的),先看清再动手
Modelfile.skeleton业务人设定下来之后把人设和参数固化,保证团队行为一致

5.2 把命令行用成生产力:离线批处理

上一讲的结论是「纯 CPU 机器只适合离线批处理」——这就是那个场景的落地形式。机器慢没关系,晚上挂着跑,第二天来看结果;而且几百条数据全要出网的话,调云端 API 反而又贵又不合规。

batch_ask.py —— 批量跑问题并存成 CSV,支持断点续跑批处理
"""批量跑:把一个文件里的几百条问题喂给本地模型,结果存成 CSV。

私有化部署最划算的用法之一就是离线批处理 ——
机器慢没关系,晚上挂着跑,第二天来看结果。
这类活儿交给云端 API 反而贵,而且几百条数据全要出网。

这个脚本负责把这件事做稳:
    · 断点续跑:中途挂了重跑,已完成的不再重复调用
    · 逐条落盘:跑一条写一条,进程被杀也不丢前面的结果
    · 失败隔离:单条失败记下错误继续跑,不让一条脏数据毁掉整批

用法:
    python3 batch_ask.py --model qwen2:1.5b --input questions.txt --output result.csv
    python3 batch_ask.py --model qwen2:1.5b --input questions.txt \
        --system "你是质检助手,只回答是或否。"

依赖:只用标准库。
"""
import argparse
import csv
import json
import os
import sys
import time
import urllib.error
import urllib.request

BASE = os.environ.get("OLLAMA_BASE", "http://127.0.0.1:11434").rstrip("/")


def ask(model, system, question, timeout, retries=2):
    """问一条。失败重试,重试仍失败就抛出去由上层记录。"""
    messages = []
    if system:
        messages.append({"role": "system", "content": system})
    messages.append({"role": "user", "content": question})

    payload = {
        "model": model,
        "messages": messages,
        "stream": False,
        # 批处理要可复现:同一批数据重跑,结果应该一致
        "options": {"temperature": 0},
    }
    body = json.dumps(payload).encode("utf-8")

    last = None
    for attempt in range(retries + 1):
        try:
            req = urllib.request.Request(
                BASE + "/api/chat", data=body,
                headers={"Content-Type": "application/json"})
            with urllib.request.urlopen(req, timeout=timeout) as resp:
                data = json.loads(resp.read().decode("utf-8"))
            return data["message"]["content"].strip(), data.get("eval_count", 0)
        except (urllib.error.URLError, TimeoutError, KeyError) as exc:
            last = exc
            if attempt < retries:
                # 退避重试:服务在加载模型或正忙时,等一下往往就好了
                time.sleep(2 ** attempt)
    raise RuntimeError(str(last))


def load_done(path):
    """读已有的输出文件,拿到已经跑完的行号,用于断点续跑。

    几百条跑到一半断了,从头再来既费时间又浪费电。
    """
    done = set()
    if not os.path.exists(path):
        return done
    with open(path, encoding="utf-8", newline="") as f:
        for row in csv.DictReader(f):
            if row.get("index"):
                done.add(int(row["index"]))
    return done


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--model", required=True)
    ap.add_argument("--input", required=True, help="问题文件,一行一条")
    ap.add_argument("--output", default="batch_result.csv")
    ap.add_argument("--system", default="", help="统一人设,可留空")
    ap.add_argument("--timeout", type=int, default=300)
    args = ap.parse_args()

    with open(args.input, encoding="utf-8") as f:
        questions = [ln.strip() for ln in f if ln.strip()]
    if not questions:
        print("输入文件里没有问题")
        return 1

    done = load_done(args.output)
    if done:
        print("检测到已有结果 %d 条,将跳过这些继续跑。" % len(done))

    # 追加模式打开;文件不存在时先写表头
    new_file = not os.path.exists(args.output)
    fout = open(args.output, "a", encoding="utf-8", newline="")
    writer = csv.writer(fout)
    if new_file:
        writer.writerow(["index", "question", "answer", "tokens", "seconds", "error"])
        fout.flush()

    ok = fail = 0
    t_start = time.time()
    for i, q in enumerate(questions, 1):
        if i in done:
            continue
        t0 = time.time()
        try:
            answer, tokens = ask(args.model, args.system, q, args.timeout)
            err = ""
            ok += 1
        except RuntimeError as exc:
            # 单条失败不中断整批:把错误写进结果文件,人工再看
            answer, tokens, err = "", 0, str(exc)
            fail += 1

        writer.writerow([i, q, answer, tokens, "%.1f" % (time.time() - t0), err])
        # 每条都 flush:进程被杀也不会丢掉前面已经跑出来的结果
        fout.flush()

        status = "OK " if not err else "FAIL"
        print("[%s] %d/%d  %.1fs  %s"
              % (status, i, len(questions), time.time() - t0, q[:30]))

    fout.close()
    elapsed = time.time() - t_start
    print("-" * 56)
    print("完成 %d 条,失败 %d 条,总耗时 %.1f 分钟" % (ok, fail, elapsed / 60))
    print("结果已写入 %s" % args.output)
    if fail:
        print("⚠️  有失败记录,在 CSV 的 error 列里;修好之后重跑同一条命令即可续跑。")
    return 0


if __name__ == "__main__":
    sys.exit(main())

跑几百条的脚本和跑一条的脚本,差别全在下面这三件事上:

机制怎么实现的不做会怎样
断点续跑先读输出 CSV,已完成的行号跳过跑到一半断了只能从头来,一晚白跑
逐条落盘每写一行就 flush()进程被杀,内存里的结果全丢
失败隔离单条失败写进 error 列,继续跑一条脏数据抛异常,整批停在第 37 条
批处理的 temperature 固定为 0 和压测一个道理:同一批数据重跑,结果应该一致。否则今天筛出 40 条异常、明天重跑变成 43 条,你无法向任何人解释这三条是怎么多出来的

5.3 把这几份接进真实流程

单独跑没问题,接进团队流程时还要注意几件事:

  • 首次部署脚本要幂等。它可能被跑很多遍。ollama pull 本身是幂等的(已存在就只检查更新),但 systemctl start 在服务已跑时会静默成功——这没问题,别画蛇添足去先 stop
  • 运维脚本永远保持「默认只读」。删除类命令一律注释掉,让使用者主动放开。这条习惯能挡住绝大多数误删事故。
  • Modelfile 要进版本库。它是一份配置,不是一次性命令。进了 Git,人设改了哪一版、谁改的才查得到。
⚠️ 别把这三份脚本合成一个 看起来都是 Ollama 相关的操作,但触发时机完全不同:一个是一次性的,一个是周期性的,一个是配置。合成一个「万能脚本」的结果,是每次只想做其中一件事,却要小心翼翼地注释掉另外两段——反而更容易出事。
✅ 生产环境还差一步 这三份脚本假设服务已经由 systemd 托管。手动 ollama serve 起的服务关掉终端就没了,不能用于生产。怎么写 service 文件、怎么开远程访问、怎么控并发,是下一讲《Linux 企业级部署》的内容。

06易错点汇总

按「命令位置 / 安装启动 / 模型管理 / 对话与调参」四类归并

⚠️ 一、命令敲错了地方

  • 在系统终端敲斜杠指令。比如直接敲 /show,shell 报 command not found。斜杠指令必须先 ollama run 进对话。
  • 在对话里敲 ollama list这个更坑——模型不会报错,它会当成普通问题,给你编一段像模像样的「本地模型列表」。那个列表是假的。看到输出是自然语言而不是表格,就说明敲错地方了。
  • 看不出自己在哪一边。看提示符:$ / # 是系统终端,>>> 是对话里。这一眼能省掉大量瞎猜。
  • /bye 之后以为还在对话里。退出之后再敲斜杠指令就会报 command not found,白排查半天。

⚠️ 二、安装与启动

  • 只验证了 ollama -v 就认为装好了。命令装上不等于服务起来了。必须再跑一次 ollama list,能返回(哪怕是空的)才算通。
  • 服务已经在跑还去敲 ollama serve会报 address already in use。这不是故障,恰恰说明服务是好的。要重启用 systemctl restart ollama
  • 手动 serve 起的服务当成生产可用。关掉终端就没了。生产必须 systemd 托管。
  • 刚装完 list 是空的就以为失败。空列表是正确的,它恰好证明客户端和服务通了。别重装。
  • 没改 OLLAMA_MODELS 就开始拉模型。默认落在系统盘,拉几个就满。改路径之后已拉的模型不会自动搬家,所以这件事要在拉模型之前做。

⚠️ 三、模型管理

  • 模型名不写版本号。qwen2qwen2:1.5b 拉到的不是同一个东西。凡是写进脚本和文档的一律带冒号版本,否则同事跑出来的结果和你对不上。
  • listps 搞混。list磁盘上有什么ps显存里跑着什么。排查显存占用只能看 ps
  • 「list 里有但 ps 里没有」以为出故障了。正常——模型还没被加载而已。
  • 用 ID 前缀删模型。ollama rm 只认全名,不像 Docker 那样支持前缀。
  • 删之前没确认哪个在线上用。删掉要重新下载才能恢复,几个 G 拉很久。运维脚本里的删除命令默认注释掉,先看清单再放开。
  • 删了基座模型,自定义模型跟着废了。ollama create 出来的是引用不是副本,存在依赖关系。
  • 部署脚本里用 ollama run 拉模型。它会进对话界面等输入,脚本卡死在那儿。脚本里一律用 pull,或者 run 模型 "问题" 这种一次性形式。

⚠️ 四、对话与调参

  • 以为 /set 能存住。斜杠指令只在当前会话有效/bye 之后全部回到默认。要长期生效就写 Modelfile,或在 API 请求里传 options
  • 「我没在用,显存怎么还占着」。十有八九是 keep_alive 还没到期(默认 5 分钟)。这是设计行为,不是内存泄漏,别因此重装。
  • 不知道怎么立刻释放显存。发一次 keep_alive0 的请求即可,这是唯一不用重启服务就能卸载单个模型的办法。
  • 一上来就调 top_k / top_p先把 temperature 调对,绝大多数问题就解决了。确定性任务调到 0.1~0.3。
  • 随手把 num_ctx 拉到很大。它直接决定 KV 缓存大小,显存占用同步上涨,并发场景下还要再乘并发数。调之前先算账。
  • 改了 ModelfileTEMPLATE模板由基座模型自带,改错会导致模型答非所问。确实要改,先用 ollama show 模型 --template 把原版抄出来。

07自测题

点击题目展开答案;能把这 18 题说清楚,命令行这一关就过了

一、两套命令
怎么一眼看出自己现在该敲哪套命令?

看提示符。$# 是系统终端,敲ollama 前缀的命令;>>> 是模型对话里,敲/ 前缀的指令。

在对话框里敲 ollama list 会发生什么?为什么这比在终端敲斜杠指令更危险?

模型会把它当成一个普通问题,编一段像模像样的「本地模型列表」。危险在于它不报错——而在系统终端敲 /show,shell 会直接报 command not found,你立刻知道错了。判断办法:输出是自然语言而不是表格,就说明敲错地方了。

ollama 命令和 Ollama 服务是什么关系?

ollama客户端,它把你的命令变成 HTTP 请求发给 127.0.0.1:11434 上常驻的服务进程,服务才是真正加载模型、跑推理的那个。所以「命令装上了」和「服务起来了」是两件事。

关掉终端窗口,为什么模型还占着显存?

因为模型是加载在后台服务进程里的,和你那个终端窗口没有关系。要释放得等 keep_alive 到期,或者发一次 keep_alive 为 0 的请求,或者重启服务。

二、安装与启动
装完之后用哪两条命令验证?只过第一条说明什么?

ollama --version 验证命令装上了;ollama list 验证服务通了。只过第一条说明命令有了但服务没起来——这是 Linux 手动解压安装最常见的状态,一键脚本才会顺带装好 systemd 服务。

刚装完 ollama list 什么都没有,是不是装失败了?

不是。空列表(只有表头)是正确的,它恰恰证明客户端和服务之间通了。很多人看到「什么都没有」就去重装,白折腾。

ollama serveaddress already in use,该怎么处理?

说明 11434 端口已经被现有服务占着——这不是故障,恰恰说明服务是好的,直接用就行。真要重启用 systemctl restart ollama,不要用「再敲一遍 serve」这种方式。

什么情况下才需要手动敲 ollama serve

只有 Linux 手动解压安装的情况需要。Windows/macOS 安装包和 Linux 一键脚本都会自动起服务。而且手动 serve 起的服务关掉终端就没了,不能用于生产——生产必须 systemd 托管。

OLLAMA_MODELS 为什么要在拉模型之前就改好?

默认路径在系统盘,模型动辄几个到几十 GB,拉几个就满。而改路径之后已经拉过的模型不会自动搬家,要么手动移过去,要么重新拉——所以这件事必须在拉模型之前做。

三、模型管理
ollama listollama ps 分别查什么?

list磁盘上下载了哪些模型ps内存/显存里正跑着哪些。「list 里有但 ps 里没有」是正常的,只说明模型还没被加载。排查显存占用只能看 ps

pullrun 有什么区别?部署脚本里该用哪个?

pull 下载完就停;run 下载完直接进入对话界面。脚本里必须用 pull——用 run 会让脚本停在对话界面上等输入,整个自动化流程卡死。要在脚本里问一句话,用 ollama run 模型 "问题" 这种一次性形式。

为什么模型名一定要写成 qwen2:1.5b 而不是 qwen2

只写名字会拉到仓库的默认标签,体积可能大得多,而且不同时间拉到的可能不一样。团队里两个人各拉一次很可能拿到不同的模型,后面对不上性能数据就会互相扯皮。

ollama rm 能用 ID 前缀吗?删了能恢复吗?

不能用前缀,只认模型全名(不像 Docker)。删掉之后只能重新下载,几个 G 可能要拉很久。所以运维脚本里的删除命令应该默认注释掉,先跑一遍看清单,确认无误再放开。

ollama create 出来的自定义模型会额外占多少磁盘?

几乎不额外占——它是对基座模型的引用,不是副本。但反过来说,基座模型被删掉之后,自定义模型也跟着不能用了。清理磁盘时要留意这层依赖。

四、对话与调参
/set parameter temperature 0.3 改的设置能存住吗?

存不住。斜杠指令只在当前这次会话有效/bye 之后再 run,所有设置回到默认。要长期生效有两条路:写进 Modelfile 固化成新模型,或者在每次 API 请求里传 options

模型被前面的对话带偏了,怎么办?

/clear 清空上下文,相当于重新开一局。不需要退出再进。

「我没在用,显存怎么还占着」——怎么解释、怎么立刻释放?

keep_alive 还没到期(默认 5 分钟),设计行为,不是内存泄漏ollama ps 能看到还有多久过期。要立刻释放就发一次 keep_alive0 的请求,这是唯一不用重启服务就能卸载单个模型的办法

四个生成参数里,日常最该调哪一个?

temperature。确定性任务(分类、抽取、改写)调到 0.1~0.3,创作类保持默认 0.8 或往上。top_k(默认 40)和 top_p(默认 0.9)一般不用动;num_ctx(默认 4096)只在处理长文档时才调大,而且要先算显存账。

Modelfile 里哪一条指令最好不要动?为什么?

TEMPLATE。提示词模板由基座模型自带,改错会导致模型答非所问。确实要改,先用 ollama show 模型 --template 把原版抄出来再改。

命令速查表

系统终端里敲(前缀 ollama

命令作用要点
ollama -v看版本验证命令装上了;不验证服务
ollama serve前台启动服务关掉终端就没了,生产别用
ollama pull 模型下载模型脚本里用它,不要用 run
ollama list磁盘上有哪些模型空列表也算正常
ollama ps显存里跑着哪些PROCESSOR 列是判断溢出的唯一依据
ollama show 模型看模型底细不用跑起来就能看;加 --template 看模板
ollama run 模型进入交互对话提示符变成 >>>
ollama run 模型 "问题"问一次就退出适合写进脚本、配管道
ollama create 名 -f Modelfile固化人设成新模型是引用不是副本,依赖基座模型
ollama rm 模型删除模型只认全名;删了要重新下载

对话里敲(前缀 /

指令作用要点
/?列出所有指令忘了就敲它
/show info看当前模型信息确认自己用的是哪一档
/set parameter 名 值临时调参数退出即失效
/set system "..."临时设人设试提示词用,不用改代码
/load 模型切换模型方便横向对比回答
/clear清空上下文被带偏了就敲它,不用退出
/bye退出对话回到系统终端

关键环境变量

变量默认说明
OLLAMA_MODELS系统盘下的用户目录拉模型之前就要改,改后已有模型不会自动搬家
OLLAMA_KEEP_ALIVE5m模型用完在内存里留多久;-1 永不卸载,0 立刻卸载
OLLAMA_HOST127.0.0.1:11434监听地址,改远程访问用;下一讲展开
✅ 一句话收束本讲 命令不用背,记住那条生命周期就够了:远程仓库 →(pull)→ 本地磁盘 →(run)→ 显存list 看中间一段,ps 看最后一段,rm 往回删。再加上一条分界线——ollama 的在终端敲,带 / 的在对话里敲,这一讲就通了。