Kermt Continue Pretrain
NVIDIAOfficialPublished by NVIDIA
- Skill
- Workflow
- Data and AI
- Apache-2.0
- Medium risk
- Has scripts
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
The kermt-continue-pretrain skill manages the end-to-end workflow for continuing the pretraining of KERMT models (grover_base, cmim, or hybrid). It handles input validation, data preparation, and the execution of training runs within a containerized environment. The skill supports both fresh-schedule training and true resumption of interrupted runs, ensuring that model architecture and vocabulary constraints are strictly maintained.
What it does
- Validates system requirements and GPU availability for training.
- Resolves and validates user-provided or released KERMT checkpoints.
- Prepares custom SMILES data for training while enforcing vocabulary consistency.
- Configures and launches detached training runs with hyperparameter overrides.
- Provides progress monitoring and logging for long-running training tasks.
- Generates a manifest for reproducible training runs.
When to use it
- When you need to continue training an existing KERMT checkpoint on a new SMILES dataset.
- When you want to perform a fresh-schedule continuation of a pretrained model.
- When you need to resume an interrupted training run from a specific checkpoint.
- When you want to leverage NVIDIA-released pretrained hybrid models for further training.
Advantages
- Ensures architectural and vocabulary integrity by validating checkpoints before training.
- Automates complex data preparation and container orchestration.
- Supports flexible hyperparameter tuning while preventing invalid configuration overrides.
- Provides clear error handling and redirection to appropriate workflows for incompatible models.
Usage examples
- “Continue pretraining the hybrid model on my custom_data.csv.”
- The agent will prompt for consent to download the released hybrid model, validate the CSV, prepare the data, and launch the training process.
- “Resume my interrupted pretraining run from /path/to/checkpoint.pt.”
- The agent will validate the checkpoint for resume compatibility and restart the training process from the exact state saved in the checkpoint.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/nvidia/kermt-continue-pretrain/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip kermt-continue-pretrain.zip -d .agents/skills
Documentation by NVIDIA
kermt-continue-pretrain
Continue pretraining from a user-supplied KERMT checkpoint (grover_base / cmim / hybrid). The skill is the workflow orchestrator: it validates inputs, prepares the corpus, launches the runner, and returns a run directory.
Skill and runtime paths
Set SKILL_DIR to the absolute path of this installed skill directory. Export
KERMT_REPO as the absolute path to the KERMT checkout used for model
execution. The bundled container helper mounts that checkout at
/workspace and this skill at /skill (read-only). Commands inside
the container use /skill/scripts/; defaults are bundled in config/.
See Released models for checkpoint bundle requirements.
Downloads and local outputs
The optional released-model branch reads config/released_model.json for the
Hugging Face repository, pinned revision, and filenames. The bundled
scripts/fetch_released_model.py downloads the model bundle over HTTPS into
the host directory the user selects. Public models work without credentials;
if HF_TOKEN is set, the container helper forwards it for Hugging Face
authentication. Prepared data, logs, and workflow results go into the chosen
run directory.
Hardware requirements
GPUs: 1–N CUDA-capable NVIDIA GPUs. The runner auto-detects via
torch.cuda.device_count();--gpus 0,2overrides. On a single GPU the runner falls back to--batch_size 32 --save_interval 500; on multi-GPU it uses thedefaults_pretrain.jsonvalues (currentlybatch_size 256). Note:--gpus Nuses torch.cuda indexing, which can differ fromnvidia-smi's display order on multi-GPU hosts (PCI bus vs. CUDA enumeration). To target a specific physical GPU, setCUDA_VISIBLE_DEVICESbefore invoking, or runpython -c "import torch; print([torch.cuda.get_device_name(i) for i in range(torch.cuda.device_count())])"to confirm which device you're picking.VRAM: the default
--batch-size 256is sized for A100-class hardware (80 GB VRAM). On smaller GPUs, downscale to avoid OOM:GPU class VRAM Suggested --batch-sizeL4, T4, V100 16 GB 16–24 GB 32–64 A100 40 GB, L40, A40 40–48 GB 128 A100 80 GB, H100, H200 80 GB 256 (default) These are rough starting points — pass
--batch-size Nto override.Disk: tens of GB depending on corpus size + epochs (each checkpoint is several hundred MB).
Driver / CUDA: any host supporting CUDA 12.6 (the kermt image base).
kermt-setupvalidates this up-front.
Inputs
Required:
--csv <path>— the pretrain CSV (single columnsmiles). If you have separate train/val CSVs, pass--val-csv <path>too.
Checkpoint (optional — defaults to the released model if omitted):
--ckpt <path>— the input pretrain checkpoint to continue from. Must be a grover_base (with vocab heads), cmim, or hybrid ckpt; the validator rejects everything else with a redirect to the correct workflow. If omitted, the skill offers to download the released pretrained hybrid model nvidia/NV-KERMT-70M-v2 and continue-pretrain from it — see "Resolve & validate the checkpoint" (workflow step 3). The released bundle ships its three vocab files alongside the ckpt, so the authoritative-vocab pass-through (step 5) works automatically.--pretrained-release— explicit opt-in to use the released model without the interactive prompt (for non-interactive / agent runs). Mutually exclusive with--ckpt.--model-dir <dir>— where to save the downloaded bundle (default$KERMT_REPO/models/NV-KERMT-70M-v2/). An already-complete bundle there is reused, not re-downloaded.
Optional:
--val-csv <path>— separate validation CSV. Without it, the prep step auto-splits the input by--val-frac 0.1(random shuffle with--seed).--epochs N/--batch-size N/--init-lr F/--max-lr F/--final-lr F/--warmup-epochs F/--weight-decay F/--dropout F/--save-interval N/--seed N— training-hyperparameter overrides. Anything not given is filled fromconfig/defaults_pretrain.json.--vocab-loss-weight F(hybrid only) /--latent-dim N/--contrastive-temperature F(cmim and hybrid only) — loss / decoder overrides.--wandb-project NAME/--wandb-run-name NAME— optional Weights & Biases logging. When--wandb-projectis set, rank 0 logs train/val losses; the run name is honored only alongside a project. Off by default. (Independent of the ckpt'swandb_run_idcontinuity handling under--resume.)--resume— see "Modes" section below.--gpus 0,2— restrict to a GPU subset. Default uses all visible GPUs.--from-prepare <dir>— skip the prepare step and reuse an existingprepare_data.jsonin<dir>. Useful when iterating on hyperparameters.
Modes
The runner has two modes for ingesting the input ckpt, dispatched on whether
--resume is set. Pick based on intent:
Default (fresh-schedule continue-pretrain)
Use when: you have a finished pretrain ckpt and want to continue training it — on a new corpus, with a different objective, or just for more epochs than its original plan. The previous training's step counter and schedule shape are no longer relevant; you want a new learning-rate schedule for the new run.
What gets loaded from the ckpt:
✓ Model weights (encoder + vocab heads + contrast head + decoder, whatever is there)
✓ Optimizer state (Adam's running m1/m2 moments — warm-starts the new schedule so the first few hundred steps aren't dominated by noisy gradient-estimate startup)
✗ Scheduler step counter (reset to 0)
✗ Epoch counter (reset to 0)
✗ Batch counter (reset to 0)
✗ wandb run id (new wandb run, not a continuation)
Schedule shape (init/max/final LR, warmup epochs, total epochs): from
your CLI args or defaults_pretrain.json. A fresh NoamLR is constructed
from these values and starts at step 0.
--resume (true resume)
Use when: a previous run was interrupted (crash, OOM, Ctrl-C) and you want to pick up exactly where it left off — same dataset, same schedule, same training trajectory.
What gets loaded from the ckpt: everything in the
save_model_for_restart format. Model weights + optimizer state +
scheduler_step + epoch + batch_idx + wandb_run_id are all restored. The
new run continues from the saved step in the saved schedule (which is
recovered from the ckpt's saved_args). Mid-epoch resume works too —
pretrain_ddp.py's sampler skip-count picks up at the saved batch index
within the saved epoch.
Schedule shape: inherited from the ckpt's saved_args. CLI overrides
of any schedule flag (--epochs / --warmup-epochs / --init-lr / --max-lr / --final-lr) are rejected with a hard error — pure resume means pure
resume; if you want to change the schedule, drop --resume and start a
fresh-schedule run.
Requirements: the ckpt must have been saved via save_model_for_restart
(i.e., carry optimizer / scheduler_step / epoch / batch_idx keys). If
any of these is missing, the runner errors with a clear message and
suggests dropping --resume.
The default mode is the right choice ~90% of the time. Reach for --resume
only when you genuinely need to continue a single interrupted training
run.
Workflow
Let $KERMT_REPO be the path to your kermt repo checkout, and assume
kermt-setup has already built kermt:latest. All paths below are on the
host; the helper bind-mounts them at known container paths.
Pre-flight: ensure container + system probe.
"$SKILL_DIR/scripts/kermt_container.sh" check_system | python -c " import json, sys; d = json.load(sys.stdin) if not d['ok']: print('System check failed:', d['gaps']); sys.exit(1) print(f'OK: {len(d[\"gpus\"])} GPU(s); {d[\"disk\"][\"free_gb\"]} GB free; CUDA via container toolkit') "Surface any
gapsto the user. Refuse to proceed ifok: false.Compute run directory.
RUN_DIR=$KERMT_REPO/runs/continue-pretrain_$(date -u +%Y-%m-%dT%H-%M-%SZ)Resolve & validate the checkpoint.
Resolve — only if
--ckptwas omitted. Default to the released pretrained hybrid model nvidia/NV-KERMT-70M-v2:Consent gate. Unless
--pretrained-releasewas passed, ask the user: "No checkpoint given — download the released model nvidia/NV-KERMT-70M-v2 (NVIDIA Open Model License, https://huggingface.co/nvidia/NV-KERMT-70M-v2) and continue-pretrain from it? [y/N]". Never download without an explicit yes (or--pretrained-release). If both--ckptand--pretrained-releaseare given, abort — they conflict.Save location. Default
$KERMT_REPO/models/NV-KERMT-70M-v2/; honor--model-dir <dir>if given. An already-complete bundle is reused.Download (foreground; ~282 MB on first fetch):
"$SKILL_DIR/scripts/kermt_container.sh" run --model-dir <save-dir> -- \ "python /skill/scripts/fetch_released_model.py --out /model"Parse the JSON; abort on
ok: false(surfaceerrors). On success set<user-ckpt> = <save-dir>/kermt_contrastive_v2.0.pt. The bundle's three vocab files land in<save-dir>too, so step 5's--vocab-dirauto-detection (which looks in the ckpt's parent directory) finds them with no extra work.
Validate the resolved (or user-provided) ckpt:
"$SKILL_DIR/scripts/kermt_container.sh" run --ckpt <user-ckpt> -- \ "python /skill/scripts/check_checkpoint.py --mode continue_pretrain --ckpt /ckpt"Parse the JSON. Abort on
ok: false, showing the error verbatim. The error message redirects the user tokermt-add-cmim-pretrainfor encoder-only ckpts, or tokermt-finetunefor finetuned ckpts.Validate the data.
"$SKILL_DIR/scripts/kermt_container.sh" run --data <user-csv> -- \ "python /skill/scripts/check_data.py --mode pretrain --csv /data/<basename>"Abort on
ok: false.Prepare the data (skip if
--from-preparegiven). Pass the ckpt's vocab through. Look in the ckpt's parent directory for the conventionalpretrain_atom_vocab.{json,pkl},pretrain_bond_vocab.{json,pkl}, andpretrain_smiles_vocab.pklfiles (the bundling convention for released models; seereferences/released-models.md). If all three are present, auto-pass via--vocab-dir <ckpt_parent_dir>. If only some are present, pass them via explicit flags (--atom-vocab,--bond-vocab,--smiles-vocab). If none are present, ask the user for--vocab-dir— or refuse to proceed, because rebuilding a fresh vocab from the new corpus would silently mismatch the ckpt's vocab heads (the ckpt's vocab is authoritative for continue-pretrain).Note the two-layer mount pattern: pass the host directory to
kermt_container.sh --vocab-dir(which mounts it at/vocabinside the container), and reference/vocabfrom the innerprepare_data.pycommand. The same pattern applies to every host path the inner command needs to read (--data <host-csv>→/data/<basename>,--ckpt <host-ckpt>→/ckpt).VOCAB_DIR=$(dirname <user-ckpt>) "$SKILL_DIR/scripts/kermt_container.sh" run \ --data <user-csv> --vocab-dir $VOCAB_DIR --run-dir $RUN_DIR -- \ "python /skill/scripts/prepare_data.py --mode pretrain \\ --csv /data/<basename> --out /runs/data \\ --vocab-dir /vocab \\ [--val-csv /data/<val-basename>] [--val-frac 0.1] [--seed 0]"Outputs land at
$RUN_DIR/data/prepare_data.jsonwithvocab_source: "user_provided". The runner step 7 will verify the vocab files' entry counts match the ckpt's vocab-head sizes and refuse to launch on mismatch.Estimate runtime + confirm with user.
Pretrain wall time depends on corpus size × epochs × GPU count.
Tell the user the estimate; ask "proceed?" unless
--yesflag was given (agent-non-interactive case).Example estimate template:
~N hours on K GPUs for E epochs over M molecules (~steps/epoch × seconds/step).
Launch the runner detached.
"$SKILL_DIR/scripts/kermt_container.sh" run_detached \\ --name kermt-continue-pretrain-<ts> \\ --ckpt <user-ckpt> --run-dir $RUN_DIR -- \\ "python /skill/scripts/run_pretrain_local.py \\ --ckpt /ckpt \\ --prepare-manifest /runs/data/prepare_data.json \\ --out /runs \\ [--epochs N --batch-size N --init-lr F ...]"Returns the container name + id + log file path.
Report to the user. Output a short summary:
Container name + id
$RUN_DIR/run.json(the manifest with cmd_replay + image digest)Log file:
$RUN_DIR/logs/pretrain_ddp.logTensorBoard:
$RUN_DIR/logs/tb(open withtensorboard --logdir $RUN_DIR/logs/tb)Suggest invoking
kermt-monitor <RUN_DIR>to check progress.
Hard rules
Never download the released model without consent. When
--ckptis omitted, downloadnvidia/NV-KERMT-70M-v2only after an explicit user "yes" or an explicit--pretrained-releaseflag.--ckptand--pretrained-releaseare mutually exclusive.Never modify the user's input ckpt. The runner symlinks it into the save_dir; the symlink is what pretrain_ddp.py auto-resumes from. The source file stays untouched.
Never silently override arch. If the user passes a
--hidden-sizeetc. that doesn't match the ckpt-derived value, the runner aborts loudly. Arch params come from the ckpt, period.Never block on the long-running pretrain itself. The runner is invoked via
run_detached; the skill returns immediately after step 8. Usekermt-monitorfor progress.Echo applied defaults back to the user. The
args_appliedfield ofrun.jsonrecords every flag's value + source (user / default-config / auto-1gpu / auto-multi-gpu). Skill should surface a summary of any flag not user-specified so the user knows what was assumed.
Common errors
model_type='finetuned'rejected → the ckpt is a downstream finetune, not a pretrain. The error redirects to the relevant workflow.grover_base ckpt has no vocab head→ encoder-only ckpt (e.g. the original-grovergrover_base.pt). The error redirects tokermt-add-cmim-pretrain.prepare_data manifest is missing required outputs→ user passed--from-prepareto a directory where prepare was run with--skip-vocabor--skip-split. Re-run prepare without those flags.--gpus allnot available → installnvidia-container-toolkit; checkkermt_container.sh check_system.
Replayability
The run.json cmd_replay field is a single-line command that re-runs the
pretrain with the same inputs, hyperparameters, and arch. To replay:
# Inside the kermt container:
$(jq -r .cmd_replay $RUN_DIR/run.json)If ok_to_replay: false in the manifest (because the kermt repo working
tree was dirty at launch time), the replay may not be bit-exact — pin the
exact commit via the repo.commit field and git checkout it
first.
Files
- ATTRIBUTION.mdAdded by AutoGineer853 B
- BENCHMARK.md7.8 KiB
- LICENSELicence file added by AutoGineer11.2 KiB
- SKILL.md16.0 KiB
- config/defaults_pretrain.json2.6 KiB
- config/released_model.json398 B
- evals/evals.json7.5 KiB
- references/released-models.md1.6 KiB
- scripts/_utils.pyScript14.1 KiB
- scripts/check_checkpoint.pyScript20.1 KiB
- scripts/check_data.pyScript11.9 KiB
- scripts/fetch_released_model.pyScript7.9 KiB
- scripts/kermt_container.shScript18.9 KiB
- scripts/prepare_data.pyScript36.5 KiB
- scripts/run_pretrain_local.pyScript34.8 KiB
- skill-card.md4.6 KiB
- skill.oms.sig6.8 KiB
scripts/kermt_container.sh
#!/usr/bin/env bash
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
# kermt_container.sh — bootstrap helper for the kermt agent skills.
#
# Two ways to use this file:
#
# 1. As a subcommand dispatcher (recommended for skills):
# "$SKILL_DIR/scripts/kermt_container.sh" ensure_image
# "$SKILL_DIR/scripts/kermt_container.sh" run --ckpt /host/ckpt.pt -- python -c 'import torch; print(torch.cuda.device_count())'
# "$SKILL_DIR/scripts/kermt_container.sh" run_detached --name foo --run-dir runs/foo -- bash train.sh
#
# 2. Sourced into a shell or another script, then call the kermt_* functions
# directly:
# source "$SKILL_DIR/scripts/kermt_container.sh"
# kermt_ensure_image
# kermt_run --ckpt /host/ckpt.pt -- python ...
#
# Configuration (override via env vars before invocation):
# KERMT_IMAGE docker image tag (default: kermt:latest)
# KERMT_REPO host path to the kermt repo checkout (default: auto-derived
# from this script's location)
# KERMT_GPUS value passed to docker --gpus (default: all)
#
# Mount flags accepted by kermt_run / kermt_run_detached:
# --data <path> bind to /data (read-only). If <path> is a file,
# its PARENT directory is mounted at /data so
# commands can use /data/<basename>; if <path> is a
# directory, it is mounted at /data directly.
# --ckpt <path> bind to /ckpt (read-only; the path is mounted as-is)
# --vocab-dir <dir> bind to /vocab (read-only)
# --run-dir <dir> bind to /runs (read-write; created on host if missing)
# --model-dir <dir> bind to /model (read-write; created on host if missing).
# Target for released-model downloads (fetch_released_model.py).
#
# Additional flags for kermt_run_detached:
# --name <name> docker container name (default: kermt-<UTC-timestamp>-<pid>)
#
# Everything after `--` is the command passed to the container. It runs inside
# the `kermt` conda environment (the image's default env).
set -o pipefail
: "${KERMT_IMAGE:=kermt:latest}"
: "${KERMT_GPUS:=all}"
# The skill may be installed outside the KERMT checkout. Mount its own helpers
# separately so container commands always execute the distributed skill copy.
_kermt_script_dir="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
_kermt_bundle_dir="$(cd "$_kermt_script_dir/.." && pwd)"
if [[ -z "${KERMT_REPO:-}" ]]; then
for _kermt_start in "$_kermt_script_dir" "$PWD"; do
_kermt_candidate="$_kermt_start"
while [[ "$_kermt_candidate" != / ]]; do
if [[ -f "$_kermt_candidate/main.py" && -d "$_kermt_candidate/kermt" ]]; then
KERMT_REPO="$_kermt_candidate"
break 2
fi
_kermt_candidate="$(dirname "$_kermt_candidate")"
done
done
unset _kermt_start _kermt_candidate
fi
unset _kermt_script_dir
_kermt_require_repo() {
if [[ -z "${KERMT_REPO:-}" || ! -f "$KERMT_REPO/main.py" || ! -d "$KERMT_REPO/kermt" ]]; then
echo "[kermt] Set KERMT_REPO to the KERMT checkout containing main.py and kermt/." >&2
return 1
fi
KERMT_REPO="$(cd "$KERMT_REPO" && pwd)" || return $?
export KERMT_REPO
}
# -----------------------------------------------------------------------------
# Host environment checks
# -----------------------------------------------------------------------------
kermt_check_docker() {
if ! command -v docker >/dev/null 2>&1; then
echo "[kermt] error: docker not found on PATH. Install Docker first." >&2
return 1
fi
if ! docker info >/dev/null 2>&1; then
echo "[kermt] error: docker daemon not reachable. Is the docker service running, and is your user in the 'docker' group?" >&2
return 1
fi
}
kermt_check_system() {
# Probe host system and report GPU presence + VRAM + compute capability +
# driver / CUDA version + disk space. Emits a single JSON document to
# stdout that the calling skill consumes; exits 0 with `ok: false` and a
# populated `gaps` array when anything is below the per-workflow minimum,
# exits 1 only on unexpected internal errors. Uses host nvidia-smi + df +
# host python3 (stdlib only).
python3 - "$KERMT_REPO" "$KERMT_IMAGE" <<'PYEOF'
import json, os, shutil, subprocess, sys
repo, image = sys.argv[1], sys.argv[2]
result = {
"ok": True,
"gpus": [],
"disk": {"path": repo, "free_gb": None, "min_gb": 20},
"host": {"docker": None, "nvidia_smi": None, "container_toolkit": None},
"image": {"tag": image, "present_locally": None},
"gaps": [],
}
def _gap(msg):
result["ok"] = False
result["gaps"].append(msg)
# docker presence
try:
r = subprocess.run(["docker", "info"], capture_output=True, text=True, timeout=10)
result["host"]["docker"] = "ok" if r.returncode == 0 else f"failed: {r.stderr.strip().splitlines()[-1] if r.stderr else 'unknown'}"
if r.returncode != 0:
_gap("docker daemon not reachable (is the service running, and is your user in the 'docker' group?)")
except FileNotFoundError:
result["host"]["docker"] = "not found"
_gap("docker not on PATH; install Docker first")
except Exception as e:
result["host"]["docker"] = f"error: {e}"
_gap(f"docker probe failed: {e}")
# nvidia-smi (host driver)
try:
r = subprocess.run(
["nvidia-smi", "--query-gpu=name,memory.total,compute_cap,driver_version,uuid",
"--format=csv,noheader,nounits"],
capture_output=True, text=True, timeout=10,
)
if r.returncode == 0:
result["host"]["nvidia_smi"] = "ok"
for line in r.stdout.strip().splitlines():
parts = [p.strip() for p in line.split(",")]
if len(parts) >= 5:
try:
vram_mb = int(parts[1])
except ValueError:
vram_mb = None
result["gpus"].append({
"name": parts[0],
"vram_mb": vram_mb,
"compute_cap": parts[2],
"driver": parts[3],
"uuid": parts[4],
})
if not result["gpus"]:
_gap("nvidia-smi succeeded but reported no GPUs")
else:
result["host"]["nvidia_smi"] = "failed"
_gap("nvidia-smi found but failed; is the NVIDIA driver loaded?")
except FileNotFoundError:
result["host"]["nvidia_smi"] = "not found"
_gap("nvidia-smi not on PATH; install the NVIDIA driver")
except Exception as e:
result["host"]["nvidia_smi"] = f"error: {e}"
_gap(f"nvidia-smi probe failed: {e}")
# disk free at the repo location
try:
free_bytes = shutil.disk_usage(repo).free
free_gb = free_bytes // (1024**3)
result["disk"]["free_gb"] = free_gb
if free_gb < result["disk"]["min_gb"]:
_gap(f"disk free at {repo} is {free_gb} GB; need at least {result['disk']['min_gb']} GB for the kermt image")
except Exception as e:
_gap(f"could not check disk space at {repo}: {e}")
# image presence (informational only)
try:
r = subprocess.run(["docker", "image", "inspect", image], capture_output=True, text=True, timeout=10)
result["image"]["present_locally"] = (r.returncode == 0)
except Exception:
result["image"]["present_locally"] = None
# nvidia-container-toolkit probe — only meaningful if both docker and a
# locally-present image are available. Pick kermt:$tag first; fall back to
# the small CUDA base image if that's the only one present; otherwise skip
# (avoid pulling anything).
def _probe_image():
for img in (image, "nvidia/cuda:12.6.3-base-ubuntu22.04"):
r = subprocess.run(["docker", "image", "inspect", img], capture_output=True)
if r.returncode == 0:
return img
return None
probe_img = _probe_image()
if probe_img:
try:
r = subprocess.run(
["docker", "run", "--rm", "--gpus", "all", probe_img, "nvidia-smi"],
capture_output=True, text=True, timeout=60,
)
if r.returncode == 0:
result["host"]["container_toolkit"] = f"ok (probed via {probe_img})"
else:
result["host"]["container_toolkit"] = f"failed (probed via {probe_img})"
_gap("`docker run --gpus all` failed; install nvidia-container-toolkit and ensure the host driver supports it")
except Exception as e:
result["host"]["container_toolkit"] = f"error: {e}"
_gap(f"nvidia-container-toolkit probe failed: {e}")
else:
result["host"]["container_toolkit"] = "skipped (no probe image present locally; run ensure_image first)"
print(json.dumps(result, indent=2))
PYEOF
}
kermt_check_gpu() {
# Probes whether `docker --gpus all` is wired up (nvidia-container-toolkit).
# Image-selection priority (never pulls anything):
# 1) $KERMT_IMAGE if it exists locally,
# 2) else nvidia/cuda:12.6.3-base-ubuntu22.04 if it exists locally,
# 3) else skip with a warning (return 0). The smoke test inside kermt_run
# will catch broken GPU passthrough later anyway.
local probe_img=""
if docker image inspect "$KERMT_IMAGE" >/dev/null 2>&1; then
probe_img="$KERMT_IMAGE"
elif docker image inspect nvidia/cuda:12.6.3-base-ubuntu22.04 >/dev/null 2>&1; then
probe_img="nvidia/cuda:12.6.3-base-ubuntu22.04"
else
echo "[kermt] check_gpu: skipped — neither '$KERMT_IMAGE' nor 'nvidia/cuda:12.6.3-base-ubuntu22.04' is present locally. Run 'ensure_image' first, or this probe will be exercised by the in-container smoke test." >&2
return 0
fi
if ! docker run --rm --gpus all "$probe_img" nvidia-smi >/dev/null 2>&1; then
echo "[kermt] error: 'docker run --gpus all' failed (probe image: $probe_img). Install nvidia-container-toolkit and ensure the host has a CUDA-capable NVIDIA driver." >&2
return 1
fi
}
# -----------------------------------------------------------------------------
# Image build / verification
# -----------------------------------------------------------------------------
kermt_ensure_image() {
_kermt_require_repo || return $?
kermt_check_docker || return $?
if docker image inspect "$KERMT_IMAGE" >/dev/null 2>&1; then
local id
id=$(docker image inspect "$KERMT_IMAGE" --format '{{.Id}}' 2>/dev/null | cut -c1-19)
echo "[kermt] image '$KERMT_IMAGE' already present (${id:-unknown})"
return 0
fi
echo "[kermt] image '$KERMT_IMAGE' not found; building from $KERMT_REPO/Dockerfile"
echo "[kermt] first build typically takes 10-20 minutes on a typical workstation; subsequent runs reuse the cached image"
docker build -t "$KERMT_IMAGE" -f "$KERMT_REPO/Dockerfile" "$KERMT_REPO"
}
# -----------------------------------------------------------------------------
# Mount-flag parser, internal
# -----------------------------------------------------------------------------
# Reads flags from the caller's positional args until it hits '--', appending
# `-v src:dst[:ro]` pairs into the caller-provided array name (passed as $1).
# Returns the number of caller-provided args consumed via _kermt_consumed.
# This is bash-specific (uses nameref via `declare -n`).
_kermt_parse_mounts() {
local -n _out="$1"
shift
_kermt_consumed=0
while [[ $# -gt 0 ]]; do
case "$1" in
--)
return 0
;;
--data)
[[ -e "$2" ]] || { echo "[kermt] --data path not found: $2" >&2; return 1; }
# If the user passes a file, mount its parent directory at /data so
# downstream commands can refer to /data/<basename>. Mounting a
# single file at /data makes the path-as-directory pattern in the
# skill examples (`--csv /data/<basename>`) fail with "not found".
if [[ -d "$2" ]]; then
_out+=("-v" "$(realpath "$2"):/data:ro")
else
_out+=("-v" "$(realpath "$(dirname "$2")"):/data:ro")
fi
shift 2; _kermt_consumed=$((_kermt_consumed + 2))
;;
--ckpt)
[[ -e "$2" ]] || { echo "[kermt] --ckpt path not found: $2" >&2; return 1; }
_out+=("-v" "$(realpath "$2"):/ckpt:ro")
shift 2; _kermt_consumed=$((_kermt_consumed + 2))
;;
--vocab-dir)
[[ -d "$2" ]] || { echo "[kermt] --vocab-dir not found or not a directory: $2" >&2; return 1; }
_out+=("-v" "$(realpath "$2"):/vocab:ro")
shift 2; _kermt_consumed=$((_kermt_consumed + 2))
;;
--run-dir)
mkdir -p "$2" || { echo "[kermt] failed to create --run-dir: $2" >&2; return 1; }
_out+=("-v" "$(realpath "$2"):/runs")
shift 2; _kermt_consumed=$((_kermt_consumed + 2))
;;
--model-dir)
mkdir -p "$2" || { echo "[kermt] failed to create --model-dir: $2" >&2; return 1; }
_out+=("-v" "$(realpath "$2"):/model")
shift 2; _kermt_consumed=$((_kermt_consumed + 2))
;;
*)
return 0
;;
esac
done
}
# -----------------------------------------------------------------------------
# Foreground / detached run
# -----------------------------------------------------------------------------
# Capture host-side git state for the repo and emit `-e KERMT_REPO_COMMIT=…
# -e KERMT_REPO_DIRTY=true|false` flags. Used by the run / run_detached
# wrappers so the runner's run.json manifest gets honest commit info even
# though `git -C /workspace` inside the container fails due to bind-mount
# ownership.
_kermt_git_env_flags() {
local commit="unknown"
local dirty="false"
if command -v git >/dev/null 2>&1 && [[ -d "$KERMT_REPO/.git" ]]; then
local c
c=$(git -C "$KERMT_REPO" rev-parse HEAD 2>/dev/null) && commit="$c"
# `--untracked-files=no` filters out user-private notes (e.g. a CLAUDE.md
# or RELEASE_PLAN_v2.0.md at the repo root) that wouldn't affect
# reproducibility — only modifications to tracked files do.
if [[ -n "$(git -C "$KERMT_REPO" status --porcelain --untracked-files=no 2>/dev/null | head -n 1)" ]]; then
dirty="true"
fi
fi
printf '%s\n%s\n%s\n%s\n' "-e" "KERMT_REPO_COMMIT=$commit" "-e" "KERMT_REPO_DIRTY=$dirty"
}
# Forward HF_TOKEN into the container when it is set, so fetch_released_model.py
# can authenticate to Hugging Face. The current release is public (no token
# needed); this only guards against shared-IP rate limits or a future gated
# repo. Emits nothing when HF_TOKEN is unset.
_kermt_hf_env_flags() {
if [[ -n "${HF_TOKEN:-}" ]]; then
printf '%s\n%s\n' "-e" "HF_TOKEN=$HF_TOKEN"
fi
}
kermt_run() {
kermt_ensure_image || return $?
local mount_args=()
_kermt_parse_mounts mount_args "$@" || return $?
shift "$_kermt_consumed"
if [[ "${1:-}" != "--" ]]; then
echo "[kermt] expected '--' separating mount flags from the command (got '${1:-}')" >&2
return 1
fi
shift
if [[ $# -eq 0 ]]; then
echo "[kermt] no command supplied after '--'" >&2
return 1
fi
local git_args=()
while IFS= read -r line; do git_args+=("$line"); done < <(_kermt_git_env_flags)
local hf_args=()
while IFS= read -r line; do hf_args+=("$line"); done < <(_kermt_hf_env_flags)
docker run --rm --gpus "$KERMT_GPUS" \
--user "$(id -u):$(id -g)" \
-v "$KERMT_REPO:/workspace" \
-v "$_kermt_bundle_dir:/skill:ro" \
"${mount_args[@]}" \
-w /workspace \
-e KERMT_REPO=/workspace \
-e PYTHONPATH=/workspace \
-e HOME=/tmp/kermt-home \
"${git_args[@]}" \
"${hf_args[@]}" \
"$KERMT_IMAGE" \
conda run -n kermt --no-capture-output bash -c "$*"
}
kermt_run_detached() {
kermt_ensure_image || return $?
local name=""
local mount_args=()
# Pull --name out first, then let the shared mount parser handle the rest.
while [[ $# -gt 0 ]]; do
case "$1" in
--name) name="$2"; shift 2 ;;
--) break ;;
--data|--ckpt|--vocab-dir|--run-dir|--model-dir) break ;;
*) break ;;
esac
done
_kermt_parse_mounts mount_args "$@" || return $?
shift "$_kermt_consumed"
if [[ "${1:-}" != "--" ]]; then
echo "[kermt] expected '--' separating mount flags from the command (got '${1:-}')" >&2
return 1
fi
shift
if [[ $# -eq 0 ]]; then
echo "[kermt] no command supplied after '--'" >&2
return 1
fi
if [[ -z "$name" ]]; then
name="kermt-$(date -u +%Y%m%dT%H%M%SZ)-$$"
fi
local cid
local git_args=()
while IFS= read -r line; do git_args+=("$line"); done < <(_kermt_git_env_flags)
local hf_args=()
while IFS= read -r line; do hf_args+=("$line"); done < <(_kermt_hf_env_flags)
cid=$(docker run -d --gpus "$KERMT_GPUS" \
--user "$(id -u):$(id -g)" \
--name "$name" \
-v "$KERMT_REPO:/workspace" \
-v "$_kermt_bundle_dir:/skill:ro" \
"${mount_args[@]}" \
-w /workspace \
-e KERMT_REPO=/workspace \
-e PYTHONPATH=/workspace \
-e HOME=/tmp/kermt-home \
"${git_args[@]}" \
"${hf_args[@]}" \
"$KERMT_IMAGE" \
conda run -n kermt --no-capture-output bash -c "$*") || return $?
echo "[kermt] container started: name=$name id=$cid"
echo "[kermt] follow logs: docker logs -f $name"
echo "[kermt] wait for exit: docker wait $name"
echo "[kermt] stop: docker stop $name"
echo "$cid"
}
# -----------------------------------------------------------------------------
# Subcommand dispatch when invoked directly (not sourced)
# -----------------------------------------------------------------------------
if [[ "${BASH_SOURCE[0]:-$0}" == "${0}" ]]; then
cmd="${1:-}"; shift || true
case "$cmd" in
check_docker) kermt_check_docker "$@" ;;
check_gpu) kermt_check_gpu "$@" ;;
check_system) kermt_check_system "$@" ;;
ensure_image) kermt_ensure_image "$@" ;;
run) kermt_run "$@" ;;
run_detached) kermt_run_detached "$@" ;;
""|-h|--help)
cat >&2 <<EOF
usage: $0 <subcommand> [args...]
Subcommands:
check_docker Verify docker is installed and the daemon is reachable.
check_gpu Verify 'docker --gpus all' works (nvidia-container-toolkit).
check_system Emit a JSON probe of host GPU + VRAM + compute_cap +
driver + disk space + container toolkit + image presence.
Exits 0 with ok=false + a 'gaps' list when anything's
below the per-workflow minimum.
ensure_image Build kermt:latest from \$KERMT_REPO/Dockerfile if missing.
run [flags] -- ... Run a command inside the container (foreground, --rm).
run_detached [flags] -- ...
Run detached; prints container name + id + log hint.
Mount flags (for run / run_detached):
--data <path> bind to /data (read-only)
--ckpt <path> bind to /ckpt (read-only)
--vocab-dir <dir> bind to /vocab (read-only)
--run-dir <dir> bind to /runs (read-write; created on host if missing)
--model-dir <dir> bind to /model (read-write; released-model download target)
Additional flags for run_detached:
--name <name> container name (default: kermt-<timestamp>-<pid>)
Environment overrides:
KERMT_IMAGE default kermt:latest
KERMT_REPO checkout path; otherwise discovered above the skill or working directory
KERMT_GPUS default all
EOF
exit 1
;;
*)
echo "[kermt] unknown subcommand: $cmd" >&2
echo "[kermt] run '$0 --help' for usage" >&2
exit 1
;;
esac
fi
Requirements and compatibility
Written for: Claude Code, OpenAI Codex
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs Python
The author says: Requires docker, nvidia-container-toolkit, and a CUDA-capable NVIDIA GPU. Designed for Claude Code, Codex, and Nemotron.
Security
What our scanner noticed· Medium risk
- Large base64-encoded blobs
- Scripts that your agent may run
This skill includes scripts that your agent may run. Read them before you use it.
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Published by
- NVIDIA
- Publisher
- NVIDIA (Official)
- Source
- NVIDIA/skills · skills/bionemo-kermt-continue-pretrain · commit fce3cca
- Licence
- Apache License 2.0 (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE-APACHE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by NVIDIA or NVIDIA.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download kermt-continue-pretrain.zip