Watermarking en LLM: cómo funciona, cómo detectarlo y qué implica para tus agentes de IA autónomos
Introducción
¿Te ha pasado que una revisión de contenido generado por IA te devolvió un “texto sospechoso”? Cada vez que un modelo de lenguaje grande (LLM) escribe, lleva escondida una huella digital que permite saber si el texto es sintético y, de ser así, de qué modelo proviene. Esa huella es el watermarking: un código invisible incrustado en la secuencia de tokens, no una marca de agua visual.
En los últimos meses el tema ha explotado: búsquedas de “LLM watermark” han subido un 420 % en Google Trends y los principales proveedores (OpenAI, Anthropic, Cohere…) ya ofrecen APIs con watermark activado por defecto. Si trabajas con agentes autónomos, la presencia de watermarks afecta la generación, la detección y, sobre todo, la toma de decisiones de esos sistemas.
Este artículo es una guía práctica para entender, implementar y mitigar el watermarking en tus flujos de IA. Veremos cómo funciona, cómo detectarlo con código listo para copiar‑pegar y qué medidas tomar para que tus agentes no se vean sorprendidos.
1. ¿Qué es el watermarking de LLM?
- Definición breve: inserción deliberada de patrones estadísticos en la salida del modelo que pueden ser leídos posteriormente por un detector.
- Diferencia con una marca visual: la marca de agua tradicional es visible para el ojo humano; el watermark de LLM es invisible y solo se revela mediante análisis de probabilidad de los tokens.
1.1. Principios básicos
| Concepto | Qué ocurre |
|---|---|
| Selección de tokens | Durante la generación, el modelo elige entre dos subconjuntos de vocabulario (A y B). Un bit de watermark se codifica al forzar la selección a A (bit = 0) o B (bit = 1). |
| Probabilidad ajustada | Se aumenta ligeramente la probabilidad de los tokens del subconjunto elegido (p + ε) y se disminuye la del otro (p − ε). El cambio es tan pequeño que la calidad percibida no se degrada. |
| Detección | Un algoritmo cuenta cuántos tokens caen en A vs. B y evalúa la desviación respecto a una distribución aleatoria mediante una prueba de hipótesis (p‑valor). |
2. Implementación rápida: añadir watermark a un modelo de código abierto
A continuación, un ejemplo funcional usando Hugging Face Transformers y el modelo Llama‑2‑7B. El código inserta un watermark con ε = 0.05 y lo verifica después de la generación.
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
import random
import numpy as np
from scipy.stats import binom_test
model_name = "meta-llama/Llama-2-7b-chat-hf"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name, torch_dtype=torch.float16).to("cuda")
# 1️⃣ Definir los subconjuntos A y B (ejemplo aleatorio)
vocab_size = tokenizer.vocab_size
indices = list(range(vocab_size))
random.shuffle(indices)
split = vocab_size // 2
subset_A = set(indices[:split])
subset_B = set(indices[split:])
epsilon = 0.05 # fuerza del watermark
def apply_watermark(logits):
# logits: Tensor [batch, vocab]
probs = torch.softmax(logits, dim=-1)
# aumentar probabilidad de tokens del subconjunto activo
mask_A = torch.zeros_like(probs)
mask_A[list(subset_A)] = 1.0
mask_B = 1.0 - mask_A
# elegimos bit aleatorio (0 -> A, 1 -> B)
bit = random.randint(0, 1)
if bit == 0:
probs = probs * (1 + epsilon * mask_A) / (1 + epsilon * mask_A).sum(dim=-1, keepdim=True)
else:
probs = probs * (1 + epsilon * mask_B) / (1 + epsilon * mask_B).sum(dim=-1, keepdim=True)
return torch.log(probs), bit
def generate_with_watermark(prompt, max_new_tokens=50):
input_ids = tokenizer(prompt, return_tensors="pt").input_ids.to("cuda")
generated = input_ids
bits = []
for _ in range(max_new_tokens):
outputs = model(generated)
logits = outputs.logits[:, -1, :]
log_probs, bit = apply_watermark(logits)
next_token = torch.argmax(log_probs, dim=-1, keepdim=True)
generated = torch.cat([generated, next_token], dim=1)
bits.append(bit)
if next_token.item() == tokenizer.eos_token_id:
break
text = tokenizer.decode(generated[0], skip_special_tokens=True)
return text, bits
def detect_watermark(text, bits_expected_len=None, alpha=0.05):
ids = tokenizer.encode(text, return_tensors="pt")
# contar cuántos tokens caen en A vs B
count_A = sum(1 for i in ids[0].tolist() if i in subset_A)
count_B = sum(1 for i in ids[0].tolist() if i in subset_B)
n = count_A + count_B
# prueba binomial bajo hipótesis nula p=0.5
p_val = binom_test(count_A, n, 0.5, alternative="two-sided")
return {"count_A": count_A, "count_B": count_B, "p_value": p_val, "watermarked": p_val < alpha}
# -------------------------------------------------
# Uso rápido
prompt = "Escribe una breve reseña sobre la novela 'Cien años de soledad'."
texto, bits = generate_with_watermark(prompt)
print("Salida generada:", texto)
print("Bits insertados:", bits)
det = detect_watermark(texto)
print("Detección:", det)
Qué observar:
- La generación sigue siendo coherente; la diferencia de calidad es imperceptible para usuarios humanos.
- El detector devuelve un p‑value < 0.05 en la mayoría de los casos, confirmando la presencia del watermark.
3. Impacto del watermarking en agentes autónomos
3.1. Flujo típico de un agente
- Recibe instrucción → genera texto con LLM.
- Evalúa salida (por ejemplo, para decidir si publicar, ejecutar una acción o almacenar).
- Registra metadatos (incluye posible watermark).
3.2. Problemas reales
| Problema | Consecuencia | Mitigación práctica |
|---|---|---|
| Detección falsa positiva | El agente descarta contenido propio creyendo que es sintético. | Ajustar el umbral de p‑value según la tolerancia al error (ej. 0.01). |
| Watermark adversarial | Un atacante inserta watermarks falsos para manipular la lógica del agente. | Validar la firma del watermark (si el proveedor la firma criptográficamente) o combinar con análisis de estilo. |
| Degradación de creatividad | En tareas creativas, el patrón de selección de tokens puede limitar la diversidad. | Desactivar watermark en modos “creative” mediante la API (watermark=False). |
4. Detectar watermarks en producción (scripts listos)
4.1. CLI rápido con detect-watermark
Guarda el siguiente script como detect_watermark.py y ejecútalo contra cualquier archivo de texto:
#!/usr/bin/env python3
import sys, json
from pathlib import Path
from your_watermark_lib import detect_watermark # importa la función del bloque anterior
if len(sys.argv) < 2:
print("Uso: detect_watermark.py <archivo.txt>")
sys.exit(1)
path = Path(sys.argv[1])
text = path.read_text(encoding="utf-8")
result = detect_watermark(text)
print(json.dumps(result, indent=2, ensure_ascii=False))
chmod +x detect_watermark.py
./detect_watermark.py salida.txt
4.2. Integración en pipelines CI/CD
yaml
# .github/workflows/watermark.yml
name: Detectar Watermarks en Salidas de IA
on: [push, pull_request]
jobs:
watermark-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Instalar dependencias
run: pip install torch transformers scipy
- name: Ejecutar detección
run: |
python detect_watermark.py generated_output.txt > report.json
cat report.json
python -c "
import json, sys
r = json.load(open('report.json'))
if r['watermarked']:
---
*Herramienta mencionada: [Groq Cloud](https://groq.com)*
Top comments (0)