Skip to content

Commit a2d7eaf

Browse files
Liang (AIML-Compute) Hechanglan
authored andcommitted
[AXLearnFT] Introducing ft_trainer
GitOrigin-RevId: c9db5cc
1 parent 9b008aa commit a2d7eaf

1 file changed

Lines changed: 137 additions & 0 deletions

File tree

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# Copyright © 2023 Apple Inc.
2+
3+
"""Main function for launching the fault tolerant trainer.
4+
5+
This is a simplified wrapper that executes the trainer in a subprocess for
6+
fault tolerance and process isolation.
7+
8+
Example usage:
9+
python3 -m axlearn.common.launch_ft_trainer_main \
10+
--module=text.gpt.c4_trainer \
11+
--config=fuji-70B-v2-flash \
12+
--trainer_dir=gs://bucket/experiments/test \
13+
--data_dir=gs://bucket/tensorflow_datasets \
14+
--jax_backend=tpu \
15+
--max_restarts=5
16+
"""
17+
18+
import os
19+
import signal
20+
import subprocess
21+
import sys
22+
from typing import Optional
23+
24+
from absl import app, flags, logging
25+
26+
# Import launch and launch_trainer to get the FLAGS definitions.
27+
from axlearn.common import launch, launch_trainer, measurement # pylint: disable=unused-import
28+
29+
flags.DEFINE_integer(
30+
"max_restarts", 3, "Maximum number of times to restart the trainer subprocess on failure"
31+
)
32+
33+
34+
def forward_signal_to_trainer(trainer_process: Optional[subprocess.Popen]):
35+
"""Set up signal handler to forward signals to the trainer subprocess."""
36+
37+
def signal_handler(signum, *_):
38+
"""Forward signals to the trainer subprocess."""
39+
if trainer_process is not None:
40+
logging.info("Forwarding signal %d to trainer subprocess", signum)
41+
try:
42+
trainer_process.send_signal(signum)
43+
except ProcessLookupError:
44+
pass
45+
else:
46+
logging.info("Ft trainer received signal %d, no active trainer process", signum)
47+
sys.exit(1)
48+
49+
# Only register the SIGTERM for the preemption.
50+
signal.signal(signal.SIGTERM, signal_handler)
51+
52+
53+
def run_ft_trainer():
54+
"""Run the trainer as a subprocess for fault tolerance."""
55+
logging.info("Starting fault tolerant trainer...")
56+
57+
trainer_process: Optional[subprocess.Popen] = None
58+
59+
# Build command to run the original trainer
60+
cmd = [sys.executable, "-m", "axlearn.common.launch_trainer_main"]
61+
62+
# Pass through all other arguments except FT-specific ones
63+
for arg in sys.argv[1:]:
64+
if not arg.startswith("--max_restarts"):
65+
cmd.append(arg)
66+
67+
max_restarts = flags.FLAGS.max_restarts
68+
restart_count = 0
69+
70+
while restart_count <= max_restarts:
71+
if restart_count > 0:
72+
logging.info("Ft trainer restart attempt %d/%d", restart_count, max_restarts)
73+
74+
logging.info("Ft trainer starting trainer subprocess: %s", " ".join(cmd))
75+
76+
try:
77+
# Run trainer as subprocess
78+
with subprocess.Popen(
79+
cmd,
80+
stdout=subprocess.PIPE,
81+
stderr=subprocess.STDOUT,
82+
text=True,
83+
bufsize=1, # Line buffered
84+
env=os.environ.copy(),
85+
) as process:
86+
# Update global process reference and set up signal handler
87+
trainer_process = process
88+
forward_signal_to_trainer(trainer_process)
89+
# Stream output in real-time
90+
if process.stdout:
91+
for line in iter(process.stdout.readline, ""):
92+
if line:
93+
# Use print instead of logging to avoid double formatting.
94+
print(f"[FT_TRAINER] {line}", end="", flush=True)
95+
96+
# Wait for completion
97+
returncode = process.wait()
98+
99+
# Clear process reference
100+
trainer_process = None
101+
102+
if returncode == 0:
103+
logging.info("Trainer completed successfully in ft trainer")
104+
return
105+
else:
106+
logging.error("Trainer process exited with code %d", returncode)
107+
if restart_count < max_restarts:
108+
restart_count += 1
109+
continue
110+
else:
111+
logging.error(
112+
"Trainer process reachees max restarts (%d). Exiting...", max_restarts
113+
)
114+
sys.exit(returncode)
115+
116+
except (subprocess.SubprocessError, OSError) as e:
117+
logging.error("Ft trainer failed to run trainer subprocess: %s", e)
118+
# Clear process reference on exception
119+
trainer_process = None
120+
if restart_count < max_restarts:
121+
restart_count += 1
122+
continue
123+
else:
124+
logging.error(
125+
"Trainer process reaches max restarts (%d) after exception. Exiting...",
126+
max_restarts,
127+
)
128+
sys.exit(1)
129+
130+
131+
def main(_):
132+
run_ft_trainer()
133+
134+
135+
if __name__ == "__main__":
136+
measurement.define_flags()
137+
app.run(main)

0 commit comments

Comments
 (0)