Repository navigation
feat(perf): long-context prefix injection via prefix_file/prefix_role - #1531
Conversation
Slice a real-text corpus to exactly (target_input_len - prompt_len) tokens and prepend it to each prompt as a system or user message, so every request hits the target input length while keeping real-language entropy. - TextLengthArgs: add prefix_file / prefix_role with cross-field validation - DatasetPluginBase: load prefix ids once, build per-prompt prefix text, inject into messages (plain-text fallback for /v1/completions) - extract converge_to_token_len as the shared decode/re-encode skeleton for random prompt generation and prefix fitting (fill strategy injected) - enable for openqa / longalpaca / line_by_line / single-turn ShareGPT - tests: prefix budget fitting, tokenizer round-trip drift, wrapper regression Closes #1524
stress_test/parameters.md: regroup flat sections by workflow (input construction / load patterns / SLA / replay); stress_test/examples.md: group 14 flat H2 sections into 7 workflow-ordered ones, fix bash line-continuation comment, add long-context prefix injection examples; stress_test/multi_turn.md: fold --duration soft-exit and chat-template FAQ into their owning sections, move swe_smith-only params under its dataset entry, sync en table with zh [int, int] range syntax. Also fix 6 pre-existing Sphinx xref warnings (stale anchors into clip_benchmark / mteb / agent-native docs) and jsonl lexer warnings; 'make docs' now builds zh+en with zero warnings.
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
There was a problem hiding this comment.
👋 Review Summary
Nice work on adding real-text long-context prefix injection and centralizing the token-length convergence logic. The design is thoughtfully wired through the perf datasets and backed by solid unit and integration tests with fake tokenizers.
🛡️ Key Risks & Issues
- Long-context prefix combined with
input_len_mode='drop'ends up effectively disabling the prefix: only prompts whose bare-token length already equalstarget_input_lenare kept, which in turn gives a zero prefix budget, so no prefix is ever injected even whenprefix_fileis set. This makes that configuration misleading for users who expect genuine long-context behavior. - For multi-turn ShareGPT datasets, the prefix budget is measured only against the last user turn, while earlier conversation history remains untouched. That means the total input length (history + prefix + last user turn) can significantly exceed
target_input_lenand potentially the backend’s context window. This is more of a semantic/operational risk than a bug, but it would be good to clarify or enforce bounds so perf runs don’t accidentally push requests beyond supported limits. - The new
converge_to_token_lenhelper does repeated decode/re-encode loops over long token sequences for both random prompts and prefixes. At largetarget_input_lenvalues and high request counts, this can become a noticeable CPU cost on the driver side, and the drift-warning path infit_prefix_to_budgetmay produce a lot of log output if a real tokenizer fails to converge, which could impact perf runs.
🧪 Verification Advice
- Add a targeted test that exercises
prefix_filetogether withinput_len_mode='drop'andtarget_input_len, making the current behavior explicit and providing a guard rail if you decide to change semantics (e.g., by rejecting this combination or warning at construction). - Extend prefix-injection integration tests beyond
line_by_line/share_gpt_zhto includeopenqaandlongalpaca, using small local fixtures and a fake tokenizer. This will help ensureprepare_messagesandapply_prefix_to_messagesbehave as expected across all wired datasets. - Consider a lightweight perf/CLI smoke test that uses
prefix_filethroughrun_perf_benchmark(with a fake tokenizer and tiny dataset) to validate the end-to-end wiring from CLIdataset_argsto the plugins.
💡 Thoughts & Suggestions
- It may be worth tightening or documenting the semantics around
prefix_file+input_len_mode='drop'so users don’t accidentally configure a long prefix that never applies. A schema-level validator or constructor warning would make intent clearer. - For multi-turn datasets like ShareGPT, consider whether
target_input_lenshould represent the total request length (including history) or just the final turn. If the former, introducing an optional cap on overall message tokens or documenting the current behavior explicitly would help avoid context overruns. - If prefix-based long-context perf runs become common at very high token counts, you might want to monitor and, if necessary, optimize
converge_to_token_len(e.g., by reducing retries when drift is small or caching converged slices for repeated budgets) to keep the driver overhead under control.
🤖 Generated by Qoder • View workflow run
Drop the direct unit tests of fit_prefix_to_budget / converge_to_token_len / gen_prompt_decode_to_target_len and the schema-only checks: their behaviour is already reachable through the plugin pipeline, so they duplicated coverage at a lower level. Tokenizer round-trip drift was the one case a reversible fake tokenizer could not reach, so it is now driven end-to-end via DriftingTokenizer (a token that decode() drops, matching the real skip_special_tokens cause). Verified by mutation: short-circuiting the convergence loop fails exactly this test (assert 9 == 10) while the other 11 pass. Config guardrails (missing / empty prefix_file, prefix_file without target_input_len) collapse into one parametrized case through plugin init. 21 tests / 6 classes -> 12 tests / 3 classes, 355 -> 240 lines.
There was a problem hiding this comment.
Pull request overview
Adds long-context prefix injection to the perf dataset pipeline so real-text prompts can be reliably expanded to 128K/256K-scale fixed input lengths via --dataset-args (prefix_file, prefix_role), alongside a small refactor to share the decode→re-encode convergence loop and extensive doc updates for the stress-test guide.
Changes:
- Add
prefix_file/prefix_roleto perf dataset args with validation and implement prefix injection in the dataset base pipeline (incl. tiling +/v1/completionsfallback). - Refactor the “decode→re-encode until token length converges” loop into a shared helper and reuse it for both random prompt generation and prefix fitting.
- Add end-to-end tests for prefix injection and update/restructure zh+en stress-test documentation accordingly.
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/perf/test_prefix_injection.py | New end-to-end tests covering prefix budgeting, tiling, drift convergence, and validation. |
| evalscope/perf/plugin/datasets/utils.py | Extract shared convergence helper and add prefix-budget fitting utility. |
| evalscope/perf/plugin/datasets/base.py | Load/tokenize prefix corpus once and inject prefix into prompts/messages based on role/template mode. |
| evalscope/perf/plugin/datasets/dataset_args.py | Add prefix_file / prefix_role to TextLengthArgs with cross-field validation. |
| evalscope/perf/plugin/datasets/line_by_line.py | Route string prompts through prepare_messages() to enable prefix injection. |
| evalscope/perf/plugin/datasets/openqa.py | Route prompts through prepare_messages() to enable prefix injection. |
| evalscope/perf/plugin/datasets/longalpaca.py | Route prompts through prepare_messages() to enable prefix injection. |
| evalscope/perf/plugin/datasets/share_gpt.py | Apply prefix injection to multi-message conversations (budgeted against last user turn). |
| docs/zh/user_guides/stress_test/quick_start.md | Update anchors and wording to match restructured stress-test docs. |
| docs/zh/user_guides/stress_test/parameters.md | Restructure parameter reference; document long-context prefix injection and related workflow sections. |
| docs/zh/user_guides/stress_test/multi_turn.md | Restructure multi-turn guide; fold orphan sections and align headings/anchors. |
| docs/zh/user_guides/stress_test/examples.md | Add prefix-injection examples and reorganize examples into workflow order. |
| docs/zh/third_party/deepsearchqa.md | Fix/adjust anchor reference to agent MCP guide section. |
| docs/zh/get_started/parameters.md | Fix/adjust anchor reference for Agent evaluation section. |
| docs/zh/advanced_guides/custom_dataset/embedding.md | Fix/adjust anchor reference into MTEB backend docs. |
| docs/zh/advanced_guides/custom_dataset/clip.md | Fix/adjust anchor reference into CLIP backend docs. |
| docs/en/user_guides/stress_test/quick_start.md | Update anchors and wording to match restructured stress-test docs. |
| docs/en/user_guides/stress_test/parameters.md | Restructure parameter reference; document long-context prefix injection and related workflow sections. |
| docs/en/user_guides/stress_test/multi_turn.md | Restructure multi-turn guide; fold orphan sections and align headings/anchors. |
| docs/en/user_guides/stress_test/examples.md | Add prefix-injection examples and reorganize examples into workflow order. |
| docs/en/advanced_guides/custom_dataset/embedding.md | Fix/adjust anchor reference into MTEB backend docs. |
| docs/en/advanced_guides/custom_dataset/clip.md | Fix/adjust anchor reference into CLIP backend docs. |
Close the parameter-combination and length-definition gaps in long-context prefix injection (issue #1524): - Reject `prefix_file` + `input_len_mode='drop'` at config time: `drop` leaves a zero prefix budget so the prefix would never be injected. - ShareGPT / multi-turn budget is now measured over the whole conversation (all turns), not just the last user turn; conversations longer than the target are dropped (warn-once) instead of silently truncated. `drop` is rejected for multi-turn since a conversation almost never lands on the target exactly. - line_by_line: fail fast when a JSON messages/request-body line is combined with length control, which would otherwise bypass it and mix input lengths. - user / plain-text (/v1/completions) paths count prefix and prompt independently, so the total may drift ~1 token at the BPE join; documented in zh/en guides. `prefix_role='system'` stays exact. Add regression tests: drop combination, whole-conversation budget, over-long drop, multi-turn drop rejection, and JSON-line fail-fast.
Only apply the whole-conversation length budget when a prefix_file is configured; without a prefix, ShareGPT keeps the legacy behavior of fitting/filtering the last user turn only (target_input_len cap/drop or the min/max_prompt_length filter). - share_gpt: route on _prefix_ids; drop the plugin-local drop guard, since the general validator already forbids prefix_file + input_len_mode='drop' and the no-prefix path legitimately supports drop-on-last-turn. - base.prepare_conversation: simplify to the prefix-only path (target/tokenizer are guaranteed when a prefix is set). - docs: clarify that ShareGPT uses last-turn fitting by default and the whole-conversation budget only under prefix_file. - tests: replace the drop-rejection test with no-prefix last-turn cap and drop-filter cases.
feat(perf): long-context prefix injection via
prefix_file/prefix_roleCloses #1524
What
Benchmarking 128K/256K-scale long contexts with real text was previously impossible:
randomgives fixed lengths but high-entropy meaningless tokens (prefix-cache hit rate / MTP acceptance rate unrealistic), while real instruction datasets are mostly 4K-8K tokens, so a 128Ktarget_input_lenfilters everything out.This PR adds prefix injection to the perf dataset pipeline: point
prefix_fileat a long text corpus, and the framework slices it to exactlytarget_input_len − prompt_lentokens, prepending it to each short prompt as asystem(default) orusermessage — every request hits the target length while keeping real-language entropy.evalscope perf \ --dataset openqa \ --tokenizer-path Qwen/Qwen2.5-0.5B-Instruct \ --dataset-args '{"target_input_len": 8192, "prefix_file": "long_text.txt", "prefix_role": "system"}' \ ...Key changes
Feature (
evalscope/perf/plugin/datasets/)dataset_args.py:TextLengthArgsgainsprefix_file/prefix_rolewith cross-field validation (prefix_filerequirestarget_input_len; unknown keys still fail fast).base.py: load + encode the corpus once at construction (auto-tile with a warning when shorter than the budget), compute the per-prompt token budget, inject into messages; plain-text concatenation fallback for/v1/completions(no chat template) with a warning.utils.py: extractconverge_to_token_len()as the shared decode→re-encode convergence skeleton;gen_prompt_decode_to_target_len(random dataset) and the newfit_prefix_to_budgetare now thin wrappers that only differ in theirfillstrategy (random vocab sampling vs. sequential corpus continuation). Removes the previously duplicated retry loop.openqa/longalpaca/line_by_line/ single-turn ShareGPT (net −16 lines in the four plugins).Tests (
tests/perf/test_prefix_injection.py)Docs (zh + en, second commit)
stress_test/examples.md: new "Long-context Benchmarking (Prefix Injection)" section; restructured 14 flat H2 sections into 7 workflow-ordered groups; fixed a bash line-continuation-comment bug.stress_test/parameters.md: restructured by workflow (input construction / load patterns / replay); new "Long-context Prefix Injection" reference.stress_test/multi_turn.md: folded orphan sections (--durationsoft exit, chat-template FAQ) into owning sections; moved swe_smith-only params under its dataset entry; synced the en table with the zh[int, int]range syntax.clip_benchmark/mteb/agent/native);make docsnow builds zh+en with zero warnings.Verification
make lintpasses;tests/perf/test_prefix_injection.py+test_request_generation.py: 34 passed./v1/completionsfallback ±1 token (concatenation-boundary token merge, documented in the parameter reference)make docs: zh + en Sphinx builds succeed with 0 warnings.