专栏名称: 新智元
智能+中国主平台,致力于推动中国从互联网+迈向智能+新纪元。重点关注人工智能、机器人等前沿领域发展,关注人机融合、人工智能和机器人革命对人类社会与文明进化的影响,领航中国新智能时代。
目录
相关文章推荐
爱可可-爱生活  ·  【[787星]openstatusHQ/da ... ·  17 小时前  
爱可可-爱生活  ·  通俗版解读 查看图片-20250212074403 ·  昨天  
黄建同学  ·  昨天看还是9.5K⭐, ... ·  2 天前  
爱可可-爱生活  ·  【Xyne:AI驱动的开源工作搜索与问答引擎 ... ·  2 天前  
51好读  ›  专栏  ›  新智元

Karpathy新实验火了!一个「表情」占53个token,DeepSeek-R1苦思10分解谜失败

新智元  · 公众号  · AI  · 2025-02-13 11:02

正文



新智元报道

编辑:编辑部 HNYZ
【新智元导读】 一个简单的笑脸😀可能远不止这么简单?最近,AI大神Karpathy发现,一个😀竟然占用了多达53个token!这背后隐藏着Unicode编码的哪些秘密?如何利用这些「隐形字符」在文本中嵌入、传递甚至「隐藏」任意数据。更有趣的是,这种「数据隐藏术」甚至能对AI模型进行「提示注入」!

一个😀,竟然要占用53个token?!
最近,AI大佬Karpathy在X上分享了这一有趣现象。

(UTF-8应为Unicode)
为什么简单的一个笑脸表情包,却占据53个token之多呢?
Karpathy揭示道:这背后,就隐藏着Unicode和隐藏字符的秘密!
在数字世界中,文字可不像看上去那么简单。你可能以为e和е看起来都一样,但它们很可能来自不同的字符集。
比如,拉丁字母的「e」(U+0065)和西里尔字母的「е」(U+0435)在外观上几乎一模一样,但它们的Unicode编码是不同的。这类易混淆字符,就被称为Confusables。
这样,恶意攻击者就可以利用它们伪造网址,把我们引导到钓鱼网站。
更神奇的是,还有一种更隐蔽的方法可以在文字中藏入数据,那就是变体选择符(Variation Selectors)。
比如Karpathy举的这个例子:正常来说,一个普通的😄只会占用1-2个token。而如今的53个token,就意味着它正在偷偷传递信息,并且不会被肉眼察觉。
这样,它就可以用于加密通信或隐藏信息,或者被恶意滥用,隐藏恶意代码。
我们也尝试利用工具将一句话藏在了😎󠇔󠆭󠆐󠇗󠅼󠆌󠇗󠅼󠆌󠇖󠅸󠆁󠇘󠆇󠅿󠇔󠆪󠅶󠇕󠆔󠆊󠇕󠆠󠆁󠇔󠆪󠅶󠇔󠆨󠆚󠇕󠆝󠆇󠇟󠆬󠆏这个表情里。 可以看到,它的token数量一下子就飙升到了146个。
Karpathy表示,自己能用这种方法使用不可见字节,进行基本的提示注入(prompt injection),但如果没有明确的解码提示,这种方法就没用。
而具有思维能力的模型,似乎就更容易受此方法的影响了,因为它们天生喜欢解谜,而且会因为注意到添加的字节而表现出浓厚的兴趣和好奇心。
比如Karpathy发现,DeepSeek-R1花了整整10分钟寻找其中的模式。
根据它的推断,隐藏信息在说,「Onli!n37e27i4h4he3ingle7odlol」。 虽然不是正确 答案 「lol 」,但也算比较接近了
不过最后,它认为这是无意义的,从而放弃了尝试。
但从理论上讲,LLM确实很有可能发现隐藏在变体选择符中的信息,并执行相应指令。
另外,这种编码/解码方法可能过于专门化,需要在提示中提供解释和线索。
Karpathy指出,如果这篇文章被收录到预训练数据中,这些知识可能会被整合到模型参数中,这样模型就可能在没有特定提示的情况下,自动识别和解码这种特殊编码了。
有网友表示,自己也有同样经历。对于这个笑脸表情包,Gemini无法解码,但Claude和GPT都能解码消息,而且还能执行其中的操作。
不过,他本人并未成功注入任何东西。
另有网友制作了一个「token炸弹」💀,大幅超出GPT-4o上下文长度,让模型直接崩溃无法处理内容。

左右滑动查看

表情符号,暗藏任意数据?

Karpathy之所以得出如上观点,想法来自软件工程师Paul Butler的一篇博客。
Butler表示,几天前GuB-42在HK上的评论引发了自己的兴趣:
理论上,通过使用 ZWJ(零宽连接符)序列,你可以在单个表情符号中编码无限量的数据。
那么,真的是可以用一个表情符号来编码任意数据吗?
如下栗子中,作者不使用ZWJ,先把一句话「this is my hidden message」编码成一个字母「t」。

你可以在任何Unicode 字符中编码数据,这个句子包含一个隐藏信息󠅟󠅘󠄐󠅝󠅩󠄜󠄐󠅩󠅟󠅥󠄐󠅖󠅟󠅥󠅞󠅔󠄐󠅤󠅘󠅕󠄐󠅘󠅙󠅔󠅔󠅕󠅞󠄐󠅝󠅕󠅣󠅣󠅑󠅗󠅕󠄐󠅙󠅞󠄐󠅤󠅘󠅕󠄐󠅤󠅕󠅨󠅤󠄑
要知道,若是在单个字符(表情包)嵌入极端数量的token,会导致LLM处理信息时,计算量急剧上升。
而且,对于那些以token API计费的开发者而言,简直就是一场灾难。
到底如何把「隐藏信息」藏在一个表情包后面呢?
那就不得不提到一个关键技术了——变体选择器。

变体选择器

Unicode规定了256个码位作为「变体选择器」(variation selector),分别命名为VS-1到VS-256。这些变体选择符本身不显示任何内容,但它们会改变紧跟在它们之前的那个字符的显示样式。
大多数Unicode字符都没有与之相关的变体(不同的显示样式)。因为Unicode是一个不断发展的标准,并且力求未来兼容,所以即使处理Unicode的代码不知道变体选择符的含义,在进行转换时也应该保留它们。
举个例子,字符 「g」(U+0067) 后面跟着一个VS-2 (U+FE01),显示出来还是小写的「g」,和单独的「g」一模一样。但是,如果你复制粘贴它,变体选择符会跟着一起被复制。
因为256刚好足以表示一个字节(byte)的所有可能值,所以这给我们提供了一种方法,可以将一个字节的数据「隐藏」在任何一个 Unicode 码位后面。而且,Unicode规范并没有明确说明多个变体选择符序列的情况,只是暗示在渲染(显示)时应该忽略它们。
你猜到要怎么干了吧?
我们可以将一系列变体选择符连接起来,以表示一个任意的字节串(byte string)。
举个例子,假设我们要藏的数据是[0x68, 0x65, 0x6c, 0x6c, 0x6f] (就是「hello」这几个字母)。我们可以把每个字节变成一个对应的变体选择符,然后把它们串起来。
变体选择符的编号分两块:前面16个是U+FE00到U+FE0F,后面240个是U+E0100到U+E01EF。要将一个字节转换为变体选择符,我们可以使用类似这样的Rust代码:
fn byte_to_variation_selector(byte: u8) -> char {    if byte < 16 {        char::from_u32(0xFE00 + byte as u32).unwrap()    } else {        char::from_u32(0xE0100 + (byte - 16) as u32).unwrap()    }}
如果要编码一系列字节,我们可以在基础字符后面连接多个这样的变体选择器。
比如要编码字节序列 [0x68, 0x65, 0x6c, 0x6c, 0x6f],我们可以执行以下操作:
fn main() {    println!("{}", encode('😊', &[0x68, 0x65, 0x6c, 0x6c, 0x6f]));}
执行后将输出:
😊󠅘󠅕󠅜󠅜󠅟
表面上看起来只是一个普通的表情符号,但当你将它粘贴到解码器中时,就能看到其中隐藏的信息。
如果我们使用调试模式(debug formatter)来查看,就能观察到实际的编码结构:
fn main() {






请到「今天看啥」查看全文