llama.cpp笔记

自行编译llama.cpp的笔记。

cmake列出所有选项

在llama.cpp目录下:

1
cmake -B build -LH

H是human readable。这样就可以定制自己想要编译的内容了,比如不编译test。

只编译 llama-server

我只需要 llama-server,其他的 tool 都不感兴趣,那么 cmake 打开和关闭的选项如下:

1
2
3
4
5
6
7
8
9
10
11
12
cmake -B build \
-DGGML_CUDA=ON \
-DBUILD_TESTING=OFF \
-DGGML_CPU=ON \
-DGGML_CPU_REPACK=ON \
-DGGML_CUDA_GRAPHS=OFF \
-DLLAMA_ALL_WARNINGS=OFF \
-DLLAMA_BUILD_COMMON=ON \
-DLLAMA_BUILD_EXAMPLES=OFF \
-DLLAMA_BUILD_TESTS=OFF \
-DLLAMA_BUILD_TOOLS=ON \
-DLLAMA_CURL=OFF

因为有 Nvidia 显卡所以启用 CUDA,但是因为是单卡所以不需要 CUDA_GRAPHS。因为已经提前下载好模型,所以不需要运行时拉取模型的功能,禁用curl。而那四个LLAMA_BUILD_是测试过,如果 COMMON 和 TOOLS 设置成 OFF,cmake --build时会提示没有 llama-server 这个 target。虽然加速框架用的是CUDA,但是不得不启用 CPU,否则在加载模型的时候会报错 no CPU backend 无法加载。

配置好之后编译:

Read More

gpg and agent in Mac and Arch

一直以来我都被macos gpg-agent forward 到 archlinux 的不稳定而苦恼。即使是这篇文章也不一定是完美的解决方案,不过目前来看是能够运行的。

1. 背景配置

mac上我没有使用 gpgtool,因为它的 gpg 版本太低了。我使用的是 brew 安装的 gpg 以及输入密码用的 pinentry-mac。一定要安装 pinentry-mac, 这样才能把密码保存到 Keychain,以便日后无密码解锁 private key。

安装完之后,参考 use Apple Keychain to store GPG Passphrases配置 .gnupg 下的文件:

1
2
# gpg-agent.conf
pinentry-program /opt/homebrew/bin/pinentry-mac

而 archlinux 上也需要改 .gnupg 的文件:

1
2
# gpg.conf
use-agent

二者都需要在 .zshrc 中配置 GPG_TTY:

1
export GPG_TTY=$(tty)

还需要根据官方教程 配置 .ssh/config 以及 /etc/ssh/sshd_config

Read More

近况

从六月上完课、结束实习到现在一个月的时间发生了很多事情。

结束实习之后立刻就迎来了正式工作的面试,之后也是成功通过了,拿到了offer。等待签证申请下来,下个月就去上班了。自21年本科毕业之后迎来的最后一次、一个多月的“暑假”。

而结束实习之前就签订了新房子的租约,之后就准备搬家;但是奶奶突然脑出血、情况危急,于是爸妈让我赶紧回去见最后一面——本来以为撑不过那个晚上、见不上最后一面的——于是推迟了搬家,损失了订金。奶奶跟爷爷一样,都是等子女都见过一遍了(虽然这只是我们的一厢情愿,奶奶在入院那一晚就已经没有意识了)才走的。大家都守过一个晚上,看着监护仪的数字从天黑到天亮;最后是爸爸守的那一晚走的,我跟妈妈说这也是好事,爸爸没有错过最后的时刻——因为爷爷走的时候就是我们刚到家准备休息,姑妈就告诉我们不行了、在抢救了;等我们回到医院已经离开了。

这一次奶奶走的时候,不知道是因为再也没有比爸爸大一辈的人了,所以下一次就是我要处理后事了;还是说因为爷爷走的时候我自我保护了,导致什么都记不起来,所以这一次想要记住后事要做什么;我努力地记住要做的所有事,也感觉到自己跟爸爸紧密地联系在一起,所谓的“父子兵”的感觉。

本来计划的火化时间是星期五,后来发现我们处理事情很快,星期二——另一个吉日——就可以火化了,于是就提前了。送别仪式上我一直没哭,直到主持人说起“这一生⋯⋯”的时候才突然忍不住。我发现自己最忍不住的不是奶奶离开了这个世界,因为我多多少少觉得她和爷爷依然在陪着我们;而且奶奶这个身体真的太老旧了,最后这段时间她视力、听力、味觉甚至对温度的感知都几乎没有了,就像是被困在这个身体里面了一样;我也不知道为什么会对“她不是只有这20年,在我出生之前她还经历了漫长而精彩的一生”而泣不成声。在仪式都结束之后大家聚餐的时候,奶奶精彩的前半生的各种故事倒是在长辈聊天的时候不停地解密,还挺快乐的。

回来之后继续搬家的事情。因为叫了搬家公司,所以只需要把东西拆了、打包好,他们就会一股脑全部搬到新家这边。最惨的是床包括床垫、衣柜的尺寸都不对,放不下,只能扔掉重新买;而且宜家衣柜的组装差点没给我累死。最后还叫了清洁公司把旧屋大扫除了,干干净净交还给业主。原来的业主以及中介也是不怎么扯皮的人,看了一下没问题之后业主就爽快把押金退回了。不过这一次折腾下来也花了很多钱,家还是得少点搬。新家我挺喜欢的,由于是顶楼——除了楼上的超级富豪单元之外——所以阳台上面无遮挡,阳光超好;朝向大山,所以不会有工地的声音,很宁静;交通也比较方便。以后也打算长住了——更别说昨天做饭把电磁炉的陶瓷玻璃面板搞花了,这下只能长住来掩盖了。

最近还入坑了3D打印,建模开始熟练了一点,也打印了一些有用的生活用品,主要是收纳类——在香港3D打印是有价值的,毕竟小东西不好买到,价格也贵。

希望之后一切都顺利,也希望家人都平平安安、身体健康。

Read More

docker in incus with cuda

容器使用 CUDA

首先是 nvidia-container-toolkit 需要安装。然后 incus launch 时需要加上一些 flag。 常规的包括:

  1. -c security.nesting=true
  2. -c security.syscalls.intercept.mknod=true
  3. -c security.syscalls.intercept.setxattr=true

这几个都是在 incus 里面运行 docker 所需要的,常规操作没有太多变化。而为了 CUDA,还需要以下flag:

  1. -c nvidia.runtime=true,这样就会自动挂载一些 binary 和 library 进去,从而不需要在里面浪费空间再装一次。但是这里有坑,详见 capabilities 配置
  2. -c nvidia.driver.capabilities=compute,utility,video,特别是最后的 video。默认情况下只会设置 compute 和 utility,导致容器内找不到 encoder 的库,用不了硬件加速 codec。

为了最大兼容性,毕竟 ubuntu 等系统的驱动版本可能不一致,导致其他应用不兼容,所以容器的系统我用了跟 host 一样的 archlinux,所以完整的 launch 是:

1
2
3
4
5
6
incus launch images:archlinux/cloud/amd64 dockerd \
-c security.nesting=true \
-c security.syscalls.intercept.mknod=true \
-c security.syscalls.intercept.setxattr=true \
-c nvidia.runtime=true \
-c nvidia.driver.capabilities=compute,utility,video

除了这些flag,还需要把 gpu 添加给容器[1]。使用 physical type 也没有关系,不会导致 container 独占GPU的:

incus config device add dockerd nvidia0 gpu

此时在容器内安装 ffmpeg 并用以下命令测试[2],应该不会提示任何错误的:

Read More

AI相关的探索经验

vLLM 与 bge-m3

huggingface上的 bge-m3 是 pytorch 模型,而 vLLM 喜欢 safetensors。好在讨论区已经有自动转换bot给出了pr:Adding safetensors variant of this model。那么用huggingface-cli下载到本地,通过指定revision的方式就可以了:

1
huggingface-cli download BAAI/bge-m3 --revision refs/pr/116 --exclude pytorch_model.bin --local-dir ./bge-m3

而 vLLM serve 的命令是:

1
2
3
4
5
6
7
VLLM_ATTENTION_BACKEND=FLASH_ATTN VLLM_USE_V1=0 vllm serve bge-m3 --max-num-seqs 2 \
--enforce-eager \
--task embed \
--disable-log-requests \
--swap-space 0 \
--gpu-memory-utilization 0.2 \
--served-model-name bge-m3

Read More

搬迁至hexo的记录

punycode deprecated 及泛用的 override

首先用Stackoverflow - chenop: trace deprecated的方法找到是谁在用deprecated的:

1
npx cross-env NODE_OPTIONS=--trace-deprecation npx hexo g

然后在package.json中用overrides来强行更新dependency的dependency:

1
2
3
4
5
6
7
8
9
10
{
//...
"overrides": {
"markdown-it": "^14.1.0",
"warehouse": "^6.0.0",
"glob": "^11.0.1",
"stylus": "^0.64.0"
},
//...
}

这个方法来自于:Stackoverflow - Choco Li

最后还有隐藏的deprecated,在package-lock.json中搜索depreca就能看到了。

mermaid tag

threeq/hexo-tag-mermaid 用的是提前render好的方式,但是需要 build 时就调用 mermaid,带来了很多复杂性。在chatgpt帮助下,我改成了引入 mermaid.js,让客户端渲染的方案sieveLau/hexo-tag-mermaid。格式:

1
2
3
{% mermaid %}
....
{% endmermaid %}

Read More

fix oc_ext_apps corrupted

在 upgrade nextcloud 的过程里,执行 occ upgrade 的时候发现错误提示:

1
- ERROR: An exception occurred while executing a query: SQLSTATE[HY000]: General error: 1877 Table `nextcloud`.`oc_ex_apps` is corrupted. Please drop the table and recreate.

搜索了 nextcloud 社区,虽然没有直接的解决方法,但是从这个对话里得到了 schema。虽然是 postgresql 的,但是让 chatgpt 转换成 mariadb 就行了。修复过程如下:

  1. 连接到 mariadb,root 身份直接 mariadb -uroot
  2. 然后在 mysql shell 中执行以下命令(这里数据库名字之类的用的默认名字):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
USE nextcloud;
DROP TABLE oc_ex_apps;
CREATE TABLE nextcloud.oc_ex_apps (
id BIGINT NOT NULL,
appid VARCHAR(32) NOT NULL,
version VARCHAR(32) NOT NULL,
name VARCHAR(64) NOT NULL,
daemon_config_name VARCHAR(64) NOT NULL DEFAULT '0',
port SMALLINT NOT NULL,
secret VARCHAR(256) NOT NULL,
status JSON NOT NULL,
enabled SMALLINT NOT NULL DEFAULT 0,
created_time BIGINT NOT NULL
);

这样就修好了。不过这个 schema 是 30.0.x 的,为了避免刻舟求剑,可以定时用以下命令导出整个 nextcloud 数据库中所有 table 的 schema:

1
mariadb-dump --no-data nextcloud > nextcloud_schema.sql

Read More

Auto Suspend and Wake On LAN

自从购买了 4070 Ti Super 之后 AI 机就处于长期启动的状态。但是仔细思考平时的使用情况,实际上不是24小时不间断需要AI辅助。根据父亲对他自己的电脑的测量,待机状态下都会有30多瓦的功耗,这是不必要的。于是在参考Wiki、结合之前在windows上的 wake-on-lan 的经验,我决定在 AI 机上做一个 autosuspend,同时也要有办法在请求来的时候唤醒并处理。

具体分成两部分:AI 机在没有活跃请求连接ollama端口的时候等待5分钟,然后睡眠;网关机如果发现 AI 机昏迷了导致 502 错误,就会执行 wol 来唤醒。

autosuspend

自动昏迷采用的是 Autosuspend,Arch Linux用 AUR 安装即可。安装完成之后修改 /etc/autosuspend.conf

  1. idle_time 设置为 300(5分钟);
  2. 下面一大堆预设的 check 全部注释,我们只使用 ActiveConnection:
1
2
3
4
[check.AI]
class = ActiveConnection
enabled = true
ports = 11434

如果有需要把22端口也加上防止ssh连接时意外昏迷。然后 systemctl enable --now autosuspend启动服务即可。

wake-on-lan

这部分有点复杂,因为 AI 机的板载网卡是 r8125,会有几个问题:

  1. windows的驱动会在关机时禁用wake-on-lan,影响其他系统使用这个功能;这个可以通过在 UEFI 里面打开 PXE (一般在Network Stack里面)来让 UEFI 无视windows驱动的设置
  2. Arch Linux 的内核用 r8169 模块来驱动 r8125,但是会导致 wake-on-lan 无法工作,需要从 AUR 安装 r8125-dkms,并且屏蔽 r8169 的加载。具体参考 Arch Wiki

Read More

Open WebUI with Qwen QwQ on Tavily Search

Open WebUI虽然内置了 Web Search 功能,而且能够调用 Tavily,但是效果比较一般。在社区提供的 Tool 中,victor1203提供了一个可用的 Tavily 搜索工具,Web Search with Tavily. 不过现时 Tavily 的 API 有所变动,所以需要进行一些修改。

我的 Open WebUI 是使用 docker 部署的,为了能够使用 tavily-python,需要进入 docker 容器并安装相应的依赖。步骤:

  1. 进入 docker 容器:docker exec -it openwebui bash
  2. 安装依赖:d open_webui && pip install tavily-python
  3. 退出 docker 容器:exit
  4. 重启 docker 容器:docker restart openwebui

然后打开“工作空间”,点击 “工具”,新建一个工具,填入以下代码:

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
"""
title: Tavily Search Tool
author: victor1203
maintainer: sievelau
description: This tool performs internet searches using the Tavily API to get real-time information with advanced context and Q&A capabilities
required_open_webui_version: 0.4.0
requirements: tavily-python
version: 1.1.0
licence: MIT
"""

from pydantic import BaseModel, Field
from typing import Optional, Literal
from tavily import TavilyClient


class Tools:
def __init__(self):
"""Initialize the Tool with valves."""
self.valves = self.Valves()
self._client = None

class Valves(BaseModel):
tavily_api_key: str = Field(
"", description="Your Tavily API key (starts with 'tvly-')"
)
search_depth: str = Field(
"basic", description="Search depth - basic or advanced"
)
include_answer: bool = Field(
True, description="Include an AI-generated answer in the response"
)
max_results: int = Field(
5, description="Maximum number of search results to return (1-10)"
)

def _get_client(self) -> TavilyClient:
"""Get or create Tavily client instance."""
if self._client is None:
self._client = TavilyClient(api_key=self.valves.tavily_api_key)
return self._client

async def search(self, query: str, __event_emitter__=None) -> str:
"""
Perform a Tavily search and return the results.

Args:
query: The search query string
search_type: Type of search to perform:
- regular: Standard search with full results
- context: Optimized for RAG applications
- qa: Quick answer to a specific question

Returns:
A formatted string containing the search results
"""
try:
# Input validation
if not self.valves.tavily_api_key:
return "Error: Tavily API key not configured. Please set up the API key in the tool settings."

# Emit status that search is starting
if __event_emitter__:
await __event_emitter__(
{
"type": "status",
"data": {
"description": f"Initiating Tavily search...",
"done": False,
},
}
)

client = self._get_client()

# Perform the search based on type
if __event_emitter__:
await __event_emitter__(
{
"type": "status",
"data": {
"description": "Fetching search results...",
"done": False,
},
}
)
result = client.search(
query=query,
search_depth=self.valves.search_depth,
include_answer=self.valves.include_answer,
max_results=self.valves.max_results,
)

# Format regular search results
formatted_results = ""

# Include AI answer if available
if self.valves.include_answer and "answer" in result:
formatted_results += f"AI Answer:\n{result['answer']}\n\n"

# Add search results
formatted_results += "Tavily Search Results:\n\n"
for i, item in enumerate(
result.get("results", [])[: self.valves.max_results], 1
):
formatted_results += f"{i}. {item.get('title', 'No title')}\n"
formatted_results += f"URL: {item.get('url', 'No URL')}\n"
formatted_results += f"Content: {item.get('content', 'No content')}\n"
formatted_results += f"Relevance Score: {item.get('score', '0.0')}\n"
formatted_results += "\n"

if __event_emitter__:
await __event_emitter__(
{
"type": "status",
"data": {
"description": "Search completed successfully",
"done": True,
},
}
)

return formatted_results

except Exception as e:
error_message = f"An error occurred while performing the search: {str(e)}"
if __event_emitter__:
await __event_emitter__(
{
"type": "status",
"data": {"description": error_message, "done": True},
}
)
return error_message

具体的修改是:

  1. 删除了 search type,因为现在只有 search,具体的类型通过 topic 来指定,但是作为 genereal search,我们不需要这个参数。
  2. 把 formatted_results 的引导内容添加上 Tavily 这个关键词,方便 RAG Template 中指导模型
  3. 删除了 snippet,转而增加了 content 来指定搜索结果的内容
  4. 增加了 score,方便后面更改 RAG Template 来告诉模型根据此字段来判断相关度

之后,在工作空间的“模型”中新建一个 WebSearch,工具勾上我们刚才新增的这个tool,这就做好了一个 agent。

但是我们还需要更改 RAG Template。默认的 template 是设计成与内置的 web search 进行配合的,但是我们用 tool 来传递搜索结果会变成只有一个 source id,导致引用不正确。所以需要在 guideline 增加两条:

  1. - If the context contains "Tavily Search Results", you should not include inline citation. You should indicate the source url in this syntax: [Title](URL). Make sure you are using markdown link syntax and take care of the quotes. This rule take precedence over the previous rule about source_id.
  2. - If the context contains "Tavily Search Results", you should determine result quality by the Relevance Score.

Read More

NFS on ZFS and MacOS

之前通过 SMB 共享的一个目录,今天在写入 3G 左右大小的文件的时候,复制结束之后报错 -51。由于搜索了一番仍未找到解决方案,只能退而求其次试用 NFS 共享。根据 Archwiki 上关于 NFSZFS 的配置,结合今天的试错经历,总结如下:

配置固定端口方便 firewall 放行

需要配置 rpc.mountd 和 lockd 的端口,这两个的配置方式有所不同。

对于 rpc.mountd,只需要修改 /etc/nfs.conf,找到 [mountd] 这一节,去掉 port=0 这一行的注释并修改成自己喜欢的端口比如 2050 即可,然后 restart nfs-server.service 即可。

对于 lockd,亦即 rpcinfo -p 中输出的 nlockmgr 条目,则需要新建 /etc/modprobe.d/nfs.conf,填入以下内容来设置端口,比如改成 2051:

1
options lockd nlm_udpport=2051 nlm_tcpport=2051

保存之后需要 reboot 才能生效。

综合以上配置,加上 nfs 自身使用的 111 端口和 nfs v4 的 2049 端口,nftables需要以下规则放行相关端口:

1
2
tcp dport {2049,2050,2051,111} accept comment "nfs"
udp dport {2050,2051,111} accept comment "nfs"

如果不放行 mountd 的端口,那么在 Finder 连接 NFS 时会直接显示无法连接;如果不放行 lockd 的端口,那么 Finder 能够正常连接,但是打开子目录的时候就会超时并且断开连接。

Read More