
Implémentation de référence d'un schéma de tatouage LLM multi-bits utilisant l'étalement de charge utile codée, le re-pondération non biaisée et le décodage ECC à décision souple pour intégrer et détecter des messages dans du texte généré.
Implémentation de référence de Robust and Scalable Multi-bit LLM Watermarking via Coded Payload Spreading.
WeaveMark intègre un message de k bits dans du texte généré par LLM tout en maintenant la distribution des tokens non biaisée. Il combine l'étalement de charge utile codée (κ bits de mot de code par token sur ℓ couches, avec une affectation couche→bit mélangée par contexte), le décodage ECC à décision souple à partir des marges de vote par bit, et un rééquilibrage multicouche non biaisé (chaque direction étant XORée avec un tirage à pile ou face contextuel). Des couches dédiées à zéro bit, partitionnées à partir du contexte plutôt que du message, offrent une détection de présence indépendante du message.
weavemark/ bibliothèque
watermark.py processeur de logits WeaveMark (intégration)
extraction.py extract_bits, detect_zerobit
prf.py PRF à clé (partagée entre intégration/extraction)
data.py chargement de jeux de données, préparation des invites, enregistrements JSONL
device.py vérification CUDA
ecc/ décodeurs Golay, Reed-Muller, à décision souple
generate.py génération de texte filigrané / brut
detect.py extraction + z-score zéro bit + PPL
evaluate_downstream.py qualité de résumé / traduction
perplexity.py scoreur PPL
attacks/ substitution, DIPPER
run_*.py pipelines (génération+détection, attaques, aval)
data/OpenGen.jsonl invites OpenGen (--dataset opengen) ; voir note ci-dessous
data/OpenGen.jsonl (le benchmark public standard OpenGen) est omis de cette
archive pour des raisons de taille ; placez-le sous data/ pour utiliser --dataset opengen. Les
jeux de données c4 et openwebtext sont diffusés depuis le Hub et ne nécessitent aucun fichier local.
Exécutez tous les scripts depuis la racine du dépôt afin que import weavemark soit résolu.
Nécessite Python 3.10–3.12 et un GPU CUDA (les modèles se chargent en 4 bits via bitsandbytes ; il n'existe pas de chemin CPU). LLaMA-3-8B en 4 bits tient dans ~16 Go.
cd WeaveMark
python -m venv .venv && source .venv/bin/activate # ou : conda create -n weavemark python=3.11
Installez une version CUDA de PyTorch avant requirements.txt — un simple
pip install torch donne une roue CPU uniquement sous Windows, et la roue doit contenir
des kernels pour votre GPU (RTX série 50 / sm_120 nécessite torch ≥ 2.7 sur CUDA ≥ 12.8 ;
les anciennes roues cu121 s'arrêtent à sm_90 et échouent à l'exécution). Choisissez la commande
correspondante sur https://pytorch.org.
pip install torch --index-url https://download.pytorch.org/whl/cu128 # exemple : Blackwell
pip install -r requirements.txt
Vérifiez que le GPU exécute réellement les kernels (is_available() seul ne suffit pas — il renvoie
True même lorsque la roue ne contient pas de kernels pour l'architecture) :
python -c "import torch; t=torch.zeros(8).cuda().normal_(); print(torch.__version__, torch.cuda.get_device_capability(), (t*t).sum().item())"
LLaMA-3 et C4 sont soumis à des conditions d'accès : huggingface-cli login et acceptez leurs
conditions. OpenGen est fourni avec le dépôt. Sélectionnez un GPU avec 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 écrit detailed_eval_*.csv et summary_eval_*.csv ; les colonnes clés
sont success_rate et, avec --zerobit_threshold, z_success_rate.
run_main.py enchaîne génération + détection (modifiez les constantes en haut du fichier).
Jeux de données : c4 / openwebtext sont diffusés depuis le Hub et tronqués à
--prompt_len mots ; opengen lit data/OpenGen.jsonl et tronque chaque
préfixe à --prompt_len tokens.
ECC msg→mot de code : golay 12→24, rm 16→32, dual_golay 24→48, dual_rm
32→64. none intègre --message sans codage.
Réglages de l'article : le traçage multi-bits utilise --bpt 10 --num_layers 10 --window_size 2 --prob_delta 1.0 --num_zerobit_layers 0, ECC selon la longueur du message ; ajoutez
--num_zerobit_layers 2 --enable_zerobit pour le réglage combiné ; le zéro bit pur
est --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 enchaînent attaque + détection sur texte entier
(--stride 999 évalue chaque extrait dans son intégralité). run_downstream.py exécute
la synthèse (CNN/DailyMail) et la traduction (WMT16 en→ro) avec BERTScore +
ROUGE/BLEU.
window_size = 2 : la conception non biaisée interdit la réutilisation d'un contexte, donc une fenêtre
plus grande réduit la perte par saut (voir annexe).no_watermark réutilisent les mêmes clés/paramètres pour mesurer le taux de
faux positifs.@inproceedings{weavemark,
title = {Robust and Scalable Multi-bit LLM Watermarking via Coded Payload Spreading},
author = {Anonymous},
year = {2026},
note = {Under review}
}
attacks/dipper.py est adapté de
martiansideofthemoon/ai-detection-paraphrases
(sa licence s'applique à ce fichier). Publié sous licence MIT (voir LICENSE) ; définissez le
titulaire du droit d'auteur avant toute publication.
| Indicateur | Symbole | Signification | Typique |
|---|
--bpt | κ | bits par token | 10 |
--num_layers | ℓ | couches multi-bits | 10 |
--window_size | h | fenêtre de contexte | 2 |
--prob_delta | δ | force de rééquilibrage | 1.0 |
--ecc_method | — | none/golay/dual_golay/rm/dual_rm | rm |
--num_zerobit_layers | ℓ_z | couches zéro bit | 0 ou 2 |
--top_k | K | top-k d'échantillonnage | 50 |