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
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
cudaplacement 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/scipyare treated as ancillary — present in everything, never the story — and can't independently drive a verdict.
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.)
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
Tests
tests/ contains the canonical fixtures:
tuesday_night.py— sklearn SVC + GridSearchCV on a declared T4 → BLOCKtorch_configured.py— torch with device placement → PASStorch_forgot_device.py— torch, no device call → ADVISExgb_default.py— XGBoost with default (CPU) params → ADVISEsvm_demo.ipynb— notebook parsing incl.%magic/!shellstripping → BLOCKcuda_on_cpu.py— unconditional.to("cuda")on a CPU runtime → BLOCKguarded_cuda.py— cuda placement behindis_available()guard → PASSflash_on_t4.py— flash-attn on a T4 (cc 7.5 < 8.0) → BLOCK; same file on A100 → PASSbnb_on_mac.py— bitsandbytes on Apple Silicon (MPS) → BLOCK
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nocando-0.2.0.tar.gz.
File metadata
- Download URL: nocando-0.2.0.tar.gz
- Upload date:
- Size: 11.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
efa348519278896c06e40f6d43f6dd9f9e351c867de471fcd8fbdb44ac5f3113
|
|
| MD5 |
983a2a086e7dad62139a2fb30203ba4e
|
|
| BLAKE2b-256 |
09742bd70b9a53aee478ffa1427c521c0d69fd9ceef2f7c554326236ed8bfe21
|
File details
Details for the file nocando-0.2.0-py3-none-any.whl.
File metadata
- Download URL: nocando-0.2.0-py3-none-any.whl
- Upload date:
- Size: 12.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1b54d834c9e89771296030541e362295ab5d6e554e994e6ac8f52fe18c695da3
|
|
| MD5 |
b3b55c67b12783f0aa2031aca985e4f3
|
|
| BLAKE2b-256 |
b6268da5b6b39aaa0dfb2c60ba0aadde62a4f8c21fc76f035a9af10fc0213e35
|