اگر فایلِ واژگانِ یک توکن‌ساز را نگه داریم، آیا می‌توانیم همان توکن‌های قبلی را دوباره بسازیم؟ در رمزگذاریِ مبتنی بر ادغامِ زوج‌ها، پاسخ لزوماً مثبت نیست. ترتیبِ قواعد هم بخشی از قرارداد است. این درس، یک ورودیِ کوتاه و ازپیش‌جداشده را با قواعدِ داده‌شده می‌شکند؛ آموزشِ توکن‌ساز، اجرای مدل و سنجشِ کیفیتِ زبان در آن وجود ندارد. هدف، دیدنِ دقیقِ تفاوتِ «قطعهٔ موجود» با «اتصالِ دارایِ تقدم» است.

۱. الفبا، رتبهٔ ادغام و یک ورودیِ ازپیش‌جداشده

قطعه‌های پایهٔ جدا در کنار یک رشتهٔ آمادهٔ اتصال
تصویر مفهومی: قطعه‌های پایهٔ جدا در کنار یک رشتهٔ آمادهٔ اتصال؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

در فصلِ کاملِ BPE در دورهٔ Hugging Face، آموزش با نمادهای پایه آغاز می‌شود و زوج‌های پرتکرار را ادغام می‌کند. برای رمزگذاریِ ورودیِ تازه، قواعدِ آموخته‌شده به ترتیب اعمال می‌شوند. ما مرحلهٔ آموزش را اجرا نمی‌کنیم؛ الفبا و فهرستِ مرتبِ قواعد، ورودیِ صریحِ برنامه‌اند. رتبه، همان جایگاهِ قاعده در این فهرست است، نه فراوانیِ تازه‌ای که هنگامِ رمزگذاری حساب شود.

متن، str دقیق با صفر تا هشت نقطهٔ کد است. الفبا، tuple دقیقِ حداکثر هشت نمادِ یکتای تک‌نقطه‌ای دارد. قواعد نیز tuple دقیقِ حداکثر هشت زوج‌اند. هر جزءِ زوج باید از الفبا یا حاصلِ قاعده‌ای زودتر باشد. چسباندنِ دو جزء باید توکنی تازه با حداکثر هشت نقطهٔ کد بسازد؛ خروجیِ تکراری و ارجاع به توکنِ آینده رد می‌شوند.

این قیود، قالبِ کوچکِ تألیفیِ ما هستند؛ از آن‌ها نتیجه نمی‌گیریم که هر فهرستِ معتبر حتماً با بیشترین فراوانی از پیکره‌ای آموخته شده است. فاصلهٔ سفید، چند بازهٔ کنترلی و جانشین‌های یونیکد در الفبا رد می‌شوند؛ نیم‌فاصله حفظ می‌شود. این انتخابِ محدود، تضمینِ امنیتِ متن نیست. نمادِ ناشناخته هم خطاست و خودکار به توکنِ مجهول تبدیل نمی‌شود.

تابع فقط یک قطعهٔ آماده را می‌گیرد. تقسیمِ جمله به واژه یا قطعه‌های پیش‌پردازش، تشخیصِ زبان و سیاستِ علامت‌های ویژه بیرونِ رابط‌اند. خروجی، تاپلِ توکن‌ها، پایانِ برش‌ها و شمارِ اتصال‌هاست. چسباندنِ توکن‌ها باید عینِ متنِ اولیه باشد؛ واحدِ پایان‌ها نقطهٔ کد است و آخرین پایانِ متنِ ناتهی، طولِ همان متن خواهد بود.

۲. واژگانِ یکسان می‌توانند برشِ متفاوت بدهند

دو نوارِ همسان با اتصالِ متفاوتِ جفت‌های مجاور
تصویر مفهومی: دو نوارِ همسان با اتصالِ متفاوتِ جفت‌های مجاور؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

الفبای a، b و c را بگیرید. یک فهرست ابتدا زوجِ a و b و سپس زوجِ b و c را ادغام می‌کند؛ فهرستِ دیگر همین دو قاعده را برعکس دارد. هر دو، مجموعهٔ توکن‌های a، b، c، ab و bc را می‌سازند. با این حال، روی abc، فهرستِ اول ab و c می‌دهد و فهرستِ دوم a و bc؛ پس موجودیِ نهاییِ واژگان، رتبهٔ قواعد را تعیین نمی‌کند.

در حالتِ دوم، وجودِ ab در واژگان مجوز نمی‌دهد که از ابتدای متن بلندترین تطبیق را برداریم. قاعدهٔ bc زودتر عمل کرده و b دیگر یک توکنِ مستقل در کنارِ a نیست. رمزگذاری، انتخابِ آزاد از همهٔ زیررشته‌های واژگان نیست. اگر قالبِ ذخیره، ترتیبِ قواعد را حذف کند، بازسازیِ همین رفتار بدون اطلاعاتِ اضافی ممکن نیست.

کمترین تعدادِ توکن هم هدفِ این قرارداد نیست. برای zabc، قواعدِ z با a، سپس a با b و سرانجام ab با c را بدهید. نخست za ساخته می‌شود؛ دو قاعدهٔ بعدی دیگر محلِ مناسبی ندارند و پاسخ za، b و c است. اما z و abc هر دو در واژگان‌اند و متن را با دو قطعه می‌پوشانند. این پوششِ کوتاه‌تر، خروجیِ قواعدِ مرتب نیست.

درسِ جداسازی با کمترین هزینهٔ کل مسئلهٔ دیگری حل می‌کند: از میانِ همهٔ پوشش‌های کامل، هدفی جمع‌پذیر را کمینه می‌کند. اینجا نه هزینهٔ مدخل داریم، نه جدولِ بهترین پیشوند. شاهدِ دوقطعه‌ای فقط اختلافِ هدف‌ها را نشان می‌دهد؛ کمتر بودنِ توکن، کیفیتِ زبانی یا پاسخِ بهترِ مدل را ثابت نمی‌کند.

۳. هم‌پوشانی، ترتیبِ قواعد و نقطهٔ کد را صریح کنیم

سه قطعهٔ مجاور با یک اتصالِ مشترکِ میانی
تصویر مفهومی: سه قطعهٔ مجاور با یک اتصالِ مشترکِ میانی؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

هر قاعده، توکن‌های جاری را از چپ می‌بیند. اگر دو همسایه مطابق باشند، هر دو مصرف و یک توکن جایگزین می‌شود؛ سپس از آن زوج عبور می‌کنیم. در aaa با قاعدهٔ a و a، دو محلِ هم‌پوشان داریم، اما نمادِ وسط نمی‌تواند دو بار مصرف شود. نتیجه aa و a است. در aaaa، دو اتصالِ جدا نتیجهٔ aa و aa می‌دهند.

توکنِ مرکب می‌تواند جزءِ قاعدهٔ بعد باشد. قواعدِ a با b، سپس ab با c و بعد abc با d، ورودیِ abcd را به یک توکن می‌رسانند. فهرستِ وارونه‌ای که پیش از معرفیِ ab از آن استفاده کند، در رابطِ ما معتبر نیست. همهٔ قواعد پیش از نخستین اتصال بررسی می‌شوند؛ متنِ تهی و قاعدهٔ بی‌استفاده، راهِ فرار از اعتبارسنجی نیستند.

طبق بخشِ کاملِ پشتیبانیِ یونیکد در پایتون، str و نمایشِ بایتی دو لایهٔ متفاوت‌اند. یک حرفِ ترکیبی ممکن است چند نقطهٔ کد داشته باشد. برنامه، شکلِ پیش‌ساخته و حرفِ همراهِ علامتِ ترکیبی را خودکار برابر نمی‌کند؛ اتصالِ دو نماد هم نرمال‌سازی نیست. نیم‌فاصله فقط نمادی است که باید در الفبای ورودی حاضر باشد.

نامِ تاریخیِ BPE نباید واحدِ این نمونه را پنهان کند. اینجا نمادِ پایه یک نقطهٔ کد است؛ پیاده‌سازیِ بایت‌محور با الفبای بایت و قواعدِ مدل یکسان نیست. خروجی را نیز خوشهٔ نویسه‌ای یا واژهٔ صحیحِ فارسی نمی‌نامیم. نسخهٔ خوانده‌شدهٔ راهنمای پایتون ۳٫۱۵٫۱ است؛ نسخهٔ مفسرِ واقعی جداگانه در خروجی ثبت می‌شود.

۴. اجرای ترتیبی را با بازنویسیِ رویدادمحور بسنجیم

دو میزِ مستقل برای بررسیِ یک چیدمانِ نهایی
تصویر مفهومی: دو میزِ مستقل برای بررسیِ یک چیدمانِ نهایی؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

کد را در bpe_ranked_merge_contract.py ذخیره کنید. فقط کتابخانهٔ استاندارد لازم است. مسیرِ اصلی برای هر قاعده یک گذرِ چپ‌به‌راست روی توکن‌های جاری دارد. پایانِ برشِ دو توکنِ متصل، پایانِ توکنِ دوم است. ساختِ فهرستِ بعدی، ورودی‌ها را تغییر نمی‌دهد و پاسخِ هر فراخوانی، ظرفِ تازه‌ای دارد.

"""Own bounded ranked Unicode-symbol merges; no training or byte-level tokenizer."""
from itertools import permutations, product
import json
import platform
import sys

MAX_POINTS = 8
MAX_ALPHABET = 8
MAX_RULES = 8


def _validate(text, alphabet, merges):
    if type(text) is not str:
        raise TypeError("exact str text required")
    if len(text) > MAX_POINTS:
        raise ValueError("text exceeds codepoint bound")
    if type(alphabet) is not tuple:
        raise TypeError("exact tuple alphabet required")
    if len(alphabet) > MAX_ALPHABET:
        raise ValueError("alphabet exceeds bound")
    known = set()
    for symbol in alphabet:
        if type(symbol) is not str:
            raise TypeError("exact str base symbol required")
        if len(symbol) != 1:
            raise ValueError("base symbol must be one codepoint")
        number = ord(symbol)
        if (symbol.isspace() or number < 32 or 127 <= number <= 159
                or 0xD800 <= number <= 0xDFFF):
            raise ValueError("selected whitespace/control/surrogate excluded")
        if symbol in known:
            raise ValueError("duplicate base symbol")
        known.add(symbol)
    if type(merges) is not tuple:
        raise TypeError("exact tuple rule schedule required")
    if len(merges) > MAX_RULES:
        raise ValueError("too many rules")
    # Validate every rule before any text merge, including unused late rules.
    for pair in merges:
        if type(pair) is not tuple or len(pair) != 2:
            raise TypeError("each rule must be an exact two-tuple")
        left, right = pair
        if type(left) is not str or type(right) is not str:
            raise TypeError("exact str rule components required")
        if not left or not right or left not in known or right not in known:
            raise ValueError("rule component not introduced strictly earlier")
        combined = left + right
        if len(combined) > MAX_POINTS or combined in known:
            raise ValueError("new bounded output token required")
        known.add(combined)
    base = set(alphabet)
    if any(point not in base for point in text):
        raise ValueError("unknown base codepoint; no implicit UNK policy")


def encode_ranked(text, alphabet, merges):
    """Apply supplied rules in rank order to one already separated pre-token.

    Each rule consumes matching nonoverlapping pairs from the left. Base
    symbols are exact one-codepoint strings, not UTF-8 bytes or graphemes.
    """
    _validate(text, alphabet, merges)
    pieces = [(point, index + 1) for index, point in enumerate(text)]
    merged_count = 0
    for left, right in merges:
        next_pieces = []
        cursor = 0
        while cursor < len(pieces):
            if (cursor + 1 < len(pieces) and pieces[cursor][0] == left
                    and pieces[cursor + 1][0] == right):
                next_pieces.append((left + right, pieces[cursor + 1][1]))
                merged_count += 1
                cursor += 2
            else:
                next_pieces.append(pieces[cursor])
                cursor += 1
        pieces = next_pieces
    return {"tokens": tuple(token for token, end in pieces),
            "end_cuts": tuple(end for token, end in pieces),
            "merges": merged_count}


def one_event_reference(text, alphabet, merges):
    """Valid-input reference: repeatedly choose one ranked adjacency event.

    No call to the main routine, validator or batched rule scan. Rebuild all
    candidates after each one-pair replacement; the left original offset
    breaks equal-rank overlaps. Supplied rules introduce unique new tokens.
    """
    ranks = {pair: rank for rank, pair in enumerate(merges)}
    segments = [(text[index:index + 1], index, index + 1)
                for index in range(len(text))]
    event_count = 0
    while True:
        candidates = []
        for index in range(len(segments) - 1):
            pair = (segments[index][0], segments[index + 1][0])
            if pair in ranks:
                candidates.append((ranks[pair], segments[index][1], index))
        if not candidates:
            break
        rank, original_start, chosen = min(candidates)
        first, second = segments[chosen], segments[chosen + 1]
        replacement = (first[0] + second[0], first[1], second[2])
        segments[chosen:chosen + 2] = [replacement]
        event_count += 1
    return {"tokens": tuple(segment[0] for segment in segments),
            "end_cuts": tuple(segment[2] for segment in segments),
            "merges": event_count}


def rejected(*arguments):
    try:
        encode_ranked(*arguments)
    except (TypeError, ValueError):
        return True
    return False


def run_example():
    checks = {}
    alphabet = ("z", "a", "b", "c")
    rules = (("z", "a"), ("a", "b"), ("ab", "c"))
    ranked = encode_ranked("zabc", alphabet, rules)
    checks["manual_supplied_rank_trace_preserves_complete_text_and_cuts"] = (
        ranked == {"tokens": ("za", "b", "c"),
                   "end_cuts": (2, 3, 4), "merges": 1}
        and "".join(ranked["tokens"]) == "zabc")
    abc = ("a", "b", "c")
    ab_first_rules = (("a", "b"), ("b", "c"))
    bc_first_rules = tuple(reversed(ab_first_rules))
    ab_first = encode_ranked("abc", abc, ab_first_rules)
    bc_first = encode_ranked("abc", abc, bc_first_rules)
    vocabulary_a = set(abc) | {a + b for a, b in ab_first_rules}
    vocabulary_b = set(abc) | {a + b for a, b in bc_first_rules}
    checks["same_vocabulary_different_rank_changes_selected_boundaries"] = (
        vocabulary_a == vocabulary_b
        and ab_first == {"tokens": ("ab", "c"), "end_cuts": (2, 3), "merges": 1}
        and bc_first == {"tokens": ("a", "bc"), "end_cuts": (1, 3), "merges": 1})
    vocabulary = set(alphabet) | {a + b for a, b in rules}
    alternative = ("z", "abc")
    checks["ranked_merges_are_not_minimum_token_count_or_longest_match"] = (
        "".join(alternative) == "zabc" and all(t in vocabulary for t in alternative)
        and "zabc" not in vocabulary and len(ranked["tokens"]) == 3 > len(alternative)
        and bc_first["tokens"] != ("ab", "c") and "ab" in vocabulary_a)
    overlap = encode_ranked("aaa", ("a",), (("a", "a"),))
    checks["equal_pair_overlaps_consume_leftmost_nonoverlapping_symbols"] = (
        overlap == {"tokens": ("aa", "a"), "end_cuts": (2, 3), "merges": 1}
        and encode_ranked("aaaa", ("a",), (("a", "a"),))
        == {"tokens": ("aa", "aa"), "end_cuts": (2, 4), "merges": 2})
    compound = encode_ranked("abcd", ("a", "b", "c", "d"),
                             (("a", "b"), ("ab", "c"), ("abc", "d")))
    checks["introduced_compound_symbol_enables_later_rank"] = (
        compound == {"tokens": ("abcd",), "end_cuts": (4,), "merges": 3})
    empty = encode_ranked("", (), ())
    checks["empty_and_no_applicable_rules_have_literal_complete_results"] = (
        empty == {"tokens": (), "end_cuts": (), "merges": 0}
        and encode_ranked("cab", abc, ())
        == {"tokens": ("c", "a", "b"), "end_cuts": (1, 2, 3), "merges": 0}
        and encode_ranked("c", abc, ab_first_rules)["tokens"] == ("c",))
    checks["unknown_base_symbol_rejected_instead_of_inventing_UNK"] = (
        rejected("abx", abc, ab_first_rules) and rejected("a", (), ())
        and not rejected("", (), ()))
    unicode_alphabet = ("\u0645", "\u06cc", "\u200c", "e", "\u0301", "\u00e9")
    unicode_text = "\u0645\u06cc\u200ce\u0301"
    unicode_result = encode_ranked(unicode_text, unicode_alphabet,
                                   (("\u0645", "\u06cc"), ("e", "\u0301")))
    checks["raw_codepoints_ZWNJ_and_canonical_forms_are_not_normalized"] = (
        unicode_result == {"tokens": ("\u0645\u06cc", "\u200c", "e\u0301"),
                           "end_cuts": (2, 3, 5), "merges": 2}
        and "".join(unicode_result["tokens"]) == unicode_text
        and len(unicode_text.encode("utf-8")) > len(unicode_text)
        and encode_ranked("\u00e9", unicode_alphabet, ())["tokens"] == ("\u00e9",)
        and "e\u0301" != "\u00e9")
    schedules = [()]
    for first in product("ab", repeat=2):
        schedules.append((first,))
        available = ("a", "b", first[0] + first[1])
        for second in product(available, repeat=2):
            if second[0] + second[1] not in available:
                schedules.append((first, second))
    family_ok = True
    cases = 0
    for schedule in schedules:
        for size in range(7):
            for letters in product("ab", repeat=size):
                text = "".join(letters)
                actual = encode_ranked(text, ("a", "b"), schedule)
                expected = one_event_reference(text, ("a", "b"), schedule)
                family_ok = (actual == expected) and family_ok
                cases += 1
    checks["finite_valid_schedules_match_one_event_reference_ONE_group"] = (
        family_ok and len(schedules) == 37 and cases == 4699)
    order_cases = 0
    order_ok = True
    for ordering in permutations(abc):
        actual = encode_ranked("abc", ordering, ab_first_rules)
        order_ok = (actual == ab_first) and order_ok
        order_cases += 1
    checks["base_alphabet_order_is_not_merge_rank"] = bool(order_ok and order_cases == 6)

    class StringSubclass(str):
        pass
    class TupleSubclass(tuple):
        pass
    invalid_types = [
        (None, abc, ()), (b"a", abc, ()), (True, abc, ()),
        (StringSubclass("a"), abc, ()), ("a", list(abc), ()),
        ("a", TupleSubclass(abc), ()), ("", (None,), ()),
        ("", (True,), ()), ("", (StringSubclass("a"),), ()),
        ("a", abc, []), ("a", abc, TupleSubclass(())),
        ("a", abc, (["a", "b"],)), ("a", abc, (("a",),)),
        ("a", abc, ((b"a", "b"),)), ("a", abc, ((True, "b"),)),
        ("a", abc, ((StringSubclass("a"), "b"),))]
    touched = []
    def forbidden_generator():
        touched.append(True)
        yield "a"
    checks["exact_shapes_types_reject_without_generator_consumption"] = (
        all(rejected(*args) for args in invalid_types)
        and rejected("", forbidden_generator(), ())
        and rejected("a", abc, forbidden_generator()) and touched == [])
    invalid_rules = [
        ("", ("a", "a"), ()), ("", ("",), ()), ("", ("ab",), ()),
        ("", (" ",), ()), ("", ("\x00",), ()), ("", ("\x80",), ()),
        ("", ("\ud800",), ()), ("", abc, (("ab", "a"),)),
        ("a", abc, (("a", "b"), ("x", "a"))),
        ("", abc, (("a", "b"), ("a", "b"))),
        ("", abc, (("a", "b"), ("b", "c"), ("ab", "c"), ("a", "bc"))),
        ("", abc, (("", "a"),))]
    checks["all_unused_rules_validate_introduction_and_unique_outputs"] = (
        all(rejected(*args) for args in invalid_rules))
    maximum_alphabet = tuple("abcdefgh")
    maximum_rules = (("a", "b"), ("b", "c"), ("c", "d"), ("d", "e"),
                     ("e", "f"), ("f", "g"), ("g", "h"), ("ab", "cd"))
    maximum = encode_ranked("abcdefgh", maximum_alphabet, maximum_rules)
    length_eight = encode_ranked("a" * 8, ("a",),
                                 (("a", "a"), ("aa", "aa"), ("aaaa", "aaaa")))
    too_long_token = (("a", "a"), ("aa", "a"), ("aaa", "a"),
                      ("aaaa", "a"), ("aaaa", "aaaaa"))
    checks["inclusive_resource_caps_and_oversized_token_rejection"] = (
        maximum["tokens"] == ("abcd", "ef", "gh")
        and length_eight["tokens"] == ("a" * 8,) and length_eight["end_cuts"] == (8,)
        and rejected("a" * 9, ("a",), ())
        and rejected("", tuple("abcdefghi"), ())
        and rejected("", maximum_alphabet, maximum_rules + (("ef", "gh"),))
        and rejected("", ("a",), too_long_token))
    before = (alphabet, rules)
    exposed = encode_ranked("zabc", alphabet, rules)
    exposed["tokens"] = ("changed",)
    checks["fresh_results_input_preservation_and_irrelevant_rule_invariance"] = (
        (alphabet, rules) == before and encode_ranked("zabc", alphabet, rules) == ranked
        and type(ranked["tokens"]) is tuple and type(ranked["end_cuts"]) is tuple
        and encode_ranked("abc", abc, ab_first_rules + (("c", "a"),)) == ab_first
        and encode_ranked("", alphabet, rules) == empty)
    if len(checks) != 14 or not all(type(value) is bool and value for value in checks.values()):
        raise AssertionError(checks)
    return {
        "runtime": {"implementation": platform.python_implementation(),
                    "python": platform.python_version(), "dependencies": "stdlib only"},
        "contract": {"text_codepoints": [0, MAX_POINTS],
                     "alphabet_symbols": [0, MAX_ALPHABET], "merge_rules": [0, MAX_RULES],
                     "maximum_output_token_codepoints": MAX_POINTS,
                     "rank": "rule tuple position, ascending",
                     "overlap": "leftmost nonoverlapping pair consumption",
                     "scope": "one supplied already separated raw-codepoint pre-token",
                     "training_byte_level_normalization_or_UNK": False},
        "manual_ranked_fixture": {"text": "zabc", "alphabet": alphabet,
                                  "rules": rules, "result": ranked,
                                  "valid_fewer_token_vocabulary_cover": alternative},
        "same_vocabulary_fixture": {"text": "abc", "AB_first": ab_first,
                                    "BC_first": bc_first, "vocabulary": sorted(vocabulary_a)},
        "overlap_fixture": overlap, "compound_fixture": compound,
        "empty_fixture": empty, "literal_unicode_fixture": unicode_result,
        "finite_family": {"valid_rule_schedules": len(schedules), "texts_per_schedule": 127,
                          "cases_inside_ONE_group": cases},
        "base_alphabet_orders_inside_one_group": order_cases,
        "invalid_shapes_and_types_inside_one_group": len(invalid_types) + 2,
        "invalid_rule_semantics_inside_one_group": len(invalid_rules),
        "maximum_fixture": maximum, "checks": checks,
        "unique_executed_checks": len(checks), "all_passed": True,
        "limitations": [
            "Own bounded supplied-rule inference, not tokenizer training",
            "Not byte-level BPE, grapheme segmentation, Persian word recognition or HF/GPT output compatibility",
            "Rule schedules satisfy introduction/new-output constraints; frequency-learnability is not proved",
            "Normalization, pre-tokenization, special tokens, dropout and unknown fallback are outside contract",
            "No minimum-token-count, language/search/model-quality or speed/memory claim",
            "Finite oracle cases are one check group, not an all-input proof"]}


if __name__ == "__main__":
    result = json.dumps(run_example(), ensure_ascii=True, indent=2, allow_nan=False)
    sys.stdout.buffer.write((result + "\n").encode("utf-8"))

مرجعِ مستقل، گذرِ کاملِ هر قاعده را تکرار نمی‌کند. پس از هر اتصال، همهٔ همسایه‌های جاری را از نو نامزد می‌کند، کمترین رتبه و در تساوی چپ‌ترین جایگاهِ اصلی را می‌گیرد و فقط یک زوج را جایگزین می‌کند. این مرجع، تابعِ اصلی و اعتبارسنجِ آن را صدا نمی‌زند. هر رویداد تعدادِ توکن‌ها را کم می‌کند، بنابراین برای متنِ ناتهی بیش از طولِ متن منهای یک اتصال نداریم.

مقایسهٔ این دو روش به قراردادِ معرفیِ توکن‌ها وابسته است: خروجیِ تازهٔ یک قاعده نمی‌تواند جزءِ قاعده‌ای زودتر باشد، چون آن جزء باید قبلاً موجود می‌بود و تولیدِ دوباره ممنوع است. اتصال نیز چیزی را بی‌جانشین حذف نمی‌کند تا دو نمادِ قدیمی از دو سوی آن ناگهان همسایه شوند. پس عبور از رتبه‌ای زودتر، با اتصالِ دیرتر نیازمندِ بازگشت به همان رتبه نمی‌شود.

خانوادهٔ محدود، همهٔ فهرست‌های معتبرِ صفر تا دو قاعده روی الفبای a و b را با همهٔ رشته‌های طولِ صفر تا شش مقایسه می‌کند. سی‌وهفت فهرست و صدوبیست‌وهفت رشته، ۴۶۹۹ مورد در یک گروهِ مرجع می‌دهند. شاهدهای دستی، هم‌پوشانی، ترتیب، یونیکد، ورودیِ نامعتبر، سقف‌ها و استقلالِ نتیجه گروه‌های جدا هستند؛ تعدادِ حالت‌ها را چهارده گروهِ مستقل نمی‌نامیم.

{
  "runtime": {
    "implementation": "CPython",
    "python": "3.14.5",
    "dependencies": "stdlib only"
  },
  "contract": {
    "text_codepoints": [
      0,
      8
    ],
    "alphabet_symbols": [
      0,
      8
    ],
    "merge_rules": [
      0,
      8
    ],
    "maximum_output_token_codepoints": 8,
    "rank": "rule tuple position, ascending",
    "overlap": "leftmost nonoverlapping pair consumption",
    "scope": "one supplied already separated raw-codepoint pre-token",
    "training_byte_level_normalization_or_UNK": false
  },
  "manual_ranked_fixture": {
    "text": "zabc",
    "alphabet": [
      "z",
      "a",
      "b",
      "c"
    ],
    "rules": [
      [
        "z",
        "a"
      ],
      [
        "a",
        "b"
      ],
      [
        "ab",
        "c"
      ]
    ],
    "result": {
      "tokens": [
        "za",
        "b",
        "c"
      ],
      "end_cuts": [
        2,
        3,
        4
      ],
      "merges": 1
    },
    "valid_fewer_token_vocabulary_cover": [
      "z",
      "abc"
    ]
  },
  "same_vocabulary_fixture": {
    "text": "abc",
    "AB_first": {
      "tokens": [
        "ab",
        "c"
      ],
      "end_cuts": [
        2,
        3
      ],
      "merges": 1
    },
    "BC_first": {
      "tokens": [
        "a",
        "bc"
      ],
      "end_cuts": [
        1,
        3
      ],
      "merges": 1
    },
    "vocabulary": [
      "a",
      "ab",
      "b",
      "bc",
      "c"
    ]
  },
  "overlap_fixture": {
    "tokens": [
      "aa",
      "a"
    ],
    "end_cuts": [
      2,
      3
    ],
    "merges": 1
  },
  "compound_fixture": {
    "tokens": [
      "abcd"
    ],
    "end_cuts": [
      4
    ],
    "merges": 3
  },
  "empty_fixture": {
    "tokens": [],
    "end_cuts": [],
    "merges": 0
  },
  "literal_unicode_fixture": {
    "tokens": [
      "می",
      "‌",
      "é"
    ],
    "end_cuts": [
      2,
      3,
      5
    ],
    "merges": 2
  },
  "finite_family": {
    "valid_rule_schedules": 37,
    "texts_per_schedule": 127,
    "cases_inside_ONE_group": 4699
  },
  "base_alphabet_orders_inside_one_group": 6,
  "invalid_shapes_and_types_inside_one_group": 18,
  "invalid_rule_semantics_inside_one_group": 12,
  "maximum_fixture": {
    "tokens": [
      "abcd",
      "ef",
      "gh"
    ],
    "end_cuts": [
      4,
      6,
      8
    ],
    "merges": 5
  },
  "checks": {
    "manual_supplied_rank_trace_preserves_complete_text_and_cuts": true,
    "same_vocabulary_different_rank_changes_selected_boundaries": true,
    "ranked_merges_are_not_minimum_token_count_or_longest_match": true,
    "equal_pair_overlaps_consume_leftmost_nonoverlapping_symbols": true,
    "introduced_compound_symbol_enables_later_rank": true,
    "empty_and_no_applicable_rules_have_literal_complete_results": true,
    "unknown_base_symbol_rejected_instead_of_inventing_UNK": true,
    "raw_codepoints_ZWNJ_and_canonical_forms_are_not_normalized": true,
    "finite_valid_schedules_match_one_event_reference_ONE_group": true,
    "base_alphabet_order_is_not_merge_rank": true,
    "exact_shapes_types_reject_without_generator_consumption": true,
    "all_unused_rules_validate_introduction_and_unique_outputs": true,
    "inclusive_resource_caps_and_oversized_token_rejection": true,
    "fresh_results_input_preservation_and_irrelevant_rule_invariance": true
  },
  "unique_executed_checks": 14,
  "all_passed": true,
  "limitations": [
    "Own bounded supplied-rule inference, not tokenizer training",
    "Not byte-level BPE, grapheme segmentation, Persian word recognition or HF/GPT output compatibility",
    "Rule schedules satisfy introduction/new-output constraints; frequency-learnability is not proved",
    "Normalization, pre-tokenization, special tokens, dropout and unknown fallback are outside contract",
    "No minimum-token-count, language/search/model-quality or speed/memory claim",
    "Finite oracle cases are one check group, not an all-input proof"
  ]
}

اعدادِ گزارش، دامنهٔ همین اجرای محدودند؛ اثباتِ درستی برای همهٔ ورودی‌ها یا سنجشِ زمان و حافظه نیستند. متن و قواعد عمداً کوچک‌اند تا کد و مرجع قابلِ بازبینی بمانند. هیچ پیکره، بستهٔ توکن‌ساز، مدلِ زبانی یا API بیرونی در اجرای مثال استفاده نمی‌شود.

۵. چهار پرسش دربارهٔ قرارداد و مرزِ BPE

قطعه‌های مفهومی با مرزِ مشخص در کنار یک کتابِ بسته
تصویر مفهومی: قطعه‌های مفهومی با مرزِ مشخص در کنار یک کتابِ بسته؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

پرسش اول: چرا دو واژگانِ یکسان به یک پاسخ نمی‌رسند؟ پاسخ: واژگان فقط امکانِ وجودِ قطعه‌ها را می‌گوید. در abc، ادغامِ زودترِ ab، زوجِ bc را از دسترس خارج می‌کند؛ تقدمِ bc نتیجه را برعکس می‌سازد. برای بازسازیِ رفتار، ترتیبِ قواعد و سیاستِ پیش‌پردازش هم باید روشن باشند.

پرسش دوم: آیا در zabc باید پاسخِ دوقطعه‌ای را جایگزین کنیم؟ پاسخ: فقط اگر مسئله را عوض کنیم. z و abc پوششِ موجود و کوتاه‌تری هستند، اما قواعدِ داده‌شده za را زودتر می‌سازند. تغییر به کمترین تعدادِ قطعه یا بلندترین تطبیق، اجرای همان قرارداد نیست و باید رابط و آزمونِ جدا داشته باشد.

پرسش سوم: در aaa چرا دو زوج را هم‌زمان نمی‌گیریم؟ پاسخ: هر دو به a میانی نیاز دارند. مصرفِ غیرهم‌پوشانِ چپ‌به‌راست ابتدا دو نمادِ اول را می‌گیرد و نمادِ آخر باقی می‌ماند. این قاعدهٔ تساوی را از ابتدا گفته‌ایم؛ آن را از ترتیبِ تصادفیِ پیمایش استنباط نمی‌کنیم.

پرسش چهارم: آیا این تابع، توکن‌سازِ فارسی یا پیشنهاددهندهٔ واژه است؟ پاسخ: خیر؛ یک قطعهٔ آماده و قواعدِ تألیفی دارد. تکمیلِ پیشوند با Trie از مدخل‌ها پیشنهاد می‌دهد، اما این تابع فقط مرزهای همان رشته را انتخاب می‌کند. منابع در ۳۰ مهر ۱۴۰۵ خوانده شدند؛ تاریخِ دسترسی، تاریخِ انتشارِ نامعلومِ صفحات یا گزارشِ خبرِ تازه نیست.

منابع و مطالعهٔ بیشتر

بررسی منابع: ۳۰ مهر ۱۴۰۵. تحلیل‌ها و پیشنهادهای اجرایی، جمع‌بندی نویسنده‌اند.