Skip to content

Commit

Permalink
Reorganize tests to make them easier to find (#768)
Browse files Browse the repository at this point in the history
* Add eval loader to eval script

* small input tests

* updates

* fix typing and formatting

* fixes, add tests

* remove circular dependency

* tests pass

* nits + small fixes

* add metrics at the end, refactor to put icl/gauntlet as helpers

* NOT

* metrics instead of models, add unit tests

* Move tests into directories

* add copyright to inits

* fix relative paths

* fixes

* revert gauntlet test change

* Support inputs_embeds (#687)

* support inputs_embeds

* update tests to test inputs_embeds

* make iids optional inputs to fwd

* remove check for both iids and inputs_embeds

in MPTForCausalLM. It is checked in the base model, and it is actually a common practice to pass both during autoregressive generation. Embeds are used first, then once the kvcache is nonempty, iids are used instead

* reorder kwargs

* add more tests

* fix device merge artifact in test_model.oy

* fix generate test

* yapf

* Better error message when test does not complete (#769)

* run script tests first

* comment out

* ascripts -> scripts

* bad dirs

* try this

* hacks

* add a note about a_scripts

---------

Co-authored-by: Sam Havens <[email protected]>
  • Loading branch information
2 people authored and dakinggg committed Dec 2, 2023
1 parent 1001bb6 commit 6d05cf3
Show file tree
Hide file tree
Showing 51 changed files with 176 additions and 154 deletions.
30 changes: 21 additions & 9 deletions scripts/inference/convert_composer_to_hf.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,19 +168,11 @@ def parse_args() -> Namespace:
return parser.parse_args()


def convert_composer_to_hf(args: Namespace) -> None:
def _convert_composer_to_hf(args: Namespace) -> None:
print()
print('#' * 30)
print('Converting Composer checkpoint to HuggingFace checkpoint format...')

# Register MPT auto classes so that this script works with MPT
# This script will not work without modification for other custom models,
# but will work for other HuggingFace causal LMs
from transformers.models.auto.configuration_auto import CONFIG_MAPPING
CONFIG_MAPPING._extra_content['mpt'] = MPTConfig
MPTConfig.register_for_auto_class()
MPTForCausalLM.register_for_auto_class('AutoModelForCausalLM')

_, _, local_folder_path = parse_uri(args.hf_output_path)

config, tokenizer = write_huggingface_pretrained_from_composer_checkpoint(
Expand Down Expand Up @@ -296,5 +288,25 @@ def convert_composer_to_hf(args: Namespace) -> None:
)


def convert_composer_to_hf(args: Namespace) -> None:
# Register MPT auto classes so that this script works with MPT
# This script will not work without modification for other custom models,
# but will work for other HuggingFace causal LMs
from transformers.models.auto.configuration_auto import CONFIG_MAPPING
CONFIG_MAPPING._extra_content['mpt'] = MPTConfig
MPTConfig.register_for_auto_class()
MPTForCausalLM.register_for_auto_class('AutoModelForCausalLM')

try:
_convert_composer_to_hf(args)
except Exception as e:
raise e
finally:
# Undo auto registration after running the script
del CONFIG_MAPPING._extra_content['mpt']
delattr(MPTConfig, '_auto_class')
delattr(MPTForCausalLM, '_auto_class')


if __name__ == '__main__':
convert_composer_to_hf(parse_args())
6 changes: 6 additions & 0 deletions tests/a_scripts/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Copyright 2022 MosaicML LLM Foundry authors
# SPDX-License-Identifier: Apache-2.0

# TODO: This test directory is called "a_scripts" to enforce that these tests are run
# first. More clean up should be done to ensure tests can be run in any order and
# don't leave around artifacts
2 changes: 2 additions & 0 deletions tests/a_scripts/data_prep/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Copyright 2022 MosaicML LLM Foundry authors
# SPDX-License-Identifier: Apache-2.0
28 changes: 28 additions & 0 deletions tests/a_scripts/data_prep/test_convert_dataset_hf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Copyright 2022 MosaicML LLM Foundry authors
# SPDX-License-Identifier: Apache-2.0

import os
from argparse import Namespace
from pathlib import Path

from scripts.data_prep.convert_dataset_hf import main as main_hf


def test_download_script_from_api(tmp_path: Path):
# test calling it directly
path = os.path.join(tmp_path, 'my-copy-c4-1')
main_hf(
Namespace(
**{
'dataset': 'c4',
'data_subset': 'en',
'splits': ['val_xsmall'],
'out_root': path,
'compression': None,
'concat_tokens': None,
'bos_text': None,
'eos_text': None,
'no_wrap': False,
'num_workers': None
}))
assert os.path.exists(path)
27 changes: 27 additions & 0 deletions tests/a_scripts/data_prep/test_convert_dataset_json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Copyright 2022 MosaicML LLM Foundry authors
# SPDX-License-Identifier: Apache-2.0

import os
from argparse import Namespace
from pathlib import Path

from scripts.data_prep.convert_dataset_json import main as main_json


def test_json_script_from_api(tmp_path: Path):
# test calling it directly
path = os.path.join(tmp_path, 'my-copy-arxiv-1')
main_json(
Namespace(
**{
'path': 'scripts/data_prep/example_data/arxiv.jsonl',
'out_root': path,
'compression': None,
'split': 'train',
'concat_tokens': None,
'bos_text': None,
'eos_text': None,
'no_wrap': False,
'num_workers': None
}))
assert os.path.exists(path)
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,14 @@
# SPDX-License-Identifier: Apache-2.0

import os
import sys

import pytest

# Add repo root to path so we can import scripts and test it
repo_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
sys.path.append(repo_dir)
import pathlib
from concurrent.futures import ProcessPoolExecutor
from glob import glob
from typing import Callable, Iterable, List
from unittest.mock import Mock, patch

import numpy as np
import pytest
from streaming import StreamingDataset
from transformers import AutoTokenizer

Expand Down
2 changes: 2 additions & 0 deletions tests/a_scripts/eval/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Copyright 2022 MosaicML LLM Foundry authors
# SPDX-License-Identifier: Apache-2.0
34 changes: 16 additions & 18 deletions tests/test_eval.py → tests/a_scripts/eval/test_eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
import copy
import os
import pathlib
import sys
from typing import Any
from typing import Any, Union

import omegaconf as om
import pytest
Expand All @@ -14,15 +13,10 @@

from llmfoundry import COMPOSER_MODEL_REGISTRY
from llmfoundry.utils import build_tokenizer
from scripts.eval.eval import main # noqa: E402
from tests.data_utils import (create_arxiv_dataset, create_c4_dataset_xxsmall,
gpt_tiny_cfg)

# Add repo root to path so we can import scripts and test it
repo_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
sys.path.append(repo_dir)

from scripts.eval.eval import main # noqa: E402


@pytest.fixture(autouse=True)
def set_correct_cwd():
Expand All @@ -35,11 +29,16 @@ def set_correct_cwd():
os.chdir('..')


@pytest.fixture()
def mock_saved_model_path():
# load the eval and model config
with open('eval/yamls/test_eval.yaml', 'r', encoding='utf-8') as f:
@pytest.fixture
def eval_cfg(foundry_dir: str) -> Union[om.ListConfig, om.DictConfig]:
yaml_path = os.path.join(foundry_dir, 'scripts/eval/yamls/test_eval.yaml')
with open(yaml_path, 'r', encoding='utf-8') as f:
eval_cfg = om.OmegaConf.load(f)
return eval_cfg


@pytest.fixture()
def mock_saved_model_path(eval_cfg: Union[om.ListConfig, om.DictConfig]):
model_cfg = eval_cfg.models[0]
# set device to cpu
device = 'cpu'
Expand All @@ -60,12 +59,11 @@ def mock_saved_model_path():
os.remove(saved_model_path)


def test_icl_eval(capfd: Any, mock_saved_model_path: Any):
with open('eval/yamls/test_eval.yaml', 'r', encoding='utf-8') as f:
test_cfg = om.OmegaConf.load(f)
test_cfg.models[0].load_path = mock_saved_model_path
assert isinstance(test_cfg, om.DictConfig)
main(test_cfg)
def test_icl_eval(eval_cfg: Union[om.ListConfig, om.DictConfig], capfd: Any,
mock_saved_model_path: Any):
eval_cfg.models[0].load_path = mock_saved_model_path
assert isinstance(eval_cfg, om.DictConfig)
main(eval_cfg)
out, _ = capfd.readouterr()
expected_results = '| Category | Benchmark | Subtask | Accuracy | Number few shot | Model |\n|:----------------------------|:---------------|:----------|-----------:|:------------------|:---------|\n| language_understanding_lite | lambada_openai | | 0 | 0-shot | tiny_mpt |'
assert expected_results in out
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,26 @@
# SPDX-License-Identifier: Apache-2.0
import copy
import os
import sys
import warnings

import omegaconf
import pytest
from omegaconf import DictConfig
from omegaconf import OmegaConf as om

# Add repo root to path so we can import scripts and test it
repo_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
sys.path.append(repo_dir)

from scripts.eval.eval import main # noqa: E402


class TestHuggingFaceEvalYAMLInputs:
"""Validate and tests error handling for the input YAML file."""

@pytest.fixture
def cfg(self) -> DictConfig:
def cfg(self, foundry_dir: str) -> DictConfig:
"""Create YAML cfg fixture for testing purposes."""
conf_path: str = os.path.join(repo_dir,
'scripts/eval/yamls/hf_eval.yaml')
conf_path: str = os.path.join(
foundry_dir,
'scripts/eval/yamls/hf_eval.yaml',
)
with open(conf_path, 'r', encoding='utf-8') as config:
test_cfg = om.load(config)
assert isinstance(test_cfg, DictConfig)
Expand Down Expand Up @@ -78,15 +75,17 @@ def test_optional_mispelled_params_raise_warning(self,
class TestMPTEvalYAMLInputs:

@pytest.fixture
def cfg(self) -> DictConfig:
def cfg(self, foundry_dir: str) -> DictConfig:
"""Create YAML cfg fixture for testing purposes."""
conf_path: str = os.path.join(repo_dir,
'scripts/eval/yamls/mpt_eval.yaml')
conf_path: str = os.path.join(
foundry_dir,
'scripts/eval/yamls/mpt_eval.yaml',
)
with open(conf_path, 'r', encoding='utf-8') as config:
test_cfg = om.load(config)

test_cfg.icl_tasks[0].dataset_uri = os.path.join(
repo_dir, 'scripts', test_cfg.icl_tasks[0].dataset_uri)
foundry_dir, 'scripts', test_cfg.icl_tasks[0].dataset_uri)

# make tests use cpu initialized transformer models only
test_cfg.models[0].model.init_device = 'cpu'
Expand Down
2 changes: 2 additions & 0 deletions tests/a_scripts/inference/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Copyright 2022 MosaicML LLM Foundry authors
# SPDX-License-Identifier: Apache-2.0
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,26 @@
import math
import os
import pathlib
import sys
from typing import Callable
from unittest.mock import ANY, MagicMock, patch

from composer import Trainer
from composer.loggers import MLFlowLogger
from composer.utils import dist, get_device, using_torch_2

from llmfoundry.callbacks import HuggingFaceCheckpointer
from llmfoundry.models.mpt.modeling_mpt import ComposerMPTCausalLM

# Add repo root to path so we can import scripts and test it
repo_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
sys.path.append(repo_dir)
import shutil
from argparse import Namespace
from typing import Optional, cast
from typing import Callable, Optional, cast
from unittest.mock import ANY, MagicMock, patch

import pytest
import torch
import transformers
from composer import Trainer
from composer.loggers import MLFlowLogger
from composer.utils import dist, get_device, using_torch_2
from omegaconf import DictConfig
from omegaconf import OmegaConf as om
from torch.utils.data import DataLoader
from transformers import PreTrainedModel, PreTrainedTokenizerBase

from llmfoundry import COMPOSER_MODEL_REGISTRY
from llmfoundry.callbacks import HuggingFaceCheckpointer
from llmfoundry.data.finetuning import build_finetuning_dataloader
from llmfoundry.models.mpt.modeling_mpt import ComposerMPTCausalLM
from llmfoundry.utils.builders import build_optimizer, build_tokenizer
from scripts.inference.convert_composer_to_hf import convert_composer_to_hf
from tests.data_utils import make_tiny_ft_dataset
Expand Down
2 changes: 2 additions & 0 deletions tests/a_scripts/train/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Copyright 2022 MosaicML LLM Foundry authors
# SPDX-License-Identifier: Apache-2.0
21 changes: 4 additions & 17 deletions tests/test_training.py → tests/a_scripts/train/test_train.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
# Copyright 2022 MosaicML LLM Foundry authors
# SPDX-License-Identifier: Apache-2.0
import copy
import os
import pathlib
from typing import Any, Optional
from typing import Optional

import pytest
from composer.loggers import InMemoryLogger
Expand All @@ -16,22 +15,10 @@
gpt_tiny_cfg)


@pytest.fixture(autouse=False)
def set_correct_cwd():
if not os.getcwd().endswith('llm-foundry/scripts'):
os.chdir('scripts')

yield

if os.getcwd().endswith('llm-foundry/scripts'):
os.chdir('..')


@pytest.mark.parametrize('averages', [{
'core_average': ['language_understanding_lite']
}, None])
def test_train_gauntlet(averages: Optional[dict], set_correct_cwd: Any,
tmp_path: pathlib.Path):
def test_train_gauntlet(averages: Optional[dict], tmp_path: pathlib.Path):
"""Test training run with a small dataset."""
dataset_name = create_c4_dataset_xxsmall(tmp_path)
test_cfg = gpt_tiny_cfg(dataset_name, 'cpu')
Expand All @@ -40,7 +27,7 @@ def test_train_gauntlet(averages: Optional[dict], set_correct_cwd: Any,
'label':
'lambada_openai',
'dataset_uri':
'eval/local_data/language_understanding/lambada_openai_small.jsonl',
'scripts/eval/local_data/language_understanding/lambada_openai_small.jsonl',
'num_fewshot': [0],
'icl_task_type':
'language_modeling'
Expand Down Expand Up @@ -110,7 +97,7 @@ def test_train_gauntlet(averages: Optional[dict], set_correct_cwd: Any,
-1][-1] == 0


def test_train_multi_eval(set_correct_cwd: Any, tmp_path: pathlib.Path):
def test_train_multi_eval(tmp_path: pathlib.Path):
"""Test training run with multiple eval datasets."""
c4_dataset_name = create_c4_dataset_xxsmall(tmp_path)
test_cfg = gpt_tiny_cfg(c4_dataset_name, 'cpu')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,13 @@
import copy
import json
import os
import sys
import warnings

import omegaconf
import pytest
from omegaconf import DictConfig
from omegaconf import OmegaConf as om

# Add repo root to path so we can import scripts and test it
repo_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
sys.path.append(repo_dir)

from scripts.train.train import main # noqa: E402


Expand Down Expand Up @@ -54,10 +49,10 @@ class TestTrainingYAMLInputs:
"""Validate and tests error handling for the input YAML file."""

@pytest.fixture
def cfg(self) -> DictConfig:
def cfg(self, foundry_dir: str) -> DictConfig:
"""Create YAML cfg fixture for testing purposes."""
conf_path: str = os.path.join(
repo_dir, 'scripts/train/yamls/pretrain/testing.yaml')
foundry_dir, 'scripts/train/yamls/pretrain/testing.yaml')
with open(conf_path, 'r', encoding='utf-8') as config:
test_cfg = om.load(config)
assert isinstance(test_cfg, DictConfig)
Expand Down
2 changes: 2 additions & 0 deletions tests/callbacks/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Copyright 2022 MosaicML LLM Foundry authors
# SPDX-License-Identifier: Apache-2.0
File renamed without changes.
Loading

0 comments on commit 6d05cf3

Please sign in to comment.