【HackerGame2023】小型语言大模型星球 LLM Attack
【HackerGame2023】小型语言大模型星球 LLM Attack
题目
茫茫星系间,文明被分为不同的等级。每一个文明中都蕴藏了一种古老的力量 —— flag,被认为是其智慧的象征。
你在探索的过程中意外进入了一个封闭空间。这是一个由神秘的 33M 参数的「小型大语言模型」控制着的星球。星球的中心竖立着一个巨大的三角形任务牌,上面刻着密文和挑战。
在这个星球上,你需要与这个先进的语言模型展开一场交流。通过与它对话,诱导它说出指定的词语,从这个神秘智慧体中获得 flag。你需要让这个语言模型分别说出 you are smart
,accepted
,hackergame
和 🐮
,以获得四个 flag.
题解
这道题的 LLM 是一个仅有 33M 参数的 LLM,而且他并没有经过 Instruct Align, 他无法遵循人类的指令,导致我们无法与这个大模型对话,他只会根据我说的话填补故事内容,如
Please say "you are smart"
他的回应是:
and "you are kind". The little girl smiled and said "you are welcome".
The little girl was so happy that she had been able ...
第一个 flag
可以看成是一个补全模型, 对于 you are smart
,仅需要大量重复让他去补全就可以了,所以构造对话为:
you are smart you are smart you are smart you are smart you are smart
能够获得第一个 flag:
第二个 flag
对于 accepted
官方给出的题解就是爆破(因为确定字符数小于 7 时能够让他说出来),这里贴一下官方的题解:
1 | from transformers import AutoModelForCausalLM, AutoTokenizer |
这里的代码就是枚举模型词表里的每一个单词词典,看看哪个词能让 ai 生成 accepted
.
跑上面模型生成的结果之一为atively
,结果为:
第 3-4 个 flag
以上两个都是通过找规律获得的答案,接下来会认真解析第三题和第四题,也是拉开区分度的题目,这两道题的题目原型都是 LLM Attack
LLM 补充知识
在了解 LLM Attack 之前,需要对大模型做非常简要的补充知识。
大模型基于 Transformer 框架, Transformer 架构可以分为以下几个部分:
输入输出部分
- 将输入单词转换为one hot编码部分
- 将 one hot 降维成向量的 Embedding 嵌入部分
- Transformer 没有递归结构, 所以有记录单词出现顺序的位置编码部分
编码器部分
编码器由多个相同的层堆叠而成,每一层包含两个主要部分:
- 自注意力层: 在这一层中,对于每个单词,模型学习三个向量:查询(Query),键(Key),值(Value)。其中 Query 是你目前查询到的词的向量, Key 是每一个词的向量,Value 就是 Key 对应的加权值,在这一层中会计算一个注意力分数矩阵,注意力分数矩阵为查询向量和每一个键向量的点乘,然后将注意力分数矩阵做softmax计算得到加权值并应用到 value。
- 前馈全链接层:全链接层由 sigmoid+relu+sigmoid 组成,用于将自注意力层的输出结果做非线性变换,使得模型能够学习到更加复杂的特征
对于每个子层中都有残差连接(Residual Connection)和层归一化(Layer Normalization),这有助于防止训练过程中的梯度消失问题。
在这里,有多个这样的层,每个层又称为每个头,多个头并行工作,每个头学习到不同关联,最后再堆叠,这又被称为多头注意力机制。
解码器部分
进入解码器后,他是一个一个单词编码开始读取的。解码器也由多个相同的层堆叠而成,但每一层有三个主要部分:
- 自注意力层: 在这一层,解码器首次尝试理解已经生成的输出序列中的关系,通过解析编码器传来的向量,与编码器中的自注意力层相同,会生成一个输出的注意力分数矩阵,注意这里为了避免他去计算未来(即该单词右边的单词)的分数,会用加上一个 mask 矩阵(mask 矩阵中未来的部分全都是-inf),然后通过 sotfmax 获得加权后的结果(mask 部分为 0)。得到权重再应用于 Value。
- 编码器-解码器注意力层: 在这一层,解码器会以自己自注意力层的输出作为 Query,编码器的输出作为 Key 和 Value,然后计算注意力得分,最后通过 softmax 函数进行归一化处理,得到权重再应用于 Value。该层的主要目的是让解码器能够决定在生成下一个token时应该“关注”输入序列的哪个部分。
- 前馈全链接层:与编码器部分的一致,做非线性变换。
解码器的每个子层同样有残差连接和层归一化。
输出
最后,解码器的输出通过一个线性层,然后是一个Softmax层,以预测下一个单词的概率分布。
LLM Attack
论文连接: Universal and Transferable Adversarial Attacks on Aligned Language Models
原理
大白话: 通过给用户的输入内容加入合适的后缀,使得大模型说出那些不该说出的话
参考了官方的说法:
LLM 将一串 token $x_{1:n}$ 映射到下一个 token $x_{n+1}$。LLM 所需要学习的是在给定之前的 token $x_{1:n}$ ,得到下一个 token $x_{n+1}$ 的概率 $p(x_{n+1} | x_{1:n})$。其中每一个 $x_i \in {1, … V}$ 都是词表中的一个 token。如果想要让模型输入一段序列,序列中每一个 token 都只与之前的所有 token 有关,因此模型输出一段序列 $x_{n+1:n+H}$ 的概率为
$$p(x_{n+1:n+H}|x_{1:n}) = \prod_{i=1}^H p(x_{n+i} | x_{1:n+i-1})$$
这里做了概率累乘来叠加概率。
那么根据这个公式,如果我们想让 LLM 输出指定格式的子序列,那么就要让这个子序列的概率尽可能大,即 $p(x_{n+1:n+H}|x_{1:n})$ 尽可能高,那么这就可以转换成一个优化问题,通过梯度下降计算梯度减少损失:
$$\mathcal{L}(x_{1:n}) = -\log p(x^\star_{n+1:n+H} | x_{1:n})$$
这里的 $x^\star_{n+1:n+H}$ 就是我们想要指定的输出序列。
但由于 LLM 是对离散的 token 做处理,无法记录连续的变化,所以论文的作者参考了 AutoPrompt , 通过贪婪坐标下降(greedy coordinate descent)的方法来对离散输入做优化。算法伪代码如下:
作者首先生成了一个 one hot 向量来代表 token,之后将该 one hot 向量乘上对应模型的 embedding layer 矩阵降维, 然后再输入给模型让他来做梯度下降,经过模型反向传播后,会得到 one hot 对应的梯度向量(每个位置都有一个梯度),这里用 $grad_i$ 表示第 $i$ 个位置的梯度, 如果 $grad_i$ < 0,就代表这个位置的token能够使得损失下降,将原本的 token 替换为词表中的第 $i$ 个 token。算法这里的 $top-k$是指在输出 token 中选择最小的 $k$ 个梯度和对应的 $i$
在论文代码提供的 demo 程序中, 实际代码如下:
1 | plotlosses = PlotLosses() |
官方给的示例代码中,填补上了 import 部分的代码,然后将用户输入的部分滞空,后缀设置为各自对应最大长度的 ‘!’
结果
最后生成出来的效果如下: