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/check_data.py
#!/usr/bin/env python3
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""Validate a CSV input for a given KERMT agent workflow.
Mode-dispatched. Emits a single JSON object to stdout that the calling skill
parses to decide whether to proceed (`ok: true`) or surface a structured error
back to the user (`ok: false` with `errors[]`). The JSON shape is stable
across modes; only the contract for what counts as `ok` differs per mode.
Modes
-----
pretrain Pretrain corpus CSV. Requires a `smiles` column. Other columns
are ignored. Label columns are not required (and not expected).
finetune Labeled CSV for a downstream task. Requires `smiles` plus
>=1 numeric target column. Target columns are specified via
`--targets <col1> <col2> ...`. If `--targets` is omitted, the
validator auto-detects numeric non-smiles columns and reports
them; the skill will then prompt the user to confirm or refine.
inference CSV to run predictions on. Requires `smiles`. Target columns are
not required (and not expected — predictions are written out).
embed CSV to extract embeddings from. Requires `smiles` only.
SMILES validation
-----------------
By default the validator samples up to 20 SMILES (first 10 + last 10) and
checks each one parses with RDKit. Pass `--strict-rdkit` to parse every
SMILES (slow on large corpora). A SMILES is considered "invalid" if RDKit
returns `None` from `MolFromSmiles(smi, sanitize=True)` — empty / null
rows are counted separately.
Duplicate-SMILES detection is always full (cheap).
Output (stdout)
---------------
{
"ok": true | false,
"mode": str,
"csv_path": str,
"num_rows": int,
"num_columns": int,
"columns": [str, ...],
"has_smiles_column": bool,
"smiles_column_name": str | null, // actual header used (may differ in case)
"num_blank_smiles": int,
"num_invalid_smiles": int, // among the parsed sample
"smiles_check_method": "sampled" | "full",
"smiles_check_count": int,
"num_duplicate_smiles": int,
"target_columns": [str, ...], // populated only for finetune mode
"num_missing_per_target": { col: int, ... },
"auto_detected_targets": [str, ...], // when --targets is omitted in finetune mode
"errors": [str, ...],
"warnings": [str, ...]
}
Exit code: 0 on `ok: true`, 1 on `ok: false`. Loader exceptions are caught
and surfaced into `errors[]` with `ok: false` (still exit 1).
CLI
---
check_data.py --mode <mode> --csv <path>
[--targets <col1> <col2> ...] # finetune only
[--strict-rdkit] # full SMILES parse
"""
from __future__ import annotations
import argparse
import json
import sys
import traceback
from pathlib import Path
from typing import Any
import pandas as pd
CANONICAL_SMILES_COLUMN = "smiles"
SMILES_SAMPLE_PER_END = 10 # how many SMILES from head + how many from tail to sample
def _find_smiles_column(columns: list[str]) -> str | None:
"""Return the actual column header matching 'smiles' case-insensitively, or None."""
for c in columns:
if c.lower() == CANONICAL_SMILES_COLUMN:
return c
return None
def _parse_smiles_sample(smiles_values: list[str], full: bool) -> tuple[int, int, str]:
"""Run RDKit MolFromSmiles on a sample or all of the SMILES. Returns
(num_parsed, num_invalid, method)."""
# Import here so the script can still surface a clean JSON error if RDKit
# is unavailable in the host env.
try:
from rdkit import Chem
from rdkit import RDLogger
RDLogger.DisableLog("rdApp.*") # suppress per-mol parse warnings
except ImportError as exc:
raise RuntimeError(
f"RDKit is not importable in this environment: {exc}. "
"Run check_data.py inside the kermt container."
) from exc
if full or len(smiles_values) <= 2 * SMILES_SAMPLE_PER_END:
sample = smiles_values
method = "full"
else:
sample = smiles_values[:SMILES_SAMPLE_PER_END] + smiles_values[-SMILES_SAMPLE_PER_END:]
method = "sampled"
invalid = 0
parsed = 0
for smi in sample:
if not smi: # already counted as blank elsewhere
continue
parsed += 1
mol = Chem.MolFromSmiles(smi, sanitize=True)
if mol is None:
invalid += 1
return parsed, invalid, method
def _autodetect_target_columns(df: pd.DataFrame, smiles_col: str) -> list[str]:
"""Pick columns that look like numeric targets. A column qualifies if it
is (a) not the smiles column and (b) >=80% of non-null values convert to float.
Heuristic only — returned for the skill to prompt the user to confirm."""
candidates: list[str] = []
for col in df.columns:
if col == smiles_col:
continue
ser = df[col].dropna()
if len(ser) == 0:
continue
try:
converted = pd.to_numeric(ser, errors="coerce")
except (TypeError, ValueError):
continue
if converted.notna().sum() / max(len(ser), 1) >= 0.8:
candidates.append(col)
return candidates
def validate(mode: str, csv_path: str, targets: list[str] | None, strict_rdkit: bool) -> dict[str, Any]:
result: dict[str, Any] = {
"ok": False,
"mode": mode,
"csv_path": csv_path,
"num_rows": 0,
"num_columns": 0,
"columns": [],
"has_smiles_column": False,
"smiles_column_name": None,
"num_blank_smiles": 0,
"num_invalid_smiles": 0,
"smiles_check_method": "sampled",
"smiles_check_count": 0,
"num_duplicate_smiles": 0,
"target_columns": [],
"num_missing_per_target": {},
"auto_detected_targets": [],
"errors": [],
"warnings": [],
}
# 1. Read the CSV.
path = Path(csv_path)
if not path.is_file():
result["errors"].append(f"CSV not found: {csv_path}")
return result
try:
df = pd.read_csv(path)
except pd.errors.EmptyDataError:
result["errors"].append(f"CSV is empty (no header): {csv_path}")
return result
except Exception as exc: # noqa: BLE001
result["errors"].append(f"failed to read CSV {csv_path}: {type(exc).__name__}: {exc}")
return result
result["num_rows"] = int(len(df))
result["num_columns"] = int(len(df.columns))
result["columns"] = [str(c) for c in df.columns]
# 2. Locate the SMILES column.
smiles_col = _find_smiles_column(result["columns"])
if smiles_col is None:
result["errors"].append(
f"no column named 'smiles' (case-insensitive) found in CSV. "
f"Available columns: {result['columns']}"
)
return result
result["has_smiles_column"] = True
result["smiles_column_name"] = smiles_col
if smiles_col != CANONICAL_SMILES_COLUMN:
result["warnings"].append(
f"SMILES column is named '{smiles_col}' but downstream code expects '{CANONICAL_SMILES_COLUMN}' "
f"(lowercase). Rename the column to '{CANONICAL_SMILES_COLUMN}' before running the workflow."
)
# 3. Blank-SMILES count + duplicate count + RDKit parse check.
smi_series = df[smiles_col].astype(str).fillna("").str.strip()
blank_mask = smi_series.eq("") | smi_series.str.lower().eq("nan")
result["num_blank_smiles"] = int(blank_mask.sum())
nonblank = smi_series[~blank_mask]
result["num_duplicate_smiles"] = int(len(nonblank) - nonblank.nunique())
if len(nonblank) == 0:
result["errors"].append("no non-blank SMILES found in the CSV")
return result
try:
parsed, invalid, method = _parse_smiles_sample(nonblank.tolist(), full=strict_rdkit)
except RuntimeError as exc:
result["errors"].append(str(exc))
return result
result["smiles_check_count"] = parsed
result["num_invalid_smiles"] = invalid
result["smiles_check_method"] = method
if invalid > 0:
scope = "all rows" if method == "full" else f"the {parsed} sampled rows"
result["errors"].append(
f"{invalid} out of {parsed} SMILES in {scope} failed to parse with RDKit. "
"Either pre-clean the CSV with scripts/clean_smiles.py or pass --strict-rdkit to see "
"the full count."
)
# 4. Target-column handling — finetune mode only.
if mode == "finetune":
if targets:
missing = [t for t in targets if t not in df.columns]
if missing:
result["errors"].append(
f"target column(s) not found in CSV: {missing}. "
f"Available columns: {result['columns']}"
)
else:
result["target_columns"] = list(targets)
for t in targets:
nan_count = int(df[t].isna().sum())
result["num_missing_per_target"][t] = nan_count
# Confirm numeric-ish.
nonnan = df[t].dropna()
converted = pd.to_numeric(nonnan, errors="coerce")
non_numeric_count = int(converted.isna().sum())
if non_numeric_count > 0:
result["warnings"].append(
f"target column '{t}' has {non_numeric_count} non-numeric value(s) "
f"that will be dropped by the finetune runner."
)
else:
# Auto-detect — surface candidates so the skill can prompt the user.
result["auto_detected_targets"] = _autodetect_target_columns(df, smiles_col)
if not result["auto_detected_targets"]:
result["errors"].append(
"no numeric non-smiles columns detected. finetune needs at least one target column; "
"specify it explicitly via --targets <col>."
)
else:
result["warnings"].append(
f"--targets was not specified; auto-detected candidate target columns "
f"{result['auto_detected_targets']}. The skill will prompt the user to confirm."
)
# 5. Small-corpus warning — only for pretrain (other modes can be tiny by design).
if mode == "pretrain" and result["num_rows"] < 100:
result["warnings"].append(
f"pretrain corpus is only {result['num_rows']} molecule(s). Pretraining typically "
f"needs orders of magnitude more — verify this is the intended input."
)
result["ok"] = not result["errors"]
return result
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="Validate a CSV input for a KERMT agent workflow.")
parser.add_argument("--mode", required=True, choices=["pretrain", "finetune", "inference", "embed"])
parser.add_argument("--csv", required=True, help="Path to the input CSV")
parser.add_argument("--targets", nargs="+", default=None,
help="(finetune only) target column names. If omitted, the validator auto-detects "
"numeric non-smiles columns and reports them as candidates.")
parser.add_argument("--strict-rdkit", action="store_true",
help="Parse every SMILES with RDKit rather than sampling (slow on large CSVs).")
args = parser.parse_args(argv)
try:
result = validate(args.mode, args.csv, args.targets, args.strict_rdkit)
except Exception as exc: # noqa: BLE001
print(traceback.format_exc(), file=sys.stderr)
print(json.dumps({
"ok": False,
"mode": args.mode,
"csv_path": args.csv,
"errors": [f"unhandled exception in validator: {type(exc).__name__}: {exc}"],
"warnings": [],
}, indent=2))
return 1
print(json.dumps(result, indent=2))
return 0 if result["ok"] else 1
if __name__ == "__main__":
sys.exit(main())
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