CryptGPT:使用维吉尼亚密码的隐私保护语言模型(第一部分)

社区文章 发布于 2024 年 6 月 16 日

never-steal tl;dr - 我们从头开始,使用维吉尼亚密码加密数据集,预训练了一个 GPT-2 分词器和模型,其性能与普通 GPT-2 一样好。但要使用它,你需要知道加密密钥。

这里的隐私保护是指防止模型提供商获取数据。想象一下,如果 OpenAI 使用这个方案训练了一个 GPT-4,然后将密钥提供给政府。那么即使模型托管在 OpenAI 的服务器上,政府也可以安全地使用它,而 OpenAI 无需与政府共享模型权重。


引言

GPT-4 这样的语言模型非常棒。它们可以生成文本、回答问题,并协助完成各种任务。但随着它们越来越受欢迎,人们开始担心隐私问题。我们如何确保用于训练这些模型的数据和它们生成的内容保持私密?

在关于隐私保护语言模型系列的第一部分中,我将深入探讨一种使用维吉尼亚密码的简单方法。目标是查看我们是否可以加密训练数据并仍然使用它而不会降低性能。在未来的文章中,我将探讨更高级的方法,如前缀密钥和使用ChaCha20作为密码。

问题

问题是这样的:语言模型非常有用,但它们也带来了隐私风险。当你用文本数据训练模型时,有时这些数据可能会在之后被提取出来,这意味着私人信息可能会暴露。这对于希望数据保密的的用户来说是一个重大问题。

挑战在于找到一种在不损害数据隐私的情况下训练和使用这些模型的方法。目前的方法,如安全多方计算同态加密,速度太慢,无法实际应用。我们需要一种真正有效且高效的解决方案。

其他方法

目前正在探索几种方法来解决语言模型中的隐私问题:

  1. 安全多方计算:允许多方协同计算一个函数,同时保持各自输入数据的私密性。但它速度极慢,对于实时应用来说不切实际。
  2. 同态加密:允许你在不解密数据的情况下对加密数据执行计算。但它也极其计算密集,尚未广泛用于大型语言模型。
  3. 差分隐私:通过向数据中添加随机噪声来保护个人隐私。它需要大量用户来提供强大的隐私保障,并且仍然依赖于信任模型提供商正确实施它。

这些方法都有缺点,使得它们在实践中难以使用。目前还没有可行的LLM全同态加密变体,差分隐私除非你平衡使用,否则效果不佳。MPC是目前唯一可行的选择,但它非常缓慢。

为了让你了解MPC方法目前有多慢:最快的LLaMA-7B MPC实现在生成时每个token需要**五分钟**,并且随着上下文大小的增加而增加。天呐!

理念和关键见解

这个想法是使用加密来保护语言模型的输入和输出。我决定探索一种简单的加密方法:维吉尼亚密码,而不是依赖计算密集型方法。

维吉尼亚密码是最简单(也是最古老)的密码之一,它采用了一种多字母替换形式(每个字母都被分配了多个替换字母)。

可以把它看作是凯撒密码的升级版。它于1553年首次被描述,但直到1863年才被破解,这足足用了三个世纪。弱点:如果有人找到密钥长度,那么它就可以被破解,并且短密钥很容易被破解。

Token 稳定性和学习

在语言模型中,文本被分词成更小的片段,这些 token 用于训练和生成文本。为了使模型有效学习,加密方法必须在文本片段与其密文之间保持稳定的、一对一的对应关系。这种稳定性确保模型能够以学习未加密文本的相同方式学习加密文本。

维吉尼亚密码通过其逐字符替换提供了这种稳定性。例如,如果输入字符串“123”加密为“CA9”,则在输入中添加另一个字符,使其变为“1234”,将导致“CA9D”(假设而言)。这种可预测且稳定的转换允许模型像学习纯文本一样学习加密文本中的模式。

训练和推理成本之间的权衡

了解训练和推理成本之间的权衡非常重要。HE(同态加密)和MPC(多方计算)方法由于在推理(测试时)期间产生的巨大计算开销,对于语言模型来说是难以处理的。然而,我们可以将这种计算负担转移到训练阶段。通过在加密语料库上预训练模型,模型可以在推理时无需任何额外计算即可处理加密数据。

CryptGPT 目前使用维吉尼亚密码进行初始验证。如果模型能够有效地从维吉尼亚加密文本中学习,那么它将为使用更强大的加密方法(如 ChaCha20)打开大门。

训练 CryptGPT

为了测试语言模型是否能够从加密文本中学习,我尝试在 GPT-2 架构上使用维吉尼亚密码。

GPT-2 是一个很好的起点,因为它简单且提供了非常坚实的基础。GPT-2 有几种大小,从 Small(1.24 亿参数)到 XL(15 亿)。我选择了 GPT-2 small 和 GPT-2 large 进行此实验。

以下是我实现训练过程的方式。

flowchart

重要提示:为简洁起见,我删减了下面的代码片段,你可以在此处找到完整的原始代码。

步骤 1:加密数据集

首先,我们需要使用维吉尼亚密码加密数据集。与典型的维吉尼亚实现不同,我们的目标是支持所有 Unicode 字符。下面是我用于加密和解密的代码:

len_unicode = 55215  # NOT 65536 because surrogates are not allowed in python

def encrypt(message, key):
    encrypted = ""
    split_message = [
        message[i : i + len(key)] for i in range(0, len(message), len(key))
    ]

    for each_split in split_message:
        i = 0
        for letter in each_split:
            number = (ord(letter) + ord(key[i])) % len_unicode
            encrypted += chr(number)
            i += 1

    return encrypted

def decrypt(cipher, key):
    decrypted = ""
    split_encrypted = [
        cipher[i : i + len(key)] for i in range(0, len(cipher), len(key))
    ]

    for each_split in split_encrypted:
        i = 0
        for letter in each_split:
            number = (ord(letter) - ord(key[i])) % len_unicode
            decrypted += chr(number)
            i += 1

    return decrypted

加密你的数据集。我们需要加密数据集,然后将其保存为文件,因为分词器训练管道要求将其拆分为文件。以下是一个说明加密过程的片段:

num_proc = multiprocessing.cpu_count()
dataset = load_dataset("openwebtext", num_proc=num_proc_load_dataset)
dataset = dataset.map(
    lambda row: dict(encrypted=encrypt_(row["text"]) + gpt2_tokenizer.eos_token),
    num_proc=num_proc-1
)

def combine_and_save(rows, idxs):
    idx = idxs[0]
    texts = rows["text"]
    save_dir = "./cryptgpt-data"
    file = f"{save_dir}/data-{idx}.txt"
    with open(file, 'w') as f:
        for text in texts:
            f.write(text)
    return dict(file=[file]*len(texts))

dataset.map(combine_and_save, batched=True, batch_size=1000, with_indices=True, num_proc=num_proc // 2)

步骤 2:训练分词器

接下来,使用 字节对编码 对加密数据集进行分词器训练,并使用 tokenizers Python 包。这个过程在 96 核 CPU、680 GB 内存的机器上耗时 3 小时。

from tokenizers import Tokenizer, BPE, ByteLevel, BpeTrainer

eos_token = gpt2_tokenizer.eos_token

tokenizer = Tokenizer(BPE(unk_token=eos_token))

processor = ByteLevel()
tokenizer.processor = processor

tokenizer.enable_truncation(max_length=gpt2_tokenizer.model_max_length)

trainer = BpeTrainer(
    vocab_size=gpt2_tokenizer.vocab_size,
    special_tokens=[eos_token],
)

train_files = glob.glob("./cryptgpt-data/*.txt")

tokenizer.train(train_files, trainer=trainer)

然后我们将其上传到huggingface hub

步骤 3:训练模型

最后,使用 Axolotl 在加密数据集上训练模型。

Axolotl 配置(YAML)
base_model: diwank/cryptgpt-large
hub_model_id: diwank/cryptgpt-large

model_type: GPT2LMHeadModel
tokenizer_type: AutoTokenizer
trust_remote_code: true  # required for CryptGPTTokenizer
output_dir: ./outputs/model-out

datasets:
  - path: diwank/encrypted-openwebtext
    type: completion

sequence_len: 1024
pad_to_sequence_len: true
train_on_inputs: true

gradient_accumulation_steps: 1
micro_batch_size: 128
optimizer: adamw_bnb_8bit
adam_beta1: 0.9
adam_beta2: 0.95
seed: 42

lr_scheduler: cosine
learning_rate: 6e-4
cosine_min_lr_ratio: 0.1  # min: 6e-5
weight_decay: 0.15

bf16: auto

max_steps: 600000

模型训练

我训练了两个模型变体:GPT-2(1.37 亿参数)和 GPT-2 Large(8.12 亿参数)。训练是在一台 8xA100 机器上使用 Axolotl 训练框架进行的。训练 GPT-2 模型大约花费了 40 小时,而 GPT-2 Large 模型大约花费了 80 小时。

训练日志和模型工件

您可以在此处找到训练日志:

训练好的模型可在 HuggingFace 上进行进一步分析:

此项目的代码位于 GitHub

结果

两个模型的损失曲线都表明它们能够有效地从加密数据中学习。以下是 GPT-2 Large 模型的损失曲线:

Loss Curve

损失曲线显示稳定下降,这表明该模型确实能够从维吉尼亚加密文本中学习到有意义的模式。很酷,对吧?

此方法的局限性

  1. 模型和分词器与密钥绑定

最大的限制是模型和分词器与训练期间使用的加密密钥紧密耦合。这意味着每个模型只有一个密钥,更改密钥需要重新训练整个模型。

  1. 容易受到频率分析攻击

维吉尼亚密码虽然在初始验证中有效,但容易受到频率分析攻击。如果攻击者能够获取大量的密文,他们可以使用以下方法破解加密:

  • 弗里德曼测试方法:通过分析密文中重复序列的频率来确定密钥长度。

  • 自相关测试:一旦知道了密钥长度,就可以通过比较每个子序列中字符的频率与语言中字符的预期频率来推导出关键字。这是通过将每个子序列中字符的频率与语言中字符的预期频率进行比较来完成的。

  1. 模型权重泄露

即使没有足够的密文可用,如果模型权重被泄露,攻击者仍然可以利用此漏洞。通过从模型中采样大量文本,从随机 token 开始并使用高温生成,攻击者可以生成足够的文本来执行频率分析,并可能推导出加密密钥。

解决这些局限性

  1. 将模型和分词器与密钥解耦

解决这个问题的一种方法是冻结模型的内部层,只在密钥更改时重新训练外部层。因为对加密数据的核心理解可以保留在内部层中,而外部层适应新的密钥。

  1. 使用更强的加密算法

为了应对频率分析攻击的脆弱性,我们可以使用更强大的加密算法,例如 XChaCha20。然而,为了保持 Token 稳定性,nonce 必须是固定的(我知道,我知道)。这会引入一些限制,但仍然可以增强加密文本的安全性。这里的挑战在于平衡 Token 稳定性和强大的加密。

  1. 减轻模型权重泄露

为了降低这种风险,我们可以使用一个秘密的前缀密钥。前缀密钥是一个特定的序列,在训练期间添加到每个输入的开头。模型只有在存在此前缀时才能生成有意义的文本。如果没有正确的前缀,模型生成的任何文本都将是乱码。这确保了即使模型权重被泄露,攻击者也无法在没有前缀密钥的情况下生成有用的输出。

目的何在

隐私!例如,政府或私人公司可以与像 OpenAI 这样的组织合作,使用这种方法为他们训练模型。在这种情况下,OpenAI 不需要共享模型权重或架构,而合作方也不必透露其原始数据集或推理输入和输出。

工作示例

import os
os.environ["ENCRYPTION_KEY"] = "<you cant guess this>"

from cryptgpt.prepare_dataset import encrypt_, decrypt_
from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline

model = AutoModelForCausalLM.from_pretrained("diwank/cryptgpt-large")
tokenizer = AutoTokenizer.from_pretrained("diwank/cryptgpt-large")

pipe = pipeline("text-generation", model=model, tokenizer=tokenizer)
decrypt_(pipe(encrypt_("hello"))[0]["generated_text"])

结果:'你好\n\n祝你好运!\n\n今年的全美全明星队,Inc.的婴儿潮一代没有重新'

(我喜欢它在这次回复中生成的内容 哈哈 xD)

未来工作

这项工作是在 Julep AI 完成的,这是一个致力于构建人工智能代理基础设施的人工智能实验室。您可以在这里了解更多关于 Julep AI 的信息,我们一直在寻找优秀的人才加入我们的团队!欢迎在我们的 Discord 服务器上打个招呼。

总结和未来方向

在本系列的第一部分中,我们展示了语言模型可以在不损失性能的情况下,使用维吉尼亚密码在加密文本上进行训练。我们验证了加密可以有效地集成到训练和推理过程中的假设。

未来的工作将集中于:

  • 实施更强大的加密方法,如 XChaCha20。
  • 探索使用前缀密钥以进一步增强安全性。
  • 完善方法,使模型和分词器与加密密钥解耦。

本系列的第二部分将更深入地探讨这些高级方法及其实现,旨在使隐私保护语言模型更加健壮和实用。

密码分析师和 LLM 研究人员的挑战

我邀请密码分析师和 LLM 研究人员尝试找出我们实验中使用的密钥。模型权重和分词器都是开源的,您可以在这里访问它们。作为额外奖励,我将向任何成功破解加密的人提供 50 小时的时间(或等值的美元)。祝您好运,祝您黑客愉快!(优惠截止日期为 7 月 15 日——本文发布一个月后)

你可以在 X/TwitterLinkedIn 上找到我。

challenge-accepted

社区

注册登录评论

© . This site is unofficial and not affiliated with Hugging Face, Inc.