Skip to content

Commit d9a6fdf

Browse files
lzy-eduwanglei19991004zhaoyinglia
authored
Add perf monitor integration (flagos-ai#1216)
### PR Category Train ### PR Types New Features, Improvements ### PR Description This PR ports the perf monitor integration from flagos-ai#1180 onto the latest main branch. It includes: - Perf monitor integration for the training flow - Runner and launcher updates for perf monitor support - Compatibility updates for the latest main branch Based on: - flagos-ai#1180 --------- Co-authored-by: wanglei19991004 <13453558035@163.com> Co-authored-by: zhaoyingli <86812880+zhaoyinglia@users.noreply.github.com>
1 parent 53a3293 commit d9a6fdf

13 files changed

Lines changed: 1088 additions & 0 deletions

File tree

flagscale/runner/backend/backend_megatron.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,18 @@
1010
)
1111
from flagscale.runner.utils import get_pkg_dir, logger, parse_hostfile, resolve_path
1212

13+
PERF_MONITOR_RUNNER_KEYS = (
14+
"enable_perf_monitor",
15+
"perf_log_interval",
16+
"perf_log_dir",
17+
"perf_console_output",
18+
"perf_log_format",
19+
"perf_memory_tracking",
20+
"perf_breakdown",
21+
"perf_max_log_files",
22+
"perf_model_type",
23+
)
24+
1325

1426
class MegatronBackend(BackendBase):
1527
def __init__(self, config: DictConfig):
@@ -20,6 +32,7 @@ def __init__(self, config: DictConfig):
2032

2133
def _prepare(self):
2234
_update_config_train(self.config)
35+
self._prepare_perf_monitor_config()
2336
self.user_args = _get_args_megatron(self.config)
2437
self.rdzv_id = datetime.now().strftime("%Y%m%d_%H%M%S.%f")
2538
self.user_envs = self.config.experiment.get("envs", {})
@@ -30,6 +43,22 @@ def _prepare(self):
3043
logger.info("\n************** configuration **************")
3144
logger.info(f"\n{OmegaConf.to_yaml(self.config)}")
3245

46+
def _prepare_perf_monitor_config(self):
47+
system_config = self.config.train.system
48+
runner_config = self.config.experiment.runner
49+
50+
OmegaConf.set_struct(system_config, False)
51+
for key in PERF_MONITOR_RUNNER_KEYS:
52+
if runner_config.get(key, None) is not None and system_config.get(key, None) is None:
53+
system_config[key] = runner_config.get(key)
54+
55+
if system_config.get("perf_log_dir", None) is not None:
56+
system_config.perf_log_dir = resolve_path(
57+
system_config.perf_log_dir, "system.perf_log_dir"
58+
)
59+
elif system_config.get("enable_perf_monitor", False):
60+
system_config.perf_log_dir = os.path.join(system_config.logging.log_dir, "perf_monitor")
61+
3362
def generate_run_script(
3463
self,
3564
config,
@@ -78,6 +107,8 @@ def generate_run_script(
78107
f.write(f"mkdir -p {system_config.logging.details_dir}\n")
79108
f.write(f"mkdir -p {system_config.logging.tensorboard_dir}\n")
80109
f.write(f"mkdir -p {system_config.logging.wandb_save_dir}\n")
110+
if system_config.get("perf_log_dir", None):
111+
f.write(f"mkdir -p {system_config.perf_log_dir}\n")
81112
f.write("\n")
82113
f.write(f"cd {pkg_dir}\n")
83114
f.write("\n")

flagscale/runner/launcher/launcher_ssh.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,24 @@ def _get_runner_cmd_train(
116116
del runner_args["nsys_rep_file_path"]
117117
if "deploy" in runner_args:
118118
del runner_args["deploy"]
119+
if "enable_perf_monitor" in runner_args:
120+
del runner_args["enable_perf_monitor"]
121+
if "perf_log_interval" in runner_args:
122+
del runner_args["perf_log_interval"]
123+
if "perf_log_dir" in runner_args:
124+
del runner_args["perf_log_dir"]
125+
if "perf_console_output" in runner_args:
126+
del runner_args["perf_console_output"]
127+
if "perf_log_format" in runner_args:
128+
del runner_args["perf_log_format"]
129+
if "perf_memory_tracking" in runner_args:
130+
del runner_args["perf_memory_tracking"]
131+
if "perf_breakdown" in runner_args:
132+
del runner_args["perf_breakdown"]
133+
if "perf_max_log_files" in runner_args:
134+
del runner_args["perf_max_log_files"]
135+
if "perf_model_type" in runner_args:
136+
del runner_args["perf_model_type"]
119137
runner_args["rdzv_id"] = rdzv_id
120138
# runner_args["master_addr"] = master_addr
121139
# runner_args["master_port"] = master_port

flagscale/train/megatron/training/arguments_fs.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -889,6 +889,73 @@ def _add_vision_args(parser):
889889
)
890890
return parser
891891

892+
def _add_perf_monitor_args(parser):
893+
group = parser.add_argument_group(title="flagscale perf monitor")
894+
895+
group.add_argument(
896+
"--enable-perf-monitor",
897+
action="store_true",
898+
default=False,
899+
help="Enable FlagScale performance monitoring during training.",
900+
)
901+
group.add_argument(
902+
"--perf-log-interval",
903+
type=int,
904+
default=10,
905+
help="Log performance metrics every N iterations.",
906+
)
907+
group.add_argument(
908+
"--perf-log-dir",
909+
type=str,
910+
default=None,
911+
help="Directory used to save performance monitor logs.",
912+
)
913+
group.add_argument(
914+
"--perf-console-output",
915+
action="store_true",
916+
default=False,
917+
help="Also emit performance monitor logs to stdout on rank 0.",
918+
)
919+
group.add_argument(
920+
"--perf-log-format",
921+
type=str,
922+
choices=["text", "json", "both"],
923+
default="both",
924+
help="Output format for performance monitor files.",
925+
)
926+
group.add_argument(
927+
"--perf-memory-tracking",
928+
dest="perf_memory_tracking",
929+
action="store_true",
930+
help="Track CUDA memory usage in the performance monitor.",
931+
)
932+
group.add_argument(
933+
"--no-perf-memory-tracking",
934+
dest="perf_memory_tracking",
935+
action="store_false",
936+
help="Disable CUDA memory tracking in the performance monitor.",
937+
)
938+
group.set_defaults(perf_memory_tracking=True)
939+
group.add_argument(
940+
"--perf-breakdown",
941+
action="store_true",
942+
default=False,
943+
help="Include estimated component breakdowns in performance logs.",
944+
)
945+
group.add_argument(
946+
"--perf-max-log-files",
947+
type=int,
948+
default=10,
949+
help="Maximum number of historical performance log files to keep.",
950+
)
951+
group.add_argument(
952+
"--perf-model-type",
953+
type=str,
954+
choices=["auto", "gpt", "llama", "qwen", "mixtral", "aquila", "moe"],
955+
default="auto",
956+
help="Model type hint used for FLOPS estimation.",
957+
)
958+
return parser
892959

893960
def _add_flagos_args(parser):
894961
group = parser.add_argument_group(title="flagscale fl")
@@ -977,4 +1044,5 @@ def add_flagscale_arguments(parser):
9771044
parser = _add_peft_args(parser)
9781045
parser = _add_flagos_args(parser)
9791046
parser = _add_flagscale_specific_args(parser)
1047+
parser = _add_perf_monitor_args(parser)
9801048
return parser

flagscale/train/megatron/training/training.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,12 @@ def set_startup_timestamps(program_start=None, main_entry=None):
257257
from megatron.training.global_vars import get_spiky_loss_detector
258258
from megatron.training.peft import PEFT # Import PEFT from peft module
259259
from megatron.plugin.hetero.parallel_context import get_parallel_context
260+
from flagscale.train.perf_monitor.hooks import (
261+
initialize_perf_monitor,
262+
perf_monitor_end_iteration,
263+
perf_monitor_end_training,
264+
perf_monitor_start_iteration,
265+
)
260266

261267
from megatron.plugin.platform import get_platform
262268
cur_platform = get_platform()
@@ -2987,6 +2993,7 @@ def train(
29872993
timers('interval-time', log_level=0).start(barrier=True)
29882994
print_datetime('before the start of training step')
29892995
report_memory_flag = True
2996+
perf_callback = initialize_perf_monitor(args)
29902997
pre_hook_enabled = False
29912998
should_exit = False
29922999
exit_code = 0
@@ -3019,6 +3026,7 @@ def train(
30193026
num_microbatches = get_num_microbatches()
30203027

30213028
########## FlagScale Begin ##########
3029+
writer = get_tensorboard_writer()
30223030
wandb_writer = get_wandb_writer()
30233031
if wandb_writer and args.wandb_log_model:
30243032
# wandb.watch's log_freg needs to take the accumulated number of microbatches into account
@@ -3278,6 +3286,8 @@ def trace_handler(p):
32783286
num_zeros_in_grad = 0
32793287
max_attention_logit = None
32803288
else:
3289+
if perf_callback is not None:
3290+
perf_monitor_start_iteration(iteration)
32813291
ft_integration.on_training_step_start()
32823292
(
32833293
loss_dict,
@@ -3292,6 +3302,8 @@ def trace_handler(p):
32923302
forward_step_func, train_data_iterator, model, optimizer, opt_param_scheduler, config, forward_backward_func, iteration=iteration
32933303
)
32943304
ft_integration.on_training_step_end()
3305+
if perf_callback is not None:
3306+
perf_monitor_end_iteration(iteration, writer, wandb_writer)
32953307
if should_checkpoint:
32963308
save_checkpoint_and_time(
32973309
iteration,
@@ -3569,6 +3581,7 @@ def trace_handler(p):
35693581

35703582
# Flush TensorBoard, WandB writers and one-logger.
35713583
writer = get_tensorboard_writer()
3584+
perf_monitor_end_training(writer, wandb_writer)
35723585
if writer:
35733586
writer.flush()
35743587

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""FlagScale performance monitor utilities."""
2+
3+
from .hooks import (
4+
get_perf_monitor,
5+
initialize_perf_monitor,
6+
perf_monitor_end_iteration,
7+
perf_monitor_end_training,
8+
perf_monitor_start_iteration,
9+
)
10+
from .perf_metrics import FLOPSMeasurementCallback, PerformanceMonitor
11+
12+
__all__ = [
13+
"FLOPSMeasurementCallback",
14+
"PerformanceMonitor",
15+
"get_perf_monitor",
16+
"initialize_perf_monitor",
17+
"perf_monitor_end_iteration",
18+
"perf_monitor_end_training",
19+
"perf_monitor_start_iteration",
20+
]
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"""FLOPS estimation helpers for performance monitoring."""
2+
3+
from __future__ import annotations
4+
5+
6+
class FLOPSFormulas:
7+
"""Collection of approximate transformer FLOPS formulas."""
8+
9+
@staticmethod
10+
def attention_flops(batch_size, seq_length, hidden_size, num_attention_heads):
11+
head_dim = hidden_size // max(num_attention_heads, 1)
12+
qkv_flops = 3 * 2 * batch_size * seq_length * hidden_size * hidden_size
13+
score_flops = 2 * batch_size * num_attention_heads * seq_length * seq_length * head_dim
14+
value_flops = 2 * batch_size * num_attention_heads * seq_length * seq_length * head_dim
15+
out_flops = 2 * batch_size * seq_length * hidden_size * hidden_size
16+
return qkv_flops + score_flops + value_flops + out_flops
17+
18+
@staticmethod
19+
def gqa_attention_flops(
20+
batch_size, seq_length, hidden_size, num_attention_heads, num_query_groups
21+
):
22+
head_dim = hidden_size // max(num_attention_heads, 1)
23+
kv_hidden_size = head_dim * max(num_query_groups, 1)
24+
q_flops = 2 * batch_size * seq_length * hidden_size * hidden_size
25+
kv_flops = 2 * 2 * batch_size * seq_length * hidden_size * kv_hidden_size
26+
score_flops = 2 * batch_size * num_attention_heads * seq_length * seq_length * head_dim
27+
value_flops = 2 * batch_size * num_attention_heads * seq_length * seq_length * head_dim
28+
out_flops = 2 * batch_size * seq_length * hidden_size * hidden_size
29+
return q_flops + kv_flops + score_flops + value_flops + out_flops
30+
31+
@staticmethod
32+
def ffn_flops(batch_size, seq_length, hidden_size, ffn_hidden_size, use_swiglu=False):
33+
if use_swiglu:
34+
gate_flops = 2 * batch_size * seq_length * hidden_size * ffn_hidden_size
35+
up_flops = 2 * batch_size * seq_length * hidden_size * ffn_hidden_size
36+
swiglu_flops = batch_size * seq_length * ffn_hidden_size
37+
down_flops = 2 * batch_size * seq_length * ffn_hidden_size * hidden_size
38+
return gate_flops + up_flops + swiglu_flops + down_flops
39+
40+
up_flops = 2 * batch_size * seq_length * hidden_size * ffn_hidden_size
41+
down_flops = 2 * batch_size * seq_length * ffn_hidden_size * hidden_size
42+
return up_flops + down_flops
43+
44+
@staticmethod
45+
def moe_flops(
46+
batch_size,
47+
seq_length,
48+
hidden_size,
49+
ffn_hidden_size,
50+
num_experts,
51+
top_k,
52+
use_swiglu=False,
53+
):
54+
router_flops = 2 * batch_size * seq_length * hidden_size * num_experts
55+
active_tokens = batch_size * seq_length * top_k
56+
if use_swiglu:
57+
expert_flops = (
58+
3 * 2 * active_tokens * hidden_size * ffn_hidden_size
59+
+ active_tokens * ffn_hidden_size
60+
)
61+
else:
62+
expert_flops = 4 * active_tokens * hidden_size * ffn_hidden_size
63+
return router_flops + expert_flops
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""Training-loop hooks for the performance monitor."""
2+
3+
from __future__ import annotations
4+
5+
import torch
6+
7+
from flagscale.train.perf_monitor.perf_metrics import FLOPSMeasurementCallback
8+
9+
_perf_monitor_callback = None
10+
11+
12+
def initialize_perf_monitor(args):
13+
"""Initialize the global performance monitor if enabled."""
14+
global _perf_monitor_callback
15+
16+
if not getattr(args, "enable_perf_monitor", False):
17+
_perf_monitor_callback = None
18+
return None
19+
20+
log_interval = getattr(args, "perf_log_interval", 10)
21+
_perf_monitor_callback = FLOPSMeasurementCallback(args, log_interval=log_interval)
22+
23+
if not torch.distributed.is_initialized() or torch.distributed.get_rank() == 0:
24+
print(f"[Performance Monitor] Initialized with log interval: {log_interval}")
25+
26+
return _perf_monitor_callback
27+
28+
29+
def get_perf_monitor():
30+
return _perf_monitor_callback
31+
32+
33+
def perf_monitor_start_iteration(iteration):
34+
if _perf_monitor_callback is not None:
35+
_perf_monitor_callback.on_train_batch_start(iteration)
36+
37+
38+
def perf_monitor_end_iteration(iteration, writer=None, wandb_writer=None):
39+
if _perf_monitor_callback is not None:
40+
_perf_monitor_callback.on_train_batch_end(iteration, writer, wandb_writer)
41+
42+
43+
def perf_monitor_end_training(writer=None, wandb_writer=None):
44+
global _perf_monitor_callback
45+
if _perf_monitor_callback is not None:
46+
_perf_monitor_callback.on_train_end(writer, wandb_writer)
47+
_perf_monitor_callback = None

0 commit comments

Comments
 (0)