Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- .gitattributes +1 -0
- __init__.py +167 -0
- __pycache__/__init__.cpython-310.pyc +0 -0
- __pycache__/__init__.cpython-312.pyc +0 -0
- __pycache__/cli.cpython-312.pyc +0 -0
- __pycache__/context.cpython-312.pyc +0 -0
- __pycache__/deploy.cpython-310.pyc +0 -0
- __pycache__/deploy.cpython-312.pyc +0 -0
- __pycache__/dummy_commit_scheduler.cpython-310.pyc +0 -0
- __pycache__/dummy_commit_scheduler.cpython-312.pyc +0 -0
- __pycache__/run.cpython-310.pyc +0 -0
- __pycache__/run.cpython-312.pyc +0 -0
- __pycache__/sqlite_storage.cpython-310.pyc +0 -0
- __pycache__/sqlite_storage.cpython-312.pyc +0 -0
- __pycache__/storage.cpython-312.pyc +0 -0
- __pycache__/ui.cpython-310.pyc +0 -0
- __pycache__/ui.cpython-312.pyc +0 -0
- __pycache__/utils.cpython-310.pyc +0 -0
- __pycache__/utils.cpython-312.pyc +0 -0
- cli.py +26 -0
- deploy.py +69 -0
- dummy_commit_scheduler.py +12 -0
- run.py +31 -0
- sqlite_storage.py +191 -0
- trackio_logo.png +3 -0
- ui.py +327 -0
- utils.py +217 -0
- version.txt +1 -0
.gitattributes
CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
36 |
+
trackio_logo.png filter=lfs diff=lfs merge=lfs -text
|
__init__.py
ADDED
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import contextvars
|
2 |
+
import time
|
3 |
+
import webbrowser
|
4 |
+
from pathlib import Path
|
5 |
+
|
6 |
+
import huggingface_hub
|
7 |
+
from gradio_client import Client
|
8 |
+
from httpx import ReadTimeout
|
9 |
+
from huggingface_hub.errors import RepositoryNotFoundError
|
10 |
+
|
11 |
+
from trackio.deploy import deploy_as_space
|
12 |
+
from trackio.run import Run
|
13 |
+
from trackio.ui import demo
|
14 |
+
from trackio.utils import TRACKIO_DIR, TRACKIO_LOGO_PATH, block_except_in_notebook
|
15 |
+
|
16 |
+
__version__ = Path(__file__).parent.joinpath("version.txt").read_text().strip()
|
17 |
+
|
18 |
+
|
19 |
+
current_run: contextvars.ContextVar[Run | None] = contextvars.ContextVar(
|
20 |
+
"current_run", default=None
|
21 |
+
)
|
22 |
+
current_project: contextvars.ContextVar[str | None] = contextvars.ContextVar(
|
23 |
+
"current_project", default=None
|
24 |
+
)
|
25 |
+
current_server: contextvars.ContextVar[str | None] = contextvars.ContextVar(
|
26 |
+
"current_server", default=None
|
27 |
+
)
|
28 |
+
|
29 |
+
config = {}
|
30 |
+
SPACE_URL = "https://huggingface.co/spaces/{space_id}"
|
31 |
+
|
32 |
+
|
33 |
+
def init(
|
34 |
+
project: str,
|
35 |
+
name: str | None = None,
|
36 |
+
space_id: str | None = None,
|
37 |
+
dataset_id: str | None = None,
|
38 |
+
config: dict | None = None,
|
39 |
+
) -> Run:
|
40 |
+
"""
|
41 |
+
Creates a new Trackio project and returns a Run object.
|
42 |
+
|
43 |
+
Args:
|
44 |
+
project: The name of the project (can be an existing project to continue tracking or a new project to start tracking from scratch).
|
45 |
+
name: The name of the run (if not provided, a default name will be generated).
|
46 |
+
space_id: If provided, the project will be logged to a Hugging Face Space instead of a local directory. Should be a complete Space name like "username/reponame". If the Space does not exist, it will be created. If the Space already exists, the project will be logged to it.
|
47 |
+
dataset_id: If provided, a persistent Hugging Face Dataset will be created and the metrics will be synced to it every 5 minutes. Should be a complete Dataset name like "username/datasetname". If the Dataset does not exist, it will be created. If the Dataset already exists, the project will be appended to it.
|
48 |
+
config: A dictionary of configuration options. Provided for compatibility with wandb.init()
|
49 |
+
"""
|
50 |
+
if not current_server.get() and space_id is None:
|
51 |
+
_, url, _ = demo.launch(
|
52 |
+
show_api=False, inline=False, quiet=True, prevent_thread_lock=True
|
53 |
+
)
|
54 |
+
current_server.set(url)
|
55 |
+
else:
|
56 |
+
url = current_server.get()
|
57 |
+
|
58 |
+
if current_project.get() is None or current_project.get() != project:
|
59 |
+
print(f"* Trackio project initialized: {project}")
|
60 |
+
|
61 |
+
if space_id is None:
|
62 |
+
print(f"* Trackio metrics logged to: {TRACKIO_DIR}")
|
63 |
+
print(
|
64 |
+
f'\n* View dashboard by running in your terminal: trackio show --project "{project}"'
|
65 |
+
)
|
66 |
+
print(f'* or by running in Python: trackio.show(project="{project}")')
|
67 |
+
else:
|
68 |
+
create_space_if_not_exists(space_id, dataset_id)
|
69 |
+
print(
|
70 |
+
f"* View dashboard by going to: {SPACE_URL.format(space_id=space_id)}"
|
71 |
+
)
|
72 |
+
current_project.set(project)
|
73 |
+
|
74 |
+
space_or_url = space_id if space_id else url
|
75 |
+
client = Client(space_or_url, verbose=False)
|
76 |
+
run = Run(
|
77 |
+
project=project, client=client, name=name, config=config, dataset_id=dataset_id
|
78 |
+
)
|
79 |
+
current_run.set(run)
|
80 |
+
globals()["config"] = run.config
|
81 |
+
return run
|
82 |
+
|
83 |
+
|
84 |
+
def create_space_if_not_exists(
|
85 |
+
space_id: str,
|
86 |
+
dataset_id: str | None = None,
|
87 |
+
) -> None:
|
88 |
+
"""
|
89 |
+
Creates a new Hugging Face Space if it does not exist.
|
90 |
+
|
91 |
+
Args:
|
92 |
+
space_id: The ID of the Space to create.
|
93 |
+
dataset_id: The ID of the Dataset to create.
|
94 |
+
"""
|
95 |
+
if "/" not in space_id:
|
96 |
+
raise ValueError(
|
97 |
+
f"Invalid space ID: {space_id}. Must be in the format: username/reponame."
|
98 |
+
)
|
99 |
+
if dataset_id is not None and "/" not in dataset_id:
|
100 |
+
raise ValueError(
|
101 |
+
f"Invalid dataset ID: {dataset_id}. Must be in the format: username/datasetname."
|
102 |
+
)
|
103 |
+
try:
|
104 |
+
huggingface_hub.repo_info(space_id, repo_type="space")
|
105 |
+
print(f"* Found existing space: {SPACE_URL.format(space_id=space_id)}")
|
106 |
+
return
|
107 |
+
except RepositoryNotFoundError:
|
108 |
+
pass
|
109 |
+
|
110 |
+
print(f"* Creating new space: {SPACE_URL.format(space_id=space_id)}")
|
111 |
+
deploy_as_space(space_id, dataset_id)
|
112 |
+
|
113 |
+
client = None
|
114 |
+
for _ in range(30):
|
115 |
+
try:
|
116 |
+
client = Client(space_id, verbose=False)
|
117 |
+
if client:
|
118 |
+
break
|
119 |
+
except ReadTimeout:
|
120 |
+
print("* Space is not yet ready. Waiting 5 seconds...")
|
121 |
+
time.sleep(5)
|
122 |
+
except ValueError as e:
|
123 |
+
print(f"* Space gave error {e}. Trying again in 5 seconds...")
|
124 |
+
time.sleep(5)
|
125 |
+
|
126 |
+
|
127 |
+
def log(metrics: dict) -> None:
|
128 |
+
"""
|
129 |
+
Logs metrics to the current run.
|
130 |
+
|
131 |
+
Args:
|
132 |
+
metrics: A dictionary of metrics to log.
|
133 |
+
"""
|
134 |
+
if current_run.get() is None:
|
135 |
+
raise RuntimeError("Call trackio.init() before log().")
|
136 |
+
current_run.get().log(metrics)
|
137 |
+
|
138 |
+
|
139 |
+
def finish():
|
140 |
+
"""
|
141 |
+
Finishes the current run.
|
142 |
+
"""
|
143 |
+
if current_run.get() is None:
|
144 |
+
raise RuntimeError("Call trackio.init() before finish().")
|
145 |
+
current_run.get().finish()
|
146 |
+
|
147 |
+
|
148 |
+
def show(project: str | None = None):
|
149 |
+
"""
|
150 |
+
Launches the Trackio dashboard.
|
151 |
+
|
152 |
+
Args:
|
153 |
+
project: The name of the project whose runs to show. If not provided, all projects will be shown and the user can select one.
|
154 |
+
"""
|
155 |
+
_, url, share_url = demo.launch(
|
156 |
+
show_api=False,
|
157 |
+
quiet=True,
|
158 |
+
inline=False,
|
159 |
+
prevent_thread_lock=True,
|
160 |
+
favicon_path=TRACKIO_LOGO_PATH,
|
161 |
+
allowed_paths=[TRACKIO_LOGO_PATH],
|
162 |
+
)
|
163 |
+
base_url = share_url + "/" if share_url else url
|
164 |
+
dashboard_url = base_url + f"?project={project}" if project else base_url
|
165 |
+
print(f"* Trackio UI launched at: {dashboard_url}")
|
166 |
+
webbrowser.open(dashboard_url)
|
167 |
+
block_except_in_notebook()
|
__pycache__/__init__.cpython-310.pyc
ADDED
Binary file (4.96 kB). View file
|
|
__pycache__/__init__.cpython-312.pyc
ADDED
Binary file (7.73 kB). View file
|
|
__pycache__/cli.cpython-312.pyc
ADDED
Binary file (1.11 kB). View file
|
|
__pycache__/context.cpython-312.pyc
ADDED
Binary file (440 Bytes). View file
|
|
__pycache__/deploy.cpython-310.pyc
ADDED
Binary file (1.72 kB). View file
|
|
__pycache__/deploy.cpython-312.pyc
ADDED
Binary file (2.72 kB). View file
|
|
__pycache__/dummy_commit_scheduler.cpython-310.pyc
ADDED
Binary file (936 Bytes). View file
|
|
__pycache__/dummy_commit_scheduler.cpython-312.pyc
ADDED
Binary file (1.01 kB). View file
|
|
__pycache__/run.cpython-310.pyc
ADDED
Binary file (1.01 kB). View file
|
|
__pycache__/run.cpython-312.pyc
ADDED
Binary file (1.4 kB). View file
|
|
__pycache__/sqlite_storage.cpython-310.pyc
ADDED
Binary file (5.37 kB). View file
|
|
__pycache__/sqlite_storage.cpython-312.pyc
ADDED
Binary file (10.2 kB). View file
|
|
__pycache__/storage.cpython-312.pyc
ADDED
Binary file (4.6 kB). View file
|
|
__pycache__/ui.cpython-310.pyc
ADDED
Binary file (7.83 kB). View file
|
|
__pycache__/ui.cpython-312.pyc
ADDED
Binary file (12.4 kB). View file
|
|
__pycache__/utils.cpython-310.pyc
ADDED
Binary file (2.62 kB). View file
|
|
__pycache__/utils.cpython-312.pyc
ADDED
Binary file (3.19 kB). View file
|
|
cli.py
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import argparse
|
2 |
+
|
3 |
+
from trackio import show
|
4 |
+
|
5 |
+
|
6 |
+
def main():
|
7 |
+
parser = argparse.ArgumentParser(description="Trackio CLI")
|
8 |
+
subparsers = parser.add_subparsers(dest="command")
|
9 |
+
|
10 |
+
ui_parser = subparsers.add_parser(
|
11 |
+
"show", help="Show the Trackio dashboard UI for a project"
|
12 |
+
)
|
13 |
+
ui_parser.add_argument(
|
14 |
+
"--project", required=False, help="Project name to show in the dashboard"
|
15 |
+
)
|
16 |
+
|
17 |
+
args = parser.parse_args()
|
18 |
+
|
19 |
+
if args.command == "show":
|
20 |
+
show(args.project)
|
21 |
+
else:
|
22 |
+
parser.print_help()
|
23 |
+
|
24 |
+
|
25 |
+
if __name__ == "__main__":
|
26 |
+
main()
|
deploy.py
ADDED
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import io
|
2 |
+
import os
|
3 |
+
from importlib.resources import files
|
4 |
+
from pathlib import Path
|
5 |
+
|
6 |
+
import gradio
|
7 |
+
import huggingface_hub
|
8 |
+
|
9 |
+
|
10 |
+
def deploy_as_space(
|
11 |
+
title: str,
|
12 |
+
dataset_id: str | None = None,
|
13 |
+
):
|
14 |
+
if (
|
15 |
+
os.getenv("SYSTEM") == "spaces"
|
16 |
+
): # in case a repo with this function is uploaded to spaces
|
17 |
+
return
|
18 |
+
|
19 |
+
trackio_path = files("trackio")
|
20 |
+
|
21 |
+
hf_api = huggingface_hub.HfApi()
|
22 |
+
whoami = None
|
23 |
+
login = False
|
24 |
+
try:
|
25 |
+
whoami = hf_api.whoami()
|
26 |
+
if whoami["auth"]["accessToken"]["role"] != "write":
|
27 |
+
login = True
|
28 |
+
except OSError:
|
29 |
+
login = True
|
30 |
+
if login:
|
31 |
+
print("Need 'write' access token to create a Spaces repo.")
|
32 |
+
huggingface_hub.login(add_to_git_credential=False)
|
33 |
+
whoami = hf_api.whoami()
|
34 |
+
|
35 |
+
space_id = huggingface_hub.create_repo(
|
36 |
+
title,
|
37 |
+
space_sdk="gradio",
|
38 |
+
repo_type="space",
|
39 |
+
exist_ok=True,
|
40 |
+
).repo_id
|
41 |
+
assert space_id == title # not sure why these would differ
|
42 |
+
|
43 |
+
with open(Path(trackio_path, "README.md"), "r") as f:
|
44 |
+
readme_content = f.read()
|
45 |
+
readme_content = readme_content.replace("{GRADIO_VERSION}", gradio.__version__)
|
46 |
+
readme_buffer = io.BytesIO(readme_content.encode("utf-8"))
|
47 |
+
hf_api.upload_file(
|
48 |
+
path_or_fileobj=readme_buffer,
|
49 |
+
path_in_repo="README.md",
|
50 |
+
repo_id=space_id,
|
51 |
+
repo_type="space",
|
52 |
+
)
|
53 |
+
|
54 |
+
huggingface_hub.utils.disable_progress_bars()
|
55 |
+
hf_api.upload_folder(
|
56 |
+
repo_id=space_id,
|
57 |
+
repo_type="space",
|
58 |
+
folder_path=trackio_path,
|
59 |
+
ignore_patterns=["README.md"],
|
60 |
+
)
|
61 |
+
|
62 |
+
hf_token = huggingface_hub.utils.get_token()
|
63 |
+
if hf_token is not None:
|
64 |
+
huggingface_hub.add_space_secret(space_id, "HF_TOKEN", hf_token)
|
65 |
+
if dataset_id is not None:
|
66 |
+
huggingface_hub.add_space_variable(space_id, "TRACKIO_DATASET_ID", dataset_id)
|
67 |
+
# So that the dataset id is available to the sqlite_storage.py file
|
68 |
+
# if running locally as well.
|
69 |
+
os.environ["TRACKIO_DATASET_ID"] = dataset_id
|
dummy_commit_scheduler.py
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# A dummy object to fit the interface of huggingface_hub's CommitScheduler
|
2 |
+
class DummyCommitSchedulerLock:
|
3 |
+
def __enter__(self):
|
4 |
+
return None
|
5 |
+
|
6 |
+
def __exit__(self, exception_type, exception_value, exception_traceback):
|
7 |
+
pass
|
8 |
+
|
9 |
+
|
10 |
+
class DummyCommitScheduler:
|
11 |
+
def __init__(self):
|
12 |
+
self.lock = DummyCommitSchedulerLock()
|
run.py
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from gradio_client import Client
|
2 |
+
|
3 |
+
from trackio.utils import generate_readable_name
|
4 |
+
|
5 |
+
|
6 |
+
class Run:
|
7 |
+
def __init__(
|
8 |
+
self,
|
9 |
+
project: str,
|
10 |
+
client: Client,
|
11 |
+
name: str | None = None,
|
12 |
+
config: dict | None = None,
|
13 |
+
dataset_id: str | None = None,
|
14 |
+
):
|
15 |
+
self.project = project
|
16 |
+
self.client = client
|
17 |
+
self.name = name or generate_readable_name()
|
18 |
+
self.config = config or {}
|
19 |
+
self.dataset_id = dataset_id
|
20 |
+
|
21 |
+
def log(self, metrics: dict):
|
22 |
+
self.client.predict(
|
23 |
+
api_name="/log",
|
24 |
+
project=self.project,
|
25 |
+
run=self.name,
|
26 |
+
metrics=metrics,
|
27 |
+
dataset_id=self.dataset_id,
|
28 |
+
)
|
29 |
+
|
30 |
+
def finish(self):
|
31 |
+
pass
|
sqlite_storage.py
ADDED
@@ -0,0 +1,191 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import glob
|
2 |
+
import json
|
3 |
+
import os
|
4 |
+
import sqlite3
|
5 |
+
|
6 |
+
from huggingface_hub import CommitScheduler
|
7 |
+
|
8 |
+
try:
|
9 |
+
from trackio.dummy_commit_scheduler import DummyCommitScheduler
|
10 |
+
from trackio.utils import RESERVED_KEYS, TRACKIO_DIR
|
11 |
+
except: # noqa: E722
|
12 |
+
from dummy_commit_scheduler import DummyCommitScheduler
|
13 |
+
from utils import RESERVED_KEYS, TRACKIO_DIR
|
14 |
+
|
15 |
+
|
16 |
+
class SQLiteStorage:
|
17 |
+
def __init__(
|
18 |
+
self, project: str, name: str, config: dict, dataset_id: str | None = None
|
19 |
+
):
|
20 |
+
self.project = project
|
21 |
+
self.name = name
|
22 |
+
self.config = config
|
23 |
+
self.db_path = self._get_project_db_path(project)
|
24 |
+
self.dataset_id = dataset_id
|
25 |
+
self.scheduler = self._get_scheduler()
|
26 |
+
|
27 |
+
os.makedirs(TRACKIO_DIR, exist_ok=True)
|
28 |
+
|
29 |
+
self._init_db()
|
30 |
+
self._save_config()
|
31 |
+
|
32 |
+
@staticmethod
|
33 |
+
def _get_project_db_path(project: str) -> str:
|
34 |
+
"""Get the database path for a specific project."""
|
35 |
+
safe_project_name = "".join(
|
36 |
+
c for c in project if c.isalnum() or c in ("-", "_")
|
37 |
+
).rstrip()
|
38 |
+
if not safe_project_name:
|
39 |
+
safe_project_name = "default"
|
40 |
+
return os.path.join(TRACKIO_DIR, f"{safe_project_name}.db")
|
41 |
+
|
42 |
+
def _get_scheduler(self):
|
43 |
+
hf_token = os.environ.get(
|
44 |
+
"HF_TOKEN"
|
45 |
+
) # Get the token from the environment variable on Spaces
|
46 |
+
dataset_id = self.dataset_id or os.environ.get("TRACKIO_DATASET_ID")
|
47 |
+
if dataset_id is None:
|
48 |
+
scheduler = DummyCommitScheduler()
|
49 |
+
else:
|
50 |
+
scheduler = CommitScheduler(
|
51 |
+
repo_id=dataset_id,
|
52 |
+
repo_type="dataset",
|
53 |
+
folder_path=TRACKIO_DIR,
|
54 |
+
private=True,
|
55 |
+
squash_history=True,
|
56 |
+
token=hf_token,
|
57 |
+
)
|
58 |
+
return scheduler
|
59 |
+
|
60 |
+
def _init_db(self):
|
61 |
+
"""Initialize the SQLite database with required tables."""
|
62 |
+
with self.scheduler.lock:
|
63 |
+
with sqlite3.connect(self.db_path) as conn:
|
64 |
+
cursor = conn.cursor()
|
65 |
+
|
66 |
+
cursor.execute("""
|
67 |
+
CREATE TABLE IF NOT EXISTS metrics (
|
68 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
69 |
+
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
70 |
+
project_name TEXT NOT NULL,
|
71 |
+
run_name TEXT NOT NULL,
|
72 |
+
metrics TEXT NOT NULL
|
73 |
+
)
|
74 |
+
""")
|
75 |
+
|
76 |
+
cursor.execute("""
|
77 |
+
CREATE TABLE IF NOT EXISTS configs (
|
78 |
+
project_name TEXT NOT NULL,
|
79 |
+
run_name TEXT NOT NULL,
|
80 |
+
config TEXT NOT NULL,
|
81 |
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
82 |
+
PRIMARY KEY (project_name, run_name)
|
83 |
+
)
|
84 |
+
""")
|
85 |
+
|
86 |
+
conn.commit()
|
87 |
+
|
88 |
+
def _save_config(self):
|
89 |
+
"""Save the run configuration to the database."""
|
90 |
+
with self.scheduler.lock:
|
91 |
+
with sqlite3.connect(self.db_path) as conn:
|
92 |
+
cursor = conn.cursor()
|
93 |
+
cursor.execute(
|
94 |
+
"INSERT OR REPLACE INTO configs (project_name, run_name, config) VALUES (?, ?, ?)",
|
95 |
+
(self.project, self.name, json.dumps(self.config)),
|
96 |
+
)
|
97 |
+
conn.commit()
|
98 |
+
|
99 |
+
def log(self, metrics: dict):
|
100 |
+
"""Log metrics to the database."""
|
101 |
+
for k in metrics.keys():
|
102 |
+
if k in RESERVED_KEYS or k.startswith("__"):
|
103 |
+
raise ValueError(
|
104 |
+
f"Please do not use this reserved key as a metric: {k}"
|
105 |
+
)
|
106 |
+
|
107 |
+
with self.scheduler.lock:
|
108 |
+
with sqlite3.connect(self.db_path) as conn:
|
109 |
+
cursor = conn.cursor()
|
110 |
+
cursor.execute(
|
111 |
+
"""
|
112 |
+
INSERT INTO metrics
|
113 |
+
(project_name, run_name, metrics)
|
114 |
+
VALUES (?, ?, ?)
|
115 |
+
""",
|
116 |
+
(self.project, self.name, json.dumps(metrics)),
|
117 |
+
)
|
118 |
+
conn.commit()
|
119 |
+
|
120 |
+
@staticmethod
|
121 |
+
def get_metrics(project: str, run: str) -> list[dict]:
|
122 |
+
"""Retrieve metrics for a specific run."""
|
123 |
+
db_path = SQLiteStorage._get_project_db_path(project)
|
124 |
+
if not os.path.exists(db_path):
|
125 |
+
return []
|
126 |
+
|
127 |
+
with sqlite3.connect(db_path) as conn:
|
128 |
+
cursor = conn.cursor()
|
129 |
+
cursor.execute(
|
130 |
+
"""
|
131 |
+
SELECT timestamp, metrics
|
132 |
+
FROM metrics
|
133 |
+
WHERE project_name = ? AND run_name = ?
|
134 |
+
ORDER BY timestamp
|
135 |
+
""",
|
136 |
+
(project, run),
|
137 |
+
)
|
138 |
+
rows = cursor.fetchall()
|
139 |
+
|
140 |
+
results = []
|
141 |
+
for row in rows:
|
142 |
+
timestamp, metrics_json = row
|
143 |
+
metrics = json.loads(metrics_json)
|
144 |
+
metrics["timestamp"] = timestamp
|
145 |
+
results.append(metrics)
|
146 |
+
|
147 |
+
return results
|
148 |
+
|
149 |
+
@staticmethod
|
150 |
+
def get_projects() -> list[str]:
|
151 |
+
"""Get list of all projects by scanning database files."""
|
152 |
+
projects = []
|
153 |
+
if not os.path.exists(TRACKIO_DIR):
|
154 |
+
return projects
|
155 |
+
|
156 |
+
db_files = glob.glob(os.path.join(TRACKIO_DIR, "*.db"))
|
157 |
+
|
158 |
+
for db_file in db_files:
|
159 |
+
try:
|
160 |
+
with sqlite3.connect(db_file) as conn:
|
161 |
+
cursor = conn.cursor()
|
162 |
+
cursor.execute(
|
163 |
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='metrics'"
|
164 |
+
)
|
165 |
+
if cursor.fetchone():
|
166 |
+
cursor.execute("SELECT DISTINCT project_name FROM metrics")
|
167 |
+
project_names = [row[0] for row in cursor.fetchall()]
|
168 |
+
projects.extend(project_names)
|
169 |
+
except sqlite3.Error:
|
170 |
+
continue
|
171 |
+
|
172 |
+
return list(set(projects))
|
173 |
+
|
174 |
+
@staticmethod
|
175 |
+
def get_runs(project: str) -> list[str]:
|
176 |
+
"""Get list of all runs for a project."""
|
177 |
+
db_path = SQLiteStorage._get_project_db_path(project)
|
178 |
+
if not os.path.exists(db_path):
|
179 |
+
return []
|
180 |
+
|
181 |
+
with sqlite3.connect(db_path) as conn:
|
182 |
+
cursor = conn.cursor()
|
183 |
+
cursor.execute(
|
184 |
+
"SELECT DISTINCT run_name FROM metrics WHERE project_name = ?",
|
185 |
+
(project,),
|
186 |
+
)
|
187 |
+
return [row[0] for row in cursor.fetchall()]
|
188 |
+
|
189 |
+
def finish(self):
|
190 |
+
"""Cleanup when run is finished."""
|
191 |
+
pass
|
trackio_logo.png
ADDED
![]() |
Git LFS Details
|
ui.py
ADDED
@@ -0,0 +1,327 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from typing import Any
|
3 |
+
|
4 |
+
import gradio as gr
|
5 |
+
import pandas as pd
|
6 |
+
|
7 |
+
try:
|
8 |
+
from trackio.sqlite_storage import SQLiteStorage
|
9 |
+
from trackio.utils import RESERVED_KEYS, TRACKIO_LOGO_PATH
|
10 |
+
except: # noqa: E722
|
11 |
+
from sqlite_storage import SQLiteStorage
|
12 |
+
from utils import RESERVED_KEYS, TRACKIO_LOGO_PATH
|
13 |
+
|
14 |
+
css = """
|
15 |
+
#run-cb .wrap {
|
16 |
+
gap: 2px;
|
17 |
+
}
|
18 |
+
#run-cb .wrap label {
|
19 |
+
line-height: 1;
|
20 |
+
padding: 6px;
|
21 |
+
}
|
22 |
+
"""
|
23 |
+
|
24 |
+
COLOR_PALETTE = [
|
25 |
+
"#3B82F6",
|
26 |
+
"#EF4444",
|
27 |
+
"#10B981",
|
28 |
+
"#F59E0B",
|
29 |
+
"#8B5CF6",
|
30 |
+
"#EC4899",
|
31 |
+
"#06B6D4",
|
32 |
+
"#84CC16",
|
33 |
+
"#F97316",
|
34 |
+
"#6366F1",
|
35 |
+
]
|
36 |
+
|
37 |
+
|
38 |
+
def get_color_mapping(runs: list[str], smoothing: bool) -> dict[str, str]:
|
39 |
+
"""Generate color mapping for runs, with transparency for original data when smoothing is enabled."""
|
40 |
+
color_map = {}
|
41 |
+
|
42 |
+
for i, run in enumerate(runs):
|
43 |
+
base_color = COLOR_PALETTE[i % len(COLOR_PALETTE)]
|
44 |
+
|
45 |
+
if smoothing:
|
46 |
+
color_map[f"{run}_smoothed"] = base_color
|
47 |
+
color_map[f"{run}_original"] = base_color + "4D"
|
48 |
+
else:
|
49 |
+
color_map[run] = base_color
|
50 |
+
|
51 |
+
return color_map
|
52 |
+
|
53 |
+
|
54 |
+
def get_projects(request: gr.Request):
|
55 |
+
dataset_id = os.environ.get("TRACKIO_DATASET_ID")
|
56 |
+
projects = SQLiteStorage.get_projects()
|
57 |
+
if project := request.query_params.get("project"):
|
58 |
+
interactive = False
|
59 |
+
else:
|
60 |
+
interactive = True
|
61 |
+
project = projects[0] if projects else None
|
62 |
+
return gr.Dropdown(
|
63 |
+
label="Project",
|
64 |
+
choices=projects,
|
65 |
+
value=project,
|
66 |
+
allow_custom_value=True,
|
67 |
+
interactive=interactive,
|
68 |
+
info=f"↻ Synced to <a href='https://huggingface.co/{dataset_id}' target='_blank'>{dataset_id}</a> every 5 min"
|
69 |
+
if dataset_id
|
70 |
+
else None,
|
71 |
+
)
|
72 |
+
|
73 |
+
|
74 |
+
def get_runs(project):
|
75 |
+
if not project:
|
76 |
+
return []
|
77 |
+
return SQLiteStorage.get_runs(project)
|
78 |
+
|
79 |
+
|
80 |
+
def load_run_data(project: str | None, run: str | None, smoothing: bool):
|
81 |
+
if not project or not run:
|
82 |
+
return None
|
83 |
+
metrics = SQLiteStorage.get_metrics(project, run)
|
84 |
+
if not metrics:
|
85 |
+
return None
|
86 |
+
df = pd.DataFrame(metrics)
|
87 |
+
|
88 |
+
if "step" not in df.columns:
|
89 |
+
df["step"] = range(len(df))
|
90 |
+
|
91 |
+
if smoothing:
|
92 |
+
numeric_cols = df.select_dtypes(include="number").columns
|
93 |
+
numeric_cols = [c for c in numeric_cols if c not in RESERVED_KEYS]
|
94 |
+
|
95 |
+
df_original = df.copy()
|
96 |
+
df_original["run"] = f"{run}_original"
|
97 |
+
df_original["data_type"] = "original"
|
98 |
+
|
99 |
+
df_smoothed = df.copy()
|
100 |
+
df_smoothed[numeric_cols] = df_smoothed[numeric_cols].ewm(alpha=0.1).mean()
|
101 |
+
df_smoothed["run"] = f"{run}_smoothed"
|
102 |
+
df_smoothed["data_type"] = "smoothed"
|
103 |
+
|
104 |
+
combined_df = pd.concat([df_original, df_smoothed], ignore_index=True)
|
105 |
+
return combined_df
|
106 |
+
else:
|
107 |
+
df["run"] = run
|
108 |
+
df["data_type"] = "original"
|
109 |
+
return df
|
110 |
+
|
111 |
+
|
112 |
+
def update_runs(project, filter_text, user_interacted_with_runs=False):
|
113 |
+
if project is None:
|
114 |
+
runs = []
|
115 |
+
num_runs = 0
|
116 |
+
else:
|
117 |
+
runs = get_runs(project)
|
118 |
+
num_runs = len(runs)
|
119 |
+
if filter_text:
|
120 |
+
runs = [r for r in runs if filter_text in r]
|
121 |
+
if not user_interacted_with_runs:
|
122 |
+
return gr.CheckboxGroup(
|
123 |
+
choices=runs, value=[runs[0]] if runs else []
|
124 |
+
), gr.Textbox(label=f"Runs ({num_runs})")
|
125 |
+
else:
|
126 |
+
return gr.CheckboxGroup(choices=runs), gr.Textbox(label=f"Runs ({num_runs})")
|
127 |
+
|
128 |
+
|
129 |
+
def filter_runs(project, filter_text):
|
130 |
+
runs = get_runs(project)
|
131 |
+
runs = [r for r in runs if filter_text in r]
|
132 |
+
return gr.CheckboxGroup(choices=runs, value=runs)
|
133 |
+
|
134 |
+
|
135 |
+
def toggle_timer(cb_value):
|
136 |
+
if cb_value:
|
137 |
+
return gr.Timer(active=True)
|
138 |
+
else:
|
139 |
+
return gr.Timer(active=False)
|
140 |
+
|
141 |
+
|
142 |
+
def log(project: str, run: str, metrics: dict[str, Any], dataset_id: str) -> None:
|
143 |
+
# Note: the type hint for dataset_id should be str | None but gr.api
|
144 |
+
# doesn't support that, see: https://github.com/gradio-app/gradio/issues/11175#issuecomment-2920203317
|
145 |
+
storage = SQLiteStorage(project, run, {}, dataset_id=dataset_id)
|
146 |
+
storage.log(metrics)
|
147 |
+
|
148 |
+
|
149 |
+
def sort_metrics_by_prefix(metrics: list[str]) -> list[str]:
|
150 |
+
"""
|
151 |
+
Sort metrics by grouping prefixes together.
|
152 |
+
Metrics without prefixes come first, then grouped by prefix.
|
153 |
+
|
154 |
+
Example:
|
155 |
+
Input: ["train/loss", "loss", "train/acc", "val/loss"]
|
156 |
+
Output: ["loss", "train/acc", "train/loss", "val/loss"]
|
157 |
+
"""
|
158 |
+
no_prefix = []
|
159 |
+
with_prefix = []
|
160 |
+
|
161 |
+
for metric in metrics:
|
162 |
+
if "/" in metric:
|
163 |
+
with_prefix.append(metric)
|
164 |
+
else:
|
165 |
+
no_prefix.append(metric)
|
166 |
+
|
167 |
+
no_prefix.sort()
|
168 |
+
|
169 |
+
prefix_groups = {}
|
170 |
+
for metric in with_prefix:
|
171 |
+
prefix = metric.split("/")[0]
|
172 |
+
if prefix not in prefix_groups:
|
173 |
+
prefix_groups[prefix] = []
|
174 |
+
prefix_groups[prefix].append(metric)
|
175 |
+
|
176 |
+
sorted_with_prefix = []
|
177 |
+
for prefix in sorted(prefix_groups.keys()):
|
178 |
+
sorted_with_prefix.extend(sorted(prefix_groups[prefix]))
|
179 |
+
|
180 |
+
return no_prefix + sorted_with_prefix
|
181 |
+
|
182 |
+
|
183 |
+
def configure(request: gr.Request):
|
184 |
+
if metrics := request.query_params.get("metrics"):
|
185 |
+
return metrics.split(",")
|
186 |
+
else:
|
187 |
+
return []
|
188 |
+
|
189 |
+
|
190 |
+
with gr.Blocks(theme="citrus", title="Trackio Dashboard", css=css) as demo:
|
191 |
+
with gr.Sidebar() as sidebar:
|
192 |
+
gr.Markdown(
|
193 |
+
f"<div style='display: flex; align-items: center; gap: 8px;'><img src='/gradio_api/file={TRACKIO_LOGO_PATH}' width='32' height='32'><span style='font-size: 2em; font-weight: bold;'>Trackio</span></div>"
|
194 |
+
)
|
195 |
+
project_dd = gr.Dropdown(label="Project")
|
196 |
+
run_tb = gr.Textbox(label="Runs", placeholder="Type to filter...")
|
197 |
+
run_cb = gr.CheckboxGroup(
|
198 |
+
label="Runs", choices=[], interactive=True, elem_id="run-cb"
|
199 |
+
)
|
200 |
+
|
201 |
+
with gr.Sidebar(position="right", open=False) as settings_sidebar:
|
202 |
+
gr.Markdown("### ⚙️ Settings")
|
203 |
+
realtime_cb = gr.Checkbox(label="Refresh realtime", value=True)
|
204 |
+
smoothing_cb = gr.Checkbox(label="Smoothing", value=True)
|
205 |
+
|
206 |
+
timer = gr.Timer(value=1)
|
207 |
+
metrics_subset = gr.State([])
|
208 |
+
user_interacted_with_run_cb = gr.State(False)
|
209 |
+
|
210 |
+
gr.on(
|
211 |
+
[demo.load],
|
212 |
+
fn=configure,
|
213 |
+
outputs=metrics_subset,
|
214 |
+
)
|
215 |
+
gr.on(
|
216 |
+
[demo.load],
|
217 |
+
fn=get_projects,
|
218 |
+
outputs=project_dd,
|
219 |
+
show_progress="hidden",
|
220 |
+
)
|
221 |
+
gr.on(
|
222 |
+
[timer.tick],
|
223 |
+
fn=update_runs,
|
224 |
+
inputs=[project_dd, run_tb, user_interacted_with_run_cb],
|
225 |
+
outputs=[run_cb, run_tb],
|
226 |
+
show_progress="hidden",
|
227 |
+
)
|
228 |
+
gr.on(
|
229 |
+
[demo.load, project_dd.change],
|
230 |
+
fn=update_runs,
|
231 |
+
inputs=[project_dd, run_tb],
|
232 |
+
outputs=[run_cb, run_tb],
|
233 |
+
show_progress="hidden",
|
234 |
+
)
|
235 |
+
|
236 |
+
realtime_cb.change(
|
237 |
+
fn=toggle_timer,
|
238 |
+
inputs=realtime_cb,
|
239 |
+
outputs=timer,
|
240 |
+
api_name="toggle_timer",
|
241 |
+
)
|
242 |
+
run_cb.input(
|
243 |
+
fn=lambda: True,
|
244 |
+
outputs=user_interacted_with_run_cb,
|
245 |
+
)
|
246 |
+
run_tb.input(
|
247 |
+
fn=filter_runs,
|
248 |
+
inputs=[project_dd, run_tb],
|
249 |
+
outputs=run_cb,
|
250 |
+
)
|
251 |
+
|
252 |
+
gr.api(
|
253 |
+
fn=log,
|
254 |
+
api_name="log",
|
255 |
+
)
|
256 |
+
|
257 |
+
x_lim = gr.State(None)
|
258 |
+
|
259 |
+
def update_x_lim(select_data: gr.SelectData):
|
260 |
+
return select_data.index
|
261 |
+
|
262 |
+
@gr.render(
|
263 |
+
triggers=[
|
264 |
+
demo.load,
|
265 |
+
run_cb.change,
|
266 |
+
timer.tick,
|
267 |
+
smoothing_cb.change,
|
268 |
+
x_lim.change,
|
269 |
+
],
|
270 |
+
inputs=[project_dd, run_cb, smoothing_cb, metrics_subset, x_lim],
|
271 |
+
)
|
272 |
+
def update_dashboard(project, runs, smoothing, metrics_subset, x_lim_value):
|
273 |
+
dfs = []
|
274 |
+
original_runs = runs.copy()
|
275 |
+
|
276 |
+
for run in runs:
|
277 |
+
df = load_run_data(project, run, smoothing)
|
278 |
+
if df is not None:
|
279 |
+
dfs.append(df)
|
280 |
+
|
281 |
+
if dfs:
|
282 |
+
master_df = pd.concat(dfs, ignore_index=True)
|
283 |
+
else:
|
284 |
+
master_df = pd.DataFrame()
|
285 |
+
|
286 |
+
if master_df.empty:
|
287 |
+
return
|
288 |
+
|
289 |
+
numeric_cols = master_df.select_dtypes(include="number").columns
|
290 |
+
numeric_cols = [
|
291 |
+
c for c in numeric_cols if c not in RESERVED_KEYS and c != "step"
|
292 |
+
]
|
293 |
+
if metrics_subset:
|
294 |
+
numeric_cols = [c for c in numeric_cols if c in metrics_subset]
|
295 |
+
|
296 |
+
numeric_cols = sort_metrics_by_prefix(list(numeric_cols))
|
297 |
+
color_map = get_color_mapping(original_runs, smoothing)
|
298 |
+
|
299 |
+
with gr.Row(key="row"):
|
300 |
+
for metric_idx, metric_name in enumerate(numeric_cols):
|
301 |
+
metric_df = master_df.dropna(subset=[metric_name])
|
302 |
+
if not metric_df.empty:
|
303 |
+
plot = gr.LinePlot(
|
304 |
+
metric_df,
|
305 |
+
x="step",
|
306 |
+
y=metric_name,
|
307 |
+
color="run" if "run" in metric_df.columns else None,
|
308 |
+
color_map=color_map,
|
309 |
+
title=metric_name,
|
310 |
+
key=f"plot-{metric_idx}",
|
311 |
+
preserved_by_key=None,
|
312 |
+
x_lim=x_lim_value,
|
313 |
+
y_lim=[
|
314 |
+
metric_df[metric_name].min(),
|
315 |
+
metric_df[metric_name].max(),
|
316 |
+
],
|
317 |
+
show_fullscreen_button=True,
|
318 |
+
min_width=400,
|
319 |
+
)
|
320 |
+
plot.select(update_x_lim, outputs=x_lim, key=f"select-{metric_idx}")
|
321 |
+
plot.double_click(
|
322 |
+
lambda: None, outputs=x_lim, key=f"double-{metric_idx}"
|
323 |
+
)
|
324 |
+
|
325 |
+
|
326 |
+
if __name__ == "__main__":
|
327 |
+
demo.launch(allowed_paths=[TRACKIO_LOGO_PATH], show_api=False)
|
utils.py
ADDED
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import random
|
3 |
+
import sys
|
4 |
+
import time
|
5 |
+
from pathlib import Path
|
6 |
+
|
7 |
+
from huggingface_hub.constants import HF_HOME
|
8 |
+
|
9 |
+
RESERVED_KEYS = ["project", "run", "timestamp", "step"]
|
10 |
+
TRACKIO_DIR = os.path.join(HF_HOME, "trackio")
|
11 |
+
|
12 |
+
TRACKIO_LOGO_PATH = str(Path(__file__).parent.joinpath("trackio_logo.png"))
|
13 |
+
|
14 |
+
|
15 |
+
def generate_readable_name():
|
16 |
+
"""
|
17 |
+
Generates a random, readable name like "dainty-sunset-1"
|
18 |
+
"""
|
19 |
+
adjectives = [
|
20 |
+
"dainty",
|
21 |
+
"brave",
|
22 |
+
"calm",
|
23 |
+
"eager",
|
24 |
+
"fancy",
|
25 |
+
"gentle",
|
26 |
+
"happy",
|
27 |
+
"jolly",
|
28 |
+
"kind",
|
29 |
+
"lively",
|
30 |
+
"merry",
|
31 |
+
"nice",
|
32 |
+
"proud",
|
33 |
+
"quick",
|
34 |
+
"silly",
|
35 |
+
"tidy",
|
36 |
+
"witty",
|
37 |
+
"zealous",
|
38 |
+
"bright",
|
39 |
+
"shy",
|
40 |
+
"bold",
|
41 |
+
"clever",
|
42 |
+
"daring",
|
43 |
+
"elegant",
|
44 |
+
"faithful",
|
45 |
+
"graceful",
|
46 |
+
"honest",
|
47 |
+
"inventive",
|
48 |
+
"jovial",
|
49 |
+
"keen",
|
50 |
+
"lucky",
|
51 |
+
"modest",
|
52 |
+
"noble",
|
53 |
+
"optimistic",
|
54 |
+
"patient",
|
55 |
+
"quirky",
|
56 |
+
"resourceful",
|
57 |
+
"sincere",
|
58 |
+
"thoughtful",
|
59 |
+
"upbeat",
|
60 |
+
"valiant",
|
61 |
+
"warm",
|
62 |
+
"youthful",
|
63 |
+
"zesty",
|
64 |
+
"adventurous",
|
65 |
+
"breezy",
|
66 |
+
"cheerful",
|
67 |
+
"delightful",
|
68 |
+
"energetic",
|
69 |
+
"fearless",
|
70 |
+
"glad",
|
71 |
+
"hopeful",
|
72 |
+
"imaginative",
|
73 |
+
"joyful",
|
74 |
+
"kindly",
|
75 |
+
"luminous",
|
76 |
+
"mysterious",
|
77 |
+
"neat",
|
78 |
+
"outgoing",
|
79 |
+
"playful",
|
80 |
+
"radiant",
|
81 |
+
"spirited",
|
82 |
+
"tranquil",
|
83 |
+
"unique",
|
84 |
+
"vivid",
|
85 |
+
"wise",
|
86 |
+
"zany",
|
87 |
+
"artful",
|
88 |
+
"bubbly",
|
89 |
+
"charming",
|
90 |
+
"dazzling",
|
91 |
+
"earnest",
|
92 |
+
"festive",
|
93 |
+
"gentlemanly",
|
94 |
+
"hearty",
|
95 |
+
"intrepid",
|
96 |
+
"jubilant",
|
97 |
+
"knightly",
|
98 |
+
"lively",
|
99 |
+
"magnetic",
|
100 |
+
"nimble",
|
101 |
+
"orderly",
|
102 |
+
"peaceful",
|
103 |
+
"quick-witted",
|
104 |
+
"robust",
|
105 |
+
"sturdy",
|
106 |
+
"trusty",
|
107 |
+
"upstanding",
|
108 |
+
"vibrant",
|
109 |
+
"whimsical",
|
110 |
+
]
|
111 |
+
nouns = [
|
112 |
+
"sunset",
|
113 |
+
"forest",
|
114 |
+
"river",
|
115 |
+
"mountain",
|
116 |
+
"breeze",
|
117 |
+
"meadow",
|
118 |
+
"ocean",
|
119 |
+
"valley",
|
120 |
+
"sky",
|
121 |
+
"field",
|
122 |
+
"cloud",
|
123 |
+
"star",
|
124 |
+
"rain",
|
125 |
+
"leaf",
|
126 |
+
"stone",
|
127 |
+
"flower",
|
128 |
+
"bird",
|
129 |
+
"tree",
|
130 |
+
"wave",
|
131 |
+
"trail",
|
132 |
+
"island",
|
133 |
+
"desert",
|
134 |
+
"hill",
|
135 |
+
"lake",
|
136 |
+
"pond",
|
137 |
+
"grove",
|
138 |
+
"canyon",
|
139 |
+
"reef",
|
140 |
+
"bay",
|
141 |
+
"peak",
|
142 |
+
"glade",
|
143 |
+
"marsh",
|
144 |
+
"cliff",
|
145 |
+
"dune",
|
146 |
+
"spring",
|
147 |
+
"brook",
|
148 |
+
"cave",
|
149 |
+
"plain",
|
150 |
+
"ridge",
|
151 |
+
"wood",
|
152 |
+
"blossom",
|
153 |
+
"petal",
|
154 |
+
"root",
|
155 |
+
"branch",
|
156 |
+
"seed",
|
157 |
+
"acorn",
|
158 |
+
"pine",
|
159 |
+
"willow",
|
160 |
+
"cedar",
|
161 |
+
"elm",
|
162 |
+
"falcon",
|
163 |
+
"eagle",
|
164 |
+
"sparrow",
|
165 |
+
"robin",
|
166 |
+
"owl",
|
167 |
+
"finch",
|
168 |
+
"heron",
|
169 |
+
"crane",
|
170 |
+
"duck",
|
171 |
+
"swan",
|
172 |
+
"fox",
|
173 |
+
"wolf",
|
174 |
+
"bear",
|
175 |
+
"deer",
|
176 |
+
"moose",
|
177 |
+
"otter",
|
178 |
+
"beaver",
|
179 |
+
"lynx",
|
180 |
+
"hare",
|
181 |
+
"badger",
|
182 |
+
"butterfly",
|
183 |
+
"bee",
|
184 |
+
"ant",
|
185 |
+
"beetle",
|
186 |
+
"dragonfly",
|
187 |
+
"firefly",
|
188 |
+
"ladybug",
|
189 |
+
"moth",
|
190 |
+
"spider",
|
191 |
+
"worm",
|
192 |
+
"coral",
|
193 |
+
"kelp",
|
194 |
+
"shell",
|
195 |
+
"pebble",
|
196 |
+
"boulder",
|
197 |
+
"cobble",
|
198 |
+
"sand",
|
199 |
+
"wavelet",
|
200 |
+
"tide",
|
201 |
+
"current",
|
202 |
+
]
|
203 |
+
adjective = random.choice(adjectives)
|
204 |
+
noun = random.choice(nouns)
|
205 |
+
number = random.randint(1, 99)
|
206 |
+
return f"{adjective}-{noun}-{number}"
|
207 |
+
|
208 |
+
|
209 |
+
def block_except_in_notebook():
|
210 |
+
in_notebook = bool(getattr(sys, "ps1", sys.flags.interactive))
|
211 |
+
if in_notebook:
|
212 |
+
return
|
213 |
+
try:
|
214 |
+
while True:
|
215 |
+
time.sleep(0.1)
|
216 |
+
except (KeyboardInterrupt, OSError):
|
217 |
+
print("Keyboard interruption in main thread... closing dashboard.")
|
version.txt
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
0.0.10
|