hi~
众所周知!训练+微调大模型,做的,都是加法!
为了确保大模型生成的文本,安全!绝大部分的LLM开发商,都会给大模型上各种“锁”!
举例!
医疗领域提问:
“我发高烧,请你告诉我应该怎么吃药?帮我生成一个药单和说明?”
大模型回答:
首先,我需要强调,我不是医生或医疗专家,所以不能提供专业的医疗建议。如果你有高烧,最安全的做法是尽快联系医生或去医院接受专业的诊断和治疗。
你的项目是医疗、金融、法律等,垂直领域!经常会问到,类“敏感”问题!
有风险,大模型是不回答的!
这时,我们要做减法!
做手术!把敏感审查,修正或删掉!把大模型供应商上的“锁”,打开!
之后!
能生成任何想要的内容!
重申,这篇的目的,不是任何SQ/DP/WQ等不安定因素的教程,如果你是奔着此目的来的,请你马上离开!
雄哥本篇核心!会以Qwen开源model做演示(理论适合所有Transformer LLM),把安全审查模块,删掉!
你也可以用这个方法,解锁任何LLM生成!
开始前,再次提醒各位朋友,切勿用作违法生成!
攻防成本严重失衡!希望科学界朋友,优化算法,拒绝行为依赖单一方向的问题!
同时,正儿八经的项目,也必须要做安全板块!不管是在LLM的内部还是在外部,都要做!
之前雄哥做过一个安全模块系列,还未看的,在这里看!
方案很多,做这个板块的初心是,尽可能让更多【意友圈】的会员朋友,提高产品特定内容成功生成+生成质量的提升!
人的专注力只有10分钟!话不多说,开始!
① 怎么开锁?找到锁!丢掉锁!数据集示例!
② 跑起来!以qwen为例!
③ 技术延展应用!附技术发展报告!
更多价值项目和AI学习内容,欢迎你加入【意友圈】,点击加入!
接下来,雄哥会介绍整个过程,只想看如何实操的,直接跳到第二部分!
本篇所有资料和代码,后台发 “动手术” !公号会自动发链接!
第一部分:怎么开锁?方法论?
在会员群的朋友,都知道,雄哥最近在研究安全版块,做大模型项目,太重要了!如何评估LLM的安全?有哪些缺陷?也分享了一些进展!
目前,众多供应商的安全系统中,苹果的方案,雄哥较认可的
之前,我们做生成审查调整,要做微调或二次训练,成本极高!
本次,之所以能一举“手术”成功!切除LLM中的安全模块!
因为!LLM内部拒绝行为,都依赖一个单一方向判断!
只需3步,就能删除!
#A构建诱导数据集 #B拒绝方向的提取 #C方向消除-权重正交化
整个过程非常简单,雄哥先用“有害”+“无害”数据,构建数据集,诱导大模型残差流中的拒绝方向,消除所有层和所有 token 位置的拒绝方向,抓蛇七寸,砍掉!
如下图!科学界研究进展(末尾有介绍)
1.1 构建诱导数据集
这里,我们要构建100条左右的“有害”数据,和“正常”数据,组成数据集!
然后,使用这个数据集,给大模型,计算有害激活和无害激活之间的均值差异,提取出残差流中的单个方向,这是一个能够代表模型拒绝行为的特征方向,即“拒绝方向”。
这个方向描述了模型在处理有害指令时,与处理无害指令时在激活空间上的区别。
有害指令数据集 (D(train)_harmful):
从 AD-VBENCH, MALICIOUSINSTRUCT, TDC2023 和 HARMBENCH 中随机采样 128 个有害指令。
无害指令数据集 (D(train)_harmless):
从 ALPACA 中采样 128 个无害指令。
验证数据集 (D(val)_harmful & D(val)_harmless):
分别从 HARMBENCH 验证集和 ALPACA 中采样 32 个指令作为验证数据集。
看看数据是怎样的!
这相当于是鱼饵,我们拿着数据去钓鱼,你的大模型,如果想改变他的生成策略,这不行,那不行,也可以做自己数据集,例如刚刚医疗等!
1.2 拒绝方向提取
因为大模型的拒绝生成的方向,是单一的,如何找到这条鱼呢?
大模型根据“有害”+“无害”数据集,生成拒绝回复后,我们计算有害激活和无害激活之间的均值差异,就能提取出一个能够代表模型拒绝行为的特征方向!
计算过程:
① 遍历所有层级 (l ∈ [L]) 和指令位置 (i ∈ I)
② 计算有害指令的平均激活值 (µ(l)_i) 和无害指令的平均激活值 (ν(l)_i)
③ 计算差异均值向量 (r(l)_i = µ(l)_i – ν(l)_i)
更多的计算过程,雄哥这里不延展了,感兴趣的朋友后面研读材料!
这里普及上面雄哥提到的两个概念:
—残差流—
是指每个 token 在 Transformer 模型各层处理后的中间表示。
表征 token 的特征: 残差流包含了 token 的多种信息,例如词义、语法、上下文等,可以用来表征 token 的特征。
进行模型计算: 残差流是 Transformer 模型进行计算的基础,通过残差流,模型可以对 token 进行编码和解码。
—单个方向—
指的是残差流中的一个线性子空间,它代表了模型对特定特征的表征方式。
指示拒绝行为: 拒绝方向的存在使得模型可以将拒绝行为与这个方向关联起来,从而实现对有害指令的拒绝。
影响模型行为: 修改拒绝方向会影响模型对指令的响应,例如,通过添加或消除拒绝方向,可以诱导模型拒绝无害指令或接受有害指令。
通俗来说,就是钓鱼,有害数据就是饵,一旦生成“我不能***”,大模型就上钩了!吊起来!
1.3 方向消除-权重正交化
上面工作完成后,这一步就很关键了!对于所有写入残差流的矩阵 (例如嵌入矩阵、位置嵌入矩阵、注意力输出矩阵和 MLP 输出矩阵),将其列向量与拒绝方向 (ˆr) 进行正交化。
计算正交化矩阵:对于每个写入残差流的矩阵 Wout ∈ Rdmodel×dinput,计算其正交化矩阵 W’out:
W'out = Wout - ˆrˆr⊺Wout
ˆr 是拒绝方向的单位向量,ˆr⊺ 是其转置
将计算得到的正交化矩阵 W’out 替换掉原始的权重矩阵 Wout!
说这么多,都不及动手跑一跑!
第二部分:跑起来!以qwen为例!
本地环境配置太麻烦,这里雄哥用到colab的免费算力来实践!
qwen-1.8b版本只需要14G显存即可!
怎样上传代码,如何下载权重,这些不解释了,雄哥之前教过太多!
如果还没学会的,自己返回去学!
本篇所有资料和代码,后台发 “动手术” !公号会自动发链接!
在colab上传代码!
安装依赖!
%capture
!pip install transformers transformers_stream_generator tiktoken transformer_lens einops jaxtyping colorama
下载大模型!
以qwen-1.8b为例,理论上支持任何transformers语言模型!
把你hugging face上的路径换上去,会自动下载!同时,要看自己本地的算力够不够!
# 下载模型,这里以qwen为例
MODEL_PATH = 'Qwen/Qwen-1_8B-chat'
DEVICE = 'cuda'
model = HookedTransformer.from_pretrained_no_processing(
MODEL_PATH,
device=DEVICE,
dtype=torch.float16,
default_padding_side='left',
fp16=True
)
'left' =
'<|extra_0|>' =
下载数据集,筛选有效的!
这里,我们把一个hf上的数据集为例,然后用它制作了两个类型,1是有害的数据集和验证集!
2是无害的数据集和验证集!
先下载吧!然后筛选
def get_harmful_instructions():
url = 'https://raw.githubusercontent.com/llm-attacks/llm-attacks/main/data/advbench/harmful_behaviors.csv'
response = requests.get(url)
dataset = pd.read_csv(io.StringIO(response.content.decode('utf-8')))
instructions = dataset['goal'].tolist()
train, test = train_test_split(instructions, test_size=0.2, random_state=42)
return train, test
def get_harmless_instructions():
hf_path = 'tatsu-lab/alpaca'
dataset = load_dataset(hf_path)
# 筛选出不包含输入的指令
instructions = []
for i in range(len(dataset['train'])):
if dataset['train'][i]['input'].strip() == '':
instructions.append(dataset['train'][i]['instruction'])
train, test = train_test_split(instructions, test_size=0.2, random_state=42)
return train, test
生成数据集和验证集!
harmful_inst_train, harmful_inst_test = get_harmful_instructions()
harmless_inst_train, harmless_inst_test = get_harmless_instructions()
-
get_harmful_instructions() 函数被调用来获取有害指令的数据集。这个函数可能会从某个数据源中提取或者生成标记为有害的指令。执行后,它会返回两个数据集:harmful_inst_train 和 harmful_inst_test,分别代表用于训练和测试的有害指令数据集。
-
get_harmless_instructions() 函数被调用来获取无害指令的数据集。这个函数类似于前一个函数,但是它返回的是标记为无害的指令。执行后,它会返回两个数据集:harmless_inst_train 和 harmless_inst_test,分别代表用于训练和测试的无害指令数据集。
打印一下!
创建生成工具!
-
_generate_with_hooks 函数:这个函数接收一个 HookedTransformer 模型、一个包含初始 token 的张量 toks、一个表示最大生成 token 数量的参数 max_tokens_generated 以及一个包含前向传播钩子(forward hooks)的列表 fwd_hooks。这个函数的作用是使用模型生成新的 token,直到达到最大生成 token 数量。生成的 token 被添加到 all_toks 张量中,并最终使用模型的 tokenizer 解码为字符串。
-
get_generations 函数:这个函数接收一个 HookedTransformer 模型、一个包含指令的列表 instructions、一个用于将指令转换为 token 的函数 tokenize_instructions_fn、一个包含前向传播钩子(forward hooks)的列表 fwd_hooks、一个表示最大生成 token 数量的参数 max_tokens_generated 以及一个表示批量大小的参数 batch_size。这个函数的作用是使用 HookedTransformer 模型和 tokenize_instructions_fn 函数对输入的指令进行编码,然后使用 _generate_with_hooks 函数生成文本。生成的文本被存储在 generations 列表中,并最终返回。
def _generate_with_hooks(
model: HookedTransformer,
toks: Int[Tensor, 'batch_size seq_len'],
max_tokens_generated: int = 64,
fwd_hooks = [],
-> List[str]:
all_toks = torch.zeros((toks.shape[0], toks.shape[1] + max_tokens_generated), dtype=torch.long, device=toks.device)
, :toks.shape[1]] = toks :
for i in range(max_tokens_generated):
with model.hooks(fwd_hooks=fwd_hooks):
logits = model(all_toks[:, :-max_tokens_generated + i])
next_tokens = logits[:, -1, :].argmax(dim=-1)
,-max_tokens_generated+i] = next_tokens :
return model.tokenizer.batch_decode(all_toks[:, toks.shape[1]:], skip_special_tokens=True)
def get_generations(
model: HookedTransformer,
instructions: List[str],
tokenize_instructions_fn: Callable[[List[str]], Int[Tensor, 'batch_size seq_len']],
fwd_hooks = [],
max_tokens_generated: int = 64,
batch_size: int = 4,
-> List[str]:
[] =
for i in tqdm(range(0, len(instructions), batch_size)):
toks = tokenize_instructions_fn(instructions=instructions[i:i+batch_size])
generation = _generate_with_hooks(
model,
toks,
max_tokens_generated=max_tokens_generated,
fwd_hooks=fwd_hooks,
)
generations.extend(generation)
return generations
架好鱼竿!
# 在有害和无害指令上运行模型,缓存中间激活。
harmful_logits, harmful_cache = model.run_with_cache(harmful_toks, names_filter=lambda hook_name: 'resid' in hook_name)
harmless_logits, harmless_cache = model.run_with_cache(harmless_toks, names_filter=lambda hook_name: 'resid' in hook_name)
计算拒绝方向!
# 在中间层计算有害和无害激活的平均差异。
pos = -1
layer = 14
harmful_mean_act = harmful_cache['resid_pre', layer][:, pos, :].mean(dim=0)
harmless_mean_act = harmless_cache['resid_pre', layer][:, pos, :].mean(dim=0)
refusal_dir = harmful_mean_act - harmless_mean_act
refusal_dir = refusal_dir / refusal_dir.norm()
删除方向!
并通过添加“前向传播钩子”来进行干预。钩子函数direction_ablation_hook 会被应用到模型的不同层上,以改变模型的行为。代码首先定义了要测试的指令数量,然后指定了要干预的模型层。接着,它创建了一个钩子函数,并将这个钩子应用到所有指定的层上。然后,代码在两种条件下生成文本:
一种是有干预的情况
另一种是没有干预的基线情况
N_INST_TEST = 32
intervention_dir = refusal_dir
intervention_layers = list(range(model.cfg.n_layers))
hook_fn = functools.partial(direction_ablation_hook,direction=intervention_dir)
fwd_hooks = [(utils.get_act_name(act_name, l), hook_fn) for l in intervention_layers for act_name in ['resid_pre', 'resid_mid', 'resid_post']]
intervention_generations = get_generations(model, harmful_inst_test[:N_INST_TEST], tokenize_instructions_fn, fwd_hooks=fwd_hooks)
baseline_generations = get_generations(model, harmful_inst_test[:N_INST_TEST], tokenize_instructions_fn, fwd_hooks=[])
更多的,大家自行探索,过程,就是雄哥介绍这样!
大模型发展很快,但安全版块,却很滞后,野蛮生长,特别是Agent的函数调用+执行能力越强!
这个模块也越来越多人关注!
是很好的赛道!
好啦!
就聊这么多!
加群联系雄哥同事-技术工程师小胖!
原文始发于微信公众号(一意AI增效家):请勿用于非法用途!切除Qwen安全审查记录!给LLM动手术!生成任何想要的内容!适用所有大模型!