强化学习 • 已更新 • 22 • 1

8月23日,[Surya
Narreddi](https://x.com/kickingkeys/status/2091570990048276897) 发布了一段精美的视频,展示了一个语言模型绘制的水彩画。该模型通过
p5.brush 编写 JavaScript,这个库“为 p5.js
添加了自然绘图工具”。视频迅速走红,截至撰写本文时已获得超过 150 万次观看。
该视频附带了一篇[博客
注意:关于项目背后的背景,由 Surya 本人讲述,请观看[他的论文视频]。
在本文中,我尝试用 TRL 和
OpenEnv 复现他的想法。参考池数据集、RL
环境、训练脚本以及训练好的模型,全部开源。
整个流程在 Hugging Face 上端到端运行:
一旦两个 Space 启动,配方只需一条命令。复制
环境 和
评分模型,设置两个
环境变量用于奖励混合,然后启动:
hf jobs uv run train/watercolour_grpo.py --flavor h200 --timeout 48h --secrets HF_TOKEN -- \
--env-url https://<you>-watercolour-env.hf.space \
--model Qwen/Qwen3.5-35B-A3B --lora --all-linear --bf16 --gradient-checkpointing \
--subject 'a peach hibiscus' --references 4 \
--top-p 0.95 --top-k 20 \
--lr 5e-5 --lr-scheduler constant_with_warmup --warmup-steps 5 \
--scale-rewards none \
--steps 110 --n-episodes 240 --num-generations 8 \
--per-device-batch-size 1 --gradient-accumulation-steps 8 \
--max-completion-length 8192 \
--run-tag my-run --out <you>/watercolour-grpo --push-to-hub
本文的其余部分讲述的是如何实现这一目标,[每一部分都在
仓库中](https://github.com/adithya-s-k/HuggingEnvs/tree/main/02-watercolour)。
我一步步遵循了原博客,只在确实需要时才做改动。我自己的每一个想法都放进了清单,而不是放进实验里,这份清单最终成了结尾处的“我接下来会尝试什么”,旁边是已发布产物的完整列表。如果你已经读过他的文章,那么整体框架和
奖励设计对你来说会很熟悉。新的内容是开放实现、人工评分的池子,以及三种奖励混合方式的训练与比较,它从[你需要构建的 RL
环境](https://huggingface.co#the-rl-environment-you-need-to-build)开始。
这些画看起来松散、不完美、手工感强,而当下图像模型产出的却是完美(统计上平均)的图片。我猜这种反差正是视频
走红的重要原因。它让我想起生成式 AI 艺术的早期,那时重点
是探索媒介。DeepDream(2015)本是一个调试工具,人们把它变成了艺术,像 [Edmond de
Belamy](https://en.wikipedia.org/wiki/Edmond_de_Belamy)(2018)这样的作品来自艺术家们探索
GAN 能做什么,而像 Mario Klingemann 这样的艺术家在那几年用神经网络制作了梦幻般的肖像。
这个项目感觉更接近那些早期日子。在他的论文中,Surya 描述了通向这里的路径。他从提示文本到图像模型开始,在那里提示词是
你唯一能拉动的杠杆,更多细节只能在一定程度上换来更多控制。训练
模型本身则走得更远。这个想法的
另一半是媒介。模型编写一段约 150
行的 JavaScript 程序来绘制图像。那个模型输出就是代码。你可以阅读、编辑并再次运行它,每一笔背后的决策都可见。而风格来自一个限制:模型只被允许使用库中十个方法。更多内容见下文。
在同一时期,Anna Ridler
拍摄了数千朵郁金香,手工标注每一朵,把数据集本身作为艺术品展出,后来又在上面训练了一个模型。我通过构建这个项目时
AI 代理带回的参考文献发现了她的作品,并且很喜欢它,因为这个
项目做了非常相似的事情:手工策划一组图像,然后针对它们进行训练。
最近大多数关于语言模型的 RL 工作都使用可验证的奖励。例如,有已知答案的数学题、能通过测试的代码,或者对错分明且运行成本低的评分器。这个项目更接近更早的例外——RLHF,即模型从人类偏好中学习一个奖励模型。
这里的奖励是审美偏好。没有正确答案。这个项目真正的问题
是:你能否在品味之上做 RL。
奖励,正如他的博客所定义、也正如我构建的 RL 环境所实现的那样:
| 术语 | 权重 | 衡量内容 |
|---|---|---|
gate |
||
| 0.05 | 草图能编译、能画出东西、没有作弊 | |
length |
||
| 0.05 | 对更长代码片段的软性推动 | |
| 成对评判器 | 0.60 | 风格,与从池中抽取的参考进行比较 |
HPSv3 是一个开放的 7B 偏好模型。给它一张图像和一段文字描述,它会返回
一个分数,表示一个人对该图像的偏好程度。它是在大量
人类对成对图像的选择数据上训练的,因此它的分数是许多人品味的平均值。成对评判模型是
通过 HF Inference Providers 调用的通用视觉模型。成对评判模型会看到候选
画作
与从池中随机选取的四张参考图并排放置,并由一段关于权衡标准的文字描述(晕染、半透明淡彩、柔和边缘)引导,每次比较都以两种呈现顺序进行,其分数是候选画作
赢得的比较所占的比例。它唯一的标准就是那个池,因此它的分数就是我的品味,正如
那些评分所编码的那样。
那些就是 Narreddi 收敛到的权重。在这里,池定义了品味。这把工作从调超参数转变为构建决定何为美的集合。
我用这个奖励训练了三次运行。它们唯一的区别在于两个模型评判之间的权重分配:
| 运行 | 成对评判 | HPSv3 | 角色 |
|---|---|---|---|
judge-led |
|||
| 0.60 | 0.30 | 原始混合,在第 110 步停止 | |
hps-led |
|||
| 0.30 | 0.60 | 中间点,在第 110 步停止 | |
hps-only |
|||
| 0.00 | 0.90 | 验证运行,在第 60 步停止 |
我从 hps-only 开始,以验证该流程是否能够学习。一旦奖励开始上升且指标健康,就没有理由再运行更久,所以我转而启动了两个更长的运行。更长的运行所提出的问题是:你能把 HPSv3 的多少能力交给成对评判?评判承载的权重越多,奖励就越意味着我的品味而非所有人的品味,也就应该越难攀升。顺带一提,如果你把它推得足够远,或者你的风格与平均值相差太远,模型可能会完全停止。
幸运的是,它并没有停止,两个开启成对评判的运行也都学会了。人工评分的池可以引导策略,至少从指标和最终画作来看是这样。数字如下。
免责声明。如果我们使用前沿模型,它已经可以根据提示生成绘制水彩画的 JavaScript 代码。这就是起点。这里的工作是关于教一个更小的模型去做这件事,并结合一个人自己的艺术偏好。
环境包裹了模型与奖励之间的一切,包括模型用于绘画的 JavaScript 库、限制它的系统提示、渲染每张草图的无头 Chromium,以及拒绝作弊的门控。
这个库所做的工作比看起来要多。
p5.brush,由
@acamposuribe 开发,模拟的是一种媒介,而不是绘制
形状:颜料会渗出填充的边缘,纸张有纹理,笔触有分量,流场会拖动笔触四处移动。当模型调用 brush.fillBleed(0.25)
时,它是在决定墨水
渗出多远。
注意。p5.brush 的作者早在这一切之前就一直在尝试教机器画画。2022 年,他创作了一个生成艺术系列,其中隐藏着一本关于教 p5.js 像孩子一样画画的日记:“它几乎不会使用蜡笔 [...] 它无法遵循简单的指令。我今天就到此为止,非常令人恼火。”这个系列原本计划有三件作品,他完成了两件。当 Surya 的视频走红时,[他引用了它],分享了那本日记,并说这件作品是第三件自行到来的作品。
p5.brush 暴露了 47 个方法。提示词允许 10 个:scaleBrushes
、noStroke
、fill
、noFill
、fillBleed
、fillTexture
、beginShape
、vertex
、endShape
和 circle
。其他三十七个方法所添加的功能,如线条、排线和自定义画笔,会破坏水彩效果。有了这十个方法,模型只能绘制填充形状,而库会为每一个形状添加晕染。
draw()
以及它渲染到的内容。注释是模型自己的。奖励 0.864,129 行,第 22 步。每幅画的完整源代码都在 rollouts 数据集中。他的博客文章为我节省了大量可能浪费在迭代提示词上的时间。冗长的 API 参考会让模型发明不存在的方法,而他 200 次 GEPA 迭代收敛到了一个没有文档的严格允许列表。我看到了同样的失败,并手动编写了允许列表。我对该配方唯一的补充是一句话:将每片花瓣涂两到三次,先涂一大笔,然后在里面涂一笔更小、更不透明的。这个小改动让我的输出色彩丰富了很多。
注意。如果你是第一次听说 [GEPA],它是一个自动提示词优化器。一个语言模型用平实的语言反思当前提示词在哪里失败,并提出一个更好的提示词,然后循环重复。
门控是最后一块。草图必须能编译,使用库而不是直接调用 p5,在画布上放置真正的颜料,并且不能试图欺骗评分器,例如在画布上写文字。
池子由 178 幅画 组成,根据我的个人偏好分为
两个层级,love
和 okay
。所有这些实际上都是由模型生成的。四个开放权重模型,通过 Inference Providers 调用,编写了 p5.brush 草图,每个模型都基于一张
来自 iNaturalist 的真实、开放许可的芙蓉照片。一个视觉模型对每张草图给出了书面
反馈,经过三次细化迭代。然后每一张最终渲染图都由我逐一评分,178 张入选。
| 生成器 | 画作数量 |
|---|
在这里,我选择了四个不同的模型家族来测试它们不同的风格。这四个是在快速可靠性检查中每次都能生成有效草图的开放模型,另外两个候选模型因未通过而被放弃。如果你想生成自己的池子,你可能会选择其他模型。
这些层级在奖励中起着实际作用。当成对评判器抽取四个参考时,一半来自 love
,一半来自 okay
,因此策略总是面对一些它
有时能击败的对手,而且无论对哪个层级,获胜的报酬相同。这是我少数几个
有意的改动之一:原始版本只与最高层级比较,而我在抽取中保留了
较容易的层级,这样弱小的早期策略仍然能获得信号。
那里没有人类创作的画作,这是一个真正的局限。p5.brush 是一个小众库,其中存在的、带有可访问代码的人类作品只有寥寥几件,远达不到训练语料库所需的数量,正如他的博客也指出的那样。
正如我之前已经讨论过的,这里有趣的想法是,模型将学会模仿池中包含的内容。如果我们把环境指向不同的数据集,奖励就会自动改变,而无需改动一行代码。对于我生成并公开分享的数据集,我也附上了源草图。
如果我们更仔细地观察评判器,它们回答的是不同的问题。HPSv3 判断它是否是一朵花,而成对评判器则判断它是否以我选择的风格画得好。
在任何东西奏效之前,有一段很长的奖励曲线平坦期。如果你曾尝试在没有开放工件的情况下复现一篇研究论文/博客,你大概能感同身受。每一次运行都在检验我认为关于问题所在的一个合理理论。一次运行需要很多时间,所以我在还在查看上一次运行结果的同时,就排队了下一次运行。一如既往,从更简单且能奏效的东西开始,然后在此基础上构建,才是答案。一个简单的控制任务,没有浏览器也没有评判器,是第一次成功学习的尝试,原因是我的学习率太低了。
另一个让我花时间才发现的变化是正确调整 LoRA 参数。通常的 target_modules
列表假设的是稠密模型,而 Qwen/Qwen3.5-35B-A3B 是一个专家混合模型,其大多数投影的命名方式不同,所以适配器只训练了四十层中的十层。我通过将其改为
all-linear
解决了这个问题,这会触及每一个线性层。此架构中的路由专家是融合张量,即使 all-linear
也会使其保持冻结,但其他所有部分都会获得适配器,而这已足够学习。修复方法是在 TRL 的 GRPOTrainer 中做了四处更改:
| 设置 | 从 | 到 | 原因 |
|---|---|---|---|
| 学习率 | 2e-5 | 5e-5 | |
| 《LoRA Without Regret》用于 GRPO 的上限 | |||
| 调度器 | linear |
||
constant_with_warmup |
|||
| 线性衰减在运行中途就消耗了大部分学习率,所以奖励从未起飞 | |||
scale_rewards |
|||
group |
|||
none |
|||
| 一个门控拒绝正在缩小组内其他所有优势 | |||
target_modules |
|||
| 手动列表 | all-linear |
||
| 触及每一个线性层 |
这四处更改解锁了第一次成功的运行(hps-only
),奖励明显改善。
有了这个配置,三次运行都能学习。两次评判器运行都启动了 200 步,并在 110 步时停止,奖励仍在缓慢攀升。一步需要十五到十八分钟,而混合之间的比较已经稳定,所以我停止了这两次以节省算力。每次运行前三分之一和最后三分之一的平均组奖励:
| 运行 | 步数 | 前三分之一 | 最后三分之一 | Δ |
|---|---|---|---|---|
hps-only |
||||
| 60 | 0.58 | 0.71 | +0.13 | |
judge-led |
||||
| 110 | 0.45 | 0.72 | +0.27 | |
hps-led |
||||
| 110 | 0.57 | 0.82 | +0.24 |
这三条曲线与我的品味所占的权重相符。评判器
权重越大,起点越低,攀升越嘈杂。judge-led
在最初三十步几乎平坦
才开始移动。这与解决调试的是同一个动作。把问题缩小到
某个东西能学习,然后一次一个地把困难的部分加回来。
成对评判项本身在使用它的两次运行中都有所上升。随着训练推进,模型在与池子的比较中赢得更多
比较,这是 hps-only
无法做到的。任何一次运行中都没有出现某一组坍缩到相同奖励的情况,而那种情况正是会
杀死梯度的 GRPO 失败模式。给好奇的读者:各指标曲线(HPSv3、颜料覆盖率、熵)在仓库中以 CSV 形式提供。
完整的启动命令、硬件以及将其变成另外两次运行的两个环境变量都在[这份
配方](https://github.com/adithya-s-k/HuggingEnvs/tree/main/02-watercolour)中。
**在每一次运行中,模型学到的第一件事就是停止产出糟糕的
画作**,即那些总奖励低于 0.3 的近空白画布和无定形涂抹。在
hps-only
中,组均值上升的四分之三来自糟糕画作变得罕见。在评判运行中这种坍缩更加陡峭:低于 0.3 的 rollout 在 judge-led
的三段中从 99 降到 16,在 hps-led
中从 37 降到 4。
这就是为什么最直观的视觉呈现——每一步的最佳画作——在
hps-only
中几乎看不出差别。它在整个运行中变化 +0.034,而中位数变化 +0.155。
学习体现在分布的中段。
成对评判改变的是顶部。 在 hps-only
中,画作变得更可靠,但没有变得更好。好画作的质量对
组均值只贡献了 +0.03,而且一旦 HPSv3 看到中心周围有花瓣和一根茎,它就不再要求
更多颜料。开启评判后,故事的另一半出现了。这里的更好意味着更接近
池子,也就是更接近我评为“好”或我更喜欢的作品。这在 judge-led
中贡献了 +0.12,在 hps-led
中贡献了 +0.16
,每一步的最佳画作也上升了,并且
两次运行中颜料覆盖率都翻倍(0.11 到 0.23,以及 0.13 到 0.30),而 hps-only
几乎没让它动。有了一个可供追赶的参照,一幅好画作仍然可以变得更好,模型也开始因使用更多颜料而获得奖励。
还有一个发现。模型忽略了一条明确的指令,而且它这样做是对的。
系统提示要求十五到三十个填充形状。如果我们看真实的均值,它在 7 到 9 之间,而且
n_shapes
在任何一次运行中都几乎与奖励不相关(+0.000、−0.14、+0.07)。策略
并没有因为遵守那句话而获得奖励,所以它不遵守。
hps-only
这条路线也有一个上限。如果每个 rollout 都匹配它的好
画作,那次运行的均值会停在 0.771。更多步数是否会打破它,是一个悬而未决的问题。
这些画作还显示出表格所遗漏的东西。在每一次运行内部,它们看起来都
很相似。随着训练推进,每一组内部的奖励变得更接近,开场视频中的中位数画作看起来像是同一朵花的不同演绎。这就是 GRPO
在用由单一主题构建的池子做它被设计来做的事。池子决定了
什么算作多样性,就像它决定了什么算作质量一样。如果奖励
只为匹配某一朵花而支付,模型就学会画那一朵花。更多样的输出需要
更多样的池子,而构建一个池子是更多的策展工作。Surya 较新的作品就是一个例子。[Alex Yango 的动物
画作](https://x.com/alexyango/status/2091696296931574217)是同样的配方,只是
池子里的不同选择。这是审美奖励与数学评分器之间最大的区别。数字背后是一项非常人性化的工作,即决定什么该被纳入奖励集合。Jason Liu 关于品味的文章用一句话概括了通用版本。AI 把瓶颈从制作转移到了察觉。
Surya 在博客结尾列出了一些他最喜欢的作品。我不挑选自己的,而是下面放一面墙,展示在两次评判运行中奖励得分最高的 178 幅画作,与参考池所包含的数量相同,顺序不分先后。打开它,挑选你自己的。
要做出选择,你看了很多,留下了少数几幅,而这正是构建这个项目奖励的工作。每一次运行的每一幅画,连同它的草图和奖励,都在 rollouts 数据集中,并可在这个画廊中浏览。
由于奖励部分基于我的品味,以我作为观看者的评判来收尾是公平的。在我看来,judge-led
是最终最多样、在艺术上最有趣的运行。hps-led
画出了令人信服的水彩画,但它最好的作品共享一种柔和、湿画法的外观,几乎自成一种风格。hps-only
收敛得最厉害,它的大多数画作都落在相同的颜色上。你可以在画廊中自行判断,那里有每一次运行的每一幅画,可按步骤和奖励排序。
这个项目主要是基础设施。一次运行需要一个训练器、两个 Space、一个推理路由器和 websocket,才能连续数小时保持健康,而每一个悄悄失败的部件都会在别处变成一个错误的数字。一半的工作是检查你读到的数字是否与实际发生的情况相符。
基础设施的故障正以零的形式进入奖励。 一次超时的渲染或一个没有应答的评分器,与一幅糟糕的画作得分相同,在组内为 0.0。在我所有的运行中,这大约占 rollouts 的 1.5%,而在最差的一次运行中,达到了 5.2%。这会让模型在噪声上训练,所以这些路径现在
返回 None
,该 rollout 被排除在组外。
我还在 OpenEnv 中发现了一个 bug,并将修复提交到了上游。 客户端保持一个
持久 websocket,而被远端关闭的 socket 仍被缓存,因此之后每次
调用都失败,即使环境是健康的。我花了两次半途而废的运行
才发现它。修复已[提交
上游](https://github.com/huggingface/OpenEnv/pull/1103),用它启动的运行
此后一直运行干净。
judge-led
运行,各取最好的四幅。第 12 步的画作是否看起来差了半分,由你决定。一步的奖励取决于它抽到了哪些参考。 成对评判器
每步采样四个参考,所以每一步面对一组不同的对手,而有些抽取就是更难。GRPO 本身大多是安全的,因为优势是在组内
计算的,一次困难的抽取会让整个组一起移动。我
当时读的曲线并不安全,一些看起来像糟糕步骤的东西只是困难的抽取。
上图就是一个例子。第 12 步比第 11 步低了半分,主要是因为
它抽到了这次运行中最难的参考,而画作本身看起来差不多。
四舍五入的数字,且仅针对已完成的运行。
| 组件 | 所需资源 |
|---|---|
| 训练器 | 1 块 H200。60 步需 18 小时,110 步约需 34 小时 |
| HPSv3 | 一个 a100-large Space,在整个运行期间保持在线 |
| 环境 | 一个 cpu-upgrade Space,可以及时完成渲染 |
| 成对评判器 | Qwen/Qwen3-VL-30B-A3B-Instruct 的 Inference Providers 配额 |
| 素材池,一次性 | 来自 iNaturalist 的开放许可照片、四个生成器的 Inference Providers 配额,评分则需你亲自投入时间 |
一步是八次 rollout,耗时十五到十八分钟,其中 **70% 到 80% 是
渲染**。单次渲染耗时 69 到 96 秒,而截止时间是 90 秒。部分原因是
意料之中的:这个 Space 没有 GPU,所以 Chromium 用软件方式渲染 WEBGL 画布,而
p5.brush 的晕染和纹理是繁重的像素运算。即便如此,我原本以为它会
更快,而我至今没有找到全部原因。
评分器的开销可能超过使用它的训练:HPSv3 必须在整个运行期间保持在线,所以运行结束时请暂停该 Space,或设置它的休眠计时器。
一切都运行在 [HF
Jobs](https://huggingface.co/docs/huggingface_hub/guides/jobs) 上,环境是一个
Docker Space,指标记录在
trackio 中。
这个项目的原则是在所有资源开放的前提下复现该配方,而不是改进它,所以一路上积累了一堆未尝试的想法。以下是我真正会去尝试的那些,按证据多少排序。
多步,并让模型看到自己画的东西。 这是我会尝试的第一件事。原博客
训练的是单轮,所以我也训练单轮,而在这种
设置下,模型是闭着眼睛作画的。没有任何图像输入,它得到的唯一
反馈就是一个数字。反馈循环有效的证据就是素材池
本身。参考画作来自模型在视觉评判器下迭代三轮的产物,后面的轮次
更好。定义奖励的素材,是用策略从未获得过的循环制作出来的。
更小的模型。 有证据表明 35B 超出了所需。在我的侧
实验中,一个 4B 模型已经能写出通过门槛的有效草图。如果一个 4B 能学会
这件事,实验成本就会下降一个数量级。
清单上的其他想法还有:在开始 RL 之前对素材池来源做 SFT、显式地奖励颜料
使用、随着运行推进把评判器的参考混合从简单逐步移到困难,直到
只剩下 love
、放宽十种方法的允许列表以获得更大的视觉范围(我在这方面的
尝试让更多草图崩溃,并破坏了水彩观感),以及通过对同一张图像评分两次来检查成对评判器到底有多一致。
而且这个方法并不专属于花卉。[Alex Yango 用同样的
机制画了动物](https://x.com/alexyango/status/2091696296931574217),[Brendan Hogan
训练了画布动画](https://x.com/brendanh0gan/status/2092650655789855222)
,对照的是一批人工评分的片段。我之前还用 [Simon
Willison 的鹈鹕
基准](https://huggingface.co/blog/sergiopaniego/pelican-env-openenv) 玩过类似的东西,那里代码会被渲染成图像并评分。
而在这一切之下,是这个项目无法解答的问题。**由模型创作的 178 幅画作
定义了这个训练后的模型认为什么是美的。** 素材池是瓶颈,而它是整条流水线中
没有原则性答案的那一部分。
对于任何复现此工作的人,有两处与 Narreddi 的配方有意偏离,这两点已在文章前面讨论过:
love
以及一半来自 okay
,而不是
仅与顶级层级比较,因此早期较弱的策略仍能获得信号。其余部分,LoRA 使用 all-linear
,基础设施故障返回 None
而不是 0.0,在第 110 步停止评判运行,这些是我在此过程中不得不做出的决定
因为他的博客没有具体说明。他的实现未公开,所以
我无法判断它们是与他的选择一致还是有所偏离。
| artifact | where |
|---|---|
| 配方,以及如何复现 | |
02-watercolour/ |
watercolour-reference-pool
watercolour-env
watercolour-hpsv3
watercolour-grpo-hps-only
watercolour-rollouts-hps-only
watercolour-grpo-judge-led
watercolour-rollouts-judge-led
watercolour-grpo-hps-led
watercolour-rollouts-hps-led
watercolour-gallery
judge-led
hps-led
hps-only
results/
本文中每次 rollout 的数字可以从已发布的数据集重新计算。这里没有任何内容依赖于任何 Space 保持开启。
方法和原始想法属于 Surya Narreddi。库属于 Alejandro Campos Uribe。
每次运行的每幅画,以及创作它的草图
显示轨迹信息和可视化
显示交互式轨迹可视化
显示交互式轨迹可视化
