fix ppo callbacks - #9872
Open
leuitong wants to merge 1 commit into
Open
Conversation
This fix restores user callbacks on the PPO trainer that TRL's PPOTrainer.__init__ silently discards.
leuitong
marked this pull request as draft
August 7, 2026 13:55
leuitong
marked this pull request as ready for review
August 7, 2026 13:56
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This fix restores user callbacks on the PPO trainer that TRL's PPOTrainer.init silently discards.
PR type
PR information
Problem
Swift's user-trainer callbacks (e.g. early_stop, lisa, adalora, activation_cpu_offload, perf_log or other user-define callbacks) were silently dropped and never fired during PPO training.
Root cause: SwiftMixin.init registers these callbacks on the HF Trainer by passing callbacks=... into HFTrainer.init (swift/trainers/mixin.py). But the swift PPOTrainer then invokes TRL's PPOTrainer.init (ppo_trainer_init) with a new_kwargs whitelist that
did not include callbacks. TRL's init then runs:
self.callbacks = default_callbacks if callbacks is None else default_callbacks + callbacks
self.callback_handler = CallbackHandler(self.callbacks, ...)
Because callbacks is None, TRL rebuilds callback_handler from scratch with only default/integration callbacks, discarding every callback the SwiftMixin had just registered. Consequently, features relying on these callbacks (early stopping, LISA layer swapping,
activation CPU offload, perf logging) were inactive for PPO runs.
Fix
In swift/rlhf_trainers/ppo_trainer.py:
for callback in self.args.callbacks:
self.add_callback(callbacks_map[callback](self.args, self))
This mirrors SwiftMixin._get_callbacks exactly (instantiate callbacks_map[name](args, trainer)), sharing the same construction paths/signatures across swift versions. Placing it after TRL's init attaches to the new callback_handler instead of the discarded one. At
this point the handler holds only default/reporting callbacks, so re-adding causes no dedup warnings and no double-invocation (the originals died with the old handler).
Experiment results
Paste your experiment result here(if needed).
A custom callback was specified but did not take effect in the logs.
--callbacks custom_post_training_callback{'eps': 0, 'objective/kl': '3.323', 'objective/entropy': '2083', 'objective/non_score_reward': '-0.1661', 'objective/rlhf_reward': '2.631', 'objective/scores': '2.797', 'policy/approxkl_avg': '0.0006469', 'policy/clipfrac_avg': '0.001334', 'loss/policy_avg': '-0.002931', 'loss/value_avg': '1.114', 'val/clipfrac_avg': '0.01949', 'policy/entropy_avg': '0.6767', 'val/ratio': '0.9998', 'val/ratio_var': '8e-08', 'val/num_eos_tokens': 3, 'lr': '5e-06', 'episode': 16, 'epoch': '1.6', 'global_step/max_steps': '2/3', 'elapsed_time': '6m 42s', 'remaining_time': '3m 21s', 'memory(GiB)': '114.2', 'train_speed(s/it)': '201.1'} [2026-08-06 17:16:00] Train: 33%|███▎ | 1/3 [06:42<06:43, 201.99s/it] Train: 33%|███▎ | 1/3 [06:42<06:43, 201.99s/it] Train: 67%|██████▋ | 2/3 [06:42<03:21, 201.00s/it]2026-08-06 17:16:00,340 - accelerate.accelerator - INFO - {'eps': 0, 'objective/kl': '3.431', 'objective/entropy': '1856', 'objective/non_score_reward': '-0.1715', 'objective/rlhf_reward': '3.75', 'objective/scores': '3.922', 'policy/approxkl_avg': '0.0006374', 'policy/clipfrac_avg': '0.001667', 'loss/policy_avg': '-0.001021', 'loss/value_avg': '1.103', 'val/clipfrac_avg': '0.006758', 'policy/entropy_avg': '0.6533', 'val/ratio': '1', 'val/ratio_var': '4.2e-07', 'val/num_eos_tokens': 4, 'lr': '2.5e-06', 'episode': 24, 'epoch': '2.4', 'global_step/max_steps': '3/3', 'elapsed_time': '10m 4s', 'remaining_time': '0s', 'memory(GiB)': '114.2', 'train_speed(s/it)': '201.3'} Train: 67%|██████▋ | 2/3 [10:03<03:21, 201.00s/it] Train: 67%|██████▋ | 2/3 [10:03<03:21, 201.00s/it] Train: 100%|██████████| 3/3 [10:03<00:00, 201.28s/it][INFO:swift] Saving model checkpoint to /mnt/ais/checkpoint/0000000002/179494994/1/v0-20260806-170439/checkpoint-3 Train: 100%|██████████| 3/3 [10:04<00:00, 201.51s/it] [INFO:swift] last_model_checkpoint: /mnt/ais/checkpoint/0000000002/179494994/1/v0-20260806-170439/checkpoint-3 [INFO:swift] best_model_checkpoint: None [INFO:swift] images_dir: /mnt/ais/checkpoint/0000000002/179494994/1/v0-20260806-170439/images [2026-08-06 17:16:01] 2026-08-06 17:16:01,126 - matplotlib.font_manager - INFO - generated new fontManager [2026-08-06 17:16:02] [INFO:swift] End time of running main: 2026-08-06 17:16:02.901463 [rank0]:[W806 17:16:03.662950460 ProcessGroupNCCL.cpp:1553] Warning: WARNING: destroy_process_group() was not called before program exit, which can leak resources. For more info, please see https://pytorch.org/docs/stable/distributed.html#shutdown (function operator())now fixed: