Metadata-Version: 2.4
Name: crunr
Version: 2.4.1
Summary: Run any compute job on GPU — crunr cloud or your own AWS EC2
Author-email: Sandeep Singh <sandeep@crunr.com>
License-Expression: MIT
Project-URL: Homepage, https://crunr.com
Project-URL: Repository, https://github.com/sandy088/crunr
Keywords: aws,ec2,cloud,gpu,machine-learning,spot,gpu-cloud,mlops
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: System :: Distributed Computing
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: boto3>=1.34
Requires-Dist: botocore>=1.34
Requires-Dist: rich>=13.7
Provides-Extra: dev
Requires-Dist: pytest>=7.4; extra == "dev"
Requires-Dist: pytest-mock>=3.12; extra == "dev"
Requires-Dist: moto[ec2,sts]>=5.0; extra == "dev"
Requires-Dist: ruff>=0.3; extra == "dev"
Requires-Dist: mypy>=1.8; extra == "dev"
Requires-Dist: boto3-stubs[ec2,sts]>=1.34; extra == "dev"

# crunr

Run any compute job on a GPU — no DevOps required.

```bash
pip install crunr
```

crunr supports two backends. Switch with one command:

```bash
crunr use cloud   # → crunr cloud GPU  (no AWS account needed)
crunr use aws     # → your own AWS EC2  (default)
```

Once you've set a context, all commands (`run`, `jobs`, `logs`, `pull`, `share`, `cancel`, `balance`) automatically target the right backend.

---

## Quick start — crunr cloud

The fastest way to run a GPU job. No AWS account, no instance management.

```bash
# 1. Get an API key at cloud.crunr.com → Dashboard → API Keys
crunr cloud login

# 2. Switch to cloud context
crunr use cloud

# 3. Run a job
crunr run train.py --gpu
crunr run train.py --instance a100 --max-hours 4

# 4. Check status
crunr jobs

# 5. Download outputs
crunr pull <JOB_ID>
```

Outputs are stored in crunr cloud storage. Download whenever you want — even days later.

---

## Quick start — AWS EC2

Bring your own AWS account. You pay AWS directly, no markup.

```bash
# 1. Configure AWS credentials (one-time)
crunr auth

# 2. Switch to AWS context (default, skip if you haven't changed it)
crunr use aws

# 3. Run a job
crunr run train.py --gpu

# 4. Check history
crunr jobs
```

---

## Context system

crunr uses a persistent context so you don't have to type `--cloud` on every command.

```bash
crunr use cloud          # all commands now target crunr cloud
crunr use aws            # all commands now target AWS EC2

crunr run train.py       # goes to whichever context is active
crunr run train.py --cloud  # force cloud for this run only (any context)
```

Context is saved in `~/.crunr/config.json`. The current context is shown on every command output.

---

## crunr cloud commands

| Command | Description |
|---|---|
| `crunr cloud login` | Save your crunr cloud API key |
| `crunr run <script>` | Submit a job (cloud context) |
| `crunr jobs` | List all your jobs, newest first |
| `crunr logs <JOB_ID>` | Stream live logs |
| `crunr pull <JOB_ID>` | Download outputs to local machine |
| `crunr share <JOB_ID>` | Generate presigned download links (24h by default) |
| `crunr cancel <JOB_ID>` | Cancel a queued or running job |
| `crunr balance` | Show credit balance and storage usage |
| `crunr cloud jobs --limit 50` | Show more jobs |
| `crunr share <JOB_ID> --ttl 1` | 1-hour links |

### `crunr run` options (cloud context)

```
script              Python/shell script to run
--instance GPU      GPU type: rtx4090 (default), a100, h100, rtx3090, rtx3080, rtx4080, …
--gpu               Request GPU (cheapest available)
--memory GB         Minimum GPU VRAM in GB
--max-hours N       Hard time limit in hours (default: 2). Job auto-stops at this limit.
--env KEY=VALUE     Environment variable passed to the job (repeatable)
--dir PATH          Local directory to sync (default: current directory)
```

### GPU types

| Type | GPU | VRAM |
|---|---|---|
| `rtx3080` | NVIDIA RTX 3080 | 10 GB |
| `rtx3090` | NVIDIA RTX 3090 | 24 GB |
| `rtx4080` | NVIDIA RTX 4080 | 16 GB |
| `rtx4090` | NVIDIA RTX 4090 | 24 GB |
| `a100` | NVIDIA A100 | 40–80 GB |
| `h100` | NVIDIA H100 | 80 GB |

### Storage

- **25 GB free** per account
- **$0.05/GB-month** for overage
- Outputs **auto-expire 30 days** after a run
- Downloads are always **free**

Check usage anytime with `crunr balance`.

### Credits

crunr cloud uses prepaid credits. 1 credit = $0.000001 USD. You only pay for GPU seconds used.

```bash
crunr balance                  # check credits
# Top up at cloud.crunr.com/dashboard/billing
```

---

## AWS EC2 commands

| Command | Description |
|---|---|
| `crunr auth` | Configure AWS credentials |
| `crunr run <script>` | Provision EC2, run job, download outputs |
| `crunr jobs` | Show local job history |
| `crunr logs <JOB_ID>` | Show stdout log from S3 |
| `crunr pull <JOB_ID>` | Download outputs from S3 |
| `crunr share <JOB_ID>` | Generate presigned S3 download links |
| `crunr ps` | List running EC2 instances |
| `crunr clean` | Terminate all orphaned instances |
| `crunr ssh` | Open a shell on a running instance |
| `crunr s3 setup` | Create S3 bucket for output persistence |
| `crunr s3 list` | List jobs stored in S3 |
| `crunr s3 pull <JOB_ID>` | Download a job from S3 |
| `crunr s3 status` | Show bucket usage and config |
| `crunr s3 rm <JOB_ID>` | Delete a job from S3 |

### `crunr run` options (aws context)

```
script              Python/shell script to run
--gpu               Request a GPU instance (cheapest available)
--memory GB         Minimum GPU VRAM or RAM in GB
--instance TYPE     Exact EC2 instance type (e.g. g5.xlarge)
--disk GB           Root EBS volume size (default: 8 GB CPU, 150 GB GPU)
--spot              Use spot pricing (cheaper, may be interrupted)
--env KEY=VALUE     Environment variable passed to the job (repeatable)
--dir PATH          Local directory to sync (default: current directory)
--profile NAME      AWS credential profile
--region REGION     Override AWS region

# S3 output persistence
--s3                Back up outputs using saved config (run 'crunr s3 setup' first)
--s3-bucket NAME    S3 bucket name (auto-created if needed)
--s3-prefix PREFIX  Key prefix (default: crunr-jobs)
--s3-no-local       Skip local download — outputs in S3 only
--s3-ttl DAYS       Auto-delete this job's S3 data after N days
```

### Instance picker

If you pass an unknown `--instance` type, crunr shows a full table of available EC2 instances with GPU specs and pricing, and lets you pick interactively:

```
  ! Unknown EC2 instance type: rtx4090
  crunr cloud GPU types don't work in aws context.
  Switch to cloud:  crunr use cloud

  GPU instances
  #   Instance        GPU chip      GPUs × VRAM   vCPUs   RAM      ~$/hr
  ──────────────────────────────────────────────────────────────────────
  1   g4dn.xlarge     NVIDIA T4     1 × 16 GB     4       16 GB    $0.526
  ...

  Pick an instance [1–32]:
```

### How AWS runs work

1. **Provision** — selects the cheapest matching spot instance (falls back to on-demand)
2. **Sync** — uploads your local directory (rsync or scp+tar)
3. **Execute** — runs your command with live log streaming
4. **Collect** — downloads `outputs/` back to your machine
5. **Terminate** — instance always destroyed, even on Ctrl+C or crash

### Saving outputs (AWS)

Write files to an `outputs/` directory and crunr downloads it automatically:

```python
import os
os.makedirs("outputs", exist_ok=True)
with open("outputs/result.txt", "w") as f:
    f.write("done")
```

### S3 output persistence

With S3 enabled, the EC2 instance pushes outputs to S3 before the instance terminates. Your results survive network failures and client disconnects.

```bash
crunr s3 setup --bucket crunr-yourname-outputs   # one-time setup
crunr run train.py --gpu --s3                     # run with S3 backup
crunr s3 pull <JOB_ID>                           # recover after disconnect
```

### AWS IAM permissions

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "RunrVerify",
      "Effect": "Allow",
      "Action": ["sts:GetCallerIdentity"],
      "Resource": "*"
    },
    {
      "Sid": "RunrDescribe",
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeInstances", "ec2:DescribeImages", "ec2:DescribeKeyPairs",
        "ec2:DescribeSecurityGroups", "ec2:DescribeSpotPriceHistory",
        "ec2:DescribeAvailabilityZones", "ec2:DescribeVpcs",
        "ec2:DescribeSubnets", "ec2:DescribeInstanceTypes", "ec2:DescribeInstanceStatus"
      ],
      "Resource": "*"
    },
    {
      "Sid": "RunrInstances",
      "Effect": "Allow",
      "Action": [
        "ec2:CreateKeyPair", "ec2:DeleteKeyPair",
        "ec2:CreateSecurityGroup", "ec2:AuthorizeSecurityGroupIngress",
        "ec2:RunInstances", "ec2:TerminateInstances", "ec2:CreateTags",
        "ec2:RequestSpotInstances", "ec2:DescribeSpotInstanceRequests",
        "ec2:CancelSpotInstanceRequests"
      ],
      "Resource": "*"
    },
    {
      "Sid": "CrunrS3Bucket",
      "Effect": "Allow",
      "Action": [
        "s3:CreateBucket", "s3:ListBucket", "s3:GetBucketLocation",
        "s3:PutBucketPublicAccessBlock", "s3:PutBucketPolicy",
        "s3:PutEncryptionConfiguration", "s3:PutBucketOwnershipControls",
        "s3:PutLifecycleConfiguration", "s3:GetLifecycleConfiguration"
      ],
      "Resource": "arn:aws:s3:::crunr-*"
    },
    {
      "Sid": "CrunrS3Objects",
      "Effect": "Allow",
      "Action": ["s3:PutObject", "s3:GetObject", "s3:DeleteObject"],
      "Resource": "arn:aws:s3:::crunr-*/*"
    },
    {
      "Sid": "CrunrIAMRole",
      "Effect": "Allow",
      "Action": [
        "iam:CreateRole", "iam:GetRole", "iam:PutRolePolicy",
        "iam:GetRolePolicy", "iam:DeleteRolePolicy", "iam:DeleteRole", "iam:TagRole"
      ],
      "Resource": "arn:aws:iam::*:role/crunr-s3-writer"
    },
    {
      "Sid": "CrunrIAMProfile",
      "Effect": "Allow",
      "Action": [
        "iam:CreateInstanceProfile", "iam:GetInstanceProfile",
        "iam:AddRoleToInstanceProfile", "iam:RemoveRoleFromInstanceProfile",
        "iam:DeleteInstanceProfile", "iam:TagInstanceProfile"
      ],
      "Resource": "arn:aws:iam::*:instance-profile/crunr-instance-profile"
    },
    {
      "Sid": "CrunrPassRole",
      "Effect": "Allow",
      "Action": "iam:PassRole",
      "Resource": "arn:aws:iam::*:role/crunr-s3-writer",
      "Condition": {
        "StringEquals": {"iam:PassedToService": "ec2.amazonaws.com"}
      }
    }
  ]
}
```

The S3, IAM, and PassRole blocks are only needed if you use `--s3`.

---

## Requirements

- Python 3.10+
- For AWS context: an AWS account with appropriate IAM permissions
- For cloud context: a crunr account at [cloud.crunr.com](https://cloud.crunr.com)

## License

MIT
