Files
wehub-resource-sync 593b94c120
pytest / Unit Tests (push) Has been cancelled
pytest / Integration (integration_tests_a) (push) Has been cancelled
pytest / Integration (integration_tests_b) (push) Has been cancelled
pytest / Integration (integration_tests_c) (push) Has been cancelled
pytest / Integration (integration_tests_d) (push) Has been cancelled
pytest / Integration (integration_tests_e) (push) Has been cancelled
pytest / Integration (integration_tests_f) (push) Has been cancelled
pytest / Integration (integration_tests_g) (push) Has been cancelled
pytest / Integration (integration_tests_h) (push) Has been cancelled
pytest / Integration (integration_tests_i) (push) Has been cancelled
pytest / Integration (integration_tests_j) (push) Has been cancelled
pytest / Distributed (distributed_a) (push) Has been cancelled
pytest / Distributed (distributed_b) (push) Has been cancelled
pytest / Distributed (distributed_c) (push) Has been cancelled
pytest / Distributed (distributed_d) (push) Has been cancelled
pytest / Distributed (distributed_e) (push) Has been cancelled
pytest / Distributed (distributed_f) (push) Has been cancelled
pytest / Minimal Install (push) Has been cancelled
pytest / Event File (push) Has been cancelled
pytest (slow) / py-slow (push) Has been cancelled
Publish JSON Schema / publish-schema (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:49:20 +08:00

419 lines
15 KiB
Python

# Copyright (c) 2023 Predibase, Inc., 2020 Uber Technologies, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
import json
import os
import os.path
import pathlib
import shutil
import subprocess
import sys
import pytest
import yaml
from ludwig.constants import (
BATCH_SIZE,
COMBINER,
EVAL_BATCH_SIZE,
INPUT_FEATURES,
NAME,
OUTPUT_FEATURES,
PREPROCESSING,
TRAINER,
)
from ludwig.globals import MODEL_FILE_NAME
from ludwig.types import FeatureConfigDict
from ludwig.utils.data_utils import load_yaml
from tests.integration_tests.utils import category_feature, generate_data, number_feature, sequence_feature
pytestmark = pytest.mark.integration_tests_f
def _run_commands(commands, **ludwig_kwargs):
for arg_name, value in ludwig_kwargs.items():
commands += ["--" + arg_name, value]
cmdline = " ".join(commands)
print(cmdline)
completed_process = subprocess.run(cmdline, shell=True, stdout=subprocess.PIPE, env=os.environ.copy())
assert completed_process.returncode == 0
return completed_process
def _run_ludwig(command, **ludwig_kwargs):
ludwig_bin = os.path.join(os.path.dirname(sys.executable), "ludwig")
commands = [ludwig_bin, command]
return _run_commands(commands, **ludwig_kwargs)
def _prepare_data(csv_filename, config_filename):
# Single sequence input, single category output
input_features = [sequence_feature(encoder={"reduce_output": "sum"})]
output_features = [category_feature(decoder={"vocab_size": 3}, reduce_input="sum")]
# Generate test data
dataset_filename = generate_data(input_features, output_features, csv_filename)
# generate config file
config = {
"input_features": input_features,
"output_features": output_features,
"combiner": {"type": "concat", "output_size": 14},
TRAINER: {"epochs": 2, BATCH_SIZE: 128, EVAL_BATCH_SIZE: 128},
}
with open(config_filename, "w") as f:
yaml.dump(config, f)
return dataset_filename
def _prepare_hyperopt_data(csv_filename, config_filename):
# Single sequence input, single category output
input_features = [sequence_feature(encoder={"reduce_output": "sum"})]
output_features = [category_feature(decoder={"vocab_size": 2}, reduce_input="sum")]
# Generate test data
dataset_filename = generate_data(input_features, output_features, csv_filename)
# generate config file
config = {
"input_features": input_features,
"output_features": output_features,
"combiner": {"type": "concat", "output_size": 4},
TRAINER: {"epochs": 2, BATCH_SIZE: 128},
"hyperopt": {
"parameters": {
"trainer.learning_rate": {
"space": "loguniform",
"lower": 0.0001,
"upper": 0.01,
}
},
"goal": "minimize",
"output_feature": output_features[0]["name"],
"validation_metrics": "loss",
"executor": {
"type": "ray",
"num_samples": 2,
},
"search_alg": {
"type": "variant_generator",
},
},
}
with open(config_filename, "w") as f:
yaml.dump(config, f)
return dataset_filename
def test_train_cli_dataset(tmpdir, csv_filename):
"""Test training using `ludwig train --dataset`."""
config_filename = os.path.join(tmpdir, "config.yaml")
dataset_filename = _prepare_data(csv_filename, config_filename)
_run_ludwig("train", dataset=dataset_filename, config=config_filename, output_directory=str(tmpdir))
def test_train_cli_gpu_memory_limit(tmpdir, csv_filename):
"""Test training using `ludwig train --dataset --gpu_memory_limit`."""
config_filename = os.path.join(tmpdir, "config.yaml")
dataset_filename = _prepare_data(csv_filename, config_filename)
_run_ludwig(
"train", dataset=dataset_filename, config=config_filename, output_directory=str(tmpdir), gpu_memory_limit="0.5"
)
def test_train_cli_training_set(tmpdir, csv_filename):
"""Test training using `ludwig train --training_set`."""
config_filename = os.path.join(tmpdir, "config.yaml")
dataset_filename = _prepare_data(csv_filename, config_filename)
validation_filename = shutil.copyfile(dataset_filename, os.path.join(tmpdir, "validation.csv"))
test_filename = shutil.copyfile(dataset_filename, os.path.join(tmpdir, "test.csv"))
_run_ludwig(
"train",
training_set=dataset_filename,
validation_set=validation_filename,
test_set=test_filename,
config=config_filename,
output_directory=str(tmpdir),
)
def test_export_mlflow_cli(tmpdir, csv_filename):
"""Test export_mlflow cli."""
config_filename = os.path.join(tmpdir, "config.yaml")
dataset_filename = _prepare_data(csv_filename, config_filename)
_run_ludwig("train", dataset=dataset_filename, config=config_filename, output_directory=str(tmpdir))
_run_ludwig(
"export_mlflow",
model_path=os.path.join(tmpdir, "experiment_run", MODEL_FILE_NAME),
output_path=os.path.join(tmpdir, "data/results/mlflow"),
)
def test_experiment_cli(tmpdir, csv_filename):
"""Test experiment cli."""
config_filename = os.path.join(tmpdir, "config.yaml")
dataset_filename = _prepare_data(csv_filename, config_filename)
_run_ludwig("experiment", dataset=dataset_filename, config=config_filename, output_directory=str(tmpdir))
def test_predict_cli(tmpdir, csv_filename):
"""Test predict cli."""
config_filename = os.path.join(tmpdir, "config.yaml")
dataset_filename = _prepare_data(csv_filename, config_filename)
_run_ludwig("train", dataset=dataset_filename, config=config_filename, output_directory=str(tmpdir))
_run_ludwig(
"predict",
dataset=dataset_filename,
model=os.path.join(tmpdir, "experiment_run", MODEL_FILE_NAME),
output_directory=os.path.join(tmpdir, "predictions"),
)
def test_evaluate_cli(tmpdir, csv_filename):
"""Test evaluate cli."""
config_filename = os.path.join(tmpdir, "config.yaml")
dataset_filename = _prepare_data(csv_filename, config_filename)
_run_ludwig("train", dataset=dataset_filename, config=config_filename, output_directory=str(tmpdir))
_run_ludwig(
"evaluate",
dataset=dataset_filename,
model=os.path.join(tmpdir, "experiment_run", MODEL_FILE_NAME),
output_directory=os.path.join(tmpdir, "predictions"),
)
@pytest.mark.distributed
@pytest.mark.distributed_f
def test_hyperopt_cli(tmpdir, csv_filename):
"""Test hyperopt cli."""
config_filename = os.path.join(tmpdir, "config.yaml")
dataset_filename = _prepare_hyperopt_data(csv_filename, config_filename)
_run_ludwig("hyperopt", dataset=dataset_filename, config=config_filename, output_directory=str(tmpdir))
def test_visualize_cli(tmpdir, csv_filename):
"""Test Ludwig 'visualize' cli."""
config_filename = os.path.join(tmpdir, "config.yaml")
dataset_filename = _prepare_data(csv_filename, config_filename)
_run_ludwig("train", dataset=dataset_filename, config=config_filename, output_directory=str(tmpdir))
_run_ludwig(
"visualize",
visualization="learning_curves",
model_names="run",
training_statistics=os.path.join(tmpdir, "experiment_run", "training_statistics.json"),
output_directory=os.path.join(tmpdir, "visualizations"),
)
def test_collect_summary_activations_weights_cli(tmpdir, csv_filename):
"""Test collect_summary cli."""
config_filename = os.path.join(tmpdir, "config.yaml")
dataset_filename = _prepare_data(csv_filename, config_filename)
_run_ludwig("train", dataset=dataset_filename, config=config_filename, output_directory=str(tmpdir))
assert _run_ludwig("collect_summary", model=os.path.join(tmpdir, "experiment_run", MODEL_FILE_NAME))
@pytest.mark.parametrize(
"model_name",
[
"alexnet",
"convnext_base",
"convnext_large",
"convnext_small",
"convnext_tiny",
"densenet121",
"densenet161",
"densenet169",
"openai-community/gpt2",
"facebook/opt-125m",
],
)
def test_collect_summary_pretrained_model_cli(model_name):
"""Test collect_summary pretrained model cli."""
assert _run_ludwig("collect_summary", pretrained_model=model_name)
def test_synthesize_dataset_cli(tmpdir, csv_filename):
"""Test synthesize_data cli."""
# test depends on default setting of --dataset_size
# if this parameter is specified, _run_ludwig fails when
# attempting to build the cli parameter structure
_run_ludwig(
"synthesize_dataset",
output_path=os.path.join(tmpdir, csv_filename),
features="'[ \
{name: text, type: text}, \
{name: category, type: category}, \
{name: number, type: number}, \
{name: binary, type: binary}, \
{name: set, type: set}, \
{name: bag, type: bag}, \
{name: sequence, type: sequence}, \
{name: timeseries, type: timeseries}, \
{name: date, type: date}, \
{name: h3, type: h3}, \
{name: vector, type: vector}, \
{name: audio, type: audio}, \
{name: image, type: image} \
]'",
)
def test_preprocess_cli(tmpdir, csv_filename):
"""Test preprocess `ludwig preprocess."""
config_filename = os.path.join(tmpdir, "config.yaml")
dataset_filename = _prepare_data(csv_filename, config_filename)
_run_ludwig("preprocess", dataset=dataset_filename, preprocessing_config=config_filename)
@pytest.mark.parametrize(
"second_seed_offset,random_seed,type_of_run",
[
(0, 42, "train"), # same seed train: should be reproducible
(1, 42, "train"), # different seed train: should diverge
(0, 42, "experiment"), # same seed experiment: should be reproducible
],
ids=["same_seed_train", "diff_seed_train", "same_seed_experiment"],
)
def test_reproducible_cli_runs(
type_of_run: str, random_seed: int, second_seed_offset: int, csv_filename: str, tmpdir: pathlib.Path
) -> None:
"""
Test for reproducible training using `ludwig experiment|train --dataset`.
Args:
type_of_run(str): type of run, either train or experiment
csv_filename(str): file path of dataset to use
random_seed(int): random seed integer to use for test
second_seed_offset(int): zero to use same random seed for second test, non-zero to use a different
seed for the second run.
tmpdir (pathlib.Path): temporary directory path
Returns: None
"""
config_filename = os.path.join(tmpdir, "config.yaml")
dataset_filename = _prepare_data(csv_filename, config_filename)
# run first model
_run_ludwig(
type_of_run,
dataset=dataset_filename,
config=config_filename,
output_directory=str(tmpdir),
skip_save_processed_input="", # skip saving preprocessed inputs for reproducibility
experiment_name="reproducible",
model_name="run1",
random_seed=str(random_seed),
)
# run second model with same seed
_run_ludwig(
type_of_run,
dataset=dataset_filename,
config=config_filename,
output_directory=str(tmpdir),
skip_save_processed_input="", # skip saving preprocessed inputs for reproducibility
experiment_name="reproducible",
model_name="run2",
random_seed=str(random_seed + second_seed_offset),
)
# retrieve training statistics and compare
with open(os.path.join(tmpdir, "reproducible_run1", "training_statistics.json")) as f:
training1 = json.load(f)
with open(os.path.join(tmpdir, "reproducible_run2", "training_statistics.json")) as f:
training2 = json.load(f)
if second_seed_offset == 0:
# same seeds should result in same output
assert training1 == training2
else:
# non-zero second_seed_offset uses different seeds and should result in different output
assert training1 != training2
# if type_of_run is experiment check test statistics and compare
if type_of_run == "experiment":
with open(os.path.join(tmpdir, "reproducible_run1", "test_statistics.json")) as f:
test1 = json.load(f)
with open(os.path.join(tmpdir, "reproducible_run2", "test_statistics.json")) as f:
test2 = json.load(f)
if second_seed_offset == 0:
# same seeds should result in same output
assert test1 == test2
else:
# non-zero second_seed_offset uses different seeds and should result in different output
assert test1 != test2
@pytest.mark.distributed
@pytest.mark.distributed_f
def test_init_config(tmpdir):
"""Test initializing a config from a dataset and a target."""
input_features = [
number_feature(),
number_feature(),
category_feature(encoder={"vocab_size": 3}),
category_feature(encoder={"vocab_size": 3}),
]
output_features = [category_feature(decoder={"vocab_size": 3})]
dataset_csv = generate_data(input_features, output_features, os.path.join(tmpdir, "dataset.csv"), num_examples=20)
output_config_path = os.path.join(tmpdir, "config.yaml")
_run_ludwig("init_config", dataset=dataset_csv, target=output_features[0][NAME], output=output_config_path)
config = load_yaml(output_config_path)
def to_name_set(features: list[FeatureConfigDict]) -> set[str]:
return {feature[NAME] for feature in features}
assert to_name_set(config[INPUT_FEATURES]) == to_name_set(input_features)
assert to_name_set(config[OUTPUT_FEATURES]) == to_name_set(output_features)
@pytest.mark.skip(reason="https://github.com/ludwig-ai/ludwig/issues/3377")
def test_render_config(tmpdir):
"""Test rendering a full config from a partial user config."""
user_config_path = os.path.join(tmpdir, "config.yaml")
input_features = [
number_feature(),
number_feature(),
category_feature(encoder={"vocab_size": 3}),
category_feature(encoder={"vocab_size": 3}),
]
output_features = [category_feature(decoder={"vocab_size": 3})]
user_config = {
INPUT_FEATURES: input_features,
OUTPUT_FEATURES: output_features,
}
with open(user_config_path, "w") as f:
yaml.dump(user_config, f)
output_config_path = os.path.join(tmpdir, "rendered.yaml")
_run_ludwig("render_config", config=user_config_path, output=output_config_path)
rendered_config = load_yaml(output_config_path)
assert len(rendered_config[INPUT_FEATURES]) == len(user_config[INPUT_FEATURES])
assert len(rendered_config[OUTPUT_FEATURES]) == len(user_config[OUTPUT_FEATURES])
assert TRAINER in rendered_config
assert COMBINER in rendered_config
assert PREPROCESSING in rendered_config