etude-sdk 0.1.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.
- etude_sdk-0.1.0/.github/dependabot.yml +21 -0
- etude_sdk-0.1.0/.github/workflows/ci.yml +95 -0
- etude_sdk-0.1.0/.github/workflows/release.yml +39 -0
- etude_sdk-0.1.0/.gitignore +10 -0
- etude_sdk-0.1.0/.python-version +1 -0
- etude_sdk-0.1.0/AGENTS.md +172 -0
- etude_sdk-0.1.0/LICENSE +21 -0
- etude_sdk-0.1.0/PKG-INFO +83 -0
- etude_sdk-0.1.0/README.md +68 -0
- etude_sdk-0.1.0/docs/launcher.md +807 -0
- etude_sdk-0.1.0/docs/tracking.md +165 -0
- etude_sdk-0.1.0/pyproject.toml +62 -0
- etude_sdk-0.1.0/scratch/.gitkeep +0 -0
- etude_sdk-0.1.0/scripts/test_network.py +79 -0
- etude_sdk-0.1.0/src/etude/__init__.py +0 -0
- etude_sdk-0.1.0/src/etude/__main__.py +5 -0
- etude_sdk-0.1.0/src/etude/cli.py +85 -0
- etude_sdk-0.1.0/src/etude/launch/__init__.py +0 -0
- etude_sdk-0.1.0/src/etude/launch/cli.py +591 -0
- etude_sdk-0.1.0/src/etude/launch/client.py +178 -0
- etude_sdk-0.1.0/src/etude/launch/commands.py +25 -0
- etude_sdk-0.1.0/src/etude/launch/controller.py +488 -0
- etude_sdk-0.1.0/src/etude/launch/database.py +777 -0
- etude_sdk-0.1.0/src/etude/launch/environment.py +50 -0
- etude_sdk-0.1.0/src/etude/launch/errors.py +2 -0
- etude_sdk-0.1.0/src/etude/launch/executor.py +168 -0
- etude_sdk-0.1.0/src/etude/launch/job_file.py +107 -0
- etude_sdk-0.1.0/src/etude/launch/listing.py +92 -0
- etude_sdk-0.1.0/src/etude/launch/machines.py +83 -0
- etude_sdk-0.1.0/src/etude/launch/measurement.py +49 -0
- etude_sdk-0.1.0/src/etude/launch/providers/__init__.py +0 -0
- etude_sdk-0.1.0/src/etude/launch/providers/cleanup.py +10 -0
- etude_sdk-0.1.0/src/etude/launch/providers/package.py +49 -0
- etude_sdk-0.1.0/src/etude/launch/providers/runpod/__init__.py +0 -0
- etude_sdk-0.1.0/src/etude/launch/providers/runpod/api.py +189 -0
- etude_sdk-0.1.0/src/etude/launch/providers/runpod/http.py +78 -0
- etude_sdk-0.1.0/src/etude/launch/providers/runpod/machines.py +160 -0
- etude_sdk-0.1.0/src/etude/launch/providers/runpod/pod.py +179 -0
- etude_sdk-0.1.0/src/etude/launch/records.py +378 -0
- etude_sdk-0.1.0/src/etude/launch/scheduler.py +1455 -0
- etude_sdk-0.1.0/src/etude/launch/schemas.py +143 -0
- etude_sdk-0.1.0/src/etude/launch/source.py +321 -0
- etude_sdk-0.1.0/src/etude/launch/ssh.py +283 -0
- etude_sdk-0.1.0/src/etude/launch/startup.py +47 -0
- etude_sdk-0.1.0/src/etude/launch/storage/__init__.py +0 -0
- etude_sdk-0.1.0/src/etude/launch/storage/bucket.py +234 -0
- etude_sdk-0.1.0/src/etude/launch/storage/disk.py +186 -0
- etude_sdk-0.1.0/src/etude/launch/storage/files.py +32 -0
- etude_sdk-0.1.0/src/etude/launch/storage/s3.py +433 -0
- etude_sdk-0.1.0/src/etude/launch/worker/__init__.py +0 -0
- etude_sdk-0.1.0/src/etude/launch/worker/attempts.py +248 -0
- etude_sdk-0.1.0/src/etude/launch/worker/courier.py +319 -0
- etude_sdk-0.1.0/src/etude/launch/worker/main.py +26 -0
- etude_sdk-0.1.0/src/etude/launch/worker/remote.py +112 -0
- etude_sdk-0.1.0/src/etude/py.typed +0 -0
- etude_sdk-0.1.0/src/etude/track/__init__.py +0 -0
- etude_sdk-0.1.0/src/etude/track/block.py +462 -0
- etude_sdk-0.1.0/src/etude/track/block_file.py +59 -0
- etude_sdk-0.1.0/src/etude/track/cli.py +135 -0
- etude_sdk-0.1.0/src/etude/track/errors.py +10 -0
- etude_sdk-0.1.0/src/etude/track/manifest.py +185 -0
- etude_sdk-0.1.0/src/etude/track/read.py +658 -0
- etude_sdk-0.1.0/src/etude/track/records.py +297 -0
- etude_sdk-0.1.0/src/etude/track/report.html +363 -0
- etude_sdk-0.1.0/src/etude/track/report.py +224 -0
- etude_sdk-0.1.0/src/etude/track/run.py +1101 -0
- etude_sdk-0.1.0/src/etude/track/serve.py +73 -0
- etude_sdk-0.1.0/src/etude/track/storage.py +191 -0
- etude_sdk-0.1.0/src/etude/track/vendor/UPLOT_LICENSE +21 -0
- etude_sdk-0.1.0/src/etude/track/vendor/uPlot.iife.min.js +2 -0
- etude_sdk-0.1.0/src/etude/track/vendor/uPlot.min.css +1 -0
- etude_sdk-0.1.0/tests/__init__.py +0 -0
- etude_sdk-0.1.0/tests/launch/__init__.py +0 -0
- etude_sdk-0.1.0/tests/launch/counting.py +81 -0
- etude_sdk-0.1.0/tests/launch/fake.py +281 -0
- etude_sdk-0.1.0/tests/launch/test_accounting.py +128 -0
- etude_sdk-0.1.0/tests/launch/test_cli.py +403 -0
- etude_sdk-0.1.0/tests/launch/test_controller.py +363 -0
- etude_sdk-0.1.0/tests/launch/test_layout.py +80 -0
- etude_sdk-0.1.0/tests/launch/test_measurement.py +284 -0
- etude_sdk-0.1.0/tests/launch/test_package.py +202 -0
- etude_sdk-0.1.0/tests/launch/test_queue.py +371 -0
- etude_sdk-0.1.0/tests/launch/test_rentals.py +128 -0
- etude_sdk-0.1.0/tests/launch/test_runpod_api.py +250 -0
- etude_sdk-0.1.0/tests/launch/test_runpod_machines.py +685 -0
- etude_sdk-0.1.0/tests/launch/test_scheduler.py +633 -0
- etude_sdk-0.1.0/tests/launch/test_source.py +226 -0
- etude_sdk-0.1.0/tests/launch/test_ssh.py +82 -0
- etude_sdk-0.1.0/tests/launch/test_storage_s3.py +206 -0
- etude_sdk-0.1.0/tests/launch/test_worker_attempts.py +207 -0
- etude_sdk-0.1.0/tests/track/__init__.py +0 -0
- etude_sdk-0.1.0/tests/track/storage.py +49 -0
- etude_sdk-0.1.0/tests/track/test_cli.py +79 -0
- etude_sdk-0.1.0/tests/track/test_measure.py +195 -0
- etude_sdk-0.1.0/tests/track/test_store.py +723 -0
- etude_sdk-0.1.0/tests/track/test_track.py +469 -0
- etude_sdk-0.1.0/uv.lock +1213 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: uv
|
|
4
|
+
directory: /
|
|
5
|
+
schedule:
|
|
6
|
+
interval: weekly
|
|
7
|
+
groups:
|
|
8
|
+
python:
|
|
9
|
+
update-types:
|
|
10
|
+
- minor
|
|
11
|
+
- patch
|
|
12
|
+
|
|
13
|
+
- package-ecosystem: github-actions
|
|
14
|
+
directory: /
|
|
15
|
+
schedule:
|
|
16
|
+
interval: weekly
|
|
17
|
+
groups:
|
|
18
|
+
actions:
|
|
19
|
+
update-types:
|
|
20
|
+
- minor
|
|
21
|
+
- patch
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
merge_group:
|
|
8
|
+
|
|
9
|
+
# A new PR head supersedes its old proof. Main and merge-group runs keep their
|
|
10
|
+
# own identity so independent integration proofs do not cancel each other.
|
|
11
|
+
concurrency:
|
|
12
|
+
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.run_id }}
|
|
13
|
+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
# Scratch belongs to work in progress. Delivery clears it before publishing a
|
|
17
|
+
# landing candidate; queue and main runs never accept that exemption.
|
|
18
|
+
scratch-clear:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
outputs:
|
|
21
|
+
candidate: ${{ steps.scratch.outputs.candidate }}
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v5
|
|
24
|
+
- name: Select landing candidates and enforce scratch cleanup
|
|
25
|
+
id: scratch
|
|
26
|
+
env:
|
|
27
|
+
EVENT_NAME: ${{ github.event_name }}
|
|
28
|
+
run: |
|
|
29
|
+
if [ ! -d scratch ]; then
|
|
30
|
+
echo "scratch/ is missing"
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
EXTRA=$(find scratch -mindepth 1 ! -path 'scratch/.gitkeep' -print)
|
|
34
|
+
if [ -n "$EXTRA" ]; then
|
|
35
|
+
if [ "$EVENT_NAME" = "pull_request" ]; then
|
|
36
|
+
echo "Checkpoint: checks are deferred until scratch/ is clear." | tee -a "$GITHUB_STEP_SUMMARY"
|
|
37
|
+
echo "candidate=false" >> "$GITHUB_OUTPUT"
|
|
38
|
+
exit 0
|
|
39
|
+
fi
|
|
40
|
+
echo "scratch/ must be clear before landing"
|
|
41
|
+
echo "$EXTRA"
|
|
42
|
+
exit 1
|
|
43
|
+
fi
|
|
44
|
+
echo "candidate=true" >> "$GITHUB_OUTPUT"
|
|
45
|
+
|
|
46
|
+
client-lint:
|
|
47
|
+
needs: [scratch-clear]
|
|
48
|
+
if: ${{ !cancelled() && needs.scratch-clear.outputs.candidate == 'true' }}
|
|
49
|
+
runs-on: ubuntu-latest
|
|
50
|
+
steps:
|
|
51
|
+
- uses: actions/checkout@v5
|
|
52
|
+
- uses: astral-sh/setup-uv@v6
|
|
53
|
+
- run: uv sync --locked
|
|
54
|
+
- run: uv run --no-sync ruff check .
|
|
55
|
+
- run: uv run --no-sync ruff format --check .
|
|
56
|
+
- run: uv run --no-sync mypy
|
|
57
|
+
|
|
58
|
+
client-test:
|
|
59
|
+
needs: [scratch-clear]
|
|
60
|
+
if: ${{ !cancelled() && needs.scratch-clear.outputs.candidate == 'true' }}
|
|
61
|
+
runs-on: ubuntu-latest
|
|
62
|
+
steps:
|
|
63
|
+
- uses: actions/checkout@v5
|
|
64
|
+
- uses: astral-sh/setup-uv@v6
|
|
65
|
+
- run: uv sync --locked
|
|
66
|
+
- name: Run the client tests with the outside network denied
|
|
67
|
+
run: uv run --no-sync python scripts/test_network.py uv run --no-sync pytest
|
|
68
|
+
timeout-minutes: 15
|
|
69
|
+
|
|
70
|
+
tests-result:
|
|
71
|
+
# A skipped check is not proof. Only PR checkpoints may skip the aggregate;
|
|
72
|
+
# a failed classifier must still produce a failing required check.
|
|
73
|
+
if: ${{ always() && (github.event_name != 'pull_request' || needs.scratch-clear.result != 'success' || needs.scratch-clear.outputs.candidate != 'false') }}
|
|
74
|
+
runs-on: ubuntu-latest
|
|
75
|
+
needs: [scratch-clear, client-lint, client-test]
|
|
76
|
+
steps:
|
|
77
|
+
- name: Aggregate check results
|
|
78
|
+
env:
|
|
79
|
+
SCRATCH_CLEAR: ${{ needs.scratch-clear.result }}
|
|
80
|
+
CLIENT_LINT: ${{ needs.client-lint.result }}
|
|
81
|
+
CLIENT_TEST: ${{ needs.client-test.result }}
|
|
82
|
+
run: |
|
|
83
|
+
failures=0
|
|
84
|
+
check_result() {
|
|
85
|
+
if [ "$2" = success ]; then
|
|
86
|
+
echo "PASS $1"
|
|
87
|
+
else
|
|
88
|
+
echo "FAIL $1 ($2)"
|
|
89
|
+
failures=1
|
|
90
|
+
fi
|
|
91
|
+
}
|
|
92
|
+
check_result scratch-clear "$SCRATCH_CLEAR"
|
|
93
|
+
check_result client-lint "$CLIENT_LINT"
|
|
94
|
+
check_result client-test "$CLIENT_TEST"
|
|
95
|
+
exit "$failures"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Pushing a tag vX.Y.Z publishes that version of etude-sdk to PyPI. PyPI trusts
|
|
4
|
+
# this workflow by name (trusted publishing), so no token is stored anywhere.
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
tags: ["v*"]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v5
|
|
14
|
+
- uses: astral-sh/setup-uv@v6
|
|
15
|
+
- name: Refuse a tag that does not match the package version
|
|
16
|
+
run: |
|
|
17
|
+
version=$(uv version --short)
|
|
18
|
+
if [ "v$version" != "$GITHUB_REF_NAME" ]; then
|
|
19
|
+
echo "tag $GITHUB_REF_NAME does not match version $version in pyproject.toml"
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
- run: uv build
|
|
23
|
+
- uses: actions/upload-artifact@v4
|
|
24
|
+
with:
|
|
25
|
+
name: dist
|
|
26
|
+
path: dist/
|
|
27
|
+
|
|
28
|
+
publish:
|
|
29
|
+
needs: build
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
environment: pypi
|
|
32
|
+
permissions:
|
|
33
|
+
id-token: write
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/download-artifact@v4
|
|
36
|
+
with:
|
|
37
|
+
name: dist
|
|
38
|
+
path: dist/
|
|
39
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Etude
|
|
2
|
+
|
|
3
|
+
Conventions for people and coding agents working on this repository.
|
|
4
|
+
|
|
5
|
+
## Units
|
|
6
|
+
|
|
7
|
+
Use steps for scientific cadence and preserve absolute continuation coordinates.
|
|
8
|
+
Use hours and dollars for machine rental windows, credentials, watchdogs and budgets.
|
|
9
|
+
Distinguish training steps, learner transitions and optimizer updates/exposures.
|
|
10
|
+
|
|
11
|
+
## Design
|
|
12
|
+
|
|
13
|
+
Design around the domain's data structures and public APIs, with one code
|
|
14
|
+
concept per real concept. Keep core types and entry points easy to find. Start
|
|
15
|
+
with the smallest types and APIs that are right; every line must earn its place.
|
|
16
|
+
|
|
17
|
+
A redesign deletes what it replaces, in the same change. Keep one
|
|
18
|
+
implementation and one owner for each fact: no `_old`, `_new`, `v2_` or backup
|
|
19
|
+
variants, and no compatibility shims unless a released database or a published
|
|
20
|
+
API with outside users requires one. Git holds old versions.
|
|
21
|
+
|
|
22
|
+
When code fails because it assumed a fixed context, prefer adapting to the
|
|
23
|
+
context over adding a guard, allowlist or precondition that refuses.
|
|
24
|
+
|
|
25
|
+
Name things for what they are (`Checkpoint`, not `CheckpointHelper`). Action
|
|
26
|
+
functions start with a verb. Private helpers start with an underscore.
|
|
27
|
+
|
|
28
|
+
## Python
|
|
29
|
+
|
|
30
|
+
Use Python 3.12 and uv for every Python command (`uv run pytest`, never bare
|
|
31
|
+
`python` or `pip`), in docs and scripts as well.
|
|
32
|
+
|
|
33
|
+
Write fully typed Python: annotate parameters and returns on every function
|
|
34
|
+
and method, including private helpers and tests, with `-> None` where it
|
|
35
|
+
applies. Use 3.12 forms (`list[T]`, `T | None`). Represent structured domain
|
|
36
|
+
data with named types; do not pass untyped dictionaries or positional tuples
|
|
37
|
+
across application boundaries. Prefer precise unions and literals to `Any`. At
|
|
38
|
+
untyped library or JSON boundaries, validate once and expose a typed
|
|
39
|
+
interface; keep a necessary cast or suppression local, with its reason. Do not
|
|
40
|
+
weaken a type to silence the checker.
|
|
41
|
+
|
|
42
|
+
A nullable annotation means `None` is a real value; a default decides whether
|
|
43
|
+
an argument may be omitted. Return `None` for expected absence only where the
|
|
44
|
+
API defines it. Raise for violated invariants and impossible states, and never
|
|
45
|
+
disguise a failure as an empty result.
|
|
46
|
+
|
|
47
|
+
Put imports at the top and import explicitly from the defining module. There
|
|
48
|
+
are two exceptions, each with a comment saying why. An optional dependency is
|
|
49
|
+
imported where it is used. A command line or other top-level entry point
|
|
50
|
+
imports what one command needs inside that command, so starting the program
|
|
51
|
+
loads only what that command runs; keep these entry points as fast to start
|
|
52
|
+
as possible, and keep the rule everywhere below them. Declare dependencies in
|
|
53
|
+
`pyproject.toml`.
|
|
54
|
+
|
|
55
|
+
## Comments and docs
|
|
56
|
+
|
|
57
|
+
Give a substantial module a short opening note: its purpose, principal types
|
|
58
|
+
and entry points, and any non-obvious ownership or data flow. Explain intent,
|
|
59
|
+
invariants, units and algorithmic choices beside the code that depends on
|
|
60
|
+
them. Keep docstrings short; include non-obvious contracts, side effects and
|
|
61
|
+
failure behaviour. No `Args:`/`Returns:` sections that repeat a signature, no
|
|
62
|
+
comments that restate code, and no comments that narrate a change.
|
|
63
|
+
|
|
64
|
+
Wrong documentation is worse than none: update it with the behaviour or delete
|
|
65
|
+
it. In user-facing docs, lead with an example and keep prose terse around it;
|
|
66
|
+
tables are for lookup, not teaching.
|
|
67
|
+
|
|
68
|
+
## Tests
|
|
69
|
+
|
|
70
|
+
Test behaviour a user would care about, and assert on results. Mock side
|
|
71
|
+
effects (network, subprocesses, paid services) but never assert on how a mock
|
|
72
|
+
was called. Do not reshape production code for a test: no interface, factory
|
|
73
|
+
or extra parameter that exists only for test doubles. Delete a flaky test
|
|
74
|
+
instead of adding retries.
|
|
75
|
+
|
|
76
|
+
## Development
|
|
77
|
+
|
|
78
|
+
Before calling work done, review it as a sceptical maintainer would: can it be
|
|
79
|
+
explained in one screen, does the API map to the real thing or to an accident
|
|
80
|
+
of implementation, what breaks at 2 a.m. and will the logs say why, is each
|
|
81
|
+
dependency and knob earning its keep, and would deleting code make it more
|
|
82
|
+
true. Fix what that finds or record it.
|
|
83
|
+
|
|
84
|
+
Make small coherent pull requests with useful descriptions and focused checks.
|
|
85
|
+
Linux account/permission tests use a disposable container’s native filesystem,
|
|
86
|
+
not macOS bind mounts (which can present different permission behavior). Keep
|
|
87
|
+
fixture inputs below its private root home, then copy journals out after execution.
|
|
88
|
+
Worker deadline tests resolve runner dependencies from the fixture image's local
|
|
89
|
+
wheelhouse with networking disabled; downloading packages must not consume the
|
|
90
|
+
short execution window used to test partial-output retention.
|
|
91
|
+
|
|
92
|
+
Commit messages say what changed and why in a sentence to a paragraph, not a
|
|
93
|
+
list of edits. Never add Co-Authored-By or other AI attribution footers.
|
|
94
|
+
|
|
95
|
+
Keep secrets out of code, output and artifacts. Prefer running a command with
|
|
96
|
+
the secret injected to reading the value. A raw value must never reach a
|
|
97
|
+
terminal, log or chat: consume it inline or redirect it. If one leaks, say so
|
|
98
|
+
and flag it for rotation without quoting it again.
|
|
99
|
+
|
|
100
|
+
Launcher records use frozen Pydantic models with named constructor fields and
|
|
101
|
+
model-owned JSON serialization. Keep `providers/runpod/http.py`,
|
|
102
|
+
`providers/runpod/pod.py`, `providers/package.py` and `environment.py`
|
|
103
|
+
standard-library-only: the RunPod guardian must start before
|
|
104
|
+
client dependency installation. The client and each job use separate environments.
|
|
105
|
+
|
|
106
|
+
Check before pushing: `uv run pytest`, `uv run mypy`, `uv run ruff check .` and
|
|
107
|
+
`uv run ruff format --check .`. CI runs the tests with the outside network
|
|
108
|
+
denied (`scripts/test_network.py`).
|
|
109
|
+
|
|
110
|
+
## Code style
|
|
111
|
+
|
|
112
|
+
Readability matters more now that code is cheap to write: optimise for the
|
|
113
|
+
person reading it.
|
|
114
|
+
|
|
115
|
+
**Names.** Prefer clarity over cleverness. Use one word per concept and use it
|
|
116
|
+
everywhere. Every word in a path or name should be different from its
|
|
117
|
+
neighbours and tell the reader something: `etude.launch.scheduler`, not
|
|
118
|
+
`etude.launch.launcher`. Avoid words that say nothing, such as engine, service,
|
|
119
|
+
contract, api, util or manager. Before choosing a name, an interface or a
|
|
120
|
+
layout, look at what established tools call the same thing and say what you
|
|
121
|
+
found.
|
|
122
|
+
|
|
123
|
+
**Imports.** Write every import as a full path from the module that defines the
|
|
124
|
+
name. Relative imports are a lint error. Put every import at the top of its
|
|
125
|
+
module: an import inside a function is a lint error too. If that makes two
|
|
126
|
+
modules import each other, move the shared part or pass it in; do not hide the
|
|
127
|
+
cycle inside a function. Keep every `__init__.py` empty, so importing one
|
|
128
|
+
module never loads the rest.
|
|
129
|
+
|
|
130
|
+
**Layout.** Add a folder only when it has a rule a test can enforce, such as
|
|
131
|
+
"nothing in here imports the scheduler". A theme is not a reason for a folder.
|
|
132
|
+
|
|
133
|
+
**Records.** Anything stored or sent between machines is a frozen Pydantic
|
|
134
|
+
model. Do not keep a second kind of record beside them.
|
|
135
|
+
|
|
136
|
+
**Command line.** Use Typer. Every input is a `--flag`; do not add positional
|
|
137
|
+
conventions a person has to remember, such as a bare `--` or `name=value`.
|
|
138
|
+
Keep output plain, because agents and logs read it.
|
|
139
|
+
|
|
140
|
+
**Python is the interface.** Work is described in Python, not in a config
|
|
141
|
+
format. A command-line command is a thin way to call the same functions.
|
|
142
|
+
|
|
143
|
+
**Dependencies.** Manage them with uv. The launcher and tracking use one set of
|
|
144
|
+
versions for every dependency, from one lock file, whether they ship as one
|
|
145
|
+
package or several. Each must work without the other being used. Code Etude runs on someone else's
|
|
146
|
+
machine gets its own environment, separate from the job's. The one exception
|
|
147
|
+
is anything that must start before an install can finish, such as the process
|
|
148
|
+
that deletes a rented machine at its deadline: that stays standard library
|
|
149
|
+
only.
|
|
150
|
+
|
|
151
|
+
**Consequential actions are explicit.** Spending money, deleting something or
|
|
152
|
+
publishing something is never a side effect of a command that reads as doing
|
|
153
|
+
something else.
|
|
154
|
+
|
|
155
|
+
**What a command may wait on.** Every command is one of three kinds and says
|
|
156
|
+
which. A read answers from the database, at once, and says how fresh its answer
|
|
157
|
+
is. An ask records what is wanted and returns with an id. Only a wait may
|
|
158
|
+
depend on another machine, and it has a time limit. A command needs only what
|
|
159
|
+
its kind implies: a read must work with no provider key and no network.
|
|
160
|
+
|
|
161
|
+
**Measure by counting.** Machine load makes timings unreliable, so tests count
|
|
162
|
+
what matters to a user: calls to a provider, a bucket, SSH and git, database
|
|
163
|
+
queries, and modules imported at start-up. Pin those numbers in tests. A change
|
|
164
|
+
to a path that reaches another machine comes with a counting test.
|
|
165
|
+
|
|
166
|
+
Launcher vocabulary: Machine owns acquisition and provider facts; Rental owns
|
|
167
|
+
only a paid window, keyed by its machine; RunAttempt is the allocation on any
|
|
168
|
+
machine. Session is internal: one start of Etude's process on a machine, and its
|
|
169
|
+
journal folder. The process itself is the worker (`etude worker`). Use rent and
|
|
170
|
+
terminate for making and deleting paid machines. A record refers to another by
|
|
171
|
+
its name or id as a plain `str`. While the launcher is a prototype its database
|
|
172
|
+
has no migrations: change the schema version and refuse older files.
|
etude_sdk-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Loopflow Studio
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
etude_sdk-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: etude-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Launch training runs on your own or rented machines and track what they record
|
|
5
|
+
Project-URL: Repository, https://github.com/loopflowstudio/etude
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.12
|
|
9
|
+
Requires-Dist: numpy<3,>=1.26
|
|
10
|
+
Requires-Dist: pydantic<3,>=2.12
|
|
11
|
+
Requires-Dist: typer<1,>=0.27
|
|
12
|
+
Provides-Extra: s3
|
|
13
|
+
Requires-Dist: boto3<2,>=1.35; extra == 's3'
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# Etude
|
|
17
|
+
|
|
18
|
+
Launch training runs on your own or rented machines, and track what they record.
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
# job.py
|
|
22
|
+
from etude.launch.records import Job
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def runs() -> Job:
|
|
26
|
+
return Job(command="uv run python train.py", max_hours=2)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
# train.py
|
|
31
|
+
from etude.track.records import Step
|
|
32
|
+
from etude.track.run import init
|
|
33
|
+
|
|
34
|
+
run = init(experiment="capacity-pilot", clocks=[Step("updates", "optimizer updates")])
|
|
35
|
+
for update in range(1, 1001):
|
|
36
|
+
loss = train_one_update()
|
|
37
|
+
run.log({"loss": loss}, updates=update)
|
|
38
|
+
if update % 500 == 0:
|
|
39
|
+
run.checkpoint(save_model(), updates=update)
|
|
40
|
+
run.finish()
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
etude launch job.py # run it on this computer, an SSH host or a rented GPU
|
|
45
|
+
etude status # where runs stand
|
|
46
|
+
etude logs RUN # what a run wrote
|
|
47
|
+
etude reports write --open # charts of every run, as one HTML file per experiment
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Everything a run records goes to your own directory or S3 bucket. There is no
|
|
51
|
+
server to run and no account to make. A run that loses its machine resumes from
|
|
52
|
+
its last checkpoint on another, and reads back as one run.
|
|
53
|
+
|
|
54
|
+
## Install
|
|
55
|
+
|
|
56
|
+
Etude needs Python 3.12 and [uv](https://docs.astral.sh/uv/).
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
uv tool install etude-sdk # the `etude` command
|
|
60
|
+
uv add etude-sdk # the library, in a training project
|
|
61
|
+
uv add 'etude-sdk[s3]' # with S3 storage
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Read more
|
|
65
|
+
|
|
66
|
+
- [Tracking](docs/tracking.md): runs, clocks, checkpoints, evaluations, reports.
|
|
67
|
+
- [The launcher](docs/launcher.md): pools, SSH hosts, rented machines, cohorts,
|
|
68
|
+
limits and recovery.
|
|
69
|
+
|
|
70
|
+
## Develop
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
uv sync
|
|
74
|
+
uv run pytest
|
|
75
|
+
uv run mypy
|
|
76
|
+
uv run ruff check . && uv run ruff format --check .
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
[AGENTS.md](AGENTS.md) holds the conventions, for people and coding agents.
|
|
80
|
+
|
|
81
|
+
## Licence
|
|
82
|
+
|
|
83
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Etude
|
|
2
|
+
|
|
3
|
+
Launch training runs on your own or rented machines, and track what they record.
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
# job.py
|
|
7
|
+
from etude.launch.records import Job
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def runs() -> Job:
|
|
11
|
+
return Job(command="uv run python train.py", max_hours=2)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
# train.py
|
|
16
|
+
from etude.track.records import Step
|
|
17
|
+
from etude.track.run import init
|
|
18
|
+
|
|
19
|
+
run = init(experiment="capacity-pilot", clocks=[Step("updates", "optimizer updates")])
|
|
20
|
+
for update in range(1, 1001):
|
|
21
|
+
loss = train_one_update()
|
|
22
|
+
run.log({"loss": loss}, updates=update)
|
|
23
|
+
if update % 500 == 0:
|
|
24
|
+
run.checkpoint(save_model(), updates=update)
|
|
25
|
+
run.finish()
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
etude launch job.py # run it on this computer, an SSH host or a rented GPU
|
|
30
|
+
etude status # where runs stand
|
|
31
|
+
etude logs RUN # what a run wrote
|
|
32
|
+
etude reports write --open # charts of every run, as one HTML file per experiment
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Everything a run records goes to your own directory or S3 bucket. There is no
|
|
36
|
+
server to run and no account to make. A run that loses its machine resumes from
|
|
37
|
+
its last checkpoint on another, and reads back as one run.
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
Etude needs Python 3.12 and [uv](https://docs.astral.sh/uv/).
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
uv tool install etude-sdk # the `etude` command
|
|
45
|
+
uv add etude-sdk # the library, in a training project
|
|
46
|
+
uv add 'etude-sdk[s3]' # with S3 storage
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Read more
|
|
50
|
+
|
|
51
|
+
- [Tracking](docs/tracking.md): runs, clocks, checkpoints, evaluations, reports.
|
|
52
|
+
- [The launcher](docs/launcher.md): pools, SSH hosts, rented machines, cohorts,
|
|
53
|
+
limits and recovery.
|
|
54
|
+
|
|
55
|
+
## Develop
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
uv sync
|
|
59
|
+
uv run pytest
|
|
60
|
+
uv run mypy
|
|
61
|
+
uv run ruff check . && uv run ruff format --check .
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
[AGENTS.md](AGENTS.md) holds the conventions, for people and coding agents.
|
|
65
|
+
|
|
66
|
+
## Licence
|
|
67
|
+
|
|
68
|
+
MIT. See [LICENSE](LICENSE).
|