freesolo-flash-dev 0.2.25__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.
- flash/__init__.py +29 -0
- flash/_channel.py +23 -0
- flash/_fileio.py +35 -0
- flash/_logging.py +49 -0
- flash/_update_check.py +266 -0
- flash/catalog.py +253 -0
- flash/cli/__init__.py +1 -0
- flash/cli/main/__init__.py +227 -0
- flash/cli/main/__main__.py +6 -0
- flash/cli/main/commands.py +636 -0
- flash/cli/main/envpush.py +317 -0
- flash/cli/main/render.py +599 -0
- flash/cli/main/training_doc.py +455 -0
- flash/client/__init__.py +14 -0
- flash/client/config.py +70 -0
- flash/client/http.py +372 -0
- flash/client/runtime_secrets.py +69 -0
- flash/client/specs.py +20 -0
- flash/cost/__init__.py +16 -0
- flash/cost/analytical.py +175 -0
- flash/cost/facts.py +114 -0
- flash/cost/spec.py +113 -0
- flash/cost/types.py +158 -0
- flash/engine/__init__.py +6 -0
- flash/engine/accounting.py +36 -0
- flash/engine/chalk_kernels.py +116 -0
- flash/engine/multiturn_rollout.py +780 -0
- flash/engine/recipe.py +86 -0
- flash/engine/vram.py +603 -0
- flash/engine/worker/__init__.py +2916 -0
- flash/engine/worker/__main__.py +4 -0
- flash/engine/worker/kernel_warmup.py +400 -0
- flash/engine/worker/lora.py +796 -0
- flash/engine/worker/packing.py +366 -0
- flash/engine/worker/perf.py +1048 -0
- flash/envs/__init__.py +10 -0
- flash/envs/adapter/__init__.py +883 -0
- flash/envs/adapter/rubric.py +222 -0
- flash/envs/base.py +52 -0
- flash/envs/registry.py +62 -0
- flash/mcp/__init__.py +1 -0
- flash/mcp/server.py +85 -0
- flash/providers/__init__.py +59 -0
- flash/providers/_auth.py +24 -0
- flash/providers/_http.py +230 -0
- flash/providers/_instance.py +416 -0
- flash/providers/_instance_bootstrap.py +517 -0
- flash/providers/_poll.py +311 -0
- flash/providers/allocator.py +193 -0
- flash/providers/base.py +431 -0
- flash/providers/hyperstack/__init__.py +127 -0
- flash/providers/hyperstack/api.py +522 -0
- flash/providers/hyperstack/auth.py +17 -0
- flash/providers/hyperstack/gpus.py +29 -0
- flash/providers/hyperstack/jobs/__init__.py +632 -0
- flash/providers/hyperstack/jobs/builders.py +122 -0
- flash/providers/hyperstack/preflight.py +23 -0
- flash/providers/hyperstack/pricing.py +26 -0
- flash/providers/hyperstack/train.py +25 -0
- flash/providers/lambdalabs/__init__.py +139 -0
- flash/providers/lambdalabs/api.py +261 -0
- flash/providers/lambdalabs/auth.py +18 -0
- flash/providers/lambdalabs/gpus.py +29 -0
- flash/providers/lambdalabs/jobs/__init__.py +724 -0
- flash/providers/lambdalabs/jobs/builders.py +118 -0
- flash/providers/lambdalabs/preflight.py +27 -0
- flash/providers/lambdalabs/pricing.py +51 -0
- flash/providers/lambdalabs/train.py +27 -0
- flash/providers/preflight.py +55 -0
- flash/providers/realized.py +80 -0
- flash/providers/runpod/__init__.py +130 -0
- flash/providers/runpod/api.py +186 -0
- flash/providers/runpod/auth.py +37 -0
- flash/providers/runpod/cost.py +57 -0
- flash/providers/runpod/gpus.py +46 -0
- flash/providers/runpod/jobs.py +956 -0
- flash/providers/runpod/keys.py +139 -0
- flash/providers/runpod/preflight.py +30 -0
- flash/providers/runpod/preload.py +915 -0
- flash/providers/runpod/pricing.py +18 -0
- flash/providers/runpod/slots.py +79 -0
- flash/providers/runpod/train/__init__.py +150 -0
- flash/providers/runpod/train/deps.py +395 -0
- flash/providers/runpod/train/endpoints.py +820 -0
- flash/py.typed +0 -0
- flash/runner/__init__.py +686 -0
- flash/runner/checkpoints.py +82 -0
- flash/runner/deploy.py +422 -0
- flash/runner/lifecycle.py +672 -0
- flash/schema/__init__.py +375 -0
- flash/schema/fields.py +331 -0
- flash/serve/__init__.py +1 -0
- flash/serve/deploy.py +326 -0
- flash/serve/pricing.py +60 -0
- flash/server/__init__.py +1 -0
- flash/server/__main__.py +20 -0
- flash/server/app.py +961 -0
- flash/server/auth.py +263 -0
- flash/server/billing.py +124 -0
- flash/server/checkpoints.py +110 -0
- flash/server/db.py +160 -0
- flash/server/environment_registry.py +102 -0
- flash/server/envs.py +360 -0
- flash/server/reconcile.py +163 -0
- flash/server/run_registry.py +150 -0
- flash/spec.py +333 -0
- freesolo_flash_dev-0.2.25.dist-info/METADATA +192 -0
- freesolo_flash_dev-0.2.25.dist-info/RECORD +111 -0
- freesolo_flash_dev-0.2.25.dist-info/WHEEL +4 -0
- freesolo_flash_dev-0.2.25.dist-info/entry_points.txt +3 -0
- freesolo_flash_dev-0.2.25.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
"""Environment publish/install machinery for the `flash env` subcommands.
|
|
2
|
+
|
|
3
|
+
`flash env install` records a Freesolo environment id locally;
|
|
4
|
+
`flash env push` packages a local Freesolo environment and uploads it through the
|
|
5
|
+
managed Flash control plane.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
from . import render
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from flash.client.http import ProgressCallback
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def cmd_env_install(args) -> int:
|
|
21
|
+
from flash.envs.adapter import is_freesolo_environment_id
|
|
22
|
+
from flash.envs.registry import INSTALLED_MANIFEST, record_installed_env
|
|
23
|
+
|
|
24
|
+
env_id = args.env_id
|
|
25
|
+
if not is_freesolo_environment_id(env_id):
|
|
26
|
+
print(
|
|
27
|
+
f'env id must be a Freesolo environment id, e.g. "your-name/your-env" (got {env_id!r})',
|
|
28
|
+
file=sys.stderr,
|
|
29
|
+
)
|
|
30
|
+
return 1
|
|
31
|
+
record_installed_env(env_id, package="freesolo")
|
|
32
|
+
if render.styled():
|
|
33
|
+
print(render.env_installed(env_id, str(INSTALLED_MANIFEST)))
|
|
34
|
+
else:
|
|
35
|
+
print(f"installed {env_id}; recorded in {INSTALLED_MANIFEST}")
|
|
36
|
+
print(f'use it via: [environment]\\nid = "{env_id}"')
|
|
37
|
+
return 0
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
_ENV_ENTRYPOINT = "environment.py"
|
|
41
|
+
_ENV_PUSH_IGNORED_NAMES = frozenset(
|
|
42
|
+
{
|
|
43
|
+
".prime",
|
|
44
|
+
".git",
|
|
45
|
+
".github",
|
|
46
|
+
"__pycache__",
|
|
47
|
+
".venv",
|
|
48
|
+
".mypy_cache",
|
|
49
|
+
".pytest_cache",
|
|
50
|
+
"pyproject.toml",
|
|
51
|
+
"source",
|
|
52
|
+
}
|
|
53
|
+
)
|
|
54
|
+
_ENV_PUSH_SIDECAR_DIRS = frozenset({"datasets"})
|
|
55
|
+
_ENV_PUSH_SIDECAR_SUFFIXES = frozenset(
|
|
56
|
+
{
|
|
57
|
+
".csv",
|
|
58
|
+
".json",
|
|
59
|
+
".jsonl",
|
|
60
|
+
".parquet",
|
|
61
|
+
".tsv",
|
|
62
|
+
".txt",
|
|
63
|
+
".yaml",
|
|
64
|
+
".yml",
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _normalize_env_name(raw: str) -> str | None:
|
|
70
|
+
import re
|
|
71
|
+
|
|
72
|
+
name = re.sub(r"[^a-z0-9]+", "-", raw.lower()).strip("-")
|
|
73
|
+
return name or None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _with_syspath_bootstrap(env_source: str) -> str:
|
|
77
|
+
"""Prepend a sys.path bootstrap so a published env can resolve shipped sibling helpers."""
|
|
78
|
+
bootstrap = (
|
|
79
|
+
"import os as _flash_os, sys as _flash_sys\n"
|
|
80
|
+
"_flash_sys.path.insert(0, _flash_os.path.dirname(__file__))\n"
|
|
81
|
+
)
|
|
82
|
+
import ast
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
tree = ast.parse(env_source)
|
|
86
|
+
except SyntaxError:
|
|
87
|
+
return bootstrap + env_source
|
|
88
|
+
insert_after = 0
|
|
89
|
+
body = tree.body
|
|
90
|
+
i = 0
|
|
91
|
+
if (
|
|
92
|
+
body
|
|
93
|
+
and isinstance(body[0], ast.Expr)
|
|
94
|
+
and isinstance(getattr(body[0], "value", None), ast.Constant)
|
|
95
|
+
and isinstance(body[0].value.value, str)
|
|
96
|
+
):
|
|
97
|
+
insert_after = body[0].end_lineno or 0
|
|
98
|
+
i = 1
|
|
99
|
+
while i < len(body) and isinstance(body[i], ast.ImportFrom) and body[i].module == "__future__":
|
|
100
|
+
insert_after = body[i].end_lineno or insert_after
|
|
101
|
+
i += 1
|
|
102
|
+
lines = env_source.splitlines(keepends=True)
|
|
103
|
+
return "".join(lines[:insert_after]) + bootstrap + "".join(lines[insert_after:])
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
_TAR_EXCLUDE_DIRS = _ENV_PUSH_IGNORED_NAMES
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _tar_b64(directory: Path) -> str:
|
|
110
|
+
"""Pack a directory into a base64 tarball, excluding caches and metadata directories."""
|
|
111
|
+
import base64
|
|
112
|
+
import io
|
|
113
|
+
import os
|
|
114
|
+
import tarfile
|
|
115
|
+
|
|
116
|
+
buf = io.BytesIO()
|
|
117
|
+
with tarfile.open(fileobj=buf, mode="w:gz") as tar:
|
|
118
|
+
for root, dirs, files in os.walk(directory):
|
|
119
|
+
root_path = Path(root)
|
|
120
|
+
dirs[:] = sorted(d for d in dirs if d not in _TAR_EXCLUDE_DIRS)
|
|
121
|
+
for name in dirs:
|
|
122
|
+
child = root_path / name
|
|
123
|
+
tar.add(child, arcname=str(child.relative_to(directory)), recursive=False)
|
|
124
|
+
for name in sorted(files):
|
|
125
|
+
child = root_path / name
|
|
126
|
+
tar.add(child, arcname=str(child.relative_to(directory)), recursive=False)
|
|
127
|
+
return base64.b64encode(buf.getvalue()).decode()
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _copy_env_sidecars(env_root: Path, dest: Path, *, entrypoint: Path) -> None:
|
|
131
|
+
"""Copy helper code and data sidecars beside environment.py."""
|
|
132
|
+
import shutil
|
|
133
|
+
|
|
134
|
+
for child in sorted(env_root.iterdir()):
|
|
135
|
+
if (
|
|
136
|
+
child == entrypoint
|
|
137
|
+
or child.name == _ENV_ENTRYPOINT
|
|
138
|
+
or child.name in _ENV_PUSH_IGNORED_NAMES
|
|
139
|
+
):
|
|
140
|
+
continue
|
|
141
|
+
if child.name.startswith("."):
|
|
142
|
+
continue
|
|
143
|
+
target = dest / child.name
|
|
144
|
+
if child.is_dir():
|
|
145
|
+
if child.name in _ENV_PUSH_SIDECAR_DIRS:
|
|
146
|
+
shutil.copytree(
|
|
147
|
+
child,
|
|
148
|
+
target,
|
|
149
|
+
ignore=shutil.ignore_patterns(*_ENV_PUSH_IGNORED_NAMES),
|
|
150
|
+
)
|
|
151
|
+
continue
|
|
152
|
+
if not child.is_file():
|
|
153
|
+
continue
|
|
154
|
+
if (
|
|
155
|
+
child.suffix == ".py" and not child.name.startswith("__")
|
|
156
|
+
) or child.suffix.lower() in _ENV_PUSH_SIDECAR_SUFFIXES:
|
|
157
|
+
shutil.copy2(child, target)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _human_bytes(n: int) -> str:
|
|
161
|
+
"""A short human-readable byte count, e.g. ``13.4 MB`` (whole bytes for sub-KB sizes)."""
|
|
162
|
+
size = float(n)
|
|
163
|
+
for unit in ("B", "KB", "MB"):
|
|
164
|
+
if size < 1024:
|
|
165
|
+
return f"{int(size)} {unit}" if unit == "B" else f"{size:.1f} {unit}"
|
|
166
|
+
size /= 1024
|
|
167
|
+
# anything >= 1024 MB falls through to GB (env packages never reach TB).
|
|
168
|
+
return f"{size:.1f} GB"
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class _UploadProgress:
|
|
172
|
+
"""A carriage-return upload progress bar on stderr; a no-op off a TTY.
|
|
173
|
+
|
|
174
|
+
Mirrors ``_LogFollowSpinner`` in commands.py: render only when stderr is interactive,
|
|
175
|
+
rewrite a single line with ``\\r``, and wipe it before normal output. Off a TTY (CI, pipes)
|
|
176
|
+
``callback`` is None, so the client uploads in one shot exactly as before and nothing is
|
|
177
|
+
written to stderr."""
|
|
178
|
+
|
|
179
|
+
_BAR_WIDTH = 24
|
|
180
|
+
|
|
181
|
+
def __init__(self, name: str):
|
|
182
|
+
self._name = name
|
|
183
|
+
self._enabled = sys.stderr.isatty()
|
|
184
|
+
self._last_len = 0
|
|
185
|
+
self._active = False
|
|
186
|
+
self._last_pct = -1
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def enabled(self) -> bool:
|
|
190
|
+
return self._enabled
|
|
191
|
+
|
|
192
|
+
@property
|
|
193
|
+
def callback(self) -> ProgressCallback | None:
|
|
194
|
+
# None off a TTY so the client keeps the plain single-shot upload path.
|
|
195
|
+
return self.update if self._enabled else None
|
|
196
|
+
|
|
197
|
+
def status(self, message: str) -> None:
|
|
198
|
+
"""Show a transient pre-upload line (e.g. ``packaging environment``)."""
|
|
199
|
+
if self._enabled:
|
|
200
|
+
self._write(message)
|
|
201
|
+
|
|
202
|
+
def update(self, sent: int, total: int) -> None:
|
|
203
|
+
if not self._enabled:
|
|
204
|
+
return
|
|
205
|
+
pct = 100 if total <= 0 else min(100, sent * 100 // total)
|
|
206
|
+
# redraw only when the whole-number percent changes (8 KB chunks => thousands of calls)
|
|
207
|
+
if pct == self._last_pct and sent < total:
|
|
208
|
+
return
|
|
209
|
+
self._last_pct = pct
|
|
210
|
+
filled = (
|
|
211
|
+
self._BAR_WIDTH if total <= 0 else min(self._BAR_WIDTH, self._BAR_WIDTH * sent // total)
|
|
212
|
+
)
|
|
213
|
+
bar = "#" * filled + "-" * (self._BAR_WIDTH - filled)
|
|
214
|
+
self._write(
|
|
215
|
+
f"uploading {self._name} [{bar}] {pct:3d}% {_human_bytes(sent)}/{_human_bytes(total)}"
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
def _write(self, message: str) -> None:
|
|
219
|
+
padding = " " * max(0, self._last_len - len(message))
|
|
220
|
+
sys.stderr.write(f"\r{message}{padding}")
|
|
221
|
+
sys.stderr.flush()
|
|
222
|
+
self._last_len = len(message)
|
|
223
|
+
self._active = True
|
|
224
|
+
|
|
225
|
+
def clear(self) -> None:
|
|
226
|
+
if not (self._enabled and self._active):
|
|
227
|
+
return
|
|
228
|
+
sys.stderr.write(f"\r{' ' * self._last_len}\r")
|
|
229
|
+
sys.stderr.flush()
|
|
230
|
+
self._active = False
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _upload_and_report(name: str, *, package_b64: str, bar: _UploadProgress | None = None) -> int:
|
|
234
|
+
"""Upload a packaged env to the managed control plane and print the returned id."""
|
|
235
|
+
from flash.client import ClientError, client_from_config
|
|
236
|
+
|
|
237
|
+
bar = bar or _UploadProgress(name)
|
|
238
|
+
try:
|
|
239
|
+
result = client_from_config().publish_env(
|
|
240
|
+
name=name, package_b64=package_b64, progress=bar.callback
|
|
241
|
+
)
|
|
242
|
+
except ClientError as exc:
|
|
243
|
+
bar.clear()
|
|
244
|
+
print(str(exc), file=sys.stderr)
|
|
245
|
+
return 1
|
|
246
|
+
bar.clear()
|
|
247
|
+
slug = result.get("id")
|
|
248
|
+
if not slug:
|
|
249
|
+
print("warning: the env was uploaded but the server returned no id", file=sys.stderr)
|
|
250
|
+
return 1
|
|
251
|
+
if render.styled():
|
|
252
|
+
print(render.env_published(slug))
|
|
253
|
+
else:
|
|
254
|
+
print(f"published {slug}")
|
|
255
|
+
print(f'reference it in your config:\n\n [environment]\n id = "{slug}"')
|
|
256
|
+
return 0
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def cmd_env_push(args) -> int:
|
|
260
|
+
import tempfile
|
|
261
|
+
|
|
262
|
+
env_name = _normalize_env_name(str(getattr(args, "name", "") or ""))
|
|
263
|
+
if not env_name:
|
|
264
|
+
print("env name required: pass `--name <name>`", file=sys.stderr)
|
|
265
|
+
return 1
|
|
266
|
+
|
|
267
|
+
src = Path(args.path)
|
|
268
|
+
if not src.exists():
|
|
269
|
+
print(f"no such path: {src}", file=sys.stderr)
|
|
270
|
+
return 1
|
|
271
|
+
|
|
272
|
+
if src.is_dir():
|
|
273
|
+
canonical_entrypoint = src / _ENV_ENTRYPOINT
|
|
274
|
+
if canonical_entrypoint.is_file():
|
|
275
|
+
entrypoint = canonical_entrypoint
|
|
276
|
+
env_root = src
|
|
277
|
+
elif (src / "pyproject.toml").is_file():
|
|
278
|
+
print(f"{src} has a pyproject.toml but no environment.py entrypoint", file=sys.stderr)
|
|
279
|
+
return 1
|
|
280
|
+
else:
|
|
281
|
+
modules = [p for p in sorted(src.glob("*.py")) if not p.name.startswith("__")]
|
|
282
|
+
if len(modules) != 1:
|
|
283
|
+
print(
|
|
284
|
+
f"{src} has no environment.py and "
|
|
285
|
+
f"{'no' if not modules else 'multiple'} top-level .py module(s); "
|
|
286
|
+
"add an environment.py entrypoint or pass the exact .py file "
|
|
287
|
+
"for a single-file smoke test.",
|
|
288
|
+
file=sys.stderr,
|
|
289
|
+
)
|
|
290
|
+
return 1
|
|
291
|
+
env_root = src
|
|
292
|
+
entrypoint = modules[0]
|
|
293
|
+
elif src.is_file() and src.suffix == ".py":
|
|
294
|
+
env_root = src.parent
|
|
295
|
+
entrypoint = src
|
|
296
|
+
else:
|
|
297
|
+
print(
|
|
298
|
+
f"cannot publish {src}: expected a Freesolo .py module or an env directory.",
|
|
299
|
+
file=sys.stderr,
|
|
300
|
+
)
|
|
301
|
+
return 1
|
|
302
|
+
|
|
303
|
+
with tempfile.TemporaryDirectory(prefix="flash-env-push-") as tmp:
|
|
304
|
+
pkg = Path(tmp)
|
|
305
|
+
module_source = entrypoint.read_text()
|
|
306
|
+
(pkg / _ENV_ENTRYPOINT).write_text(_with_syspath_bootstrap(module_source))
|
|
307
|
+
_copy_env_sidecars(env_root, pkg, entrypoint=entrypoint)
|
|
308
|
+
(pkg / "README.md").write_text(f"# {env_name}\n\nFlash Freesolo environment.\n")
|
|
309
|
+
# One progress widget spans both phases the user otherwise waits through silently:
|
|
310
|
+
# packaging (walk + gzip, slow for large datasets) and the upload itself.
|
|
311
|
+
bar = _UploadProgress(env_name)
|
|
312
|
+
bar.status("packaging environment")
|
|
313
|
+
try:
|
|
314
|
+
package_b64 = _tar_b64(pkg)
|
|
315
|
+
return _upload_and_report(env_name, package_b64=package_b64, bar=bar)
|
|
316
|
+
finally:
|
|
317
|
+
bar.clear()
|