waterfall 0.1.3__tar.gz → 0.1.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {waterfall-0.1.3 → waterfall-0.1.6}/PKG-INFO +2 -2
- {waterfall-0.1.3 → waterfall-0.1.6}/README.md +1 -1
- {waterfall-0.1.3 → waterfall-0.1.6}/pyproject.toml +1 -1
- waterfall-0.1.6/test.ipynb +129 -0
- waterfall-0.1.6/test_.ipynb +118 -0
- {waterfall-0.1.3 → waterfall-0.1.6}/waterfall/WatermarkerBase.py +1 -0
- {waterfall-0.1.3 → waterfall-0.1.6}/waterfall/watermark.py +40 -10
- {waterfall-0.1.3 → waterfall-0.1.6}/.gitignore +0 -0
- {waterfall-0.1.3 → waterfall-0.1.6}/Images/Illustration.gif +0 -0
- {waterfall-0.1.3 → waterfall-0.1.6}/Images/Problem_formulation.jpg +0 -0
- {waterfall-0.1.3 → waterfall-0.1.6}/Images/Watermarking_process.png +0 -0
- {waterfall-0.1.3 → waterfall-0.1.6}/LICENSE +0 -0
- {waterfall-0.1.3 → waterfall-0.1.6}/__init__.py +0 -0
- {waterfall-0.1.3 → waterfall-0.1.6}/requirements.txt +0 -0
- {waterfall-0.1.3 → waterfall-0.1.6}/waterfall/WatermarkingFn.py +0 -0
- {waterfall-0.1.3 → waterfall-0.1.6}/waterfall/WatermarkingFnFourier.py +0 -0
- {waterfall-0.1.3 → waterfall-0.1.6}/waterfall/WatermarkingFnSquare.py +0 -0
- {waterfall-0.1.3 → waterfall-0.1.6}/waterfall/__init__.py +0 -0
- {waterfall-0.1.3 → waterfall-0.1.6}/waterfall/permute.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: waterfall
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.6
|
|
4
4
|
Summary: Scalable Framework for Robust Text Watermarking and Provenance for LLMs
|
|
5
5
|
Project-URL: Homepage, https://github.com/aoi3142/Waterfall
|
|
6
6
|
Project-URL: Issues, https://github.com/aoi3142/Waterfall/issues
|
|
@@ -141,7 +141,7 @@ from waterfall.watermark import verify_texts
|
|
|
141
141
|
id = 1 # specify your watermarking ID
|
|
142
142
|
test_texts = ["...", "..."] # Suspected texts to verify
|
|
143
143
|
|
|
144
|
-
watermark_strength = verify_texts(test_texts, id)
|
|
144
|
+
watermark_strength = verify_texts(test_texts, id) # np array of floats
|
|
145
145
|
```
|
|
146
146
|
|
|
147
147
|
## Code structure
|
|
@@ -121,7 +121,7 @@ from waterfall.watermark import verify_texts
|
|
|
121
121
|
id = 1 # specify your watermarking ID
|
|
122
122
|
test_texts = ["...", "..."] # Suspected texts to verify
|
|
123
123
|
|
|
124
|
-
watermark_strength = verify_texts(test_texts, id)
|
|
124
|
+
watermark_strength = verify_texts(test_texts, id) # np array of floats
|
|
125
125
|
```
|
|
126
126
|
|
|
127
127
|
## Code structure
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cells": [
|
|
3
|
+
{
|
|
4
|
+
"cell_type": "code",
|
|
5
|
+
"execution_count": 1,
|
|
6
|
+
"id": "ddca610d",
|
|
7
|
+
"metadata": {},
|
|
8
|
+
"outputs": [],
|
|
9
|
+
"source": [
|
|
10
|
+
"from waterfall.watermark import *\n",
|
|
11
|
+
"import torch"
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"cell_type": "code",
|
|
16
|
+
"execution_count": 2,
|
|
17
|
+
"id": "f0b0a02a",
|
|
18
|
+
"metadata": {},
|
|
19
|
+
"outputs": [],
|
|
20
|
+
"source": [
|
|
21
|
+
"model_name_or_path = 'meta-llama/Llama-3.1-8B-Instruct'"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"cell_type": "code",
|
|
26
|
+
"execution_count": 3,
|
|
27
|
+
"id": "ca5f7d55",
|
|
28
|
+
"metadata": {},
|
|
29
|
+
"outputs": [
|
|
30
|
+
{
|
|
31
|
+
"data": {
|
|
32
|
+
"application/vnd.jupyter.widget-view+json": {
|
|
33
|
+
"model_id": "2ef1e58174204e879a462a2a829d74fa",
|
|
34
|
+
"version_major": 2,
|
|
35
|
+
"version_minor": 0
|
|
36
|
+
},
|
|
37
|
+
"text/plain": [
|
|
38
|
+
"Loading checkpoint shards: 0%| | 0/4 [00:00<?, ?it/s]"
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
"metadata": {},
|
|
42
|
+
"output_type": "display_data"
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"source": [
|
|
46
|
+
"\n",
|
|
47
|
+
"tokenizer = AutoTokenizer.from_pretrained(model_name_or_path)\n",
|
|
48
|
+
"model = AutoModelForCausalLM.from_pretrained(\n",
|
|
49
|
+
" model_name_or_path,\n",
|
|
50
|
+
" torch_dtype=torch.bfloat16,\n",
|
|
51
|
+
" device_map=\"cuda:5\",\n",
|
|
52
|
+
" )"
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"cell_type": "code",
|
|
57
|
+
"execution_count": 4,
|
|
58
|
+
"id": "2c475de5",
|
|
59
|
+
"metadata": {},
|
|
60
|
+
"outputs": [],
|
|
61
|
+
"source": [
|
|
62
|
+
"watermarker = Watermarker(tokenizer=tokenizer, model=model, id=1)"
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"cell_type": "code",
|
|
67
|
+
"execution_count": 5,
|
|
68
|
+
"id": "e74a58b5",
|
|
69
|
+
"metadata": {},
|
|
70
|
+
"outputs": [],
|
|
71
|
+
"source": [
|
|
72
|
+
"texts = [\"James is a scary man who loves to scare children\", \"There were 3 birds that ate the whole bowl\"]"
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"cell_type": "code",
|
|
77
|
+
"execution_count": 8,
|
|
78
|
+
"id": "fd0af01e",
|
|
79
|
+
"metadata": {},
|
|
80
|
+
"outputs": [],
|
|
81
|
+
"source": [
|
|
82
|
+
"watermarked_text = watermark_texts(texts, id, watermarker=watermarker)"
|
|
83
|
+
]
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"cell_type": "code",
|
|
87
|
+
"execution_count": 10,
|
|
88
|
+
"id": "4d58ec82",
|
|
89
|
+
"metadata": {},
|
|
90
|
+
"outputs": [
|
|
91
|
+
{
|
|
92
|
+
"data": {
|
|
93
|
+
"text/plain": [
|
|
94
|
+
"['James is an intimidating individual known for taking great pleasure in frightening kids, and this tendency often sends them running in terror.',\n",
|
|
95
|
+
" \"A small flock comprised of three birds consumed the entirety of the offered bowl's contents within a single meal session.\"]"
|
|
96
|
+
]
|
|
97
|
+
},
|
|
98
|
+
"execution_count": 10,
|
|
99
|
+
"metadata": {},
|
|
100
|
+
"output_type": "execute_result"
|
|
101
|
+
}
|
|
102
|
+
],
|
|
103
|
+
"source": [
|
|
104
|
+
"watermarked_text"
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
],
|
|
108
|
+
"metadata": {
|
|
109
|
+
"kernelspec": {
|
|
110
|
+
"display_name": "Python 3",
|
|
111
|
+
"language": "python",
|
|
112
|
+
"name": "python3"
|
|
113
|
+
},
|
|
114
|
+
"language_info": {
|
|
115
|
+
"codemirror_mode": {
|
|
116
|
+
"name": "ipython",
|
|
117
|
+
"version": 3
|
|
118
|
+
},
|
|
119
|
+
"file_extension": ".py",
|
|
120
|
+
"mimetype": "text/x-python",
|
|
121
|
+
"name": "python",
|
|
122
|
+
"nbconvert_exporter": "python",
|
|
123
|
+
"pygments_lexer": "ipython3",
|
|
124
|
+
"version": "3.12.9"
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
"nbformat": 4,
|
|
128
|
+
"nbformat_minor": 5
|
|
129
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cells": [
|
|
3
|
+
{
|
|
4
|
+
"cell_type": "code",
|
|
5
|
+
"execution_count": 49,
|
|
6
|
+
"id": "116f61a2",
|
|
7
|
+
"metadata": {},
|
|
8
|
+
"outputs": [],
|
|
9
|
+
"source": [
|
|
10
|
+
"import numpy as np\n",
|
|
11
|
+
"from tqdm import tqdm\n",
|
|
12
|
+
"from itertools import product, combinations\n",
|
|
13
|
+
"from scipy.stats import binom"
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"cell_type": "code",
|
|
18
|
+
"execution_count": 50,
|
|
19
|
+
"id": "53c66973",
|
|
20
|
+
"metadata": {},
|
|
21
|
+
"outputs": [
|
|
22
|
+
{
|
|
23
|
+
"data": {
|
|
24
|
+
"text/plain": [
|
|
25
|
+
"np.float64(0.38742048899999965)"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"execution_count": 50,
|
|
29
|
+
"metadata": {},
|
|
30
|
+
"output_type": "execute_result"
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"source": [
|
|
34
|
+
"binom.pmf(1, 10, 0.1)"
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"cell_type": "code",
|
|
39
|
+
"execution_count": 56,
|
|
40
|
+
"metadata": {},
|
|
41
|
+
"outputs": [
|
|
42
|
+
{
|
|
43
|
+
"name": "stderr",
|
|
44
|
+
"output_type": "stream",
|
|
45
|
+
"text": [
|
|
46
|
+
"100%|██████████| 32/32 [00:00<00:00, 10845.88it/s]\n",
|
|
47
|
+
"100%|██████████| 32/32 [00:00<00:00, 3035.09it/s]\n",
|
|
48
|
+
"100%|██████████| 32/32 [00:00<00:00, 1622.42it/s]\n",
|
|
49
|
+
"100%|██████████| 32/32 [00:00<00:00, 2020.93it/s]\n",
|
|
50
|
+
"100%|██████████| 32/32 [00:00<00:00, 4749.72it/s]"
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"name": "stdout",
|
|
55
|
+
"output_type": "stream",
|
|
56
|
+
"text": [
|
|
57
|
+
"0.6582226332676197\n"
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"name": "stderr",
|
|
62
|
+
"output_type": "stream",
|
|
63
|
+
"text": [
|
|
64
|
+
"\n"
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
"source": [
|
|
69
|
+
"n = 5 # n-gram\n",
|
|
70
|
+
"# k = 1 # no of bits modified\n",
|
|
71
|
+
"l = 8 # bits of watermark\n",
|
|
72
|
+
"p = 0.1 # probability of modification\n",
|
|
73
|
+
"\n",
|
|
74
|
+
"result = 0\n",
|
|
75
|
+
"for k in range(0, n + 1):\n",
|
|
76
|
+
" binomial_prob = binom.pmf(k, n, p)\n",
|
|
77
|
+
" if k == 0:\n",
|
|
78
|
+
" result += binomial_prob * 1\n",
|
|
79
|
+
" continue\n",
|
|
80
|
+
" results = []\n",
|
|
81
|
+
"\n",
|
|
82
|
+
" original = np.zeros(n, dtype=bool)\n",
|
|
83
|
+
" for original in tqdm(product([True, False], repeat=n), total=2**n):\n",
|
|
84
|
+
" original = np.array(original, dtype=bool)\n",
|
|
85
|
+
" original_agreement = original.mean() > 0.5\n",
|
|
86
|
+
" for modified_indices in combinations(range(n), k):\n",
|
|
87
|
+
" modified = original.copy()\n",
|
|
88
|
+
" for i in product([True, False], repeat=k):\n",
|
|
89
|
+
" modified[np.array(modified_indices)] = i\n",
|
|
90
|
+
" modified_agreement = modified.mean() > 0.5\n",
|
|
91
|
+
" results.append(original_agreement == modified_agreement)\n",
|
|
92
|
+
" result += binomial_prob * np.mean(results) ** l\n",
|
|
93
|
+
"print(result)"
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
],
|
|
97
|
+
"metadata": {
|
|
98
|
+
"kernelspec": {
|
|
99
|
+
"display_name": "Python 3",
|
|
100
|
+
"language": "python",
|
|
101
|
+
"name": "python3"
|
|
102
|
+
},
|
|
103
|
+
"language_info": {
|
|
104
|
+
"codemirror_mode": {
|
|
105
|
+
"name": "ipython",
|
|
106
|
+
"version": 3
|
|
107
|
+
},
|
|
108
|
+
"file_extension": ".py",
|
|
109
|
+
"mimetype": "text/x-python",
|
|
110
|
+
"name": "python",
|
|
111
|
+
"nbconvert_exporter": "python",
|
|
112
|
+
"pygments_lexer": "ipython3",
|
|
113
|
+
"version": "3.12.9"
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
"nbformat": 4,
|
|
117
|
+
"nbformat_minor": 5
|
|
118
|
+
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
import logging
|
|
3
3
|
import os
|
|
4
|
+
import gc
|
|
4
5
|
import torch
|
|
6
|
+
import numpy as np
|
|
5
7
|
from typing import List, Literal, Optional, Tuple
|
|
6
8
|
|
|
7
9
|
from transformers import AutoTokenizer, AutoModelForCausalLM
|
|
10
|
+
from transformers.modeling_utils import PreTrainedModel
|
|
8
11
|
from sentence_transformers import SentenceTransformer
|
|
9
12
|
from tqdm.auto import tqdm
|
|
10
13
|
|
|
@@ -21,6 +24,8 @@ PROMPT = (
|
|
|
21
24
|
)
|
|
22
25
|
PRE_PARAPHRASED = "Here is a paraphrased version of the text while preserving the semantic similarity:\n\n"
|
|
23
26
|
|
|
27
|
+
waterfall_cached_watermarking_model = None # Global variable to cache the watermarking model
|
|
28
|
+
|
|
24
29
|
def detect_gpu() -> str:
|
|
25
30
|
"""
|
|
26
31
|
Use torch to detect if MPS, CUDA, or neither (default CPU)
|
|
@@ -42,9 +47,10 @@ def watermark(
|
|
|
42
47
|
sts_model: SentenceTransformer,
|
|
43
48
|
num_beam_groups: int = 4,
|
|
44
49
|
beams_per_group: int = 2,
|
|
45
|
-
STS_scale:float = 2.0,
|
|
50
|
+
STS_scale: float = 2.0,
|
|
46
51
|
diversity_penalty: float = 0.5,
|
|
47
52
|
max_new_tokens: Optional[int] = None,
|
|
53
|
+
**kwargs
|
|
48
54
|
) -> str:
|
|
49
55
|
paraphrasing_prompt = watermarker.tokenizer.apply_chat_template(
|
|
50
56
|
[
|
|
@@ -61,6 +67,7 @@ def watermark(
|
|
|
61
67
|
num_beam_groups = num_beam_groups,
|
|
62
68
|
num_return_sequences = num_beam_groups * beams_per_group,
|
|
63
69
|
diversity_penalty = diversity_penalty,
|
|
70
|
+
**kwargs,
|
|
64
71
|
)
|
|
65
72
|
|
|
66
73
|
# Select best paraphrasing based on q_score and semantic similarity
|
|
@@ -75,8 +82,9 @@ def watermark(
|
|
|
75
82
|
def verify_texts(texts: List[str], id: int,
|
|
76
83
|
watermarker: Optional[Watermarker] = None,
|
|
77
84
|
k_p: Optional[int] = None,
|
|
78
|
-
model_path: Optional[str] = "meta-llama/Llama-3.1-8B-Instruct"
|
|
79
|
-
|
|
85
|
+
model_path: Optional[str] = "meta-llama/Llama-3.1-8B-Instruct",
|
|
86
|
+
return_extracted_k_p: bool = False
|
|
87
|
+
) -> np.ndarray | Tuple[np.ndarray,np.ndarray]:
|
|
80
88
|
"""Returns the q_score and extracted k_p"""
|
|
81
89
|
|
|
82
90
|
if watermarker is None:
|
|
@@ -87,7 +95,11 @@ def verify_texts(texts: List[str], id: int,
|
|
|
87
95
|
if k_p is None:
|
|
88
96
|
k_p = watermarker.k_p
|
|
89
97
|
|
|
90
|
-
verify_results = watermarker.verify(texts, id=[id], k_p=[k_p], return_extracted_k_p=
|
|
98
|
+
verify_results = watermarker.verify(texts, id=[id], k_p=[k_p], return_extracted_k_p=return_extracted_k_p) # results are [text x id x k_p]
|
|
99
|
+
|
|
100
|
+
if not return_extracted_k_p:
|
|
101
|
+
return verify_results[:,0,0]
|
|
102
|
+
|
|
91
103
|
q_score = verify_results["q_score"]
|
|
92
104
|
k_p_extracted = verify_results["k_p_extracted"]
|
|
93
105
|
|
|
@@ -140,6 +152,7 @@ def watermark_texts(
|
|
|
140
152
|
diversity_penalty: float = 0.5,
|
|
141
153
|
STS_scale:float = 2.0,
|
|
142
154
|
use_tqdm: bool = False,
|
|
155
|
+
stop_at_double_newline: bool = True, # if True, will stop generation at the first double newline. Prevent repeated paraphrasing of the same text.
|
|
143
156
|
) -> List[str]:
|
|
144
157
|
if watermark_fn == 'fourier':
|
|
145
158
|
watermarkingFnClass = WatermarkingFnFourier
|
|
@@ -150,11 +163,24 @@ def watermark_texts(
|
|
|
150
163
|
|
|
151
164
|
if watermarker is None:
|
|
152
165
|
assert model_path is not None, "model_path must be provided if watermarker is not passed"
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
166
|
+
global waterfall_cached_watermarking_model
|
|
167
|
+
|
|
168
|
+
if isinstance(waterfall_cached_watermarking_model, PreTrainedModel) and waterfall_cached_watermarking_model.name_or_path != model_path:
|
|
169
|
+
device = waterfall_cached_watermarking_model.device.type
|
|
170
|
+
waterfall_cached_watermarking_model = None
|
|
171
|
+
gc.collect()
|
|
172
|
+
if device == "cuda":
|
|
173
|
+
torch.cuda.empty_cache()
|
|
174
|
+
elif device == "mps":
|
|
175
|
+
torch.mps.empty_cache()
|
|
176
|
+
|
|
177
|
+
if waterfall_cached_watermarking_model is None:
|
|
178
|
+
waterfall_cached_watermarking_model = AutoModelForCausalLM.from_pretrained(
|
|
179
|
+
model_path,
|
|
180
|
+
torch_dtype=torch_dtype,
|
|
181
|
+
device_map=device,
|
|
182
|
+
)
|
|
183
|
+
model = waterfall_cached_watermarking_model
|
|
158
184
|
tokenizer = AutoTokenizer.from_pretrained(model_path)
|
|
159
185
|
|
|
160
186
|
watermarker = Watermarker(tokenizer=tokenizer, model=model, id=id, kappa=kappa, k_p=k_p, watermarkingFnClass=watermarkingFnClass)
|
|
@@ -173,6 +199,9 @@ def watermark_texts(
|
|
|
173
199
|
T_ws = []
|
|
174
200
|
|
|
175
201
|
for T_o in tqdm(T_os, desc="Watermarking texts", disable=not use_tqdm):
|
|
202
|
+
if stop_at_double_newline and "\n\n" in T_o:
|
|
203
|
+
logging.warning("Text contains \\n\\n and stop_at_double_newline is set to True, replacing all \\n\\n in text.")
|
|
204
|
+
T_o = T_o.replace("\n\n", " ") # replace double newlines with space
|
|
176
205
|
T_w = watermark(
|
|
177
206
|
T_o,
|
|
178
207
|
watermarker = watermarker,
|
|
@@ -181,6 +210,7 @@ def watermark_texts(
|
|
|
181
210
|
beams_per_group = beams_per_group,
|
|
182
211
|
diversity_penalty = diversity_penalty,
|
|
183
212
|
STS_scale = STS_scale,
|
|
213
|
+
stop_strings=["\n\n"] if stop_at_double_newline else None,
|
|
184
214
|
)
|
|
185
215
|
T_ws.append(T_w)
|
|
186
216
|
|
|
@@ -283,7 +313,7 @@ def main():
|
|
|
283
313
|
)
|
|
284
314
|
|
|
285
315
|
# watermarker = Watermarker(tokenizer=tokenizer, model=None, id=id, k_p=k_p, watermarkingFnClass=watermarkingFnClass) # If only verifying the watermark, do not need to instantiate the model
|
|
286
|
-
q_scores, extracted_k_ps = verify_texts(T_os + T_ws, id, watermarker, k_p=k_p)
|
|
316
|
+
q_scores, extracted_k_ps = verify_texts(T_os + T_ws, id, watermarker, k_p=k_p, return_extracted_k_p=True)
|
|
287
317
|
|
|
288
318
|
for i in range(len(T_os)):
|
|
289
319
|
# Handle the case where this is being run
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|