From f509329e735f89acb05a24da96e1fa72461a35de Mon Sep 17 00:00:00 2001 From: Matthew Romano Date: Fri, 25 Sep 2026 08:37:29 -0400 Subject: [PATCH 1/4] fix(llama): scan all Split steps for pretokenizer variant classification detect_split_variant() returned on the first Split step found in a Sequence pre_tokenizer, even when that step's regex classified as the generic kLlama default. Checkpoints that isolate digit-grouping (\p{N}{1,3}) as its own Split step ahead of the step whose regex actually identifies the variant (e.g. Qwen3's [^\r\n... signature) were misclassified as kLlama, silently disabling variant-specific pre-tokenization behavior for the whole checkpoint. Confirmed directly against openbmb/MiniCPM5-2B's real, published tokenizer.json, which has exactly this shape. Now scans every Split step for classification instead of only the first, and extracts digit-group size independently of which step decides the variant, since the two are not guaranteed to be the same step (confirmed: they aren't, in MiniCPM5-2B's real tokenizer.json). Throws if two different Split steps would classify to two different non-kLlama variants, matching this file's existing convention of throwing on ambiguous/unsupported pre-tokenizer contracts. New test (test_llama_pretokenizer_variant.cpp, CPU-only, no GPU/TRT dependency) uses MiniCPM5-2B's real pre_tokenizer field verbatim to verify both the newline-attachment and digit-grouping behavior this fix restores, plus a synthetic fixture for the ambiguous-variant throw case. Mutation-checked against the original code (all three checks fail on unpatched bpe_tokenizer.cpp, pass on the fix). Scoped to families/llama only; the same detect_split_variant/ classify_split pattern is duplicated per-family across most other families' own bpe_tokenizer.cpp copies, per this repo's own per-family duplication convention. Fixing those is out of scope here. Signed-off-by: Matthew Romano --- families/llama/runtime/CMakeLists.txt | 3 +- families/llama/runtime/bpe_tokenizer.cpp | 39 +++- .../cpp/test_llama_pretokenizer_variant.cpp | 191 ++++++++++++++++++ 3 files changed, 229 insertions(+), 4 deletions(-) create mode 100644 families/llama/tests/cpp/test_llama_pretokenizer_variant.cpp diff --git a/families/llama/runtime/CMakeLists.txt b/families/llama/runtime/CMakeLists.txt index e0dfbae4e9..09284950c7 100644 --- a/families/llama/runtime/CMakeLists.txt +++ b/families/llama/runtime/CMakeLists.txt @@ -35,7 +35,8 @@ install(TARGETS trtmc_model_llama ) if(TRTMC_BUILD_TESTS) - foreach(test_name IN ITEMS test_llama_chat_template test_llama_native_kv_cache) + foreach(test_name IN ITEMS test_llama_chat_template test_llama_native_kv_cache + test_llama_pretokenizer_variant) add_executable(${test_name} ${PROJECT_SOURCE_DIR}/families/llama/tests/cpp/${test_name}.cpp ) diff --git a/families/llama/runtime/bpe_tokenizer.cpp b/families/llama/runtime/bpe_tokenizer.cpp index 860defc1df..742a4e7d3c 100644 --- a/families/llama/runtime/bpe_tokenizer.cpp +++ b/families/llama/runtime/bpe_tokenizer.cpp @@ -1272,19 +1272,52 @@ class BpeTokenizer final : public ITokenizer { return pretok::Variant::kLlama; } - // Detect variant from the Split inside a Sequence pre_tokenizer. + // Detect variant from the Split step(s) inside a Sequence pre_tokenizer. + // Some checkpoints isolate digit-grouping (\p{N}{1,3}) as its own Split + // step, ahead of the step whose regex actually identifies the variant + // (e.g. Qwen3's `[^\r\n...`). That earlier step's regex never matches + // any known variant signature and falls through to kLlama, so scanning + // stops before reaching the real classifier. Scan every Split step for + // classification instead of only the first; take the digit-group size + // independently, from whichever step's own regex encodes one, since the + // classifying step and the digit-grouping step are not guaranteed to be + // the same step. static pretok::Variant detect_split_variant(const nlohmann::json& pt, int& digit_group_out) { digit_group_out = 0; if (!pt.contains("pretokenizers")) return pretok::Variant::kLlama; + + pretok::Variant variant = pretok::Variant::kLlama; + bool variant_found = false; for (auto& sub : pt["pretokenizers"]) { if (sub.value("type", "") != "Split") continue; if (!sub.contains("pattern") || !sub["pattern"].contains("Regex")) continue; - return classify_split(sub, digit_group_out); + const auto regex = sub["pattern"]["Regex"].get(); + + if (digit_group_out == 0) { + if (int group = parse_digit_group(regex); group > 0) + digit_group_out = group; + } + + int step_digit_group = 0; // discarded; digit_group_out is tracked separately above + pretok::Variant step_variant = classify_split(sub, step_digit_group); + if (step_variant == pretok::Variant::kLlama) + continue; + if (variant_found && step_variant != variant) { + throw std::runtime_error( + "Ambiguous Sequence pre_tokenizer: Split steps classify to different " + "variants (" + + std::to_string(static_cast(variant)) + " and " + + std::to_string(static_cast(step_variant)) + ")"); + } + if (!variant_found) { + variant = step_variant; + variant_found = true; + } } - return pretok::Variant::kLlama; + return variant; } static bool is_space_split_pre_tokenizer(const nlohmann::json& pt, const std::string& pt_type) { diff --git a/families/llama/tests/cpp/test_llama_pretokenizer_variant.cpp b/families/llama/tests/cpp/test_llama_pretokenizer_variant.cpp new file mode 100644 index 0000000000..f4e89dd0d0 --- /dev/null +++ b/families/llama/tests/cpp/test_llama_pretokenizer_variant.cpp @@ -0,0 +1,191 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +// detect_split_variant() previously returned on the first Split step found +// in a Sequence pre_tokenizer, even when that step's regex classifies as +// the generic kLlama default. Checkpoints that isolate digit-grouping +// (\p{N}{1,3}) as its own Split step ahead of the step that actually +// identifies the variant (e.g. Qwen3's `[^\r\n...` signature) were +// misclassified as kLlama, silently disabling variant-specific +// pre-tokenization behavior (trailing-newline attachment, grouped digit +// runs) for the whole checkpoint. +// +// The first two checks below use the pre_tokenizer field exactly as +// published in openbmb/MiniCPM5-2B's tokenizer.json (a real, public +// checkpoint that triggers this misclassification), so a passing result +// here reflects the real checkpoint's own regex shapes, not a synthetic +// stand-in. The third check is a synthetic fixture verifying the +// ambiguous-classification case (two Split steps that each classify to a +// different, non-kLlama variant) throws rather than silently picking one. + +#include "families/llama/runtime/tokenizer.h" + +#include +#include +#include +#include +#include + +namespace { + +int failures = 0; + +void check_ids(const std::vector& actual, const std::vector& expected, + const char* name) { + if (actual == expected) + return; + std::cerr << "FAIL: " << name << " expected"; + for (const int32_t token : expected) + std::cerr << ' ' << token; + std::cerr << " got"; + for (const int32_t token : actual) + std::cerr << ' ' << token; + std::cerr << '\n'; + ++failures; +} + +// MiniCPM5-2B's actual pre_tokenizer field (openbmb/MiniCPM5-2B, +// tokenizer.json): a digit-grouping Split step first, then the real +// classifying (Qwen3-shaped) Split step second, then ByteLevel. +constexpr const char* kMiniCPM5PreTokenizer = R"( + "pre_tokenizer": { + "type": "Sequence", + "pretokenizers": [ + { + "type": "Split", + "pattern": {"Regex": "\\p{N}{1,3}"}, + "behavior": "Isolated", + "invert": false + }, + { + "type": "Split", + "pattern": { + "Regex": "(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\\r\\n\\p{L}\\p{N}]?\\p{L}+|\\p{N}+| ?[^\\s\\p{L}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+" + }, + "behavior": "Isolated", + "invert": false + }, + { + "type": "ByteLevel", + "add_prefix_space": false, + "trim_offsets": true, + "use_regex": false + } + ] + })"; + +// Trailing newlines after punctuation should stay attached to the same +// pre-token (Qwen3-variant behavior) rather than split off as their own +// whitespace token. Only reachable if the second Split step's regex is +// actually used to classify the variant, not the first. +void check_newline_attachment() { + const std::string tokenizer_json = std::string(R"({ + "model": { + "type": "BPE", + "vocab": { + ".": 0, + "Ċ": 1, + "ĊĊ": 2, + ".ĊĊ": 3 + }, + "merges": ["Ċ Ċ", ". ĊĊ"] + },)") + kMiniCPM5PreTokenizer + R"( + })"; + + auto tokenizer = trtmc::CreateBpeTokenizer(tokenizer_json.data(), tokenizer_json.size(), false); + if (!tokenizer) { + std::cerr << "FAIL: check_newline_attachment: tokenizer was not created\n"; + ++failures; + return; + } + check_ids(tokenizer->encode(".\n\n"), {3}, "check_newline_attachment"); +} + +// Multi-digit runs should be grouped into chunks of up to 3 digits +// (\p{N}{1,3}), each its own pre-token, not left as one unbounded digit +// run (the kLlama-misclassified behavior) and not split into individual +// one-digit pre-tokens (the failure mode of a fix that scans for the +// variant but couples digit-group size to the same winning step, which +// does not carry the {1,3} grouping in this checkpoint's real regex). +void check_digit_grouping() { + const std::string tokenizer_json = std::string(R"({ + "model": { + "type": "BPE", + "vocab": { + "1": 0, + "5": 1, + "0": 2, + "15": 3, + "150": 4, + "1500": 5 + }, + "merges": ["1 5", "15 0", "150 0"] + },)") + kMiniCPM5PreTokenizer + R"( + })"; + + auto tokenizer = trtmc::CreateBpeTokenizer(tokenizer_json.data(), tokenizer_json.size(), false); + if (!tokenizer) { + std::cerr << "FAIL: check_digit_grouping: tokenizer was not created\n"; + ++failures; + return; + } + // Correct (grouped by 3): ["150", "0"] -> ids {4, 2}. + // kLlama misclassification (unpatched bug): one ungrouped word "1500" -> {5}. + // Coupled/naive fix (digit_group taken from the same step as the + // variant, which for this checkpoint's real second step is 0): four + // one-digit words -> {0, 1, 2, 2}. + check_ids(tokenizer->encode("1500"), {4, 2}, "check_digit_grouping"); +} + +// Synthetic fixture: two Split steps that each classify to a different, +// non-kLlama variant. Not a real checkpoint shape -- this exercises the +// ambiguous-classification guard rather than reproducing a reported bug. +void check_ambiguous_variants_throw() { + const std::string tokenizer_json = R"({ + "model": { + "type": "BPE", + "vocab": {"a": 0}, + "merges": [] + }, + "pre_tokenizer": { + "type": "Sequence", + "pretokenizers": [ + { + "type": "Split", + "pattern": {"Regex": "[^(\\s)]+"}, + "behavior": "Isolated", + "invert": false + }, + { + "type": "Split", + "pattern": {"Regex": "[^\\r\\n\\p{L}\\p{N}]?\\p{L}+"}, + "behavior": "Isolated", + "invert": false + } + ] + } + })"; + + bool threw = false; + try { + trtmc::CreateBpeTokenizer(tokenizer_json.data(), tokenizer_json.size(), false); + } catch (const std::runtime_error&) { + threw = true; + } + if (!threw) { + std::cerr + << "FAIL: check_ambiguous_variants_throw: expected std::runtime_error, none thrown\n"; + ++failures; + } +} + +} // namespace + +int main() { + check_newline_attachment(); + check_digit_grouping(); + check_ambiguous_variants_throw(); + return failures == 0 ? 0 : 1; +} From f0c70eaca98a3f11c65b1e38b6c9a2e738332fdd Mon Sep 17 00:00:00 2001 From: JiaxinD Date: Fri, 25 Sep 2026 09:07:21 -0700 Subject: [PATCH 2/4] test(llama): add pinned MiniCPM5-1B coverage Signed-off-by: JiaxinD --- families/llama/tests/MINICPM5_1B.md | 24 ++++++++++++++ .../tests/fixtures/minicpm5-1b-config.json | 30 ++++++++++++++++++ .../llama/tests/manifests/minicpm5-1b.json | 23 ++++++++++++++ families/llama/tests/test_minicpm5_config.py | 31 +++++++++++++++++++ .../performance/config/release.yaml | 4 +++ 5 files changed, 112 insertions(+) create mode 100644 families/llama/tests/MINICPM5_1B.md create mode 100644 families/llama/tests/fixtures/minicpm5-1b-config.json create mode 100644 families/llama/tests/manifests/minicpm5-1b.json create mode 100644 families/llama/tests/test_minicpm5_config.py diff --git a/families/llama/tests/MINICPM5_1B.md b/families/llama/tests/MINICPM5_1B.md new file mode 100644 index 0000000000..3d3b70ba1c --- /dev/null +++ b/families/llama/tests/MINICPM5_1B.md @@ -0,0 +1,24 @@ +# MiniCPM5-1B checkpoint coverage + +This adds a pinned E2E selection for `openbmb/MiniCPM5-1B` through the existing +Llama family. It does not add a new architecture or claim GPU qualification. + +The configuration fixture is copied from the Apache-2.0 publisher checkpoint: +https://huggingface.co/openbmb/MiniCPM5-1B/blob/87179e5c1f455ef22e6223592d2d61351b525bfc/config.json + +Unlike the existing 2B checkpoint, the 1B model has hidden width 1536 but +16 attention heads of width 128 (attention width 2048), with two KV heads and +24 layers. CPU tests preserve that explicit width, full-context KV byte +geometry and the two stop IDs. The E2E uses the existing family build/runtime +and reference comparison with thinking disabled, FP16 candidate, FP32 reference, +a 256-token build limit and ten generated tokens. These limits are a small +parity experiment, not a long-context or performance claim. + +Validation remaining: build the pinned checkpoint on authorized GPU hardware +and run `families/llama/tests/test_e2e.py` with `--e2e-model minicpm5-1b`, using +the normal family E2E runtime environment. No large weights were downloaded locally. + +The existing Llama tokenizer's Sequence/Split classification issue is tracked +in upstream PR #1423. This checkpoint coverage must not be treated as complete +runtime qualification until tokenizer compatibility and target-GPU parity are +verified. This change does not duplicate that tokenizer implementation. diff --git a/families/llama/tests/fixtures/minicpm5-1b-config.json b/families/llama/tests/fixtures/minicpm5-1b-config.json new file mode 100644 index 0000000000..a2cdeeb3ce --- /dev/null +++ b/families/llama/tests/fixtures/minicpm5-1b-config.json @@ -0,0 +1,30 @@ +{ + "_name_or_path": "openbmb/MiniCPM5-1B", + "architectures": [ + "LlamaForCausalLM" + ], + "bos_token_id": 0, + "eos_token_id": [ + 1, + 130073 + ], + "pad_token_id": 1, + "hidden_act": "silu", + "hidden_size": 1536, + "initializer_range": 0.02, + "intermediate_size": 4608, + "max_position_embeddings": 131072, + "model_type": "llama", + "num_attention_heads": 16, + "num_hidden_layers": 24, + "num_key_value_heads": 2, + "head_dim": 128, + "rms_norm_eps": 1e-06, + "rope_theta": 5000000, + "rope_scaling": null, + "tie_word_embeddings": false, + "torch_dtype": "bfloat16", + "transformers_version": "5.6.2", + "use_cache": true, + "vocab_size": 130560 +} diff --git a/families/llama/tests/manifests/minicpm5-1b.json b/families/llama/tests/manifests/minicpm5-1b.json new file mode 100644 index 0000000000..7b7f2412f7 --- /dev/null +++ b/families/llama/tests/manifests/minicpm5-1b.json @@ -0,0 +1,23 @@ +{ + "name": "minicpm5-1b", + "hf_id": "openbmb/MiniCPM5-1B", + "hf_revision": "87179e5c1f455ef22e6223592d2d61351b525bfc", + "bundle": "minicpm5-1b.bundle", + "family": "llama", + "task": "text_generation", + "trust_remote_code": false, + "precision": "fp16", + "testcases": [ + { + "name": "minicpm5-1b", + "premerge": true, + "prompt": "What is the capital of France? Answer in one word.", + "max_new_tokens": 10, + "use_chat_template": true, + "reference_precision": "fp32", + "enable_thinking": false + } + ], + "max_sequence_length": 256, + "tensor_parallel_size": 1 +} diff --git a/families/llama/tests/test_minicpm5_config.py b/families/llama/tests/test_minicpm5_config.py new file mode 100644 index 0000000000..33ff8163e6 --- /dev/null +++ b/families/llama/tests/test_minicpm5_config.py @@ -0,0 +1,31 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Pinned MiniCPM5-1B configuration and checkpoint-selection coverage.""" + +import json +from pathlib import Path + +from ..build_routing import native_kv_cache_geometry +from ..config import ModelConfig +from .test_e2e import _CASES + + +def test_minicpm5_1b_checkpoint_is_selectable(): + manifest, case = _CASES["minicpm5-1b"] + assert manifest["hf_id"] == "openbmb/MiniCPM5-1B" + assert manifest["hf_revision"] == "87179e5c1f455ef22e6223592d2d61351b525bfc" + assert manifest["family"] == "llama" and manifest["tensor_parallel_size"] == 1 + assert case["use_chat_template"] and not case["enable_thinking"] + + +def test_minicpm5_1b_preserves_explicit_attention_width_and_stop_tokens(): + raw = json.loads((Path(__file__).parent / "fixtures/minicpm5-1b-config.json").read_text()) + config = ModelConfig.from_json(json.dumps(raw)) + assert config.hidden_size == 1536 + assert config.head_dim == 128 + assert config.attention_size == 2048 + assert config.attention_size != config.hidden_size + assert native_kv_cache_geometry(config, 131072) == (24576, 3221225472) + assert config.eos_token_id == [1, 130073] + assert config.raw["eos_token_id"] == [1, 130073] diff --git a/qualification_tests/benchmark_qualification/performance/config/release.yaml b/qualification_tests/benchmark_qualification/performance/config/release.yaml index f891fda4be..52d63393b5 100644 --- a/qualification_tests/benchmark_qualification/performance/config/release.yaml +++ b/qualification_tests/benchmark_qualification/performance/config/release.yaml @@ -18,6 +18,10 @@ defaults: asset_loading_included: false excluded_profiles: + - model: minicpm5-1b + reason: >- + Checkpoint E2E coverage is declared, but target-GPU parity and a release + performance workload/receipt remain unverified. - model: moge-2-vitl reason: >- Family-owned accuracy and internal performance qualification are present, From eb14db2370252065ff9fd4f02cc249fd3b7f8a14 Mon Sep 17 00:00:00 2001 From: JiaxinD Date: Sat, 26 Sep 2026 09:13:40 -0700 Subject: [PATCH 3/4] test(llama): check MiniCPM5 native prompt length Signed-off-by: JiaxinD --- families/llama/tests/MINICPM5_1B.md | 26 ++++++++++++++++++ .../llama/tests/manifests/minicpm5-1b.json | 27 +++++++++++++++++++ families/llama/tests/runtime_receipt.py | 9 +++++++ families/llama/tests/test_e2e.py | 8 ++++++ families/llama/tests/test_runtime_receipt.py | 24 +++++++++++++++++ 5 files changed, 94 insertions(+) diff --git a/families/llama/tests/MINICPM5_1B.md b/families/llama/tests/MINICPM5_1B.md index 3d3b70ba1c..46d575b86f 100644 --- a/families/llama/tests/MINICPM5_1B.md +++ b/families/llama/tests/MINICPM5_1B.md @@ -22,3 +22,29 @@ The existing Llama tokenizer's Sequence/Split classification issue is tracked in upstream PR #1423. This checkpoint coverage must not be treated as complete runtime qualification until tokenizer compatibility and target-GPU parity are verified. This change does not duplicate that tokenizer implementation. + +## Tokenizer-sensitive premerge coverage + +The second premerge case uses raw Chinese punctuation, a ten-digit sequence and +an English continuation prompt. Its `expected_prompt_token_ids` come from the +pinned publisher tokenizer, including its `` post-processor token. It uses the +same FP16/FP32 generation comparison and limits as the chat case; no acceptance +threshold is changed. A separate native prefill receipt assertion requires +16 tokens, including BOS, so the observed 18-token baseline cannot pass merely +by generating similar text. This assertion retains the subsequent Hugging Face +generation comparison; it does not turn the case into a KV-contract-only test. +It checks token count, not equality of every native input ID. Selecting `--e2e-model minicpm5-1b` selects both cases. + +CPU comparison against Hugging Face Tokenizers 0.22.2 found six mismatches out +of 28 text probes on the initial PR source (f0c70eac). Replacing only the Llama +tokenizer source with PR #1423 at f509329e735f89acb05a24da96e1fa72461a35de made +all 28 probes match. That comparison disabled special-token insertion on both +sides and covered digits, Chinese punctuation, code, whitespace and multilingual +text. The new manifest prompt was also checked separately against the publisher +post-processor. These are tokenizer-only CPU results, not model inference proof. + +For example, `1234567890` encodes as `[5645, 12740, 17371, 37]` without special +tokens in the pinned tokenizer. The initial PR runtime instead produced +`[5645, 12740, 1877, 1609]`. The dependency fixes that discrepancy. Until #1423 +is integrated, the newly declared case exposes a known tokenizer compatibility +gap; this PR remains a draft and does not copy the dependency's implementation. diff --git a/families/llama/tests/manifests/minicpm5-1b.json b/families/llama/tests/manifests/minicpm5-1b.json index 7b7f2412f7..066d7aa2f9 100644 --- a/families/llama/tests/manifests/minicpm5-1b.json +++ b/families/llama/tests/manifests/minicpm5-1b.json @@ -16,6 +16,33 @@ "use_chat_template": true, "reference_precision": "fp32", "enable_thinking": false + }, + { + "name": "minicpm5-1b-multilingual-digits", + "premerge": true, + "prompt": "你好,世界!\n1234567890\nThe next number after 9 is", + "expected_prompt_token_ids": [ + 0, + 75828, + 68572, + 3708, + 5645, + 12740, + 17371, + 37, + 220, + 608, + 2633, + 1471, + 1302, + 242, + 46, + 357 + ], + "max_new_tokens": 10, + "use_chat_template": false, + "reference_precision": "fp32", + "expected_runtime_prefill_tokens": 16 } ], "max_sequence_length": 256, diff --git a/families/llama/tests/runtime_receipt.py b/families/llama/tests/runtime_receipt.py index 7ff8507045..7f66977c34 100644 --- a/families/llama/tests/runtime_receipt.py +++ b/families/llama/tests/runtime_receipt.py @@ -24,6 +24,15 @@ def prefill_observations(stderr: str) -> tuple[tuple[int, int, int], ...]: return tuple(values) +def assert_prefill_token_count(stderr: str, expected_tokens: int) -> None: + """Check one generation's native prompt length, including special tokens.""" + observations = prefill_observations(stderr) + assert len(observations) == 1, f"expected one prefill receipt, got {observations}" + assert observations[0][0] == expected_tokens, ( + f"native prefill token count {observations[0][0]} != {expected_tokens}" + ) + + def assert_native_kv_receipt(payload: dict, case: dict, prompt_tokens: int) -> None: stderr = str(payload["runtime_stderr"]) expected_rows = int(case["expected_kv_cache_rows"]) diff --git a/families/llama/tests/test_e2e.py b/families/llama/tests/test_e2e.py index fb32605d8d..c54536dc38 100644 --- a/families/llama/tests/test_e2e.py +++ b/families/llama/tests/test_e2e.py @@ -563,6 +563,14 @@ def test_e2e(case_name: str, request, tmp_path: Path) -> None: f"(bundle max={manifest['max_sequence_length']})" in payload["runtime_stderr"] ) + if "expected_runtime_prefill_tokens" in case: + from families.llama.tests.runtime_receipt import assert_prefill_token_count + + with evidence_stage("compare"): + assert_prefill_token_count( + payload["runtime_stderr"], int(case["expected_runtime_prefill_tokens"]) + ) + if _NATIVE_KV_FIELDS <= case.keys(): from families.llama.tests.runtime_receipt import assert_native_kv_receipt diff --git a/families/llama/tests/test_runtime_receipt.py b/families/llama/tests/test_runtime_receipt.py index 81948b9a85..8bb4244cdb 100644 --- a/families/llama/tests/test_runtime_receipt.py +++ b/families/llama/tests/test_runtime_receipt.py @@ -78,3 +78,27 @@ def test_receipt_rejects_missing_decode_step() -> None: payload["decode_ms"] = 0.0 with pytest.raises(AssertionError): assert_native_kv_receipt(payload, _case(), 65) + + +@pytest.mark.parametrize("launches, max_chunk", [(1, 16), (2, 8)]) +def test_prefill_count_includes_bos_without_requiring_a_kv_only_case(launches, max_chunk): + from families.llama.tests.runtime_receipt import assert_prefill_token_count + + stderr = f"[trtmc.prefill] tokens=16 launches={launches} max_chunk={max_chunk}" + assert_prefill_token_count(stderr, 16) + + +@pytest.mark.parametrize( + "stderr", + [ + "", + "[trtmc.prefill] tokens=18 launches=1 max_chunk=18", + "[trtmc.prefill] tokens=8 launches=1 max_chunk=8\n" + "[trtmc.prefill] tokens=8 launches=1 max_chunk=8", + ], +) +def test_prefill_count_rejects_missing_wrong_or_repeated_receipts(stderr): + from families.llama.tests.runtime_receipt import assert_prefill_token_count + + with pytest.raises(AssertionError): + assert_prefill_token_count(stderr, 16) From b2a75d79120b114968b22a1e68e649cdabe836db Mon Sep 17 00:00:00 2001 From: JiaxinD Date: Sat, 26 Sep 2026 18:06:30 -0700 Subject: [PATCH 4/4] fix(llama): resolve staged E2E checkpoints offline Signed-off-by: JiaxinD --- families/llama/tests/MINICPM5_1B.md | 13 ++++++ families/llama/tests/test_checkpoint_cache.py | 44 +++++++++++++++++++ families/llama/tests/test_e2e.py | 1 + 3 files changed, 58 insertions(+) create mode 100644 families/llama/tests/test_checkpoint_cache.py diff --git a/families/llama/tests/MINICPM5_1B.md b/families/llama/tests/MINICPM5_1B.md index 46d575b86f..b7948fecc5 100644 --- a/families/llama/tests/MINICPM5_1B.md +++ b/families/llama/tests/MINICPM5_1B.md @@ -48,3 +48,16 @@ tokens in the pinned tokenizer. The initial PR runtime instead produced `[5645, 12740, 1877, 1609]`. The dependency fixes that discrepancy. Until #1423 is integrated, the newly declared case exposes a known tokenizer compatibility gap; this PR remains a draft and does not copy the dependency's implementation. + +## Staged checkpoint prerequisite + +The Llama E2E lookup consumes a pre-populated Hugging Face snapshot cache and +never refreshes Hub metadata during the test. Community GPU CI stages that cache +before entering its offline test container. For a manual run, populate the cache +with the pinned checkpoint first; an absent revision or missing `config.json` +still fails rather than selecting another checkpoint. + +This avoids a Hub 1.32 repository-tree lookup that otherwise raises +`OfflineModeIsEnabled` even when the pinned snapshot is already cached. The +regression tests use temporary local snapshots without any tree index or model +weights. No manifest revisions, comparisons or acceptance thresholds are changed. diff --git a/families/llama/tests/test_checkpoint_cache.py b/families/llama/tests/test_checkpoint_cache.py new file mode 100644 index 0000000000..9f765a0ca5 --- /dev/null +++ b/families/llama/tests/test_checkpoint_cache.py @@ -0,0 +1,44 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Exercise staged checkpoint lookup without Hub metadata or network access.""" + +import pytest +from huggingface_hub import constants +from huggingface_hub.errors import LocalEntryNotFoundError + +from .test_e2e import _checkpoint + + +@pytest.fixture +def offline_cache(tmp_path, monkeypatch): + monkeypatch.setattr(constants, "HF_HUB_CACHE", str(tmp_path)) + monkeypatch.setattr(constants, "HF_HUB_OFFLINE", True) + return tmp_path + + +def test_pinned_checkpoint_uses_snapshot_without_cached_tree(offline_cache): + revision = "a" * 40 + snapshot = offline_cache / "models--trtmc-test--llama" / "snapshots" / revision + snapshot.mkdir(parents=True) + (snapshot / "config.json").write_text("{}", encoding="utf-8") + + assert _checkpoint({"hf_id": "trtmc-test/llama", "hf_revision": revision}) == snapshot + + +def test_missing_checkpoint_is_not_replaced_by_another_revision(offline_cache): + cached = offline_cache / "models--trtmc-test--llama" / "snapshots" / ("a" * 40) + cached.mkdir(parents=True) + (cached / "config.json").write_text("{}", encoding="utf-8") + + with pytest.raises(LocalEntryNotFoundError): + _checkpoint({"hf_id": "trtmc-test/llama", "hf_revision": "b" * 40}) + + +def test_staged_snapshot_still_requires_config(offline_cache): + revision = "a" * 40 + snapshot = offline_cache / "models--trtmc-test--llama" / "snapshots" / revision + snapshot.mkdir(parents=True) + + with pytest.raises(AssertionError): + _checkpoint({"hf_id": "trtmc-test/llama", "hf_revision": revision}) diff --git a/families/llama/tests/test_e2e.py b/families/llama/tests/test_e2e.py index c54536dc38..4897da495b 100644 --- a/families/llama/tests/test_e2e.py +++ b/families/llama/tests/test_e2e.py @@ -113,6 +113,7 @@ def _checkpoint(manifest: dict) -> Path: snapshot_download( repo_id=manifest["hf_id"], revision=manifest.get("hf_revision"), + local_files_only=True, ) ) assert (path / "config.json").is_file(), path