crystal-technologies's picture
Upload 2711 files
6e73cd3
# Copyright 2022 MosaicML LLM Foundry authors
# SPDX-License-Identifier: Apache-2.0
import copy
import logging
import os
import sys
import warnings
from typing import Any, Dict, List, Optional, Union
import torch
from composer import Trainer
from composer.core import Evaluator
from composer.core.callback import Callback
from composer.utils import dist, get_device, reproducibility
from omegaconf import DictConfig, ListConfig
from omegaconf import OmegaConf as om
from transformers import PreTrainedTokenizerBase
from llmfoundry import (COMPOSER_MODEL_REGISTRY, ComposerHFCausalLM,
MPTForCausalLM, build_finetuning_dataloader,
build_text_denoising_dataloader)
from llmfoundry.data.text_data import build_text_dataloader
from llmfoundry.utils.builders import (build_algorithm, build_callback,
build_icl_data_and_gauntlet,
build_logger, build_optimizer,
build_scheduler, build_tokenizer)
from llmfoundry.utils.config_utils import (log_config, pop_config,
process_init_device,
update_batch_size_info)
def validate_config(cfg: DictConfig):
"""Validates compatible model and dataloader selection."""
loaders = [cfg.train_loader]
if 'eval_loader' in cfg:
eval_loader = cfg.eval_loader
if isinstance(eval_loader, ListConfig):
for loader in eval_loader:
if loader.label is None:
raise ValueError(
'When specifying multiple evaluation datasets, each one must include the \
`label` attribute.')
loaders.append(loader)
else:
loaders.append(eval_loader)
for loader in loaders:
if loader.name == 'text':
if cfg.model.name in ['hf_prefix_lm', 'hf_t5']:
raise ValueError(
f'Model type "{cfg.model.name}" is not supported when using the "text " ' +\
f'dataloader. Please use the "text_denoising" dataloader to pre-train that model type.')
elif loader.name == 'text_denoising':
if cfg.model.name == 'hf_causal_lm':
raise ValueError(
f'Model type "{cfg.model.name}" is not supported when using the "text_denoising" ' +\
f'dataloader. Please use the "text" dataloader to pre-train that model type.')
if loader.mixture_of_denoisers.decoder_only_format and cfg.model.name == 'hf_t5':
warnings.warn(
'Model type "hf_t5" requires `decoder_only_format` to be ``False``. ' +\
'Overriding `decoder_only_format` from ``True`` to ``False``.')
loader.mixture_of_denoisers.decoder_only_format = False
if (not loader.mixture_of_denoisers.decoder_only_format
) and cfg.model.name == 'hf_prefix_lm':
warnings.warn(
'Model type "hf_prefix_lm" requires `decoder_only_format` to be ``True``. ' +\
'Overriding `decoder_only_format` from ``False`` to ``True``.')
loader.mixture_of_denoisers.decoder_only_format = True
if 'icl_tasks' in cfg:
if cfg.model.name == 'hf_t5':
raise ValueError(
'ICL evaluation does not currently support Encoder-Decoder models, such as "hf_t5".'
)
if (cfg.model.get('fc_type', 'torch') != 'te' and 'te' not in cfg.model.get(
'ffn_config', {}).get('ffn_type', 'mptmlp') and
'fp8' in cfg.precision):
warnings.warn(
"fp8 only supported for te.Linear layers. Either set `cfg.model.fc_typ='te'` or "
+
"`cfg.model.ffn_config.ffn_type='te_ln_mlp'` to enable layers using fp8 precision."
)
if (cfg.model.get('fc_type', 'torch') == 'te' or
'te' in cfg.model.get('ffn_config', {}).get('ffn_type', 'mptmlp')):
fsdp_config = cfg.get('fsdp_config', None)
act_ckpt = fsdp_config.get('activation_checkpointing', False)
act_ckpt_reentrant = fsdp_config.get(
'activation_checkpointing_reentrant', True)
if fsdp_config is not None and act_ckpt == True and act_ckpt_reentrant == False:
warnings.warn(
'`te.Linear` layers do not support activation_checkpointing with '
+ '`activation_checkpointing_reentrant = False`. ' +
'Setting cfg.fsdp_config.activation_checkpointing_reentrant=True.'
)
cfg.fsdp_config.activation_checkpointing_reentrant = True
if 'te' in cfg.model.get('ffn_config', {}).get('ffn_type', 'mptmlp'):
warnings.warn(
'`te.LayerNormMLP` requires has issues with torch._dynamo. ' +
'Setting `torch._dynamo.config.suppress_errors = True` and falling back to eager.'
)
torch._dynamo.config.suppress_errors = True # type: ignore (third-party)
if cfg.model.get('load_in_8bit', False):
raise ValueError(
'`load_in_8bit` is only supported for evaluation rather than training.'
)
def build_composer_model(model_cfg: DictConfig,
tokenizer: PreTrainedTokenizerBase):
warnings.filterwarnings(
action='ignore',
message='Torchmetrics v0.9 introduced a new argument class property')
if model_cfg.name not in COMPOSER_MODEL_REGISTRY:
raise ValueError(
f'Not sure how to build model with name={model_cfg.name}')
return COMPOSER_MODEL_REGISTRY[model_cfg.name](model_cfg, tokenizer)
def build_composer_peft_model(
pretrained_model_name_or_path: str, lora_args: Dict[str, Any],
tokenizer: PreTrainedTokenizerBase) -> ComposerHFCausalLM:
try:
from peft import LoraConfig, get_peft_model
except ImportError as e:
raise ImportError(
'Error importing from peft. Please verify that peft and peft utils '
+
'are installed by running `pip install -e .[peft]` from `llm-foundry/`. '
+ f'Error encountered: {e}')
# 1) loads a hf model, 2) adds peft modules, 3) wraps it in a ComposerHFCausalLM.
print('Building Lora config...')
lora_cfg = LoraConfig(**lora_args)
print('Building model from HuggingFace checkpoint...')
model = MPTForCausalLM.from_pretrained(pretrained_model_name_or_path,
trust_remote_code=True)
print('Model built!')
print('Adding Lora modules...')
model = get_peft_model(model, lora_cfg)
print('Lora modules added!')
model = ComposerHFCausalLM(model, tokenizer)
return model
def print_trainable_parameters(model: torch.nn.Module) -> None:
# Prints the number of trainable parameters in the model.
trainable_params = 0
all_param = 0
for _, param in model.named_parameters():
all_param += param.numel()
if param.requires_grad:
trainable_params += param.numel()
print(
f'trainable params: {trainable_params} || all params: {all_param} || trainable%: {100 * trainable_params / all_param}'
)
def build_dataloader(cfg: DictConfig, tokenizer: PreTrainedTokenizerBase,
device_batch_size: int):
if cfg.name == 'text':
return build_text_dataloader(
cfg,
tokenizer,
device_batch_size,
)
elif cfg.name == 'text_denoising':
return build_text_denoising_dataloader(
cfg,
tokenizer,
device_batch_size,
)
elif cfg.name == 'finetuning':
return build_finetuning_dataloader(
cfg,
tokenizer,
device_batch_size,
)
else:
raise ValueError(f'Not sure how to build dataloader with config: {cfg}')
def main(cfg: DictConfig) -> Trainer:
# Filter deprecation warning from torch internal usage
warnings.filterwarnings(
action='ignore',
category=UserWarning,
message=
'torch.distributed.*_base is a private function and will be deprecated.*'
)
# Check for incompatibilities between the model and data loaders
validate_config(cfg)
# Resolve all interpolation variables as early as possible
om.resolve(cfg)
# Create copy of config for logging
logged_cfg: DictConfig = copy.deepcopy(cfg)
# Get max split size mb
max_split_size_mb: Optional[int] = cfg.pop('max_split_size_mb', None)
if max_split_size_mb is not None:
os.environ[
'PYTORCH_CUDA_ALLOC_CONF'] = f'max_split_size_mb:{max_split_size_mb}'
# Set seed first
seed: int = pop_config(cfg, 'seed', must_exist=True)
reproducibility.seed_all(seed)
# Initialize pytorch distributed training process groups
dist_timeout: Union[int, float] = pop_config(cfg,
'dist_timeout',
must_exist=False,
default_value=600.0)
dist.initialize_dist(get_device(None), timeout=dist_timeout)
# Get global and device batch size information from distributed/single node setting
cfg = update_batch_size_info(cfg)
logged_cfg.update(cfg, merge=True)
# Mandatory model training configs
model_config: DictConfig = pop_config(cfg, 'model', must_exist=True)
tokenizer_config: Dict[str, Any] = pop_config(cfg,
'tokenizer',
must_exist=True,
convert=True)
optimizer_config: Dict[str, Any] = pop_config(cfg,
'optimizer',
must_exist=True,
convert=True)
scheduler_config: Dict[str, Any] = pop_config(cfg,
'scheduler',
must_exist=True,
convert=True)
train_loader_config: DictConfig = pop_config(cfg,
'train_loader',
must_exist=True)
# Optional fsdp data, fine-tuning, and eval configs
fsdp_config: Optional[Dict[str, Any]] = pop_config(cfg,
'fsdp_config',
must_exist=False,
default_value=None,
convert=True)
lora_config: Optional[Dict[str, Any]] = pop_config(cfg,
'lora',
must_exist=False,
default_value=None,
convert=True)
eval_loader_config: Optional[Union[DictConfig, ListConfig]] = pop_config(
cfg, 'eval_loader', must_exist=False, default_value=None)
icl_tasks_config: Optional[Union[ListConfig,
str]] = pop_config(cfg,
'icl_tasks',
must_exist=False,
default_value=None)
eval_gauntlet_config: Optional[Union[DictConfig,
str]] = pop_config(cfg,
'eval_gauntlet',
must_exist=False,
default_value=None)
if eval_gauntlet_config is None:
eval_gauntlet_config = pop_config(cfg,
'model_gauntlet',
must_exist=False,
default_value=None)
if eval_gauntlet_config is not None:
print(
'Use of the key `model_gauntlet` is deprecated, please use the key `eval_gauntlet`'
)
icl_subset_num_batches: Optional[int] = pop_config(cfg,
'icl_subset_num_batches',
must_exist=False,
default_value=None)
icl_seq_len: Optional[int] = pop_config(cfg,
'icl_seq_len',
must_exist=False,
default_value=None)
# Optional logging, evaluation and callback configs
logger_configs: Optional[DictConfig] = pop_config(cfg,
'loggers',
must_exist=False,
default_value=None)
callback_configs: Optional[DictConfig] = pop_config(cfg,
'callbacks',
must_exist=False,
default_value=None)
algorithm_configs: Optional[DictConfig] = pop_config(cfg,
'algorithms',
must_exist=False,
default_value=None)
# Mandatory hyperparameters for training
device_train_batch_size: int = pop_config(cfg,
'device_train_batch_size',
must_exist=True)
device_eval_batch_size: int = pop_config(cfg,
'device_eval_batch_size',
must_exist=True)
max_duration: Union[int, str] = pop_config(cfg,
'max_duration',
must_exist=True)
eval_interval: Union[int, str] = pop_config(cfg,
'eval_interval',
must_exist=True)
precision: str = pop_config(cfg, 'precision', must_exist=True)
max_seq_len: int = pop_config(cfg, 'max_seq_len', must_exist=True)
# Optional parameters will be set to default values if not specified.
default_run_name: str = os.environ.get('RUN_NAME', 'llm')
run_name: str = pop_config(cfg,
'run_name',
must_exist=False,
default_value=default_run_name)
save_folder: Optional[str] = pop_config(cfg,
'save_folder',
must_exist=False,
default_value=None)
save_latest_filename: str = pop_config(cfg,
'save_latest_filename',
must_exist=False,
default_value='latest-rank{rank}.pt')
save_overwrite: bool = pop_config(cfg,
'save_overwrite',
must_exist=False,
default_value=False)
save_weights_only: bool = pop_config(cfg,
'save_weights_only',
must_exist=False,
default_value=False)
save_filename: str = pop_config(
cfg,
'save_filename',
must_exist=False,
default_value='ep{epoch}-ba{batch}-rank{rank}.pt')
save_interval: Union[str, int] = pop_config(cfg,
'save_interval',
must_exist=False,
default_value='1000ba')
save_num_checkpoints_to_keep: int = pop_config(
cfg, 'save_num_checkpoints_to_keep', must_exist=False, default_value=-1)
progress_bar = pop_config(cfg,
'progress_bar',
must_exist=False,
default_value=False)
log_to_console: bool = pop_config(cfg,
'log_to_console',
must_exist=False,
default_value=True)
python_log_level: Optional[str] = pop_config(cfg,
'python_log_level',
must_exist=False,
default_value='debug')
console_log_interval: Union[int, str] = pop_config(cfg,
'console_log_interval',
must_exist=False,
default_value='1ba')
device_train_microbatch_size: Union[str, int] = pop_config(
cfg,
'device_train_microbatch_size',
must_exist=False,
default_value='auto')
eval_subset_num_batches: int = pop_config(cfg,
'eval_subset_num_batches',
must_exist=False,
default_value=-1)
eval_first: bool = pop_config(cfg,
'eval_first',
must_exist=False,
default_value=False)
load_path: str = pop_config(cfg,
'load_path',
must_exist=False,
default_value=None)
load_weights_only: bool = pop_config(cfg,
'load_weights_only',
must_exist=False,
default_value=False)
load_ignore_keys: Optional[List[str]] = pop_config(cfg,
'load_ignore_keys',
must_exist=False,
default_value=None)
# Enable autoresume from model checkpoints if possible
autoresume_default: bool = False
if logged_cfg.get('run_name', None) is not None \
and save_folder is not None \
and not save_overwrite \
and not save_weights_only:
autoresume_default = True
if cfg.get('autoresume') is None and autoresume_default:
print('As run_name, save_folder, and save_latest_filename are set, \
changing autoresume default to True...')
autoresume: bool = pop_config(cfg,
'autoresume',
must_exist=False,
default_value=autoresume_default)
# Pop known unused parameters that are used as interpolation variables or
# created by update_batch_size_info.
pop_config(cfg, 'data_local', must_exist=False)
pop_config(cfg, 'data_remote', must_exist=False)
pop_config(cfg, 'global_seed', must_exist=False)
pop_config(cfg, 'global_train_batch_size', must_exist=False)
pop_config(cfg, 'n_gpus', must_exist=False)
pop_config(cfg, 'device_train_grad_accum', must_exist=False)
# Warn users for unused parameters
for key in cfg:
warnings.warn(
f'Unused parameter {key} found in cfg. Please check your yaml to ensure this parameter is necessary.'
)
# Warn if fsdp is enabled but user only has 1 GPU
if dist.get_world_size() == 1 and fsdp_config is not None:
warnings.warn(
'FSDP is not applicable for single-GPU training. Reverting to DDP.')
fsdp_config = None
# set logging level
if python_log_level is not None:
logging.basicConfig(
# Example of format string
# 2022-06-29 11:22:26,152: rank0[822018][MainThread]: INFO: Message here
format=
f'%(asctime)s: rank{dist.get_global_rank()}[%(process)d][%(threadName)s]: %(levelname)s: %(name)s: %(message)s'
)
logging.getLogger('llmfoundry').setLevel(python_log_level.upper())
# Initialize context
init_context = process_init_device(model_config, fsdp_config)
logged_cfg.update({'fsdp_config': fsdp_config}, merge=True)
# Build tokenizer
tokenizer_name = tokenizer_config['name']
tokenizer_kwargs = tokenizer_config.get('kwargs', {})
tokenizer = build_tokenizer(tokenizer_name, tokenizer_kwargs)
# Scheduler
scheduler_name: str = scheduler_config.pop('name')
scheduler = build_scheduler(scheduler_name, scheduler_config)
# Loggers
loggers = [
build_logger(str(name), logger_cfg)
for name, logger_cfg in logger_configs.items()
] if logger_configs else None
# Callbacks
callbacks: List[Callback] = [
build_callback(str(name), callback_cfg)
for name, callback_cfg in callback_configs.items()
] if callback_configs else []
# Algorithms
algorithms = [
build_algorithm(str(name), algorithm_cfg)
for name, algorithm_cfg in algorithm_configs.items()
] if algorithm_configs else None
# Dataloaders
print('Building train loader...')
train_loader = build_dataloader(
train_loader_config,
tokenizer,
device_train_batch_size,
)
## Evaluation
print('Building eval loader...')
evaluators = []
eval_loaders = []
if eval_loader_config is not None:
is_multi_eval = isinstance(eval_loader_config, ListConfig)
eval_configs = eval_loader_config if is_multi_eval else [
eval_loader_config
]
for eval_config in eval_configs:
eval_dataloader = build_dataloader(eval_config, tokenizer,
device_eval_batch_size)
eval_loader = Evaluator(
label=f'eval/{eval_config.label}' if is_multi_eval else 'eval',
dataloader=eval_dataloader,
metric_names=[], # we will add these after model is created
)
eval_loaders.append(eval_loader)
eval_gauntlet_callback = None
if icl_tasks_config is not None:
icl_evaluators, _, eval_gauntlet_callback = build_icl_data_and_gauntlet(
icl_tasks_config, eval_gauntlet_config, tokenizer,
device_eval_batch_size, icl_seq_len if icl_seq_len else max_seq_len,
icl_subset_num_batches)
evaluators.extend(icl_evaluators)
if eval_gauntlet_callback is not None:
callbacks.append(eval_gauntlet_callback)
# Build Model
print('Initializing model...')
with init_context:
if lora_config is not None: # frozen model + trainable lora modules
model: ComposerHFCausalLM = build_composer_peft_model(
model_config.pretrained_model_name_or_path, lora_config['args'],
tokenizer)
print_trainable_parameters(model) # should not be 100%
else: # standard model
model = build_composer_model(model_config, tokenizer)
if model_config.get('master_weights_dtype') in ('bf16', 'bfloat16'):
model = model.to(dtype=torch.bfloat16)
elif model_config.get('master_weights_dtype') in ('f16', 'float16'):
model = model.to(dtype=torch.float16)
# Log number of parameters
n_params = sum(p.numel() for p in model.parameters())
logged_cfg.update({'n_params': n_params})
# Optimizer
optimizer_name: str = optimizer_config.pop('name')
optimizer = build_optimizer(model, optimizer_name, optimizer_config)
# Now add the eval metrics
if eval_loader_config is not None:
assert model.train_metrics is not None
eval_metric_names = list(model.train_metrics.keys())
for eval_loader in eval_loaders:
eval_loader.metric_names = eval_metric_names
evaluators.insert(0, eval_loader) # Put the base eval_loaders first
# Build the Trainer
print('Building trainer...')
trainer = Trainer(
run_name=run_name,
seed=seed,
model=model,
train_dataloader=train_loader,
eval_dataloader=evaluators,
optimizers=optimizer,
schedulers=scheduler,
max_duration=max_duration,
eval_interval=eval_interval,
eval_subset_num_batches=eval_subset_num_batches,
progress_bar=progress_bar,
log_to_console=log_to_console,
console_log_interval=console_log_interval,
loggers=loggers,
callbacks=callbacks,
precision=precision,
algorithms=algorithms,
device_train_microbatch_size=device_train_microbatch_size,
fsdp_config=fsdp_config,
save_folder=save_folder,
save_filename=save_filename,
save_latest_filename=save_latest_filename,
save_interval=save_interval,
save_num_checkpoints_to_keep=save_num_checkpoints_to_keep,
save_overwrite=save_overwrite,
save_weights_only=save_weights_only,
load_path=load_path,
load_weights_only=load_weights_only,
load_ignore_keys=load_ignore_keys,
autoresume=autoresume,
python_log_level=python_log_level,
dist_timeout=dist_timeout,
)
print('Logging config')
log_config(logged_cfg)
torch.cuda.empty_cache()
# Eval first if requested
if eval_first and trainer.state.timestamp.batch.value == 0:
trainer.eval()
print('Starting training...')
trainer.fit()
print('Done.')
return trainer
if __name__ == '__main__':
yaml_path, args_list = sys.argv[1], sys.argv[2:]
with open(yaml_path) as f:
yaml_cfg = om.load(f)
cli_cfg = om.from_cli(args_list)
cfg = om.merge(yaml_cfg, cli_cfg)
om.resolve(cfg)
assert isinstance(cfg, DictConfig)
main(cfg)