Python SDK
neural_studio lets any Python process create a run on the Experiments
page and stream metrics, console output, system telemetry, model statistics,
artifacts and media to it. Inside a Neural Studio runtime it needs no setup;
anywhere else it needs an API key.
import neural_studio as ns
run = ns.init(name="resnet-baseline", config={"lr": 3e-4, "batch_size": 256}, tags=["baseline"])
for step, batch in enumerate(loader):
loss = train_step(batch)
run.log({"loss": loss, "lr": scheduler.get_last_lr()[0]}, step=step)
run.finish() # also runs automatically at process exit
Installing
- In a runtime: the SDK is preinstalled in every environment image, so
import neural_studioworks in a notebook cell and in the terminal. The image carries no optional extras. For GPU telemetry runpip install nvidia-ml-py; for images from arrays, JPEG or WebP encoding and Pillow inputs runpip install numpy Pillow. Do not runpip install "neural-studio[media]"inside a runtime: it would replace the preinstalled SDK with the release on PyPI. - Anywhere else:
pip install neural-studio(Python 3.10 to 3.13; the only dependencies arerequestsandpsutil). The extras areneural-studio[gpu](NVML bindings for GPU telemetry) andneural-studio[media](numpy and Pillow). The package also installs thenscommand.
Authentication
Inside a runtime the kernel and the web terminal carry a session key, so
ns.init authenticates on its own and the workspace is known. Outside, a
full-access API key is resolved in this order, first hit
wins:
- the
NEURAL_STUDIO_API_KEYenvironment variable; - the
api_key=argument tons.init; - inside a runtime only, the session key;
api_key = "ns_..."in the config file.
The config file is the first of these that exists: $NEURAL_STUDIO_CONFIG,
$XDG_CONFIG_HOME/neural-studio/config.toml,
~/.config/neural-studio/config.toml, ~/.neural-studio/config.toml. It is
TOML with two keys, api_key and api_url. The API base URL is api_url=,
then NEURAL_STUDIO_API_URL, then the config file, then
https://api.neuralstudio.co. The key travels in the X-API-Key header.
A session key can only create and feed runs in its own workspace; a full-access key can also read run data and open SSH. Neither can launch a runtime or touch files, environments, secrets or billing.
ns.init
ns.init(name, **options) creates the run and returns a Run handle. All
options are keyword-only.
| Argument | Default | What it does |
|---|---|---|
name | required | 1 to 200 characters. |
config | None | A dict of hyperparameters; the server's copy seeds run.config. |
tags | None | A list of strings. |
workspace | None | The workspace's UUID. Required outside a runtime, also in offline mode; inside a runtime it defaults to the session's workspace, and echo $NEURAL_STUDIO_WORKSPACE_ID in the terminal prints it. Names are not accepted. |
description | None | Up to 4,000 characters. |
api_key | None | See the resolution order above. |
api_url | None | Highest priority for the API base URL. |
enable_system_metrics | True | Starts the background sampler (below). When False, samples passed to run.log_system_metrics are not sent either. |
system_metrics_interval | 15.0 | Seconds between samples; must be positive. |
autocapture | True | Records the Python version, platform, installed packages, CUDA availability and version, and the git commit, branch and dirty flag when the working directory is a repository. There are no framework hooks: nothing patches PyTorch, TensorFlow or JAX. |
rank | None | Distributed rank, else the RANK then LOCAL_RANK environment variables, else 0. Every rank logs to the same run. |
mode | "online" | "offline" never opens a socket; see Offline runs. |
settings | None | An ns.Settings (below). |
capture_console | True | Tees Python-level sys.stdout and sys.stderr into the run's Logs tab. Output reaches the terminal first and capture never blocks; \r redraws (tqdm) collapse to about one line a second. Writes that bypass sys.stdout are not captured. |
ns.init also installs an exception hook and an exit hook, so a run that
is never finished by hand is still closed when the process ends (see
finish). There is no project, group, resume or
context-manager form, and no module-level log function: log through the
handle.
The run handle
run.id, run.workspace_id, run.name, run.account_id and run.rank are
plain attributes. The rest:
run.log(metrics, step=None)appends scalars and never blocks: points land in an in-memory ring, a sealer thread writes them to a local SQLite spill file, and a sender thread ships gzip batches with retries. Names are 1 to 200 characters;boolbecomes 0 or 1; NaN and Inf are accepted and the charts mark divergence instead of dropping it.stepis a non-negativeint; when omitted, each metric counts its own steps from 0. Values may also be media (below). Logging afterfinishraisesRuntimeError.run.configis a live mapping.run.config.update({...})andrun.config["epochs"] = 10merge at the top level (a nested dict replaces the stored one) and send a background update; one update is at most 256 KiB of JSON. Offline runs journal updates forns sync.run.summaryis a read-only{metric: {"last", "min", "max", "step"}}maintained locally with the server's rules:lastandsteponly move forward, NaN and Inf never enterminormax.run.stepis the last explicit step or the highest automatic one.run.finishedisTrueoncefinishran.
Images, tables and histograms
A value in run.log may be one media object or a list of 1 to 256 of the
same kind. A key keeps one kind of media for the life of the run, and media
keys match ^[\w\-./: ]+$.
run.log({
"samples": [ns.Image(img.cpu().numpy(), caption=label) for img, label in examples],
"predictions": ns.Table(columns=["id", "label", "score"], data=rows),
"fc_weights": ns.Histogram(model.fc.weight),
}, step=step)
ns.Image(data, caption=None, *, format=None, thumbnail=True)takes PNG, JPEG or WebP bytes, a path to such a file, a numpy array (HxW, orHxWxCwith 1 to 4 channels; floats in[0, 1]are scaled, booleans become 0 or 255) or a Pillow image. A torch tensor is not accepted: passtensor.cpu().numpy(). Arrays are encoded as PNG with the standard library;format="jpeg"or"webp"and Pillow inputs need Pillow. Arrays larger than 4,096 px a side or 16.8 megapixels are downscaled; encoded bytes over those limits or over 4 MiB are refused. Captions are at most 500 characters.ns.Table(columns=None, data=None, *, dataframe=None, caption=None)takescolumnsplus rows, or a pandasdataframe=. Cells are text (at most 10,000 characters), numbers, booleans orNone; a non-finite number becomesNone. At most 10,000 rows, 200 columns and 8 MiB as JSON.ns.Histogram(values=None, *, bins=64, counts=None, edges=None, caption=None)cuts raw values (a list, a numpy array or a torch tensor; non-finite values are dropped) intobinsequal bins, or takes explicitcountsand one more strictly increasingedges. 1 to 512 bins.
Media is encoded on a background thread and sent in batches of up to 256 items or 32 MiB. The per-run and per-workspace caps are on the limits page.
Artifacts and attention maps
run.log_artifact(name, data, *, kind="file", content_type=None, step=None, metadata=None)attaches a file and never blocks.datais bytes, a path (opened on the upload thread) or a readable binary file object. Names match^[\w\-. /]{1,200}$with no..; one artifact is at most 64 MiB andmetadataat most 8 KiB. The server keys artifacts by(kind, name, step)and the last write wins;stepis not defaulted, so an artifact logged without one replaces the previous artifact of that name. Offline runs do not upload artifacts and drop them after one warning.run.log_attention(name, tensor, *, step=None, tokens=None, layer=None, dtype="float32")stores an attention map shaped[T,T],[H,T,T]or[B,H,T,T](numpy or torch; only batch element 0 is kept) as anattentionartifact the Artifacts tab renders as a heatmap. A map over 64 MiB is downcast to float16, and skipped with a warning if it still does not fit.
Model watching
run.watch(model, *, log="gradients", log_freq=100, stats=None, max_params=1024, scaler=None) registers hooks on a torch.nn.Module
(PyTorch must be importable; the SDK never installs it). Every log_freq
backward passes it records gradients/<param>/norm,
parameters/<param>/<stat> and gradients/global_norm at the run's current
step, right before the next run.log call's own points. log is
"gradients", "parameters" or "all"; stats picks parameter statistics
from norm, mean, std, absmax and zero_frac (default norm, mean,
std) and is ignored for gradient-only watching; at most max_params
parameters are watched, in model order. The returned watcher exposes
manifest, watched_names, flush_pending() and remove(). A second call
replaces the first. run.unwatch() flushes and detaches the hooks.
System metrics
A background thread samples the machine's CPU percentage and RAM in use
through psutil, and per-GPU utilisation, memory, temperature and power
through NVML when pynvml is importable. One sample every
system_metrics_interval seconds, posted in batches of four. The System tab
of the run shows them. run.log_system_metrics(sample) injects a sample
by hand (keys cpu_percent, ram_used_bytes, gpu_utilization,
gpu_memory_bytes, gpu_temperature_c, gpu_power_w); it needs the
sampler to be enabled and raises RuntimeError in offline mode.
Finishing a run
run.finish(exit_code=0, *, timeout=30.0, status=None) is idempotent.
The status is completed for exit code 0 and failed otherwise, unless
status names one of completed, failed, stopped or disconnected.
Within timeout seconds it detaches run.watch, stops console capture and
the sampler, drains metrics (60% of the time left), console lines, media
and uploads, logs one warning naming anything left behind, then marks the
run finished on the server. Whatever could not be delivered stays in the
spill file, with the finish owed, for ns sync.
A process that ends without calling finish is finished by the exit hook
within 5 seconds: failed with exit code 1 after an uncaught exception,
disconnected otherwise. A run whose process vanishes without either is
marked disconnected by the server once its heartbeats stop.
Offline runs and ns sync
Every run seals its batches into ./.neural-studio/run-<id>.db (one more
file per extra rank, run-<id>-r<rank>.db) before sending them, so a crash
or a dead network loses nothing. ns.init(..., mode="offline") goes one step
further and never opens a socket: no key is needed, the run gets a local id,
and config updates and console lines are journaled too. Artifacts are the
exception; they are dropped offline.
ns sync # scans ./.neural-studio and replays anything undelivered
ns sync path/to/run-<id>.db
ns sync, or ns.sync(path=None) from Python, creates each offline run on
the server (folding in the journaled config), replays metric batches,
console lines and media, performs any owed finish (an offline run ends
completed), and vacuums the file. Each batch carries a fixed id, so a
replay is delivered exactly once and running ns sync again is a no-op.
Credentials resolve exactly as for ns.init. The exit code is 0 when
nothing is left pending and nothing was discarded, else 1; from Python the
SyncReport has files_scanned, runs_created, batches_sent,
batches_dropped, log_lines_sent, media_sent, media_dropped,
runs_finished, unfinished, lost and clean.
In a runtime the working directory is ~/workspace, which is durable and
counts against your storage pool. Keep the spill on the local disk instead:
run = ns.init(name="...", settings=ns.Settings(spill_dir="/tmp/ns-spill"))
Settings
ns.Settings is a frozen dataclass of pipeline knobs, all optional:
ring_size (65,536 points in memory), seal_max_points (5,000),
seal_interval_s (2.0), send_backoff_base_s (0.5), send_backoff_cap_s
(60.0), spill_dir (./.neural-studio) and heartbeat_idle_s (30.0).
Workspace secrets
Inside a runtime, every secret the environment references by name is an
environment variable. ns.secrets.get("HF_TOKEN") reads it and raises
KeyError with a message that says why when it is absent (not defined in
the workspace, not referenced by the environment, or not inside a runtime);
ns.secrets.get(name, default) returns the default instead.
ns.secrets.names() and ns.secrets.missing() list the names that were
injected and the referenced names that did not resolve. Nothing here calls
the API. See Isolation and secrets.
Errors
All SDK errors derive from ns.NeuralStudioError: NeuralStudioAuthError
(no key could be resolved), NeuralStudioValidationError (a bad argument;
also a ValueError), NeuralStudioConfigError (an unreadable config file)
and NeuralStudioAPIError (a non-2xx response, with status_code, code,
message and details; a network failure has status_code 0 and code
network.error). A finished run raises plain RuntimeError on log, and
an unknown finish status raises ValueError.
Environment variables
| Variable | Meaning |
|---|---|
NEURAL_STUDIO_API_KEY | API key; wins over every other source. |
NEURAL_STUDIO_API_URL | API base URL. |
NEURAL_STUDIO_CONFIG | Path of the config file to read first. |
NEURAL_STUDIO_SESSION_API_KEY | Set in a runtime: the session key, and the signal that the SDK is inside a runtime. |
NEURAL_STUDIO_WORKSPACE_ID, NEURAL_STUDIO_SESSION_ID | Set in a runtime: the default workspace and the session the run is attached to. |
RANK, LOCAL_RANK | Default distributed rank. |
NS_USER_SECRET_NAMES, NS_USER_SECRETS_MISSING | Set in a runtime: the manifests behind ns.secrets. |
Related
- The ns command:
ns ssh-config,ns ssh,ns sync. - API keys: creating one and what it can reach.
- Experiments: what the runs look like in the app.
- Limits: artifact, log and media caps.