letify 2.3.0__tar.gz → 2.4.0__tar.gz
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.
- {letify-2.3.0 → letify-2.4.0}/PKG-INFO +1 -1
- {letify-2.3.0 → letify-2.4.0}/letify/__init__.py +1 -1
- {letify-2.3.0 → letify-2.4.0}/letify/providers/base.py +11 -0
- {letify-2.3.0 → letify-2.4.0}/letify/providers/kaggle.py +27 -2
- {letify-2.3.0 → letify-2.4.0}/letify/transport/pipeline.py +44 -0
- {letify-2.3.0 → letify-2.4.0}/letify-core/Cargo.lock +3 -3
- {letify-2.3.0 → letify-2.4.0}/letify-core/Cargo.toml +1 -1
- {letify-2.3.0 → letify-2.4.0}/letify-ext/package-lock.json +2 -2
- {letify-2.3.0 → letify-2.4.0}/letify-ext/package.json +1 -1
- {letify-2.3.0 → letify-2.4.0}/pyproject.toml +1 -1
- {letify-2.3.0 → letify-2.4.0}/tests/test_kaggle.py +62 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_transport.py +32 -0
- {letify-2.3.0 → letify-2.4.0}/.github/workflows/ci.yml +0 -0
- {letify-2.3.0 → letify-2.4.0}/.github/workflows/docs.yml +0 -0
- {letify-2.3.0 → letify-2.4.0}/.github/workflows/publish.yml +0 -0
- {letify-2.3.0 → letify-2.4.0}/.gitignore +0 -0
- {letify-2.3.0 → letify-2.4.0}/.researchtree +0 -0
- {letify-2.3.0 → letify-2.4.0}/LICENSE +0 -0
- {letify-2.3.0 → letify-2.4.0}/README.md +0 -0
- {letify-2.3.0 → letify-2.4.0}/docs/guide/01-getting-started.md +0 -0
- {letify-2.3.0 → letify-2.4.0}/docs/guide/02-providers.md +0 -0
- {letify-2.3.0 → letify-2.4.0}/docs/guide/03-execution-modes.md +0 -0
- {letify-2.3.0 → letify-2.4.0}/docs/guide/04-environments-and-data.md +0 -0
- {letify-2.3.0 → letify-2.4.0}/docs/guide/05-concurrency.md +0 -0
- {letify-2.3.0 → letify-2.4.0}/docs/guide/06-cost.md +0 -0
- {letify-2.3.0 → letify-2.4.0}/docs/guide/07-troubleshooting.md +0 -0
- {letify-2.3.0 → letify-2.4.0}/docs/guide/08-releasing.md +0 -0
- {letify-2.3.0 → letify-2.4.0}/docs/guide/README.md +0 -0
- {letify-2.3.0 → letify-2.4.0}/docs/guide/ko/01-/354/213/234/354/236/221/355/225/230/352/270/260.md" +0 -0
- {letify-2.3.0 → letify-2.4.0}/docs/guide/ko/02-/355/224/204/353/241/234/353/260/224/354/235/264/353/215/224.md" +0 -0
- {letify-2.3.0 → letify-2.4.0}/docs/guide/ko/03-/354/213/244/355/226/211-/353/260/251/354/213/235.md" +0 -0
- {letify-2.3.0 → letify-2.4.0}/docs/guide/ko/04-/355/231/230/352/262/275/352/263/274-/353/215/260/354/235/264/355/204/260.md" +0 -0
- {letify-2.3.0 → letify-2.4.0}/docs/guide/ko/05-/353/217/231/354/213/234-/354/213/244/355/226/211.md" +0 -0
- {letify-2.3.0 → letify-2.4.0}/docs/guide/ko/06-/353/271/204/354/232/251.md" +0 -0
- {letify-2.3.0 → letify-2.4.0}/docs/guide/ko/07-/353/254/270/354/240/234-/355/225/264/352/262/260.md" +0 -0
- {letify-2.3.0 → letify-2.4.0}/docs/guide/ko/08-/353/246/264/353/246/254/354/212/244.md" +0 -0
- {letify-2.3.0 → letify-2.4.0}/docs/guide/ko/README.md +0 -0
- {letify-2.3.0 → letify-2.4.0}/docs/locales/README_ko.md +0 -0
- {letify-2.3.0 → letify-2.4.0}/docs/mkdocs.yml +0 -0
- {letify-2.3.0 → letify-2.4.0}/examples/README.md +0 -0
- {letify-2.3.0 → letify-2.4.0}/examples/nvfp4-lora/.letify.example +0 -0
- {letify-2.3.0 → letify-2.4.0}/examples/nvfp4-lora/00_smoke.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/examples/nvfp4-lora/01_sweep.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/examples/nvfp4-lora/02_resume.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/examples/nvfp4-lora/03_watch.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/examples/nvfp4-lora/README.md +0 -0
- {letify-2.3.0 → letify-2.4.0}/examples/nvfp4-lora/README_ko.md +0 -0
- {letify-2.3.0 → letify-2.4.0}/examples/nvfp4-lora/common.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/examples/nvfp4-lora/recipe.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/cli.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/config/__init__.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/config/inventory.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/config/login.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/config/schema.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/config/secrets.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/config/writer.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/declare/__init__.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/declare/cache.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/declare/env.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/declare/function.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/declare/instance.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/errors.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/install.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/launcher.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/paths.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/protocol/__init__.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/protocol/codec.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/protocol/driver.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/protocol/handle.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/protocol/wire.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/protocol/worker.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/providers/__init__.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/providers/colab.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/providers/colab_files.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/providers/colab_keepalive.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/providers/elice.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/providers/kaggle_adapter.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/providers/local.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/providers/modal.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/providers/modal_adapter.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/providers/naming.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/providers/shell.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/providers/tunnel.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/providers/usage.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/remoting/__init__.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/remoting/capability.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/remoting/device/__init__.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/remoting/device/client.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/remoting/device/cuda.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/remoting/device/executor.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/remoting/device/frames.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/remoting/device/guard.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/remoting/device/tensor.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/remoting/device/trace.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/remoting/device/value.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/remoting/loader.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/remoting/probe.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/render.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/runtime/__init__.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/runtime/bootstrap.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/runtime/channel.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/runtime/lease.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/runtime/pool.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/runtime/session.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/runtime/telemetry.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/store/__init__.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/store/backends/__init__.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/store/backends/filesystem.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/store/backends/google_auth.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/store/backends/layout.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/store/backends/objects.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/store/cas.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/store/pathdata.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/store/sendorder.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/store/volume.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/stubs.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/tools.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/transport/__init__.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/transport/agent.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/transport/announce.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/transport/link.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/transport/nat.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/transport/portmap.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/transport/probe.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/transport/quic.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/transport/rendezvous.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/transport/setup.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/transport/sshopts.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify/transport/strategies.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-core/.gitignore +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-core/build.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-core/letify-agent/Cargo.toml +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-core/letify-agent/src/driver.rs +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-core/letify-agent/src/main.rs +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-core/letify-driver/Cargo.toml +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-core/letify-driver/src/client.rs +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-core/letify-driver/src/exports.rs +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-core/letify-driver/src/image.rs +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-core/letify-driver/src/lib.rs +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-core/letify-driver/src/status.rs +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-core/letify-driver/src/table.rs +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-core/letify-quic/Cargo.toml +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-core/letify-quic/src/main.rs +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-core/letify-wire/Cargo.toml +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-core/letify-wire/src/lib.rs +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-core/letify-wire/tests/throughput.rs +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-core/rust-toolchain.toml +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-ext/.vscodeignore +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-ext/LICENSE +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-ext/README.md +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-ext/media/icon.svg +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-ext/src/cli.ts +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-ext/src/extension.ts +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-ext/src/history.ts +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-ext/src/model.ts +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-ext/src/poll.ts +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-ext/src/render.ts +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-ext/test/cli.test.ts +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-ext/test/manifest.test.ts +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-ext/test/model.test.ts +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-ext/test/regressions.test.ts +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify-ext/tsconfig.json +0 -0
- {letify-2.3.0 → letify-2.4.0}/letify_providers.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/scripts/sync-main.sh +0 -0
- {letify-2.3.0 → letify-2.4.0}/scripts/version.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/conftest.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/fake_kaggle_adapter.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/fake_modal_adapter.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_cli.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_cli_output.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_colab_keepalive.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_config.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_core.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_data.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_data_streaming.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_declare.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_device.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_editor_sessions.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_elice.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_frames.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_install.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_kaggle_cookie.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_kaggle_link.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_link_log.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_notebook.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_parallel_placement.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_paths.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_portmap.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_protocol.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_providers.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_quic.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_remoting.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_render.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_runtime.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_ship.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_store.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_strategies.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_stubs.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_tunnel_setup.py +0 -0
- {letify-2.3.0 → letify-2.4.0}/tests/test_version.py +0 -0
|
@@ -383,6 +383,17 @@ class Provider(abc.ABC):
|
|
|
383
383
|
"""
|
|
384
384
|
return None
|
|
385
385
|
|
|
386
|
+
@property
|
|
387
|
+
def fallback_mib_per_s(self) -> float:
|
|
388
|
+
"""What this provider's own fallback carries, or 0 when it is not known.
|
|
389
|
+
|
|
390
|
+
Spec "A floor rejection never picks something slower": a link the floor rejected
|
|
391
|
+
is kept when it beats this, because refusing a slow link only makes sense when
|
|
392
|
+
what happens instead is better.
|
|
393
|
+
"""
|
|
394
|
+
value = self.config.option("fallback_mib_per_s")
|
|
395
|
+
return max(0.0, float(value)) if isinstance(value, (int, float)) else 0.0
|
|
396
|
+
|
|
386
397
|
@property
|
|
387
398
|
def any_accelerator(self) -> bool:
|
|
388
399
|
"""Whether a card other than the one asked for is accepted, from ``any_accelerator``."""
|
|
@@ -103,6 +103,9 @@ FIRESTORE_DOCUMENT = (
|
|
|
103
103
|
#: runnable. A freshly created empty notebook cancels its own session because it has nothing
|
|
104
104
|
#: to run, so the session is started with one committed cell.
|
|
105
105
|
NOTEBOOK_TITLE = "letify runtime"
|
|
106
|
+
#: What the kernel bridge carries, measured sending 64 MiB on a live account. The
|
|
107
|
+
#: floor compares a link against this rather than refusing one that beats it.
|
|
108
|
+
BRIDGE_MIB_PER_S = 2.0
|
|
106
109
|
NOTEBOOK_BODY = json.dumps(
|
|
107
110
|
{
|
|
108
111
|
"cells": [
|
|
@@ -1112,7 +1115,9 @@ class Kaggle(Provider):
|
|
|
1112
1115
|
usage_unit = "GPU hours"
|
|
1113
1116
|
usage_source = "the weekly accelerator quota the official Kaggle CLI's quota command reads"
|
|
1114
1117
|
|
|
1115
|
-
|
|
1118
|
+
# Spec "Kaggle runtimes": /kaggle/working is the notebook's output directory on its
|
|
1119
|
+
# own 19.5 GB device, while /kaggle is the overlay with about 1 TB free.
|
|
1120
|
+
default_workspace = "/kaggle/letify"
|
|
1116
1121
|
|
|
1117
1122
|
def account_note(self) -> str | None:
|
|
1118
1123
|
"""How the account's cookie is doing, for `letify providers`, with no network call.
|
|
@@ -1319,6 +1324,7 @@ class Kaggle(Provider):
|
|
|
1319
1324
|
return LinkFloor(
|
|
1320
1325
|
min_bps=float(min_mib) * MIB if isinstance(min_mib, (int, float)) else default.min_bps,
|
|
1321
1326
|
max_rtt_ms=float(max_rtt) if isinstance(max_rtt, (int, float)) else default.max_rtt_ms,
|
|
1327
|
+
fallback_bps=self.fallback_mib_per_s * MIB,
|
|
1322
1328
|
)
|
|
1323
1329
|
|
|
1324
1330
|
@property
|
|
@@ -1339,7 +1345,15 @@ class Kaggle(Provider):
|
|
|
1339
1345
|
|
|
1340
1346
|
try:
|
|
1341
1347
|
link = self.link_over(bridge, name=name)
|
|
1342
|
-
except Exception:
|
|
1348
|
+
except Exception as exc:
|
|
1349
|
+
# Never silent: a session that quietly changed channel is one nobody can
|
|
1350
|
+
# account for. Spec "A floor rejection never picks something slower".
|
|
1351
|
+
from ..transport.announce import printer
|
|
1352
|
+
|
|
1353
|
+
printer(bool(self.__dict__.get("announce", True)))(
|
|
1354
|
+
f"{self.alias}: the kernel bridge is carrying {name} at about "
|
|
1355
|
+
f"{self.fallback_mib_per_s:.1f} MiB/s, because no link was chosen: {exc}"
|
|
1356
|
+
)
|
|
1343
1357
|
return bridge
|
|
1344
1358
|
self._links[name] = link
|
|
1345
1359
|
return PersistentChannel(
|
|
@@ -1347,6 +1361,17 @@ class Kaggle(Provider):
|
|
|
1347
1361
|
name=name,
|
|
1348
1362
|
)
|
|
1349
1363
|
|
|
1364
|
+
@property
|
|
1365
|
+
def fallback_mib_per_s(self) -> float:
|
|
1366
|
+
"""The kernel bridge, measured at 2.0 MiB/s sending 64 MiB on a live account.
|
|
1367
|
+
|
|
1368
|
+
Spec "A floor rejection never picks something slower": without this a punched link
|
|
1369
|
+
at down 8.1 MiB/s was refused for being under the 10 MiB/s floor and 26 GB went
|
|
1370
|
+
over the 2 MiB/s bridge instead.
|
|
1371
|
+
"""
|
|
1372
|
+
value = self.config.option("fallback_mib_per_s")
|
|
1373
|
+
return max(0.0, float(value)) if isinstance(value, (int, float)) else BRIDGE_MIB_PER_S
|
|
1374
|
+
|
|
1350
1375
|
@property
|
|
1351
1376
|
def transfer_connection_cost_s(self) -> float:
|
|
1352
1377
|
"""A punch, which is what one more stream costs here. Measured at about 10 s."""
|
|
@@ -61,11 +61,39 @@ class LinkFloor:
|
|
|
61
61
|
|
|
62
62
|
min_bps: float
|
|
63
63
|
max_rtt_ms: float
|
|
64
|
+
#: What the provider's own fallback carries, when it is known. A link the floor
|
|
65
|
+
#: rejected is kept when it beats this. Spec "A floor rejection never picks something
|
|
66
|
+
#: slower".
|
|
67
|
+
fallback_bps: float = 0.0
|
|
64
68
|
|
|
65
69
|
@classmethod
|
|
66
70
|
def default(cls) -> LinkFloor:
|
|
67
71
|
return cls(min_bps=DEFAULT_MIN_MIB_PER_S * MIB, max_rtt_ms=DEFAULT_MAX_RTT_MS)
|
|
68
72
|
|
|
73
|
+
def kept_below(self, result: ProbeResult, floored: list[str]) -> str | None:
|
|
74
|
+
"""Why a link the floor rejected is used anyway, or None when it is not.
|
|
75
|
+
|
|
76
|
+
Spec "A floor rejection never picks something slower".
|
|
77
|
+
"""
|
|
78
|
+
if not floored or not self.beats_fallback(result):
|
|
79
|
+
return None
|
|
80
|
+
return (
|
|
81
|
+
f"kept below the floor, because the fallback carries "
|
|
82
|
+
f"{self.fallback_bps / MIB:.1f} MiB/s"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def beats_fallback(self, result: ProbeResult) -> bool:
|
|
86
|
+
"""Whether this probe is faster than the path that would be used instead.
|
|
87
|
+
|
|
88
|
+
Spec "A floor rejection never picks something slower": the floor refuses a slow
|
|
89
|
+
link so a slow link is not used, which is only right when what happens instead is
|
|
90
|
+
better. A provider with no measurable fallback has nothing to compare against, so
|
|
91
|
+
the answer is False and the rejection stands.
|
|
92
|
+
"""
|
|
93
|
+
if not self.fallback_bps:
|
|
94
|
+
return False
|
|
95
|
+
return result.upload_bps > self.fallback_bps and result.download_bps > self.fallback_bps
|
|
96
|
+
|
|
69
97
|
def violations(self, result: ProbeResult) -> list[str]:
|
|
70
98
|
"""Each direction, or the round trip, that the probe failed to clear."""
|
|
71
99
|
found = _below(
|
|
@@ -312,6 +340,10 @@ class Pipeline:
|
|
|
312
340
|
return link
|
|
313
341
|
if measured is not None:
|
|
314
342
|
floored = self.floor.violations(measured)
|
|
343
|
+
kept = self.floor.kept_below(measured, floored)
|
|
344
|
+
if kept:
|
|
345
|
+
self._say(f"{link.strategy} {kept}")
|
|
346
|
+
floored = []
|
|
315
347
|
if floored:
|
|
316
348
|
self._say(f"rejected {link.strategy}: below the floor: {', '.join(floored)}")
|
|
317
349
|
_close(link)
|
|
@@ -389,6 +421,10 @@ class Pipeline:
|
|
|
389
421
|
_close(link)
|
|
390
422
|
return None
|
|
391
423
|
floored = self.floor.violations(measured)
|
|
424
|
+
kept = self.floor.kept_below(measured, floored)
|
|
425
|
+
if kept:
|
|
426
|
+
self._say(f"cached {name} {kept}")
|
|
427
|
+
floored = []
|
|
392
428
|
if floored:
|
|
393
429
|
self._say(f"cached {name} rejected: below the floor: {', '.join(floored)}")
|
|
394
430
|
reasons.append(f"{name}: below the floor: {', '.join(floored)}")
|
|
@@ -573,6 +609,10 @@ class Pipeline:
|
|
|
573
609
|
else:
|
|
574
610
|
if measured is not None:
|
|
575
611
|
floored = self.floor.violations(measured)
|
|
612
|
+
kept = self.floor.kept_below(measured, floored)
|
|
613
|
+
if kept:
|
|
614
|
+
self._say(f"{link.strategy} {kept}")
|
|
615
|
+
floored = []
|
|
576
616
|
if floored:
|
|
577
617
|
below = f"{link.strategy}: below the floor: {', '.join(floored)}"
|
|
578
618
|
self._say(f"rejected {below}")
|
|
@@ -598,6 +638,10 @@ class Pipeline:
|
|
|
598
638
|
above_floor: list[tuple[Any, ProbeResult]] = []
|
|
599
639
|
for link, measured in probed:
|
|
600
640
|
floored = self.floor.violations(measured)
|
|
641
|
+
kept = self.floor.kept_below(measured, floored)
|
|
642
|
+
if kept:
|
|
643
|
+
self._say(f"{link.strategy} {kept}")
|
|
644
|
+
floored = []
|
|
601
645
|
if floored:
|
|
602
646
|
self._say(f"rejected {link.strategy}: below the floor: {', '.join(floored)}")
|
|
603
647
|
below = f"{link.strategy}: below the floor: {', '.join(floored)}"
|
|
@@ -138,7 +138,7 @@ dependencies = [
|
|
|
138
138
|
|
|
139
139
|
[[package]]
|
|
140
140
|
name = "letify-agent"
|
|
141
|
-
version = "2.
|
|
141
|
+
version = "2.4.0"
|
|
142
142
|
dependencies = [
|
|
143
143
|
"letify-wire",
|
|
144
144
|
"libloading",
|
|
@@ -146,7 +146,7 @@ dependencies = [
|
|
|
146
146
|
|
|
147
147
|
[[package]]
|
|
148
148
|
name = "letify-driver"
|
|
149
|
-
version = "2.
|
|
149
|
+
version = "2.4.0"
|
|
150
150
|
dependencies = [
|
|
151
151
|
"letify-wire",
|
|
152
152
|
]
|
|
@@ -164,7 +164,7 @@ dependencies = [
|
|
|
164
164
|
|
|
165
165
|
[[package]]
|
|
166
166
|
name = "letify-wire"
|
|
167
|
-
version = "2.
|
|
167
|
+
version = "2.4.0"
|
|
168
168
|
|
|
169
169
|
[[package]]
|
|
170
170
|
name = "libc"
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "letify-status",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "letify-status",
|
|
9
|
-
"version": "2.
|
|
9
|
+
"version": "2.4.0",
|
|
10
10
|
"license": "SEE LICENSE IN LICENSE",
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"@types/node": "^18.19.0",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "letify-status",
|
|
3
3
|
"displayName": "letify Status",
|
|
4
4
|
"description": "letify quota and GPU activity in the status bar, read from the letify CLI.",
|
|
5
|
-
"version": "2.
|
|
5
|
+
"version": "2.4.0",
|
|
6
6
|
"publisher": "darkpyonix",
|
|
7
7
|
"license": "SEE LICENSE IN LICENSE",
|
|
8
8
|
"repository": {
|
|
@@ -1232,3 +1232,65 @@ def test_a_kaggle_account_is_asked_for_its_sessions(launcher_from, fake_kaggle_c
|
|
|
1232
1232
|
row = rows.get("kaggle_a")
|
|
1233
1233
|
assert row is not None, rows
|
|
1234
1234
|
assert row.get("reason") != "session discovery is not supported"
|
|
1235
|
+
|
|
1236
|
+
|
|
1237
|
+
def test_the_kaggle_workspace_is_not_under_the_output_directory() -> None:
|
|
1238
|
+
"""Spec "Kaggle": /kaggle/working is the output directory, on its own 19.5 GB device.
|
|
1239
|
+
|
|
1240
|
+
Measured on a live session: /kaggle/working has 19.5 GB against Kaggle's 20 GB output
|
|
1241
|
+
limit, while /kaggle is the overlay with 1069.9 GB free. A venv and a call's blobs are
|
|
1242
|
+
not the notebook's output, and a 26 GB dataset does not fit on the smaller one.
|
|
1243
|
+
/kaggle/temp, which looks like the obvious scratch path, does not exist on the image.
|
|
1244
|
+
"""
|
|
1245
|
+
provider = kaggle_provider()
|
|
1246
|
+
assert provider.workspace_root == "/kaggle/letify"
|
|
1247
|
+
assert "/kaggle/working" not in provider.workspace_root
|
|
1248
|
+
|
|
1249
|
+
|
|
1250
|
+
def test_a_kaggle_account_can_still_name_its_own_workspace(config_file) -> None:
|
|
1251
|
+
"""Spec: an account that wants another path sets workspace."""
|
|
1252
|
+
from conftest import provider_of
|
|
1253
|
+
|
|
1254
|
+
from letify.providers import Kaggle
|
|
1255
|
+
|
|
1256
|
+
provider = provider_of(Kaggle, "kaggle_a", workspace="/kaggle/working/letify")
|
|
1257
|
+
assert provider.workspace_root == "/kaggle/working/letify"
|
|
1258
|
+
|
|
1259
|
+
|
|
1260
|
+
def test_the_kaggle_floor_knows_what_the_bridge_carries() -> None:
|
|
1261
|
+
"""Spec "A floor rejection never picks something slower": the bridge is 2.0 MiB/s."""
|
|
1262
|
+
from letify.transport.pipeline import MIB
|
|
1263
|
+
|
|
1264
|
+
provider = kaggle_provider()
|
|
1265
|
+
assert provider.fallback_mib_per_s == 2.0
|
|
1266
|
+
assert provider.link_floor.fallback_bps == 2.0 * MIB
|
|
1267
|
+
|
|
1268
|
+
|
|
1269
|
+
def test_an_account_can_say_what_its_bridge_carries(config_file) -> None:
|
|
1270
|
+
"""Spec: an account overrides it with fallback_mib_per_s."""
|
|
1271
|
+
from conftest import provider_of
|
|
1272
|
+
|
|
1273
|
+
from letify.providers import Kaggle
|
|
1274
|
+
|
|
1275
|
+
provider = provider_of(Kaggle, "kaggle_a", fallback_mib_per_s=5)
|
|
1276
|
+
assert provider.fallback_mib_per_s == 5.0
|
|
1277
|
+
|
|
1278
|
+
|
|
1279
|
+
def test_falling_back_to_the_bridge_says_so(monkeypatch, capsys) -> None:
|
|
1280
|
+
"""Spec: taking the fallback is never silent.
|
|
1281
|
+
|
|
1282
|
+
A live session spent 25 minutes on the bridge with no line saying which channel it was
|
|
1283
|
+
on, because the fallback was reached through a bare `except Exception: return bridge`.
|
|
1284
|
+
"""
|
|
1285
|
+
provider = kaggle_provider()
|
|
1286
|
+
provider.announce = True
|
|
1287
|
+
|
|
1288
|
+
def refuse(channel, name=None):
|
|
1289
|
+
raise RuntimeError("every strategy was below the floor")
|
|
1290
|
+
|
|
1291
|
+
monkeypatch.setattr(provider, "link_over", refuse)
|
|
1292
|
+
bridge = object()
|
|
1293
|
+
assert provider.channel_over(bridge, name="k-1") is bridge
|
|
1294
|
+
err = capsys.readouterr().err
|
|
1295
|
+
assert "the kernel bridge is carrying k-1" in err
|
|
1296
|
+
assert "2.0 MiB/s" in err
|
|
@@ -901,3 +901,35 @@ def test_a_platform_without_the_kernel_copy_still_splices(monkeypatch) -> None:
|
|
|
901
901
|
left.close()
|
|
902
902
|
near.close()
|
|
903
903
|
thread.join(10)
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
def test_a_link_below_the_floor_is_kept_when_the_fallback_is_slower(isolated_home, capsys) -> None:
|
|
907
|
+
"""Spec "A floor rejection never picks something slower".
|
|
908
|
+
|
|
909
|
+
A live Kaggle session refused a punched link at down 8.1 MiB/s for being under the
|
|
910
|
+
10 MiB/s floor and then carried 26 GB over the 2 MiB/s kernel bridge instead, which is
|
|
911
|
+
the opposite of what the floor is for.
|
|
912
|
+
"""
|
|
913
|
+
floor = LinkFloor(min_bps=10 * MIB, max_rtt_ms=300.0, fallback_bps=2 * MIB)
|
|
914
|
+
punch = FakeStrategy("tcp_punch", 2, result=result(8.1, 8.1, rtt=190.0))
|
|
915
|
+
link = pipeline([punch], floor=floor).connect()
|
|
916
|
+
assert link.strategy == "tcp_punch"
|
|
917
|
+
err = capsys.readouterr().err
|
|
918
|
+
assert "kept below the floor" in err
|
|
919
|
+
assert "2.0 MiB/s" in err
|
|
920
|
+
|
|
921
|
+
|
|
922
|
+
def test_a_link_slower_than_the_fallback_is_still_refused(isolated_home) -> None:
|
|
923
|
+
"""Spec: only a link that beats the fallback is kept; a slower one is given up."""
|
|
924
|
+
floor = LinkFloor(min_bps=10 * MIB, max_rtt_ms=300.0, fallback_bps=2 * MIB)
|
|
925
|
+
punch = FakeStrategy("tcp_punch", 2, result=result(8.1, 0.5, rtt=190.0))
|
|
926
|
+
with pytest.raises(letify.ProviderUnavailable):
|
|
927
|
+
pipeline([punch], floor=floor).connect()
|
|
928
|
+
|
|
929
|
+
|
|
930
|
+
def test_a_provider_with_no_known_fallback_keeps_failing_below_the_floor(isolated_home) -> None:
|
|
931
|
+
"""Spec: a provider with no measurable fallback has nothing to compare against."""
|
|
932
|
+
floor = LinkFloor(min_bps=10 * MIB, max_rtt_ms=300.0)
|
|
933
|
+
punch = FakeStrategy("tcp_punch", 2, result=result(8.1, 8.1, rtt=190.0))
|
|
934
|
+
with pytest.raises(letify.ProviderUnavailable):
|
|
935
|
+
pipeline([punch], floor=floor).connect()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{letify-2.3.0 → letify-2.4.0}/docs/guide/ko/01-/354/213/234/354/236/221/355/225/230/352/270/260.md"
RENAMED
|
File without changes
|
|
File without changes
|
{letify-2.3.0 → letify-2.4.0}/docs/guide/ko/03-/354/213/244/355/226/211-/353/260/251/354/213/235.md"
RENAMED
|
File without changes
|
|
File without changes
|
{letify-2.3.0 → letify-2.4.0}/docs/guide/ko/05-/353/217/231/354/213/234-/354/213/244/355/226/211.md"
RENAMED
|
File without changes
|
|
File without changes
|
{letify-2.3.0 → letify-2.4.0}/docs/guide/ko/07-/353/254/270/354/240/234-/355/225/264/352/262/260.md"
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|