Majid Al-RaimiCorpora, Unicode and UTF-8

ICS 582Lecture 02Part 05

Corpora, Unicode and UTF-8

What a corpus is and why it must be documented, how Unicode names every character with a code point, and how UTF-8 packs code points into bytes that any model can consume.

Concepts
4
Slides
29-37
Reading
24 min
Understood
0/4 concepts

Why this part matters

Your research project processes Arabic, and Arabic is where text representation stops being a detail. One letter has four visual shapes, one sound has several hamza spellings, legacy keyboards emit presentation forms, and every letter costs two bytes. A tokenizer that does not know this splits words in places no linguist would recognize.

This part builds the foundation under every tokenizer count in the rest of the course. First it asks what a corpus actually is and what you owe a reader who will trust a model trained on it. Then it descends from characters to numbers (code points), from numbers to bytes (UTF-8), and finally explains why bytes are the floor that byte-level BPE in part 7 stands on. Every corpus you use or build for the project must carry a data statement to be publishable, and every byte count you will ever see in a tokenizer log traces back to the four-row mapping table taught here.

By the end you can

  1. Name the variation dimensions of a corpus and list the fields of a data statement or datasheet.
  2. Distinguish code point, glyph and encoding, and explain why saying Unicode is an encoding is wrong.
  3. Apply NFC, NFD and NFKC to precomposed and decomposed Latin and Arabic examples and say which tokenization bug each prevents.
  4. Encode any code point into UTF-8 bytes by hand using the four-row table, and decode bytes back.
  5. Explain why bytes remove the unknown-character problem and what they cost.

Two datasets both labelled "Arabic corpus" can be almost unrelated. One is Modern Standard Arabic newswire from 2005, written by trained journalists in a formal register. The other is Gulf-dialect tweets from 2024, full of Arabizi, emoji and code switching into English. A model trained on the first will stumble on the second, and a vocabulary learned from either will miss most of the other. Same language label, different domain, genre, time, demographics and variety.

A Corpus is a structured collection of texts, often annotated, drawn from sources such as books, news, web crawls, social media and speech transcripts. The word "structured" matters: a corpus is a deliberate sample with metadata, not a pile of files. Jurafsky and Martin describe the dimensions along which such samples vary as language and variety (including code switching), genre, the demographics of the writer or speaker, and time. The table below adds domain, which the slides list separately, and gives an Arabic example for each.

DimensionQuestion it answersArabic example
Language and varietyWhich language, dialect or register, and is there code switching?Modern Standard Arabic newswire versus Najdi tweets, or Arabizi in Latin letters
GenreWhat kind of text is it?Quranic commentary, a parliamentary transcript, a product review
TimeWhen was it written or spoken?Pre-2011 news versus post-2020 social media, where vocabulary shifted
DemographicsWho wrote or spoke it, by age, gender, region, education?Teenage gamers in Riyadh versus retired teachers in Cairo
DomainWhat is it about?Medical records, legal contracts, football commentary
Dimensions along which corpora vary
A corpus is a stack of strata: domain, genre, time, demographics and variety. A data statement is the tag that records all five.

Models inherit their corpus

A language model is a compressed description of its training corpus, so it inherits the corpus's biases and blind spots. Bender and Friedman argue that systems perform best for the populations represented in their training data, so bias and exclusion arise when that data does not match the people the system is deployed for, and the fix begins with knowing what the data contains. Without that knowledge, a result on a benchmark cannot be interpreted: a 95% accuracy on 2005 newswire says nothing about 2024 tweets. To interpret any result you need the metadata below.

Metadata a corpus must carry before its results can be interpreted

Collection procedure
How texts were sampled or crawled, and what was filtered out
Time period
When the texts were produced, not only when they were collected
Languages and varieties
Which languages, dialects and registers, and how much code switching
Speaker or author demographics
Age, gender, region, education and any other known attributes
Licensing and privacy
Who may use the data, for what, and whether consent was obtained

Data statements and datasheets

Two papers turned that metadata list into a standard document. Bender and Friedman (2018) proposed the data statement for NLP datasets, a short structured write-up with lettered sections from curation rationale through language variety, speaker and annotator demographics, speech situation and text characteristics. Gebru and colleagues (2021) proposed the datasheet for any machine learning dataset, borrowing the idea from electronics: a component datasheet lists operating ranges and test conditions, so a dataset should too. The slides merge the two into six fields.

The six datasheet fields, with the matching sections in the two papers

Motivation and intended use
Why the corpus was built, by whom, funded how, and what tasks it is meant for (Gebru: motivation and uses; Bender and Friedman: A, curation rationale)
Composition
What the instances are, how many, which languages, varieties and demographics they cover (Gebru: composition; Bender and Friedman: B to F)
Collection process
How and when the texts were gathered, by crawl, purchase or recording, and who did it (Gebru: collection process; Bender and Friedman: I, provenance)
Preprocessing and annotation
Cleaning, filtering, tokenization and labelling steps, and the annotators' guidelines and demographics (Gebru: preprocessing, cleaning, labeling; Bender and Friedman: D)
Ethical considerations
Consent, privacy, sensitive content and the populations who could be harmed by a model trained on it (Gebru: uses and maintenance)
Distribution constraints
License, access terms, versioning and who maintains the release (Gebru: distribution and maintenance)

The payoff is transparency, reproducibility and responsible use. Transparency because a reader can judge whether your corpus matches their population. Reproducibility because someone else can rebuild or extend the sample. Responsible use because a datasheet states what the data must not be used for, which is the only place such a limit can live once the files are downloaded.

Recall

Name four fields of a datasheet, and state the one-sentence reason a corpus needs one.

Any four of motivation and intended use, composition, collection process, preprocessing and annotation, ethical considerations, distribution constraints. Models inherit the biases and limitations of their training corpus, so results cannot be interpreted without knowing what the corpus contains.

Quick check

Which datasheet field explains why the corpus was created?

Type the word café on two computers. On a Mac the é arrives as one unit, U+00E9. On a system with a decomposed input method it arrives as two, the plain letter U+0065 followed by a combining acute accent U+0301. On screen the two strings are pixel for pixel identical. In a program they are unequal, they have different lengths, and a tokenizer trained on one will split the other into pieces it never saw during training. Arabic has the same trap: a legacy keyboard emits the lam-alef ligature U+FEFB as one character, while a modern one emits U+0644 then U+0627.

To reason about this you need three separate words. Unicode assigns every character a Code point, an integer between 0 and 0x10FFFF written with a U+ prefix in hexadecimal, such as U+0061 for the letter a. That gives 1,114,112 possible positions, and the standard covers Latin, Cyrillic, Arabic, Han, emoji and over 170 other scripts within them. A Glyph is the rendered shape a font draws for a code point. The Unicode core specification is explicit that an abstract character should not be confused with a glyph: the code point is the identity, the glyph is one of many possible pictures of it.

The mapping runs many to many in both directions. One glyph can stand for several code points: the é above is one shape and two encodings, and Latin A (U+0041), Cyrillic А (U+0410) and Greek Α (U+0391) look identical in most fonts. One code point can also have several glyphs: the Arabic letter beh U+0628 is drawn as isolated, initial, medial or final form depending on its neighbours, yet the text stores a single code point and the font chooses the shape.

ASCII: the first 128 code points

The oldest slice of Unicode is ASCII, a 7-bit code with 128 positions defined in RFC 20, which occupies code points U+0000 to U+007F unchanged. The slide shows a fragment of the table in hexadecimal and decimal. The version below adds the binary column, because the binary reveals a fact you will use every time you write a case-folding rule.

CharacterHexDecimalBinary
<3C600011 1100
=3D610011 1101
>3E620011 1110
?3F630011 1111
@40640100 0000
A41650100 0001
B42660100 0010
C43670100 0011
\5C920101 1100
]5D930101 1101
^5E940101 1110
_5F950101 1111
`60960110 0000
a61970110 0001
b62980110 0010
c63990110 0011
A fragment of the ASCII table, with the binary column the slide caption promised

Compare A at 0100 0001 with a at 0110 0001. They differ in exactly one bit, bit 5, which is worth 0x20 or 32. Every uppercase letter sits 32 positions before its lowercase partner, so Case folding in ASCII is a single OR with 0x20, which is why it is so fast and why it breaks the moment text leaves ASCII. Arabic has no case, and Greek final sigma has no simple bit rule, so Unicode defines case folding as a table lookup instead.

a=0x61=0x41+0x20=A    0x20\text{a} = 0x61 = 0x41 + 0x20 = \text{A} \;\lor\; 0x20
ASCII lowercase is one bit away from uppercase

Normalization: one canonical spelling for each character

Back to café. Unicode calls U+00E9 the precomposed form and U+0065 U+0301 the decomposed form, and declares them canonically equivalent: they must be treated as the same character. Arabic has the same pair. Alef with hamza above, U+0623, decomposes canonically to bare alef U+0627 plus the combining hamza above U+0654, as the Unicode code chart records. A different, weaker relation is compatibility equivalence: the lam-alef ligature U+FEFB is compatible with U+0644 U+0627, and the ligature (U+FB01) with the two letters f and i. Compatibility variants look different or carry formatting, so folding them loses information.

One glyph é can be stored as one code point or as two. NFC folds both spellings into the single precomposed form.

Unicode normalization converts text to one of four standard forms defined in Unicode Standard Annex 15. The D forms decompose, the C forms decompose and then recompose, and the K forms also apply the lossy compatibility mappings. The table gives the result for é, for the lam-alef ligature and for the fi ligature, with the UTF-8 byte count of each result so you can see that normalization changes lengths, not only identities.

FormOperationé (either spelling)ﻻ U+FEFBfi U+FB01
NFDCanonical decompositionU+0065 U+0301 (3 bytes)U+FEFB (3 bytes)U+FB01 (3 bytes)
NFCCanonical decomposition, then canonical compositionU+00E9 (2 bytes)U+FEFB (3 bytes)U+FB01 (3 bytes)
NFKDCompatibility decompositionU+0065 U+0301 (3 bytes)U+0644 U+0627 (4 bytes)f i (2 bytes)
NFKCCompatibility decomposition, then canonical compositionU+00E9 (2 bytes)U+0644 U+0627 (4 bytes)f i (2 bytes)
The four normalization forms of UAX 15, Table 1

Worked example

NFD and NFC on a Latin and an Arabic letter

  1. Decompose é

    NFD(U+00E9) looks up the canonical decomposition and returns U+0065 U+0301, base letter then combining mark. Two code points, three UTF-8 bytes.
  2. Recompose é

    NFC(U+0065 U+0301) first decomposes (no change) and then composes the base with its mark, returning U+00E9. One code point, two bytes. Both input spellings end in the same place.
  3. Decompose alef with hamza

    NFD(U+0623) returns U+0627 U+0654, and NFC folds that pair back to U+0623. Bare alef U+0627 stays bare under every form: it is a different letter, not a variant spelling.
  4. Fold the ligature

    NFC(U+FEFB) is unchanged, three bytes. NFKC(U+FEFB) returns U+0644 U+0627, two letters and four bytes, because the ligature is only compatibility-equivalent.
  5. Result

    NFC is the safe default for storage, because it is lossless and shortest. NFKC is the matching form, because it also folds ligatures, fullwidth digits and superscripts, at the price of erasing them.

Here is the tokenizer bug normalization prevents. Suppose a BPE vocabulary is learned on NFC text, so "café" is one frequent token. Now NFD input arrives. The tokenizer sees c a f e U+0301, finds no merge that includes a lone combining mark, and emits "cafe" plus a separate accent token that no training word ever contained. Equality tests, dictionary lookups and Tokenization all fail in the same way, and all three are fixed by normalizing once, at ingestion, before anything else runs. Note that alef normalization in Arabic NLP toolkits, which folds U+0623, U+0625 and U+0622 into bare alef, is an application rule on top of Unicode, not one of the four forms.

Recall

Define code point and glyph, and give one example of each direction of the many-to-many mapping.

A code point is the abstract integer identifier Unicode assigns to a character, such as U+0061. A glyph is the shape a font renders. One glyph to many code points: é as U+00E9 or U+0065 U+0301. One code point to many glyphs: beh U+0628 drawn in isolated, initial, medial and final form.

Recall

Name two normalization forms and one tokenization bug that normalization prevents.

NFC and NFD (or NFKC and NFKD). Without normalization, é stored as U+00E9 and as U+0065 U+0301 tokenize differently, so the same word gets two vocabulary entries and equality and dictionary lookups fail.

Quick check

Which statement correctly separates Unicode from UTF-8?

Quick check

NFC applied to the sequence U+0065 U+0301 produces what?

The Arabic word كتاب has four letters, and Python's len agrees: four. Encode it and count the bytes: eight. The English word "Hi" is two and two. The difference is the whole content of this concept. Every Code point above U+007F needs more than one byte, and the rule that decides how many, and which bits go where, is small enough to learn by heart.

UTF-8 is a variable-length encoding form that maps each Unicode scalar value (a code point that is not a surrogate) to a sequence of one to four bytes. It is defined in the Unicode core specification and, for the Internet, in RFC 3629. Code points below 0x80 take one byte, and most other characters take two, three or four. The slide's mapping table is the exact rule, reproduced here with the payload capacity of each row.

RangeCode point bitsPayload bitsByte 1Byte 2Byte 3Byte 4
U+0000 to U+007F0xxxxxxx70xxxxxxx
U+0080 to U+07FF00000yyy yyxxxxxx11110yyyyy10xxxxxx
U+0800 to U+FFFFzzzzyyyy yyxxxxxx161110zzzz10yyyyyy10xxxxxx
U+010000 to U+10FFFF000uuuuu zzzzyyyy yyxxxxxx2111110uuu10uuzzzz10yyyyyy10xxxxxx
Code point ranges to UTF-8 byte templates (RFC 3629, section 3)

Read the templates as two kinds of byte. The first byte of every sequence is the lead byte, and its run of leading ones announces the length: 0 means one byte, 110 two, 1110 three and 11110 four. Every later byte is a continuation byte and starts 10, leaving six payload bits. The payload capacities are 7, 11, 16 and 21 bits, and the last is no accident: the largest code point U+10FFFF is exactly a 21-bit number, so four bytes suffice for the whole codespace.

n(c)={1c<272c<2113c<2164c10FFFF16n(c) = \begin{cases} 1 & c < 2^{7} \\ 2 & c < 2^{11} \\ 3 & c < 2^{16} \\ 4 & c \le \text{10FFFF}_{16} \end{cases}
Byte count from the code point, using the payload capacity of each row
The 16 bits of U+0628 flow into the two-byte template. Prefix bits 110 and 10 are fixed. The 11 payload bits land in order and spell D8 A8.

Worked example

Encode the Arabic letter beh, U+0628

  1. Write the code point in binary

    0x0628 is 0000 0110 0010 1000. Its value 1576 lies between 0x80 and 0x7FF, so it takes the two-byte row with 11 payload bits.
  2. Take the low 11 bits

    Drop the five leading zeros: 11000 101000. The template is 110yyyyy 10xxxxxx, so yyyyy = 11000 and xxxxxx = 101000.
  3. Fill the templates

    Byte 1 is 110 + 11000 = 11011000 = D8. Byte 2 is 10 + 101000 = 10101000 = A8.
  4. Result

    U+0628 encodes as D8 A8, two bytes.

Worked example

Encode the grinning face emoji, U+1F600

  1. Pick the row

    0x1F600 is above 0xFFFF, so it takes the four-byte row with 21 payload bits: 0 0001 1111 0110 0000 0000.
  2. Split into u, z, y, x groups

    uuuuu = 00001, zzzz = 1111, yyyyyy = 011000, xxxxxx = 000000. The five u bits straddle two bytes: the top three go into byte 1 and the low two into byte 2, exactly as the template 11110uuu 10uuzzzz shows.
  3. Fill the templates

    Byte 1: 11110 + 000 = F0. Byte 2: 10 + 01 + 1111 = 10011111 = 9F. Byte 3: 10 + 011000 = 98. Byte 4: 10 + 000000 = 80.
  4. Result

    U+1F600 encodes as F0 9F 98 80, four bytes.

Worked example

Encode the letter a, U+0061

  1. Pick the row and copy

    0x61 is below 0x80, so the template is 0xxxxxxx and the seven payload bits 1100001 sit under a leading zero: 01100001.
  2. Result

    U+0061 encodes as 61, the same number as its ASCII code. Nothing changed.
CharacterCode pointRangeBytesUTF-8 (hex)
aU+0061U+0000 to U+007F161
éU+00E9U+0080 to U+07FF2C3 A9
بU+0628U+0080 to U+07FF2D8 A8
U+20ACU+0800 to U+FFFF3E2 82 AC
U+4E2DU+0800 to U+FFFF3E4 B8 AD
U+FEFBU+0800 to U+FFFF3EF BB BB
😀U+1F600U+010000 to U+10FFFF4F0 9F 98 80
Seven characters across the four rows

Now do it live. The simulator below encodes whatever you type, one character at a time, and shows the row, the payload bits, the templates and the resulting bytes. It preloads Hi كتاب 😀: nine code points, ten UTF-16 units and sixteen bytes. The € 中 preset exercises the three-byte row, the only one with a 1110 lead byte. Switch the normalization control to NFD with the café preset, or to NFKC with the ligature preset, and watch the byte count change before a single byte is encoded.

SimulatorUTF-8 encoder: from code point to bytes
Code points9charsWhat Python len() counts
UTF-16 units10unitsWhat JavaScript length counts
UTF-8 bytes16bytesWhat is stored and sent
Bytes per char1.78ratio1.00 for pure ASCII
  1. HU+0048
    U+0000 to U+007F
    1 byte, 7 payload bits
    bits 1001000
    01001000
    0xxxxxxx
    48
  2. iU+0069
    U+0000 to U+007F
    1 byte, 7 payload bits
    bits 1101001
    01101001
    0xxxxxxx
    69
  3. U+0020
    U+0000 to U+007F
    1 byte, 7 payload bits
    bits 0100000
    00100000
    0xxxxxxx
    20
  4. كU+0643
    U+0080 to U+07FF
    2 bytes, 11 payload bits
    bits 11001000011
    11011001
    110yyyyy
    10000011
    10xxxxxx
    D9 83
  5. تU+062A
    U+0080 to U+07FF
    2 bytes, 11 payload bits
    bits 11000101010
    11011000
    110yyyyy
    10101010
    10xxxxxx
    D8 AA
  6. اU+0627
    U+0080 to U+07FF
    2 bytes, 11 payload bits
    bits 11000100111
    11011000
    110yyyyy
    10100111
    10xxxxxx
    D8 A7
  7. بU+0628
    U+0080 to U+07FF
    2 bytes, 11 payload bits
    bits 11000101000
    11011000
    110yyyyy
    10101000
    10xxxxxx
    D8 A8
  8. U+0020
    U+0000 to U+007F
    1 byte, 7 payload bits
    bits 0100000
    00100000
    0xxxxxxx
    20
  9. 😀U+1F600
    U+010000 to U+10FFFF
    4 bytes, 21 payload bits
    bits 000011111011000000000
    11110000
    11110uuu
    10011111
    10uuzzzz
    10011000
    10yyyyyy
    10000000
    10xxxxxx
    F0 9F 98 80
U+0000 to U+007F0xxxxxxx
U+0080 to U+07FF110yyyyy 10xxxxxx
U+0800 to U+FFFF1110zzzz 10yyyyyy 10xxxxxx
U+010000 to U+10FFFF11110uuu 10uuzzzz 10yyyyyy 10xxxxxx

Dim cells are the fixed prefix bits of each byte template. Accent cells are the payload bits copied from the code point, left to right. Switch to NFD or NFKC to watch a precomposed letter or a ligature change its byte count before encoding.

Decoding, and why the prefixes matter

The prefixes are what make UTF-8 safe to read from any position. A decoder that sees a byte starting 0 knows it is a complete ASCII character. A byte starting 110, 1110 or 11110 announces one, two or three continuation bytes to follow. A byte starting 10 can never begin a character, so a decoder dropped into the middle of a stream steps backwards at most three bytes to find a lead byte. RFC 3629 lists this as a design property (character boundaries are easily found from anywhere in an octet stream), Jurafsky and Martin call it self-synchronizing, and it is why a corrupted byte damages one character rather than the rest of the file.

Hi ب😀 as nine bytes. Carets mark lead bytes by their prefix, continuation bytes carry 10, and brackets group each character's span.

Worked example

Decode D8 A8 back to a code point

  1. Read the lead byte

    D8 is 11011000. It starts 110, so expect exactly one continuation byte and keep the five bits after the prefix: 11000.
  2. Read the continuation byte

    A8 is 10101000. It starts 10 as required, so keep the six bits after the prefix: 101000.
  3. Concatenate

    11000 + 101000 = 11000101000 = 0x628.
  4. Result

    U+0628, the letter beh. Had the second byte started with anything other than 10, the decoder would report an error at that exact position.

Three consequences follow from the table and are worth stating explicitly. First, backward compatibility with ASCII: every code point below 0x80 becomes the single byte with the same value, so an ASCII file is already a valid UTF-8 file and a UTF-8 file that happens to contain only ASCII is byte for byte an ASCII file. Second, bytes below 0x80 never appear inside a multibyte sequence, so a program that searches for a space or a newline byte will never find one in the middle of an Arabic letter. Third, some byte values are simply impossible: C0, C1 and F5 to FF never occur in valid UTF-8, because they would encode overlong sequences or code points past U+10FFFF (RFC 3629, section 1, with the syntax that rules them out in section 4).

Recall

How many UTF-8 bytes do U+0628, U+00E9, U+4E2D and U+1F600 need, and why?

2, 2, 3 and 4. The first two fall in U+0080 to U+07FF, the third in U+0800 to U+FFFF, and the fourth in U+010000 to U+10FFFF.

Recall

Encode ñ, U+00F1, by hand.

0x00F1 is 0000 0000 1111 0001, in the two-byte row. Payload 00011 110001, so byte 1 is 110 + 00011 = 11000011 = C3 and byte 2 is 10 + 110001 = 10110001 = B1. Result C3 B1.

Recall

Why is UTF-8 backward compatible with ASCII, and how does a decoder find where a character starts?

Code points below 0x80 map to the single byte 0xxxxxxx with the same value, so an ASCII file is unchanged. Lead bytes begin 0, 110, 1110 or 11110 and continuation bytes always begin 10, so a byte starting 10 can never be a start and any position resynchronizes within three bytes.

Quick check

How many bytes does UTF-8 use for the Arabic letter beh, U+0628?

Quick check

A decoder reads a byte whose top bits are 10. What does it conclude?

A tokenizer trained with a character vocabulary in 2020 meets the melting face emoji 🫠, added as U+1FAE0 in Unicode 14 the following year. It has never seen the character, so it emits the unknown token. The same happens with an Arabizi spelling like "ktaab" typed into a word-level vocabulary, or any typo, any new script, any rare Han character. Now imagine the base symbols were bytes instead. Every UTF-8 byte is one of exactly 256 values, all of them already in the vocabulary, so there is nothing left to be unknown.

That is Byte-level tokenization: treat the UTF-8 bytes of the text, not its characters, as the atoms. Radford and colleagues motivated it in the GPT-2 paper with the numbers. A Code point base vocabulary would start at over 130,000 symbols before a single merge (about 160,000 in Unicode 17), while a byte-level base needs only 256. Jurafsky and Martin draw the consequence: because there are only 256 possible byte values, there will be no unknown tokens, and the <UNK> symbol becomes unnecessary. Arbitrary scripts, emoji and typos all decompose into bytes the model has seen.

PropertyCharacter vocabularyByte vocabulary
Base symbolsAbout 160,000 assigned characters (over 130,000 when GPT-2 was written)Exactly 256 byte values
Unknown input possibleYes, any unseen characterNo, every byte is in the base
Base units for كتاب4 characters8 bytes
Interpretability of a base unitA letter you can readHalf of an Arabic letter
StructureGiven by the scriptRecovered by learned merges
Character base versus byte base

The price is structure. Bytes do not know where a character ends. The word كتاب becomes eight opaque symbols, and the letter ب is D8 A8, two units that mean nothing on their own. A sentence of Arabic is roughly twice as many base units as Latin text of the same length, which the simulator's كتاب preset shows as 4 characters against 8 bytes. Left alone, a byte model would spend its capacity relearning the UTF-8 table. So bytes are never used alone: they are combined with learned merges that glue frequent byte sequences back into characters, then into subwords. Jurafsky and Martin note that BPE over bytes rediscovers the two- and three-byte UTF-8 sequences early, and that it can also learn invalid sequences straddling a character boundary, which implementations filter out. GPT-2 adds a rule that prevents merges across character categories, because naive byte merges were sub-optimal.

Recall

What does Python return for len of the string كتاب, and for len of its UTF-8 encoding? Which number does a byte-level tokenizer start from?

4 and 8. Python's len counts code points, the encoded form has two bytes per letter, and a byte-level tokenizer starts from the eight bytes.

Quick check

How large is the base vocabulary of a byte-level tokenizer before any merges?

Recap

If you remember nothing else

  • A corpus is a situated sample that varies by domain, genre, time, demographics and variety. Models inherit its limits, so document it with a data statement or datasheet.
  • Datasheet fields: motivation and intended use, composition, collection process, preprocessing and annotation, ethical considerations, distribution constraints.
  • A code point is an integer from U+0000 to U+10FFFF. A glyph is the drawn shape. The two map many to many.
  • ASCII is the first 128 code points, and upper and lower case letters differ by exactly 0x20.
  • NFC and NFD make é and e plus U+0301 compare and tokenize alike. Only NFKC and NFKD also fold the ligature ﻻ into ل plus ا.
  • UTF-8 uses 1, 2, 3 or 4 bytes with lead patterns 0, 110, 1110, 11110 and continuation bytes 10xxxxxx. Payloads hold 7, 11, 16 and 21 bits.
  • U+0628 is D8 A8, U+00E9 is C3 A9, U+4E2D is E4 B8 AD, U+1F600 is F0 9F 98 80, and a is 61.
  • String length counts code points, not bytes: كتاب is 4 code points and 8 bytes.
  • Bytes give a 256-symbol base with no unknown character. Learned merges restore the structure that bytes lose.

Sources