-
Notifications
You must be signed in to change notification settings - Fork 195
AIR CLI Integration: Port code_source Snapshotting #5897
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
75892be
0be392f
9d7e3d9
f0d74ce
0fb507e
c88b599
2964af6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| bundle: | ||
| name: air-run-submit | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
|
|
||
| === submit with a git code_source | ||
| >>> [CLI] experimental air run -f run.yaml | ||
| Submitted run 555 | ||
| View at: [DATABRICKS_URL]/jobs/runs/555 | ||
|
|
||
| === the ai_runtime_task carries the snapshot + provenance paths | ||
| >>> print_requests.py //api/2.2/jobs/runs/submit | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💯 |
||
| { | ||
| "method": "POST", | ||
| "path": "/api/2.2/jobs/runs/submit", | ||
| "body": { | ||
| "run_name": "submit-smoke", | ||
| "tasks": [ | ||
| { | ||
| "task_key": "submit-smoke", | ||
| "run_if": "ALL_SUCCESS", | ||
| "ai_runtime_task": { | ||
| "experiment": "submit-smoke", | ||
| "deployments": [ | ||
| { | ||
| "command_path": "/Workspace/Users/[USERNAME]/.air/cli_launch/submit-smoke/submit-smoke_[RUN_ID]/command.sh", | ||
| "compute": { | ||
| "accelerator_type": "GPU_1xH100", | ||
| "accelerator_count": 1 | ||
| } | ||
| } | ||
| ], | ||
| "code_source_path": "/Workspace/Users/[USERNAME]/.air/repo_snapshots/[SNAPSHOT_TARBALL]", | ||
| "git_state_path": "/Workspace/Users/[USERNAME]/.air/cli_launch/submit-smoke/submit-smoke_[RUN_ID]/git_state.json" | ||
| }, | ||
| "environment_key": "default", | ||
| "max_retries": 3, | ||
| "retry_on_timeout": true | ||
| } | ||
| ], | ||
| "environments": [ | ||
| { | ||
| "environment_key": "default", | ||
| "spec": { | ||
| "environment_version": "4" | ||
| } | ||
| } | ||
| ], | ||
| "idempotency_token": "[UUID]" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| experiment_name: submit-smoke | ||
| command: python train.py | ||
| compute: | ||
| accelerator_type: GPU_1xH100 | ||
| num_accelerators: 1 | ||
| code_source: | ||
| type: snapshot | ||
| snapshot: | ||
| root_path: . | ||
| git: | ||
| commit: COMMIT_SHA |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| # Pinned commit dates keep the resolved HEAD SHA — and thus the snapshot cache key | ||
| # baked into the tarball name — stable across runs. | ||
| export GIT_AUTHOR_DATE="2020-01-01T00:00:00Z" | ||
| export GIT_COMMITTER_DATE="2020-01-01T00:00:00Z" | ||
| git-repo-init | ||
|
|
||
| # Pin the config to the committed HEAD. git.commit tolerates a dirty working tree | ||
| # (the pinned revision is what gets archived), which matters here because the | ||
| # acceptance harness writes output.txt into the working dir as the script runs. | ||
| sed "s/COMMIT_SHA/$(git rev-parse HEAD)/" run.yaml.tmpl > run.yaml | ||
|
|
||
| title "submit with a git code_source" | ||
| trace $CLI experimental air run -f run.yaml | ||
|
|
||
| title "the ai_runtime_task carries the snapshot + provenance paths" | ||
| trace print_requests.py //api/2.2/jobs/runs/submit | ||
|
|
||
| rm -fr .git |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| # A real (non-dry-run) submit that packages a git code_source, uploads the | ||
| # tarball + provenance sidecars, and POSTs runs/submit. No bundle deploy, so no | ||
| # engine matrix. | ||
| RecordRequests = true | ||
|
|
||
| # run.yaml is generated from run.yaml.tmpl at test time (commit SHA templated in); | ||
| # it isn't a committed input to diff. | ||
| Ignore = ["run.yaml"] | ||
|
|
||
| [EnvMatrix] | ||
| DATABRICKS_BUNDLE_ENGINE = [] | ||
|
|
||
| # The SDK probes host reachability with a HEAD request; stub it for determinism. | ||
| [[Server]] | ||
| Pattern = "HEAD /" | ||
| Response.Body = '' | ||
|
|
||
| [[Server]] | ||
| Pattern = "POST /api/2.2/jobs/runs/submit" | ||
| Response.Body = ''' | ||
| {"run_id": 555} | ||
| ''' | ||
|
|
||
| # The snapshot tarball is named <dir>_<cachekey[:16]>.tar.gz, where <dir> is the | ||
| # test's temp-dir basename and the cache key derives from the pinned commit SHA. | ||
| # Both are stable given the pinned commit dates in the script, but the temp-dir | ||
| # basename varies per run, so collapse the whole tarball filename to a stable token. | ||
| [[Repls]] | ||
| Old = '[a-zA-Z0-9._-]+_[0-9a-f]{16}\.tar\.gz' | ||
| New = '[SNAPSHOT_TARBALL]' | ||
|
|
||
| # The per-run launch directory ends in <run>_<16 hex>; the random suffix varies. | ||
| [[Repls]] | ||
| Old = 'submit-smoke_[0-9a-f]{16}' | ||
| New = 'submit-smoke_[RUN_ID]' |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| experiment_name: smoke-test | ||
| command: python train.py | ||
| compute: | ||
| accelerator_type: GPU_1xH100 | ||
| num_accelerators: 1 | ||
| code_source: | ||
| type: snapshot | ||
| snapshot: | ||
| root_path: . | ||
| git: | ||
| branch: main | ||
| remote: origin |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| experiment_name: smoke-test | ||
| command: python train.py | ||
| compute: | ||
| accelerator_type: GPU_1xH100 | ||
| num_accelerators: 1 | ||
| code_source: | ||
| type: snapshot | ||
| snapshot: | ||
| root_path: . | ||
| git: | ||
| branch: main | ||
| include_paths: | ||
| - src |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -52,6 +52,13 @@ type aiRuntimeTask struct { | |
| Deployments []aiRuntimeDeployment `json:"deployments"` | ||
| MlflowRun string `json:"mlflow_run,omitempty"` | ||
| MlflowExperimentDirectory string `json:"mlflow_experiment_directory,omitempty"` | ||
| // CodeSourcePath points at the uploaded snapshot tarball; the remote | ||
| // entry_script extracts it to /databricks/code_source/<dir>. GitStatePath and | ||
| // GitDiffPath are the optional provenance sidecars (see snapshot.go). All are | ||
| // omitempty: a workspace-only run (no code_source) carries none of them. | ||
| CodeSourcePath string `json:"code_source_path,omitempty"` | ||
| GitStatePath string `json:"git_state_path,omitempty"` | ||
| GitDiffPath string `json:"git_diff_path,omitempty"` | ||
| } | ||
|
|
||
| // environmentSpec carries the bare runtime channel ("4", "5", ...). | ||
|
|
@@ -106,8 +113,9 @@ func dlRuntimeImage(ctx context.Context, runtimeVersion string) string { | |
| } | ||
|
|
||
| // buildSubmitPayload assembles the runs/submit payload. commandPath is the | ||
| // workspace path of the uploaded command.sh; dlImage is the runtime channel. | ||
| func buildSubmitPayload(cfg *runConfig, commandPath, dlImage string) jobsSubmitRun { | ||
| // workspace path of the uploaded command.sh; dlImage is the runtime channel; snap | ||
| // carries the code snapshot paths (zero value when the run has no code_source). | ||
| func buildSubmitPayload(cfg *runConfig, commandPath, dlImage string, snap snapshotResult) jobsSubmitRun { | ||
| task := aiRuntimeTask{ | ||
| Experiment: cfg.ExperimentName, | ||
| Deployments: []aiRuntimeDeployment{{ | ||
|
|
@@ -117,6 +125,9 @@ func buildSubmitPayload(cfg *runConfig, commandPath, dlImage string) jobsSubmitR | |
| AcceleratorCount: cfg.Compute.NumAccelerators, | ||
| }, | ||
| }}, | ||
| CodeSourcePath: snap.CodeSourcePath, | ||
| GitStatePath: snap.GitStatePath, | ||
| GitDiffPath: snap.GitDiffPath, | ||
| } | ||
| if cfg.MLflowRunName != nil { | ||
| task.MlflowRun = *cfg.MLflowRunName | ||
|
|
@@ -195,14 +206,11 @@ func submitToken(flag string, cfg *runConfig) (string, error) { | |
| // upload the launch artifacts, assemble the Jobs payload, and submit it. It | ||
| // returns the new run_id and its dashboard URL. | ||
| func submitWorkload(ctx context.Context, w *databricks.WorkspaceClient, cfg *runConfig, configPath, idempotencyKey string) (int64, string, error) { | ||
| // Resolving usage_policy_name to a budget policy id and packaging a | ||
| // code_source snapshot are not ported yet; reject rather than silently drop. | ||
| // Resolving usage_policy_name to a budget policy id is not ported yet; reject | ||
| // rather than silently drop. | ||
| if cfg.UsagePolicyName != nil { | ||
| return 0, "", errors.New("usage_policy_name is not yet supported") | ||
| } | ||
| if cfg.CodeSource != nil { | ||
| return 0, "", errors.New("code_source is not yet supported") | ||
| } | ||
|
|
||
| // Resolve the idempotency token first so a bad key fails before any upload. | ||
| token, err := submitToken(idempotencyKey, cfg) | ||
|
|
@@ -240,8 +248,18 @@ func submitWorkload(ctx context.Context, w *databricks.WorkspaceClient, cfg *run | |
| return 0, "", err | ||
| } | ||
|
|
||
| // Package and upload the code snapshot, if any. The resulting paths ride on the | ||
| // ai_runtime_task; a run with no code_source leaves them empty. | ||
| var snap snapshotResult | ||
| if cfg.CodeSource != nil { | ||
| snap, err = snapshotCodeSource(ctx, w, cfg.CodeSource.Snapshot, configPath, base, funcDir) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You also need to check whether |
||
| if err != nil { | ||
| return 0, "", err | ||
| } | ||
| } | ||
|
|
||
| runtimeVersion, _ := cfg.runtimeVersion() | ||
| payload := buildSubmitPayload(cfg, path.Join(funcDir, commandScriptName), dlRuntimeImage(ctx, runtimeVersion)) | ||
| payload := buildSubmitPayload(cfg, path.Join(funcDir, commandScriptName), dlRuntimeImage(ctx, runtimeVersion), snap) | ||
| payload.IdempotencyToken = token | ||
|
|
||
| jc, err := newJobsSubmitClient(w) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
something to consider is a "how to review this pr" section in your readme which can detail what are the important aspects of the pr to consider.
I'd also love to see screenshots from this working e2e
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for this note, i will add in a 'how to review this pr' in future PRs. I have also added screenshots from the e2e tests I ran.
Regarding the speed for the research folder snapshot:
=> total time is ~9s (warm) and ~12s (cold) , which matches the python speed
the upload path will depend on the network but it scales with the size of the tarball which is 18.9 MB (18M) (compressed from the 75MB of source)