From 2e375b0b03203f7098f0575dca713017f5f7a2ac Mon Sep 17 00:00:00 2001 From: yhna940 Date: Sat, 22 Jun 2024 17:50:13 +0900 Subject: [PATCH 01/27] Add torch profiler --- src/accelerate/accelerator.py | 13 ++++++++ src/accelerate/utils/__init__.py | 1 + src/accelerate/utils/dataclasses.py | 39 ++++++++++++++++++++++++ tests/test_kwargs_handlers.py | 46 ++++++++++++++++++++++++++++- 4 files changed, 98 insertions(+), 1 deletion(-) diff --git a/src/accelerate/accelerator.py b/src/accelerate/accelerator.py index 9005e1fb563..f5c3c5fbdf3 100755 --- a/src/accelerate/accelerator.py +++ b/src/accelerate/accelerator.py @@ -64,6 +64,7 @@ LoggerType, MegatronLMPlugin, PrecisionType, + ProfileKwargs, ProjectConfiguration, RNGType, TorchDynamoPlugin, @@ -341,6 +342,7 @@ def __init__( self.init_handler = None self.fp8_recipe_handler = None self.autocast_handler = None + self.profile_handler = None self.has_lomo_optimizer = False if kwargs_handlers is not None: @@ -373,6 +375,11 @@ def __init__( raise ValueError("You can only pass one `AutocastKwargs` in `kwargs_handler`.") else: self.autocast_handler = handler + elif isinstance(handler, ProfileKwargs): + if self.profile_handler is not None: + raise ValueError("You can only pass one `ProfileKwargs` in `kwargs_handler`.") + else: + self.profile_handler = handler kwargs = self.init_handler.to_kwargs() if self.init_handler is not None else {} self.state = AcceleratorState( @@ -3356,6 +3363,12 @@ def autocast(self, cache_enabled: bool = False, autocast_handler: AutocastKwargs yield autocast_context.__exit__(*sys.exc_info()) + @contextmanager + def profile(self, profile_handler: ProfileKwargs | None = None): + if profile_handler is None: + profile_handler = self.profile_handler or ProfileKwargs() + yield from profile_handler.build() + @property def optimizer_step_was_skipped(self): """ diff --git a/src/accelerate/utils/__init__.py b/src/accelerate/utils/__init__.py index 66595e9b87b..387099c7b4a 100644 --- a/src/accelerate/utils/__init__.py +++ b/src/accelerate/utils/__init__.py @@ -48,6 +48,7 @@ LoggerType, MegatronLMPlugin, PrecisionType, + ProfileKwargs, ProjectConfiguration, RNGType, SageMakerDistributedType, diff --git a/src/accelerate/utils/dataclasses.py b/src/accelerate/utils/dataclasses.py index 11d3bc31aae..7de877740fd 100644 --- a/src/accelerate/utils/dataclasses.py +++ b/src/accelerate/utils/dataclasses.py @@ -350,6 +350,45 @@ def __post_init__(self): raise ValueError(f"`optimization_level` must be one of {' or '.join(get_args(OptLevel))}") +# Literal +ProfilerActivity = Literal["cpu", "xpu", "mtia", "cuda"] + + +@dataclass +class ProfileKwargs(KwargsHandler): + activities: Optional[List[ProfilerActivity]] = None + schedule_option: Optional[Dict[str, int]] = None + on_trace_ready: Optional[Callable] = None + record_shapes: bool = False + profile_memory: bool = False + with_stack: bool = False + with_flops: bool = False + with_modules: bool = False + + def _get_profiler_activity(self, activity: ProfilerActivity) -> List[torch.profiler.ProfilerActivity]: + if activity := activity.upper() in torch.profiler.ProfilerActivity.__members__: + return torch.profiler.ProfilerActivity[activity] + else: + raise ValueError(f"Invalid profiler activity: {activity}") + + def build(self) -> torch.profiler.profile: + """ + Build a profiler object with the current configuration. + """ + return torch.profiler.profile( + activities=[self._get_profiler_activity(activity) for activity in self.activities] + if self.activities is not None + else None, + schedule=torch.profiler.schedule(**self.schedule_option) if self.schedule_option is not None else None, + on_trace_ready=self.on_trace_ready, + record_shapes=self.record_shapes, + profile_memory=self.profile_memory, + with_stack=self.with_stack, + with_flops=self.with_flops, + with_modules=self.with_modules, + ) + + class DeprecatedFieldDescriptor: """ Descriptor for deprecated fields in an enum class. diff --git a/tests/test_kwargs_handlers.py b/tests/test_kwargs_handlers.py index 6a6467cc754..6388a1d6ca5 100644 --- a/tests/test_kwargs_handlers.py +++ b/tests/test_kwargs_handlers.py @@ -29,7 +29,7 @@ require_non_cpu, require_non_xpu, ) -from accelerate.utils import AutocastKwargs, KwargsHandler, TorchDynamoPlugin, clear_environment +from accelerate.utils import AutocastKwargs, KwargsHandler, ProfileKwargs, TorchDynamoPlugin, clear_environment from accelerate.utils.dataclasses import DistributedType @@ -96,6 +96,50 @@ def test_autocast_kwargs(self): # We should be back in fp16 assert g_float16.dtype == torch.float16 + def test_profile_kwargs(self): + # Arrange + schedule_options = [ + dict(wait=1, warmup=1, active=2, repeat=1), + dict(wait=2, warmup=2, active=2, repeat=2), + dict(wait=0, warmup=1, active=3, repeat=3, skip_first=1), + dict(wait=3, warmup=2, active=1, repeat=1, skip_first=2), + dict(wait=1, warmup=0, active=1, repeat=5), + ] + + total_steps = 100 + + for option in schedule_options: + count = 0 + + def on_trace_ready(prof): + nonlocal count + count += 1 + print(prof.key_averages().table(sort_by="cpu_memory_usage", row_limit=-1)) + + kwargs = ProfileKwargs(on_trace_ready=on_trace_ready, schedule_option=option) + accelerator = Accelerator(kwargs_handlers=[kwargs]) + + # Act + with accelerator.profile() as prof: + for _ in range(total_steps): + prof.step() + torch.tensor([1, 2, 3, 4, 5], device=accelerator.device) + + # Assert + assert isinstance(prof, torch.profiler.profile) + + # Calculate the expected count value + steps_per_cycle = option["wait"] + option["warmup"] + option["active"] + effective_steps = max(0, total_steps - option.get("skip_first", 0)) + cycles = effective_steps // steps_per_cycle + + if option["repeat"] > 0: + expected_count = min(cycles, option["repeat"]) + else: + expected_count = cycles + + assert count == expected_count, f"Option: {option}, Expected count: {expected_count}, but got {count}" + def test_torch_dynamo_plugin(self): with clear_environment(): prefix = "ACCELERATE_DYNAMO_" From 4637ad54762ca6ff57891f4e3911a2022b1069db Mon Sep 17 00:00:00 2001 From: yhna940 Date: Sun, 23 Jun 2024 01:01:08 +0900 Subject: [PATCH 02/27] Add example --- examples/by_feature/profile.py | 262 ++++++++++++++++++++++++++++ src/accelerate/accelerator.py | 7 +- src/accelerate/utils/dataclasses.py | 1 + tests/test_kwargs_handlers.py | 18 +- 4 files changed, 276 insertions(+), 12 deletions(-) create mode 100644 examples/by_feature/profile.py diff --git a/examples/by_feature/profile.py b/examples/by_feature/profile.py new file mode 100644 index 00000000000..30975cf380a --- /dev/null +++ b/examples/by_feature/profile.py @@ -0,0 +1,262 @@ +# Copyright 2021 The HuggingFace Inc. team. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import argparse +import os + +import evaluate +import torch +from datasets import load_dataset +from torch.optim import AdamW +from torch.utils.data import DataLoader +from transformers import AutoModelForSequenceClassification, AutoTokenizer, get_linear_schedule_with_warmup, set_seed + +from accelerate import Accelerator, DistributedType +from accelerate.utils import ProfileKwargs + + +######################################################################## +# This is a fully working simple example to use Accelerate +# and perform profiling +# +# This example trains a Bert base model on GLUE MRPC +# in any of the following settings (with the same script): +# - single CPU or single GPU +# - multi GPUS (using PyTorch distributed mode) +# - (multi) TPUs +# - fp16 (mixed-precision) or fp32 (normal precision) +# +# To run it in each of these various modes, follow the instructions +# in the readme for examples: +# https://github.com/huggingface/accelerate/tree/main/examples +# +######################################################################## + + +MAX_GPU_BATCH_SIZE = 16 +EVAL_BATCH_SIZE = 32 + + +def get_dataloaders(accelerator: Accelerator, batch_size: int = 16): + """ + Creates a set of `DataLoader`s for the `glue` dataset, + using "bert-base-cased" as the tokenizer. + + Args: + accelerator (`Accelerator`): + An `Accelerator` object + batch_size (`int`, *optional*): + The batch size for the train and validation DataLoaders. + """ + tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") + datasets = load_dataset("glue", "mrpc") + + def tokenize_function(examples): + # max_length=None => use the model max length (it's actually the default) + outputs = tokenizer(examples["sentence1"], examples["sentence2"], truncation=True, max_length=None) + return outputs + + # Apply the method we just defined to all the examples in all the splits of the dataset + # starting with the main process first: + with accelerator.main_process_first(): + tokenized_datasets = datasets.map( + tokenize_function, + batched=True, + remove_columns=["idx", "sentence1", "sentence2"], + ) + + # We also rename the 'label' column to 'labels' which is the expected name for labels by the models of the + # transformers library + tokenized_datasets = tokenized_datasets.rename_column("label", "labels") + + def collate_fn(examples): + # On TPU it's best to pad everything to the same length or training will be very slow. + max_length = 128 if accelerator.distributed_type == DistributedType.XLA else None + # When using mixed precision we want round multiples of 8/16 + if accelerator.mixed_precision == "fp8": + pad_to_multiple_of = 16 + elif accelerator.mixed_precision != "no": + pad_to_multiple_of = 8 + else: + pad_to_multiple_of = None + + return tokenizer.pad( + examples, + padding="longest", + max_length=max_length, + pad_to_multiple_of=pad_to_multiple_of, + return_tensors="pt", + ) + + # Instantiate dataloaders. + train_dataloader = DataLoader( + tokenized_datasets["train"], shuffle=True, collate_fn=collate_fn, batch_size=batch_size + ) + eval_dataloader = DataLoader( + tokenized_datasets["validation"], shuffle=False, collate_fn=collate_fn, batch_size=EVAL_BATCH_SIZE + ) + + return train_dataloader, eval_dataloader + + +# For testing only +if os.environ.get("TESTING_MOCKED_DATALOADERS", None) == "1": + from accelerate.test_utils.training import mocked_dataloaders + + get_dataloaders = mocked_dataloaders # noqa: F811 + + +def training_function(config, args): + # For testing only + if os.environ.get("TESTING_MOCKED_DATALOADERS", None) == "1": + config["num_epochs"] = 2 + # New Code # + profile_kwargs = ProfileKwargs( + record_shapes=args.record_shapes, + profile_memory=args.profile_memory, + with_stack=args.with_stack, + with_flops=args.with_flops, + json_trace_path=args.json_trace_path, + ) + # Initialize accelerator + accelerator = Accelerator(cpu=args.cpu, mixed_precision=args.mixed_precision, kwargs_handlers=[profile_kwargs]) + # Sample hyper-parameters for learning rate, batch size, seed and a few other HPs + lr = config["lr"] + num_epochs = int(config["num_epochs"]) + seed = int(config["seed"]) + batch_size = int(config["batch_size"]) + + metric = evaluate.load("glue", "mrpc") + + set_seed(seed) + train_dataloader, eval_dataloader = get_dataloaders(accelerator, batch_size) + # Instantiate the model (we build the model here so that the seed also control new weights initialization) + model = AutoModelForSequenceClassification.from_pretrained("bert-base-cased", return_dict=True) + + # We could avoid this line since the accelerator is set with `device_placement=True` (default value). + # Note that if you are placing tensors on devices manually, this line absolutely needs to be before the optimizer + # creation otherwise training will not work on TPU (`accelerate` will kindly throw an error to make us aware of that). + model = model.to(accelerator.device) + + # Instantiate optimizer + optimizer = AdamW(params=model.parameters(), lr=lr) + + # Instantiate scheduler + lr_scheduler = get_linear_schedule_with_warmup( + optimizer=optimizer, + num_warmup_steps=100, + num_training_steps=(len(train_dataloader) * num_epochs), + ) + + # Prepare everything + # There is no specific order to remember, we just need to unpack the objects in the same order we gave them to the + # prepare method. + model, optimizer, train_dataloader, eval_dataloader, lr_scheduler = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader, lr_scheduler + ) + + # Now we train the model + for epoch in range(num_epochs): + model.train() + for step, batch in enumerate(train_dataloader): + # We could avoid this line since we set the accelerator with `device_placement=True`. + batch.to(accelerator.device) + # We use the new `accumulate` context manager to perform gradient accumulation + # New Code # + with accelerator.accumulate(model), accelerator.profile() as prof: + output = model(**batch) + loss = output.loss + accelerator.backward(loss) + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + + # New Code # + print(prof.key_averages().table(sort_by="self_cpu_time_total", row_limit=-1)) + + model.eval() + for step, batch in enumerate(eval_dataloader): + # We could avoid this line since we set the accelerator with `device_placement=True`. + batch.to(accelerator.device) + with torch.no_grad(): + outputs = model(**batch) + predictions = outputs.logits.argmax(dim=-1) + predictions, references = accelerator.gather_for_metrics((predictions, batch["labels"])) + metric.add_batch( + predictions=predictions, + references=references, + ) + + eval_metric = metric.compute() + # Use accelerator.print to print only on the main process. + accelerator.print(f"epoch {epoch}:", eval_metric) + + +def main(): + parser = argparse.ArgumentParser(description="Simple example of training script.") + parser.add_argument( + "--mixed_precision", + type=str, + default=None, + choices=["no", "fp16", "bf16", "fp8"], + help="Whether to use mixed precision. Choose" + "between fp16 and bf16 (bfloat16). Bf16 requires PyTorch >= 1.10." + "and an Nvidia Ampere GPU.", + ) + # New Code # + parser.add_argument( + "--record_shapes", + action="store_true", + default=False, + type=bool, + help="If passed, will record shapes for profiling.", + ) + # New Code # + parser.add_argument( + "--profile_memory", + action="store_true", + default=False, + type=bool, + help="If passed, will profile memory.", + ) + # New Code # + parser.add_argument( + "--with_stack", + action="store_true", + default=False, + type=bool, + help="If passed, will profile stack.", + ) + # New Code # + parser.add_argument( + "--with_flops", + action="store_true", + default=False, + type=bool, + help="If passed, will profile flops.", + ) + # New Code # + parser.add_argument( + "--json_trace_path", + type=str | None, + default=None, + help="If passed, will save a json trace to the specified path.", + ) + parser.add_argument("--cpu", action="store_true", help="If passed, will train on the CPU.") + args = parser.parse_args() + config = {"lr": 2e-5, "num_epochs": 3, "seed": 42, "batch_size": 16} + training_function(config, args) + + +if __name__ == "__main__": + main() diff --git a/src/accelerate/accelerator.py b/src/accelerate/accelerator.py index f5c3c5fbdf3..173e62d10fd 100755 --- a/src/accelerate/accelerator.py +++ b/src/accelerate/accelerator.py @@ -3367,7 +3367,12 @@ def autocast(self, cache_enabled: bool = False, autocast_handler: AutocastKwargs def profile(self, profile_handler: ProfileKwargs | None = None): if profile_handler is None: profile_handler = self.profile_handler or ProfileKwargs() - yield from profile_handler.build() + profiler: torch.profiler.profile = profile_handler.build() + + yield from profiler + + if profile_handler.json_trace_path is not None: + profiler.export_chrome_trace(profile_handler.json_trace_path) @property def optimizer_step_was_skipped(self): diff --git a/src/accelerate/utils/dataclasses.py b/src/accelerate/utils/dataclasses.py index 7de877740fd..2fa2f431be8 100644 --- a/src/accelerate/utils/dataclasses.py +++ b/src/accelerate/utils/dataclasses.py @@ -364,6 +364,7 @@ class ProfileKwargs(KwargsHandler): with_stack: bool = False with_flops: bool = False with_modules: bool = False + json_trace_path: Optional[str] = None def _get_profiler_activity(self, activity: ProfilerActivity) -> List[torch.profiler.ProfilerActivity]: if activity := activity.upper() in torch.profiler.ProfilerActivity.__members__: diff --git a/tests/test_kwargs_handlers.py b/tests/test_kwargs_handlers.py index 6388a1d6ca5..65e2dfba7d0 100644 --- a/tests/test_kwargs_handlers.py +++ b/tests/test_kwargs_handlers.py @@ -110,6 +110,13 @@ def test_profile_kwargs(self): for option in schedule_options: count = 0 + steps_per_cycle = option["wait"] + option["warmup"] + option["active"] + effective_steps = max(0, total_steps - option.get("skip_first", 0)) + cycles = effective_steps // steps_per_cycle + if option["repeat"] > 0: + expected_count = min(cycles, option["repeat"]) + else: + expected_count = cycles def on_trace_ready(prof): nonlocal count @@ -127,17 +134,6 @@ def on_trace_ready(prof): # Assert assert isinstance(prof, torch.profiler.profile) - - # Calculate the expected count value - steps_per_cycle = option["wait"] + option["warmup"] + option["active"] - effective_steps = max(0, total_steps - option.get("skip_first", 0)) - cycles = effective_steps // steps_per_cycle - - if option["repeat"] > 0: - expected_count = min(cycles, option["repeat"]) - else: - expected_count = cycles - assert count == expected_count, f"Option: {option}, Expected count: {expected_count}, but got {count}" def test_torch_dynamo_plugin(self): From 715dfef3b0ab77d236467bd1b68a108884b32329 Mon Sep 17 00:00:00 2001 From: yhna940 Date: Sun, 23 Jun 2024 01:05:52 +0900 Subject: [PATCH 03/27] Fix rank 0 saving --- examples/by_feature/profile.py | 6 +++++- src/accelerate/accelerator.py | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/examples/by_feature/profile.py b/examples/by_feature/profile.py index 30975cf380a..164564ec3f3 100644 --- a/examples/by_feature/profile.py +++ b/examples/by_feature/profile.py @@ -182,7 +182,11 @@ def training_function(config, args): optimizer.zero_grad() # New Code # - print(prof.key_averages().table(sort_by="self_cpu_time_total", row_limit=-1)) + print( + prof.key_averages().table( + sort_by="self_cpu_time_total" if args.cpu else "self_cuda_time_total", row_limit=-1 + ) + ) model.eval() for step, batch in enumerate(eval_dataloader): diff --git a/src/accelerate/accelerator.py b/src/accelerate/accelerator.py index 173e62d10fd..414757ac10e 100755 --- a/src/accelerate/accelerator.py +++ b/src/accelerate/accelerator.py @@ -3371,8 +3371,9 @@ def profile(self, profile_handler: ProfileKwargs | None = None): yield from profiler - if profile_handler.json_trace_path is not None: + if self.is_main_process and profile_handler.json_trace_path is not None: profiler.export_chrome_trace(profile_handler.json_trace_path) + self.wait_for_everyone() @property def optimizer_step_was_skipped(self): From 0682cf89a223043897774176bfa2c062310cdee2 Mon Sep 17 00:00:00 2001 From: yhna940 Date: Sun, 23 Jun 2024 01:47:43 +0900 Subject: [PATCH 04/27] Add docstring --- src/accelerate/accelerator.py | 54 ++++++++++++++++++++++++++- src/accelerate/utils/dataclasses.py | 58 +++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 2 deletions(-) diff --git a/src/accelerate/accelerator.py b/src/accelerate/accelerator.py index 414757ac10e..8e8a27201aa 100755 --- a/src/accelerate/accelerator.py +++ b/src/accelerate/accelerator.py @@ -221,8 +221,8 @@ class Accelerator: Set `True` if the learning rate scheduler is stepped at the same time as the optimizer, `False` if only done under certain circumstances (at the end of each epoch, for instance). kwargs_handlers (list of [`~utils.KwargsHandler`], *optional*) - A list of [`~utils.KwargsHandler`] to customize how the objects related to distributed training or mixed - precision are created. See [kwargs](kwargs) for more information. + A list of [`~utils.KwargsHandler`] to customize how the objects related to distributed training, profiling + or mixed precision are created. See [kwargs](kwargs) for more information. dynamo_backend (`str` or [`~utils.DynamoBackend`], *optional*, defaults to `"no"`): Set to one of the possible dynamo backends to optimize your training with torch dynamo. gradient_accumulation_plugin ([`~utils.GradientAccumulationPlugin`], *optional*): @@ -3365,6 +3365,56 @@ def autocast(self, cache_enabled: bool = False, autocast_handler: AutocastKwargs @contextmanager def profile(self, profile_handler: ProfileKwargs | None = None): + """ + Will profile the code inside the context manager. The profile will be saved to a Chrome Trace file if + `profile_handler.json_trace_path` is set. + + A different `profile_handler` can be passed in to override the one set in the `Accelerator` object. + + Args: + profile_handler (`ProfileKwargs`, *optional*): + The profile handler to use for this context manager. If not passed, will use the one set in the + `Accelerator` object. + + Example: + + ```python + # Profile with default settings + from accelerate import Accelerator + + accelerator = Accelerator() + with accelerator.profile() as prof: + train() + print(prof.key_averages().table(sort_by="self_cpu_time_total", row_limit=10)) + + + # Profile with custom callalbe handler + from accelerate import Accelerator + from accelerate.utils import ProfileKwargs + + + def custom_handler(prof): + print(prof.key_averages().table(sort_by="self_cpu_time_total", row_limit=10)) + + + kwargs = ProfileKwargs(schedule_option=dict(wait=1, warmup=1, active=1), on_trace_ready=custom_handler) + accelerator = Accelerator(kwarg_handler=[kwargs]) + with accelerator.profile() as prof: + for _ in range(10): + train_iteration() + prof.step() + + + # Profile with chrome trace file + from accelerate import Accelerator + from accelerate.utils import ProfileKwargs + + kwargs = ProfileKwargs(json_trace_path="profile.json") + accelerator = Accelerator(kwarg_handler=[kwargs]) + with accelerator.profile(): + train() + ``` + """ if profile_handler is None: profile_handler = self.profile_handler or ProfileKwargs() profiler: torch.profiler.profile = profile_handler.build() diff --git a/src/accelerate/utils/dataclasses.py b/src/accelerate/utils/dataclasses.py index 2fa2f431be8..5fed99a856c 100644 --- a/src/accelerate/utils/dataclasses.py +++ b/src/accelerate/utils/dataclasses.py @@ -356,6 +356,54 @@ def __post_init__(self): @dataclass class ProfileKwargs(KwargsHandler): + """ + Use this object in your [`Accelerator`] to customize the initialization of the profiler. Please refer to the + documentation of this [context manager](https://pytorch.org/docs/stable/profiler.html#torch.profiler.profile) for + more information on each argument. + + + + `torch.profiler` is only available in PyTorch 1.8.1 and later versions. + + + + Example: + + ```python + from accelerate import Accelerator + from accelerate.utils import ProfileKwargs + + kwargs = ProfileKwargs(activities=["cpu", "cuda"]) + accelerator = Accelerator(kwargs_handlers=[kwargs]) + ``` + + Args: + activities (`List[str]`, *optional*, default to `None`): + The list of activity groups to use in profiling. Must be one of `"cpu"`, `"xpu"`, `"mtia"`, or `"cuda"`. + schedule_option (`Dict[str, int]`, *optional*, default to `None`): + The schedule option to use for the profiler. Available keys are `wait`, `warmup`, `active`, `repeat` and + `skip_first` The profiler will skip the first `skip_first` steps, then wait for `wait` steps, then do the + warmup for the next `warmup` steps, then do the active recording for the next `active` steps and then + repeat the cycle starting with `wait` steps. The optional number of cycles is specified with the `repeat` + parameter, the zero value means that the cycles will continue until the profiling is finished. + on_trace_ready (`Callable`, *optional*, default to `None`): + Callable that is called at each step when schedule returns `ProfilerAction.RECORD_AND_SAVE` during the + profiling. + record_shapes (`bool`, *optional*, default to `False`): + Save information about operator’s input shapes. + profile_memory (`bool`, *optional*, default to `False`): + Track tensor memory allocation/deallocation + with_stack (`bool`, *optional*, default to `False`): + Record source information (file and line number) for the ops. + with_flops (`bool`, *optional*, default to `False`): + Use formula to estimate the FLOPS of specific operators + with_modules (`bool`, *optional*, default to `False`): + Record module hierarchy (including function names) corresponding to the callstack of the op. + json_trace_path (`str`, *optional*, default to `None`): + Exports the collected trace in Chrome JSON format. Chrome use 'chrome://tracing' view json file. Defaults + to None, which means profiling does not store json files. + """ + activities: Optional[List[ProfilerActivity]] = None schedule_option: Optional[Dict[str, int]] = None on_trace_ready: Optional[Callable] = None @@ -367,6 +415,13 @@ class ProfileKwargs(KwargsHandler): json_trace_path: Optional[str] = None def _get_profiler_activity(self, activity: ProfilerActivity) -> List[torch.profiler.ProfilerActivity]: + """Get the profiler activity from the string. + + Args: + activity (str): The profiler activity name. + + Returns: + List[torch.profiler.ProfilerActivity]: The profiler activity.""" if activity := activity.upper() in torch.profiler.ProfilerActivity.__members__: return torch.profiler.ProfilerActivity[activity] else: @@ -375,6 +430,9 @@ def _get_profiler_activity(self, activity: ProfilerActivity) -> List[torch.profi def build(self) -> torch.profiler.profile: """ Build a profiler object with the current configuration. + + Returns: + torch.profiler.profile: The profiler object. """ return torch.profiler.profile( activities=[self._get_profiler_activity(activity) for activity in self.activities] From a118456b6ba6e4e9715db3c630ae7e1fbf62af8e Mon Sep 17 00:00:00 2001 From: yhna940 Date: Sun, 23 Jun 2024 01:50:59 +0900 Subject: [PATCH 05/27] Add profile readme --- examples/by_feature/README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/examples/by_feature/README.md b/examples/by_feature/README.md index 3d6388d375d..7f4e90b6836 100644 --- a/examples/by_feature/README.md +++ b/examples/by_feature/README.md @@ -99,3 +99,23 @@ These arguments should be added at the end of any method for starting the python ```bash accelerate launch ./ddp_comm_hook.py --mixed_precision fp16 --ddp_comm_hook power_sgd ``` + +### Profiler (`profiler.py`) + +- Shows how to use the profiling capabilities of `Accelerate` to profile PyTorch models during training. +- Uses the `ProfileKwargs` handler to customize profiling options, including activities, scheduling, and additional profiling options. +- Can generate and save profiling traces in JSON format for visualization in Chrome's tracing tool. + +Arguments available: +- `--record_shapes`: If passed, records shapes for profiling. +- `--profile_memory`: If passed, profiles memory usage. +- `--with_stack`: If passed, profiles stack traces. +- `--with_flops`: If passed, profiles floating point operations (FLOPS). +- `--json_trace_path`: If specified, saves the profiling trace to the given path in JSON format. +- `--cpu`: If passed, trains on the CPU instead of GPU. + +These arguments should be added at the end of any method for starting the Python script (such as `python`, `accelerate launch`, `python -m torchrun`), such as: + +```bash +accelerate launch ./profiler.py --record_shapes --profile_memory --with_stack --with_flops --json_trace_path "profile.json" --cpu +``` From 8c5fd53c5669dba1a3d8c817b6e3c2588362e1bf Mon Sep 17 00:00:00 2001 From: yhna940 Date: Sun, 23 Jun 2024 02:03:41 +0900 Subject: [PATCH 06/27] Fix minor --- src/accelerate/accelerator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/accelerate/accelerator.py b/src/accelerate/accelerator.py index 8e8a27201aa..2dfc29b9db3 100755 --- a/src/accelerate/accelerator.py +++ b/src/accelerate/accelerator.py @@ -3417,9 +3417,9 @@ def custom_handler(prof): """ if profile_handler is None: profile_handler = self.profile_handler or ProfileKwargs() - profiler: torch.profiler.profile = profile_handler.build() - yield from profiler + with profile_handler.build() as profiler: + yield profiler if self.is_main_process and profile_handler.json_trace_path is not None: profiler.export_chrome_trace(profile_handler.json_trace_path) From f6a8d083e163aa0846bdecf1660e8de9ee0c7eac Mon Sep 17 00:00:00 2001 From: yhna940 Date: Sun, 23 Jun 2024 02:16:42 +0900 Subject: [PATCH 07/27] Fix example path --- examples/by_feature/README.md | 4 ++-- src/accelerate/__init__.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/by_feature/README.md b/examples/by_feature/README.md index 7f4e90b6836..7c418e7d70f 100644 --- a/examples/by_feature/README.md +++ b/examples/by_feature/README.md @@ -100,7 +100,7 @@ These arguments should be added at the end of any method for starting the python accelerate launch ./ddp_comm_hook.py --mixed_precision fp16 --ddp_comm_hook power_sgd ``` -### Profiler (`profiler.py`) +### Profile (`profile.py`) - Shows how to use the profiling capabilities of `Accelerate` to profile PyTorch models during training. - Uses the `ProfileKwargs` handler to customize profiling options, including activities, scheduling, and additional profiling options. @@ -117,5 +117,5 @@ Arguments available: These arguments should be added at the end of any method for starting the Python script (such as `python`, `accelerate launch`, `python -m torchrun`), such as: ```bash -accelerate launch ./profiler.py --record_shapes --profile_memory --with_stack --with_flops --json_trace_path "profile.json" --cpu +accelerate launch ./profile.py --record_shapes --profile_memory --with_stack --with_flops --json_trace_path "profile.json" --cpu ``` diff --git a/src/accelerate/__init__.py b/src/accelerate/__init__.py index 882f262c335..49dc87b9082 100644 --- a/src/accelerate/__init__.py +++ b/src/accelerate/__init__.py @@ -37,6 +37,7 @@ FullyShardedDataParallelPlugin, GradScalerKwargs, InitProcessGroupKwargs, + ProfileKwargs, find_executable_batch_size, infer_auto_device_map, is_rich_available, From a1261b900f32433e5a8f0241e2a41017b48ef470 Mon Sep 17 00:00:00 2001 From: yhna940 Date: Sun, 23 Jun 2024 13:04:00 +0900 Subject: [PATCH 08/27] Add exp test code --- examples/by_feature/README.md | 4 +- .../by_feature/{profile.py => profiler.py} | 50 +++++++------------ tests/test_examples.py | 5 ++ 3 files changed, 26 insertions(+), 33 deletions(-) rename examples/by_feature/{profile.py => profiler.py} (88%) diff --git a/examples/by_feature/README.md b/examples/by_feature/README.md index 7c418e7d70f..70b1d3acf5d 100644 --- a/examples/by_feature/README.md +++ b/examples/by_feature/README.md @@ -100,7 +100,7 @@ These arguments should be added at the end of any method for starting the python accelerate launch ./ddp_comm_hook.py --mixed_precision fp16 --ddp_comm_hook power_sgd ``` -### Profile (`profile.py`) +### Profiler (`profiler.py`) - Shows how to use the profiling capabilities of `Accelerate` to profile PyTorch models during training. - Uses the `ProfileKwargs` handler to customize profiling options, including activities, scheduling, and additional profiling options. @@ -117,5 +117,5 @@ Arguments available: These arguments should be added at the end of any method for starting the Python script (such as `python`, `accelerate launch`, `python -m torchrun`), such as: ```bash -accelerate launch ./profile.py --record_shapes --profile_memory --with_stack --with_flops --json_trace_path "profile.json" --cpu +accelerate launch ./profiler.py --record_shapes --profile_memory --with_flops --json_trace_path "profiler.json" ``` diff --git a/examples/by_feature/profile.py b/examples/by_feature/profiler.py similarity index 88% rename from examples/by_feature/profile.py rename to examples/by_feature/profiler.py index 164564ec3f3..2cdf14647a3 100644 --- a/examples/by_feature/profile.py +++ b/examples/by_feature/profiler.py @@ -124,7 +124,6 @@ def training_function(config, args): profile_kwargs = ProfileKwargs( record_shapes=args.record_shapes, profile_memory=args.profile_memory, - with_stack=args.with_stack, with_flops=args.with_flops, json_trace_path=args.json_trace_path, ) @@ -168,25 +167,25 @@ def training_function(config, args): # Now we train the model for epoch in range(num_epochs): model.train() - for step, batch in enumerate(train_dataloader): - # We could avoid this line since we set the accelerator with `device_placement=True`. - batch.to(accelerator.device) - # We use the new `accumulate` context manager to perform gradient accumulation - # New Code # - with accelerator.accumulate(model), accelerator.profile() as prof: - output = model(**batch) - loss = output.loss - accelerator.backward(loss) - optimizer.step() - lr_scheduler.step() - optimizer.zero_grad() - - # New Code # - print( - prof.key_averages().table( - sort_by="self_cpu_time_total" if args.cpu else "self_cuda_time_total", row_limit=-1 - ) + # New Code # + with accelerator.profile() as prof: + for step, batch in enumerate(train_dataloader): + # We could avoid this line since we set the accelerator with `device_placement=True`. + batch.to(accelerator.device) + # We use the new `accumulate` context manager to perform gradient accumulation + with accelerator.accumulate(model): + output = model(**batch) + loss = output.loss + accelerator.backward(loss) + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + # New Code # + accelerator.print( + prof.key_averages().table( + sort_by="self_cpu_time_total" if args.cpu else "self_cuda_time_total", row_limit=-1 ) + ) model.eval() for step, batch in enumerate(eval_dataloader): @@ -222,7 +221,6 @@ def main(): "--record_shapes", action="store_true", default=False, - type=bool, help="If passed, will record shapes for profiling.", ) # New Code # @@ -230,29 +228,19 @@ def main(): "--profile_memory", action="store_true", default=False, - type=bool, help="If passed, will profile memory.", ) # New Code # - parser.add_argument( - "--with_stack", - action="store_true", - default=False, - type=bool, - help="If passed, will profile stack.", - ) - # New Code # parser.add_argument( "--with_flops", action="store_true", default=False, - type=bool, help="If passed, will profile flops.", ) # New Code # parser.add_argument( "--json_trace_path", - type=str | None, + type=str, default=None, help="If passed, will save a json trace to the specified path.", ) diff --git a/tests/test_examples.py b/tests/test_examples.py index d37f8cece68..cba31ea25ba 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -55,6 +55,7 @@ "megatron_lm_gpt_pretraining.py", "early_stopping.py", "ddp_comm_hook.py", + "profiler.py", ] @@ -248,6 +249,10 @@ def test_early_stopping(self): testargs = ["examples/by_feature/early_stopping.py"] run_command(self.launch_args + testargs) + def test_profiler(self): + testargs = ["examples/by_feature/profiler.py"] + run_command(self.launch_args + testargs) + @require_multi_gpu def test_ddp_comm_hook(self): testargs = ["examples/by_feature/ddp_comm_hook.py", "--ddp_comm_hook", "fp16"] From 49b95e322ccc47ea7fb7bd316a8ac1fc48f3c59c Mon Sep 17 00:00:00 2001 From: yhna940 Date: Sun, 23 Jun 2024 13:24:11 +0900 Subject: [PATCH 09/27] Rename profile dir --- examples/by_feature/README.md | 4 ++-- examples/by_feature/profiler.py | 4 ++-- src/accelerate/accelerator.py | 15 ++++++++++----- src/accelerate/utils/__init__.py | 1 + src/accelerate/utils/constants.py | 1 + src/accelerate/utils/dataclasses.py | 4 ++-- 6 files changed, 18 insertions(+), 11 deletions(-) diff --git a/examples/by_feature/README.md b/examples/by_feature/README.md index 70b1d3acf5d..06ea6149f24 100644 --- a/examples/by_feature/README.md +++ b/examples/by_feature/README.md @@ -111,11 +111,11 @@ Arguments available: - `--profile_memory`: If passed, profiles memory usage. - `--with_stack`: If passed, profiles stack traces. - `--with_flops`: If passed, profiles floating point operations (FLOPS). -- `--json_trace_path`: If specified, saves the profiling trace to the given path in JSON format. +- `--output_trace_dir`: If specified, saves the profiling trace to the given path in JSON format. - `--cpu`: If passed, trains on the CPU instead of GPU. These arguments should be added at the end of any method for starting the Python script (such as `python`, `accelerate launch`, `python -m torchrun`), such as: ```bash -accelerate launch ./profiler.py --record_shapes --profile_memory --with_flops --json_trace_path "profiler.json" +accelerate launch ./profiler.py --record_shapes --profile_memory --with_flops --output_trace_dir "profiler.json" ``` diff --git a/examples/by_feature/profiler.py b/examples/by_feature/profiler.py index 2cdf14647a3..c9699b31a60 100644 --- a/examples/by_feature/profiler.py +++ b/examples/by_feature/profiler.py @@ -125,7 +125,7 @@ def training_function(config, args): record_shapes=args.record_shapes, profile_memory=args.profile_memory, with_flops=args.with_flops, - json_trace_path=args.json_trace_path, + output_trace_dir=args.output_trace_dir, ) # Initialize accelerator accelerator = Accelerator(cpu=args.cpu, mixed_precision=args.mixed_precision, kwargs_handlers=[profile_kwargs]) @@ -239,7 +239,7 @@ def main(): ) # New Code # parser.add_argument( - "--json_trace_path", + "--output_trace_dir", type=str, default=None, help="If passed, will save a json trace to the specified path.", diff --git a/src/accelerate/accelerator.py b/src/accelerate/accelerator.py index 2dfc29b9db3..1d4fee82e77 100755 --- a/src/accelerate/accelerator.py +++ b/src/accelerate/accelerator.py @@ -103,7 +103,7 @@ save_fsdp_optimizer, wait_for_everyone, ) -from .utils.constants import FSDP_PYTORCH_VERSION +from .utils.constants import FSDP_PYTORCH_VERSION, PROFILE_NAME from .utils.modeling import get_state_dict_offloaded_model from .utils.other import is_compiled_module @@ -3367,7 +3367,7 @@ def autocast(self, cache_enabled: bool = False, autocast_handler: AutocastKwargs def profile(self, profile_handler: ProfileKwargs | None = None): """ Will profile the code inside the context manager. The profile will be saved to a Chrome Trace file if - `profile_handler.json_trace_path` is set. + `profile_handler.output_trace_dir` is set. A different `profile_handler` can be passed in to override the one set in the `Accelerator` object. @@ -3409,7 +3409,7 @@ def custom_handler(prof): from accelerate import Accelerator from accelerate.utils import ProfileKwargs - kwargs = ProfileKwargs(json_trace_path="profile.json") + kwargs = ProfileKwargs(output_trace_dir="output_trace") accelerator = Accelerator(kwarg_handler=[kwargs]) with accelerator.profile(): train() @@ -3421,8 +3421,13 @@ def custom_handler(prof): with profile_handler.build() as profiler: yield profiler - if self.is_main_process and profile_handler.json_trace_path is not None: - profiler.export_chrome_trace(profile_handler.json_trace_path) + if profile_handler.output_trace_dir is None: + return + + os.makedirs(profile_handler.output_trace_dir, exist_ok=True) + profiler.export_chrome_trace( + os.path.join(profile_handler.output_trace_dir, f"{PROFILE_NAME}_{self.process_index}.json") + ) self.wait_for_everyone() @property diff --git a/src/accelerate/utils/__init__.py b/src/accelerate/utils/__init__.py index 387099c7b4a..3d2cede7d6e 100644 --- a/src/accelerate/utils/__init__.py +++ b/src/accelerate/utils/__init__.py @@ -14,6 +14,7 @@ from .constants import ( MODEL_NAME, OPTIMIZER_NAME, + PROFILE_NAME, RNG_STATE_NAME, SAFE_MODEL_NAME, SAFE_WEIGHTS_INDEX_NAME, diff --git a/src/accelerate/utils/constants.py b/src/accelerate/utils/constants.py index 52d1e7bab18..4fafea09e9c 100644 --- a/src/accelerate/utils/constants.py +++ b/src/accelerate/utils/constants.py @@ -22,6 +22,7 @@ OPTIMIZER_NAME = "optimizer" SCHEDULER_NAME = "scheduler" SAMPLER_NAME = "sampler" +PROFILE_NAME = "profile" WEIGHTS_NAME = f"{MODEL_NAME}.bin" WEIGHTS_PATTERN_NAME = "pytorch_model{suffix}.bin" WEIGHTS_INDEX_NAME = f"{WEIGHTS_NAME}.index.json" diff --git a/src/accelerate/utils/dataclasses.py b/src/accelerate/utils/dataclasses.py index 5fed99a856c..a325dad5cf7 100644 --- a/src/accelerate/utils/dataclasses.py +++ b/src/accelerate/utils/dataclasses.py @@ -399,7 +399,7 @@ class ProfileKwargs(KwargsHandler): Use formula to estimate the FLOPS of specific operators with_modules (`bool`, *optional*, default to `False`): Record module hierarchy (including function names) corresponding to the callstack of the op. - json_trace_path (`str`, *optional*, default to `None`): + output_trace_dir (`str`, *optional*, default to `None`): Exports the collected trace in Chrome JSON format. Chrome use 'chrome://tracing' view json file. Defaults to None, which means profiling does not store json files. """ @@ -412,7 +412,7 @@ class ProfileKwargs(KwargsHandler): with_stack: bool = False with_flops: bool = False with_modules: bool = False - json_trace_path: Optional[str] = None + output_trace_dir: Optional[str] = None def _get_profiler_activity(self, activity: ProfilerActivity) -> List[torch.profiler.ProfilerActivity]: """Get the profiler activity from the string. From 761c5faaa7d7b65e6aa076c61e41dbb2f96dd094 Mon Sep 17 00:00:00 2001 From: yhna940 Date: Sun, 23 Jun 2024 13:25:33 +0900 Subject: [PATCH 10/27] Change readme --- examples/by_feature/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/by_feature/README.md b/examples/by_feature/README.md index 06ea6149f24..7b825dd18b3 100644 --- a/examples/by_feature/README.md +++ b/examples/by_feature/README.md @@ -111,11 +111,11 @@ Arguments available: - `--profile_memory`: If passed, profiles memory usage. - `--with_stack`: If passed, profiles stack traces. - `--with_flops`: If passed, profiles floating point operations (FLOPS). -- `--output_trace_dir`: If specified, saves the profiling trace to the given path in JSON format. +- `--output_trace_dir`: If specified, saves the profiling trace to the given dir in JSON format. - `--cpu`: If passed, trains on the CPU instead of GPU. These arguments should be added at the end of any method for starting the Python script (such as `python`, `accelerate launch`, `python -m torchrun`), such as: ```bash -accelerate launch ./profiler.py --record_shapes --profile_memory --with_flops --output_trace_dir "profiler.json" +accelerate launch ./profiler.py --record_shapes --profile_memory --with_flops --output_trace_dir "profiler" ``` From b1c3fb1094a673f468dce18968382f5f01a0ee8e Mon Sep 17 00:00:00 2001 From: yhna940 Date: Sun, 23 Jun 2024 13:36:57 +0900 Subject: [PATCH 11/27] Change save format --- src/accelerate/accelerator.py | 6 ++++-- src/accelerate/utils/__init__.py | 2 +- src/accelerate/utils/constants.py | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/accelerate/accelerator.py b/src/accelerate/accelerator.py index 1d4fee82e77..d9461ff7aa4 100755 --- a/src/accelerate/accelerator.py +++ b/src/accelerate/accelerator.py @@ -103,7 +103,7 @@ save_fsdp_optimizer, wait_for_everyone, ) -from .utils.constants import FSDP_PYTORCH_VERSION, PROFILE_NAME +from .utils.constants import FSDP_PYTORCH_VERSION, PROFILE_PATTERN_NAME from .utils.modeling import get_state_dict_offloaded_model from .utils.other import is_compiled_module @@ -3426,7 +3426,9 @@ def custom_handler(prof): os.makedirs(profile_handler.output_trace_dir, exist_ok=True) profiler.export_chrome_trace( - os.path.join(profile_handler.output_trace_dir, f"{PROFILE_NAME}_{self.process_index}.json") + os.path.join( + profile_handler.output_trace_dir, PROFILE_PATTERN_NAME.format(suffix=f"_{self.process_index}") + ) ) self.wait_for_everyone() diff --git a/src/accelerate/utils/__init__.py b/src/accelerate/utils/__init__.py index 3d2cede7d6e..82ef2158ba4 100644 --- a/src/accelerate/utils/__init__.py +++ b/src/accelerate/utils/__init__.py @@ -14,7 +14,7 @@ from .constants import ( MODEL_NAME, OPTIMIZER_NAME, - PROFILE_NAME, + PROFILE_PATTERN_NAME, RNG_STATE_NAME, SAFE_MODEL_NAME, SAFE_WEIGHTS_INDEX_NAME, diff --git a/src/accelerate/utils/constants.py b/src/accelerate/utils/constants.py index 4fafea09e9c..1d088843011 100644 --- a/src/accelerate/utils/constants.py +++ b/src/accelerate/utils/constants.py @@ -22,7 +22,7 @@ OPTIMIZER_NAME = "optimizer" SCHEDULER_NAME = "scheduler" SAMPLER_NAME = "sampler" -PROFILE_NAME = "profile" +PROFILE_PATTERN_NAME = "profile{suffix}.json" WEIGHTS_NAME = f"{MODEL_NAME}.bin" WEIGHTS_PATTERN_NAME = "pytorch_model{suffix}.bin" WEIGHTS_INDEX_NAME = f"{WEIGHTS_NAME}.index.json" From 367f56ab0673da20db3d173babca9f016301a514 Mon Sep 17 00:00:00 2001 From: yhna940 Date: Sun, 23 Jun 2024 13:41:26 +0900 Subject: [PATCH 12/27] Minor --- src/accelerate/accelerator.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/accelerate/accelerator.py b/src/accelerate/accelerator.py index d9461ff7aa4..ec604979407 100755 --- a/src/accelerate/accelerator.py +++ b/src/accelerate/accelerator.py @@ -3415,8 +3415,7 @@ def custom_handler(prof): train() ``` """ - if profile_handler is None: - profile_handler = self.profile_handler or ProfileKwargs() + profile_handler = profile_handler or self.profile_handler or ProfileKwargs() with profile_handler.build() as profiler: yield profiler From 93600f6e9e1b6bec8733ffeeb65c84434d5410ae Mon Sep 17 00:00:00 2001 From: yhna940 Date: Sun, 23 Jun 2024 16:02:54 +0900 Subject: [PATCH 13/27] Enhance docstring example --- src/accelerate/accelerator.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/accelerate/accelerator.py b/src/accelerate/accelerator.py index ec604979407..058eac5fdad 100755 --- a/src/accelerate/accelerator.py +++ b/src/accelerate/accelerator.py @@ -3381,18 +3381,15 @@ def profile(self, profile_handler: ProfileKwargs | None = None): ```python # Profile with default settings from accelerate import Accelerator + from accelerate.utils import ProfileKwargs accelerator = Accelerator() with accelerator.profile() as prof: train() - print(prof.key_averages().table(sort_by="self_cpu_time_total", row_limit=10)) - - - # Profile with custom callalbe handler - from accelerate import Accelerator - from accelerate.utils import ProfileKwargs + accelerator.print(prof.key_averages().table()) + # Profile with the custom handler def custom_handler(prof): print(prof.key_averages().table(sort_by="self_cpu_time_total", row_limit=10)) @@ -3405,10 +3402,7 @@ def custom_handler(prof): prof.step() - # Profile with chrome trace file - from accelerate import Accelerator - from accelerate.utils import ProfileKwargs - + # Profile and export to Chrome Trace kwargs = ProfileKwargs(output_trace_dir="output_trace") accelerator = Accelerator(kwarg_handler=[kwargs]) with accelerator.profile(): From 817cd704049cb798e5e55a729a45b6bb62bc015f Mon Sep 17 00:00:00 2001 From: yhna940 Date: Sun, 23 Jun 2024 17:03:37 +0900 Subject: [PATCH 14/27] Add user guide --- docs/source/_toctree.yml | 2 + docs/source/usage_guides/profiler.md | 310 +++++++++++++++++++++++++++ src/accelerate/utils/dataclasses.py | 15 +- 3 files changed, 323 insertions(+), 4 deletions(-) create mode 100644 docs/source/usage_guides/profiler.md diff --git a/docs/source/_toctree.yml b/docs/source/_toctree.yml index 9c37be3403e..fc6763416e2 100644 --- a/docs/source/_toctree.yml +++ b/docs/source/_toctree.yml @@ -60,6 +60,8 @@ title: Apple M1 GPUs - local: usage_guides/ipex title: IPEX training with CPU + - local: usage_guides/profiler + title: Profiler title: Training - isExpanded: true sections: diff --git a/docs/source/usage_guides/profiler.md b/docs/source/usage_guides/profiler.md new file mode 100644 index 00000000000..168fb028d04 --- /dev/null +++ b/docs/source/usage_guides/profiler.md @@ -0,0 +1,310 @@ + + +## Profiler + +Profiler is a tool that allows the collection of performance metrics during training and inference. Profiler’s context manager API can be used to better understand what model operators are the most expensive, examine their input shapes and stack traces, study device kernel activity, and visualize the execution trace. It provides insights into the performance of your model, allowing you to optimize and improve it. + +This guide explains how to use PyTorch Profiler to measure the time and memory consumption of the model’s operators and how to integrate this with 🤗 Accelerate. We will cover various use cases and provide examples for each. + +### Using profiler to analyze execution time + +Let’s see how we can use profiler to analyze the execution time: + + + + +```python +import torch +import torchvision.models as models +from torch.profiler import profile, record_function, ProfilerActivity + +model = models.resnet18() +inputs = torch.randn(5, 3, 224, 224) + +with profile(activities=[ProfilerActivity.CPU], record_shapes=True) as prof: + model(inputs) + +print(prof.key_averages().table(sort_by="cpu_time_total", row_limit=10)) +``` + + + + +```python +from accelerate import Accelerator, ProfileKwargs +import torch +import torchvision.models as models + +model = models.resnet18() +inputs = torch.randn(5, 3, 224, 224) + +profile_kwargs = ProfileKwargs( + activities=["cpu"], + record_shapes=True +) + +accelerator = Accelerator(cpu=True, kwargs_handlers=[profile_kwargs]) +model = accelerator.prepare(model) + +with accelerator.profile() as prof: + with torch.no_grad(): + model(inputs) + +print(prof.key_averages().table(sort_by="cpu_time_total", row_limit=10)) +``` + + + + +The resulting table output (omitting some columns): + +``` +--------------------------------- ------------ ------------ ------------ ------------ + Name Self CPU CPU total CPU time avg # of Calls +--------------------------------- ------------ ------------ ------------ ------------ + aten::conv2d 171.000us 52.260ms 2.613ms 20 + aten::convolution 227.000us 52.089ms 2.604ms 20 + aten::_convolution 270.000us 51.862ms 2.593ms 20 + aten::mkldnn_convolution 51.273ms 51.592ms 2.580ms 20 + aten::batch_norm 118.000us 7.059ms 352.950us 20 + aten::_batch_norm_impl_index 315.000us 6.941ms 347.050us 20 + aten::native_batch_norm 6.305ms 6.599ms 329.950us 20 + aten::max_pool2d 40.000us 4.008ms 4.008ms 1 + aten::max_pool2d_with_indices 3.968ms 3.968ms 3.968ms 1 + aten::add_ 780.000us 780.000us 27.857us 28 +--------------------------------- ------------ ------------ ------------ ------------ +Self CPU time total: 67.016ms +``` + +## Exporting chrome trace + +You can examine the sequence of profiled operators and CUDA kernels in Chrome trace viewer (`chrome://tracing`): + +![exporting](https://pytorch.org/tutorials/_images/trace_img.png) + + + + +```python +model = models.resnet18().cuda() +inputs = torch.randn(5, 3, 224, 224).cuda() + +with profile(activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA]) as prof: + model(inputs) + +prof.export_chrome_trace("trace.json") +``` + + + + +```python +profile_kwargs = ProfileKwargs( + activities=["cpu", "cuda"], + output_trace_dir="trace" +) + +accelerator = Accelerator(kwargs_handlers=[profile_kwargs]) +model = accelerator.prepare(model) + +with accelerator.profile() as prof: + model(inputs) + +# The trace will be saved to the specified directory +``` + + + + +## Using Profiler to Analyze Long-Running Jobs + +Profiler offers an additional API to handle long-running jobs (such as training loops). Tracing all of the execution can be slow and result in very large trace files. To avoid this, use optional arguments: + +- `schedule_option`: Scheduling options allow you to control when profiling is active. This is useful for long-running jobs to avoid collecting too much data. Available keys are `wait`, `warmup`, `active`, `repeat` and `skip_first`. The profiler will skip the first `skip_first` steps, then wait for `wait` steps, then do the warmup for the next `warmup` steps, then do the active recording for the next `active` steps and then repeat the cycle starting with `wait` steps. The optional number of cycles is specified with the `repeat` parameter, the zero value means that the cycles will continue until the profiling is finished. +- `on_trace_ready`: specifies a function that takes a reference to the profiler as an input and is called by the profiler each time the new trace is ready. + +To illustrate how the API works, consider the following example: + + + + +```python +from torch.profiler import schedule + +my_schedule = schedule( + skip_first=10, + wait=5, + warmup=1, + active=3, + repeat=2 +) + +def trace_handler(p): + output = p.key_averages().table(sort_by="self_cuda_time_total", row_limit=10) + print(output) + p.export_chrome_trace("/tmp/trace_" + str(p.step_num) + ".json") + +with profile( + activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA], + schedule=my_schedule, + on_trace_ready=trace_handler +) as p: + for idx in range(8): + model(inputs) + p.step() +``` + + + + +```python +def trace_handler(p): + output = p.key_averages().table(sort_by="self_cuda_time_total", row_limit=10) + print(output) + p.export_chrome_trace("/tmp/trace_" + str(p.step_num) + ".json") + +profile_kwargs = ProfileKwargs( + activities=["cpu", "cuda"], + schedule_option={"wait": 5, "warmup": 1, "active": 3, "repeat": 2, "skip_first": 10}, + on_trace_ready=trace_handler +) + +accelerator = Accelerator(kwargs_handlers=[profile_kwargs]) +model = accelerator.prepare(model) + +with accelerator.profile() as prof: + for idx in range(8): + model(inputs) + prof.step() +``` + + + + +## Additional Arguments + +### Stack Traces + +Enabling stack tracing can provide more detailed information about where time is being spent in your code. + + + + +```python +with profile( + activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA], + with_stack=True +) as prof: + model(inputs) + +print(prof.key_averages(group_by_stack_n=5).table(sort_by="self_cuda_time_total", row_limit=2)) +``` + +The resulting table output (omitting some columns): + +``` +------------------------------------------------------- ------------ ------------ + Name Self CPU Self CUDA +------------------------------------------------------- ------------ ------------ + aten::cudnn_convolution 136.164ms 2.916ms +cudnn_infer_maxwell_scudnn_winograd_128x128_ldg1_ldg... 0.000us 1.703ms +------------------------------------------------------- ------------ ------------ +Self CPU time total: 294.445ms +Self CUDA time total: 4.100ms +``` + + + + +```python +profile_kwargs = ProfileKwargs( + with_stack=True +) +accelerator = Accelerator(kwargs_handlers=[profile_kwargs]) + +with accelerator.profile() as prof: + model(inputs) + +print(prof.key_averages(group_by_stack_n=5).table(sort_by="self_cuda_time_total", row_limit=2)) +``` + + + + +### FLOPS + +To measure floating-point operations (FLOPS): + + + + +```python +with profile( + activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA], + with_flops=True +) as prof: + model(inputs) + +print(prof.key_averages().table(sort_by="flops", row_limit=10)) +``` + + + + +```python +profile_kwargs = ProfileKwargs( + with_flops=True +) +accelerator = Accelerator(kwargs_handlers=[profile_kwargs]) + +with accelerator.profile() as prof: + model(inputs) + +print(prof.key_averages().table(sort_by="flops", row_limit=10)) +``` + +The resulting table output (omitting some columns): + +``` +------------------------------------------------------- ------------ ------------ ------------ + Name Self CPU Self CUDA Total FLOPs +------------------------------------------------------- ------------ ------------ ------------ + aten::conv2d 197.000us 0.000us 18135613440.000 + aten::addmm 103.000us 17.000us 5120000.000 + aten::mul 29.000us 2.000us 30.000 + aten::convolution 409.000us 0.000us -- + aten::_convolution 253.000us 0.000us -- + aten::cudnn_convolution 5.465ms 2.970ms -- + cudaEventRecord 138.000us 0.000us -- + cudaStreamIsCapturing 43.000us 0.000us -- + cudaStreamGetPriority 40.000us 0.000us -- + cudaDeviceGetStreamPriorityRange 10.000us 0.000us -- +------------------------------------------------------- ------------ ------------ ------------ +Self CPU time total: 21.938ms +Self CUDA time total: 4.165ms +``` + + + + +## Conclusion and Further Information + +PyTorch Profiler is a powerful tool for analyzing the performance of your models. By integrating it with 🤗 Accelerate, you can easily profile your models and gain insights into their performance, helping you to optimize and improve them. + +For more detailed information, refer to the [PyTorch Profiler documentation](https://pytorch.org/docs/stable/profiler.html). \ No newline at end of file diff --git a/src/accelerate/utils/dataclasses.py b/src/accelerate/utils/dataclasses.py index a325dad5cf7..3b74682a496 100644 --- a/src/accelerate/utils/dataclasses.py +++ b/src/accelerate/utils/dataclasses.py @@ -382,7 +382,7 @@ class ProfileKwargs(KwargsHandler): The list of activity groups to use in profiling. Must be one of `"cpu"`, `"xpu"`, `"mtia"`, or `"cuda"`. schedule_option (`Dict[str, int]`, *optional*, default to `None`): The schedule option to use for the profiler. Available keys are `wait`, `warmup`, `active`, `repeat` and - `skip_first` The profiler will skip the first `skip_first` steps, then wait for `wait` steps, then do the + `skip_first`. The profiler will skip the first `skip_first` steps, then wait for `wait` steps, then do the warmup for the next `warmup` steps, then do the active recording for the next `active` steps and then repeat the cycle starting with `wait` steps. The optional number of cycles is specified with the `repeat` parameter, the zero value means that the cycles will continue until the profiling is finished. @@ -422,10 +422,17 @@ def _get_profiler_activity(self, activity: ProfilerActivity) -> List[torch.profi Returns: List[torch.profiler.ProfilerActivity]: The profiler activity.""" - if activity := activity.upper() in torch.profiler.ProfilerActivity.__members__: - return torch.profiler.ProfilerActivity[activity] - else: + + profiler_activity_map: dict[str, torch.profiler.ProfilerActivity] = { + "cpu": torch.profiler.ProfilerActivity.CPU, + "xpu": torch.profiler.ProfilerActivity.XPU, + "mita": torch.profiler.ProfilerActivity.MTIA, + "cuda": torch.profiler.ProfilerActivity.CUDA, + } + + if activity not in profiler_activity_map: raise ValueError(f"Invalid profiler activity: {activity}") + return profiler_activity_map[activity] def build(self) -> torch.profiler.profile: """ From 7503639aaebc9d0ff371fc95c926e6f09ce77aa1 Mon Sep 17 00:00:00 2001 From: yhna940 Date: Sun, 23 Jun 2024 17:47:25 +0900 Subject: [PATCH 15/27] Add memory profile guide --- docs/source/imgs/profile_export.png | Bin 0 -> 107863 bytes docs/source/usage_guides/profiler.md | 134 ++++++++++++++++----------- 2 files changed, 78 insertions(+), 56 deletions(-) create mode 100644 docs/source/imgs/profile_export.png diff --git a/docs/source/imgs/profile_export.png b/docs/source/imgs/profile_export.png new file mode 100644 index 0000000000000000000000000000000000000000..c6c17fc88c4a35f180e7844a21548f8320e8b8e5 GIT binary patch literal 107863 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

-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ℜ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�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 HvH$-AfnXE1 literal 0 HcmV?d00001 diff --git a/docs/source/usage_guides/profiler.md b/docs/source/usage_guides/profiler.md index 168fb028d04..adce904ec47 100644 --- a/docs/source/usage_guides/profiler.md +++ b/docs/source/usage_guides/profiler.md @@ -14,13 +14,15 @@ specific language governing permissions and limitations under the License. rendered properly in your Markdown viewer. --> -## Profiler +# Profiler Profiler is a tool that allows the collection of performance metrics during training and inference. Profiler’s context manager API can be used to better understand what model operators are the most expensive, examine their input shapes and stack traces, study device kernel activity, and visualize the execution trace. It provides insights into the performance of your model, allowing you to optimize and improve it. This guide explains how to use PyTorch Profiler to measure the time and memory consumption of the model’s operators and how to integrate this with 🤗 Accelerate. We will cover various use cases and provide examples for each. -### Using profiler to analyze execution time +## Using profiler to analyze execution time + +Profiler allows one to check which operators were called during the execution of a code range wrapped with a profiler context manager. Let’s see how we can use profiler to analyze the execution time: @@ -90,11 +92,81 @@ The resulting table output (omitting some columns): Self CPU time total: 67.016ms ``` +To get a finer granularity of results and include operator input shapes, pass `group_by_input_shape=True` (note: this requires running the profiler with `record_shapes=True`): + +```python +print(prof.key_averages(group_by_input_shape=True).table(sort_by="cpu_time_total", row_limit=10)) +``` + +## Using profiler to analyze memory consumption + +Profiler can also show the amount of memory (used by the model’s tensors) that was allocated (or released) during the execution of the model’s operators. To enable memory profiling functionality pass `profile_memory=True`. + + + + +```python +model = models.resnet18() +inputs = torch.randn(5, 3, 224, 224) + +with profile(activities=[ProfilerActivity.CPU], + profile_memory=True, record_shapes=True) as prof: + model(inputs) + +print(prof.key_averages().table(sort_by="self_cpu_memory_usage", row_limit=10)) +``` + + + + +```python +model = models.resnet18() +inputs = torch.randn(5, 3, 224, 224) + +profile_kwargs = ProfileKwargs( + activities=["cpu"], + profile_memory=True, + record_shapes=True +) + +accelerator = Accelerator(cpu=True, kwargs_handlers=[profile_kwargs]) +model = accelerator.prepare(model) + +with accelerator.profile() as prof: + model(inputs) + +print(prof.key_averages().table(sort_by="self_cpu_memory_usage", row_limit=10)) +``` + +The resulting table output (omitting some columns): + +``` +--------------------------------- ------------ ------------ ------------ + Name CPU Mem Self CPU Mem # of Calls +--------------------------------- ------------ ------------ ------------ + aten::empty 94.85 Mb 94.85 Mb 205 + aten::max_pool2d_with_indices 11.48 Mb 11.48 Mb 1 + aten::addmm 19.53 Kb 19.53 Kb 1 + aten::mean 10.00 Kb 10.00 Kb 1 + aten::empty_strided 492 b 492 b 5 + aten::cat 240 b 240 b 6 + aten::abs 480 b 240 b 4 + aten::masked_select 120 b 112 b 1 + aten::ne 61 b 53 b 3 + aten::eq 30 b 30 b 1 +--------------------------------- ------------ ------------ ------------ +Self CPU time total: 69.332ms +``` + + + + + ## Exporting chrome trace You can examine the sequence of profiled operators and CUDA kernels in Chrome trace viewer (`chrome://tracing`): -![exporting](https://pytorch.org/tutorials/_images/trace_img.png) +![exporting](../imgs/profile_export.png) @@ -195,64 +267,14 @@ with accelerator.profile() as prof: -## Additional Arguments - -### Stack Traces - -Enabling stack tracing can provide more detailed information about where time is being spent in your code. - - - - -```python -with profile( - activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA], - with_stack=True -) as prof: - model(inputs) - -print(prof.key_averages(group_by_stack_n=5).table(sort_by="self_cuda_time_total", row_limit=2)) -``` - -The resulting table output (omitting some columns): - -``` -------------------------------------------------------- ------------ ------------ - Name Self CPU Self CUDA -------------------------------------------------------- ------------ ------------ - aten::cudnn_convolution 136.164ms 2.916ms -cudnn_infer_maxwell_scudnn_winograd_128x128_ldg1_ldg... 0.000us 1.703ms -------------------------------------------------------- ------------ ------------ -Self CPU time total: 294.445ms -Self CUDA time total: 4.100ms -``` - - - - -```python -profile_kwargs = ProfileKwargs( - with_stack=True -) -accelerator = Accelerator(kwargs_handlers=[profile_kwargs]) - -with accelerator.profile() as prof: - model(inputs) - -print(prof.key_averages(group_by_stack_n=5).table(sort_by="self_cuda_time_total", row_limit=2)) -``` - - - +## FLOPS -### FLOPS +Use formula to estimate the FLOPs (floating point operations) of specific operators (matrix multiplication and 2D convolution). To measure floating-point operations (FLOPS): - + ```python with profile( From cfa90bfc592349021ab7e56b47d51776080a6520 Mon Sep 17 00:00:00 2001 From: yhna940 Date: Sun, 23 Jun 2024 17:56:59 +0900 Subject: [PATCH 16/27] Enhance error msg --- src/accelerate/utils/dataclasses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/accelerate/utils/dataclasses.py b/src/accelerate/utils/dataclasses.py index 3b74682a496..e4d082e5148 100644 --- a/src/accelerate/utils/dataclasses.py +++ b/src/accelerate/utils/dataclasses.py @@ -431,7 +431,7 @@ def _get_profiler_activity(self, activity: ProfilerActivity) -> List[torch.profi } if activity not in profiler_activity_map: - raise ValueError(f"Invalid profiler activity: {activity}") + raise ValueError(f"Invalid profiler activity: {activity}. Must be one of {list(profiler_activity_map)}.") return profiler_activity_map[activity] def build(self) -> torch.profiler.profile: From 9cd1ea5ff249fd04e8325fa8baeea0bb8d91bb06 Mon Sep 17 00:00:00 2001 From: yhna940 Date: Sun, 23 Jun 2024 19:23:33 +0900 Subject: [PATCH 17/27] Fix type hinting --- src/accelerate/utils/dataclasses.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/accelerate/utils/dataclasses.py b/src/accelerate/utils/dataclasses.py index e4d082e5148..a3171af89c8 100644 --- a/src/accelerate/utils/dataclasses.py +++ b/src/accelerate/utils/dataclasses.py @@ -414,14 +414,15 @@ class ProfileKwargs(KwargsHandler): with_modules: bool = False output_trace_dir: Optional[str] = None - def _get_profiler_activity(self, activity: ProfilerActivity) -> List[torch.profiler.ProfilerActivity]: + def _get_profiler_activity(self, activity: ProfilerActivity) -> torch.profiler.ProfilerActivity: """Get the profiler activity from the string. Args: activity (str): The profiler activity name. Returns: - List[torch.profiler.ProfilerActivity]: The profiler activity.""" + torch.profiler.ProfilerActivity: The profiler activity. + """ profiler_activity_map: dict[str, torch.profiler.ProfilerActivity] = { "cpu": torch.profiler.ProfilerActivity.CPU, From 882804710bc20d71e9c314a884f5307352c94837 Mon Sep 17 00:00:00 2001 From: yhna940 Date: Sun, 23 Jun 2024 19:31:28 +0900 Subject: [PATCH 18/27] Minor refactor --- src/accelerate/utils/dataclasses.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/accelerate/utils/dataclasses.py b/src/accelerate/utils/dataclasses.py index a3171af89c8..dd0aaabe865 100644 --- a/src/accelerate/utils/dataclasses.py +++ b/src/accelerate/utils/dataclasses.py @@ -442,11 +442,16 @@ def build(self) -> torch.profiler.profile: Returns: torch.profiler.profile: The profiler object. """ + activities: Optional[List[ProfilerActivity]] = None + if self.activities is not None: + activities = [self._get_profiler_activity(activity) for activity in self.activities] + schedule: Optional[torch.profiler.schedule] = None + if self.schedule_option is not None: + schedule = torch.profiler.schedule(**self.schedule_option) + return torch.profiler.profile( - activities=[self._get_profiler_activity(activity) for activity in self.activities] - if self.activities is not None - else None, - schedule=torch.profiler.schedule(**self.schedule_option) if self.schedule_option is not None else None, + activities=activities, + schedule=schedule, on_trace_ready=self.on_trace_ready, record_shapes=self.record_shapes, profile_memory=self.profile_memory, From a2243ed0d14c24e3b95190e0e659d55f21fbdacd Mon Sep 17 00:00:00 2001 From: yhna940 Date: Mon, 24 Jun 2024 22:36:49 +0900 Subject: [PATCH 19/27] Fix hf tag --- docs/source/usage_guides/profiler.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/source/usage_guides/profiler.md b/docs/source/usage_guides/profiler.md index adce904ec47..2bba688efdb 100644 --- a/docs/source/usage_guides/profiler.md +++ b/docs/source/usage_guides/profiler.md @@ -70,7 +70,7 @@ print(prof.key_averages().table(sort_by="cpu_time_total", row_limit=10)) ``` - + The resulting table output (omitting some columns): @@ -159,7 +159,7 @@ Self CPU time total: 69.332ms ``` - + ## Exporting chrome trace @@ -182,7 +182,7 @@ prof.export_chrome_trace("trace.json") ``` - + ```python profile_kwargs = ProfileKwargs( @@ -200,7 +200,7 @@ with accelerator.profile() as prof: ``` - + ## Using Profiler to Analyze Long-Running Jobs @@ -241,7 +241,7 @@ with profile( ``` - + ```python def trace_handler(p): @@ -265,7 +265,7 @@ with accelerator.profile() as prof: ``` - + ## FLOPS @@ -287,7 +287,7 @@ print(prof.key_averages().table(sort_by="flops", row_limit=10)) ``` - + ```python profile_kwargs = ProfileKwargs( @@ -323,7 +323,7 @@ Self CUDA time total: 4.165ms ``` - + ## Conclusion and Further Information From ab9a78b1a50bf26d42bf54ccfbb4dfa3dbcd0ab5 Mon Sep 17 00:00:00 2001 From: yhna940 Date: Mon, 24 Jun 2024 22:41:28 +0900 Subject: [PATCH 20/27] Fix copyright year --- docs/source/usage_guides/profiler.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/usage_guides/profiler.md b/docs/source/usage_guides/profiler.md index 2bba688efdb..1b01814cc8a 100644 --- a/docs/source/usage_guides/profiler.md +++ b/docs/source/usage_guides/profiler.md @@ -1,5 +1,5 @@