Categoría: Sin categoría

  • Poda de vocabulario + Continual Pre-training: adapta cualquier LLM al español

    Poda de vocabulario + Continual Pre-training: adapta cualquier LLM al español

    Poda de vocabulario + Continual Pre-training: adapta cualquier LLM al español

    En los artículos previos vimos que cada idioma necesita su propio tokenizador. Entrenamos uno con 128.000 tokens específico para español que reduce los tokens en un 59 % frente al de GPT-2.

    Relacionado: Dos minutos para la gloria: la máquina humana del cañón naval español

    Relacionado: Poda de vocabulario + Continual Pre-training: adapta cualquier LLM al español

    Pero hay un problema: los modelos modernos como Llama 3.2 3B ya no dejan cambiar el tokenizador. El tokenizador está incrustado en el modelo: sus matrices de embedding (input) y LM head (output) tienen una fila por cada token del vocabulario. Si el tokenizador tiene 128.000 tokens, esas matrices ocupan ~1 GB de VRAM solo almacenando tokens de idiomas que nunca vas a usar.

    La solución es la poda de vocabulario (Vocabulary Pruning). Consiste en:

    1. Escanear qué tokens del vocabulario se usan realmente en español
    2. Podar el resto — eliminar cirílico, kanjis, árabe, etc.
    3. Re-mapear los IDs para que sean contiguos
    4. Reducir las matrices del modelo al nuevo tamaño
    5. Hacer continual pre-training con corpus español para que el modelo se adapte a su nuevo vocabulario reducido

    En este artículo lo hacemos paso a paso con TinyLlama 1.1B (modelo abierto, sin autenticación, cabe en cualquier GPU). Los mismos pasos aplican a Llama 3.2 3B, Mistral, o cualquier modelo de la familia Llama.


    El problema: vocabularios multilingües desaprovechados

    Los modelos actuales se entrenan con vocabularios enormes para cubrir todos los idiomas del mundo:

    Modelo Tokens en vocabulario
    GPT-2 (solo inglés) 50.257
    Llama 3 / 3.1 128.000
    Llama 3.2 128.000
    Mistral 32.768
    Gemma 2 256.000

    Cada token en el vocabulario necesita una fila en la matriz de input embeddings (que convierte IDs en vectores) y otra en la de output embeddings / LM head (que convierte vectores en logits).

    En TinyLlama 1.1B (32.000 tokens, dimensión 2048):

    Input embeddings:  32.000 × 2048 = 65,5 millones de parámetros
    Output LM head:    32.000 × 2048 = 65,5 millones de parámetros
    Total embeddings:  131,0 millones de parámetros (11,9 % del modelo)
    

    En Llama 3.2 3B (128.000 tokens, dimensión 4096):

    Input embeddings:  128.000 × 4096 = 524,3 millones de parámetros
    Output LM head:    128.000 × 4096 = 524,3 millones de parámetros
    Total embeddings:  1.048,6 millones de parámetros (~33 % del modelo)
    

    Un tercio del modelo se dedica a almacenar vectores para tokens cirílicos, kanjis, escritura árabe, caracteres devanagari… que si trabajas solo en español nunca vas a usar.


    Paso 1: Escanear qué tokens se usan realmente

    Primero necesitas un corpus representativo de español. Cuanto más variado, mejor. Para esta demo usamos ~2 MB de texto en español (artículos publicados, borradores del blog, y contenido técnico de IA).

    from transformers import AutoTokenizer
    from collections import Counter
    
    model_id = "TinyLlama/TinyLlama-1.1B-Chat-v1.0"
    tokenizer = AutoTokenizer.from_pretrained(model_id)
    
    with open("corpus_espanol.txt", "r", encoding="utf-8") as f:
        corpus = f.read()
    
    used_ids = set()
    freqs = Counter()
    chunk_size = 100_000
    
    for i in range(0, len(corpus), chunk_size):
        chunk = corpus[i:i + chunk_size]
        ids = tokenizer.encode(chunk)
        used_ids.update(ids)
        freqs.update(ids)
    
    # Preservar tokens especiales (<s>, </s>, <pad>, etc.)
    used_ids.update(tokenizer.all_special_ids)
    saved_ids = sorted(list(used_ids))
    

    Resultado con TinyLlama 1.1B:

    Vocabulario total:          32.000 tokens
    Tokens usados en español:   13.713 tokens
    Tokens podables:            18.287 tokens
    Reducción:                   57,1 %
    

    Solo el 43 % del vocabulario de TinyLlama se usa para texto en español. El 57 % restante son tokens para otros idiomas que podemos eliminar sin perder funcionalidad.

    Los tokens más frecuentes en nuestro corpus español son los esperados: espacios, puntuación, artículos, preposiciones y palabras completas como «aprendizaje», «modelo», «datos».


    Paso 2: Podar el tokenizer.json

    El tokenizer de Hugging Face guarda su vocabulario en un archivo tokenizer.json. Basta con quedarse solo con los tokens que usamos y reasignarles IDs contiguos:

    import json
    
    # Cargar tokenizer.json
    with open("tokenizer.json", "r") as f:
        tok_data = json.load(f)
    
    old_vocab = tok_data["model"]["vocab"]
    old_to_new = {old_id: new_id for new_id, old_id in enumerate(saved_ids)}
    
    # Nuevo vocabulario: solo los tokens que conservamos
    new_vocab = {}
    for token_str, old_id in old_vocab.items():
        if isinstance(old_id, int) and old_id in old_to_new:
            new_vocab[token_str] = old_to_new[old_id]
    
    tok_data["model"]["vocab"] = new_vocab
    tok_data["model"]["added_tokens"] = [
        t for t in tok_data["model"].get("added_tokens", [])
        if t.get("id") in old_to_new
    ]
    
    with open("tokenizer.json", "w") as f:
        json.dump(tok_data, f, ensure_ascii=False, indent=2)
    
    print(f"Tokenizer.json: {len(old_vocab)} → {len(new_vocab)} tokens")
    

    Paso 3: Podar las matrices del modelo

    Las matrices embedding y lm_head tienen una fila por token. Creamos matrices nuevas con solo las filas de los tokens que conservamos:

    import torch
    from transformers import AutoModelForCausalLM
    
    model = AutoModelForCausalLM.from_pretrained(model_id, torch_dtype=torch.float32)
    
    old_emb = model.get_input_embeddings()
    old_head = model.get_output_embeddings()
    
    new_emb_weight = torch.zeros(len(saved_ids), old_emb.weight.shape[1], dtype=old_emb.weight.dtype)
    new_head_weight = torch.zeros(len(saved_ids), old_head.weight.shape[1], dtype=old_head.weight.dtype)
    
    with torch.no_grad():
        for new_id, old_id in enumerate(saved_ids):
            new_emb_weight[new_id] = old_emb.weight[old_id]
            new_head_weight[new_id] = old_head.weight[old_id]
    
    # Asignar las nuevas matrices
    model.resize_token_embeddings(len(saved_ids))
    
    # Copiar los pesos podados
    with torch.no_grad():
        model.get_input_embeddings().weight.copy_(new_emb_weight)
        model.get_output_embeddings().weight.copy_(new_head_weight)
    
    model.save_pretrained("./modelo-podado-es")
    

    Resultado con TinyLlama 1.1B:

    Matriz Antes Después Ahorro
    Input embeddings 262,1 MB 112,3 MB 149,8 MB
    LM head 262,1 MB 112,3 MB 149,8 MB
    Total (fp32) 524,3 MB 224,7 MB 299,6 MB
    Total (fp16, GPU) 262,1 MB 112,3 MB ~150 MB

    En una GTX 1070 de 8 GB, esos 150 MB pueden ser la diferencia entre que un modelo quepa o no quepa en VRAM. Y sin perder ninguna capacidad en español — solo eliminamos tokens de otros idiomas.


    Paso 4: Verificar que el modelo funcione

    Después de la poda, las secuencias de tokens de cualquier texto en español se conservan exactamente igual. Los IDs cambian (porque reasignamos a contiguos), pero el significado es el mismo:

    Original: "Hola mundo, esto es una prueba del modelo podado."
    IDs originales:  [1, 379, 2963, 13864, 29892, 18261, 831, 1185, 28854, 2291, 628, 29472, 2532, 912, 29889]
    IDs podados:     [1, 193, 2266, 8407, 13530, 10071, 593, 904, 13259, 1754, 420, 13403, 1930, 663, 13527]
    Tokens totales:  15 → 15 ✅
    

    Caracteres del español bien manejados: la ñ, los acentos (áéíóú), los signos de apertura (¿¡) se tokenizan correctamente tanto antes como después de la poda.


    Paso 5: Continual Pre-training

    Podar el vocabulario no basta. El modelo se ha quedado sin los vectores de los tokens que eliminamos, pero nunca ha visto texto español continuo con su nuevo vocabulario. Ahora toca el continual pre-training.

    La configuración de entrenamiento depende del hardware disponible:

    Para RTX 3060 12GB (configuración recomendada)

    from transformers import TrainingArguments
    from trl import SFTTrainer
    
    training_args = TrainingArguments(
        output_dir="./llm-espanol-final",
        per_device_train_batch_size=2,
        gradient_accumulation_steps=8,  # Batch efectivo: 16
        learning_rate=1e-5,
        bf16=True,                       # Ampere+ soporta bf16
        optim="adamw_8bit",              # Ahorra VRAM
        gradient_checkpointing=True,
        max_steps=1000,
        logging_steps=10,
        save_strategy="steps",
        save_steps=200,
    )
    

    Para GTX 1070 8GB (nuestro hardware)

    La GTX 1070 (Pascal) no soporta bf16 por hardware. Alternativas:

    • Usar fp16 (cuidado con underflow en gradientes pequeños)
    • 4-bit quantization (QLoRA): reduce el modelo a 4 bits sin perder calidad
    • Gradient checkpointing + CPU offloading: mueve capas a RAM cuando no se usan
    • Usar Llama 3.2 1B en lugar de 3B (cabe justo en 8 GB con fp16)
    from transformers import BitsAndBytesConfig
    
    bnb_config = BitsAndBytesConfig(
        load_in_4bit=True,
        bnb_4bit_use_double_quant=True,
        bnb_4bit_quant_type="nf4",
        bnb_4bit_compute_dtype=torch.float16
    )
    

    El corpus de entrenamiento es el español que ya tenemos: Wikipedia en español (18 GB), nuestros artículos, y cualquier texto técnico en español que puedas recopilar. El modelo hace continual pre-training: no aprende desde cero, sino que ajusta sus pesos al nuevo idioma partiendo del conocimiento que ya tiene.


    Proyección para Llama 3.2 3B

    Los mismos pasos aplican a Llama 3.2 3B. La diferencia es el tamaño:

    Métrica TinyLlama 1.1B Llama 3.2 3B
    Vocabulario 32.000 128.000
    Dimensión embedding 2048 4096
    Memoria embeddings (fp16) 262 MB 2,1 GB
    Ahorro estimado (57 % poda) 150 MB ~1,2 GB
    Modelo completo (fp16) ~2,2 GB ~6,0 GB

    En Llama 3.2 3B, las matrices de embedding ocupan 2,1 GB (un tercio del modelo). Podar el 57 % del vocabulario libera ~1,2 GB de VRAM — suficiente para duplicar el batch size o añadir más contexto.


    Conclusiones

    1. Los modelos multilingües desperdician ~57 % de su vocabulario en español. Si trabajas solo en este idioma, puedes podar sin perder calidad.

    2. La poda de vocabulario libera ~150-1200 MB de VRAM dependiendo del modelo. En hardware modesto (GTX 1070 8 GB), esto puede ser la diferencia entre que quepa o no.

    3. El proceso es barato: escanear el corpus y podar las matrices se hace en minutos en CPU. El continual pre-training posterior es la parte cara, pero solo hay que hacerlo una vez.

    4. El código es reutilizable. El script poda_vocabulario.py funciona con cualquier modelo de la familia Llama.

    5. Combinado con nuestro tokenizador BPE español, el ahorro es acumulativo: menos tokens al codificar + menos vectores que almacenar.


    Fuentes y recursos

    • Script completo: poda_vocabulario.py (repositorio del proyecto: https://github.com/Carlos-droid/bpe-espanol)
    • Artículo anterior: Cómo entrenar tu propio tokenizador BPE para español (https://ventasymundopop.com/entrena-tu-tokenizador-bpe-espanol/)
    • Artículo anterior: Tokenización y Embeddings (https://ventasymundopop.com/tokenizacion-embeddings-llm-desde-cero-2/)
    • Giles Thomas, «Writing an LLM from scratch» (CC BY 4.0): https://www.gilesthomas.com/llm-from-scratch
    • Sebastian Raschka, «Build a Large Language Model (from Scratch)»: https://sebastianraschka.com/llms-from-scratch/
    • Repositorio de Raschka: https://github.com/rasbt/LLMs-from-scratch
    • TinyLlama: https://huggingface.co/TinyLlama/TinyLlama-1.1B-Chat-v1.0
    • Búsqueda original sobre poda: Zhou et al., «Vocabulary Pruning for Multilingual LLMs», 2024.

    Ver también en el glosario: Fuentes Y Recursos, Fuentes Y Recursos, Transformers, Tokenización, Tokenizador, Embeddings, Tercio, Token

  • NAV-S30 · El navio como maquina de Estado

    NAV-S30 · El navio como maquina de Estado

    NAV-S30 · El navio como maquina de Estado

    Satélite del principal: NAV-P30 — el-navio-como-maquina-de-estado
    Fuente: DRIVE (smart_rag_nomic_v2, domain=drive) · validable contra RAG
    Estado: BORRADOR para corrección (iPad)

    Relacionado: El navío como máquina de Estado


    1. El ángulo

    Construir y sostener un navio de linea movilizaba bosques, fundiciones, hospitales y escuelas. El buque era el producto visible de todo un Estado.

    2. Por qué importa este satélite

    Acompaña al principal 30 bajándolo a un hecho, un buque o una cifra
    concreta. La base DRIVE tiene material de respaldo (ver fuentes sugeridas).

    3. Notas para el editor

    • Cotejar con fuentes DRIVE: DRIVE: 10_evolucion_construccion, 07_ferrol_astillero.
    • Validar contra RAG (reranker bge-reranker-v2-m3) antes de publicar.
    • Enlazar con el principal y satélites vecinos.

    Borrador generado por Hermes · pendiente de revisión humana antes de publicar.

  • NAV-S29 · Trafalgar y la perdida del mando del mar

    NAV-S29 · Trafalgar y la perdida del mando del mar

    NAV-S29 · Trafalgar y la perdida del mando del mar

    Satélite del principal: NAV-P29 — trafalgar-y-la-perdida-del-mando-del-mar
    Fuente: DRIVE (smart_rag_nomic_v2, domain=drive) · validable contra RAG
    Estado: BORRADOR para corrección (iPad)

    Relacionado: NAV-S07 · Trafalgar: analisis tactico

    Relacionado: Trafalgar y la pérdida del mando del mar


    1. El ángulo

    Tras Trafalgar, Gran Bretana cerrro el Mediterraneo y el Atlantico a Espana. La derrota no fue el fin de la Armada, sino el fin de su mando del mar.

    2. Por qué importa este satélite

    Acompaña al principal 29 bajándolo a un hecho, un buque o una cifra
    concreta. La base DRIVE tiene material de respaldo (ver fuentes sugeridas).

    3. Notas para el editor

    • Cotejar con fuentes DRIVE: DRIVE: 06_trafalgar_1805, 08_carrera_de_indias.
    • Validar contra RAG (reranker bge-reranker-v2-m3) antes de publicar.
    • Enlazar con el principal y satélites vecinos.

    Borrador generado por Hermes · pendiente de revisión humana antes de publicar.

  • NAV-S28 · Especial: lecciones de la Armada (cierre)

    NAV-S28 · Especial: lecciones de la Armada (cierre)

    NAV-S28 · Especial: lecciones de la Armada (cierre)

    Satélite del principal: NAV-P28 — especial-lecciones-de-la-armada-cierre
    Fuente: DRIVE (smart_rag_nomic_v2, domain=drive) · validable contra RAG
    Estado: BORRADOR para corrección (iPad)

    Relacionado: Especial fin de año: lecciones de la Armada

    Relacionado: Especial: lecciones de la Armada (cierre)


    1. El ángulo

    La Armada fue una maquina de estados: hombres, madera, hierro y numeros. Su historia es la de un sistema que funciono por procedimiento, no por gesta.

    2. Por qué importa este satélite

    Acompaña al principal 28 bajándolo a un hecho, un buque o una cifra
    concreta. La base DRIVE tiene material de respaldo (ver fuentes sugeridas).

    3. Notas para el editor

    • Cotejar con fuentes DRIVE: DRIVE: 22_final_armada_vela, 23_mitos_realidades.
    • Validar contra RAG (reranker bge-reranker-v2-m3) antes de publicar.
    • Enlazar con el principal y satélites vecinos.

    Borrador generado por Hermes · pendiente de revisión humana antes de publicar.

  • NAV-S27 · Lecciones de la Armada para hoy

    NAV-S27 · Lecciones de la Armada para hoy

    NAV-S27 · Lecciones de la Armada para hoy

    Satélite del principal: NAV-P27 — lecciones-de-la-armada-para-hoy
    Fuente: DRIVE (smart_rag_nomic_v2, domain=drive) · validable contra RAG
    Estado: BORRADOR para corrección (iPad)

    Relacionado: Especial: lecciones de la Armada (cierre)

    Relacionado: NAV-S28 · Especial: lecciones de la Armada (cierre)


    1. El ángulo

    Logistica, coordinacion y mando decidieron mas que el heroismo. Las lecciones de la vela aplican a cualquier sistema complejo: el detalle vence al simbolo.

    2. Por qué importa este satélite

    Acompaña al principal 27 bajándolo a un hecho, un buque o una cifra
    concreta. La base DRIVE tiene material de respaldo (ver fuentes sugeridas).

    3. Notas para el editor

    • Cotejar con fuentes DRIVE: DRIVE: 06_trafalgar_1805, 13_logistica_naval.
    • Validar contra RAG (reranker bge-reranker-v2-m3) antes de publicar.
    • Enlazar con el principal y satélites vecinos.

    Borrador generado por Hermes · pendiente de revisión humana antes de publicar.

  • NAV-S26 · Herencia de la construccion naval

    NAV-S26 · Herencia de la construccion naval

    NAV-S26 · Herencia de la construccion naval

    Satélite del principal: NAV-P26 — herencia-de-la-construccion-naval
    Fuente: DRIVE (smart_rag_nomic_v2, domain=drive) · validable contra RAG
    Estado: BORRADOR para corrección (iPad)

    Relacionado: La herencia de la construcción naval española

    Relacionado: Herencia de la construcción naval


    1. El ángulo

    Los astilleros, las escuelas y los planos sobrevivieron al vapor y alimentaron la ingenieria naval moderna de Espana. La herencia fue tecnica, no solo mito.

    2. Por qué importa este satélite

    Acompaña al principal 26 bajándolo a un hecho, un buque o una cifra
    concreta. La base DRIVE tiene material de respaldo (ver fuentes sugeridas).

    3. Notas para el editor

    • Cotejar con fuentes DRIVE: DRIVE: 10_evolucion_construccion, 07_ferrol_astillero.
    • Validar contra RAG (reranker bge-reranker-v2-m3) antes de publicar.
    • Enlazar con el principal y satélites vecinos.

    Borrador generado por Hermes · pendiente de revisión humana antes de publicar.

    Ver también en el glosario: Astilleros

  • NAV-S25 · Ciencia y poder naval en la Ilustracion

    NAV-S25 · Ciencia y poder naval en la Ilustracion

    NAV-S25 · Ciencia y poder naval en la Ilustracion

    Satélite del principal: NAV-P25 — ciencia-y-poder-naval-en-la-ilustracion
    Fuente: DRIVE (smart_rag_nomic_v2, domain=drive) · validable contra RAG
    Estado: BORRADOR para corrección (iPad)

    Relacionado: Ciencia y poder naval en la España ilustrada

    Relacionado: Ciencia y poder naval en la Ilustración


    1. El ángulo

    La Ilustracion uso la ciencia para el Estado: academias, planos, estadisticas. El poder naval se calculaba tanto como se combatia.

    2. Por qué importa este satélite

    Acompaña al principal 25 bajándolo a un hecho, un buque o una cifra
    concreta. La base DRIVE tiene material de respaldo (ver fuentes sugeridas).

    3. Notas para el editor

    • Cotejar con fuentes DRIVE: DRIVE: 21_armada_ciencia, 28_ciencia_poder.
    • Validar contra RAG (reranker bge-reranker-v2-m3) antes de publicar.
    • Enlazar con el principal y satélites vecinos.

    Borrador generado por Hermes · pendiente de revisión humana antes de publicar.

  • NAV-S24 · Exploracion y Patagonia

    NAV-S24 · Exploracion y Patagonia

    NAV-S24 · Exploracion y Patagonia

    Satélite del principal: NAV-P24 — exploracion-y-patagonia
    Fuente: DRIVE (smart_rag_nomic_v2, domain=drive) · validable contra RAG
    Estado: BORRADOR para corrección (iPad)

    Relacionado: La Armada y la exploración de la Patagonia

    Relacionado: Exploración y Patagonia


    1. El ángulo

    Las costas patagonicas fueron cartografiadas por la Armada en misiones de reconocimiento. El mapa era poder: quien lo dibujaba, lo controlaba.

    2. Por qué importa este satélite

    Acompaña al principal 24 bajándolo a un hecho, un buque o una cifra
    concreta. La base DRIVE tiene material de respaldo (ver fuentes sugeridas).

    3. Notas para el editor

    • Cotejar con fuentes DRIVE: DRIVE: 27_armada_exploracion, 20_cabo_hornos.
    • Validar contra RAG (reranker bge-reranker-v2-m3) antes de publicar.
    • Enlazar con el principal y satélites vecinos.

    Borrador generado por Hermes · pendiente de revisión humana antes de publicar.

    Ver también en el glosario: Reconocimiento

  • NAV-S23 · Mercantes vs buques de guerra

    NAV-S23 · Mercantes vs buques de guerra

    NAV-S23 · Mercantes vs buques de guerra

    Satélite del principal: NAV-P23 — mercantes-vs-buques-de-guerra
    Fuente: DRIVE (smart_rag_nomic_v2, domain=drive) · validable contra RAG
    Estado: BORRADOR para corrección (iPad)

    Relacionado: Barcos mercantes vs. buques de guerra

    Relacionado: Mercantes vs buques de guerra


    1. El ángulo

    El mercante llevaba la riqueza; el de guerra la protegia. Sin escolta, el comercio era presa; la economia colonial dependia de la escolta mas que de la bandera.

    2. Por qué importa este satélite

    Acompaña al principal 23 bajándolo a un hecho, un buque o una cifra
    concreta. La base DRIVE tiene material de respaldo (ver fuentes sugeridas).

    3. Notas para el editor

    • Cotejar con fuentes DRIVE: DRIVE: 08_carrera_de_indias, 26_mercantes_vs_buques.
    • Validar contra RAG (reranker bge-reranker-v2-m3) antes de publicar.
    • Enlazar con el principal y satélites vecinos.

    Borrador generado por Hermes · pendiente de revisión humana antes de publicar.

    Ver también en el glosario: Bandera, Escolta

  • NAV-S22 · Vida cotidiana a bordo en el siglo XVIII

    NAV-S22 · Vida cotidiana a bordo en el siglo XVIII

    NAV-S22 · Vida cotidiana a bordo en el siglo XVIII

    Satélite del principal: NAV-P22 — vida-cotidiana-a-bordo-en-el-siglo-xviii
    Fuente: DRIVE (smart_rag_nomic_v2, domain=drive) · validable contra RAG
    Estado: BORRADOR para corrección (iPad)

    Relacionado: La vida cotidiana en un navío del siglo XVIII

    Relacionado: Vida cotidiana a bordo en el siglo XVIII


    1. El ángulo

    Hombres, ratas, galleta y bacalao: la vida a bordo era pobreza extrema con riesgo constante. El marinero era mano de obra barata y expuesta, no romantico.

    2. Por qué importa este satélite

    Acompaña al principal 22 bajándolo a un hecho, un buque o una cifra
    concreta. La base DRIVE tiene material de respaldo (ver fuentes sugeridas).

    3. Notas para el editor

    • Cotejar con fuentes DRIVE: DRIVE: 03_vida_a_bordo, 01_alimentacion_marineros.
    • Validar contra RAG (reranker bge-reranker-v2-m3) antes de publicar.
    • Enlazar con el principal y satélites vecinos.

    Borrador generado por Hermes · pendiente de revisión humana antes de publicar.

    Ver también en el glosario: Marinero