Skip to main content

Pre-flight environment-semantics linter for ML workloads — catches code/hardware mismatches (e.g. scikit-learn on a GPU runtime) before you occupy the hardware.

Project description

nocando

A pre-flight environment-semantics linter for ML workloads. It answers one question before you occupy hardware — in either direction:

Is the code you wrote coherent with the environment you declared?

That covers CPU-only workloads parked on GPUs (waste), unconditional .cuda() calls headed for a CPU runtime (crash at minute 40), CUDA-requiring libraries on Apple Silicon (import failure), and wrong-generation hardware (flash-attn needs compute capability >= 8.0; a Colab T4 is 7.5). The unit of analysis is the mismatch. Deliberate slow-on-CPU choices are not flagged — can't legislate morality.

Syntax linters know your language. Type checkers know your data shapes. Nothing knows that from sklearn.svm import SVC plus "Runtime: T4" is an incoherent sentence — the constraint lives at the intersection of library × hardware × platform, and nobody owns intersections. nocando owns this one. Zero LLM calls, zero telemetry, pure static analysis.

Certified by Ted (stoop tabby, QA).

Why

Wasted accelerator time is deadweight loss: a scikit-learn grid search on a Colab T4 produces heat, not gradients, for however many hours you sit there. The knowledge to prevent this already exists — scattered across Stack threads and subreddits, indexed by symptom, findable only after you've failed. nocando inverts the indexing: it audits your declared intent (code + runtime) at the moment of declaration.

Usage

python nocando.py train.py --runtime t4
python nocando.py analysis.ipynb --runtime a100
python nocando.py train.py                 # autodetect via nvidia-smi / MPS
python nocando.py train.py --runtime t4 --json   # machine-readable, for CI

In Colab, one cell before your run:

!wget -q https://<your-host>/nocando.py
!python nocando.py /content/drive/MyDrive/train.py --runtime t4

Locations

Every finding names where it is AND quotes the actual offending line, not just what's wrong — pandas (line 3: import pandas as pd), or in a notebook, scikit-learn (cell 1: clf = SVC().fit(...)). Scripts and R files get real line numbers; notebooks get cell numbers instead, deliberately — Colab and Jupyter show you cells, not one continuous file's line count, so a raw line number would be actively misleading there. A two-location fact like a contradictory pin shows both, each quoted (line 6: numpy=1.26.4and line 9:numpy==2.1.0``). One honest gap: prose-vocabulary hits (a workload named in an assignment's markdown, not yet in code) have no precise anchor yet — see AGENTS.md rather than a faked location.

Verdicts

Verdict Exit Meaning
✓ CLEARED FOR TAKEOFF 0 Accelerator request is coherent with this code
△ WILL RUN, BUT 1 Runs, but the allocation is wasteful or unconfigured (e.g. torch imported, no .to(device); XGBoost without device='cuda')
✗ NO CAN DO 2 Nothing in this code can address the requested accelerator

Design constraint: the false-positive budget is ~zero (an advisor that interrupts wrongly gets disabled in a week). So verdicts are conservative:

  • BLOCK fires only on true incoherence: no GPU-capable library at all, a hard CUDA requirement in a non-CUDA environment, unguarded cuda placement on a CPU runtime, or a compute-capability generation mismatch.
  • A script that is one config line away from coherent (XGBoost, LightGBM, CatBoost, spaCy) gets ADVISE, never BLOCK.
  • numpy/scipy are treated as ancillary — present in everything, never the story — and can't independently drive a verdict.

Three audit dimensions

A third dimension answers the "how many other such mismatches exist" question honestly, rather than by trying to enumerate an unbounded space. Ingestion / coverage-honesty: nocando builds a bill of materials from whatever Frankenstein mix of libraries an artifact contains — including ones it has never seen combined before, or never seen at all — and reports two things without hardcoding pairwise trivia that would expire (numpy 2.0's ABI break was a real hazard in 2024; baking it in as a permanent alarm today would itself be shipping a stale fact as evergreen, the exact failure this project exists to prevent). What it checks for free, requiring zero domain knowledge: self-contradictory version pins — the same package pinned to two incompatible exact versions in the same artifact (e.g. conda says numpy=1.26, pip says numpy==2.1) is unconditionally a real conflict, no ecosystem expertise needed, never goes stale. What it reports instead of guessing: every recognized-but-unscanned library, explicitly, so a clean report is never mistaken for "verified compatible" — absence of a warning means no rule fired, not that no problem exists.

Two audit dimensions

nocando checks two independent things, and it's worth naming the difference. Runtime coherence (the original scope): can this code use the declared hardware. Environment-manager coherence (new): does the install sequence match how these tools actually cohere with each other — a fragility/reproducibility question, never a "will not run" one, so these findings are always ADVISE-capped and can never produce BLOCK on their own.

Two grounded cases today: conda+pip ordering (conda's own docs: "once pip has been used, conda will be unaware of these changes and may make modifications that would break the environment" — the documented fix is conda-first-pip-last), and notebook bang-vs-magic installs (!pip install shells out and may target a different Python than the kernel; %pip install is an IPython magic guaranteed to hit the active kernel — nothing in either command's output signals which one you needed). environment.yml is audited directly for channel-mixing and unpinned git dependencies.

Languages and formats

nocando audits Python and R — Colab serves both kernels, and R's classical stack (tree, rpart, randomForest, gbm, e1071, cluster...) is uniformly CPU-bound, so the GPU trap is identical in either tongue. Inputs: .py, .ipynb (Python or R kernels), .R/.Rmd, and prose artifacts (.txt, .md, .pdf via optional pypdf) — assignment handouts are audited directly: embedded library()/import preambles are parsed as imports, and named workloads in the prose (GridSearchCV, cv.tree, k-means...) are caught prospectively, before any code exists.

Capability map

Hand-verified deterministic tier covering the common catastrophic cases: CPU-only libraries (scikit-learn, pandas, statsmodels, NLTK, gensim, Prophet…), conditionally-GPU libraries (XGBoost, LightGBM, CatBoost, spaCy), and native accelerator frameworks (PyTorch, TensorFlow, JAX, RAPIDS, transformers). Extend or override with --map your_map.json.

Intake: the constraint ledger (LEDGER.md)

The audit is only as good as the declared intent, so the package now fronts with a constraint-ledger protocol (LEDGER.md): goal, must-stay, must-not, and a disposability default stating that anything unlisted — including the entire current implementation — is negotiable. It prevents the failure mode where an assistant promotes your current attempt into a requirement.

Participation is optional by design. When no ledger is provided, inference mode applies: the auditor constructs a provisional ledger from evidence, tags every entry [inferred], and asks the single cheapest high-leverage ratification question before any expensive work. The declared and inferred ledgers are the same object at different confidence levels — participation buys speed, never access. (nocando already does this at the code layer: --runtime undeclared triggers autodetection.)

Evolution (GROWTH.md, AGENTS.md)

The engine never learns; the artifact evolves. The repo is the genome (heredity via git), wild artifacts supply variation, and the ratification gates are selection. Agent sessions are generations: stateless operators that read the inheritance, metabolize new specimens, and write additions back as diffs. AGENTS.md is the orientation file that lets any successor instance pick this up cold — invariants, loops, lineage, open gaps. The map grows via a propose-ratify loop (GROWTH.md). --record "what actually happened" captures any verdict-vs-reality disagreement as a case file; a maintainer or agent turns cases into map entries + fixtures + tests; a human ratifies the diff. The agent is stateless — the map is the memory, and every learned rule is a reviewable line in git history.

Roadmap (tier 2)

The deterministic tier handles "can it." The agent tier handles "should it":

  • workload sizing (dataset dims, params → rough FLOPs / VRAM estimate)
  • allocation options with time/energy tradeoffs ("your ask is X; options A/B/C")
  • syllabus/org aperture: audit a course's notebooks or a team's repo in batch
  • retrieval-grounded advisories synthesized from the distributed corpus, reserved for cases where being occasionally wrong is survivable
  • ledger-first sessions: intake (declared or inferred) -> coherence audit -> allocation options, in that order, always

Deployment: in the path of the cut

"Measure twice, cut once" presumes you own a ruler. Here, measurement normally costs more than cutting — hours of scattered-doc hunting vs. one click of "Runtime > T4" — so cutting-first is rational and wheel-spinning is the equilibrium. nocando's job is a cost inversion: one second, zero hunting. That only works if the measure fires where the cut happens, so it ships as drop-in surfaces (deploy/), not a ritual to remember:

  • Colab, cell 1 (deploy/colab_bootstrap.py): fetches nocando, finds the active notebook, audits it against the autodetected runtime — measuring the real environment, not the declared one.
  • pre-commit hook (deploy/.pre-commit-config.yaml): zero-participation enforcement; every commit is measured whether or not anyone remembers.
  • assistant layer (LEDGER.md): paste-in intake + inference-mode rules for any chat assistant; section 7 covers users who never paste anything.
  • CI: --json + exit codes (0/1/2) drop into any pipeline gate.

Tests

tests/ contains the canonical fixtures:

  • tuesday_night.py — sklearn SVC + GridSearchCV on a declared T4 → BLOCK
  • torch_configured.py — torch with device placement → PASS
  • torch_forgot_device.py — torch, no device call → ADVISE
  • xgb_default.py — XGBoost with default (CPU) params → ADVISE
  • svm_demo.ipynb — notebook parsing incl. %magic/!shell stripping → BLOCK
  • cuda_on_cpu.py — unconditional .to("cuda") on a CPU runtime → BLOCK
  • guarded_cuda.py — cuda placement behind is_available() guard → PASS
  • flash_on_t4.py — flash-attn on a T4 (cc 7.5 < 8.0) → BLOCK; same file on A100 → PASS
  • bnb_on_mac.py — bitsandbytes on Apple Silicon (MPS) → BLOCK

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nocando-0.3.0.tar.gz (24.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nocando-0.3.0-py3-none-any.whl (24.9 kB view details)

Uploaded Python 3

File details

Details for the file nocando-0.3.0.tar.gz.

File metadata

  • Download URL: nocando-0.3.0.tar.gz
  • Upload date:
  • Size: 24.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for nocando-0.3.0.tar.gz
Algorithm Hash digest
SHA256 071fa49e877649335ac57974ff9068540d1d7598d529ef96eb462627c126d56f
MD5 a02c4b8e2d192b380407422e2bdbd029
BLAKE2b-256 99be9443f97127e2d1e30652a98ed82704bf49f0e9b66594c64df16c6b67b9d0

See more details on using hashes here.

File details

Details for the file nocando-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: nocando-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 24.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for nocando-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d28f9c7675379012e2d0947d278feb612dd57d1d5184921e7f91c3721d09b0fc
MD5 ca70efeb997a2048b073c93b22c8df1d
BLAKE2b-256 5a82b3324f66b9d13d08bcf9f4d2fd2af2fdf0c95c0900d130752483fe6e96f0

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page