Skip to content

feat(openai): avoid re-rendering full chat history on multi-turn rollout (#1658) - #1663

Open
hsusul wants to merge 2 commits into
areal-project:mainfrom
hsusul:feat/incremental-prompt-prep-1658
Open

feat(openai): avoid re-rendering full chat history on multi-turn rollout (#1658)#1663
hsusul wants to merge 2 commits into
areal-project:mainfrom
hsusul:feat/incremental-prompt-prep-1658

Conversation

@hsusul

@hsusul hsusul commented Sep 2, 2026

Copy link
Copy Markdown

Description

Resolves #1658.

In multi-turn Agentic RL rollouts (such as SWE, search, reasoning, and tool-using agent episodes), trajectories frequently reach 50–200 turns. The OpenAI-compatible client (ArealOpenAI) and Data Proxy previously re-rendered Jinja2 chat templates and tokenized the entire message history on every turn from scratch.

For an episode of length $N$, cumulative messages processed scaled as $\sum_{i=1}^N (2i-1) = N^2$ ($O(N^2)$). At $N=200$, 40,000 messages were formatted, consuming over 1.1s of CPU time per episode and creating client-side rollout stalls that starve GPU inference backends (SGLang/vLLM).

This PR introduces an incremental prompt preparation mechanism (IncrementalPromptRenderer):

  1. Incremental Suffix Rendering: Reuses the parent interaction's rendered tokens and tokenizes only newly appended delta messages against a bounded synthetic context, preserving the exact generation prompt suffix.
  2. Token Parity Contract: An automated capability probe on first use ensures 100% token-for-token mathematical identity with canonical apply_chat_template across standard architectures (Qwen, ChatML, Llama-3, etc.).
  3. Safe Fallback: Dynamically falls back to canonical full-history rendering for complex/dynamic templates (e.g. dynamic <think> modifications), missing parent tokens, or multimodal processor workflows.
  4. Concat Mode Acceleration: Implemented render_concat_child_tokens for concat chat template mode, eliminating full-history re-tokenization during multi-turn concat rollouts.
  5. No Breaking Changes: Zero changes to public ArealOpenAI signatures, CLI arguments, or trajectory export structures.

Benchmarks & Scaling

Tested across episode lengths using Qwen/Qwen3-0.6B:

Turns ($N$) Full History (Baseline) Incremental (This PR) Speedup Parity Verified
10 turns 18.1 ms 14.7 ms 1.2x 100%
50 turns 107.7 ms 61.6 ms 1.7x 100%
100 turns 342.0 ms 121.9 ms 2.8x 100%
200 turns 1172.6 ms 248.6 ms 4.7x 100%

Verification Plan

  • Comprehensive unit and regression test suite added in tests/experimental/openai/test_prompt_renderer.py:
    • 1-turn, 2-turn, 5-turn, 20-turn, 50-turn incremental token identity vs canonical full-history apply_chat_template.
    • Single tool call, parallel tool calls, sequential tool calls, text-only conversational turns, thinking tags (<think>...</think>), custom system prompts.
    • Automated fallback validation for dynamic templates and legacy parent interactions.
    • Concat mode child token extraction parity vs _concat_prompt_token_ids_with_parent.
  • Verified all existing test suites in tests/experimental/openai/ (63 passed, 0 failed).
  • Passed all 16 pre-commit hooks (ruff check, ruff format, check-yaml, spdx, check-json, etc.).

- Use lazy evaluation in testing model path dictionaries to avoid eager downloads on test collection
- Safely handle PackageNotFoundError in version check helper functions
…out (areal-project#1658)

- Introduce IncrementalPromptRenderer for O(1) prompt preparation per turn in multi-turn rollouts
- Cache base prompt prefix tokens on InteractionWithTokenLogpReward.prompt_base_token_ids
- Probe tokenizer template capability on first use to ensure 100% token-for-token mathematical parity with full-history rendering
- Add incremental child token rendering in concat chat template mode
- Fall back safely to canonical full-history rendering for dynamic or unsupported templates and multimodal processor inputs
- Add comprehensive unit and regression test suite covering single/multi-tool calling, parallel tools, conversational turns, reasoning blocks, and fallback paths
@@ -15,7 +15,6 @@
from transformers import AutoConfig

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The modification of this file should have nothing to do with this PR, please remove it or submit it separately.

return -1


class IncrementalPromptRenderer:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once reasoning tokens are saved into parent_base_token_ids, they cannot be removed, and for some models like Qwen3, when building context with apply_chat_template, it may remove the previous reasoning content, which can lead to misalignment between the incremental path and the normal path.

delta_messages = tokenizer_messages[len(parent.messages) :]
parent_base = parent.prompt_base_token_ids
if parent_base is None:
parent_base = apply_chat_template(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the tools included in the second round of requests are inconsistent with those in the first round, applying only to the incremental part here will lead to a discrepancy in content between the two.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Avoid re-rendering the full chat history on every multi-turn tool rollout

2 participants