原文地址:How Claude Watermarks AI-Generated Text,by Sebastian Raschka, on 2025-08-22
Claude如何为AI生成文本添加水印
一段 48 分钟的视频讲解,涵盖 token sampling、watermark detection 与水印移除方法
我最近发布了一则笔记 https://substack.com/@rasbt/note/c-315339554?r=gb4sb&utm_source=notes-share-action&utm_medium=web ,介绍了 Claude 全新的文本水印方案 https://www.anthropic.com/news/claude-text-watermark 及其实现方式。这个话题热度很高,也引发了非常热烈的讨论,我觉得可以进一步展开,更详细地讲解它的工作原理。
这次我没有采用常规的文字文章形式,而是录制了一期关于该主题的小型讲座(算是换一种内容呈现形式)。下方是视频以及对应的文字稿。
原本我只打算做 10 页幻灯片,录一段 10 分钟的短视频,但在制作过程中不断补充了不少关键细节,最终做成了超过 50 页幻灯片、时长 48 分钟的内容。希望这份讲解能把原理讲清楚,祝大家观看愉快!
如果你偏好使用 YouTube 播放器,可通过此链接观看:https://youtu.be/tLv7qRWFMlw
讲座幻灯片下载地址:https://sebastianraschka.com/pdf/slides/2026-08-18-claude-watermarking.pdf
视频文字稿
注:以下文字稿经过轻度编辑优化,提升了可读性,但完整保留了原视频讲座的内容顺序与讲解逻辑。
https://www.youtube.com/watch?v=tLv7qRWFMlw
Claude 文本水印的工作原理

(第 1 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw)
大家好。几天前,Anthropic 宣布将为旗下 Claude 模型的文本输出添加水印。我随后在社交媒体上发了一篇帖子,简要解释了它的运作方式,那篇帖子的传播度很高 —— 当然,热门的不是水印本身,而是大家对它背后的原理和机制更感兴趣。
既然如此,不妨把内容展开,做更细致的讲解。那篇帖子里只有一张示意图,也留下了很多疑问和讨论空间。所以我打算多做几张图示,把原理讲透。其实我最开始只打算做 10 页幻灯片带大家过一遍,最后做成了 50 页。希望这份内容能讲清楚这项水印技术的运作方式,以及水印失效、被移除的相关场景。
我觉得这是个很有价值的话题:如今很多人都在使用大语言模型(LLM),互联网上也充斥着大量可能由 LLM 生成的文本。而水印技术即将普及,不少人会担心水印会不会降低文本质量,也好奇水印到底有什么实际作用、它究竟意味着什么。
如果我们能更深入地理解水印是什么,就能更理性地判断它的利弊,形成自己的看法。所以本次分享的核心目标,就是讲清水印的底层机制,以及 Claude 这类文本水印的实现方式。

(第 2 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=101s)
这个话题也是一个很好的案例,能说明为什么从零开始理解事物会很有价值。这项水印技术,同时也能帮我们理解常规模型、乃至通用 LLM 的底层运作逻辑。
大家可能知道,我很喜欢 “从零开始” 的学习方式 —— 我写过 Build a Large Language Model From Scratch、Build a Reasoning Model From Scratch 这类书籍,也发表过不少 “从零开始” 系列的文章。对我来说,“从零开始” 往往包含代码实践。这次的内容不涉及代码,但从零写代码确实是非常有用的学习方法,它能帮你真正理解一项技术的实现细节。
在此基础上,我们才能提炼出对应的原理、图示和概念。如果不亲手实现、不写代码,很多时候对技术的理解会很模糊。当然我也发现,现在不是所有人都会从零写代码了 —— 放在以前,我们只能自己手写代码,所有实现都靠人工完成;而现在,LLM 也能写代码了。
但这并不代表读代码就没有价值,代码里承载了大量信息。就拿水印技术来说,如果你花时间从零实现过一个 LLM,就能立刻明白内部的 sampling 过程是怎么实现的,也能对应上相关的代码片段。反过来,你就能清晰地意识到:水印是在这个环节被植入的,它会带来这样那样的影响。
所以我认为,即便现在大家不会总从零写代码,出于学习目的、为了深度理解一项技术而去从零搭建某个系统,依然很有价值;对科研来说也是如此,你可以在透明的环境下调试修改,不用被层层抽象封装挡住视线。
顺便一提,这次的幻灯片里,我用了很多来自我 “从零编码” 教学素材里的图示,也算是一种巧合的联动。

(第 3 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=238s)
就在几天前(8 月 14 日),Anthropic 发布了《How Claude’s Text Watermark Works》这篇文章。文章更像是录屏演示,涵盖了幻灯片里的全部内容,细节很丰富。他们其实更新过好几次,我最开始读的时候内容要短得多。
不过整体来说,文章还是偏概念性的,只有概述,连一张示意图都没有,所以还是很难直观理解他们的方案。文章花了很多篇幅讲 “为什么要做水印”,但没讲清楚 “具体怎么做”。文中只引用了一篇相关论文,内容也非常技术化。
所以我觉得,不妨退一步,从最基础的讲起,帮大家理解这项水印技术到底要实现什么。
顺便说下水印的设计初衷:Anthropic 希望能识别出网络上的文本是否由自家模型生成 —— 比如他们可以判定 “这段文本是由 Claude Opus 4.8 生成的”。水印对用户是不可见的,只有 Anthropic 自己能解码识别,判断文本是否携带自家水印。
至于为什么只有他们能做到?后面的内容会讲到(希望视频不会太长),我们一步一步来。
(对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=338s)
LLM 文本生成的工作原理

(第 4 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=335s)
我先做一个简短的铺垫,讲解 LLM 的文本生成机制。理解了这个基础,再看水印原理就会轻松很多 —— 你会发现,水印并不是在文本生成之外额外叠加的、成本很高的复杂模块,它只是常规文本生成流程里的一个微小调整。

(第 5 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=361s)
比如我们使用 ChatGPT 时,输入提示词 the capital of Germany is,模型会输出 Berlin 作为回答。这个例子里,模型生成了两个 token:“Berlin” 和句点。为了简化讲解,我们先假设它只生成一个 token,也就是下一个 token 是 “Berlin”。
这个 token 在内部是如何生成的?当我们输入 the capital of Germany is 并得到 “Berlin” 这个结果时,后台到底发生了什么?

(第 6 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=401s)
接下来的几页幻灯片,我会简要讲解下一个 token 生成的底层过程。

(第 7 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=410s)
还是以提示词 the capital of Germany is 为例。第一步是将文本转换为 token ID,也就是分词(tokenize)后映射为对应的编号。这一步在 LLM 外部完成,不属于模型内部的计算,只是把文本转换成嵌入层可以处理的格式。

(第 8 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=442s)
转换后的 token ID 会输入 LLM,模型会输出下一个 token 的分数分布。

(第 9 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=451s)
这里只是 LLM 内部流程的概览,我不会展开讲模型本身的运算机制 —— 我在其他 “从零搭建 LLM” 的视频和书籍里已经讲过很多次了。
重点在于:当我们生成下一个 token(比如 “Berlin”)时,会得到一个分数分布,这就是 LLM 的输出结果。图里展示的是 logit 值,也就是原始分数,取值范围从负无穷到正无穷。这个例子里的分数大概在 – 8/-9 到 20 之间。
我们可以把这些分数转换成概率分布,但严格来说,根据 sampling 方式的不同,这一步不是必须的。你可以把 logit 理解成原始打分,这些分数覆盖了整个词表(vocabulary)。

(第 10 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=519s)
词表包含了 LLM 所有可能生成的词汇。在这个例子里,词表索引的第 19846 号位置拿到了最高分。我把分布做了拉伸展示 —— 如果真的把这个提示词输入 LLM,结果会更极端:其他 token 的分数都无限趋近于 0,“Berlin” 的分数会高得多。为了让图示更直观,我放大了局部,拉开了分布的差距。
“Berlin” 得分最高很好理解:对于这个明确的提示词,它就是概率最高、最合理的下一个 token。其他候选比如 Hamburg、Munich,都是模型可能猜错的选项,但以现在模型的能力,基本都能确定 “Berlin” 是正确答案。
图里的横轴是词表索引,覆盖了全部词汇。如今的 LLM 词表大概有 25 万个可输出 token,我这里只截取了 19800 到 19900 的区间,不然幻灯片根本放不下。如果真的展示 25 万个 token 的完整分布,所有柱子都会窄到几乎看不见。这只是为了教学做的截断展示。
核心结论是:常规文本生成中,我们会得到这样一个分数分布,然后选择分数最高的那个。

(第 11 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=633s)
后面我会更详细地讲选择机制 —— 其实不一定严格选最高分的那个。但为了简化,我们先假设这里取的是最高分,也就是第 19846 号 token。

(第 12 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=652s)
选中的 token ID 会经过反分词(detokenize)处理,还原成文本 “Berlin”。这就是完整的流程:输入提示词→转换为 token ID→输入 LLM→得到分数分布→选出下一个 token→还原为文本。

(第 13 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=672s)
生成的文本会被追加到输入内容之后。如果问题需要输出多个 token,就会循环执行这个流程,直到回答完成 —— 通常是模型生成了 end-of-text token。
为了简化,我只展示了生成一个 token 的单次迭代,但实际过程就是不断循环,把更新后的输入再喂给 LLM,生成下一个 token。
那么,我们具体是如何 sampling 出下一个 token 的?
(对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=699s)
Next-token sampling 的工作原理

(第 14 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=704s)
我刚才提到,技术上我们可以直接选分数最高的 token,这叫做 greedy decoding(贪心解码),是采样方式的一种。但大多数 LLM 在实际使用时,并不会一直采用贪心解码、永远选最高分的 token。
因为如果换一个提示词,永远选最高分未必是我们想要的效果 —— 模型会倾向于照搬训练数据,每次都给出完全一样的回复。我们通常希望输出有一定多样性,但又不能随机到生成毫无意义的内容。
所以实际的 sampling 流程,一般是先把分数转换成概率值。

(第 15 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=748s)
图里展示的就是分数分布。为了方便,我用 NumPy 来举例,不管用 PyTorch 还是其他工具,核心概念都是一样的。
我们会计算 softmax。实际工程里一般会用 PyTorch 等框架里数值稳定的 softmax 实现,能同时处理极大正值、极小正值和极大负值的情况。这里我写的是标准 softmax 公式,只是为了更易读,细节不用深究。

(第 16 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=800s)
重点在于,转换之后,所有概率分数相加等于 1,相当于做了一次重归一化。这就是 softmax 转换的全部作用:把原始分数归一化为概率分布。
得到概率之后,我们就可以用随机数或者随机采样算法来选词。比如在 NumPy 里,可以用choice方法,传入词表索引,再把概率作为权重传入。权重就代表了 “某个 token 被选中的可能性”。
举个例子:如果归一化之后,“Berlin” 的概率是 99%,其他所有 token 加起来只有 1%,那么采样 100 次的话,99 次都会选中 “Berlin”。
在训练充分的真实 LLM 里,“Berlin” 的概率可能会达到 99.999999%,几乎每次采样都会得到 “Berlin”,因为模型对这个答案非常确定。这就是从分布中采样的基本逻辑。
在此基础上还有 top-k sampling、top-p sampling 这类变体。简单来说,top-k sampling 就是先选出分数最高的 100 个 token,只在这 100 个里面做随机选择,避免抽到无意义的垃圾 token。这个例子里不用纠结具体采样方式,你可以默认这里已经是经过 top-k 筛选后的候选集。

(第 17 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=937s)
再举个例子:如果采样 10000 次,“Berlin” 的概率高达 99.9%,那么结果会是 9997 次抽到 “Berlin”,2 次抽到 “Hal”,1 次抽到 “Moh”。后面这两个基本就是无意义 token。
真实的 LLM 里,10000 次采样可能次次都是 “Berlin”,因为它的概率实在太高了。我这里是为了演示,故意拉开了分布差距。
(对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=979s)
从采样到水印

(第 18 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=981s)
前面我们简单讲了 LLM 的底层运作逻辑,这个概念本身就很有意思。不过我之前已经讲过很多次,就不赘述了,主要是为讲解水印原理做铺垫。

(第 19 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=1001s)
前面提到,采样时我们要么选最高分的 token,要么按概率采样,大多数时候都会选出分数靠前的 token。
再看一个没有水印的例子:我把提示词换成 today’s weather is “cold,”,下一个 token 可能是 “gray”,也可能是 “overcast”。和之前 “Berlin” 的例子不同,“gray” 和 “overcast” 在这里是可以互换的,都是非常合理的下一个 token,选哪个都不算错。
做随机采样时,因为两者分数都很高、差距很小,采样结果就近似于抛硬币,差不多一半概率出 “overcast”,一半概率出 “gray”。
这也是为什么同一个提示词反复问 LLM,往往会得到略有不同的答案 —— 在某些位置,两个候选 token 的概率几乎相等,模型随机选一个,而这个选中的 token 又会影响后续所有生成内容,最终输出就产生了差异。
(对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=1103s)
随机种子与确定性采样

(第 20 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=1105s)
这里简单讲一下随机数生成。比如我们用一个随机数生成器,每次生成 5 个数字,结果都会不一样。
但如果我们设置一个 random seed(随机种子),比如 123,再多次运行,生成的随机数序列就会完全一致。注意,这些数字依然是随机的、彼此不同的,但结果可以复现。
记住这个概念,马上就会用到。

(第 21 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=1166s)
比如刚才的例子,随机采样可能得到 “gray” 也可能得到 “overcast”。但如果我们指定随机种子为 42,模型就会每次都选中 “overcast”。本质上还是随机采样,但通过种子让结果变成了确定性的 —— 给定这个提示词,模型永远会选 “overcast”。

(第 22 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=1189s)
如果换一个随机种子,模型可能就每次都选 “gray”。依然是随机采样,只是通过随机种子实现了结果可复现。
(对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=1203s)
水印密钥的工作原理

(第 23 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=1204s)
Claude 的水印技术,思路和设置随机种子很像。但这个 “种子” 不是一个固定数字,而是一个 secret key(密钥),类似 API 密钥;再结合前面 4 个 token 的内容,共同推导出随机种子。
核心逻辑和上一页是一样的:相当于有一个固定的随机种子,每次都会选出同一个下一个 token。区别只是,这里不用固定的数字种子,而是用密钥 + 前文 token 来推导种子。后面会展开讲细节。

(第 24 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=1266s)
简单来说,水印会让文本生成在特定位置变得更有确定性。
比如左边这些合理的文本:句子 The weather today is cold and 后面,可以接 “overcast” 也可以接 “gray”;下一处可以是 “light” 或 “gentle” breeze;breeze 后面可以是 “moving” 或 “blowing” through the trees;街道可以是 “quiet” 或 “still”。每一处都有两个几乎等价的可选词。
在没有水印的情况下,运行同一个提示词,每次都可能得到不同的组合。按这里的位置数算,一共能产生 128 种不同的输出文本。文本越长,可替换的位置越多,组合数就越庞大。
举个例子,一种可能的输出是:
The weather today is cold and overcast. A light breeze is moving through the trees, and the streets seem quiet. I think I’ll stay home and read a book with a cup of tea.
另一种可能的输出是:
The weather today is cold and gray. A gentle breeze is blowing through the trees, and the streets seem still. I think I’ll stay inside and read a novel with a mug of tea.
顺便说一句,外面正好在下雨,也不知道收音效果怎么样,倒是和例子里的场景很搭。
总而言之,这两段文本都非常通顺合理,没有高下之分,只是细微的表述差异。没有水印的话,生成哪一种完全是随机的,由采样过程决定。

(第 25 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=1411s)
如果我们固定随机种子,比如设为 99,那么每次都会生成同一段文本。随机种子并没有改变采样的随机性本质,只是让结果变成了确定性的、可复现的,每次输出都完全一样。这一步还没有引入水印,只是固定了随机种子。

(第 26 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=1439s)
而水印的本质,和这个逻辑是一样的。只不过不用简单的随机种子,而是用一个 random key(随机密钥)来参与文本选择。
由此我们也能理解 Claude 官方博客里的说法:水印不会降低文本质量。从机制上看,这个结论是成立的。先声明一下,我不是在为水印背书,只是客观解释原理,请不要迁怒于讲解的人。
我想表达的是:对终端用户来说,水印的效果和固定随机种子没有区别,只是让采样过程变得确定了而已,应该不难理解。
(对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=1485s)
水印的植入位置

(第 27 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=1487s)
到这里可以做个小结:没有水印时,我们用完全随机的方式采样;有了水印之后,采样过程依然存在,只是额外加入了水印密钥,用它来驱动随机种子生成器,设定特定的随机种子,让采样结果变得确定。
两者的底层逻辑非常相似。这也呼应了我前面说的 “从零开始理解很有价值”—— 现在我们就能明确知道水印被加在了哪里:它作用于采样环节,而不是 LLM 内部。
这是个很有意思的结论:不需要重新训练模型,直接在现有 LLM 的采样阶段加上水印逻辑就可以实现,完全不用重新训练或者改动模型本身。
(对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=1573s)
水印检测的工作原理

(第 28 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=1581s)
讲完植入,再来讲讲怎么判断一段文本是否带水印。
只有持有密钥的人才能检测水印。比如网上有很多不同的文本,你随便找到一段,想知道它有没有被加水印,没有密钥是做不到的。
检测需要用到 scoring function(打分函数),用它给文本打分;如果分数超过某个阈值,就判定为带水印,否则就不带。普通用户做不了这件事,因为我们没有密钥,只有 Anthropic 持有。
不过他们在博客里提到,会开发对应的检测 API,后续会开放。声明一下,我和 Anthropic 没有任何关联,这些都是我从博客里读到的信息,仅此而已。
这个 API 可能只对部分企业开放,比如 X 平台、Substack Notes 这类需要给 AI 生成内容打标签的平台;也可能对普通用户开放,现在还不确定,只能等后续消息。
核心结论就是:只有持有水印密钥、或者使用官方检测 API,才能完成水印检测。
(对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=1680s)
如何移除水印

(第 29 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=1683s)
移除水印的话题也很有意思。理解了水印的工作原理,自然就能明白它的局限 —— 水印高度依赖文本中特定位置的特定 token。
比如这段文本里,带颜色的词就是水印作用的位置。如果我们把这些位置的词全部改掉,就能 100% 破解水印。但问题在于,我们根本不知道哪些位置是水印位点。

(第 30 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=1721s)
因为我们没有参与水印生成过程,所以不知道该修改哪些位置。实际操作中,我们只能随机改写文本,随机替换一些词,指望改到足够多的水印位点,从而消去水印。这是移除水印的一种思路。
而且我们也不知道哪些词是高分候选 —— 要知道这个,就得拿到模型权限,把提示词重新跑一遍,看哪些 token 得分最高。所以我们只能靠猜测。
比如我们可能会把 “overcast” 换成 “cloudy”,但我们并不知道 “gray” 才是原本的高分候选。这个例子里替换方向还算直观,但很多场景下并没有这么明显。
我想说明的是:普通的文本改写,只是在随机修改位置,本质上是在碰运气,猜哪些是水印位点。因为我们不知情,只能零星改几个词;但只要改的词足够多,也能破解水印。
(对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=1817s)
水印打分函数的工作原理

(第 31 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=1799s)
以上就是水印技术的核心概览。我提到过,检测水印需要用到打分函数。视频已经不短了,但作为补充,我想再简单讲讲打分函数的原理,这部分也很有意思。
它的原理略复杂,其实不理解打分函数也不影响理解水印整体。但 Anthropic 采用这种特殊设计,是为了降低检测的计算成本。
试想一下:如果要检测一段文本是否带水印,哪怕是官方自己检测,也得把原始提示词重新输入模型,得到分数分布,再用水印随机种子生成文本,然后和待检测文本做比对。这个成本非常高 —— 每检测一段文本就要跑一次 LLM,还要知道对应的提示词和模型版本,在实际场景里几乎不可行,因为很多时候我们连原始提示词都拿不到。
所以他们用了一个技巧,在采样阶段就做特殊处理,让后续检测不需要再调用 LLM。博客里提到,这个方法源自一篇《自然》期刊的论文,叫做 SynthID-Text,是谷歌几年前提出的技术。Claude 的水印方案用的就是类似的技术,不确定是不是完全一致,但官方确实引用了这篇论文。
(对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=1878s)
SynthID Text 与 Tournament Sampling

(第 32 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=1899s)
具体是怎么实现的?前面我们讲的是常规流程:输入文本→送入 LLM→得到 logit 分布→采样得到输出 token;采样过程中会用水印密钥和随机种子生成器。这个整体框架是对的,但 token 采样的具体方式比单纯的随机选择更精巧。
他们没有直接用类似 NumPy 的random.choice,而是用了更复杂的机制。

(第 33 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=1936s)
还是用之前的例子:上下文是 the weather today is cold,要生成下一个 token,候选有 “gray”“overcast”“gloomy”“cloudy”。假设 “gray” 概率 50%,“overcast” 30%,“gloomy” 15%,“cloudy” 5%,剩下的概率可以忽略。
我还是沿用之前的图示,你可以把这几个当成概率最高的候选,其他 token 概率都极低。为了更简单,我们就假设词表只有这 4 个词。
如前所述,我们可以用 NumPy 的random.choice,按概率采样出下一个 token。

(第 34 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=1993s)
我们也可以用水印密钥驱动随机种子生成器,让采样结果确定,植入我们想要的水印。但就像之前说的,采样本身成本很低,但后续检测的成本会非常高 —— 如果要检测网上随便一段文本,根本没法高效完成。

(第 35 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=2015s)
所以他们在生成阶段的采样过程中就做了特殊设计,让检测阶段可以复用这些设计。这种采样方式叫做 tournament sampling(锦标赛采样)。
它看起来可能有点复杂,但实际上没那么难,可能你需要暂停视频,对着图琢磨一下,但逻辑其实很直白,理解了就很简单。我来试着讲清楚。
我们还是有上下文,有几个概率不同的候选下一个 token。然后引入所谓的随机水印函数(random watermarking functions)。
(对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=2073s)
随机水印函数

(第 36 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=2075s)
图里有三个水印函数 G1、G2、G3。实际应用中可能有 30 个、50 个甚至更多,这里用三个只是为了幻灯片好展示。
以单词 “gray” 为例,它对应的签名(signature)是 101。意思是:用水印密钥生成随机种子,输入三个函数 G1、G2、G3;输入内容是 “gray” 以及前文中的几个词(比如前面的 “cold”),这一步我省略了上下文拼接的细节。
输入 G1 后得到值 1,这是函数本身的特性 —— 它是一个随机函数,输出要么是 0 要么是 1。在这个密钥和这个 token 下,结果就是 1。同一个密钥、换一个函数,得到的就是 0;第三个函数又得到 1。
如果有 30 个函数,就会得到一长串 0 和 1 组成的比特串。

(第 37 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=2160s)
本质上就是一个由 0 和 1 组成的比特串。这是 “gray” 对应的签名 101。
其他候选词也可以做同样的计算:“overcast” 的签名是 010,“gloomy” 是 001,“cloudy” 是 100。每个 token 都有不同的比特签名。
拿到所有候选的比特签名后,下一步就是 tournament sampling:让候选 token 两两配对比拼。
(对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=2196s)
Tournament Sampling 分步讲解

(第 38 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=2199s)
就像足球赛的淘汰赛、美式橄榄球的季后赛一样,两两对决,胜者晋级。token 也是一样,两两配对比拼,分数就来自前面的水印函数输出。
第一轮用第一个函数 G1 的结果来比拼。比如第一组是 “cloudy” 对 “gray”:“gray” 在 G1 下得 1,“cloudy” 也得 1,平局。
第二组 “overcast” 对 “gray”:“overcast” 得 0,“gray” 得 1,gray 胜出。
第三组 “gloomy” 对 “overcast”:都得 0,平局。
第四组又出现了 “gray” 对 “gray”,因为候选数量不够,有一个重复的,是随机选出来的,两者得分相同。
平局的时候,就用水印密钥驱动的随机种子来随机决定胜者。所以第一组 “cloudy” 晋级;第二组 “gray” 晋级;第三组随机选出 “overcast” 晋级;第四组随机选出 “gray” 晋级。
第一轮过后,晋级的是 “cloudy”“gray”“overcast”“gray”。接着进入第二轮锦标赛,这一轮用 G2 的结果来比拼。
“cloudy” 在 G2 下得 0,“gray” 也得 0,平局;“overcast” 得 1,“gray” 得 0,overcast 胜出。

(第 39 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=2304s)
平局的组随机选出 “gray” 晋级,另一组 “overcast” 直接晋级。最终决赛就是 “gray” 对阵 “overcast”。
决赛用 G3 的结果:“gray” 得 1,“overcast” 得 0,最终 “gray” 胜出。这就是 tournament sampling 选出 token “gray” 的完整过程。
那么水印密钥的作用是什么?回到前面的步骤,这些水印函数的输出值,都是由水印密钥决定的。密钥决定了每个 token 在每一轮的得分,所以密钥依然是整个机制的核心 —— 没有它,所有签名结果都会完全不同。
(对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=2358s)
无需重跑 LLM 的水印检测方法

(第 40 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=2351s)
以上就是改进后的采样流程。我们本来可以直接用random.choice,但那样的话,检测网上的文本就必须重跑 LLM,成本极高。而用 tournament sampling 就不用,我马上就讲原因。
这个方法看起来繁琐别扭,但它的优势在于:后续检测文本时,不需要再调用 LLM 就能完成打分,大幅降低了检测的难度和成本。

(第 41 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=2383s)

(第 42 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=2388s)
举个例子:假设我们在网上发现了一段文本,就是我们例子里的内容:the weather today is cold and gray,我们想判断它是不是 AI 生成的。
Anthropic 作为持有密钥的一方,拥有水印密钥和 G1、G2、G3 这些函数。他们把这段文本输入这些函数,计算 “gray” 这个位置的签名,得到 101,和生成阶段的结果一致。
简单来说,这个位置的比特加起来是 2(两个 1),我们就说这个位置得 2 分。这只是非常简化的演示。
如果这个位置的词不是 “gray” 而是 “overcast”,得分就是 1;“gloomy” 得分 1;“cloudy” 得分 1。我就是把每个 token 的比特位加起来,得到每个位置的分数。
这里我只算了最后一个位置,如果对文本里所有位置都做同样的计算,每个位置都会得到不同的分数。比如 “today” 得 3 分,“is” 得 2 分,“cold” 得 2 分,“gray” 得 3 分,等等。这就是把水印函数应用到全文每个位置的结果。

(第 43 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=2493s)
把每个位置三个函数的输出加起来,就得到了位置分数。水印函数的计算成本非常低,可以快速跑完整段文本,得到所有位置的分数,然后计算平均比特值。比如把所有位置的分数平均下来,得到 2.23。

(第 44 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=2515s)
如果我们修改一下文本,把 “today” 换成 “now”,把 “gray” 换成 “overcast”,这两个位置的得分就都变成了 1。再对整段文本求平均,得分就变成了 1.71。
整个计算过程完全不需要 LLM,只需要水印密钥、随机种子生成器和 G1 到 Gn 这些函数就够了。拿到平均分数之后,设置一个阈值,就能做判定。

(第 45 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=2547s)
当然实际阈值不是我举例的这个数。打个比方:我们设定 “平均分大于 2 则判定为带水印,小于 2 则判定为不带水印”。那么平均分 2.23 的那段就会被判定为带水印,而 1.71 的那段就不会。
这就是检测网络上任意文本是否带水印的方法:应用水印函数计算全文字段的得分,求平均值,再和阈值比较即可。

(第 46 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=2593s)
总结一下:tournament sampling 的核心作用,就是让水印检测变得更简单、成本更低。我们当然也可以用随机种子 +random.choice的方式做确定性采样,但那样就很难对网上的任意文本做检测。
(对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=2606s)
水印总结与局限性

(第 47 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=2610s)
核心结论依然不变:有无水印的区别,就在于采样过程是否受水印密钥控制,而采样内部采用了 tournament sampling 机制。
如前所述,检测水印必须持有密钥,以及对应的水印函数 G1 到 Gn。

(第 48 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=2634s)
再回到移除水印的话题 —— 相信不少人关心这个。理论上,只要修改所有水印位点的词,就能完全移除水印。但我们不知道哪些位置是水印位点,而且模型内部也只会选择那些 “存在多个高分候选” 的位置加水印。
比如 “trees” 这个词,可能根本找不到另一个高分的替代词,这种位置就不会被加水印。也就是说,水印只会被加在特定的、有替换空间的位置上。
因为我们不知道哪些位置是靶点,想要移除水印……

(第 49 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=2669s)
…… 就必须修改文本里的多处内容。而这对 AI 生成文本的未来意味着什么呢?我认为……
(对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=2674s)
这对 AI 生成文本意味着什么

(第 50 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=2676s)
…… 这可能反而会催生质量更差的 AI 生成文本。
如果有人想在网上大量使用 AI 生成内容,比如靠 AI 写新闻的网站,水印未必能阻止他们,生成 AI 文本依然是他们工作流的一部分。我的推测是,他们会换用其他模型。

(第 51 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=2708s)
他们会用第二个模型来改写文本,得到 “改写后的 AI 生成文本”,这样水印大概率就被消掉了。
为什么是本地模型?因为我觉得所有闭源 LLM 厂商都会跟进水印 —— 不只是 Claude,谷歌本来就是 SynthID 论文的作者,Gemini 肯定也会加水印;OpenAI 大概率也已经在做或者即将上线。我只是推测,但感觉全行业都会跟进,毕竟有欧盟法规的要求,Claude 官方博客也提到这是他们做水印的原因之一。
而本地模型至少目前还没有普遍实现水印功能。所以很多人会用本地模型来改写 Claude 生成的文本,得到去水印的内容。
为什么说这样会让文本质量下降?因为改写用的本地模型通常参数量更小、能力更弱。当然你也可以直接用本地模型从头生成,但在我看来,改写比从头生成要简单。
复杂的内容可以用最贵、能力最强的 Claude 模型来生成初稿,再用便宜的本地模型做精细的改写,消去水印。未来大概率会是这种模式。
为什么质量会变差?回到之前的图示:为了去水印而随机修改词汇,很多时候是为了改而改,很容易改得生硬别扭。最终你得到的还是 AI 生成文本,但读起来会很不自然。
(对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=2833s)
结语

(第 52 页 / 共 52 页,对应时间点:https://www.youtube.com/watch?v=tLv7qRWFMlw&t=2840s)
总而言之,我这次的目标只是讲清水印的工作原理,而不是讨论它会带来什么全球性的影响。希望这次深入底层的拆解对大家有帮助。
水印技术并没有听起来那么高深复杂,但这次也做了 52 页幻灯片,说明它也不是完全 trivial 的内容。希望这期小讲座对你有用,我们下次再见。
附:如果你喜欢这种讲解风格,我虽然不常更新 YouTube 频道,但多年来已经积累了 300 多个视频,都可以在这里查看:https://www.youtube.com/@SebastianRaschka