DEV Community

LeoJulieta
LeoJulieta

Posted on

Watermarking en LLM: Detecta y protege tus agentes IA

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)
Enter fullscreen mode Exit fullscreen mode

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

  1. Recibe instrucción → genera texto con LLM.
  2. Evalúa salida (por ejemplo, para decidir si publicar, ejecutar una acción o almacenar).
  3. 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))
Enter fullscreen mode Exit fullscreen mode
chmod +x detect_watermark.py
./detect_watermark.py salida.txt
Enter fullscreen mode Exit fullscreen mode

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)*
Enter fullscreen mode Exit fullscreen mode

Top comments (0)