Livt packages

Eccelerators.Tokenization

Version 0.2.0

Bounded BPE and Unigram tokenization, text normalization, and decoding components for Livt hardware.

Published files (27)

tests/Gpt2/Gpt2BpeTokenizer64Test.lvt

namespace Eccelerators.Tokenization.Tests.Gpt2

using Eccelerators.Tokenization.Bpe
using Eccelerators.Tokenization.Gpt2

/**
 * Minimal GPT-2 vocabulary fixture for ASCII conformance vectors.
 */
component Gpt2VocabularyFixture : IGpt2Vocabulary
{
	// Final GPT-2 token IDs used by both tokenizer and decoder tests.
	public const TOKEN_EXCLAMATION: int = 0
	public const TOKEN_COMMA: int = 11
	public const TOKEN_WORLD_WITH_LEADING_SPACE: int = 995
	public const TOKEN_HELLO: int = 15496
	public const TOKEN_END_OF_TEXT: int = 50256

	// Initial GPT-2 byte-level tokens needed by the boundary fixtures.
	public const TOKEN_BYTE_APOSTROPHE: int = 6
	public const TOKEN_BYTE_A: int = 64
	public const TOKEN_BYTE_D: int = 67
	public const TOKEN_BYTE_E: int = 68
	public const TOKEN_BYTE_H: int = 39
	public const TOKEN_BYTE_I: int = 72
	public const TOKEN_BYTE_L: int = 75
	public const TOKEN_BYTE_M: int = 76
	public const TOKEN_BYTE_O: int = 78
	public const TOKEN_BYTE_R: int = 81
	public const TOKEN_BYTE_S: int = 82
	public const TOKEN_BYTE_T: int = 83
	public const TOKEN_BYTE_V: int = 85
	public const TOKEN_BYTE_W: int = 86
	public const TOKEN_BYTE_UPPERCASE_S: int = 50
	public const TOKEN_BYTE_DIGIT_ONE: int = 16
	public const TOKEN_BYTE_DIGIT_TWO: int = 17
	public const TOKEN_BYTE_TAB: int = 197
	public const TOKEN_BYTE_LINE_FEED: int = 198
	public const TOKEN_BYTE_SPACE: int = 220

	// Literal ASCII values and fixture geometry.
	const ASCII_TAB: byte = 0x09
	const ASCII_LINE_FEED: byte = 0x0a
	const ASCII_SPACE: byte = 0x20
	const PRINTABLE_ASCII_FIRST: byte = 0x21
	const PRINTABLE_ASCII_LAST: byte = 0x7e
	const PRINTABLE_TOKEN_OFFSET: int = 33
	const TOKEN_HELLO_LENGTH: int = 5
	const TOKEN_WORLD_LENGTH: int = 6
	const TOKEN_PUNCTUATION_LENGTH: int = 1
	const TOKEN_HELLO_BYTES: byte[5] = "Hello".Encode()
	const TOKEN_WORLD_BYTES: byte[6] = " world".Encode()
	const ASCII_COMMA: byte = 0x2c
	const ASCII_EXCLAMATION: byte = 0x21

	// Retained lookup results consumed by the tokenizer/decoder fixture clients.
	byteFound: bool
	byteToken: int
	selectedToken: int
	tokenFound: bool

	/**
	 * Maps printable ASCII through the corresponding GPT-2 base symbols.
	 */
	override fn LookupByte(value: byte)
	{
		byteFound = false
		byteToken = 0

		if (value == ASCII_TAB) {
			byteFound = true
			byteToken = TOKEN_BYTE_TAB
			return
		}

		if (value == ASCII_LINE_FEED) {
			byteFound = true
			byteToken = TOKEN_BYTE_LINE_FEED
			return
		}

		if (value == ASCII_SPACE) {
			byteFound = true
			byteToken = TOKEN_BYTE_SPACE
			return
		}

		if (value >= PRINTABLE_ASCII_FIRST && value <= PRINTABLE_ASCII_LAST) {
			byteFound = true
			byteToken = (value as int) - PRINTABLE_TOKEN_OFFSET
		}
	}

	/**
	 * Returns whether the last byte is represented by this fixture.
	 */
	override fn HasByteToken() bool
	{
		return byteFound
	}

	/**
	 * Returns the initial token for the last byte lookup.
	 */
	override fn GetByteToken() int
	{
		return byteToken
	}

	/**
	 * Selects one complete token for byte-oriented decoding.
	 */
	override fn ReadToken(token: int)
	{
		selectedToken = token
		tokenFound = token == TOKEN_HELLO || token == TOKEN_COMMA || token == TOKEN_WORLD_WITH_LEADING_SPACE || token == TOKEN_EXCLAMATION
	}

	/**
	 * Returns whether the selected token is part of this fixture.
	 */
	override fn HasToken() bool
	{
		return tokenFound
	}

	/**
	 * Returns the decoded byte count for the selected token.
	 */
	override fn GetTokenLength() int
	{
		if (selectedToken == TOKEN_HELLO) {
			return TOKEN_HELLO_LENGTH
		}

		if (selectedToken == TOKEN_WORLD_WITH_LEADING_SPACE) {
			return TOKEN_WORLD_LENGTH
		}

		if (selectedToken == TOKEN_COMMA || selectedToken == TOKEN_EXCLAMATION) {
			return TOKEN_PUNCTUATION_LENGTH
		}

		return 0
	}

	/**
	 * Returns one original byte from the selected token.
	 */
	override fn GetTokenByte(index: int) byte
	{
		if (selectedToken == TOKEN_HELLO && index >= 0 && index < TOKEN_HELLO_LENGTH) {
			return TOKEN_HELLO_BYTES[index]
		}

		if (selectedToken == TOKEN_WORLD_WITH_LEADING_SPACE && index >= 0 && index < TOKEN_WORLD_LENGTH) {
			return TOKEN_WORLD_BYTES[index]
		}

		if (selectedToken == TOKEN_COMMA && index == 0) {
			return ASCII_COMMA
		}

		if (selectedToken == TOKEN_EXCLAMATION && index == 0) {
			return ASCII_EXCLAMATION
		}

		return 0x00
	}
}

/**
 * Minimal ranked merge fixture producing the pinned GPT-2 example tokens.
 */
component Gpt2MergeFixture : IBpeMergeTable
{
	// Final synthetic contraction tokens exposed to the tokenizer tests.
	public const TOKEN_CONTRACTION_S: int = 910
	public const TOKEN_CONTRACTION_T: int = 911
	public const TOKEN_CONTRACTION_M: int = 912
	public const TOKEN_CONTRACTION_D: int = 913
	public const TOKEN_CONTRACTION_RE: int = 921
	public const TOKEN_CONTRACTION_VE: int = 923
	public const TOKEN_CONTRACTION_LL: int = 925
	public const TOKEN_CONTRACTION_UPPERCASE_S: int = 930

	// Synthetic intermediate tokens make every fixture merge step readable.
	const TOKEN_HE: int = 300
	const TOKEN_HEL: int = 301
	const TOKEN_HELL: int = 302
	const TOKEN_SPACE_W: int = 400
	const TOKEN_SPACE_WO: int = 401
	const TOKEN_SPACE_WOR: int = 402
	const TOKEN_SPACE_WORL: int = 403
	const TOKEN_CROSS_CONTRACTION_BOUNDARY: int = 900
	const TOKEN_CROSS_SPACE_BOUNDARY: int = 901
	const TOKEN_CROSS_TAB_BOUNDARY: int = 902
	const TOKEN_CONTRACTION_APOSTROPHE_R: int = 920
	const TOKEN_CONTRACTION_APOSTROPHE_V: int = 922
	const TOKEN_CONTRACTION_APOSTROPHE_L: int = 924
	const MERGE_RANK_FIRST: int = 0
	const MERGE_RANK_SECOND: int = 1
	const MERGE_RANK_THIRD: int = 2
	const MERGE_RANK_FOURTH: int = 3
	const MERGE_RANK_FIFTH: int = 4

	// Retained merge-rule result from the last lookup.
	found: bool
	rank: int
	mergedToken: int

	/**
	 * Looks up the merges needed for `Hello, world!` and boundary checks.
	 */
	override fn Lookup(leftToken: int, rightToken: int)
	{
		found = false
		rank = 0
		mergedToken = 0

		if (leftToken == Gpt2VocabularyFixture.TOKEN_BYTE_H && rightToken == Gpt2VocabularyFixture.TOKEN_BYTE_E) {
			SetMatch(MERGE_RANK_FIRST, TOKEN_HE)
		}

		if (leftToken == TOKEN_HE && rightToken == Gpt2VocabularyFixture.TOKEN_BYTE_L) {
			SetMatch(MERGE_RANK_SECOND, TOKEN_HEL)
		}

		if (leftToken == TOKEN_HEL && rightToken == Gpt2VocabularyFixture.TOKEN_BYTE_L) {
			SetMatch(MERGE_RANK_THIRD, TOKEN_HELL)
		}

		if (leftToken == TOKEN_HELL && rightToken == Gpt2VocabularyFixture.TOKEN_BYTE_O) {
			SetMatch(MERGE_RANK_FOURTH, Gpt2VocabularyFixture.TOKEN_HELLO)
		}

		if (leftToken == Gpt2VocabularyFixture.TOKEN_BYTE_SPACE && rightToken == Gpt2VocabularyFixture.TOKEN_BYTE_W) {
			SetMatch(MERGE_RANK_FIRST, TOKEN_SPACE_W)
		}

		if (leftToken == TOKEN_SPACE_W && rightToken == Gpt2VocabularyFixture.TOKEN_BYTE_O) {
			SetMatch(MERGE_RANK_SECOND, TOKEN_SPACE_WO)
		}

		if (leftToken == TOKEN_SPACE_WO && rightToken == Gpt2VocabularyFixture.TOKEN_BYTE_R) {
			SetMatch(MERGE_RANK_THIRD, TOKEN_SPACE_WOR)
		}

		if (leftToken == TOKEN_SPACE_WOR && rightToken == Gpt2VocabularyFixture.TOKEN_BYTE_L) {
			SetMatch(MERGE_RANK_FOURTH, TOKEN_SPACE_WORL)
		}

		if (leftToken == TOKEN_SPACE_WORL && rightToken == Gpt2VocabularyFixture.TOKEN_BYTE_D) {
			SetMatch(MERGE_RANK_FIFTH, Gpt2VocabularyFixture.TOKEN_WORLD_WITH_LEADING_SPACE)
		}

		// These rules must not match because GPT-2 pre-tokenization separates the pairs.
		if (leftToken == Gpt2VocabularyFixture.TOKEN_BYTE_T && rightToken == Gpt2VocabularyFixture.TOKEN_BYTE_APOSTROPHE) {
			SetMatch(MERGE_RANK_FIRST, TOKEN_CROSS_CONTRACTION_BOUNDARY)
		}

		if (leftToken == Gpt2VocabularyFixture.TOKEN_BYTE_SPACE && rightToken == Gpt2VocabularyFixture.TOKEN_BYTE_SPACE) {
			SetMatch(MERGE_RANK_FIRST, TOKEN_CROSS_SPACE_BOUNDARY)
		}

		if (leftToken == Gpt2VocabularyFixture.TOKEN_BYTE_TAB && rightToken == Gpt2VocabularyFixture.TOKEN_BYTE_A) {
			SetMatch(MERGE_RANK_FIRST, TOKEN_CROSS_TAB_BOUNDARY)
		}

		if (leftToken == Gpt2VocabularyFixture.TOKEN_BYTE_APOSTROPHE && rightToken == Gpt2VocabularyFixture.TOKEN_BYTE_S) {
			SetMatch(MERGE_RANK_FIRST, TOKEN_CONTRACTION_S)
		}

		if (leftToken == Gpt2VocabularyFixture.TOKEN_BYTE_APOSTROPHE && rightToken == Gpt2VocabularyFixture.TOKEN_BYTE_UPPERCASE_S) {
			SetMatch(MERGE_RANK_FIRST, TOKEN_CONTRACTION_UPPERCASE_S)
		}

		if (leftToken == Gpt2VocabularyFixture.TOKEN_BYTE_APOSTROPHE && rightToken == Gpt2VocabularyFixture.TOKEN_BYTE_T) {
			SetMatch(MERGE_RANK_FIRST, TOKEN_CONTRACTION_T)
		}

		if (leftToken == Gpt2VocabularyFixture.TOKEN_BYTE_APOSTROPHE && rightToken == Gpt2VocabularyFixture.TOKEN_BYTE_M) {
			SetMatch(MERGE_RANK_FIRST, TOKEN_CONTRACTION_M)
		}

		if (leftToken == Gpt2VocabularyFixture.TOKEN_BYTE_APOSTROPHE && rightToken == Gpt2VocabularyFixture.TOKEN_BYTE_D) {
			SetMatch(MERGE_RANK_FIRST, TOKEN_CONTRACTION_D)
		}

		if (leftToken == Gpt2VocabularyFixture.TOKEN_BYTE_APOSTROPHE && rightToken == Gpt2VocabularyFixture.TOKEN_BYTE_R) {
			SetMatch(MERGE_RANK_FIRST, TOKEN_CONTRACTION_APOSTROPHE_R)
		}

		if (leftToken == TOKEN_CONTRACTION_APOSTROPHE_R && rightToken == Gpt2VocabularyFixture.TOKEN_BYTE_E) {
			SetMatch(MERGE_RANK_SECOND, TOKEN_CONTRACTION_RE)
		}

		if (leftToken == Gpt2VocabularyFixture.TOKEN_BYTE_APOSTROPHE && rightToken == Gpt2VocabularyFixture.TOKEN_BYTE_V) {
			SetMatch(MERGE_RANK_FIRST, TOKEN_CONTRACTION_APOSTROPHE_V)
		}

		if (leftToken == TOKEN_CONTRACTION_APOSTROPHE_V && rightToken == Gpt2VocabularyFixture.TOKEN_BYTE_E) {
			SetMatch(MERGE_RANK_SECOND, TOKEN_CONTRACTION_VE)
		}

		if (leftToken == Gpt2VocabularyFixture.TOKEN_BYTE_APOSTROPHE && rightToken == Gpt2VocabularyFixture.TOKEN_BYTE_L) {
			SetMatch(MERGE_RANK_FIRST, TOKEN_CONTRACTION_APOSTROPHE_L)
		}

		if (leftToken == TOKEN_CONTRACTION_APOSTROPHE_L && rightToken == Gpt2VocabularyFixture.TOKEN_BYTE_L) {
			SetMatch(MERGE_RANK_SECOND, TOKEN_CONTRACTION_LL)
		}
	}

	/**
	 * Returns whether the last pair has a merge rule.
	 */
	override fn HasMerge() bool
	{
		return found
	}

	/**
	 * Returns the rank of the last matching rule.
	 */
	override fn GetRank() int
	{
		return rank
	}

	/**
	 * Returns the token produced by the last matching rule.
	 */
	override fn GetMergedToken() int
	{
		return mergedToken
	}

	/**
	 * Stores one fixture match.
	 */
	fn SetMatch(matchRank: int, resultToken: int)
	{
		found = true
		rank = matchRank
		mergedToken = resultToken
	}
}

/**
 * Verifies bounded GPT-2 ASCII pre-tokenization and BPE composition.
 */
@Test
component Gpt2BpeTokenizer64Test
{
	const HELLO_WORLD_INPUT_LENGTH: int = 13
	const HELLO_WORLD_TOKEN_COUNT: int = 4
	const CONTRACTION_INPUT_LENGTH: int = 4
	const CONTRACTION_TOKEN_COUNT: int = 3
	const REPEATED_SPACE_INPUT_LENGTH: int = 3
	const REPEATED_SPACE_TOKEN_COUNT: int = 3
	const TAB_INPUT_LENGTH: int = 2
	const TAB_TOKEN_COUNT: int = 2
	const TRAILING_WHITESPACE_INPUT_LENGTH: int = 3
	const TRAILING_WHITESPACE_TOKEN_COUNT: int = 3
	const CLASS_RUN_INPUT_LENGTH: int = 4
	const CLASS_RUN_TOKEN_COUNT: int = 4
	// Literal ASCII values and fixture geometry.
	const ASCII_TAB: byte = 0x09
	const ASCII_LINE_FEED: byte = 0x0a
	const ASCII_SPACE: byte = 0x20
	const ASCII_APOSTROPHE: byte = 0x27
	const ASCII_D: byte = 0x64
	const ASCII_E: byte = 0x65
	const ASCII_L: byte = 0x6c
	const ASCII_M: byte = 0x6d
	const ASCII_R: byte = 0x72
	const ASCII_S: byte = 0x73
	const ASCII_T: byte = 0x74
	const ASCII_V: byte = 0x76
	const ASCII_A: byte = 0x61
	const ASCII_UPPERCASE_S: byte = 0x53
	const HELLO_WORLD_INPUT: byte[13] = "Hello, world!".Encode()
	const HELLO_WORLD_TOKENS: int[4] = [
		Gpt2VocabularyFixture.TOKEN_HELLO,
		Gpt2VocabularyFixture.TOKEN_COMMA,
		Gpt2VocabularyFixture.TOKEN_WORLD_WITH_LEADING_SPACE,
		Gpt2VocabularyFixture.TOKEN_EXCLAMATION,]
	const CONTRACTION_INPUT: byte[4] = "it's".Encode()
	const CONTRACTION_TOKENS: int[3] = [
		Gpt2VocabularyFixture.TOKEN_BYTE_I,
		Gpt2VocabularyFixture.TOKEN_BYTE_T,
		Gpt2MergeFixture.TOKEN_CONTRACTION_S,]
	const REPEATED_SPACE_INPUT: byte[3] = "  a".Encode()
	const REPEATED_SPACE_TOKENS: int[3] = [
		Gpt2VocabularyFixture.TOKEN_BYTE_SPACE,
		Gpt2VocabularyFixture.TOKEN_BYTE_SPACE,
		Gpt2VocabularyFixture.TOKEN_BYTE_A,]
	const TAB_INPUT: byte[2] = [ASCII_TAB, ASCII_A]
	const TAB_TOKENS: int[2] = [
		Gpt2VocabularyFixture.TOKEN_BYTE_TAB,
		Gpt2VocabularyFixture.TOKEN_BYTE_A,]
	const TRAILING_WHITESPACE_INPUT: byte[3] = [ASCII_A, ASCII_SPACE, ASCII_LINE_FEED]
	const TRAILING_WHITESPACE_TOKENS: int[3] = [
		Gpt2VocabularyFixture.TOKEN_BYTE_A,
		Gpt2VocabularyFixture.TOKEN_BYTE_SPACE,
		Gpt2VocabularyFixture.TOKEN_BYTE_LINE_FEED,]
	const CLASS_RUN_INPUT: byte[4] = "12!!".Encode()
	const CLASS_RUN_TOKENS: int[4] = [
		Gpt2VocabularyFixture.TOKEN_BYTE_DIGIT_ONE,
		Gpt2VocabularyFixture.TOKEN_BYTE_DIGIT_TWO,
		Gpt2VocabularyFixture.TOKEN_EXCLAMATION,
		Gpt2VocabularyFixture.TOKEN_EXCLAMATION,]
	const UNKNOWN_ASCII_INPUT: byte = 0x00
	const NON_ASCII_INPUT: byte = 0x80

	// Owned deterministic tables and tokenizer; each scenario resets the tokenizer.
	vocabulary: Gpt2VocabularyFixture
	mergeTable: Gpt2MergeFixture
	tokenizer: Gpt2BpeTokenizer64

	/**
	 * Connects the tokenizer to deterministic vocabulary and merge fixtures.
	 */
	new()
	{
		this.vocabulary = new Gpt2VocabularyFixture()
		this.mergeTable = new Gpt2MergeFixture()
		this.tokenizer = new Gpt2BpeTokenizer64(this.vocabulary, this.mergeTable)
	}

	/**
	 * Verifies the standard GPT-2 ASCII conformance example.
	 */
	@Test
	fn TokenizesHelloWorld()
	{
		this.tokenizer.Reset()

		for (var index = 0; index < HELLO_WORLD_INPUT_LENGTH; index++) {
			assert this.tokenizer.AcceptAscii(HELLO_WORLD_INPUT[index]) == true
		}

		this.tokenizer.Tokenize()

		assert this.tokenizer.HasError() == false
		assert this.tokenizer.GetTokenCount() == HELLO_WORLD_TOKEN_COUNT

		for (var index = 0; index < HELLO_WORLD_TOKEN_COUNT; index++) {
			assert this.tokenizer.GetToken(index) == HELLO_WORLD_TOKENS[index]
		}
	}

	/**
	 * Verifies that contraction and leading-space boundaries prevent cross merges.
	 */
	@Test
	fn PreservesGpt2PieceBoundaries()
	{
		this.tokenizer.Reset()

		for (var index = 0; index < CONTRACTION_INPUT_LENGTH; index++) {
			assert this.tokenizer.AcceptAscii(CONTRACTION_INPUT[index]) == true
		}

		this.tokenizer.Tokenize()
		assert this.tokenizer.GetTokenCount() == CONTRACTION_TOKEN_COUNT

		for (var index = 0; index < CONTRACTION_TOKEN_COUNT; index++) {
			assert this.tokenizer.GetToken(index) == CONTRACTION_TOKENS[index]
		}

		this.tokenizer.Reset()

		for (var index = 0; index < REPEATED_SPACE_INPUT_LENGTH; index++) {
			assert this.tokenizer.AcceptAscii(REPEATED_SPACE_INPUT[index]) == true
		}

		this.tokenizer.Tokenize()
		assert this.tokenizer.GetTokenCount() == REPEATED_SPACE_TOKEN_COUNT

		for (var index = 0; index < REPEATED_SPACE_TOKEN_COUNT; index++) {
			assert this.tokenizer.GetToken(index) == REPEATED_SPACE_TOKENS[index]
		}
	}

	/**
	 * Verifies that non-space whitespace cannot prefix a following text run.
	 */
	@Test
	fn KeepsTabSeparateFromFollowingText()
	{
		this.tokenizer.Reset()

		for (var index = 0; index < TAB_INPUT_LENGTH; index++) {
			assert this.tokenizer.AcceptAscii(TAB_INPUT[index]) == true
		}

		this.tokenizer.Tokenize()

		assert this.tokenizer.HasError() == false
		assert this.tokenizer.GetTokenCount() == TAB_TOKEN_COUNT

		for (var index = 0; index < TAB_TOKEN_COUNT; index++) {
			assert this.tokenizer.GetToken(index) == TAB_TOKENS[index]
		}
	}

	/**
	 * Verifies empty input and invalid output indexes without reporting failure.
	 */
	@Test
	fn HandlesEmptyInputAndInvalidIndexes()
	{
		this.tokenizer.Reset()
		this.tokenizer.Tokenize()

		assert this.tokenizer.HasError() == false
		assert this.tokenizer.GetInputLength() == 0
		assert this.tokenizer.GetTokenCount() == 0
		assert this.tokenizer.GetToken(-1) == 0
		assert this.tokenizer.GetToken(0) == 0
	}

	/**
	 * Verifies the exact input bound, overflow rejection, and Reset recovery.
	 */
	@Test
	fn EnforcesInputCapacityAndRecoversAfterReset()
	{
		this.tokenizer.Reset()

		for (var index = 0; index < Gpt2BpeTokenizer64.MAX_INPUT_BYTES; index++) {
			assert this.tokenizer.AcceptAscii(ASCII_A) == true
		}

		assert this.tokenizer.GetInputLength() == Gpt2BpeTokenizer64.MAX_INPUT_BYTES
		this.tokenizer.Tokenize()
		assert this.tokenizer.HasError() == false
		assert this.tokenizer.GetTokenCount() == Gpt2BpeTokenizer64.MAX_OUTPUT_TOKENS

		assert this.tokenizer.AcceptAscii(ASCII_A) == false
		assert this.tokenizer.HasError() == true
		this.tokenizer.Tokenize()
		assert this.tokenizer.GetTokenCount() == 0

		this.tokenizer.Reset()
		assert this.tokenizer.AcceptAscii(ASCII_A) == true
		this.tokenizer.Tokenize()
		assert this.tokenizer.HasError() == false
		assert this.tokenizer.GetTokenCount() == 1
		assert this.tokenizer.GetToken(0) == Gpt2VocabularyFixture.TOKEN_BYTE_A
	}

	/**
	 * Verifies trailing whitespace plus separate number and punctuation runs.
	 */
	@Test
	fn TokenizesWhitespaceAndCharacterClassRuns()
	{
		this.tokenizer.Reset()

		for (var index = 0; index < TRAILING_WHITESPACE_INPUT_LENGTH; index++) {
			assert this.tokenizer.AcceptAscii(TRAILING_WHITESPACE_INPUT[index]) == true
		}

		this.tokenizer.Tokenize()
		assert this.tokenizer.GetTokenCount() == TRAILING_WHITESPACE_TOKEN_COUNT

		for (var index = 0; index < TRAILING_WHITESPACE_TOKEN_COUNT; index++) {
			assert this.tokenizer.GetToken(index) == TRAILING_WHITESPACE_TOKENS[index]
		}

		this.tokenizer.Reset()

		for (var index = 0; index < CLASS_RUN_INPUT_LENGTH; index++) {
			assert this.tokenizer.AcceptAscii(CLASS_RUN_INPUT[index]) == true
		}

		this.tokenizer.Tokenize()
		assert this.tokenizer.GetTokenCount() == CLASS_RUN_TOKEN_COUNT

		for (var index = 0; index < CLASS_RUN_TOKEN_COUNT; index++) {
			assert this.tokenizer.GetToken(index) == CLASS_RUN_TOKENS[index]
		}
	}

	/**
	 * Verifies all case-insensitive contraction alternatives from GPT-2.
	 */
	@Test
	fn RecognizesAllContractionAlternatives()
	{
		AssertTwoByteContraction(ASCII_S, Gpt2MergeFixture.TOKEN_CONTRACTION_S)
		AssertTwoByteContraction(ASCII_T, Gpt2MergeFixture.TOKEN_CONTRACTION_T)
		AssertTwoByteContraction(ASCII_M, Gpt2MergeFixture.TOKEN_CONTRACTION_M)
		AssertTwoByteContraction(ASCII_D, Gpt2MergeFixture.TOKEN_CONTRACTION_D)
		AssertThreeByteContraction(
			ASCII_R, ASCII_E, Gpt2MergeFixture.TOKEN_CONTRACTION_RE)
		AssertThreeByteContraction(
			ASCII_V, ASCII_E, Gpt2MergeFixture.TOKEN_CONTRACTION_VE)
		AssertThreeByteContraction(
			ASCII_L, ASCII_L, Gpt2MergeFixture.TOKEN_CONTRACTION_LL)
		AssertTwoByteContraction(
			ASCII_UPPERCASE_S, Gpt2MergeFixture.TOKEN_CONTRACTION_UPPERCASE_S)
	}

	/**
	 * Verifies missing byte mappings and non-ASCII input are rejected.
	 */
	@Test
	fn RejectsInvalidInputAndRecoversAfterReset()
	{
		this.tokenizer.Reset()
		assert this.tokenizer.AcceptAscii(UNKNOWN_ASCII_INPUT) == true
		this.tokenizer.Tokenize()
		assert this.tokenizer.HasError() == true
		assert this.tokenizer.GetTokenCount() == 0

		this.tokenizer.Reset()
		assert this.tokenizer.AcceptAscii(NON_ASCII_INPUT) == false
		assert this.tokenizer.HasError() == true
		this.tokenizer.Tokenize()
		assert this.tokenizer.GetTokenCount() == 0

		this.tokenizer.Reset()
		assert this.tokenizer.HasError() == false
	}

	/**
	 * Verifies one two-byte contraction against the synthetic merge table.
	 */
	fn AssertTwoByteContraction(second: byte, expectedToken: int)
	{
		this.tokenizer.Reset()
		assert this.tokenizer.AcceptAscii(ASCII_APOSTROPHE) == true
		assert this.tokenizer.AcceptAscii(second) == true
		this.tokenizer.Tokenize()
		assert this.tokenizer.HasError() == false
		assert this.tokenizer.GetTokenCount() == 1
		assert this.tokenizer.GetToken(0) == expectedToken
	}

	/**
	 * Verifies one three-byte contraction against the synthetic merge table.
	 */
	fn AssertThreeByteContraction(second: byte, third: byte, expectedToken: int)
	{
		this.tokenizer.Reset()
		assert this.tokenizer.AcceptAscii(ASCII_APOSTROPHE) == true
		assert this.tokenizer.AcceptAscii(second) == true
		assert this.tokenizer.AcceptAscii(third) == true
		this.tokenizer.Tokenize()
		assert this.tokenizer.HasError() == false
		assert this.tokenizer.GetTokenCount() == 1
		assert this.tokenizer.GetToken(0) == expectedToken
	}
}