#7798·verl

[Bug] Qwen3.5 system-only prompt still fails through Continuous Token

Author: zzzzzzzxhCreated Sep 9, 2026Updated Sep 14, 2026

Environment

  • verl main at cf1649b2244a96c0ec7abea50cda9df291ee411b
  • transformers 5.10.4
  • torch 2.11.0+cu130
  • Qwen/Qwen3.5-9B processor

Reproduction

This reproducer follows the executor call used by AgentLoopWorker.ct_build_initial_tokens; it does not call apply_chat_template directly:

python
import asyncio
from transformers import AutoProcessor
from verl.utils.tokenizer.continuous_token_wiring import create_continuous_token_builder

MODEL = "/path/to/Qwen3.5-9B"

async def main():
    processor = AutoProcessor.from_pretrained(MODEL, trust_remote_code=True)
    builder = create_continuous_token_builder(
        processor.tokenizer,
        hf_model_type="qwen3_5",
        processor=processor,
    )
    messages = [{"role": "system", "content": "system instruction"}]
    return await asyncio.get_running_loop().run_in_executor(
        None,
        lambda: builder.build_initial_tokens(messages),
    )

asyncio.run(main())

Observed behavior

On clean main at cf1649b2:

builder: QwenVLContinuousTokenBuilder
jinja2.exceptions.TemplateError: System message must be at the beginning.

The traceback goes through:

AgentLoopWorker.ct_build_initial_tokens
-> QwenVLContinuousTokenBuilder.build_initial_tokens
-> render_tokens_with_mm
-> verl.utils.tokenizer.chat_template.apply_chat_template
-> processor.apply_chat_template

The same call on a patched tree returns 12 token IDs.

The native Qwen3.5 processor rejects a system-only history with No user query found in messages; the verl wrapper is expected to adapt this valid input by inserting and removing a synthetic user span.

Expected behavior

If system-only histories are supported for Continuous Token or SFT, the unified Continuous Token path should render the history without raising a template error. If they are intentionally unsupported, please document that constraint and identify the supported message shape.

This is a focused follow-up to #7725, isolating the behavior after Continuous Token became the default rollout path in #6804.