hiitsesh commited on
Commit
95fada9
·
1 Parent(s): 156f912

Force Qwen3 chat template to disable thinking mode via tokenizer patch

Browse files

Diagnosed the 100% clipped_ratio and entropy=0.07 collapse: Qwen3's chat
template was auto-inserting <think>...</think> prefixes, and the 0.6B
model was burning its entire 512-token completion budget on reasoning
text without ever emitting a tool call. The GRPOConfig flag
chat_template_kwargs={'enable_thinking': False} was silently dropped by
the installed TRL version.

Fix: load tokenizer with AutoTokenizer, wrap its apply_chat_template so
every invocation passes enable_thinking=False, then hand the patched
tokenizer to GRPOTrainer via processing_class (falling back to the older
tokenizer kwarg when needed). A startup probe logs whether <think>
tokens still appear in the generation prompt.

Made-with: Cursor

Files changed (1) hide show
  1. training/train_grpo.py +47 -1
training/train_grpo.py CHANGED
@@ -12,7 +12,7 @@ from pathlib import Path
12
  from typing import Literal
13
 
14
  from datasets import load_dataset
15
- from transformers import TrainerCallback
16
  from trl import GRPOConfig, GRPOTrainer
17
 
18
  REPO_ROOT = Path(__file__).resolve().parents[1]
@@ -434,6 +434,43 @@ class LiveLogHistoryCallback(TrainerCallback):
434
  self._write(state.log_history)
435
 
436
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
437
  def summarize_log_history(log_history):
438
  reward_rows = [row for row in log_history if "reward" in row]
439
  if not reward_rows:
@@ -481,11 +518,14 @@ def main():
481
  dataset = load_dataset("json", data_files={"train": args.train_file})
482
  training_args = build_config(args)
483
 
 
 
484
  print("Initializing GRPO Trainer...", flush=True)
485
  trainer_kwargs = {
486
  "model": args.model_name,
487
  "args": training_args,
488
  "train_dataset": dataset["train"],
 
489
  }
490
 
491
  if supports_env_factory:
@@ -499,6 +539,12 @@ def main():
499
  )
500
  trainer_kwargs["reward_funcs"] = [compatibility_reward_func]
501
 
 
 
 
 
 
 
502
  trainer = GRPOTrainer(**trainer_kwargs)
503
  trainer.add_callback(LiveLogHistoryCallback(args.metrics_json))
504
 
 
12
  from typing import Literal
13
 
14
  from datasets import load_dataset
15
+ from transformers import AutoTokenizer, TrainerCallback
16
  from trl import GRPOConfig, GRPOTrainer
17
 
18
  REPO_ROOT = Path(__file__).resolve().parents[1]
 
434
  self._write(state.log_history)
435
 
436
 
437
+ def _build_tokenizer_without_thinking(model_name: str):
438
+ """
439
+ Load the tokenizer and force Qwen3's chat template to skip the <think> block.
440
+
441
+ Qwen3 chat templates default to enable_thinking=True, which prefixes every
442
+ assistant turn with '<think>\\n</think>\\n\\n' and causes the model to burn
443
+ the entire completion budget on reasoning text without emitting a tool call.
444
+ TRL's GRPOConfig on older versions silently drops chat_template_kwargs, so
445
+ we enforce the flag by wrapping apply_chat_template at the tokenizer level.
446
+ """
447
+ tokenizer = AutoTokenizer.from_pretrained(model_name)
448
+ original_apply = tokenizer.apply_chat_template
449
+
450
+ def apply_no_thinking(*args, **kwargs):
451
+ kwargs.setdefault("enable_thinking", False)
452
+ return original_apply(*args, **kwargs)
453
+
454
+ try:
455
+ tokenizer.apply_chat_template = apply_no_thinking
456
+ probe = tokenizer.apply_chat_template(
457
+ [{"role": "user", "content": "ping"}],
458
+ tokenize=False,
459
+ add_generation_prompt=True,
460
+ )
461
+ if "<think>" in probe:
462
+ print(
463
+ "Warning: chat template still emits <think> tokens; thinking mode may be on.",
464
+ flush=True,
465
+ )
466
+ else:
467
+ print("Chat template confirmed with enable_thinking=False.", flush=True)
468
+ except Exception as exc:
469
+ print(f"Chat template probe failed: {exc}", flush=True)
470
+
471
+ return tokenizer
472
+
473
+
474
  def summarize_log_history(log_history):
475
  reward_rows = [row for row in log_history if "reward" in row]
476
  if not reward_rows:
 
518
  dataset = load_dataset("json", data_files={"train": args.train_file})
519
  training_args = build_config(args)
520
 
521
+ tokenizer = _build_tokenizer_without_thinking(args.model_name)
522
+
523
  print("Initializing GRPO Trainer...", flush=True)
524
  trainer_kwargs = {
525
  "model": args.model_name,
526
  "args": training_args,
527
  "train_dataset": dataset["train"],
528
+ "processing_class": tokenizer,
529
  }
530
 
531
  if supports_env_factory:
 
539
  )
540
  trainer_kwargs["reward_funcs"] = [compatibility_reward_func]
541
 
542
+ trainer_params = inspect.signature(GRPOTrainer.__init__).parameters
543
+ if "processing_class" not in trainer_params:
544
+ trainer_kwargs.pop("processing_class", None)
545
+ if "tokenizer" in trainer_params:
546
+ trainer_kwargs["tokenizer"] = tokenizer
547
+
548
  trainer = GRPOTrainer(**trainer_kwargs)
549
  trainer.add_callback(LiveLogHistoryCallback(args.metrics_json))
550