Files
miles/train_async.py
T

149 lines
6.4 KiB
Python

import asyncio
import logging
import os
from miles.ray.placement_group import create_rollout_components, create_training_models
from miles.ray.rollout.eval_dispatch import EvalDispatcher
from miles.ray.wiring import launch_worker_manager
from miles.utils import object_store
from miles.utils.arguments import parse_args, validate_async_off_policy_correction
from miles.utils.async_utils import eager_create_task
from miles.utils.audit_utils.process_identity import MainProcessIdentity
from miles.utils.data import remove_rollout_data_refs
from miles.utils.debug_utils.periodic_py_spy import maybe_start_periodic_pyspy_dump
from miles.utils.ft_utils.api_server.server import start_api_server
from miles.utils.ft_utils.mini_ft_controller import maybe_start_mini_ft_controller
from miles.utils.logging_utils import configure_logger
from miles.utils.misc import should_run_periodic_action
from miles.utils.tracking_utils.tracking import finish_tracking, init_tracking
logger = logging.getLogger(__name__)
# The framework supports other asynchronous approaches such as fully async (see miles/rollout/fully_async_rollout.py).
async def train(args):
assert not args.colocate, "Colocation is not supported for async training."
validate_async_off_policy_correction(args)
configure_logger(args, source=MainProcessIdentity())
maybe_start_periodic_pyspy_dump()
_worker_manager = launch_worker_manager(args)
object_store.init_instance(args, contribute_segment=False)
init_tracking(args)
# create the rollout manager, with sglang engines inside.
# need to initialize rollout manager first to calculate num_rollout
inference_controller, rollout_executor, num_rollout_per_epoch = await create_rollout_components(args)
# create the actor and critic models
actor_model, critic_model = await create_training_models(args, inference_controller, rollout_executor)
if args.api_server_port:
start_api_server(
args=args,
actor_model=actor_model,
inference_controller=inference_controller,
port=args.api_server_port,
ft_components=args.ft_components,
)
maybe_start_mini_ft_controller(args)
# always update weight first so that sglang has the loaded weights from training.
await actor_model.update_weights()
if args.check_weight_update_equal:
await inference_controller.check_weights(
action="compare",
allow_quant_error=args.check_weight_update_allow_quant_error,
selector=args.check_weight_update_selector,
skip_list=args.check_weight_update_skip_list,
)
eval_dispatcher = EvalDispatcher(args, actor_model, rollout_executor)
if args.eval_interval is not None and args.start_rollout_id == 0 and not args.skip_eval_before_train:
await inference_controller.prepare_eval()
await eval_dispatcher.dispatch(0, hf_dir=args.hf_checkpoint)
async def save_training_model(model, rollout_id, force_sync):
if args.use_critic and args.offload_train:
await model.onload()
await model.save_model(rollout_id, force_sync=force_sync)
if args.use_critic and args.offload_train:
await model.offload()
async def prepare_and_generate(rollout_id):
await inference_controller.prepare_rollout(rollout_id)
return await rollout_executor.get.remote(rollout_id)
# async train loop.
rollout_data_next_future = await eager_create_task(prepare_and_generate(args.start_rollout_id))
for rollout_id in range(args.start_rollout_id, args.num_rollout):
# Sync the last generation
if rollout_data_next_future is not None:
rollout_data_curr_ref = await rollout_data_next_future
# Start the next rollout early.
if rollout_id + 1 < args.num_rollout:
rollout_data_next_future = await eager_create_task(prepare_and_generate(rollout_id + 1))
if args.use_critic:
values = await critic_model.train(rollout_id, rollout_data_curr_ref)
if args.offload_train:
await critic_model.offload()
if rollout_id >= args.num_critic_only_steps:
await actor_model.train(rollout_id, rollout_data_curr_ref, external_data=values)
if args.offload_train:
await actor_model.offload()
else:
await actor_model.train(rollout_id, rollout_data_curr_ref)
remove_rollout_data_refs(args, rollout_data_curr_ref)
external_save = args.save_trigger_sentinel is not None and os.path.exists(args.save_trigger_sentinel)
if external_save or should_run_periodic_action(
rollout_id, args.save_interval, num_rollout_per_epoch, args.num_rollout
):
force_sync = external_save or rollout_id == args.num_rollout - 1
await save_training_model(actor_model, rollout_id, force_sync)
if args.use_critic:
await save_training_model(critic_model, rollout_id, force_sync)
await rollout_executor.save.remote(rollout_id)
if external_save:
os.remove(args.save_trigger_sentinel)
if (rollout_id + 1) % args.update_weights_interval == 0:
# sync generate before update weights to prevent update weight in the middle of generation
rollout_data_curr_ref = (await x) if (x := rollout_data_next_future) is not None else None
rollout_data_next_future = None
await actor_model.update_weights(rollout_id=rollout_id)
if should_run_periodic_action(rollout_id, args.eval_interval, num_rollout_per_epoch, args.num_rollout):
await inference_controller.prepare_eval()
await eval_dispatcher.dispatch(rollout_id, force=rollout_id == args.num_rollout - 1)
if (
args.debug_exit_after_rollout is not None
and (rollout_id - args.start_rollout_id + 1) >= args.debug_exit_after_rollout
):
logger.info(
"debug_exit_after_rollout=%d reached at rollout_id=%d, exiting",
args.debug_exit_after_rollout,
rollout_id,
)
break
await eval_dispatcher.drain()
await rollout_executor.dispose.remote()
await inference_controller.dispose()
await actor_model.dispose()
if critic_model is not None:
await critic_model.dispose()
if __name__ == "__main__":
args = parse_args()
try:
asyncio.run(train(args))
finally:
finish_tracking()