Curso / HF LLM Course / Capítulo 6
● piloto de formato

Hugging Face · LLM Course · Capítulo 6

Tokenizers: lo que pasa antes de que exista un solo número

Usaste tokenizers en cada capítulo hasta acá sin mirar adentro. Este capítulo abre esa caja: cómo se entrena un tokenizer, qué lo hace "rápido" de verdad, y los tres algoritmos que compiten por decidir dónde cortar una palabra.

1. Entrenar un tokenizer nuevo a partir de uno viejo

Entrenar un tokenizer no es entrenar un modelo — no hay descenso de gradiente ni aleatoriedad. Es un proceso estadístico y determinístico: mirar un corpus entero y decidir qué subpalabras merecen ser un token propio. Mismo corpus, mismo algoritmo, mismo resultado siempre.

AutoTokenizer.train_new_from_iterator() reentrena el vocabulario de un tokenizer existente sobre un corpus nuevo, conservando su algoritmo y tokens especiales. El ejemplo del original — reentrenar el tokenizer de GPT-2 (pensado para inglés) sobre código Python — muestra por qué esto importa: el tokenizer original parte _ y las palabras con guión bajo de forma rara porque nunca vio código en su entrenamiento. Uno reentrenado sobre CodeSearchNet aprende tokens propios como uno para "indentación de 4 espacios" o uno para las triples comillas de un docstring — 27 tokens contra 36 para la misma función, con una representación que refleja la estructura real del lenguaje.

🟢 Por qué esto importa hoy

Todo LLM de propósito general reentrena su tokenizer sobre un corpus masivo y multilingüe antes de arrancar el preentrenamiento del modelo en sí — es de los primeros pasos, no un detalle. Un vocabulario mal ajustado al dominio (código, otro idioma, notación matemática) infla la cantidad de tokens que necesitás para decir lo mismo, lo que encarece cada request y acorta la ventana de contexto útil.

2. Los poderes ocultos de un tokenizer rápido

La salida de un tokenizer no es un diccionario simple — es un BatchEncoding, y si el tokenizer es "rápido" (respaldado por Rust, vía la librería tokenizers), trae consigo un offset mapping: para cada token, recuerda exactamente qué tramo del texto original lo generó.

encoding = tokenizer("My name is Sylvain", return_offsets_mapping=True)
encoding.word_ids()         # [None, 0, 1, 2, 3, 3, 3, None] — a qué palabra pertenece cada token
encoding["offset_mapping"]  # [(0,0), (0,2), (3,7), (8,10), (11,12), (12,14), ...]

Esto no es un detalle de performance — es lo que hace posible reconstruir, sin reglas hechas a mano para cada tipo de tokenizer, qué tramo exacto del texto original corresponde a una entidad detectada por un modelo de NER: agrupás los tokens consecutivos de la misma entidad y tomás el offset desde el primero hasta el último. La misma técnica es la base de cómo un pipeline de question-answering recupera la respuesta exacta dentro del contexto, carácter por carácter.

3. El pipeline completo: normalización → pre-tokenización → modelo

Antes de partir en subpalabras, todo tokenizer pasa el texto por dos pasos previos:

Normalización

Limpieza general: minúsculas, sacar acentos, normalizar Unicode (NFC/NFKC), espacios repetidos.

Pre-tokenización

Cortar el texto en unidades chicas (típicamente palabras) — esos cortes son los límites donde el algoritmo de subpalabras puede operar.

Cada familia de tokenizer pre-tokeniza distinto: BERT corta en espacios y puntuación; GPT-2 corta igual pero conserva los espacios como parte del token siguiente (el símbolo Ġ que ya viste en capítulos anteriores); T5 (basado en SentencePiece) solo corta en espacios, reemplazándolos por , sin tocar la puntuación.

SentencePiece merece mención aparte: trata el texto como una secuencia cruda de caracteres Unicode, incluyendo los espacios en el conjunto de símbolos — por eso ni siquiera necesita un paso de pre-tokenización separado, algo clave para idiomas como japonés o chino, donde el espacio no separa palabras. Como bonus, su tokenización es reversible: decodificar es solo concatenar tokens y cambiar por espacio, sin la pérdida de información que sufre un tokenizer que colapsa espacios repetidos.

4. Los tres algoritmos: BPE, WordPiece, Unigram

Los tres resuelven el mismo problema — qué subpalabras merecen ser un token — desde direcciones opuestas:

BPEWordPieceUnigram
Arranca deVocabulario chico (caracteres)Vocabulario chico (caracteres)Vocabulario grande
Cada pasoFusiona el par más frecuenteFusiona el par con mejor score (penaliza partes ya muy frecuentes)Elimina los tokens que menos aumentan la pérdida al sacarlos
AprendeReglas de fusión + vocabularioSolo el vocabulario finalVocabulario con una probabilidad por token
TokenizarAplica las fusiones aprendidas, en ordenBusca la subpalabra más larga que esté en el vocabulario, de izquierda a derechaBusca la segmentación más probable (algoritmo de Viterbi)
Lo usanGPT, GPT-2, RoBERTa, LlamaBERT, DistilBERTT5, ALBERT, XLNet (vía SentencePiece)

Vale la pena seguir un ejemplo concreto de BPE, porque el mecanismo es simple una vez que lo ves una vez. Corpus de juguete: hug(×10), pug(×5), pun(×12), bun(×4), hugs(×5). Se arranca con cada palabra partida en caracteres:

h u g (×10)   p u g (×5)   p u n (×12)   b u n (×4)   h u g s (×5)

El par más frecuente en todo el corpus es ("u","g") — aparece 20 veces (en hug, pug y hugs). Esa es la primera fusión: "ug" entra al vocabulario, y se reemplaza en todas partes. El siguiente par más frecuente pasa a ser ("u","n") (16 veces) → se funde en "un". Después ("h","ug") (15 veces) → "hug". Y así, fusión por fusión, hasta llegar al tamaño de vocabulario que se pidió. Tokenizar una palabra nueva es aplicar esas mismas reglas de fusión, en el mismo orden en que se aprendieron.

🟢 Actualización 2026

BPE ganó la carrera de forma aplastante para LLMs decoder-only — pero la variante que se usa hoy casi siempre es BPE a nivel de byte (la que introdujo GPT-2): en vez de partir en caracteres Unicode, parte en los 256 valores de byte posibles. Eso significa que el vocabulario base es minúsculo y nunca hay un carácter — emoji, símbolo raro, cualquier idioma — que caiga en el token desconocido, porque todo, sin excepción, se puede descomponer en bytes. Los vocabularios finales de los LLM modernos también crecieron mucho respecto a los ~50k de GPT-2 — hoy es común ver vocabularios de 100k a más de 250k tokens, para cubrir muchos idiomas sin que cada uno se fragmente en demasiados tokens.

WordPiece cambia solo el criterio de qué fusionar: en vez del par más frecuente a secas, prioriza pares donde cada parte individual es poco frecuente por sí sola — evita fusionar algo como "un" + "##able" aunque el par sea común, porque ambos fragmentos ya son comunes por separado y probablemente sirven mejor sueltos.

Unigram invierte el proceso: arranca de un vocabulario enorme (todas las subcadenas posibles, o el resultado de correr BPE hasta un vocabulario grande) y en cada paso elimina el porcentaje de tokens que, sacados, menos empeoran una función de pérdida calculada sobre todo el corpus. Nunca elimina caracteres sueltos, para poder tokenizar cualquier palabra sin caer en desconocido.

5. Armar un tokenizer, bloque por bloque

La librería tokenizers expone cada etapa del pipeline como una pieza intercambiable: normalizers, pre_tokenizers, models (BPE/WordPiece/Unigram), trainers, post_processors y decoders. Armar un tokenizer propio es elegir una de cada una y entrenarlo:

from tokenizers import Tokenizer, models, normalizers, pre_tokenizers, trainers

tokenizer = Tokenizer(models.WordPiece(unk_token="[UNK]"))
tokenizer.normalizer = normalizers.Sequence([normalizers.NFD(), normalizers.Lowercase()])
tokenizer.pre_tokenizer = pre_tokenizers.Whitespace()

trainer = trainers.WordPieceTrainer(vocab_size=25000, special_tokens=["[UNK]", "[CLS]", "[SEP]"])
tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer)

El último paso, post_processor, es el que agrega los tokens especiales en su lugar ([CLS] al inicio, [SEP] entre oraciones) y asigna los token_type_ids del Capítulo 3. Un tokenizer armado así se guarda en un único JSON y se envuelve en PreTrainedTokenizerFast para usarlo exactamente igual que cualquier tokenizer de transformers — incluido subirlo al Hub con push_to_hub().

6. Resumen

  1. Entrenar un tokenizer es estadístico y determinístico — nada que ver con entrenar un modelo.
  2. Un tokenizer rápido (Rust) trae offset mapping — la pieza que hace posible NER y QA sin reglas hechas a mano por tipo de tokenizer.
  3. Normalización + pre-tokenización deciden los límites; BPE, WordPiece y Unigram deciden cómo llenar el vocabulario dentro de esos límites, desde direcciones opuestas (construir vs. podar).
  4. BPE a nivel de byte es el estándar de facto en LLMs decoder-only hoy — vocabulario base minúsculo, cero tokens desconocidos posibles.
  5. Cada etapa del pipeline es una pieza intercambiable en tokenizers — armar un tokenizer propio es elegir una de cada tipo y entrenarlo.