
Implementazione di riferimento di uno schema di watermarking LLM multi-bit che utilizza la diffusione di payload codificati, riponderazione imparziale e decodifica ECC a decisione soft per incorporare e rilevare messaggi nel testo generato.
Implementazione di riferimento di Robust and Scalable Multi-bit LLM Watermarking via Coded Payload Spreading.
WeaveMark incorpora un messaggio a k bit nel testo generato da LLM mantenendo imparziale la distribuzione dei token. Combina la diffusione del payload codificato (κ bit di codeword per token su ℓ layer, con un'assegnazione layer→bit rimescolata dal contesto), decodifica ECC a decisione soft basata sui margini di voto per bit e riponderazione multilayer imparziale (ogni direzione XORata con un lancio di moneta contestuale). Layer zero-bit dedicati, partizionati dal contesto anziché dal messaggio, forniscono il rilevamento di presenza indipendente dal messaggio.
weavemark/ libreria
watermark.py processore di logits WeaveMark (incorporamento)
extraction.py extract_bits, detect_zerobit
prf.py PRF con chiave (condivisa da embed/extract)
data.py caricamento dataset, preparazione prompt, record JSONL
device.py controllo CUDA
ecc/ decoder Golay, Reed-Muller, a decisione soft
generate.py genera testo con watermark / normale
detect.py estrazione + z-score zero-bit + PPL
evaluate_downstream.py qualità di riassunzione / traduzione
perplexity.py scorer PPL
attacks/ sostituzione, DIPPER
run_*.py pipeline (generate+detect, attacchi, downstream)
data/OpenGen.jsonl prompt OpenGen (--dataset opengen); vedi nota sotto
data/OpenGen.jsonl (il benchmark pubblico standard OpenGen) è omesso da
questo archivio per motivi di dimensione; posizionalo sotto data/ per usare --dataset opengen. I
dataset c4 e openwebtext vengono trasmessi in streaming dall'Hub e non richiedono file locali.
Esegui tutti gli script dalla radice del repository così che import weavemark funzioni.
Richiede Python 3.10–3.12 e una GPU CUDA (i modelli vengono caricati a 4-bit tramite bitsandbytes; non esiste un percorso CPU). LLaMA-3-8B a 4-bit occupa ~16 GB.
cd WeaveMark
python -m venv .venv && source .venv/bin/activate # oppure: conda create -n weavemark python=3.11
Installa una build CUDA di PyTorch prima di requirements.txt — un semplice
pip install torch fornisce una wheel solo-CPU su Windows, e la wheel deve avere
kernel per la tua GPU (RTX serie 50 / sm_120 richiede torch ≥ 2.7 su CUDA ≥ 12.8;
le wheel cu121 più vecchie si fermano a sm_90 e falliscono a runtime). Scegli il comando
corrispondente da https://pytorch.org.
pip install torch --index-url https://download.pytorch.org/whl/cu128 # esempio: Blackwell
pip install -r requirements.txt
Verifica che la GPU esegua effettivamente i kernel (is_available() da sola non basta — restituisce
True anche quando la wheel manca dei kernel per l'architettura):
python -c "import torch; t=torch.zeros(8).cuda().normal_(); print(torch.__version__, torch.cuda.get_device_capability(), (t*t).sum().item())"
LLaMA-3 e C4 sono soggetti a gating: esegui huggingface-cli login e accetta i loro termini.
OpenGen è incluso nel repository. Seleziona una GPU con CUDA_VISIBLE_DEVICES=<i>.
python generate.py --method weavemark --ecc_method rm --random_message \
--dataset c4 --num_test 100 --max_new_tokens 200 \
--bpt 10 --num_layers 10 --window_size 2 --prob_delta 1.0 \
--num_zerobit_layers 0 --do_sample
python detect.py --data_dir output_dump/<run_folder> --detect --stride 25
detect.py scrive detailed_eval_*.csv e summary_eval_*.csv; le colonne chiave
sono success_rate e, con --zerobit_threshold, z_success_rate.
run_main.py collega generate + detect (modifica le costanti in cima).
Dataset: c4 / openwebtext vengono trasmessi in streaming dall'Hub e troncati a
--prompt_len parole; opengen legge data/OpenGen.jsonl e tronca ogni
prefisso a --prompt_len token.
ECC msg→codeword: golay 12→24, rm 16→32, dual_golay 24→48, dual_rm
32→64. none incorpora --message senza codifica.
Impostazioni del paper: il tracciamento multi-bit usa --bpt 10 --num_layers 10 --window_size 2 --prob_delta 1.0 --num_zerobit_layers 0, ECC in base alla lunghezza del messaggio; aggiungi
--num_zerobit_layers 2 --enable_zerobit per l'impostazione combinata; zero-bit puro
è --bpt 0 --num_layers 0 --num_zerobit_layers 10 --enable_zerobit.
python attacks/substitution.py --data_dir output_dump/<run> --ratios 0.1 0.2 0.3
python attacks/dipper_attack.py --data_dir output_dump/<run> --lex_diversity 20 --order_diversity 20
python detect.py --data_dir output_dump/<run> --text_file attacked_text_10.jsonl --detect --stride 999
run_substitution.py / run_dipper.py collegano attacco + rilevamento su testo intero
(--stride 999 valuta ogni porzione interamente). run_downstream.py esegue
riassunzione (CNN/DailyMail) e traduzione (WMT16 en→ro) con BERTScore +
ROUGE/BLEU.
window_size = 2: il design imparziale vieta il riuso di un contesto, quindi una finestra
più ampia riduce la perdita per skip (vedi appendice).no_watermark riusano le stesse chiavi/parametri per misurare il tasso
di falsi positivi.@inproceedings{weavemark,
title = {Robust and Scalable Multi-bit LLM Watermarking via Coded Payload Spreading},
author = {Anonymous},
year = {2026},
note = {Under review}
}
attacks/dipper.py è adattato da
martiansideofthemoon/ai-detection-paraphrases
(la sua licenza si applica a quel file). Rilasciato sotto MIT (vedi LICENSE); imposta il
titolare del copyright prima della pubblicazione.
| Flag | Simbolo | Significato | Tipico |
|---|
--bpt | κ | bit per token | 10 |
--num_layers | ℓ | layer multi-bit | 10 |
--window_size | h | finestra di contesto | 2 |
--prob_delta | δ | forza di riponderazione | 1.0 |
--ecc_method | — | none/golay/dual_golay/rm/dual_rm | rm |
--num_zerobit_layers | ℓ_z | layer zero-bit | 0 o 2 |
--top_k | K | top-k di campionamento | 50 |