1010import subprocess
1111import sys
1212from typing import Any , AsyncIterator , Literal , TypedDict , cast
13+ import warnings
1314
1415from peft .tuners .lora .config import LoraConfig
1516import torch
@@ -115,7 +116,7 @@ def create_identity_lora(
115116 model_config = handler .identity_lora_model_config (base_config )
116117 with init_empty_weights ():
117118 model = AutoModelForCausalLM .from_config (
118- model_config , torch_dtype = torch .bfloat16 , trust_remote_code = True
119+ model_config , dtype = torch .bfloat16 , trust_remote_code = True
119120 )
120121 model .name_or_path = base_model
121122
@@ -142,8 +143,19 @@ def _skip_meta_to(
142143 return module
143144 return orig_to (module , * args , ** kwargs )
144145
145- with patch .object (torch .nn .Module , "to" , _skip_meta_to ):
146- peft_model = get_peft_model (model , peft_lora_config )
146+ with warnings .catch_warnings ():
147+ if bool (getattr (handler , "is_moe" , False )):
148+ warnings .filterwarnings (
149+ "ignore" ,
150+ message = (
151+ r"Unsupported layer type '.*MoeExperts.*' encountered, "
152+ r"proceed at your own risk\."
153+ ),
154+ category = UserWarning ,
155+ module = r"peft\.tuners\.tuners_utils" ,
156+ )
157+ with patch .object (torch .nn .Module , "to" , _skip_meta_to ):
158+ peft_model = get_peft_model (model , peft_lora_config )
147159
148160 os .makedirs (lora_path , exist_ok = True )
149161 peft_model .save_pretrained (lora_path )
@@ -209,6 +221,13 @@ def _on_child_process_exit(self, _error: RuntimeError) -> None:
209221 def _raise_if_child_failed (self ) -> None :
210222 self ._child_processes .raise_if_failed ()
211223
224+ def _status (self , message : str ) -> None :
225+ print (f"[ART Megatron] { message } " , flush = True )
226+
227+ @staticmethod
228+ def _display_path (path : str | os .PathLike [str ]) -> str :
229+ return str (Path (path ).resolve ())
230+
212231 @property
213232 def is_dedicated (self ) -> bool :
214233 return is_dedicated_mode (self .config )
@@ -410,6 +429,10 @@ def _adapter_exists_and_loads(self, lora_path: str) -> bool:
410429 return True
411430
412431 def _create_identity_lora (self , lora_path : str ) -> None :
432+ self ._status (
433+ "Preparing initial LoRA adapter "
434+ f"for { self .base_model } at { self ._display_path (lora_path )} "
435+ )
413436 create_identity_lora (
414437 self .base_model ,
415438 lora_path ,
@@ -531,6 +554,10 @@ async def _start_vllm_subprocess(
531554 os .makedirs (log_dir , exist_ok = True )
532555 self ._vllm_log_path = os .path .join (log_dir , "vllm-runtime.log" )
533556 self ._vllm_log_file = open (self ._vllm_log_path , "w" , buffering = 1 )
557+ self ._status (
558+ "Starting vLLM runtime "
559+ f"for { self .base_model } . Logs: { self ._display_path (self ._vllm_log_path )} "
560+ )
534561 self ._vllm_process = subprocess .Popen (
535562 managed_process_cmd (cmd ),
536563 cwd = str (get_vllm_runtime_working_dir ()),
@@ -581,6 +608,7 @@ async def _start_vllm_subprocess(
581608 ) from exc
582609 assert self ._vllm_process is not None
583610 assert self ._vllm_log_path is not None
611+ self ._status (f"vLLM runtime is ready at { self ._vllm_base_url } " )
584612 self ._child_processes .watch_popen (
585613 "vLLM runtime" ,
586614 self ._vllm_process ,
@@ -663,6 +691,7 @@ async def _sleep_runtime(self) -> None:
663691 import httpx
664692
665693 self ._raise_if_child_failed ()
694+ self ._status ("Sleeping vLLM runtime to free GPU memory for training" )
666695 async with httpx .AsyncClient () as client :
667696 response = await client .post (
668697 f"{ self ._vllm_base_url } /sleep" ,
@@ -672,11 +701,13 @@ async def _sleep_runtime(self) -> None:
672701 )
673702 response .raise_for_status ()
674703 self ._is_sleeping = True
704+ self ._status ("vLLM runtime is sleeping" )
675705
676706 async def _wake_runtime (self ) -> None :
677707 import httpx
678708
679709 self ._raise_if_child_failed ()
710+ self ._status ("Waking vLLM runtime" )
680711 async with httpx .AsyncClient () as client :
681712 response = await client .post (
682713 f"{ self ._vllm_base_url } /wake_up" ,
@@ -685,6 +716,7 @@ async def _wake_runtime(self) -> None:
685716 )
686717 response .raise_for_status ()
687718 self ._is_sleeping = False
719+ self ._status ("vLLM runtime is awake" )
688720
689721 async def register_lora_for_step (self , step : int , checkpoint_dir : str ) -> None :
690722 self ._raise_if_child_failed ()
@@ -764,6 +796,10 @@ async def _ensure_megatron_running(self) -> None:
764796 "w" ,
765797 buffering = 1 ,
766798 )
799+ self ._status (
800+ f"Starting Megatron worker on { num_gpus } GPU(s). "
801+ f"Logs: { self ._display_path (megatron_log_path )} "
802+ )
767803 self ._megatron_process = await asyncio .create_subprocess_exec (
768804 * managed_process_cmd (command ),
769805 cwd = str (project_root ),
@@ -778,6 +814,7 @@ async def _ensure_megatron_running(self) -> None:
778814 self ._megatron_process ,
779815 log_path = megatron_log_path ,
780816 )
817+ self ._status ("Megatron worker is initializing" )
781818
782819 def _clear_pending_jobs (self ) -> None :
783820 jobs_dir , _training_log_dir , _wake_lock_path = self ._megatron_runtime_paths ()
@@ -805,9 +842,10 @@ def _resolve_training_lora_path(self) -> str:
805842 async def _prepare_for_training (self ) -> str :
806843 self ._raise_if_child_failed ()
807844 self ._validate_megatron_dependencies ()
808- await self . _ensure_megatron_running ()
845+ # Shared-GPU Megatron must start after vLLM has released GPU memory.
809846 await self ._sleep_runtime ()
810847 gc_and_empty_cuda_cache ()
848+ await self ._ensure_megatron_running ()
811849
812850 lora_path = self ._resolve_training_lora_path ()
813851 self ._clear_pending_jobs ()
@@ -820,6 +858,10 @@ async def _publish_training_checkpoint(
820858 ) -> None :
821859 next_step = self ._latest_step + 1
822860 new_checkpoint_dir = get_step_checkpoint_dir (self .output_dir , next_step )
861+ self ._status (
862+ f"Publishing training checkpoint { next_step } "
863+ f"to { self ._display_path (new_checkpoint_dir )} "
864+ )
823865 os .makedirs (new_checkpoint_dir , exist_ok = True )
824866 shutil .copy (
825867 f"{ lora_path } /adapter_model.safetensors" ,
@@ -837,6 +879,7 @@ async def _publish_training_checkpoint(
837879 os .remove (wake_lock_path )
838880
839881 await self ._reload_adapter (new_checkpoint_dir , next_step )
882+ self ._status (f"Loaded checkpoint { next_step } into vLLM" )
840883
841884 async def start_openai_server (
842885 self , config : dev .OpenAIServerConfig | None
@@ -1015,18 +1058,28 @@ async def train_sft(
10151058 log_path = log_path ,
10161059 )
10171060 write_megatron_job (job , job_path = job_path )
1061+ self ._status (
1062+ f"Starting Megatron SFT job with { serialized_batches .num_batches } "
1063+ f"batch(es). First batch may take a few minutes while kernels compile. "
1064+ f"Training log: { self ._display_path (log_path )} "
1065+ )
10181066
10191067 async for result in stream_megatron_job (
10201068 job ,
10211069 job_path = job_path ,
10221070 process = self ._megatron_process ,
10231071 process_log_path = self ._megatron_log_path ,
10241072 ):
1025- yield {
1073+ metrics = {
10261074 "loss/train" : float (result ["loss" ]),
10271075 "loss/learning_rate" : float (result ["learning_rate" ]),
10281076 "loss/grad_norm" : float (result ["grad_norm" ]),
10291077 }
1078+ if "tokens_per_second" in result :
1079+ metrics ["throughput/step_trainer_tok_per_s" ] = float (
1080+ result ["tokens_per_second" ]
1081+ )
1082+ yield metrics
10301083
10311084 await self ._publish_training_checkpoint (lora_path = lora_path )
10321085 except BaseException :
0 commit comments