English · Español
04 — Manejo-como-bytes vs manejo-como-codepoints (y el corpus bilingüe §A13)¶
🇪🇸 Cuando BPE encuentra
niñooestá, ¿debe vern-i-ñ-o(codepoints) o0x6E 0x69 0xC3 0xB1 0x6F(bytes UTF-8)? La diferencia parece estética. No lo es: cambia el tamaño del vocabulario base, la robustez a errores de codificación, y — críticamente para §A13 — el número de merges necesarios para que la mitad española del corpus sea tan eficiente como la inglesa.Anchors:
LYNX_CORTEX.md§4 / PHASE 11;LYNX_CORTEX_ADDENDUM.md§A13 (corpus bilingüe); este capítulo §03 escollos de Unicode byte-level.
Los dos bandos¶
| Bando | Alfabeto base | Vocabulario en init | Ejemplo: niño |
|---|---|---|---|
| Codepoints (CP) | Caracteres Unicode | ~150.000 | [n, i, ñ, o] (4 tokens) |
| Bytes (BPE-B) | 0–255 | 256 | [0x6E, 0x69, 0xC3, 0xB1, 0x6F] (5 tokens) |
GPT-2 (Radford et al. 2019) introdujo BPE byte-level. SentencePiece (Kudo & Richardson 2018) por defecto es BPE de codepoints pero soporta byte fallback. LLaMA, Mistral, Qwen, todos usan BPE byte-level hoy.
Tabla de pros / contras¶
Bytes¶
✅ Cobertura universal. Toda cadena UTF-8 tiene representación en bytes. Nunca se necesita un token <UNK>.
✅ Alfabeto base diminuto. 256 tokens iniciales vs los 150K de Unicode — mantiene el vocab inicial barato.
✅ Compatibilidad con blobs binarios. Si el corpus §A13 incrusta metadatos (JSON, etiquetas de idioma), los bytes lo manejan nativamente.
❌ Dos merges por carácter no ASCII. ñ = 0xC3 0xB1 son dos bytes. Para hacer a ñ un único token, gastas un paso de merge por carácter no-ASCII. El español usa ñ á é í ó ú ü, así que son 7 merges solo para recuperar el alfabeto — sobrecoste que la mitad inglesa nunca paga.
❌ Sin intuición semántica para inspeccionar el vocab. 0xC3B1 es opaco para humanos. Los vocabs de codepoints muestran ñ directamente.
Codepoints¶
✅ Un token por carácter en init. El español obtiene paridad con el inglés desde el paso 0. ✅ Legible por humanos. Más fácil de depurar.
❌ <UNK> para codepoints no vistos. Emoji, puntuación exótica, mojibake — todos requieren un fallback.
❌ Vocab inicial más grande en corpora diversos. Para un corpus bilingüe inglés+español la base es ~80 codepoints — está bien. Para un corpus multilingüe, explota.
Qué contiene realmente §A13¶
El corpus bilingüe de gramática enumera 20 verbos × 5 tiempos × 3 personas × {inglés, español} ≈ 600 formas, más pronombres (I, you, he, she, it, yo, tú, él, ella) y auxiliares (will, going to, voy a, va a, ...).
Conjunto de caracteres distintos en el corpus canónico:
English : a..z A..Z + space + period → 53 codepoints
Spanish : a..z A..Z + space + period
+ á é í ó ú ñ Á É Í Ó Ú Ñ ü → 67 codepoints
Combined : ~67 codepoints
UTF-8 bytes used: ~70 (the accented chars contribute 0xC3 0xA1, 0xC3 0xA9, ...)
Así que el alfabeto base es 70 bytes si vas byte-level, 67 codepoints si vas a nivel de codepoints. El corpus §A13 es lo suficientemente pequeño como para que esto sea funcionalmente idéntico.
Pero el calendario de merges difiere¶
Este es el punto clave que la mayoría de los tutoriales se saltan.
Escenario A — BPE de codepoints sobre el corpus bilingüe¶
Tras init, ñ ya es un token. Los primeros ~50 merges fusionan bigramas frecuentes del inglés (th, er, in, _t, _a, ...) y bigramas frecuentes del español (er, ar, _e, _l, ...). En el merge 200, ambos idiomas tienen ratios comparables de tokens por palabra.
Escenario B — BPE byte-level sobre el corpus bilingüe¶
Tras init, ñ son dos tokens (0xC3 0xB1). Los primerísimos merges fusionarán el par fijo (0xC3, 0xB1) → ñ porque es el par más frecuente (cada ñ del corpus contribuye). Lo mismo para (0xC3, 0xA1) → á, etc. Eso son 7 merges forzados para "recuperar el alfabeto español" antes de capturar cualquier estructura lingüística real.
Comparación cuantitativa sobre §A13¶
Medición empírica (lab 02 — pero puedes hacer la aritmética a mano):
| Tokenizer | Media tokens por palabra inglesa | Media tokens por palabra española | Tokens para encajar 1k ejemplos |
|---|---|---|---|
| CP-BPE, vocab=256 | 1.4 | 1.6 | ~5.800 |
| Byte-BPE, vocab=256 | 1.4 | 2.1 | ~6.400 |
| Byte-BPE, vocab=512 | 1.2 | 1.5 | ~5.500 |
Los tokens-por-palabra-española para byte-BPE en vocab=256 son 50% más altos que el inglés — porque el presupuesto se consume con merges de caracteres acentuados. Duplica el vocab y la paridad regresa.
Qué significa esto para el modelo §A13¶
El mini-GPT de la Fase 17 entrena sobre secuencias de estos tokens. Si la mitad del corpus (español) produce consistentemente secuencias más largas:
- El modelo gasta más cómputo por ejemplo en español.
- El coste de attention es cuadrático en longitud de secuencia (Fase 15) — los ejemplos en español cuestan ~
(2.1/1.4)^2 = 2.25×más. - La pérdida (loss) a través de los dos idiomas se pondera injustamente hacia quien tiene secuencias más cortas.
La respuesta correcta para §A13 es BPE byte-level con vocab=512, dando paridad. El lab 02 de la Fase 11 lo impone.
Caso límite: UTF-8 subnormal¶
Un byte \xC3 que no inicia una secuencia válida de 2 bytes (p. ej., \xC3\xC3) es UTF-8 malformado. BPE byte-level alegremente lo fusiona de todos modos — no valida. Esto es una característica, no un bug: el tokenizer es robusto frente a entrada basura, que es lo que quieres para un modelo ML robusto.
BPE de codepoints tiene que validar UTF-8 antes de tokenizar. Si falla, o bien descartas el ejemplo o sustituyes \uFFFD. Cualquiera de los caminos añade complejidad de preprocesado.
Qué hacen los LLM modernos¶
| Modelo | Tipo de tokenizer | Tamaño base |
|---|---|---|
| GPT-2 | BPE byte-level | 256 |
| GPT-3 | BPE byte-level (mismo) | 256 |
| LLaMA-1 | SentencePiece + BPE (estilo CP, con byte fallback) | mixto |
| LLaMA-2 | Igual que LLaMA-1 | mixto |
| Mistral 7B | BPE byte-level | 256 |
| Qwen-1.5 | BPE byte-level | 256 |
| Gemma | SentencePiece + Unigram | - |
BPE byte-level es la elección dominante porque es verdaderamente universal. La Fase 11 implementa BPE byte-level por esa razón.
Qué entrena §A13 (concretamente)¶
scripts/train_bpe.py (lab de la Fase 11) recibe como entrada el corpus bilingüe completo (~600 frases × ~5 palabras cada una). Configuración:
TOKENIZER_CONFIG = {
"kind": "byte_bpe",
"base_vocab_size": 256,
"target_vocab_size": 512,
"merges_path": "data/tokenizer/merges.txt",
"vocab_path": "data/tokenizer/vocab.json",
# ε for word-boundary regex; GPT-2 style
"pre_tokenize_regex": r"""'s|'t|'re|'ve|'m|'ll|'d| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+""",
}
Vocab 512 da paridad. El regex de pre-tokenización sigue el patrón de GPT-2 — evita que los merges crucen fronteras de palabra.
Escollos (y cómo detectarlos)¶
- Vocab 256, corpus bilingüe, "el español entrena peor" — y culpas al modelo. Es el tokenizer. Siempre mide
mean_tokens_per_wordpor idioma antes de culpar al modelo. - BPE de codepoints sobre entrada UTF-8 que no decodificó. El tokenizer crashea o emite
\uFFFD. Elige byte-level y sáltate el modo de fallo. - Regex de pre-tokenización afinado para inglés, aplicado al español. La puntuación española (
¿ ¡) y las convenciones de signo de interrogación invertido pueden producir tokens extraños. Test en una muestra española held-out. - Mezclar corpora de entrenamiento BPE ("entrena sobre Wikipedia inglés, despliega sobre §A13 español"). El vocab estará sesgado hacia bigramas ingleses. El lab 02 de la Fase 11 impone mismo corpus de entrenamiento y de despliegue.
Citas¶
- Sennrich, R., Haddow, B., Birch, A. 2016. "Neural Machine Translation of Rare Words with Subword Units." arXiv:1508.07909 — BPE original para NMT.
- Radford, A. et al. 2019. "Language Models are Unsupervised Multitask Learners" (GPT-2). La sección 2.2 introduce el BPE byte-level.
- Kudo, T., Richardson, J. 2018. "SentencePiece: A simple and language independent subword tokenizer." arXiv:1808.06226.
Recapitulación en un párrafo¶
BPE byte-level tiene un alfabeto base de 256 tokens y representa cualquier cadena UTF-8 sin <UNK>. BPE de codepoints tiene la base de ~150K de Unicode y es legible por humanos pero necesita fallback <UNK>. Para el corpus bilingüe §A13, BPE byte-level con vocab=256 produce 50% más tokens por palabra española que por palabra inglesa porque 7 merges se gastan recuperando caracteres acentuados. Duplicar vocab a 512 restaura la paridad. La Fase 11 usa BPE byte-level con vocab=512 por defecto. El modelo §A13 de otro modo gastaría cuadráticamente de más en español bajo attention.
Anterior: 03-byte-level-and-unicode.md
Siguiente: Fase 12 (diseño del corpus).