Run work and see what ran¶
mosaic run executes one feature or one op under the job contract: the run is
identified, its parameters are recorded, its progress is reported, and its outcome is
appended to a run log.
mosaic run -m dataset.yaml --feature speed-angvel
mosaic run -m dataset.yaml --kind transcode --entries trial01 --params '{"target": "analysis"}'
mosaic run -m dataset.yaml --feature global-tsne --json # machine-readable outcome
Features and ops go through the same entry point, which is why a pipeline can hold both.
Scoping¶
Parameters come inline as JSON, from @file.json, or from @- on stdin. --scope
takes the same three forms.
Both a feature run and an op run narrow with the same four flags. --entries takes
explicit group:sequence pairs, and a bare token is a sequence in the empty group.
A token splits on its first colon. Everything before that colon is the group. The
token one:two names the group one. The sequence one:two in the empty group is
written :one:two, and a group whose own name contains a colon has no token spelling.
--groups and --sequences name a cross product the dataset enumerates, and either
may be given without the other. --entries cannot be combined with them.
Only an explicit pair list can express an arbitrary set — groups and sequences
combine as a cross-product, and three specific recordings out of a grid are not one.
--scope names the whole selector as JSON, in the shape of the Scope model the
Python example below constructs:
A program submitting work uses it, having formatted a selector rather than typed one.
A pair arrives as a two-element array. Nothing splits it on the way in. That is the
difference from --entries, and every group name reaches the run as it was written.
--scope excludes --entries, --groups and --sequences. Name the scope one way
or the other. --params and --scope cannot both read stdin, because the first @-
consumes it.
A scope key inside --params is refused. --params names the settings a feature's
or an op's model validates, and a selector is not one of them.
The same holds in Python:
from mosaic.core.scope import Scope
ds.run_feature(
SpeedAngvel(),
scope=Scope(entries=[("day1", "trial01"), ("day2", "trial07")]),
)
Reading the outcome¶
mosaic runs -m dataset.yaml --kind feature --json
mosaic status -m dataset.yaml --execution-id <ULID> --progress --json
mosaic cancel -m dataset.yaml --execution-id <ULID>
mosaic cancel -m dataset.yaml --request <request id>
mosaic runs lists attempts, mosaic status reports one, and mosaic cancel sends a
signal to a running attempt's recorded process. With --request it signals the
request's driver, which stops the running step and records the others cancelled.
A request without a running driver has its running steps signalled and its other
steps recorded cancelled. A process on another machine is not signalled.
mosaic pipeline status -m dataset.yaml --request <request id> reports a submitted
request step by step. --wait-seconds 45 holds the report until a step of the request
ends, the request does, or the time is up, so a script need not poll or watch the
driver's process. --brief lists only the steps that are running or did not finish
cleanly, and counts the rest.
When a process is gone¶
Each attempt reports a liveness: alive, dead, ended, or unknown.
mosaic pipeline status shows it beside each step's phase, and the agent tools report
it too.
- A process on this machine is
alivewhile its pid runs anddeadonce it has exited. A machine that rebooted since the attempt started also makes itdead. - A process on another machine writes a line to its run-log at least once a minute.
It is
deadafter ten minutes without one. - A run-log written by mosaic 0.17 or earlier on another machine declares no
heartbeat, so this machine cannot tell. Such an attempt is
unknown.
A request reads orphaned when nothing drives it any more: its driver stopped and no
step process may still run, or every step that started is dead. Resume it with
mosaic pipeline run -m dataset.yaml --request <request id>. Its finished steps are
skipped. mosaic cancel records a dead attempt cancelled without signalling
anything.
A failed step ends only the branch below it. The steps below it read failed with
the error type ParentFailed, which names the failed step, and the other steps still
run. A request that has ended is not run again, and mosaic pipeline run --request
reports its status and starts nothing. Answer the failed step, then submit the recipe
again. Its finished steps are cache hits.
For an unknown attempt, first make sure its process has exited. Then record it and
submit the recipe again, and the steps that finished are cache hits:
--assume-exited is refused for an attempt whose heartbeat is current.
A process is matched to its machine by host name, so every machine that shares a
dataset needs a host name of its own. A Docker container gets a new host name each
time it starts. Give it a stable one with docker run --hostname <name>, and run one
container with that name at a time.
A request submitted with --cleanup superseded removes its approved superseded runs
after every step has finished, and stays running until that is done.
mosaic pipeline status prints the cleanup below the steps: its status, how many runs
it removed of those approved, and each run it kept with the reason. skipped means a
step did not finish, so nothing was removed. A run removed because its recipe was
retired names that recipe (removed[].retired under --json). A cleanup that fails or
is cancelled leaves the request's verdict to its steps. A queue that runs the steps
itself runs no cleanup; mosaic pipeline run --request does.
Compute several entries at once. A feature that computes each entry on its own
(parallelizable in mosaic pipeline catalog <name>) can use --workers:
mosaic pipeline run -m dataset.yaml --recipe @recipe.json --workers 4
mosaic pipeline run -m dataset.yaml --request <request id> --workers 4
The count changes neither a run's identity nor its outputs, so a run with four workers
is a cache hit for one with one, and a recipe never carries it. --parallel-mode thread
is the default. process uses every core, but each worker is a separate interpreter
that imports the feature libraries and receives a pickled copy of each entry's table,
so it uses much more memory. A worker that the host stops ends the step with
WorkerProcessDied. Run the request again with fewer workers or in thread mode. The
entries already written are kept. --min-free-memory-fraction 0.1 makes a run wait for
its entries in flight while less than that share of the host's memory is available, and
fail in a resumable state when it stays lower. The commands that take the options are
mosaic run --feature, mosaic run --graph-request and mosaic pipeline run.
A run reports what it lost. A failure on one entry is recorded and surfaced as a
partial status with the failed entries named. It is not silently dropped, and it is
not promoted to a total failure. Losing every entry does raise.
That matters most before a global fit: a scaler fitted over eleven sequences when you meant twelve is a wrong result, not a smaller one. Check for shortfalls before fitting.
Where what-ran is recorded¶
There is no database on the dataset filesystem. Everything is plain files, which works on NFS, HPC scratch and external drives alike.
Results live in each feature's features/<name>/index.csv — one row per computed
entry with run_id, version, params_hash, group, sequence, abs_path and
finished_at — beside the parquet outputs themselves. Cache-hit and completeness are
decided by globbing those parquet files, never by a status flag.
from mosaic.core.pipeline import list_feature_runs
list_feature_runs(dataset, "speed-angvel__from__tracks")
Attempts live in one append-only JSONL run-log per attempt, under
<dataset_root>/.mosaic/runs/<execution_id>.jsonl. Each records its lifecycle
(started → finished / failed / cancelled), a liveness heartbeat, the phase
the attempt is in (fit, then apply, for a fitted feature), and coarse per-entry
progress. While an attempt runs, an alive line is added after each minute in which
nothing else was written. One writer, append-only, so it is NFS-safe. These files are
ephemeral: bounded by the work, at most one alive line a minute, and safe to age
out.
from mosaic.runlog import read_runs, read_run, run_log_dir
run_dir = run_log_dir(dataset.base_dir)
read_runs(run_dir, kind="feature") # newest-first attempt snapshots
read_run(run_dir, "<execution_id>") # one attempt's status and progress counters
The distinction is worth holding on to: the index says what exists, the run log says what was attempted. A run that failed leaves a log entry and no index row.