学校的服务器没给ROOT权限,这让很多操作受限,但得益于Linux优秀的系统特性,即使是普通用户也可以在权限允许的范围内运行ollama

本文使用的服务器配置在如何使用你的深度学习服务器一文中有所提及,故部分基础命令此处不再提及

部署ollama

由于没有ROOT权限,一键脚本是用不得了,只能手动部署

首先新建个文件夹来放ollama

1
mkdir -p ~/ollama && cd ~/ollama

服务器如果可以连上Github最好不过,可以用wget拉取ollama

1
wget -O ollama-linux-amd64.tgz https://ollama.com/download/ollama-linux-amd64.tgz

服务器要是不能连上Github,可以先把ollama-linux-amd64.tgz下载到本地,再用sftp(或类似工具)上传到服务器

下载/上传完成后,解压压缩包

1
tar -xvzf ollama-linux-amd64.tgz

解压完后,ollama文件夹下应该有binlib两个文件夹
image.png

使用如下命令启动ollama

1
./bin/ollama serve

示例输出如下(未指定GPU时默认使用全部的GPU)
image.png

再新建一个终端,进入ollama文件夹,运行命令就能看到ollama版本

1
./bin/ollama --version

image.pngollama 0.9.6

配置环境变量

如果只是临时用一下ollama,此节内容可选看

编辑配置文件

1
vim ~/.bashrc

在最下面插入内容,从第三行到第五行是可选变量,视实际情况使用;如第四行,家用计算机通常只有一块GPU就不需要指定GPU了

1
2
3
4
5
export OLLAMA_HOME=部署ollama的位置
export PATH=$PATH:$OLLAMA_HOME
#export OLLAMA_MODELS="$HOME/.ollama/models" # 模型存储目录
#export CUDA_VISIBLE_DEVICES="4,5" # 多块GPU情况下指定GPU
#export OLLAMA_HOST="127.0.0.1:11435" # 避免与他人端口冲突

使刚编辑的环境变量生效

1
source ~/.bashrc

GPU实时监控

Linux系统并不好实时监控GPU,所以需要辅助工具,比较推荐的是nvitop

nvidia-smi

nvidia-smi是系统工具,但不能实时监控是其一个缺点,可以通过设置定时刷新来弥补这一点

1
nvidia-smi -l

上述命令会显示历史信息,如果你只想看当前信息,可以执行

1
watch -n 1 nvidia-smi

nvitop

nvitop甚至不需要apt install,它只作为python的一个库安装

1
pip3 install nvitop

查看GPU状态

1
nvitop

image.png

gpustat

如果不想看到完整信息,仅仅是想监控GPU的运行状态,那么gpustat也是个不错的选择

可以作为系统的包进行安装(需要ROOT权限)

1
sudo apt install gpustat

也可作为Python库进行安装:

1
pip3 install gpustat

运行gpustat时需要带参数才能显示实时信息

1
gpustat -i

后台服务

总是开两个终端,一个运行ollama serve,另一个运行ollama其他命令还是太麻烦,可以使用systemd在后台运行ollama,配合环境变量,使ollama的调用更方便

编辑 ~/.config/systemd/user/ollama.service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[Unit]
Description=Ollama Background Service
After=network.target
StartLimitIntervalSec=500
StartLimitBurst=5

[Service]
Type=simple
ExecStart=%HOME/ollama/bin/ollama serve
WorkingDirectory=%HOME
Restart=on-failure
RestartSec=10s

# 环境变量
Environment="CUDA_VISIBLE_DEVICES=4,5"

# 日志配置
StandardOutput=journal
StandardError=journal
SyslogIdentifier=ollama

# 安全限制
PrivateTmp=yes
ProtectSystem=strict
NoNewPrivileges=yes

[Install]
WantedBy=default.target

服务管理常用命令

1
2
3
4
5
systemctl --user daemon-reload  # 重载配置
systemctl --user start ollama # 运行服务
systemctl --user stop ollama # 停止服务
systemctl --user status ollama # 查看服务状态
systemctl --user restart ollama # 重新运行服务

端口映射

没有ROOT权限不能操作防火墙,导致没办法远程访问服务器上ollama端口,只好通过SSH在本地对服务端口进行映射

一般默认远程SSH的22端口、用密码登陆的Linux服务器可以使用如下命令转发服务器上的端口

1
ssh -N -L 11434:localhost:11434 username@ip

倘若贵校的服务器管理非常严格(此处没有暗指某医科类大学),不仅更换了远程SSH端口,还要求使用密钥登陆,那么应当使用如下命令来转发服务器端口

1
2
3
4
ssh -N -L 11434:localhost:11434 \
-i ~/.ssh/server_key \
-p 2222 \
username@ip

这样能在本地的11434端口访问到服务器上运行的ollama服务了

在ollama上运行模型

此节默认配置好了ollama的环境变量

一些基础命令

ollama一些基础命令如下,更详细的命令可以参照菜鸟教程 Ollama 相关命令

1
2
3
4
5
ollama list              # 列出所有模型
ollama pull <model-name> # 从模型仓库拉取一个模型
ollama run <model-name> # 运行一个模型
ollama ps # 列出所有正在运行的模型
ollama rm <model-name> # 删除一个模型

image.png拉取deepseek-r1:32b模型

模型选择

尽量根据自己的硬件情况选择模型,最好在运行前确保不会爆显存

以下是一个简单的对应表格(它并不准确,只是一张大致的对应表格!),更多的模型和更详细的参数可以在ollama官网中找到

模型 显存需求 推荐显卡配置 适用场景
phi:2.7b < 4GB RTX 3050 *1 低功耗环境
gemma:2b < 4GB GTX 1660 Super *1 快速文本生成
mistral:7b 5-6GB RTX 3060 *1 日常对话/邮件处理
llama3.1:8b 5-6GB RTX 4060 *1 通用问答/学习辅助
qwen:7b 6-8GB RTX 3060 12GB *1 中文任务/代码生成
deepseek-coder:6.7b 7-8GB RTX 4060 Ti *1 编程辅助/调试
llama2:13b 10-12GB RTX 3080 *1 文档总结/复杂推理
yi:34b-q4 18-20GB RTX 3090 *1 学术研究/论文写作

可以使用以下命令评估模型的生成速率

1
ollama run <model-name> --verbose

image.pngdeepseek-r1:32b的生成速率

image.pngqwen3:30b-a3b的生成速率

虽然只测试了生成速率,但也可以从回答中看出来 Qwen3 30B A3B(q4_K_M) 模型相较于同级别量化大模型的DeepSeek R1 32B Qwen Distill(q4_K_M)在RTX 4090上的表现更好

使用python调用ollama上的模型

此处我构建了一个简单的python脚本,主要是为了测试ollama上的模型,如果想实际使用请考虑open-webui

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import requests
import json

MODEL_NAME = "mixtral:8x7b"


class OllamaClient:
def __init__(self, host="http://localhost:11434", headers=None):
"""
初始化 Ollama 客户端

:param host: Ollama 服务地址
:param headers: 自定义请求头
"""
self.base_url = host
self.headers = headers or {"Content-Type": "application/json"}

def chat(self, model, messages, stream=False, options=None):
"""
与模型进行对话

:param model: 模型名称
:param messages: 消息列表,格式: [{'role': 'user', 'content': '...'}]
:param stream: 是否使用流式响应
:param options: 模型选项
:return: 完整响应或流式生成器
"""
url = f"{self.base_url}/api/chat"
payload = {"model": model, "messages": messages, "stream": stream}

if options:
payload["options"] = options

if stream:
return self._stream_response(url, payload)
else:
return self._send_request(url, payload)

def generate(self, model, prompt, stream=False, options=None):
"""
生成文本

:param model: 模型名称
:param prompt: 提示文本
:param stream: 是否使用流式响应
:param options: 模型选项
:return: 完整响应或流式生成器
"""
url = f"{self.base_url}/api/generate"
payload = {"model": model, "prompt": prompt, "stream": stream}

if options:
payload["options"] = options

if stream:
return self._stream_response(url, payload)
else:
return self._send_request(url, payload)

def list_models(self):
"""列出所有可用模型"""
url = f"{self.base_url}/api/tags"
return self._send_request(url, method="GET")

def model_info(self, model_name):
"""获取模型详细信息"""
url = f"{self.base_url}/api/show"
return self._send_request(url, {"name": model_name})

def _send_request(self, url, payload=None, method="POST"):
"""发送 HTTP 请求"""
try:
if method == "GET":
response = requests.get(url, headers=self.headers)
else:
response = requests.post(url, json=payload, headers=self.headers)

response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"请求失败: {str(e)}")
return None

def _stream_response(self, url, payload):
"""处理流式响应"""
try:
response = requests.post(
url, json=payload, headers=self.headers, stream=True
)
response.raise_for_status()

for line in response.iter_lines():
if line:
yield json.loads(line)
except requests.exceptions.RequestException as e:
print(f"流式请求失败: {str(e)}")
yield None


# 使用示例
if __name__ == "__main__":
# 创建客户端实例
client = OllamaClient(
host="http://localhost:11434", # 本地隧道地址
headers={"x-ollama-client": "python-debug"}, # 自定义头
)

# 列出可用模型
print("📚 可用模型:")
models_response = client.list_models()
if models_response and "models" in models_response:
for model in models_response["models"]:
print(f"- {model['name']}")
else:
print("无法获取模型列表")

# 进行对话
print("\n💬 对话示例:")
chat_response = client.chat(
model=MODEL_NAME,
messages=[{"role": "user", "content": "为什么天空是蓝色的?"}],
)

if chat_response:
print(f"🤖 {chat_response['message']['content']}")

# 使用流式生成
print("\n🚀 流式生成示例:")
prompt = "用Python实现快速排序算法"
print(f"💬 提示: {prompt}")

full_response = ""
for chunk in client.generate(model=MODEL_NAME, prompt=prompt, stream=True):
if chunk and not chunk.get("done"):
text = chunk.get("response", "")
print(text, end="", flush=True)
full_response += text

# 获取模型信息
print("\n\n🔍 模型信息:")
model_info = client.model_info(MODEL_NAME)
if model_info:
print(f"名称: {model_info.get('name')}")
print(f"模型文件: {model_info.get('modelfile')}")
print(f"参数: {model_info.get('parameters')}")

# 打印配置选项
print("\n⚙️ 配置选项:")
options = model_info.get("options", {})
for key, value in options.items():
print(f"- {key}: {value}")

后记

大三暑假了,在考虑实习,面过开发岗,也面过运维实施岗,然而大公司不要我,小公司我又不想去,某些公司实习生待遇低不说,一天天还各种加班

期间有个上市的医疗信息化公司给我发了运维实施实习生岗的offer,奈何我爸妈觉得这公司压榨员工太严重,扯了一个星期最后决定不去,还是拾起了丢下一个多月的408教材,在B站大学“复学”

本来想着既然不去实习了(面了这么多公司,也不想继续投简历面试了),就在图书馆占个位学校好了,数一英一外加408这么多书,整天从宿舍背到图书馆再背回宿舍对腰椎不好;顺带一提,因为老家没学习的地方,我是“黑”在学校的——我没申请暑假留校;于是在图书馆三层找了个好位置,脚旁就是充电口,方便我给电脑边充电边看网课

这种位置很不好抢,那天上午我把自己所有的资料都放到了那个位置上,厚厚一摞,紧接着当天下午接到通知,为省电,三层不开空调了,我望向一旁图书馆的大落地窗旁苦笑了一下,决定明早把书搬到其他位置

结果第二天上去一看,书都被清走了,一般来说只有上学的时候,为了防止占位,隔一段时间会清书,保证座位的利用率,假期期间是不会清理的;到第一层的服务台找了半天也没找到,还是去回三层接水的时候发现昨天清的书还没来的及送到一层服务台,就这样拿着找来的书去四层找了个头顶滴水的桌子

伞在这期间被偷了两次,现在这把是第三把,虽然是没丢,但伞柄和伞业已“分头行动”,能用是能用,也懒得再买新的了

在四层稳定下来后,我重新审视了自己现在的进度,意识到考408有点悬,就在网上搜自命题学校,发现上海理工大学的自命题只考数据结构和操作系统,难度比408低,数据结构我已经囫囵吞枣地过了一遍,再看看操作系统系统,上海理工好像也不错,我抱着这样的想法开始看操作系统,生活好像也有了希望

翌日连续看了一上午和一中午的操作系统,下午突然想起来上学校官网查一下参考书目,接下来我就看到了上海理工新发的公告,今年所有计算机相关专业改考408

上海理工也成了妄想

继续,继续看老汤头的零基础考研数学,再背点英语单词,专业课?数据结构好了,省内还有三四所双非自命题考的是数据结构

昨天手机显示是晴天,中午图书馆正好停电,前段时间老下雨,床单被罩都没洗。想着趁停电回寝室洗洗,顺便把被子拿出去晒晒。谁知道寝室也停电,等来电了水也没来。只好先把被子拿出去晒

回到图书馆之后越看天越觉得不对劲,心中总有不安,把手机上“晴天”的气象数据发给AI,让它帮我预测下降雨,当看到60%的三十分钟内降雨率时,是的,该下雨了

我连忙跑回寝室收被子,虽然有点湿,好歹还有一面是干的,晚上能睡不是;但我被突然的大雨困在了寝室,伞在图书馆,寝室没有网也没有水,有电,也只有电,打开手机,天气APP里赫然一个太阳:晴,27/36摄氏度

雨下的不小,但短促,雨稍停我就赶回了图书馆,坐在位置上面对电脑里的老汤头,鬼使神差打开了Termius,连上了学校的服务器,一个突如其来的想法闪过,在服务器上部署个ollama,跑几个模型玩玩呗,难点只在于没有ROOT权限

这篇文章的诞生背景就是这样,如果后面还有时间的话,可能会用ollama微调or训练一些模型,到时候也许会再写篇文章记录