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.
Files changed (111) hide show
  1. flash/__init__.py +29 -0
  2. flash/_channel.py +23 -0
  3. flash/_fileio.py +35 -0
  4. flash/_logging.py +49 -0
  5. flash/_update_check.py +266 -0
  6. flash/catalog.py +253 -0
  7. flash/cli/__init__.py +1 -0
  8. flash/cli/main/__init__.py +227 -0
  9. flash/cli/main/__main__.py +6 -0
  10. flash/cli/main/commands.py +636 -0
  11. flash/cli/main/envpush.py +317 -0
  12. flash/cli/main/render.py +599 -0
  13. flash/cli/main/training_doc.py +455 -0
  14. flash/client/__init__.py +14 -0
  15. flash/client/config.py +70 -0
  16. flash/client/http.py +372 -0
  17. flash/client/runtime_secrets.py +69 -0
  18. flash/client/specs.py +20 -0
  19. flash/cost/__init__.py +16 -0
  20. flash/cost/analytical.py +175 -0
  21. flash/cost/facts.py +114 -0
  22. flash/cost/spec.py +113 -0
  23. flash/cost/types.py +158 -0
  24. flash/engine/__init__.py +6 -0
  25. flash/engine/accounting.py +36 -0
  26. flash/engine/chalk_kernels.py +116 -0
  27. flash/engine/multiturn_rollout.py +780 -0
  28. flash/engine/recipe.py +86 -0
  29. flash/engine/vram.py +603 -0
  30. flash/engine/worker/__init__.py +2916 -0
  31. flash/engine/worker/__main__.py +4 -0
  32. flash/engine/worker/kernel_warmup.py +400 -0
  33. flash/engine/worker/lora.py +796 -0
  34. flash/engine/worker/packing.py +366 -0
  35. flash/engine/worker/perf.py +1048 -0
  36. flash/envs/__init__.py +10 -0
  37. flash/envs/adapter/__init__.py +883 -0
  38. flash/envs/adapter/rubric.py +222 -0
  39. flash/envs/base.py +52 -0
  40. flash/envs/registry.py +62 -0
  41. flash/mcp/__init__.py +1 -0
  42. flash/mcp/server.py +85 -0
  43. flash/providers/__init__.py +59 -0
  44. flash/providers/_auth.py +24 -0
  45. flash/providers/_http.py +230 -0
  46. flash/providers/_instance.py +416 -0
  47. flash/providers/_instance_bootstrap.py +517 -0
  48. flash/providers/_poll.py +311 -0
  49. flash/providers/allocator.py +193 -0
  50. flash/providers/base.py +431 -0
  51. flash/providers/hyperstack/__init__.py +127 -0
  52. flash/providers/hyperstack/api.py +522 -0
  53. flash/providers/hyperstack/auth.py +17 -0
  54. flash/providers/hyperstack/gpus.py +29 -0
  55. flash/providers/hyperstack/jobs/__init__.py +632 -0
  56. flash/providers/hyperstack/jobs/builders.py +122 -0
  57. flash/providers/hyperstack/preflight.py +23 -0
  58. flash/providers/hyperstack/pricing.py +26 -0
  59. flash/providers/hyperstack/train.py +25 -0
  60. flash/providers/lambdalabs/__init__.py +139 -0
  61. flash/providers/lambdalabs/api.py +261 -0
  62. flash/providers/lambdalabs/auth.py +18 -0
  63. flash/providers/lambdalabs/gpus.py +29 -0
  64. flash/providers/lambdalabs/jobs/__init__.py +724 -0
  65. flash/providers/lambdalabs/jobs/builders.py +118 -0
  66. flash/providers/lambdalabs/preflight.py +27 -0
  67. flash/providers/lambdalabs/pricing.py +51 -0
  68. flash/providers/lambdalabs/train.py +27 -0
  69. flash/providers/preflight.py +55 -0
  70. flash/providers/realized.py +80 -0
  71. flash/providers/runpod/__init__.py +130 -0
  72. flash/providers/runpod/api.py +186 -0
  73. flash/providers/runpod/auth.py +37 -0
  74. flash/providers/runpod/cost.py +57 -0
  75. flash/providers/runpod/gpus.py +46 -0
  76. flash/providers/runpod/jobs.py +956 -0
  77. flash/providers/runpod/keys.py +139 -0
  78. flash/providers/runpod/preflight.py +30 -0
  79. flash/providers/runpod/preload.py +915 -0
  80. flash/providers/runpod/pricing.py +18 -0
  81. flash/providers/runpod/slots.py +79 -0
  82. flash/providers/runpod/train/__init__.py +150 -0
  83. flash/providers/runpod/train/deps.py +395 -0
  84. flash/providers/runpod/train/endpoints.py +820 -0
  85. flash/py.typed +0 -0
  86. flash/runner/__init__.py +686 -0
  87. flash/runner/checkpoints.py +82 -0
  88. flash/runner/deploy.py +422 -0
  89. flash/runner/lifecycle.py +672 -0
  90. flash/schema/__init__.py +375 -0
  91. flash/schema/fields.py +331 -0
  92. flash/serve/__init__.py +1 -0
  93. flash/serve/deploy.py +326 -0
  94. flash/serve/pricing.py +60 -0
  95. flash/server/__init__.py +1 -0
  96. flash/server/__main__.py +20 -0
  97. flash/server/app.py +961 -0
  98. flash/server/auth.py +263 -0
  99. flash/server/billing.py +124 -0
  100. flash/server/checkpoints.py +110 -0
  101. flash/server/db.py +160 -0
  102. flash/server/environment_registry.py +102 -0
  103. flash/server/envs.py +360 -0
  104. flash/server/reconcile.py +163 -0
  105. flash/server/run_registry.py +150 -0
  106. flash/spec.py +333 -0
  107. freesolo_flash_dev-0.2.25.dist-info/METADATA +192 -0
  108. freesolo_flash_dev-0.2.25.dist-info/RECORD +111 -0
  109. freesolo_flash_dev-0.2.25.dist-info/WHEEL +4 -0
  110. freesolo_flash_dev-0.2.25.dist-info/entry_points.txt +3 -0
  111. 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()