Repository: lebrice/SimpleParsing

Issue:
Interop with HuggingFace: Use `help` entry from field metadata dict
The dataclasses used by the HfArgumentParser have a `metatada={"help": "help string"}`, for example, here:

https://github.com/huggingface/transformers/blob/main/examples/pytorch/text-classification/run_glue.py#L80-L83

It would be good to support this, so that one could use `parser.add_arguments(TrainingArguments, "trainer")` in SimpleParsing directly!


Discussion:

Patch:
diff --git a/simple_parsing/docstring.py b/simple_parsing/docstring.py
index 1686fdf..fa66351 100644
--- a/simple_parsing/docstring.py
+++ b/simple_parsing/docstring.py
@@ -24,6 +24,16 @@ class AttributeDocString:
     desc_from_cls_docstring: str = ""
     """ The description of this field from the class docstring. """
 
+    @property
+    def help_string(self) -> str:
+        """Returns the value that will be used for the "--help" string, using the contents of self."""
+        return (
+            self.docstring_below
+            or self.comment_above
+            or self.comment_inline
+            or self.desc_from_cls_docstring
+        )
+
 
 def get_attribute_docstring(
     dataclass: type, field_name: str, accumulate_from_bases: bool = True
diff --git a/simple_parsing/helpers/serialization/serializable.py b/simple_parsing/helpers/serialization/serializable.py
index 339210d..4359918 100644
--- a/simple_parsing/helpers/serialization/serializable.py
+++ b/simple_parsing/helpers/serialization/serializable.py
@@ -461,9 +461,9 @@ extensions_to_loading_fn: dict[str, Callable[[IO], Any]] = {
     ".json": json.load,
     ".pkl": pickle.load,
 }
-extensions_to_read_mode: dict[str, str] = {}
+extensions_to_read_mode: dict[str, str] = {".pkl": "rb"}
 
-extensions_to_write_mode: dict[str, str] = {}
+extensions_to_write_mode: dict[str, str] = {".pkl": "wb"}
 extensions_to_dump_fn: dict[str, Callable[[Any, IO], None]] = {
     ".json": json.dump,
     ".pkl": pickle.dump,
diff --git a/simple_parsing/wrappers/dataclass_wrapper.py b/simple_parsing/wrappers/dataclass_wrapper.py
index dba5527..bc1ef4e 100644
--- a/simple_parsing/wrappers/dataclass_wrapper.py
+++ b/simple_parsing/wrappers/dataclass_wrapper.py
@@ -2,11 +2,15 @@ from __future__ import annotations
 
 import argparse
 import dataclasses
+import inspect
 import sys
+import textwrap
 from dataclasses import MISSING
 from logging import getLogger
 from typing import cast
 
+import docstring_parser as dp
+
 from .. import docstring, utils
 from ..utils import Dataclass
 from .field_wrapper import FieldWrapper
@@ -14,6 +18,13 @@ from .wrapper import Wrapper
 
 logger = getLogger(__name__)
 
+MAX_DOCSTRING_DESC_LINES_HEIGHT: int = 50
+"""
+Maximum number of lines of the class docstring to include in the autogenerated argument group
+description. If fields don't have docstrings or help text, then this is not used, and the entire
+docstring is used as the description of the argument group.
+"""
+
 
 class DataclassWrapper(Wrapper[Dataclass]):
     def __init__(
@@ -157,8 +168,6 @@ class DataclassWrapper(Wrapper[Dataclass]):
 
     def equivalent_argparse_code(self, leading="group") -> str:
         code = ""
-        import textwrap
-
         code += textwrap.dedent(
             f"""
         group = parser.add_argument_group(title="{self.title.strip()}", description="{self.description.strip()}")
@@ -257,7 +266,35 @@ class DataclassWrapper(Wrapper[Dataclass]):
                     return doc.comment_above
                 elif doc.comment_inline:
                     return doc.comment_inline
-        return self.dataclass.__doc__ or ""
+
+        # NOTE: The class docstring may be EXTRELEMY LARGE.
+
+        class_docstring = inspect.getdoc(self.dataclass) or ""
+        if not class_docstring:
+            return ""
+
+        doc = dp.parse(class_docstring)
+
+        from simple_parsing.decorators import _description_from_docstring
+
+        description = _description_from_docstring(doc)
+
+        num_lines = len(description.splitlines())
+        shortened_description = (
+            "\n".join(description.splitlines()[:MAX_DOCSTRING_DESC_LINES_HEIGHT]) + " ..."
+        )
+
+        fields_have_docstrings = any(f._docstring.help_string for f in self.fields)
+        docstring_is_huge = num_lines > MAX_DOCSTRING_DESC_LINES_HEIGHT
+        if not fields_have_docstrings:
+            # The fields don't have docstrings. Return the entire docstring, regardless of its
+            # size.
+            return description
+        # Fields have docstrings, so there's probably some duplication between the docstring and
+        # the dataclass fields help. Shorten the docstring, if needed.
+        if docstring_is_huge:
+            return shortened_description
+        return description
 
     # @property
     # def prefix(self) -> str:
diff --git a/simple_parsing/wrappers/field_wrapper.py b/simple_parsing/wrappers/field_wrapper.py
index 0665490..d205df4 100644
--- a/simple_parsing/wrappers/field_wrapper.py
+++ b/simple_parsing/wrappers/field_wrapper.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 import argparse
 import dataclasses
 import inspect
@@ -5,7 +7,7 @@ import sys
 import typing
 from enum import Enum, auto
 from logging import getLogger
-from typing import Any, ClassVar, Dict, List, Optional, Set, Tuple, Type, Union, cast
+from typing import Any, ClassVar, Union, cast
 
 from simple_parsing.help_formatter import TEMPORARY_TOKEN
 
@@ -102,30 +104,40 @@ class FieldWrapper(Wrapper[dataclasses.Field]):
     # Controls how nested arguments are generated.
     nested_mode: ClassVar[NestedMode] = NestedMode.DEFAULT
 
-    def __init__(self, field: dataclasses.Field, parent: Any = None, prefix: str = ""):
+    def __init__(
+        self, field: dataclasses.Field, parent: DataclassWrapper | None = None, prefix: str = ""
+    ):
         super().__init__(wrapped=field, name=field.name)
         self.field: dataclasses.Field = field
         self.prefix: str = prefix
         self._parent: Any = parent
         # Holders used to 'cache' the properties.
         # (could've used cached_property with Python 3.8).
-        self._option_strings: Optional[Set[str]] = None
-        self._required: Optional[bool] = None
-        self._docstring: docstring.AttributeDocString = docstring.AttributeDocString()
-        self._help: Optional[str] = None
-        self._metavar: Optional[str] = None
-        self._default: Optional[Union[Any, List[Any]]] = None
-        self._dest: Optional[str] = None
+        self._option_strings: set[str] | None = None
+        self._required: bool | None = None
+
+        try:
+            self._docstring = docstring.get_attribute_docstring(
+                self.parent.dataclass, self.field.name
+            )
+        except (SystemExit, Exception) as e:
+            logger.debug(f"Couldn't find attribute docstring for field {self.name}, {e}")
+            self._docstring = docstring.AttributeDocString()
+
+        self._help: str | None = None
+        self._metavar: str | None = None
+        self._default: Any | list[Any] | None = None
+        self._dest: str | None = None
         # the argparse-related options:
-        self._arg_options: Dict[str, Any] = {}
-        self._dest_field: Optional["FieldWrapper"] = None
-        self._type: Optional[Type[Any]] = None
+        self._arg_options: dict[str, Any] = {}
+        self._dest_field: FieldWrapper | None = None
+        self._type: type[Any] | None = None
 
         # stores the resulting values for each of the destination attributes.
-        self._results: Dict[str, Any] = {}
+        self._results: dict[str, Any] = {}
 
     @property
-    def arg_options(self) -> Dict[str, Any]:
+    def arg_options(self) -> dict[str, Any]:
         """Dictionary of values to be passed to the `add_argument` method.
 
         The main feature of this package is to infer these arguments
@@ -160,7 +172,7 @@ class FieldWrapper(Wrapper[dataclasses.Field]):
         parser: argparse.ArgumentParser,
         namespace: argparse.Namespace,
         values: Any,
-        option_string: Optional[str] = None,
+        option_string: str | None = None,
     ):
         """Immitates a custom Action, which sets the corresponding value from
         `values` at the right destination in the `constructor_arguments` of the
@@ -210,7 +222,7 @@ class FieldWrapper(Wrapper[dataclasses.Field]):
                     namespace.subgroups[self.dest] = value
                     logger.info(f"Chosen subgroup for '{self.dest}':  '{value}'")
 
-    def get_arg_options(self) -> Dict[str, Any]:
+    def get_arg_options(self) -> dict[str, Any]:
         """Create the `parser.add_arguments` kwargs for this field.
 
         TODO: Refactor this, following https://github.com/lebrice/SimpleParsing/issues/150
@@ -218,7 +230,7 @@ class FieldWrapper(Wrapper[dataclasses.Field]):
         if not self.field.init:
             # Don't add command-line arguments for fields that have `init=False`.
             return {}
-        _arg_options: Dict[str, Any] = {}
+        _arg_options: dict[str, Any] = {}
 
         # Not sure why, but argparse doesn't allow using a different dest for a positional arg.
         # _Appears_ trivial to support within argparse.
@@ -378,7 +390,7 @@ class FieldWrapper(Wrapper[dataclasses.Field]):
 
         return _arg_options
 
-    def duplicate_if_needed(self, parsed_values: Any) -> List[Any]:
+    def duplicate_if_needed(self, parsed_values: Any) -> list[Any]:
         """Duplicates the passed argument values if needed, such that each instance gets a value.
 
         For example, if we expected 3 values for an argument, and a single value was passed,
@@ -413,7 +425,7 @@ class FieldWrapper(Wrapper[dataclasses.Field]):
                 and len(parsed_values) == 1
                 and len(parsed_values[0]) == num_instances_to_parse
             ):
-                result: List = parsed_values[0]
+                result: list = parsed_values[0]
                 return result
 
         if not isinstance(parsed_values, (list, tuple)):
@@ -521,7 +533,7 @@ class FieldWrapper(Wrapper[dataclasses.Field]):
         return len(self.destinations) > 1
 
     @property
-    def action(self) -> Union[str, Type[argparse.Action]]:
+    def action(self) -> str | type[argparse.Action]:
         """The `action` argument to be passed to `add_argument(...)`."""
         return self.custom_arg_options.get("action", "store")
 
@@ -532,7 +544,7 @@ class FieldWrapper(Wrapper[dataclasses.Field]):
         return self.action.__name__
 
     @property
-    def custom_arg_options(self) -> Dict[str, Any]:
+    def custom_arg_options(self) -> dict[str, Any]:
         """Custom argparse options that overwrite those in `arg_options`.
 
         Can be set by using the `field` function, passing in a keyword argument
@@ -542,11 +554,11 @@ class FieldWrapper(Wrapper[dataclasses.Field]):
         return self.field.metadata.get("custom_args", {})
 
     @property
-    def destinations(self) -> List[str]:
+    def destinations(self) -> list[str]:
         return [f"{parent_dest}.{self.name}" for parent_dest in self.parent.destinations]
 
     @property
-    def option_strings(self) -> List[str]:
+    def option_strings(self) -> list[str]:
         """Generates the `option_strings` argument to the `add_argument` call.
 
         `parser.add_argument(*name_or_flags, **arg_options)`
@@ -565,10 +577,10 @@ class FieldWrapper(Wrapper[dataclasses.Field]):
 
         """
 
-        dashes: List[str] = []  # contains the leading dashes.
-        options: List[str] = []  # contains the name following the dashes.
+        dashes: list[str] = []  # contains the leading dashes.
+        options: list[str] = []  # contains the name following the dashes.
 
-        def add_args(dash: str, candidates: List[str]) -> None:
+        def add_args(dash: str, candidates: list[str]) -> None:
             for candidate in candidates:
                 options.append(candidate)
                 dashes.append(dash)
@@ -642,7 +654,7 @@ class FieldWrapper(Wrapper[dataclasses.Field]):
     #     return self._prefix
 
     @property
-    def aliases(self) -> List[str]:
+    def aliases(self) -> list[str]:
         return self.field.metadata.get("alias", [])
 
     @property
@@ -661,7 +673,7 @@ class FieldWrapper(Wrapper[dataclasses.Field]):
         return self.dest_field is not None
 
     @property
-    def dest_field(self) -> Optional["FieldWrapper"]:
+    def dest_field(self) -> FieldWrapper | None:
         """Return the `FieldWrapper` for which `self` is a proxy (same dest).
         When a `dest` argument is passed to `field()`, and its value is a
         `Field`, that indicates that this Field is just a proxy for another.
@@ -674,7 +686,7 @@ class FieldWrapper(Wrapper[dataclasses.Field]):
             return self._dest_field
         custom_dest = self.custom_arg_options.get("dest")
         if isinstance(custom_dest, dataclasses.Field):
-            all_fields: List[FieldWrapper] = []
+            all_fields: list[FieldWrapper] = []
             for parent in self.lineage():
                 all_fields.extend(parent.fields)  # type: ignore
             for other_wrapper in all_fields:
@@ -781,7 +793,7 @@ class FieldWrapper(Wrapper[dataclasses.Field]):
         self._required = value
 
     @property
-    def type(self) -> Type[Any]:
+    def type(self) -> type[Any]:
         """Returns the wrapped field's type annotation."""
         # TODO: Refactor this. Really ugly.
         if self._type is None:
@@ -809,10 +821,13 @@ class FieldWrapper(Wrapper[dataclasses.Field]):
         return self.choices is not None
 
     @property
-    def choices(self) -> Optional[List]:
+    def choices(self) -> list | None:
         """The list of possible values that can be passed on the command-line for this field, or None."""
+
         if "choices" in self.custom_arg_options:
             return self.custom_arg_options["choices"]
+        if "choices" in self.field.metadata:
+            return list(self.field.metadata["choices"])
         if "choice_dict" in self.field.metadata:
             return list(self.field.metadata["choice_dict"].keys())
         if utils.is_literal(self.type):
@@ -824,7 +839,7 @@ class FieldWrapper(Wrapper[dataclasses.Field]):
         return None
 
     @property
-    def choice_dict(self) -> Optional[Dict[str, Any]]:
+    def choice_dict(self) -> dict[str, Any] | None:
         if "choice_dict" in self.field.metadata:
             return self.field.metadata["choice_dict"]
         if utils.is_literal(self.type):
@@ -836,16 +851,11 @@ class FieldWrapper(Wrapper[dataclasses.Field]):
         return None
 
     @property
-    def help(self) -> Optional[str]:
+    def help(self) -> str | None:
         if self._help:
             return self._help
-        try:
-            self._docstring = docstring.get_attribute_docstring(
-                self.parent.dataclass, self.field.name
-            )
-        except (SystemExit, Exception) as e:
-            logger.debug(f"Couldn't find attribute docstring for field {self.name}, {e}")
-            self._docstring = docstring.AttributeDocString()
+        if self.field.metadata.get("help"):
+            return self.field.metadata.get("help")
 
         self._help = (
             self._docstring.docstring_below
@@ -864,7 +874,7 @@ class FieldWrapper(Wrapper[dataclasses.Field]):
         self._help = value
 
     @property
-    def metavar(self) -> Optional[str]:
+    def metavar(self) -> str | None:
         """Returns the 'metavar' when set using one of the `field` functions,
         else None.
         """
@@ -914,21 +924,21 @@ class FieldWrapper(Wrapper[dataclasses.Field]):
         return "subgroups" in self.field.metadata
 
     @property
-    def subgroup_choices(self) -> Dict[str, Type]:
+    def subgroup_choices(self) -> dict[str, type]:
         if not self.is_subgroup:
             raise RuntimeError(f"Field {self.field} doesn't have subgroups! ")
         return self.field.metadata["subgroups"]
 
     @property
-    def type_arguments(self) -> Optional[Tuple[Type, ...]]:
+    def type_arguments(self) -> tuple[type, ...] | None:
         return utils.get_type_arguments(self.type)
 
     @property
-    def parent(self) -> "DataclassWrapper":
+    def parent(self) -> DataclassWrapper:
         return self._parent
 
     @property
-    def subparsers_dict(self) -> Optional[Dict[str, Type]]:
+    def subparsers_dict(self) -> dict[str, type] | None:
         """The dict of subparsers, which is created either when using a
         Union[<dataclass_1>, <dataclass_2>] type annotation, or when using the
         `subparsers()` function.
@@ -943,7 +953,7 @@ class FieldWrapper(Wrapper[dataclasses.Field]):
                     for dataclass_type in type_arguments
                 }
 
-    def add_subparsers(self, parser: "ArgumentParser"):
+    def add_subparsers(self, parser: ArgumentParser):
         assert self.is_subparser
 
         # add subparsers for each dataclass type in the field.
@@ -985,7 +995,7 @@ class FieldWrapper(Wrapper[dataclasses.Field]):
         return f"group.add_argument(*{self.option_strings}, **{arg_options_string})"
 
 
-def only_keep_action_args(options: Dict[str, Any], action: Union[str, Any]) -> Dict[str, Any]:
+def only_keep_action_args(options: dict[str, Any], action: str | Any) -> dict[str, Any]:
     """Remove all the arguments in `options` that aren't required by the Action.
 
     Parameters
@@ -1002,7 +1012,7 @@ def only_keep_action_args(options: Dict[str, Any], action: Union[str, Any]) -> D
         [description]
     """
     # TODO: explicitly test these custom actions?
-    argparse_action_classes: Dict[str, Type[argparse.Action]] = {
+    argparse_action_classes: dict[str, type[argparse.Action]] = {
         "store": argparse._StoreAction,
         "store_const": argparse._StoreConstAction,
         "store_true": argparse._StoreTrueAction,


Test patch:
diff --git a/test/test_docstrings.py b/test/test_docstrings.py
index 666bb80..2f10ce4 100644
--- a/test/test_docstrings.py
+++ b/test/test_docstrings.py
@@ -200,3 +200,21 @@ def test_desc_from_cls_docstring():
         comment_inline="side",
         docstring_below="below",
     )
+
+
+def test_help_takes_value_from_docstring():
+    @dataclass
+    class Args(TestSetup):
+        """args.
+
+        Attributes:
+            user: Username
+            verbose: Display logs
+        """
+
+        user: str
+        verbose: bool = False
+
+    assert get_attribute_docstring(Args, "verbose") == AttributeDocString(
+        desc_from_cls_docstring="Display logs",
+    )
diff --git a/test/test_huggingface_compat.py b/test/test_huggingface_compat.py
new file mode 100644
index 0000000..46576a0
--- /dev/null
+++ b/test/test_huggingface_compat.py
@@ -0,0 +1,1299 @@
+""" Simple test for compatibility with HuggingFace's help text convention.
+
+This checks that Simple-Parsing can be used as a replacement for the HFArgumentParser.
+"""
+import io
+from dataclasses import dataclass, field
+from enum import Enum
+from pathlib import Path
+from typing import List, Optional, Union
+
+import pytest
+
+from simple_parsing import ArgumentParser
+from simple_parsing.docstring import get_attribute_docstring
+
+from .testutils import TestSetup, raises_invalid_choice
+
+
+@dataclass
+class ModelArguments:
+    """
+    Arguments pertaining to which model/config/tokenizer we are going to fine-tune, or train from scratch.
+    """
+
+    model_name_or_path: Optional[str] = field(
+        default=None,
+        metadata={
+            "help": (
+                "The model checkpoint for weights initialization.Don't set if you want to train a model from scratch."
+            )
+        },
+    )
+    model_type: Optional[str] = field(
+        default=None,
+        metadata={
+            "help": "If training from scratch, pass a model type from the list: "
+            # + ", ".join(MODEL_TYPES)
+        },
+    )
+    config_overrides: Optional[str] = field(
+        default=None,
+        metadata={
+            "help": (
+                "Override some existing default config settings when a model is trained from scratch. Example: "
+                "n_embd=10,resid_pdrop=0.2,scale_attn_weights=false,summary_type=cls_index"
+            )
+        },
+    )
+    config_name: Optional[str] = field(
+        default=None,
+        metadata={"help": "Pretrained config name or path if not the same as model_name"},
+    )
+    tokenizer_name: Optional[str] = field(
+        default=None,
+        metadata={"help": "Pretrained tokenizer name or path if not the same as model_name"},
+    )
+    cache_dir: Optional[str] = field(
+        default=None,
+        metadata={
+            "help": "Where do you want to store the pretrained models downloaded from huggingface.co"
+        },
+    )
+    use_fast_tokenizer: bool = field(
+        default=True,
+        metadata={
+            "help": "Whether to use one of the fast tokenizer (backed by the tokenizers library) or not."
+        },
+    )
+    model_revision: str = field(
+        default="main",
+        metadata={
+            "help": "The specific model version to use (can be a branch name, tag name or commit id)."
+        },
+    )
+    use_auth_token: bool = field(
+        default=False,
+        metadata={
+            "help": (
+                "Will use the token generated when running `huggingface-cli login` (necessary to use this script "
+                "with private models)."
+            )
+        },
+    )
+
+    def __post_init__(self):
+        if self.config_overrides is not None and (
+            self.config_name is not None or self.model_name_or_path is not None
+        ):
+            raise ValueError(
+                "--config_overrides can't be used in combination with --config_name or --model_name_or_path"
+            )
+
+
+@dataclass
+class DataTrainingArguments:
+    """
+    Arguments pertaining to what data we are going to input our model for training and eval.
+    """
+
+    dataset_name: Optional[str] = field(
+        default=None,
+        metadata={"help": "The name of the dataset to use (via the datasets library)."},
+    )
+    dataset_config_name: Optional[str] = field(
+        default=None,
+        metadata={
+            "help": "The configuration name of the dataset to use (via the datasets library)."
+        },
+    )
+    train_file: Optional[str] = field(
+        default=None, metadata={"help": "The input training data file (a text file)."}
+    )
+    validation_file: Optional[str] = field(
+        default=None,
+        metadata={
+            "help": "An optional input evaluation data file to evaluate the perplexity on (a text file)."
+        },
+    )
+    max_train_samples: Optional[int] = field(
+        default=None,
+        metadata={
+            "help": (
+                "For debugging purposes or quicker training, truncate the number of training examples to this "
+                "value if set."
+            )
+        },
+    )
+    max_eval_samples: Optional[int] = field(
+        default=None,
+        metadata={
+            "help": (
+                "For debugging purposes or quicker training, truncate the number of evaluation examples to this "
+                "value if set."
+            )
+        },
+    )
+
+    block_size: Optional[int] = field(
+        default=None,
+        metadata={
+            "help": (
+                "Optional input sequence length after tokenization. "
+                "The training dataset will be truncated in block of this size for training. "
+                "Default to the model max input length for single sentence inputs (take into account special tokens)."
+            )
+        },
+    )
+    overwrite_cache: bool = field(
+        default=False,
+        metadata={"help": "Overwrite the cached training and evaluation sets"},
+    )
+    validation_split_percentage: Optional[int] = field(
+        default=5,
+        metadata={
+            "help": "The percentage of the train set used as validation set in case there's no validation split"
+        },
+    )
+    preprocessing_num_workers: Optional[int] = field(
+        default=None,
+        metadata={"help": "The number of processes to use for the preprocessing."},
+    )
+    keep_linebreaks: bool = field(
+        default=True,
+        metadata={"help": "Whether to keep line breaks when using TXT files or not."},
+    )
+
+    def __post_init__(self):
+        if self.dataset_name is None and self.train_file is None and self.validation_file is None:
+            raise ValueError("Need either a dataset name or a training/validation file.")
+        else:
+            if self.train_file is not None:
+                extension = self.train_file.split(".")[-1]
+                assert extension in [
+                    "csv",
+                    "json",
+                    "txt",
+                ], "`train_file` should be a csv, a json or a txt file."
+            if self.validation_file is not None:
+                extension = self.validation_file.split(".")[-1]
+                assert extension in [
+                    "csv",
+                    "json",
+                    "txt",
+                ], "`validation_file` should be a csv, a json or a txt file."
+
+
+def test_help_text_gets_used():
+    parser = ArgumentParser()
+    parser.add_arguments(ModelArguments, dest="model_args")
+    parser.add_arguments(DataTrainingArguments, dest="data_args")
+    with io.StringIO() as f:
+        parser.print_help(f)
+        f.seek(0)
+        help_text = f.read()
+    assert "The number of processes to use for the preprocessing." in help_text
+
+
+@dataclass
+class Config(TestSetup):
+    log_level: Optional[str] = field(
+        default="passive",
+        metadata={
+            "help": (
+                "Logger log level to use on the main node. Possible choices are the log levels as strings: 'debug',"
+                " 'info', 'warning', 'error' and 'critical', plus a 'passive' level which doesn't set anything and"
+                " lets the application set the level. Defaults to 'passive'."
+            ),
+            "choices": [
+                "debug",
+                "info",
+                "warning",
+                "error",
+                "critical",
+            ],
+        },
+    )
+
+
+def test_choices():
+    """Checks that the `choices` in the field metadata are used as the `choice` argument to `add_argument`"""
+
+    with raises_invalid_choice():
+        Config.setup("--log_level invalid")
+    for choice in ["debug", "info", "warning", "error", "critical"]:
+        assert Config.setup(f"--log_level {choice}").log_level == choice
+
+
+class ExplicitEnum(str, Enum):
+    """
+    Enum with more explicit error message for missing values.
+    """
+
+    @classmethod
+    def _missing_(cls, value):
+        raise ValueError(
+            f"{value} is not a valid {cls.__name__}, please select one of {list(cls._value2member_map_.keys())}"
+        )
+
+
+class IntervalStrategy(ExplicitEnum):
+    NO = "no"
+    STEPS = "steps"
+    EPOCH = "epoch"
+
+
+class EvaluationStrategy(ExplicitEnum):
+    NO = "no"
+    STEPS = "steps"
+    EPOCH = "epoch"
+
+
+class HubStrategy(ExplicitEnum):
+    END = "end"
+    EVERY_SAVE = "every_save"
+    CHECKPOINT = "checkpoint"
+    ALL_CHECKPOINTS = "all_checkpoints"
+
+
+class SchedulerType(ExplicitEnum):
+    LINEAR = "linear"
+    COSINE = "cosine"
+    COSINE_WITH_RESTARTS = "cosine_with_restarts"
+    POLYNOMIAL = "polynomial"
+    CONSTANT = "constant"
+    CONSTANT_WITH_WARMUP = "constant_with_warmup"
+
+
+trainer_log_levels = {
+    "debug": -1,
+    "info": -1,
+    "warning": -1,
+    "error": -1,
+    "critical": -1,
+    "passive": -1,
+}
+
+
+class OptimizerNames(ExplicitEnum):
+    """
+    Stores the acceptable string identifiers for optimizers.
+    """
+
+    ADAMW_HF = "adamw_hf"
+    ADAMW_TORCH = "adamw_torch"
+    ADAMW_TORCH_XLA = "adamw_torch_xla"
+    ADAMW_APEX_FUSED = "adamw_apex_fused"
+    ADAFACTOR = "adafactor"
+    ADAMW_BNB = "adamw_bnb_8bit"
+    SGD = "sgd"
+    ADAGRAD = "adagrad"
+
+
+@dataclass
+class ClassWithEnum(TestSetup):
+    evaluation_strategy: Union[IntervalStrategy, str] = field(
+        default="no",
+        metadata={"help": "The evaluation strategy to use."},
+    )
+
+
+def test_enums_are_parsed_to_enum_member():
+    # NOTE: Since it's a union of str and IntervalStrategy, it shouldn't raise an error if given
+    # an invalid value.
+    assert ClassWithEnum.setup("--evaluation_strategy invalid").evaluation_strategy == "invalid"
+
+    # However, it is, once we factor in what's happening in the __post_init__ of TrainingArguments.
+    with pytest.raises(ValueError):
+        TrainingArguments.setup("--evaluation_strategy invalid")
+
+    for mode, enum_value in zip(
+        ["no", "steps", "epoch"],
+        [IntervalStrategy.NO, IntervalStrategy.STEPS, IntervalStrategy.EPOCH],
+    ):
+        assert (
+            TrainingArguments.setup(f"--evaluation_strategy {mode}").evaluation_strategy
+            == enum_value
+        )
+
+
+@dataclass
+class TrainingArguments(TestSetup):
+    """
+    TrainingArguments is the subset of the arguments we use in our example scripts **which relate to the training loop
+    itself**.
+    Using [`HfArgumentParser`] we can turn this class into
+    [argparse](https://docs.python.org/3/library/argparse#module-argparse) arguments that can be specified on the
+    command line.
+    Parameters:
+        output_dir (`str`):
+            The output directory where the model predictions and checkpoints will be written.
+        overwrite_output_dir (`bool`, *optional*, defaults to `False`):
+            If `True`, overwrite the content of the output directory. Use this to continue training if `output_dir`
+            points to a checkpoint directory.
+        do_train (`bool`, *optional*, defaults to `False`):
+            Whether to run training or not. This argument is not directly used by [`Trainer`], it's intended to be used
+            by your training/evaluation scripts instead. See the [example
+            scripts](https://github.com/huggingface/transformers/tree/main/examples) for more details.
+        do_eval (`bool`, *optional*):
+            Whether to run evaluation on the validation set or not. Will be set to `True` if `evaluation_strategy` is
+            different from `"no"`. This argument is not directly used by [`Trainer`], it's intended to be used by your
+            training/evaluation scripts instead. See the [example
+            scripts](https://github.com/huggingface/transformers/tree/main/examples) for more details.
+        do_predict (`bool`, *optional*, defaults to `False`):
+            Whether to run predictions on the test set or not. This argument is not directly used by [`Trainer`], it's
+            intended to be used by your training/evaluation scripts instead. See the [example
+            scripts](https://github.com/huggingface/transformers/tree/main/examples) for more details.
+        evaluation_strategy (`str` or [`~trainer_utils.IntervalStrategy`], *optional*, defaults to `"no"`):
+            The evaluation strategy to adopt during training. Possible values are:
+                - `"no"`: No evaluation is done during training.
+                - `"steps"`: Evaluation is done (and logged) every `eval_steps`.
+                - `"epoch"`: Evaluation is done at the end of each epoch.
+        prediction_loss_only (`bool`, *optional*, defaults to `False`):
+            When performing evaluation and generating predictions, only returns the loss.
+        per_device_train_batch_size (`int`, *optional*, defaults to 8):
+            The batch size per GPU/TPU core/CPU for training.
+        per_device_eval_batch_size (`int`, *optional*, defaults to 8):
+            The batch size per GPU/TPU core/CPU for evaluation.
+        gradient_accumulation_steps (`int`, *optional*, defaults to 1):
+            Number of updates steps to accumulate the gradients for, before performing a backward/update pass.
+            <Tip warning={true}>
+            When using gradient accumulation, one step is counted as one step with backward pass. Therefore, logging,
+            evaluation, save will be conducted every `gradient_accumulation_steps * xxx_step` training examples.
+            </Tip>
+        eval_accumulation_steps (`int`, *optional*):
+            Number of predictions steps to accumulate the output tensors for, before moving the results to the CPU. If
+            left unset, the whole predictions are accumulated on GPU/TPU before being moved to the CPU (faster but
+            requires more memory).
+        eval_delay (`float`, *optional*):
+            Number of epochs or steps to wait for before the first evaluation can be performed, depending on the
+            evaluation_strategy.
+        learning_rate (`float`, *optional*, defaults to 5e-5):
+            The initial learning rate for [`AdamW`] optimizer.
+        weight_decay (`float`, *optional*, defaults to 0):
+            The weight decay to apply (if not zero) to all layers except all bias and LayerNorm weights in [`AdamW`]
+            optimizer.
+        adam_beta1 (`float`, *optional*, defaults to 0.9):
+            The beta1 hyperparameter for the [`AdamW`] optimizer.
+        adam_beta2 (`float`, *optional*, defaults to 0.999):
+            The beta2 hyperparameter for the [`AdamW`] optimizer.
+        adam_epsilon (`float`, *optional*, defaults to 1e-8):
+            The epsilon hyperparameter for the [`AdamW`] optimizer.
+        max_grad_norm (`float`, *optional*, defaults to 1.0):
+            Maximum gradient norm (for gradient clipping).
+        num_train_epochs(`float`, *optional*, defaults to 3.0):
+            Total number of training epochs to perform (if not an integer, will perform the decimal part percents of
+            the last epoch before stopping training).
+        max_steps (`int`, *optional*, defaults to -1):
+            If set to a positive number, the total number of training steps to perform. Overrides `num_train_epochs`.
+            In case of using a finite iterable dataset the training may stop before reaching the set number of steps
+            when all data is exhausted
+        lr_scheduler_type (`str` or [`SchedulerType`], *optional*, defaults to `"linear"`):
+            The scheduler type to use. See the documentation of [`SchedulerType`] for all possible values.
+        warmup_ratio (`float`, *optional*, defaults to 0.0):
+            Ratio of total training steps used for a linear warmup from 0 to `learning_rate`.
+        warmup_steps (`int`, *optional*, defaults to 0):
+            Number of steps used for a linear warmup from 0 to `learning_rate`. Overrides any effect of `warmup_ratio`.
+        log_level (`str`, *optional*, defaults to `passive`):
+            Logger log level to use on the main process. Possible choices are the log levels as strings: 'debug',
+            'info', 'warning', 'error' and 'critical', plus a 'passive' level which doesn't set anything and lets the
+            application set the level.
+        log_level_replica (`str`, *optional*, defaults to `passive`):
+            Logger log level to use on replicas. Same choices as `log_level`"
+        log_on_each_node (`bool`, *optional*, defaults to `True`):
+            In multinode distributed training, whether to log using `log_level` once per node, or only on the main
+            node.
+        logging_dir (`str`, *optional*):
+            [TensorBoard](https://www.tensorflow.org/tensorboard) log directory. Will default to
+            *output_dir/runs/**CURRENT_DATETIME_HOSTNAME***.
+        logging_strategy (`str` or [`~trainer_utils.IntervalStrategy`], *optional*, defaults to `"steps"`):
+            The logging strategy to adopt during training. Possible values are:
+                - `"no"`: No logging is done during training.
+                - `"epoch"`: Logging is done at the end of each epoch.
+                - `"steps"`: Logging is done every `logging_steps`.
+        logging_first_step (`bool`, *optional*, defaults to `False`):
+            Whether to log and evaluate the first `global_step` or not.
+        logging_steps (`int`, *optional*, defaults to 500):
+            Number of update steps between two logs if `logging_strategy="steps"`.
+        logging_nan_inf_filter (`bool`, *optional*, defaults to `True`):
+            Whether to filter `nan` and `inf` losses for logging. If set to `True` the loss of every step that is `nan`
+            or `inf` is filtered and the average loss of the current logging window is taken instead.
+            <Tip>
+            `logging_nan_inf_filter` only influences the logging of loss values, it does not change the behavior the
+            gradient is computed or applied to the model.
+            </Tip>
+        save_strategy (`str` or [`~trainer_utils.IntervalStrategy`], *optional*, defaults to `"steps"`):
+            The checkpoint save strategy to adopt during training. Possible values are:
+                - `"no"`: No save is done during training.
+                - `"epoch"`: Save is done at the end of each epoch.
+                - `"steps"`: Save is done every `save_steps`.
+        save_steps (`int`, *optional*, defaults to 500):
+            Number of updates steps before two checkpoint saves if `save_strategy="steps"`.
+        save_total_limit (`int`, *optional*):
+            If a value is passed, will limit the total amount of checkpoints. Deletes the older checkpoints in
+            `output_dir`.
+        save_on_each_node (`bool`, *optional*, defaults to `False`):
+            When doing multi-node distributed training, whether to save models and checkpoints on each node, or only on
+            the main one.
+            This should not be activated when the different nodes use the same storage as the files will be saved with
+            the same names for each node.
+        no_cuda (`bool`, *optional*, defaults to `False`):
+            Whether to not use CUDA even when it is available or not.
+        seed (`int`, *optional*, defaults to 42):
+            Random seed that will be set at the beginning of training. To ensure reproducibility across runs, use the
+            [`~Trainer.model_init`] function to instantiate the model if it has some randomly initialized parameters.
+        data_seed (`int`, *optional*):
+            Random seed to be used with data samplers. If not set, random generators for data sampling will use the
+            same seed as `seed`. This can be used to ensure reproducibility of data sampling, independent of the model
+            seed.
+        jit_mode_eval (`bool`, *optional*, defaults to `False`):
+            Whether or not to use PyTorch jit trace for inference.
+        use_ipex (`bool`, *optional*, defaults to `False`):
+            Use Intel extension for PyTorch when it is available. [IPEX
+            installation](https://github.com/intel/intel-extension-for-pytorch).
+        bf16 (`bool`, *optional*, defaults to `False`):
+            Whether to use bf16 16-bit (mixed) precision training instead of 32-bit training. Requires Ampere or higher
+            NVIDIA architecture or using CPU (no_cuda). This is an experimental API and it may change.
+        fp16 (`bool`, *optional*, defaults to `False`):
+            Whether to use fp16 16-bit (mixed) precision training instead of 32-bit training.
+        fp16_opt_level (`str`, *optional*, defaults to 'O1'):
+            For `fp16` training, Apex AMP optimization level selected in ['O0', 'O1', 'O2', and 'O3']. See details on
+            the [Apex documentation](https://nvidia.github.io/apex/amp).
+        fp16_backend (`str`, *optional*, defaults to `"auto"`):
+            This argument is deprecated. Use `half_precision_backend` instead.
+        half_precision_backend (`str`, *optional*, defaults to `"auto"`):
+            The backend to use for mixed precision training. Must be one of `"auto", "cuda_amp", "apex", "cpu_amp"`.
+            `"auto"` will use CPU/CUDA AMP or APEX depending on the PyTorch version detected, while the other choices
+            will force the requested backend.
+        bf16_full_eval (`bool`, *optional*, defaults to `False`):
+            Whether to use full bfloat16 evaluation instead of 32-bit. This will be faster and save memory but can harm
+            metric values. This is an experimental API and it may change.
+        fp16_full_eval (`bool`, *optional*, defaults to `False`):
+            Whether to use full float16 evaluation instead of 32-bit. This will be faster and save memory but can harm
+            metric values.
+        tf32 (`bool`, *optional*):
+            Whether to enable the TF32 mode, available in Ampere and newer GPU architectures. The default value depends
+            on PyTorch's version default of `torch.backends.cuda.matmul.allow_tf32`. For more details please refer to
+            the [TF32](https://huggingface.co/docs/transformers/performance#tf32) documentation. This is an
+            experimental API and it may change.
+        local_rank (`int`, *optional*, defaults to -1):
+            Rank of the process during distributed training.
+        xpu_backend (`str`, *optional*):
+            The backend to use for xpu distributed training. Must be one of `"mpi"` or `"ccl"` or `"gloo"`.
+        tpu_num_cores (`int`, *optional*):
+            When training on TPU, the number of TPU cores (automatically passed by launcher script).
+        dataloader_drop_last (`bool`, *optional*, defaults to `False`):
+            Whether to drop the last incomplete batch (if the length of the dataset is not divisible by the batch size)
+            or not.
+        eval_steps (`int`, *optional*):
+            Number of update steps between two evaluations if `evaluation_strategy="steps"`. Will default to the same
+            value as `logging_steps` if not set.
+        dataloader_num_workers (`int`, *optional*, defaults to 0):
+            Number of subprocesses to use for data loading (PyTorch only). 0 means that the data will be loaded in the
+            main process.
+        past_index (`int`, *optional*, defaults to -1):
+            Some models like [TransformerXL](../model_doc/transformerxl) or [XLNet](../model_doc/xlnet) can make use of
+            the past hidden states for their predictions. If this argument is set to a positive int, the `Trainer` will
+            use the corresponding output (usually index 2) as the past state and feed it to the model at the next
+            training step under the keyword argument `mems`.
+        run_name (`str`, *optional*):
+            A descriptor for the run. Typically used for [wandb](https://www.wandb.com/) and
+            [mlflow](https://www.mlflow.org/) logging.
+        disable_tqdm (`bool`, *optional*):
+            Whether or not to disable the tqdm progress bars and table of metrics produced by
+            [`~notebook.NotebookTrainingTracker`] in Jupyter Notebooks. Will default to `True` if the logging level is
+            set to warn or lower (default), `False` otherwise.
+        remove_unused_columns (`bool`, *optional*, defaults to `True`):
+            Whether or not to automatically remove the columns unused by the model forward method.
+            (Note that this behavior is not implemented for [`TFTrainer`] yet.)
+        label_names (`List[str]`, *optional*):
+            The list of keys in your dictionary of inputs that correspond to the labels.
+            Will eventually default to `["labels"]` except if the model used is one of the `XxxForQuestionAnswering` in
+            which case it will default to `["start_positions", "end_positions"]`.
+        load_best_model_at_end (`bool`, *optional*, defaults to `False`):
+            Whether or not to load the best model found during training at the end of training.
+            <Tip>
+            When set to `True`, the parameters `save_strategy` needs to be the same as `evaluation_strategy`, and in
+            the case it is "steps", `save_steps` must be a round multiple of `eval_steps`.
+            </Tip>
+        metric_for_best_model (`str`, *optional*):
+            Use in conjunction with `load_best_model_at_end` to specify the metric to use to compare two different
+            models. Must be the name of a metric returned by the evaluation with or without the prefix `"eval_"`. Will
+            default to `"loss"` if unspecified and `load_best_model_at_end=True` (to use the evaluation loss).
+            If you set this value, `greater_is_better` will default to `True`. Don't forget to set it to `False` if
+            your metric is better when lower.
+        greater_is_better (`bool`, *optional*):
+            Use in conjunction with `load_best_model_at_end` and `metric_for_best_model` to specify if better models
+            should have a greater metric or not. Will default to:
+            - `True` if `metric_for_best_model` is set to a value that isn't `"loss"` or `"eval_loss"`.
+            - `False` if `metric_for_best_model` is not set, or set to `"loss"` or `"eval_loss"`.
+        ignore_data_skip (`bool`, *optional*, defaults to `False`):
+            When resuming training, whether or not to skip the epochs and batches to get the data loading at the same
+            stage as in the previous training. If set to `True`, the training will begin faster (as that skipping step
+            can take a long time) but will not yield the same results as the interrupted training would have.
+        sharded_ddp (`bool`, `str` or list of [`~trainer_utils.ShardedDDPOption`], *optional*, defaults to `False`):
+            Use Sharded DDP training from [FairScale](https://github.com/facebookresearch/fairscale) (in distributed
+            training only). This is an experimental feature.
+            A list of options along the following:
+            - `"simple"`: to use first instance of sharded DDP released by fairscale (`ShardedDDP`) similar to ZeRO-2.
+            - `"zero_dp_2"`: to use the second instance of sharded DPP released by fairscale (`FullyShardedDDP`) in
+              Zero-2 mode (with `reshard_after_forward=False`).
+            - `"zero_dp_3"`: to use the second instance of sharded DPP released by fairscale (`FullyShardedDDP`) in
+              Zero-3 mode (with `reshard_after_forward=True`).
+            - `"offload"`: to add ZeRO-offload (only compatible with `"zero_dp_2"` and `"zero_dp_3"`).
+            If a string is passed, it will be split on space. If a bool is passed, it will be converted to an empty
+            list for `False` and `["simple"]` for `True`.
+        fsdp (`bool`, `str` or list of [`~trainer_utils.FSDPOption`], *optional*, defaults to `False`):
+            Use PyTorch Distributed Parallel Training (in distributed training only).
+            A list of options along the following:
+            - `"full_shard"`: Shard parameters, gradients and optimizer states.
+            - `"shard_grad_op"`: Shard optimizer states and gradients.
+            - `"offload"`: Offload parameters and gradients to CPUs (only compatible with `"full_shard"` and
+              `"shard_grad_op"`).
+            - `"auto_wrap"`: Automatically recursively wrap layers with FSDP using `default_auto_wrap_policy`.
+        fsdp_min_num_params (`int`, *optional*, defaults to `0`):
+            FSDP's minimum number of parameters for Default Auto Wrapping. (useful only when `fsdp` field is passed).
+        deepspeed (`str` or `dict`, *optional*):
+            Use [Deepspeed](https://github.com/microsoft/deepspeed). This is an experimental feature and its API may
+            evolve in the future. The value is either the location of DeepSpeed json config file (e.g.,
+            `ds_config.json`) or an already loaded json file as a `dict`"
+        label_smoothing_factor (`float`, *optional*, defaults to 0.0):
+            The label smoothing factor to use. Zero means no label smoothing, otherwise the underlying onehot-encoded
+            labels are changed from 0s and 1s to `label_smoothing_factor/num_labels` and `1 - label_smoothing_factor +
+            label_smoothing_factor/num_labels` respectively.
+        debug (`str` or list of [`~debug_utils.DebugOption`], *optional*, defaults to `""`):
+            Enable one or more debug features. This is an experimental feature.
+            Possible options are:
+            - `"underflow_overflow"`: detects overflow in model's input/outputs and reports the last frames that led to
+              the event
+            - `"tpu_metrics_debug"`: print debug metrics on TPU
+            The options should be separated by whitespaces.
+        optim (`str` or [`training_args.OptimizerNames`], *optional*, defaults to `"adamw_hf"`):
+            The optimizer to use: adamw_hf, adamw_torch, adamw_apex_fused, or adafactor.
+        adafactor (`bool`, *optional*, defaults to `False`):
+            This argument is deprecated. Use `--optim adafactor` instead.
+        group_by_length (`bool`, *optional*, defaults to `False`):
+            Whether or not to group together samples of roughly the same length in the training dataset (to minimize
+            padding applied and be more efficient). Only useful if applying dynamic padding.
+        length_column_name (`str`, *optional*, defaults to `"length"`):
+            Column name for precomputed lengths. If the column exists, grouping by length will use these values rather
+            than computing them on train startup. Ignored unless `group_by_length` is `True` and the dataset is an
+            instance of `Dataset`.
+        report_to (`str` or `List[str]`, *optional*, defaults to `"all"`):
+            The list of integrations to report the results and logs to. Supported platforms are `"azure_ml"`,
+            `"comet_ml"`, `"mlflow"`, `"neptune"`, `"tensorboard"` and `"wandb"`. Use `"all"` to report to all
+            integrations installed, `"none"` for no integrations.
+        ddp_find_unused_parameters (`bool`, *optional*):
+            When using distributed training, the value of the flag `find_unused_parameters` passed to
+            `DistributedDataParallel`. Will default to `False` if gradient checkpointing is used, `True` otherwise.
+        ddp_bucket_cap_mb (`int`, *optional*):
+            When using distributed training, the value of the flag `bucket_cap_mb` passed to `DistributedDataParallel`.
+        dataloader_pin_memory (`bool`, *optional*, defaults to `True`):
+            Whether you want to pin memory in data loaders or not. Will default to `True`.
+        skip_memory_metrics (`bool`, *optional*, defaults to `True`):
+            Whether to skip adding of memory profiler reports to metrics. This is skipped by default because it slows
+            down the training and evaluation speed.
+        push_to_hub (`bool`, *optional*, defaults to `False`):
+            Whether or not to push the model to the Hub every time the model is saved. If this is activated,
+            `output_dir` will begin a git directory synced with the repo (determined by `hub_model_id`) and the content
+            will be pushed each time a save is triggered (depending on your `save_strategy`). Calling
+            [`~Trainer.save_model`] will also trigger a push.
+            <Tip warning={true}>
+            If `output_dir` exists, it needs to be a local clone of the repository to which the [`Trainer`] will be
+            pushed.
+            </Tip>
+        resume_from_checkpoint (`str`, *optional*):
+            The path to a folder with a valid checkpoint for your model. This argument is not directly used by
+            [`Trainer`], it's intended to be used by your training/evaluation scripts instead. See the [example
+            scripts](https://github.com/huggingface/transformers/tree/main/examples) for more details.
+        hub_model_id (`str`, *optional*):
+            The name of the repository to keep in sync with the local *output_dir*. It can be a simple model ID in
+            which case the model will be pushed in your namespace. Otherwise it should be the whole repository name,
+            for instance `"user_name/model"`, which allows you to push to an organization you are a member of with
+            `"organization_name/model"`. Will default to `user_name/output_dir_name` with *output_dir_name* being the
+            name of `output_dir`.
+            Will default to the name of `output_dir`.
+        hub_strategy (`str` or [`~trainer_utils.HubStrategy`], *optional*, defaults to `"every_save"`):
+            Defines the scope of what is pushed to the Hub and when. Possible values are:
+            - `"end"`: push the model, its configuration, the tokenizer (if passed along to the [`Trainer`]) and a
+              draft of a model card when the [`~Trainer.save_model`] method is called.
+            - `"every_save"`: push the model, its configuration, the tokenizer (if passed along to the [`Trainer`]) and
+              a draft of a model card each time there is a model save. The pushes are asynchronous to not block
+              training, and in case the save are very frequent, a new push is only attempted if the previous one is
+              finished. A last push is made with the final model at the end of training.
+            - `"checkpoint"`: like `"every_save"` but the latest checkpoint is also pushed in a subfolder named
+              last-checkpoint, allowing you to resume training easily with
+              `trainer.train(resume_from_checkpoint="last-checkpoint")`.
+            - `"all_checkpoints"`: like `"checkpoint"` but all checkpoints are pushed like they appear in the output
+              folder (so you will get one checkpoint folder per folder in your final repository)
+        hub_token (`str`, *optional*):
+            The token to use to push the model to the Hub. Will default to the token in the cache folder obtained with
+            `huggingface-cli login`.
+        hub_private_repo (`bool`, *optional*, defaults to `False`):
+            If True, the Hub repo will be set to private.
+        gradient_checkpointing (`bool`, *optional*, defaults to `False`):
+            If True, use gradient checkpointing to save memory at the expense of slower backward pass.
+        include_inputs_for_metrics (`bool`, *optional*, defaults to `False`):
+            Whether or not the inputs will be passed to the `compute_metrics` function. This is intended for metrics
+            that need inputs, predictions and references for scoring calculation in Metric class.
+        auto_find_batch_size (`bool`, *optional*, defaults to `False`)
+            Whether to find a batch size that will fit into memory automatically through exponential decay, avoiding
+            CUDA Out-of-Memory errors. Requires accelerate to be installed (`pip install accelerate`)
+        full_determinism (`bool`, *optional*, defaults to `False`)
+            If `True`, [`enable_full_determinism`] is called instead of [`set_seed`] to ensure reproducible results in
+            distributed training
+        torchdynamo (`str`, *optional*):
+            The token that is used to set the backend compiler for TorchDynamo. Possible choices are ["eager",
+            "nvfuser]. This is an experimental API and subject to change.
+        ray_scope (`str`, *optional*, defaults to `"last"`):
+            The scope to use when doing hyperparameter search with Ray. By default, `"last"` will be used. Ray will
+            then use the last checkpoint of all trials, compare those, and select the best one. However, other options
+            are also available. See the [Ray documentation](
+            https://docs.ray.io/en/latest/tune/api_docs/analysis.html#ray.tune.ExperimentAnalysis.get_best_trial) for
+            more options.
+        ddp_timeout (`int`, *optional*, defaults to 1800):
+            The timeout for `torch.distributed.init_process_group` calls, used to avoid GPU socket timeouts when
+            performing slow operations in distributed runnings. Please refer the [PyTorch documentation]
+            (https://pytorch.org/docs/stable/distributed.html#torch.distributed.init_process_group) for more
+            information.
+        use_mps_device (`bool`, *optional*, defaults to `False`):
+            Whether to use Apple Silicon chip based `mps` device.
+    """
+
+    framework = "pt"
+    output_dir: str = field(
+        default="tmp_trainer",
+        metadata={
+            "help": "The output directory where the model predictions and checkpoints will be written."
+        },
+    )
+    overwrite_output_dir: bool = field(
+        default=False,
+        metadata={
+            "help": (
+                "Overwrite the content of the output directory. "
+                "Use this to continue training if output_dir points to a checkpoint directory."
+            )
+        },
+    )
+
+    do_train: bool = field(default=False, metadata={"help": "Whether to run training."})
+    do_eval: bool = field(default=False, metadata={"help": "Whether to run eval on the dev set."})
+    do_predict: bool = field(
+        default=False, metadata={"help": "Whether to run predictions on the test set."}
+    )
+    evaluation_strategy: Union[IntervalStrategy, str] = field(
+        default="no",
+        metadata={"help": "The evaluation strategy to use."},
+    )
+    prediction_loss_only: bool = field(
+        default=False,
+        metadata={"help": "When performing evaluation and predictions, only returns the loss."},
+    )
+
+    per_device_train_batch_size: int = field(
+        default=8, metadata={"help": "Batch size per GPU/TPU core/CPU for training."}
+    )
+    per_device_eval_batch_size: int = field(
+        default=8, metadata={"help": "Batch size per GPU/TPU core/CPU for evaluation."}
+    )
+
+    per_gpu_train_batch_size: Optional[int] = field(
+        default=None,
+        metadata={
+            "help": (
+                "Deprecated, the use of `--per_device_train_batch_size` is preferred. "
+                "Batch size per GPU/TPU core/CPU for training."
+            )
+        },
+    )
+    per_gpu_eval_batch_size: Optional[int] = field(
+        default=None,
+        metadata={
+            "help": (
+                "Deprecated, the use of `--per_device_eval_batch_size` is preferred. "
+                "Batch size per GPU/TPU core/CPU for evaluation."
+            )
+        },
+    )
+
+    gradient_accumulation_steps: int = field(
+        default=1,
+        metadata={
+            "help": "Number of updates steps to accumulate before performing a backward/update pass."
+        },
+    )
+    eval_accumulation_steps: Optional[int] = field(
+        default=None,
+        metadata={
+            "help": "Number of predictions steps to accumulate before moving the tensors to the CPU."
+        },
+    )
+
+    eval_delay: Optional[float] = field(
+        default=0,
+        metadata={
+            "help": (
+                "Number of epochs or steps to wait for before the first evaluation can be performed, depending on the"
+                " evaluation_strategy."
+            )
+        },
+    )
+
+    learning_rate: float = field(
+        default=5e-5, metadata={"help": "The initial learning rate for AdamW."}
+    )
+    weight_decay: float = field(
+        default=0.0, metadata={"help": "Weight decay for AdamW if we apply some."}
+    )
+    adam_beta1: float = field(default=0.9, metadata={"help": "Beta1 for AdamW optimizer"})
+    adam_beta2: float = field(default=0.999, metadata={"help": "Beta2 for AdamW optimizer"})
+    adam_epsilon: float = field(default=1e-8, metadata={"help": "Epsilon for AdamW optimizer."})
+    max_grad_norm: float = field(default=1.0, metadata={"help": "Max gradient norm."})
+
+    num_train_epochs: float = field(
+        default=3.0, metadata={"help": "Total number of training epochs to perform."}
+    )
+    max_steps: int = field(
+        default=-1,
+        metadata={
+            "help": "If > 0: set total number of training steps to perform. Override num_train_epochs."
+        },
+    )
+    lr_scheduler_type: Union[SchedulerType, str] = field(
+        default="linear",
+        metadata={"help": "The scheduler type to use."},
+    )
+    warmup_ratio: float = field(
+        default=0.0, metadata={"help": "Linear warmup over warmup_ratio fraction of total steps."}
+    )
+    warmup_steps: int = field(default=0, metadata={"help": "Linear warmup over warmup_steps."})
+
+    log_level: Optional[str] = field(
+        default="passive",
+        metadata={
+            "help": (
+                "Logger log level to use on the main node. Possible choices are the log levels as strings: 'debug',"
+                " 'info', 'warning', 'error' and 'critical', plus a 'passive' level which doesn't set anything and"
+                " lets the application set the level. Defaults to 'passive'."
+            ),
+            "choices": trainer_log_levels.keys(),
+        },
+    )
+    log_level_replica: Optional[str] = field(
+        default="passive",
+        metadata={
+            "help": "Logger log level to use on replica nodes. Same choices and defaults as ``log_level``",
+            "choices": trainer_log_levels.keys(),
+        },
+    )
+    log_on_each_node: bool = field(
+        default=True,
+        metadata={
+            "help": (
+                "When doing a multinode distributed training, whether to log once per node or just once on the main"
+                " node."
+            )
+        },
+    )
+    logging_dir: Optional[str] = field(default=None, metadata={"help": "Tensorboard log dir."})
+    logging_strategy: Union[IntervalStrategy, str] = field(
+        default="steps",
+        metadata={"help": "The logging strategy to use."},
+    )
+    logging_first_step: bool = field(default=False, metadata={"help": "Log the first global_step"})
+    logging_steps: int = field(default=500, metadata={"help": "Log every X updates steps."})
+    logging_nan_inf_filter: bool = field(
+        default=True, metadata={"help": "Filter nan and inf losses for logging."}
+    )
+    save_strategy: Union[IntervalStrategy, str] = field(
+        default="steps",
+        metadata={"help": "The checkpoint save strategy to use."},
+    )
+    save_steps: int = field(
+        default=500, metadata={"help": "Save checkpoint every X updates steps."}
+    )
+    save_total_limit: Optional[int] = field(
+        default=None,
+        metadata={
+            "help": (
+                "Limit the total amount of checkpoints. "
+                "Deletes the older checkpoints in the output_dir. Default is unlimited checkpoints"
+            )
+        },
+    )
+    save_on_each_node: bool = field(
+        default=False,
+        metadata={
+            "help": (
+                "When doing multi-node distributed training, whether to save models and checkpoints on each node, or"
+                " only on the main one"
+            )
+        },
+    )
+    no_cuda: bool = field(
+        default=False, metadata={"help": "Do not use CUDA even when it is available"}
+    )
+    use_mps_device: bool = field(
+        default=False, metadata={"help": "Whether to use Apple Silicon chip based `mps` device."}
+    )
+    seed: int = field(
+        default=42, metadata={"help": "Random seed that will be set at the beginning of training."}
+    )
+    data_seed: Optional[int] = field(
+        default=None, metadata={"help": "Random seed to be used with data samplers."}
+    )
+    jit_mode_eval: bool = field(
+        default=False, metadata={"help": "Whether or not to use PyTorch jit trace for inference"}
+    )
+    use_ipex: bool = field(
+        default=False,
+        metadata={
+            "help": (
+                "Use Intel extension for PyTorch when it is available, installation:"
+                " 'https://github.com/intel/intel-extension-for-pytorch'"
+            )
+        },
+    )
+    bf16: bool = field(
+        default=False,
+        metadata={
+            "help": (
+                "Whether to use bf16 (mixed) precision instead of 32-bit. Requires Ampere or higher NVIDIA"
+                " architecture or using CPU (no_cuda). This is an experimental API and it may change."
+            )
+        },
+    )
+    fp16: bool = field(
+        default=False,
+        metadata={"help": "Whether to use fp16 (mixed) precision instead of 32-bit"},
+    )
+    fp16_opt_level: str = field(
+        default="O1",
+        metadata={
+            "help": (
+                "For fp16: Apex AMP optimization level selected in ['O0', 'O1', 'O2', and 'O3']. "
+                "See details at https://nvidia.github.io/apex/amp.html"
+            )
+        },
+    )
+    half_precision_backend: str = field(
+        default="auto",
+        metadata={
+            "help": "The backend to be used for half precision.",
+            "choices": ["auto", "cuda_amp", "apex", "cpu_amp"],
+        },
+    )
+    bf16_full_eval: bool = field(
+        default=False,
+        metadata={
+            "help": (
+                "Whether to use full bfloat16 evaluation instead of 32-bit. This is an experimental API and it may"
+                " change."
+            )
+        },
+    )
+    fp16_full_eval: bool = field(
+        default=False,
+        metadata={"help": "Whether to use full float16 evaluation instead of 32-bit"},
+    )
+    tf32: Optional[bool] = field(
+        default=None,
+        metadata={
+            "help": (
+                "Whether to enable tf32 mode, available in Ampere and newer GPU architectures. This is an experimental"
+                " API and it may change."
+            )
+        },
+    )
+    local_rank: int = field(default=-1, metadata={"help": "For distributed training: local_rank"})
+    xpu_backend: Optional[str] = field(
+        default=None,
+        metadata={
+            "help": "The backend to be used for distributed training on Intel XPU.",
+            "choices": ["mpi", "ccl", "gloo"],
+        },
+    )
+    tpu_num_cores: Optional[int] = field(
+        default=None,
+        metadata={"help": "TPU: Number of TPU cores (automatically passed by launcher script)"},
+    )
+    tpu_metrics_debug: bool = field(
+        default=False,
+        metadata={
+            "help": (
+                "Deprecated, the use of `--debug tpu_metrics_debug` is preferred. TPU: Whether to print debug metrics"
+            )
+        },
+    )
+    debug: str = field(
+        default="",
+        metadata={
+            "help": (
+                "Whether or not to enable debug mode. Current options: "
+                "`underflow_overflow` (Detect underflow and overflow in activations and weights), "
+                "`tpu_metrics_debug` (print debug metrics on TPU)."
+            )
+        },
+    )
+
+    dataloader_drop_last: bool = field(
+        default=False,
+        metadata={
+            "help": "Drop the last incomplete batch if it is not divisible by the batch size."
+        },
+    )
+    eval_steps: Optional[int] = field(
+        default=None, metadata={"help": "Run an evaluation every X steps."}
+    )
+    dataloader_num_workers: int = field(
+        default=0,
+        metadata={
+            "help": (
+                "Number of subprocesses to use for data loading (PyTorch only). 0 means that the data will be loaded"
+                " in the main process."
+            )
+        },
+    )
+
+    past_index: int = field(
+        default=-1,
+        metadata={
+            "help": "If >=0, uses the corresponding part of the output as the past state for next step."
+        },
+    )
+
+    run_name: Optional[str] = field(
+        default=None,
+        metadata={"help": "An optional descriptor for the run. Notably used for wandb logging."},
+    )
+    disable_tqdm: Optional[bool] = field(
+        default=None, metadata={"help": "Whether or not to disable the tqdm progress bars."}
+    )
+
+    remove_unused_columns: Optional[bool] = field(
+        default=True,
+        metadata={"help": "Remove columns not required by the model when using an nlp.Dataset."},
+    )
+    label_names: Optional[List[str]] = field(
+        default=None,
+        metadata={
+            "help": "The list of keys in your dictionary of inputs that correspond to the labels."
+        },
+    )
+
+    load_best_model_at_end: Optional[bool] = field(
+        default=False,
+        metadata={
+            "help": "Whether or not to load the best model found during training at the end of training."
+        },
+    )
+    metric_for_best_model: Optional[str] = field(
+        default=None, metadata={"help": "The metric to use to compare two different models."}
+    )
+    greater_is_better: Optional[bool] = field(
+        default=None,
+        metadata={"help": "Whether the `metric_for_best_model` should be maximized or not."},
+    )
+    ignore_data_skip: bool = field(
+        default=False,
+        metadata={
+            "help": (
+                "When resuming training, whether or not to skip the first epochs and batches to get to the same"
+                " training data."
+            )
+        },
+    )
+    sharded_ddp: str = field(
+        default="",
+        metadata={
+            "help": (
+                "Whether or not to use sharded DDP training (in distributed training only). The base option should be"
+                " `simple`, `zero_dp_2` or `zero_dp_3` and you can add CPU-offload to `zero_dp_2` or `zero_dp_3` like"
+                " this: zero_dp_2 offload` or `zero_dp_3 offload`. You can add auto-wrap to `zero_dp_2` or `zero_dp_3`"
+                " with the same syntax: zero_dp_2 auto_wrap` or `zero_dp_3 auto_wrap`."
+            ),
+        },
+    )
+    fsdp: str = field(
+        default="",
+        metadata={
+            "help": (
+                "Whether or not to use PyTorch Fully Sharded Data Parallel (FSDP) training (in distributed training"
+                " only). The base option should be `full_shard`, `shard_grad_op` or `no_shard` and you can add"
+                " CPU-offload to `full_shard` or `shard_grad_op` like this: full_shard offload` or `shard_grad_op"
+                " offload`. You can add auto-wrap to `full_shard` or `shard_grad_op` with the same syntax: full_shard"
+                " auto_wrap` or `shard_grad_op auto_wrap`."
+            ),
+        },
+    )
+    fsdp_min_num_params: int = field(
+        default=0,
+        metadata={
+            "help": (
+                "FSDP's minimum number of parameters for Default Auto Wrapping. (useful only when `fsdp` field is"
+                " passed)."
+            )
+        },
+    )
+    fsdp_transformer_layer_cls_to_wrap: Optional[str] = field(
+        default=None,
+        metadata={
+            "help": (
+                "Transformer layer class name (case-sensitive) to wrap ,e.g, `BertLayer`, `GPTJBlock`, `T5Block` .... "
+                "(useful only when `fsdp` flag is passed)."
+            )
+        },
+    )
+    deepspeed: Optional[str] = field(
+        default=None,
+        metadata={
+            "help": (
+                "Enable deepspeed and pass the path to deepspeed json config file (e.g. ds_config.json) or an already"
+                " loaded json file as a dict"
+            )
+        },
+    )
+    label_smoothing_factor: float = field(
+        default=0.0,
+        metadata={"help": "The label smoothing epsilon to apply (zero means no label smoothing)."},
+    )
+    optim: Union[OptimizerNames, str] = field(
+        default="adamw_hf",
+        metadata={"help": "The optimizer to use."},
+    )
+    adafactor: bool = field(
+        default=False, metadata={"help": "Whether or not to replace AdamW by Adafactor."}
+    )
+    group_by_length: bool = field(
+        default=False,
+        metadata={
+            "help": "Whether or not to group samples of roughly the same length together when batching."
+        },
+    )
+    length_column_name: Optional[str] = field(
+        default="length",
+        metadata={"help": "Column name with precomputed lengths to use when grouping by length."},
+    )
+    report_to: Optional[List[str]] = field(
+        default=None,
+        metadata={"help": "The list of integrations to report the results and logs to."},
+    )
+    ddp_find_unused_parameters: Optional[bool] = field(
+        default=None,
+        metadata={
+            "help": (
+                "When using distributed training, the value of the flag `find_unused_parameters` passed to "
+                "`DistributedDataParallel`."
+            )
+        },
+    )
+    ddp_bucket_cap_mb: Optional[int] = field(
+        default=None,
+        metadata={
+            "help": (
+                "When using distributed training, the value of the flag `bucket_cap_mb` passed to "
+                "`DistributedDataParallel`."
+            )
+        },
+    )
+    dataloader_pin_memory: bool = field(
+        default=True, metadata={"help": "Whether or not to pin memory for DataLoader."}
+    )
+    skip_memory_metrics: bool = field(
+        default=True,
+        metadata={"help": "Whether or not to skip adding of memory profiler reports to metrics."},
+    )
+    use_legacy_prediction_loop: bool = field(
+        default=False,
+        metadata={"help": "Whether or not to use the legacy prediction_loop in the Trainer."},
+    )
+    push_to_hub: bool = field(
+        default=False,
+        metadata={
+            "help": "Whether or not to upload the trained model to the model hub after training."
+        },
+    )
+    resume_from_checkpoint: Optional[str] = field(
+        default=None,
+        metadata={"help": "The path to a folder with a valid checkpoint for your model."},
+    )
+    hub_model_id: Optional[str] = field(
+        default=None,
+        metadata={
+            "help": "The name of the repository to keep in sync with the local `output_dir`."
+        },
+    )
+    hub_strategy: Union[HubStrategy, str] = field(
+        default="every_save",
+        metadata={"help": "The hub strategy to use when `--push_to_hub` is activated."},
+    )
+    hub_token: Optional[str] = field(
+        default=None, metadata={"help": "The token to use to push to the Model Hub."}
+    )
+    hub_private_repo: bool = field(
+        default=False, metadata={"help": "Whether the model repository is private or not."}
+    )
+    gradient_checkpointing: bool = field(
+        default=False,
+        metadata={
+            "help": "If True, use gradient checkpointing to save memory at the expense of slower backward pass."
+        },
+    )
+    include_inputs_for_metrics: bool = field(
+        default=False,
+        metadata={
+            "help": "Whether or not the inputs will be passed to the `compute_metrics` function."
+        },
+    )
+    # Deprecated arguments
+    fp16_backend: str = field(
+        default="auto",
+        metadata={
+            "help": "Deprecated. Use half_precision_backend instead",
+            "choices": ["auto", "cuda_amp", "apex", "cpu_amp"],
+        },
+    )
+    push_to_hub_model_id: Optional[str] = field(
+        default=None, metadata={"help": "The name of the repository to which push the `Trainer`."}
+    )
+    push_to_hub_organization: Optional[str] = field(
+        default=None,
+        metadata={"help": "The name of the organization in with to which push the `Trainer`."},
+    )
+    push_to_hub_token: Optional[str] = field(
+        default=None, metadata={"help": "The token to use to push to the Model Hub."}
+    )
+    _n_gpu: int = field(init=False, repr=False, default=-1)
+    mp_parameters: str = field(
+        default="",
+        metadata={
+            "help": "Used by the SageMaker launcher to send mp-specific args. Ignored in Trainer"
+        },
+    )
+
+    auto_find_batch_size: bool = field(
+        default=False,
+        metadata={
+            "help": (
+                "Whether to automatically decrease the batch size in half and rerun the training loop again each time"
+                " a CUDA Out-of-Memory was reached"
+            )
+        },
+    )
+    full_determinism: bool = field(
+        default=False,
+        metadata={
+            "help": (
+                "Whether to call enable_full_determinism instead of set_seed for reproducibility in distributed"
+                " training"
+            )
+        },
+    )
+    torchdynamo: Optional[str] = field(
+        default=None,
+        metadata={
+            "help": (
+                "Sets up the backend compiler for TorchDynamo. TorchDynamo is a Python level JIT compiler designed to"
+                " make unmodified PyTorch programs faster. TorchDynamo dynamically modifies the Python bytecode right"
+                " before its executed. It rewrites Python bytecode to extract sequences of PyTorch operations"
+                " and lifts them up into Fx graph. We can then pass these Fx graphs to other backend compilers. There"
+                " are two options - eager and nvfuser. Eager defaults to pytorch eager and is useful for debugging."
+                " nvfuser path uses AOT Autograd and nvfuser compiler to optimize the models."
+            ),
+            "choices": ["eager", "nvfuser", "fx2trt", "fx2trt-fp16"],
+        },
+    )
+    ray_scope: Optional[str] = field(
+        default="last",
+        metadata={
+            "help": (
+                'The scope to use when doing hyperparameter search with Ray. By default, `"last"` will be used. Ray'
+                " will then use the last checkpoint of all trials, compare those, and select the best one. However,"
+                " other options are also available. See the Ray documentation"
+                " (https://docs.ray.io/en/latest/tune/api_docs/analysis.html"
+                "#ray.tune.ExperimentAnalysis.get_best_trial)"
+                " for more options."
+            )
+        },
+    )
+    ddp_timeout: Optional[int] = field(
+        default=1800,
+        metadata={
+            "help": "Overrides the default timeout for distributed training (value should be given in seconds)."
+        },
+    )
+
+    def __post_init__(self):
+        # NOTE: Just a portion of the actual __post_init__ of the TrainingArguments class which is
+        # causing issues.
+        import os
+
+        # Handle --use_env option in torch.distributed.launch (local_rank not passed as an arg then).
+        # This needs to happen before any call to self.device or self.n_gpu.
+        env_local_rank = int(os.environ.get("LOCAL_RANK", -1))
+        if env_local_rank != -1 and env_local_rank != self.local_rank:
+            self.local_rank = env_local_rank
+
+        # expand paths, if not os.makedirs("~/bar") will make directory
+        # in the current directory instead of the actual home
+        #  see https://github.com/huggingface/transformers/issues/10628
+        if self.output_dir is not None:
+            self.output_dir = os.path.expanduser(self.output_dir)
+        if self.logging_dir is None and self.output_dir is not None:
+            self.logging_dir = os.path.join(self.output_dir, "tmp_trainer")
+        if self.logging_dir is not None:
+            self.logging_dir = os.path.expanduser(self.logging_dir)
+
+        # if self.disable_tqdm is None:
+        #     self.disable_tqdm = logger.getEffectiveLevel() > logging.WARN
+        import warnings
+
+        if isinstance(self.evaluation_strategy, EvaluationStrategy):
+            warnings.warn(
+                "using `EvaluationStrategy` for `evaluation_strategy` is deprecated and will be removed in version 5"
+                " of 🤗 Transformers. Use `IntervalStrategy` instead",
+                FutureWarning,
+            )
+            # Go back to the underlying string or we won't be able to instantiate `IntervalStrategy` on it.
+            self.evaluation_strategy = self.evaluation_strategy.value
+
+        self.evaluation_strategy = IntervalStrategy(self.evaluation_strategy)
+        self.logging_strategy = IntervalStrategy(self.logging_strategy)
+        self.save_strategy = IntervalStrategy(self.save_strategy)
+        self.hub_strategy = HubStrategy(self.hub_strategy)
+
+
+@pytest.mark.xfail(reason="docstring_parser can't parse the docstring of TrainingArguments!")
+def test_docstring_parse_works_with_hf_training_args():
+
+    assert get_attribute_docstring(TrainingArguments, "output_dir").desc_from_cls_docstring == (
+        "The output directory where the model predictions and checkpoints will be written."
+    )
+
+
+def test_entire_docstring_isnt_used_as_help():
+    help_text = TrainingArguments.get_help_text()
+
+    help_from_field = "Whether to use Apple Silicon chip based `mps` device."
+    assert help_from_field in help_text
+    help_from_docstring = "use_mps_device (`bool`, *optional*, defaults to `False`):"
+    assert help_from_docstring not in help_text
+
+
+@pytest.mark.parametrize(
+    "args",
+    [
+        TrainingArguments(),
+        TrainingArguments(save_strategy="steps"),
+        TrainingArguments(save_strategy=IntervalStrategy.EPOCH),
+    ],
+)
+@pytest.mark.parametrize("filename", ["bob.yaml", "bob.json", "bob.pkl", "bob.yml"])
+def test_serialization(tmp_path: Path, filename: str, args: TrainingArguments):
+    """test that serializing / deserializing a TrainingArguments works."""
+    from simple_parsing.helpers.serialization import load, save
+
+    path = tmp_path / filename
+    save(args, path)
+    assert load(TrainingArguments, path) == args