PentaCMD-47M

⚠️ Working name — rename PentaCMD throughout if you choose a different one.

A 47M-parameter, from-scratch (nanoGPT-style) small language model that turns a plain-English instruction + a family hint into a single terminal command, across five tools: bash, git, npm, python (pip/venv), and PowerShell.

It is "Version 0" — a baseline trained end-to-end as a learning project, to be iterated on.

What it does

Given a prompt in this exact format:

### Task (git): undo my last commit but keep my changes
### Command:

it generates:

git reset --soft HEAD~1

The family tag (bash | git | npm | python | powershell) is provided as an input hint so the same English can map to the right tool.

Results (held-out test set, exact-match)

Family Exact-match First-word
git 100.0% 100%
powershell 98.7% 100%
npm 97.3% 100%
python 69.3% ~100%
bash 68.0% ~75%
Blended ~86.7%

"Exact-match" = the generated command is byte-for-byte identical to the reference, including the argument. "First-word" = correct command/verb. Blended test number matches the validation number (~86.7%) — i.e. no overfitting.

Baseline before scale-up (20M model, ~36K examples): 58.7% blended.

Model details

  • Architecture: decoder-only transformer (nanoGPT-style), 8 layers, 10 heads, 640 embedding dim, 256-token context, weight-tied embeddings, ~47.2M params.
  • Tokenizer: byte-level BPE, vocab 12,000, special tokens <|endoftext|>, <|pad|>.
  • Precision: trained in fp16 (mixed precision).

Training data

  • 299,329 instruction→command pairs across 5 families, de-duplicated on (family, instruction).
  • Per family: git 120,000 · python 90,000 · bash 41,329 · powershell 28,000 · npm 20,000.
  • bash = the real-world NL2Bash corpus (~11K) + synthetic beginner commands (30K). The other four families are synthetic, generated from templates with large entity pools (thousands of distinct package/branch/file names) so the model learns to copy arbitrary arguments rather than memorize a fixed set.
  • Leak-free 90/5/5 split: grouped by command so paraphrases never straddle splits → train 269,396 / val 14,967 / test 14,966 (~6.54M training tokens).

Training procedure

  • Hardware: single NVIDIA T4 (Kaggle), ~53 minutes.
  • Optimizer: AdamW (betas 0.9/0.95, weight decay 0.1).
  • LR: 6e-4 peak, 300-step warmup, cosine decay to 6e-5.
  • Batch size 32, 14,000 steps, gradient clip 1.0.
  • Checkpointed by exact-match accuracy on a fixed val subset (not validation loss — val loss is misleading here because the leak-free split holds out unseen command groups). Best checkpoint was step 11,000.

Intended use & limitations

  • Intended: an assistant/autocomplete for common CLI tasks for beginners; an educational example of an end-to-end small-LM project.
  • Limitations:
    • python (69%) and bash (68%) are weaker. python has five installers (pip/uv/poetry/conda/pipx) so "install X" is ambiguous, and package names are long/multi-token; bash includes research-hard NL2Bash pipelines.
    • The model can produce a plausible-but-wrong argument (e.g. install the wrong package). Always review generated commands before running them, especially destructive ones (rm -rf, git reset --hard, Remove-Item).
    • Outputs are not validated by execution.

How to use

This is a custom architecture, so load the weights with the provided modeling code (modeling_pentacmd.py). Minimal inference:

import torch
from tokenizers import Tokenizer
from modeling_pentacmd import GPT   # the model class

device = "cuda" if torch.cuda.is_available() else "cpu"
tok = Tokenizer.from_file("tokenizer.json")
ckpt = torch.load("best_model.pt", map_location=device)
model = GPT(**ckpt["config"]).to(device)
model.load_state_dict(ckpt["model"]); model.eval()
eot = tok.token_to_id("<|endoftext|>")

@torch.no_grad()
def command_for(family, instruction, block_size=256, max_new=64):
    ids = tok.encode(f"### Task ({family}): {instruction}\n### Command:").ids
    x = torch.tensor([ids], device=device)
    for _ in range(max_new):
        nid = model(x[:, -block_size:])[:, -1, :].argmax(-1, keepdim=True)
        x = torch.cat([x, nid], 1)
        if nid.item() == eot: break
    return tok.decode(x[0].tolist()).split("### Command:")[-1].split("###")[0].strip()

print(command_for("npm", "install the express package"))  # -> npm install express

License / attribution

  • Model weights & synthetic data: CC BY-NC 4.0 (attribution, non-commercial).
  • bash portion: derives from TellinaTool/nl2bash and is subject to that project's own license (verify in their repo — likely GPL-3.0). Comply with it before redistributing the bash data or models trained on it.
  • Code (architecture, inference, data pipeline) is MIT — see the GitHub repo.

Citation

If you use this, please credit the author and link the GitHub/Hugging Face repos.

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support