微调的上限是由底模决定的

标签:AI

继上个月研究 LLM 的微调以来,我把各种参数都折腾了一遍,效果大致已经到极限了。
当时的数据量只有 400 多条,为了避免过拟合,我一共只训练了 2 轮。为了避免相同的训练顺序对学习造成影响,还将两次训练集的顺序打乱了。

由于数据量很少,初始 loss 更低的 PiSSA 是非常推荐使用的,不然本就稀少的数据被用于降低初期较高的 loss,显得有点浪费。
之前那篇文章我没有说明 PiSSA 的原理,这里简单提一下:
LoRA 的原理是构造 2 个低秩矩阵,其中 A 矩阵是随机值,B 矩阵全是 0,它们相乘后再和原矩阵相加作为参数。
而 PiSSA 则是将原矩阵进行奇异值分解,也就是让分解得到的两个矩阵相乘等于原矩阵。当然,完全相等也没必要,一般会使用快速奇异值分解,让 A * B 近似于原矩阵即可。这样,我们在微调 A、B 矩阵时,初始状态更接近原矩阵(初始 loss 低),微调效果也更接近全参微调(最终 loss 低)。

我选用的验证集约 40 条,Qwen2-7B-Instruct 的 eval loss 大概能降到 0.3。
之后针对验证集中表现不好的例子,我又陆续构造了约 200 条数据。至此,eval loss 大概能降到 0.2。但继续增加数据量,却发现模型会出现教不会的现象。
顺便对比一些在线的模型:gpt-4o 和 gpt-4o-mini 大概到 0.14,glm-4-flash 大概到 0.25。

又到了一年一度换域名的日子

标签:无

自从用了 12 年的 keakon.net 被墙后,本站年复一年更换域名的传统也坚持三年了。

不过这次和墙以及域名注册商跑路无关,纯粹是因为去年低价买的 keakon.uk 续费太贵,加上对 .uk 没啥好感,所以再换个便宜的。

如果有订阅 RSS 的,记得把域名换成 keakon.top,其他情况我就帮不了了。

如何微调一个自用的小模型

标签:AI

最近在用大模型做多语种的翻译,选择了 2 张 4090 能部署的当前最强模型:Qwen2-72B-Instruct-GPTQ-Int4。
但在使用过程中发现了不少问题,例如:
  • 速度太慢,用 vLLM 部署,32 并发时大概 300 tokens/s,每个请求其实不到 10 tokens/s。
  • 指令遵循不行,当要求过多时,会随机无视一些指令。
  • 喜欢废话,即使要求它只输出翻译,也可能会附带一堆多余信息。目前发现最好的方式是让它输出在一个 XML 标签里(例如 <OUTPUT></OUTPUT>),然后用字符串匹配或正则表达式来提取翻译。

那么有没有办法解决呢?

AI 时代的显卡选择

标签:AI

最近我一直在折腾大模型的推理、部署和训练,遇到了不少坑,先阶段性地总结一下。

先说下结论吧:
  • 个人学习最简单的方案:16 寸 MacBook Pro M3 Max 128 GB 内存,靠谱的低价约 28000+ RMB。最大可进行约 70B Int8 量化模型的推理(如 Qwen2-72B-Instruct-GPTQ-Int8)。
  • 个人学习+游戏需求:RTX 4090,不太好买,约 15000+ RMB。最大可进行约 30B Int4 量化模型或 7B 模型的推理。
  • 小规模部署:双 RTX 4090。最大可进行约 70B Int4 或 AWQ 量化模型的推理(如 Qwen2-72B-Instruct-GPTQ-Int4)。
  • 训练 70B 的模型:租 8 * Tesla A800/A100/H800/H100 80GB 的服务器(越往右性价比越高),每天约 1000~3000 RMB。
  • 追求速度:用各个厂商的云服务。

其他不推荐的选项:
  • 192 GB 内存的 Mac Studio 或 Mac Pro:型号较老,价格太贵,唯一的优势是可以进行约 70B 模型的推理。
  • RTX 3090 等其他游戏显卡:相较于 RTX 4090 的性价比较低。
  • Tesla A100 等高端专业显卡:价格太高,如果要进行 70B 模型的推理需要 2 张(约 15 万 RMB),性价比不如 4 张 RTX 4090。如果用于训练和微调,闲置时有点浪费。
  • AMD 显卡:性价比较高,但生态不如 NVIDIA,这意味着很多库和工具不支持 AMD 显卡或性能较差,较新的论文和库一般都只有 NVIDIA 版。不适合学习,除非你确定正好能满足你的需求。
  • 国产显卡:价格不低,生态较差,很多库无法使用或需要专门安装老版本的魔改版。

Python 的协变、逆变与不变

标签:Python

前几天在使用 httpx 时,发现它的代理参数声明的类型是 ProxiesTypes
URLTypes = Union["URL", str]
ProxyTypes = Union[URLTypes, "Proxy"]
ProxiesTypes = Union[ProxyTypes, Dict[URLTypes, Union[None, ProxyTypes]]]
可以看出,dict[str, str] 应该是符合它的参数签名的,然而我传入一个 dict[str, str] 参数后,Pylance 却会报错,这让我大为不解。

于是我又尝试简化了一下这个问题:
from typing import Mapping

a: dict[int, int] = {}
b: dict[int, int | str] = a  # error:
# Expression of type "dict[int, int]" is incompatible with declared type "dict[int, int | str]"
#   "dict[int, int]" is incompatible with "dict[int, int | str]"
#     Type parameter "_VT@dict" is invariant, but "int" is not the same as "int | str"
#     Consider switching from "dict" to "Mapping" which is covariant in the value type
c: Mapping[int, int | str] = a
d: Mapping[int | str, int] = a  # error:
# Expression of type "dict[int, int]" is incompatible with declared type "Mapping[int | str, int]"
#   "dict[int, int]" is incompatible with "Mapping[int | str, int]"
#     Type parameter "_KT@Mapping" is invariant, but "int" is not the same as "int | str"
是不是很奇怪,为啥 dict[int, int]dict[int, int | str]Mapping[int | str, int] 都不兼容,而与 Mapping[int, int | str] 兼容?

如何减小 Python 的 Docker 镜像的大小

标签:Python

虽然我对公司的屎山代码已经见怪不怪了,但是看到一个普通的 Python web 应用的 Docker 镜像大小超过 10 GB,还是让我感叹前人的智慧。
这些巨量的字节会在构建时在中美之间来回传递,运气好可能 5 分钟能构建完毕,运气不好就可能要等 1 小时了。
那么怎样才能减小这些镜像的大小呢?

重新回到 Python 的怀抱

标签:Python

在上一家公司时,我虽然是同时使用 Go 和 Python 进行开发,但 Go 的占比要远大于 Python。
作为一名 6 年的 Gopher 和 15 年的 Pythonista,我其实对这两门语言都很喜欢。虽然 Go 有很多的设计问题,我曾经也认为它设计得很敷衍,甚至现在也没多大进步,但它足够简单,我可以不用费很多心智就写出高性能的代码,且能原生地在各个平台运行。细想起来,似乎没有其他语言能做到。
而与之相对的,有三门语言是我无法提起兴趣的:C++、Ruby 和 Rust,我大概都用了不到半年就放弃了。我知道它们有不少很赞的设计,也不缺少众多的拥趸,可是我感觉在编码和阅读时,大半的精力可能都花在了和语言做斗争上,而不是去处理业务逻辑。这不得不让我想起了那句经典的 "Life is short (You need Python)"。

后端程序员如何配置 macOS

标签:Apple, Mac OS X

鉴于我的红米 K60 仅使用不到半年,电池健康度就只剩 80% 了,我入手新 MacBook Pro 后的第一件事就是安装 AIDente
锂电池的健康度主要和这三个因素相关:
  1. 循环次数:从 100% 用到 0%,或是从 100% 用到 50%,充满后再用到 50% 都算一次循环次数。可以理解为总共使用了多少电量,所以长期插电使用,而不是用电池供电是正确的。
  2. 温度:充电会导致电池温度上升,而过高(> 35°C)和过低(< 0°C)的温度都会影响电池的性能。一般越接近 25°C 越好。所谓的快充伤电池,其实是快充会导致电池升温更快。
  3. 充放电深度:过度的充放电(特别是放电)都可能对锂电池造成不可逆的损伤,尽量避免充电至 80% 以上和放电至 20% 以下。例如 100% 的充放电深度,大概 300 次循环次数就会使健康度降到 70%,80% 的充放电深度则可以到 400 次,10% 的充放电深度则可以到 6000 次(但是相当于只使用 10% 电池容量)。
AIDente 对这几点都有处理:
如果经常需要移动办公,将充电限制设置到 80% 就行了,60% 的充放电深度也够用大半天了;如果大部分时间都插电使用,限制到 70% 也够用;如果几乎不移动,让它保持在 50% 附近也是可以的。
Intel 芯片的 MacBook 是可以设置硬件充电上限,之后即使退出 AIDente 甚至关机都不会过充;而 Apple silicon 芯片则需要保持 AIDente 运行,且启用「MacBook 进入睡眠时停止充电」,并在关机后拔下充电头才能避免过充。
后面的设置就需要购买 AIDente Pro 了,但也不是非买不可:
  • 「过热保护」可以在电池温度过高时停止充电。
  • 「续航模式」可以避免短暂用电后又充到上限这种微小充电,不过这也没啥危害。
  • 「控制 MagSafe LED」可以在达到充电上限停止充电时使 MagSafe LED 显示绿色,而不是充电中的橙色。
  • 「图标样式」可以改成「咬合状态」,用来区分不同的充电状态。
  • 「硬件电池百分比」可以更精确地显示和控制电量,macOS 为了避免过度充放电,一般会隐藏一小部分电量。(比如充到 95% 就显示充满了,还剩 5% 时显示成没电了。)
我顺便还读了下它的源码,发现它是通过写入 SMC 来限制充电的。还有一个叫 battery 的项目是调用 smc 命令行实现的,可能更易懂。

为什么在高并发的场景下,需要将 MySQL 的事务隔离级别设为读已提交?

标签:性能

为缩短篇幅,本文假定读者已知晓 transaction isolation level(事务隔离级别)的基础知识。

MySQL 的默认事务隔离级别是 repeatable read(可重复读),而在都市传说中,各个互联网大厂都会将其改为 read committed(读已提交),这是为什么呢?

从字面上理解,可重复读满足了一个事务在读取某行后,如果另一个事务修改了该行,再次读取它时,能保持第一次读到的值。可是,这又有什么意义呢?谁会在一个事务里多次读取同一行呢?
其实它的实现是这样:当事务开始后,从它第一次读取数据时,就创建了一个快照,之后都是对这个快照进行查询,直到这个事务结束。也就是说,可重复读的主要作用是让事务只访问这个事务和它之前已有的数据,而不是字面上的读同一行不会变。
而读已提交只需要在每次读取时创建一个快照,读取完这个快照就用不到了。
由此可见,可重复读需要维护一个较长的快照,这自然要消耗更多的资源。

不过现实中我们不会这样简单地使用事务。一个正常的事务如果要基于读取到的数据来修改,会使用 SELECT ... FOR UPDATE 的形式来加锁。
如果这里可以利用唯一索引的话,MySQL 会对唯一索引中满足条件的行添加行锁,否则需要加 gap lock 和 next-key lock,这两把锁会增加被锁定的范围。例如表 test 有一个被索引的列 a,有一行 a 为 100 的数据。当执行 SELECT * FROM test WHERE a = 1 FOR UPDATE 后,其他事务无法插入任何 a < 100 的数据,因为被这两把锁给锁住了。
而读已提交则不会添加这两种锁,并且当需要锁住的行不存在时,并不会对其加锁,而是允许其他事务插入。
由此可见,可重复读可能锁住了更多的数据,更容易造成死锁。

简单评测 VSCode 的免费 AI 编程插件

标签:无

最近发现 VSCode 里安装的 AI 编程插件越来越多,也不知道会不会有冲突,所以想选一个最好的留下。
本次参与评测的选手有:tabnine、Codeium、CodeGeeX、fittencode、Baidu Comate、TONGYI Lingma 和 Cody(按我安装的顺序排序,有些不好用的我直接卸载了,不参与评测)。
鉴于免费的已经够我用了,收费的就不参与评测了,正好避免恰饭嫌疑。

« 看看还有什么好玩意