{"id":22277956,"url":"https://github.com/google-deepmind/synthid-text","last_synced_at":"2025-06-17T00:39:17.378Z","repository":{"id":259249033,"uuid":"877288196","full_name":"google-deepmind/synthid-text","owner":"google-deepmind","description":null,"archived":false,"fork":false,"pushed_at":"2025-06-13T13:23:45.000Z","size":2328,"stargazers_count":497,"open_issues_count":13,"forks_count":49,"subscribers_count":20,"default_branch":"main","last_synced_at":"2025-06-13T14:26:59.959Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/google-deepmind.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-10-23T12:08:45.000Z","updated_at":"2025-06-13T13:23:49.000Z","dependencies_parsed_at":"2025-06-13T14:26:47.831Z","dependency_job_id":"ad4124ae-0854-4559-9411-31de8231e382","html_url":"https://github.com/google-deepmind/synthid-text","commit_stats":null,"previous_names":["google-deepmind/synthid-text"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/google-deepmind/synthid-text","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google-deepmind%2Fsynthid-text","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google-deepmind%2Fsynthid-text/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google-deepmind%2Fsynthid-text/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google-deepmind%2Fsynthid-text/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/google-deepmind","download_url":"https://codeload.github.com/google-deepmind/synthid-text/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google-deepmind%2Fsynthid-text/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260268635,"owners_count":22983601,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-03T15:03:53.228Z","updated_at":"2025-06-17T00:39:17.367Z","avatar_url":"https://github.com/google-deepmind.png","language":"Python","funding_links":[],"categories":["Python","Tools"],"sub_categories":["(AI) Watermarking"],"readme":"# SynthID Text\n\nThis repository provides a reference implementation of the SynthID Text\nwatermarking and detection capabilities for the [research paper][nature-paper]\npublished in _Nature_. It is not intended for production use. The core library\nis [distributed on PyPI][synthid-pypi] for easy installation in the\n[Python Notebook example][synthid-colab], which demonstrates how to apply these\ntools with the [Gemma][gemma] and [GPT-2][gpt2] models.\n\n## Installation and usage\n\nThe [Colab Notebook][synthid-colab] is self-contained reference implementation\nthat:\n\n1.  Extends the [`GemmaForCausalLM`][transformers-gemma] and\n    [`GPT2LMHeadModel`][transformers-gpt2] classes from\n    [Hugging Face Transformers][transformers] with a [mix-in][synthid-mixin] to\n    enable watermarking text content generated by models running in\n    [PyTorch][pytorch]; and\n1.  Detects the watermark. This can be done either with the simple [Weighted Mean\n    detector][synthid-detector-mean] which requires no training, or with the\n    more powerful [Bayesian detector][synthid-detector-bayesian] that requires\n    [training][synthid-detector-trainer]. If using the [Weighted Mean\n    detector][synthid-detector-mean] approach across texts of varying token lengths,\n    we recommend empirically/theoretically computing the thresholds at the desired\n    false positives rate at specific token lengths, or using a weighted\n    frequentist approach as described in Appendix A.3.1.\n\nThe notebook is designed to be run end-to-end with either a Gemma or GPT-2\nmodel, and runs best on the following runtime hardware, some of which may\nrequire a [Colab Subscription][colab-subscriptions].\n\n*   Gemma v1.0 2B IT: Use a GPU with 16GB of memory, such as a T4.\n*   Gemma v1.0 7B IT: Use a GPU with 32GB of memory, such as an A100.\n*   GPT-2: Any runtime will work, though a High-RAM CPU or any GPU will be\n    faster.\n\nNOTE: This implementation is for reference and research reproducibility purposes\nonly. Due to minor variations in Gemma and Mistral models across\nimplementations, we expect minor fluctuations in the detectability and\nperplexity results obtained from this repository versus those reported in the\npaper. The subclasses introduced herein are not designed to be used in\nproduction systems. Check out the official SynthID Text implementation in\n[Hugging Face Transformers][transformers-blog] for a production-ready\nimplementation.\n\nNOTE: The `synthid_text.hashing_function.accumulate_hash()` function, used while\ncomputing G values in this reference implementation, does not provide any\nguarantees of cryptographic security.\n\n### Local notebook use\n\nThe notebook can also be used locally if installed from source. Using a virtual\nenvironment is highly recommended for any local use.\n\n```shell\n# Create and activate the virtual environment\npython3 -m venv ~/.venvs/synthid\nsource ~/.venvs/synthid/bin/activate\n\n# Download and install SynthID Text and Jupyter\ngit clone https://github.com/google-deepmind/synthid-text.git\ncd synthid-text\npip install '.[notebook-local]'\n\n# Start the Jupyter server\npython -m notebook\n```\n\nOnce your kernel is running navigate to .pynb file to execute.\n\n### Running the tests\n\nThe source installation also includes a small test suite to verify that the\nlibrary is working as expected.\n\n```shell\n# Create and activate the virtual environment\npython3 -m venv ~/.venvs/synthid\nsource ~/.venvs/synthid/bin/activate\n\n# Download and install SynthID Text with test dependencies from source\ngit clone https://github.com/google-deepmind/synthid-text.git\ncd synthid-text\npip install '.[test]'\n\n# Run the test suite\npytest .\n```\n\n## How it works\n\n### Defining a watermark configuration\n\nSynthID Text produces unique watermarks given a configuration, with the most\nimportant piece of these configurations being the `keys`: a sequence of unique\nintegers where `len(keys)` corresponds to the number of layers in the\nwatermarking or detection models.\n\nThe structure of a configuration is described in the following `TypedDict`\nsubclass, though in practice, the [mixin][synthid-mixin] class in this library\nuses a static configuration.\n\n```python\nfrom collections.abc import Sequence\nfrom typing import TypedDict\n\nimport torch\n\n\nclass WatermarkingConfig(TypedDict):\n    ngram_len: int\n    keys: Sequence[int]\n    sampling_table_size: int\n    sampling_table_seed: int\n    context_history_size: int\n    device: torch.device\n```\n\n### Applying a watermark\n\nWatermarks are applied by a [mix-in][synthid-mixin] class that wraps the\n[`GemmaForCausalLM`][transformers-gemma] and\n[`GPT2LMHeadModel`][transformers-gpt2] classes from Transformers, which results\nin two subclasses with the same API that you are used to from Transformers.\nRemember that the mix-in provided by this library uses a static watermarking\nconfiguration, making it unsuitable for production use.\n\n```python\nfrom synthid_text import synthid_mixin\nimport transformers\nimport torch\n\n\nDEVICE = (\n    torch.device('cuda:0') if torch.cuda.is_available() else torch.device('cpu')\n)\nINPUTS = [\n    \"I enjoy walking with my cute dog\",\n    \"I am from New York\",\n    \"The test was not so very hard after all\",\n    \"I don't think they can score twice in so short a time\",\n]\nMODEL_NAME = 'google/gemma-2b-it'\nTEMPERATURE = 0.5\nTOP_K = 40\nTOP_P = 0.99\n\n# Initialize a standard tokenizer from Transformers.\ntokenizer = transformers.AutoTokenizer.from_pretrained(MODEL_NAME)\n# Initialize a SynthID Text-enabled model.\nmodel = synthid_mixin.SynthIDGemmaForCausalLM.from_pretrained(\n    MODEL_NAME,\n    device_map='auto',\n    torch_dtype=torch.bfloat16,\n)\n# Prepare your inputs in the usual way.\ninputs = tokenizer(\n    INPUTS,\n    return_tensors='pt',\n    padding=True,\n).to(DEVICE)\n# Generate watermarked text.\noutputs = model.generate(\n    **inputs,\n    do_sample=True,\n    max_length=1024,\n    temperature=TEMPERATURE,\n    top_k=TOP_K,\n    top_p=TOP_P,\n)\n```\n\n### Detecting a watermark\n\nWatermark detection can be done using a variety of scoring functions (see\npaper). This repository contains code for the Mean, Weighted Mean, and Bayesian\nscoring functions described in the paper. The colab contains examples for how\nto use these scoring functions.\n\nThe Bayesian detector must be trained on watermarked and unwatermarked data\nbefore it can be used. The Bayesian detector must be trained for each unique\nwatermarking key, and the training data used for this detector model should be\nindependent from, but representative of the expected character and quality of\nthe text content the system will generate in production.\n\n```python\nimport jax.numpy as jnp\nfrom synthid_text import train_detector_bayesian\n\n\ndef load_data():\n  # Get your training and test data into the system.\n  pass\n\n\ndef process_training_data(split):\n  # Get the G values, masks, and labels for the provided split.\n  pass\n\n\ntrain_split, test_split = load_data()\ntrain_g_values, train_masks, train_labels = process_training_data(train_split)\ntest_g_values, test_masks, test_labels = process_training_data(test_split)\n\ndetector, loss = train_detector_bayesian.optimize_model(\n    jnp.squeeze(train_g_values),\n    jnp.squeeze(train_masks),\n    jnp.squeeze(train_labels),\n    jnp.squeeze(test_g_values),\n    jnp.squeeze(test_masks),\n    jnp.squeeze(test_labels),\n)\n```\n\nOnce the Bayesian detector is trained, use the `detector.score()` function to\ngenerate a per-example score indicating if the text was generated with the given\nwatermarking configuration. Score values will be between 0 and 1, with scores\ncloser to 1 indicating higher likelihood that the text was generated with the\ngiven watermark. You can adjust the acceptance threshold to your needs.\n\n```python\nfrom synthid_text import logits_processing\n\n\nCONFIG = synthid_mixin.DEFAULT_WATERMARKING_CONFIG\n\nlogits_processor = logits_processing.SynthIDLogitsProcessor(\n    **CONFIG, top_k=TOP_K, temperature=TEMPERATURE\n)\n\n# Get only the generated text from the models predictions.\noutputs = outputs[:, inputs_len:]\n\n# Compute the end-of-sequence mask, skipping first ngram_len - 1 tokens\n# \u003cbool\u003e[batch_size, output_len]\neos_token_mask = logits_processor.compute_eos_token_mask(\n    input_ids=outputs,\n    eos_token_id=tokenizer.eos_token_id,\n)[:, CONFIG['ngram_len'] - 1 :]\n# Compute the context repetition mask\n# \u003cbool\u003e[batch_size, output_len - (ngram_len - 1)]\ncontext_repetition_mask = logits_processor.compute_context_repetition_mask(\n    input_ids=outputs\n)\n\n# Compute the mask that isolates the generated text.\ncombined_mask = context_repetition_mask * eos_token_mask\n# Compute the G values for the generated text.\ng_values = logits_processor.compute_g_values(input_ids=outputs)\n\n# Score the G values, given the combined mask, and output a per-example score\n# indicating whether the\ndetector.score(g_values.cpu().numpy(), combined_mask.cpu().numpy())\n```\n\n\n## Human Data\n\nWe release the human evaluation data, where we compare watermarked text against unwatermarked text generated from the Gemma 7B model.\nThe data is located in `data/human_eval.jsonl`.\nTo get the prompts used for generating the responses, please use the following code.\n\n```python\nimport json\nimport tensorflow_datasets as tfds\n\nds = tfds.load('huggingface:eli5/LFQA_reddit', split='test_eli5')\nid_to_prompt = {}\nfor x in ds.as_numpy_iterator():\n  id_to_prompt[x['q_id'].decode()] = x['title'].decode()\n\nfull_data = []\nwith open('./data/human_eval.jsonl') as f:\n  for json_str in f:\n    x = json.loads(json_str)\n    x['question'] = id_to_prompt[x['q_id']]\n    full_data.append(x)\n```\n\n## Citing this work\n\n```bibtex\n@article{Dathathri2024,\n    author={Dathathri, Sumanth and See, Abigail and Ghaisas, Sumedh and Huang, Po-Sen and McAdam, Rob and Welbl, Johannes and Bachani, Vandana and Kaskasoli, Alex and Stanforth, Robert and Matejovicova, Tatiana and Hayes, Jamie and Vyas, Nidhi and Merey, Majd Al and Brown-Cohen, Jonah and Bunel, Rudy and Balle, Borja and Cemgil, Taylan and Ahmed, Zahra and Stacpoole, Kitty and Shumailov, Ilia and Baetu, Ciprian and Gowal, Sven and Hassabis, Demis and Kohli, Pushmeet},\n    title={Scalable watermarking for identifying large language model outputs},\n    journal={Nature},\n    year={2024},\n    month={Oct},\n    day={01},\n    volume={634},\n    number={8035},\n    pages={818-823},\n    issn={1476-4687},\n    doi={10.1038/s41586-024-08025-4},\n    url={https://doi.org/10.1038/s41586-024-08025-4}\n}\n```\n\n## License and disclaimer\n\nCopyright 2024 DeepMind Technologies Limited\n\nAll software is licensed under the Apache License, Version 2.0 (Apache 2.0);\nyou may not use this file except in compliance with the Apache 2.0 license.\nYou may obtain a copy of the Apache 2.0 license at:\nhttps://www.apache.org/licenses/LICENSE-2.0\n\nAll other materials are licensed under the Creative Commons Attribution 4.0\nInternational License (CC-BY). You may obtain a copy of the CC-BY license at:\nhttps://creativecommons.org/licenses/by/4.0/legalcode\n\nUnless required by applicable law or agreed to in writing, all software and\nmaterials distributed here under the Apache 2.0 or CC-BY licenses are\ndistributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,\neither express or implied. See the licenses for the specific language governing\npermissions and limitations under those licenses.\n\nThis is not an official Google product.\n\n[colab-subscriptions]: https://colab.research.google.com/signup\n[flax]: https://github.com/google/flax\n[gemma]: https://ai.google.dev/gemma/docs/model_card\n[gpt2]: https://huggingface.co/openai-community/gpt2\n[jax]: https://github.com/google/jax\n[pytorch]: https://pytorch.org/\n[nature-paper]: https://doi.org/10.1038/s41586-024-08025-4\n[synthid-colab]: https://colab.research.google.com/github/google-deepmind/synthid-text/blob/main/notebooks/synthid_text_huggingface_integration.ipynb\n[synthid-pypi]: https://pypi.org/project/synthid-text/\n[synthid-detector-bayesian]: ./src/synthid_text/detector_bayesian.py\n[synthid-detector-mean]: ./src/synthid_text/detector_mean.py\n[synthid-detector-trainer]: ./src/synthid_text/train_detector_bayesian.py\n[synthid-mixin]: ./src/synthid_text/synthid_mixin.py\n[transformers]: https://github.com/huggingface/transformers\n[transformers-blog]: huggingface.co/blog/synthid-text\n[transformers-gemma]: https://github.com/huggingface/transformers/blob/e55b33ceb4b0ba3c8c11f20b6e8d6ca4b48246d4/src/transformers/models/gemma/modeling_gemma.py#L996\n[transformers-gpt2]: https://github.com/huggingface/transformers/blob/e55b33ceb4b0ba3c8c11f20b6e8d6ca4b48246d4/src/transformers/models/gpt2/modeling_gpt2.py#L1185\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoogle-deepmind%2Fsynthid-text","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgoogle-deepmind%2Fsynthid-text","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoogle-deepmind%2Fsynthid-text/lists"}