hafnia 0.2.4__py3-none-any.whl → 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cli/__main__.py +13 -2
- cli/config.py +2 -1
- cli/consts.py +1 -1
- cli/dataset_cmds.py +6 -14
- cli/dataset_recipe_cmds.py +78 -0
- cli/experiment_cmds.py +226 -43
- cli/profile_cmds.py +6 -5
- cli/runc_cmds.py +5 -5
- cli/trainer_package_cmds.py +65 -0
- hafnia/__init__.py +2 -0
- hafnia/data/factory.py +1 -2
- hafnia/dataset/dataset_helpers.py +0 -12
- hafnia/dataset/dataset_names.py +8 -4
- hafnia/dataset/dataset_recipe/dataset_recipe.py +119 -33
- hafnia/dataset/dataset_recipe/recipe_transforms.py +32 -4
- hafnia/dataset/dataset_recipe/recipe_types.py +1 -1
- hafnia/dataset/dataset_upload_helper.py +206 -53
- hafnia/dataset/hafnia_dataset.py +432 -194
- hafnia/dataset/license_types.py +63 -0
- hafnia/dataset/operations/dataset_stats.py +260 -3
- hafnia/dataset/operations/dataset_transformations.py +325 -4
- hafnia/dataset/operations/table_transformations.py +39 -2
- hafnia/dataset/primitives/__init__.py +8 -0
- hafnia/dataset/primitives/classification.py +1 -1
- hafnia/experiment/hafnia_logger.py +112 -0
- hafnia/http.py +16 -2
- hafnia/platform/__init__.py +9 -3
- hafnia/platform/builder.py +12 -10
- hafnia/platform/dataset_recipe.py +99 -0
- hafnia/platform/datasets.py +44 -6
- hafnia/platform/download.py +2 -1
- hafnia/platform/experiment.py +51 -56
- hafnia/platform/trainer_package.py +57 -0
- hafnia/utils.py +64 -13
- hafnia/visualizations/image_visualizations.py +3 -3
- {hafnia-0.2.4.dist-info → hafnia-0.3.0.dist-info}/METADATA +34 -30
- hafnia-0.3.0.dist-info/RECORD +53 -0
- cli/recipe_cmds.py +0 -45
- hafnia-0.2.4.dist-info/RECORD +0 -49
- {hafnia-0.2.4.dist-info → hafnia-0.3.0.dist-info}/WHEEL +0 -0
- {hafnia-0.2.4.dist-info → hafnia-0.3.0.dist-info}/entry_points.txt +0 -0
- {hafnia-0.2.4.dist-info → hafnia-0.3.0.dist-info}/licenses/LICENSE +0 -0
cli/__main__.py
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
import click
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
import hafnia
|
|
5
|
+
from cli import (
|
|
6
|
+
consts,
|
|
7
|
+
dataset_cmds,
|
|
8
|
+
dataset_recipe_cmds,
|
|
9
|
+
experiment_cmds,
|
|
10
|
+
profile_cmds,
|
|
11
|
+
runc_cmds,
|
|
12
|
+
trainer_package_cmds,
|
|
13
|
+
)
|
|
5
14
|
from cli.config import Config, ConfigSchema
|
|
6
15
|
|
|
7
16
|
|
|
8
17
|
@click.group()
|
|
18
|
+
@click.version_option(version=hafnia.__version__)
|
|
9
19
|
@click.pass_context
|
|
10
20
|
def main(ctx: click.Context) -> None:
|
|
11
21
|
"""Hafnia CLI."""
|
|
@@ -45,7 +55,8 @@ main.add_command(profile_cmds.profile)
|
|
|
45
55
|
main.add_command(dataset_cmds.dataset)
|
|
46
56
|
main.add_command(runc_cmds.runc)
|
|
47
57
|
main.add_command(experiment_cmds.experiment)
|
|
48
|
-
main.add_command(
|
|
58
|
+
main.add_command(trainer_package_cmds.trainer_package)
|
|
59
|
+
main.add_command(dataset_recipe_cmds.dataset_recipe)
|
|
49
60
|
|
|
50
61
|
if __name__ == "__main__":
|
|
51
62
|
main(max_content_width=120)
|
cli/config.py
CHANGED
|
@@ -9,7 +9,8 @@ import cli.consts as consts
|
|
|
9
9
|
from hafnia.log import sys_logger, user_logger
|
|
10
10
|
|
|
11
11
|
PLATFORM_API_MAPPING = {
|
|
12
|
-
"
|
|
12
|
+
"trainers": "/api/v1/trainers",
|
|
13
|
+
"dataset_recipes": "/api/v1/dataset-recipes",
|
|
13
14
|
"experiments": "/api/v1/experiments",
|
|
14
15
|
"experiment_environments": "/api/v1/experiment-environments",
|
|
15
16
|
"experiment_runs": "/api/v1/experiment-runs",
|
cli/consts.py
CHANGED
|
@@ -10,7 +10,7 @@ ERROR_CREATE_PROFILE: str = "Failed to create profile. Profile name must be uniq
|
|
|
10
10
|
ERROR_GET_RESOURCE: str = "Failed to get the data from platform. Verify url or api key."
|
|
11
11
|
|
|
12
12
|
ERROR_EXPERIMENT_DIR: str = "Source directory does not exist"
|
|
13
|
-
|
|
13
|
+
ERROR_TRAINER_PACKAGE_FILE_FORMAT: str = "Trainer package must be a '.zip' file"
|
|
14
14
|
|
|
15
15
|
PROFILE_SWITCHED_SUCCESS: str = "Switched to profile:"
|
|
16
16
|
PROFILE_REMOVED_SUCCESS: str = "Removed profile:"
|
cli/dataset_cmds.py
CHANGED
|
@@ -2,12 +2,10 @@ from pathlib import Path
|
|
|
2
2
|
from typing import Optional
|
|
3
3
|
|
|
4
4
|
import click
|
|
5
|
-
from rich import print as rprint
|
|
6
5
|
|
|
7
|
-
|
|
6
|
+
from cli import consts
|
|
8
7
|
from cli.config import Config
|
|
9
8
|
from hafnia import utils
|
|
10
|
-
from hafnia.platform.datasets import create_rich_table_from_dataset
|
|
11
9
|
|
|
12
10
|
|
|
13
11
|
@click.group()
|
|
@@ -18,18 +16,12 @@ def dataset():
|
|
|
18
16
|
|
|
19
17
|
@dataset.command("ls")
|
|
20
18
|
@click.pass_obj
|
|
21
|
-
def
|
|
19
|
+
def cmd_list_datasets(cfg: Config) -> None:
|
|
22
20
|
"""List available datasets on Hafnia platform"""
|
|
21
|
+
from hafnia.platform.datasets import get_datasets, pretty_print_datasets
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
try:
|
|
27
|
-
datasets = dataset_list(cfg=cfg)
|
|
28
|
-
except Exception:
|
|
29
|
-
raise click.ClickException(consts.ERROR_GET_RESOURCE)
|
|
30
|
-
|
|
31
|
-
table = create_rich_table_from_dataset(datasets)
|
|
32
|
-
rprint(table)
|
|
23
|
+
datasets = get_datasets(cfg=cfg)
|
|
24
|
+
pretty_print_datasets(datasets)
|
|
33
25
|
|
|
34
26
|
|
|
35
27
|
@dataset.command("download")
|
|
@@ -43,7 +35,7 @@ def dataset_list(cfg: Config) -> None:
|
|
|
43
35
|
)
|
|
44
36
|
@click.option("--force", "-f", is_flag=True, default=False, help="Flag to enable force redownload")
|
|
45
37
|
@click.pass_obj
|
|
46
|
-
def
|
|
38
|
+
def cmd_dataset_download(cfg: Config, dataset_name: str, destination: Optional[click.Path], force: bool) -> Path:
|
|
47
39
|
"""Download dataset from Hafnia platform"""
|
|
48
40
|
|
|
49
41
|
from hafnia.platform import datasets
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Dict, Optional
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
from rich import print as rprint
|
|
6
|
+
|
|
7
|
+
from cli.config import Config
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.group(name="dataset-recipe")
|
|
11
|
+
def dataset_recipe() -> None:
|
|
12
|
+
"""Dataset recipe commands"""
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataset_recipe.command(name="create")
|
|
17
|
+
@click.argument("path_json_recipe", required=True)
|
|
18
|
+
@click.option(
|
|
19
|
+
"-n",
|
|
20
|
+
"--name",
|
|
21
|
+
type=str,
|
|
22
|
+
default=None,
|
|
23
|
+
show_default=True,
|
|
24
|
+
help="Name of the dataset recipe.",
|
|
25
|
+
)
|
|
26
|
+
@click.pass_obj
|
|
27
|
+
def cmd_get_or_create_dataset_recipe(cfg: Config, path_json_recipe: Path, name: Optional[str]) -> None:
|
|
28
|
+
"""Create Hafnia dataset recipe from dataset recipe JSON file"""
|
|
29
|
+
from hafnia.platform.dataset_recipe import get_or_create_dataset_recipe_from_path
|
|
30
|
+
|
|
31
|
+
endpoint = cfg.get_platform_endpoint("dataset_recipes")
|
|
32
|
+
recipe = get_or_create_dataset_recipe_from_path(path_json_recipe, endpoint=endpoint, api_key=cfg.api_key, name=name)
|
|
33
|
+
|
|
34
|
+
if recipe is None:
|
|
35
|
+
raise click.ClickException("Failed to create dataset recipe.")
|
|
36
|
+
|
|
37
|
+
rprint(recipe)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataset_recipe.command(name="ls")
|
|
41
|
+
@click.pass_obj
|
|
42
|
+
@click.option("-l", "--limit", type=int, default=None, help="Limit number of listed dataset recipes.")
|
|
43
|
+
def cmd_list_dataset_recipes(cfg: Config, limit: Optional[int]) -> None:
|
|
44
|
+
"""List available dataset recipes"""
|
|
45
|
+
from hafnia.platform.dataset_recipe import get_dataset_recipes, pretty_print_dataset_recipes
|
|
46
|
+
|
|
47
|
+
endpoint = cfg.get_platform_endpoint("dataset_recipes")
|
|
48
|
+
recipes = get_dataset_recipes(endpoint=endpoint, api_key=cfg.api_key)
|
|
49
|
+
# Sort recipes to have the most recent first
|
|
50
|
+
recipes = sorted(recipes, key=lambda x: x["created_at"], reverse=True)
|
|
51
|
+
if limit is not None:
|
|
52
|
+
recipes = recipes[:limit]
|
|
53
|
+
pretty_print_dataset_recipes(recipes)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataset_recipe.command(name="rm")
|
|
57
|
+
@click.option("-i", "--id", type=str, help="Dataset recipe ID to delete.")
|
|
58
|
+
@click.option("-n", "--name", type=str, help="Dataset recipe name to delete.")
|
|
59
|
+
@click.pass_obj
|
|
60
|
+
def cmd_delete_dataset_recipe(cfg: Config, id: Optional[str], name: Optional[str]) -> Dict:
|
|
61
|
+
"""Delete a dataset recipe by ID or name"""
|
|
62
|
+
from hafnia.platform.dataset_recipe import delete_dataset_recipe_by_id, delete_dataset_recipe_by_name
|
|
63
|
+
|
|
64
|
+
endpoint = cfg.get_platform_endpoint("dataset_recipes")
|
|
65
|
+
|
|
66
|
+
if id is not None:
|
|
67
|
+
return delete_dataset_recipe_by_id(id=id, endpoint=endpoint, api_key=cfg.api_key)
|
|
68
|
+
if name is not None:
|
|
69
|
+
dataset_recipe = delete_dataset_recipe_by_name(name=name, endpoint=endpoint, api_key=cfg.api_key)
|
|
70
|
+
if dataset_recipe is None:
|
|
71
|
+
raise click.ClickException(f"Dataset recipe with name '{name}' was not found.")
|
|
72
|
+
|
|
73
|
+
return dataset_recipe
|
|
74
|
+
|
|
75
|
+
raise click.MissingParameter(
|
|
76
|
+
"No dataset recipe identifier have been given. Provide either --id or --name. "
|
|
77
|
+
"Get available recipes with 'hafnia dataset-recipe ls'."
|
|
78
|
+
)
|
cli/experiment_cmds.py
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
+
from typing import Dict, Optional
|
|
2
3
|
|
|
3
4
|
import click
|
|
4
|
-
from rich import print as rprint
|
|
5
5
|
|
|
6
|
-
import cli.consts as consts
|
|
7
6
|
from cli.config import Config
|
|
7
|
+
from hafnia import utils
|
|
8
|
+
from hafnia.platform.dataset_recipe import (
|
|
9
|
+
get_dataset_recipe_by_id,
|
|
10
|
+
get_dataset_recipe_by_name,
|
|
11
|
+
get_or_create_dataset_recipe_by_dataset_name,
|
|
12
|
+
)
|
|
13
|
+
from hafnia.platform.trainer_package import create_trainer_package
|
|
8
14
|
|
|
9
15
|
|
|
10
16
|
@click.group(name="experiment")
|
|
@@ -13,48 +19,225 @@ def experiment() -> None:
|
|
|
13
19
|
pass
|
|
14
20
|
|
|
15
21
|
|
|
22
|
+
@experiment.command(name="environments")
|
|
23
|
+
@click.pass_obj
|
|
24
|
+
def cmd_view_environments(cfg: Config):
|
|
25
|
+
"""
|
|
26
|
+
View available experiment training environments.
|
|
27
|
+
"""
|
|
28
|
+
from hafnia.platform import get_environments, pretty_print_training_environments
|
|
29
|
+
|
|
30
|
+
envs = get_environments(cfg.get_platform_endpoint("experiment_environments"), cfg.api_key)
|
|
31
|
+
|
|
32
|
+
pretty_print_training_environments(envs)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def default_experiment_run_name():
|
|
36
|
+
return f"run-{utils.now_as_str()}"
|
|
37
|
+
|
|
38
|
+
|
|
16
39
|
@experiment.command(name="create")
|
|
17
|
-
@click.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
40
|
+
@click.option(
|
|
41
|
+
"-n",
|
|
42
|
+
"--name",
|
|
43
|
+
type=str,
|
|
44
|
+
default=default_experiment_run_name(),
|
|
45
|
+
required=False,
|
|
46
|
+
help=f"Name of the experiment. [default: run-[DATETIME] e.g. {default_experiment_run_name()}] ",
|
|
47
|
+
)
|
|
48
|
+
@click.option(
|
|
49
|
+
"-c",
|
|
50
|
+
"--cmd",
|
|
51
|
+
type=str,
|
|
52
|
+
default="python scripts/train.py",
|
|
53
|
+
show_default=True,
|
|
54
|
+
help="Command to run the experiment.",
|
|
55
|
+
)
|
|
56
|
+
@click.option(
|
|
57
|
+
"-p",
|
|
58
|
+
"--trainer-path",
|
|
59
|
+
type=Path,
|
|
60
|
+
default=None,
|
|
61
|
+
help="Path to the trainer package directory. ",
|
|
62
|
+
)
|
|
63
|
+
@click.option(
|
|
64
|
+
"-i",
|
|
65
|
+
"--trainer-id",
|
|
66
|
+
type=str,
|
|
67
|
+
default=None,
|
|
68
|
+
help="ID of the trainer package. View available trainers with 'hafnia trainer ls'",
|
|
69
|
+
)
|
|
70
|
+
@click.option(
|
|
71
|
+
"-d",
|
|
72
|
+
"--dataset",
|
|
73
|
+
type=str,
|
|
74
|
+
default=None,
|
|
75
|
+
required=False,
|
|
76
|
+
help="DatasetIdentifier: Name of the dataset. View Available datasets with 'hafnia dataset ls'",
|
|
77
|
+
)
|
|
78
|
+
@click.option(
|
|
79
|
+
"-r",
|
|
80
|
+
"--dataset-recipe",
|
|
81
|
+
type=str,
|
|
82
|
+
default=None,
|
|
83
|
+
required=False,
|
|
84
|
+
help="DatasetIdentifier: Name of the dataset recipe. View available dataset recipes with 'hafnia dataset-recipe ls'",
|
|
85
|
+
)
|
|
86
|
+
@click.option(
|
|
87
|
+
"--dataset-recipe-id",
|
|
88
|
+
type=str,
|
|
89
|
+
default=None,
|
|
90
|
+
required=False,
|
|
91
|
+
help="DatasetIdentifier: ID of the dataset recipe. View dataset recipes with 'hafnia dataset-recipe ls'",
|
|
92
|
+
)
|
|
93
|
+
@click.option(
|
|
94
|
+
"-e",
|
|
95
|
+
"--environment",
|
|
96
|
+
type=str,
|
|
97
|
+
default="Free Tier",
|
|
98
|
+
show_default=True,
|
|
99
|
+
help="Experiment environment name. View available environments with 'hafnia experiment environments'",
|
|
100
|
+
)
|
|
22
101
|
@click.pass_obj
|
|
23
|
-
def
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
102
|
+
def cmd_create_experiment(
|
|
103
|
+
cfg: Config,
|
|
104
|
+
name: str,
|
|
105
|
+
cmd: str,
|
|
106
|
+
trainer_path: Path,
|
|
107
|
+
trainer_id: Optional[str],
|
|
108
|
+
dataset: Optional[str],
|
|
109
|
+
dataset_recipe: Optional[str],
|
|
110
|
+
dataset_recipe_id: Optional[str],
|
|
111
|
+
environment: str,
|
|
112
|
+
) -> None:
|
|
113
|
+
"""
|
|
114
|
+
Create and launch a new experiment run
|
|
115
|
+
|
|
116
|
+
Requires one dataset recipe and one trainer package:.
|
|
117
|
+
- One dataset identifier is required either '--dataset', '--dataset-recipe' or '--dataset-recipe-id'.
|
|
118
|
+
- One trainer identifier is required either '--trainer-path' or '--trainer-id'.
|
|
119
|
+
|
|
120
|
+
\b
|
|
121
|
+
Examples:
|
|
122
|
+
# Launch an experiment with a dataset and a trainer package from local path
|
|
123
|
+
hafnia experiment create --dataset mnist --trainer-path ../trainer-classification
|
|
124
|
+
|
|
125
|
+
\b
|
|
126
|
+
# Launch experiment with dataset recipe by name and trainer package by id
|
|
127
|
+
hafnia experiment create --dataset-recipe mnist-recipe --trainer-id 5e454c0d-fdf1-4d1f-9732-771d7fecd28e
|
|
128
|
+
|
|
129
|
+
\b
|
|
130
|
+
# Show available options:
|
|
131
|
+
hafnia experiment create --name "My Experiment" -d mnist --cmd "python scripts/train.py" -e "Free Tier" -p ../trainer-classification
|
|
132
|
+
"""
|
|
133
|
+
from hafnia.platform import create_experiment, get_exp_environment_id
|
|
134
|
+
|
|
135
|
+
dataset_recipe_response = get_dataset_recipe_by_dataset_identifies(
|
|
136
|
+
cfg=cfg,
|
|
137
|
+
dataset_name=dataset,
|
|
138
|
+
dataset_recipe_name=dataset_recipe,
|
|
139
|
+
dataset_recipe_id=dataset_recipe_id,
|
|
140
|
+
)
|
|
141
|
+
dataset_recipe_id = dataset_recipe_response["id"]
|
|
142
|
+
|
|
143
|
+
trainer_id = get_trainer_package_by_identifies(
|
|
144
|
+
cfg=cfg,
|
|
145
|
+
trainer_path=trainer_path,
|
|
146
|
+
trainer_id=trainer_id,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
env_id = get_exp_environment_id(environment, cfg.get_platform_endpoint("experiment_environments"), cfg.api_key)
|
|
150
|
+
|
|
151
|
+
experiment = create_experiment(
|
|
152
|
+
experiment_name=name,
|
|
153
|
+
dataset_recipe_id=dataset_recipe_id,
|
|
154
|
+
trainer_id=trainer_id,
|
|
155
|
+
exec_cmd=cmd,
|
|
156
|
+
environment_id=env_id,
|
|
157
|
+
endpoint=cfg.get_platform_endpoint("experiments"),
|
|
158
|
+
api_key=cfg.api_key,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
experiment_properties = {
|
|
162
|
+
"ID": experiment.get("id", "N/A"),
|
|
163
|
+
"Name": experiment.get("name", "N/A"),
|
|
164
|
+
"State": experiment.get("state", "N/A"),
|
|
165
|
+
"Trainer Package ID": experiment.get("trainer", "N/A"),
|
|
166
|
+
"Dataset Recipe ID": experiment.get("dataset_recipe", "N/A"),
|
|
167
|
+
"Dataset ID": experiment.get("dataset", "N/A"),
|
|
168
|
+
"Created At": experiment.get("created_at", "N/A"),
|
|
169
|
+
}
|
|
170
|
+
print("Successfully created experiment: ")
|
|
171
|
+
for key, value in experiment_properties.items():
|
|
172
|
+
print(f" {key}: {value}")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def get_dataset_recipe_by_dataset_identifies(
|
|
176
|
+
cfg: Config,
|
|
177
|
+
dataset_name: Optional[str],
|
|
178
|
+
dataset_recipe_name: Optional[str],
|
|
179
|
+
dataset_recipe_id: Optional[str],
|
|
180
|
+
) -> Dict:
|
|
181
|
+
dataset_identifiers = [dataset_name, dataset_recipe_name, dataset_recipe_id]
|
|
182
|
+
n_dataset_identifies_defined = sum([bool(identifier) for identifier in dataset_identifiers])
|
|
183
|
+
|
|
184
|
+
if n_dataset_identifies_defined > 1:
|
|
185
|
+
raise click.ClickException(
|
|
186
|
+
"Multiple dataset identifiers have been provided. Define only one dataset identifier."
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
dataset_recipe_endpoint = cfg.get_platform_endpoint("dataset_recipes")
|
|
190
|
+
if dataset_name:
|
|
191
|
+
return get_or_create_dataset_recipe_by_dataset_name(dataset_name, dataset_recipe_endpoint, cfg.api_key)
|
|
192
|
+
|
|
193
|
+
if dataset_recipe_name:
|
|
194
|
+
recipe = get_dataset_recipe_by_name(dataset_recipe_name, dataset_recipe_endpoint, cfg.api_key)
|
|
195
|
+
if recipe is None:
|
|
196
|
+
raise click.ClickException(f"Dataset recipe '{dataset_recipe_name}' was not found in the dataset library.")
|
|
197
|
+
return recipe
|
|
198
|
+
|
|
199
|
+
if dataset_recipe_id:
|
|
200
|
+
return get_dataset_recipe_by_id(dataset_recipe_id, dataset_recipe_endpoint, cfg.api_key)
|
|
201
|
+
|
|
202
|
+
raise click.MissingParameter(
|
|
203
|
+
"At least one dataset identifier must be provided. Set one of the following:\n"
|
|
204
|
+
" --dataset <name> -- E.g. '--dataset mnist'\n"
|
|
205
|
+
" --dataset-recipe <name> -- E.g. '--dataset-recipe my-recipe'\n"
|
|
206
|
+
" --dataset-recipe-id <id> -- E.g. '--dataset-recipe-id 5e454c0d-fdf1-4d1f-9732-771d7fecd28e'\n"
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def get_trainer_package_by_identifies(
|
|
211
|
+
cfg: Config,
|
|
212
|
+
trainer_path: Optional[Path],
|
|
213
|
+
trainer_id: Optional[str],
|
|
214
|
+
) -> str:
|
|
215
|
+
from hafnia.platform import get_trainer_package_by_id
|
|
216
|
+
|
|
217
|
+
if trainer_path is not None and trainer_id is not None:
|
|
218
|
+
raise click.ClickException(
|
|
219
|
+
"Multiple trainer identifiers (--trainer-path, --trainer-id) have been provided. Define only one."
|
|
48
220
|
)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
221
|
+
|
|
222
|
+
if trainer_path is not None:
|
|
223
|
+
trainer_path = Path(trainer_path)
|
|
224
|
+
if not trainer_path.exists():
|
|
225
|
+
raise click.ClickException(f"Trainer package path '{trainer_path}' does not exist.")
|
|
226
|
+
trainer_id = create_trainer_package(
|
|
227
|
+
trainer_path,
|
|
228
|
+
cfg.get_platform_endpoint("trainers"),
|
|
229
|
+
cfg.api_key,
|
|
230
|
+
)
|
|
231
|
+
return trainer_id
|
|
232
|
+
|
|
233
|
+
if trainer_id:
|
|
234
|
+
trainer_response = get_trainer_package_by_id(
|
|
235
|
+
id=trainer_id, endpoint=cfg.get_platform_endpoint("trainers"), api_key=cfg.api_key
|
|
236
|
+
)
|
|
237
|
+
return trainer_response["id"]
|
|
238
|
+
|
|
239
|
+
raise click.MissingParameter(
|
|
240
|
+
"At least one trainer identifier must be provided. Set one of the following:\n"
|
|
241
|
+
" --trainer-path <path> -- E.g. '--trainer-path .'\n"
|
|
242
|
+
" --trainer-id <id> -- E.g. '--trainer-id 5e454c0d-fdf1-4d1f-9732-771d7fecd28e'\n"
|
|
60
243
|
)
|
cli/profile_cmds.py
CHANGED
|
@@ -14,7 +14,7 @@ def profile():
|
|
|
14
14
|
|
|
15
15
|
@profile.command("ls")
|
|
16
16
|
@click.pass_obj
|
|
17
|
-
def
|
|
17
|
+
def cmd_profile_ls(cfg: Config) -> None:
|
|
18
18
|
"""List all available profiles."""
|
|
19
19
|
profiles = cfg.available_profiles
|
|
20
20
|
if not profiles:
|
|
@@ -31,7 +31,7 @@ def profile_ls(cfg: Config) -> None:
|
|
|
31
31
|
@profile.command("use")
|
|
32
32
|
@click.argument("profile_name", required=True)
|
|
33
33
|
@click.pass_obj
|
|
34
|
-
def
|
|
34
|
+
def cmd_profile_use(cfg: Config, profile_name: str) -> None:
|
|
35
35
|
"""Switch to a different profile."""
|
|
36
36
|
if len(cfg.available_profiles) == 0:
|
|
37
37
|
raise click.ClickException(consts.ERROR_CONFIGURE)
|
|
@@ -51,7 +51,7 @@ def profile_use(cfg: Config, profile_name: str) -> None:
|
|
|
51
51
|
"--activate/--no-activate", help="Activate the created profile after creation", default=True, show_default=True
|
|
52
52
|
)
|
|
53
53
|
@click.pass_obj
|
|
54
|
-
def
|
|
54
|
+
def cmd_profile_create(cfg: Config, name: str, api_url: str, api_key: str, activate: bool) -> None:
|
|
55
55
|
"""Create a new profile."""
|
|
56
56
|
cfg_profile = ConfigSchema(platform_url=api_url, api_key=api_key)
|
|
57
57
|
|
|
@@ -62,7 +62,7 @@ def profile_create(cfg: Config, name: str, api_url: str, api_key: str, activate:
|
|
|
62
62
|
@profile.command("rm")
|
|
63
63
|
@click.argument("profile_name", required=True)
|
|
64
64
|
@click.pass_obj
|
|
65
|
-
def
|
|
65
|
+
def cmd_profile_rm(cfg: Config, profile_name: str) -> None:
|
|
66
66
|
"""Remove a profile."""
|
|
67
67
|
if len(cfg.available_profiles) == 0:
|
|
68
68
|
raise click.ClickException(consts.ERROR_CONFIGURE)
|
|
@@ -80,7 +80,8 @@ def profile_rm(cfg: Config, profile_name: str) -> None:
|
|
|
80
80
|
|
|
81
81
|
@profile.command("active")
|
|
82
82
|
@click.pass_obj
|
|
83
|
-
def
|
|
83
|
+
def cmd_profile_active(cfg: Config) -> None:
|
|
84
|
+
"""Show the currently active profile."""
|
|
84
85
|
try:
|
|
85
86
|
profile_show(cfg)
|
|
86
87
|
except Exception as e:
|
cli/runc_cmds.py
CHANGED
|
@@ -13,7 +13,7 @@ from hafnia.log import sys_logger, user_logger
|
|
|
13
13
|
|
|
14
14
|
@click.group(name="runc")
|
|
15
15
|
def runc():
|
|
16
|
-
"""
|
|
16
|
+
"""Creating and running trainer packages locally"""
|
|
17
17
|
pass
|
|
18
18
|
|
|
19
19
|
|
|
@@ -90,10 +90,10 @@ def launch_local(cfg: Config, exec_cmd: str, dataset: str, image_name: str) -> N
|
|
|
90
90
|
@click.pass_obj
|
|
91
91
|
def build(cfg: Config, recipe_url: str, state_file: str, repo: str) -> None:
|
|
92
92
|
"""Build docker image with a given recipe."""
|
|
93
|
-
from hafnia.platform.builder import build_image,
|
|
93
|
+
from hafnia.platform.builder import build_image, prepare_trainer_package
|
|
94
94
|
|
|
95
95
|
with TemporaryDirectory() as temp_dir:
|
|
96
|
-
metadata =
|
|
96
|
+
metadata = prepare_trainer_package(recipe_url, Path(temp_dir), cfg.api_key)
|
|
97
97
|
build_image(metadata, repo, state_file=state_file)
|
|
98
98
|
|
|
99
99
|
|
|
@@ -109,7 +109,7 @@ def build_local(recipe: Path, state_file: str, repo: str) -> None:
|
|
|
109
109
|
import seedir
|
|
110
110
|
|
|
111
111
|
from hafnia.platform.builder import build_image
|
|
112
|
-
from hafnia.utils import
|
|
112
|
+
from hafnia.utils import filter_trainer_package_files
|
|
113
113
|
|
|
114
114
|
recipe = Path(recipe)
|
|
115
115
|
|
|
@@ -123,7 +123,7 @@ def build_local(recipe: Path, state_file: str, repo: str) -> None:
|
|
|
123
123
|
with zipfile.ZipFile(recipe.as_posix(), "r") as zip_ref:
|
|
124
124
|
zip_ref.extractall(recipe_dir)
|
|
125
125
|
elif recipe.is_dir():
|
|
126
|
-
for rf in
|
|
126
|
+
for rf in filter_trainer_package_files(recipe):
|
|
127
127
|
src_path = (recipe / rf).absolute()
|
|
128
128
|
target_path = recipe_dir / rf
|
|
129
129
|
target_path.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
|
|
6
|
+
import cli.consts as consts
|
|
7
|
+
from cli.config import Config
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.group(name="trainer")
|
|
11
|
+
def trainer_package() -> None:
|
|
12
|
+
"""Trainer package commands"""
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@trainer_package.command(name="ls")
|
|
17
|
+
@click.pass_obj
|
|
18
|
+
@click.option("-l", "--limit", type=int, default=None, help="Limit number of listed trainer packages.")
|
|
19
|
+
def cmd_list_trainer_packages(cfg: Config, limit: Optional[int]) -> None:
|
|
20
|
+
"""List available trainer packages on the platform"""
|
|
21
|
+
|
|
22
|
+
from hafnia.platform.trainer_package import get_trainer_packages, pretty_print_trainer_packages
|
|
23
|
+
|
|
24
|
+
endpoint = cfg.get_platform_endpoint("trainers")
|
|
25
|
+
trainers = get_trainer_packages(endpoint, cfg.api_key)
|
|
26
|
+
|
|
27
|
+
pretty_print_trainer_packages(trainers, limit=limit)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@trainer_package.command(name="create-zip")
|
|
31
|
+
@click.argument("source")
|
|
32
|
+
@click.option(
|
|
33
|
+
"--output",
|
|
34
|
+
type=click.Path(writable=True),
|
|
35
|
+
default="./trainer.zip",
|
|
36
|
+
show_default=True,
|
|
37
|
+
help="Output trainer package path.",
|
|
38
|
+
)
|
|
39
|
+
def cmd_create_trainer_package_zip(source: str, output: str) -> None:
|
|
40
|
+
"""Create Hafnia trainer package as zip-file from local path"""
|
|
41
|
+
|
|
42
|
+
from hafnia.utils import archive_dir
|
|
43
|
+
|
|
44
|
+
path_output_zip = Path(output)
|
|
45
|
+
if path_output_zip.suffix != ".zip":
|
|
46
|
+
raise click.ClickException(consts.ERROR_TRAINER_PACKAGE_FILE_FORMAT)
|
|
47
|
+
|
|
48
|
+
path_source = Path(source)
|
|
49
|
+
path_output_zip = archive_dir(path_source, path_output_zip)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@trainer_package.command(name="view-zip")
|
|
53
|
+
@click.option("--path", type=str, default="./trainer.zip", show_default=True, help="Path of trainer.zip.")
|
|
54
|
+
@click.option("--depth-limit", type=int, default=3, help="Limit the depth of the tree view.", show_default=True)
|
|
55
|
+
def cmd_view_trainer_package_zip(path: str, depth_limit: int) -> None:
|
|
56
|
+
"""View the content of a trainer package zip file."""
|
|
57
|
+
from hafnia.utils import show_trainer_package_content
|
|
58
|
+
|
|
59
|
+
path_trainer_package = Path(path)
|
|
60
|
+
if not path_trainer_package.exists():
|
|
61
|
+
raise click.ClickException(
|
|
62
|
+
f"Trainer package file '{path_trainer_package}' does not exist. Please provide a valid path. "
|
|
63
|
+
f"To create a trainer package, use the 'hafnia trainer create-zip' command."
|
|
64
|
+
)
|
|
65
|
+
show_trainer_package_content(path_trainer_package, depth_limit=depth_limit)
|
hafnia/__init__.py
CHANGED
hafnia/data/factory.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import os
|
|
2
1
|
from pathlib import Path
|
|
3
2
|
from typing import Any
|
|
4
3
|
|
|
@@ -16,7 +15,7 @@ def load_dataset(recipe: Any, force_redownload: bool = False) -> HafniaDataset:
|
|
|
16
15
|
|
|
17
16
|
def get_dataset_path(recipe: Any, force_redownload: bool = False) -> Path:
|
|
18
17
|
if utils.is_hafnia_cloud_job():
|
|
19
|
-
return
|
|
18
|
+
return utils.get_dataset_path_in_hafnia_cloud()
|
|
20
19
|
|
|
21
20
|
path_dataset = get_or_create_dataset_path_from_recipe(recipe, force_redownload=force_redownload)
|
|
22
21
|
|
|
@@ -110,15 +110,3 @@ def split_sizes_from_ratios(n_items: int, split_ratios: Dict[str, float]) -> Dic
|
|
|
110
110
|
raise ValueError("Something is wrong. The split sizes do not match the number of items.")
|
|
111
111
|
|
|
112
112
|
return split_sizes
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def select_evenly_across_list(lst: list, num_samples: int):
|
|
116
|
-
if num_samples >= len(lst):
|
|
117
|
-
return lst # No need to sample
|
|
118
|
-
step = (len(lst) - 1) / (num_samples - 1)
|
|
119
|
-
indices = [int(round(step * i)) for i in range(num_samples)] # noqa: RUF046
|
|
120
|
-
return [lst[index] for index in indices]
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def prefix_dict(d: dict, prefix: str) -> dict:
|
|
124
|
-
return {f"{prefix}.{k}": v for k, v in d.items()}
|