aws-bootstrap-g4dn 0.2.0__py3-none-any.whl → 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,6 +10,7 @@ from aws_bootstrap.ssh import (
10
10
  _read_ssh_config,
11
11
  add_ssh_host,
12
12
  find_ssh_alias,
13
+ get_ssh_host_details,
13
14
  list_ssh_hosts,
14
15
  remove_ssh_host,
15
16
  )
@@ -295,3 +296,38 @@ def test_list_hosts_nonexistent_file(tmp_path):
295
296
  def test_remove_nonexistent_file(tmp_path):
296
297
  cfg = tmp_path / "no_such_file"
297
298
  assert remove_ssh_host("i-abc123", config_path=cfg) is None
299
+
300
+
301
+ # ---------------------------------------------------------------------------
302
+ # Port in stanza / details
303
+ # ---------------------------------------------------------------------------
304
+
305
+
306
+ def test_stanza_includes_port_when_non_default(tmp_path):
307
+ cfg = _config_path(tmp_path)
308
+ add_ssh_host("i-abc123", "1.2.3.4", "ubuntu", KEY_PATH, config_path=cfg, port=2222)
309
+ content = cfg.read_text()
310
+ assert "Port 2222" in content
311
+
312
+
313
+ def test_stanza_omits_port_when_default(tmp_path):
314
+ cfg = _config_path(tmp_path)
315
+ add_ssh_host("i-abc123", "1.2.3.4", "ubuntu", KEY_PATH, config_path=cfg)
316
+ content = cfg.read_text()
317
+ assert "Port" not in content
318
+
319
+
320
+ def test_get_ssh_host_details_parses_port(tmp_path):
321
+ cfg = _config_path(tmp_path)
322
+ add_ssh_host("i-abc123", "1.2.3.4", "ubuntu", KEY_PATH, config_path=cfg, port=2222)
323
+ details = get_ssh_host_details("i-abc123", config_path=cfg)
324
+ assert details is not None
325
+ assert details.port == 2222
326
+
327
+
328
+ def test_get_ssh_host_details_default_port(tmp_path):
329
+ cfg = _config_path(tmp_path)
330
+ add_ssh_host("i-abc123", "1.2.3.4", "ubuntu", KEY_PATH, config_path=cfg)
331
+ details = get_ssh_host_details("i-abc123", config_path=cfg)
332
+ assert details is not None
333
+ assert details.port == 22
@@ -1,16 +1,11 @@
1
- """Tests for GPU info queries via SSH (get_ssh_host_details, query_gpu_info)."""
1
+ """Tests for get_ssh_host_details (SSH config parsing)."""
2
2
 
3
3
  from __future__ import annotations
4
- import subprocess
5
4
  from pathlib import Path
6
- from unittest.mock import patch
7
5
 
8
6
  from aws_bootstrap.ssh import (
9
- _GPU_ARCHITECTURES,
10
- GpuInfo,
11
7
  add_ssh_host,
12
8
  get_ssh_host_details,
13
- query_gpu_info,
14
9
  )
15
10
 
16
11
 
@@ -47,92 +42,3 @@ def test_get_ssh_host_details_nonexistent_file(tmp_path):
47
42
  """Returns None when the SSH config file doesn't exist."""
48
43
  cfg = tmp_path / "no_such_file"
49
44
  assert get_ssh_host_details("i-abc123", config_path=cfg) is None
50
-
51
-
52
- # ---------------------------------------------------------------------------
53
- # query_gpu_info
54
- # ---------------------------------------------------------------------------
55
-
56
- NVIDIA_SMI_OUTPUT = "560.35.03, Tesla T4, 7.5\n12.8\n12.6\n"
57
-
58
-
59
- @patch("aws_bootstrap.ssh.subprocess.run")
60
- def test_query_gpu_info_success(mock_run):
61
- """Successful nvidia-smi + nvcc output returns a valid GpuInfo."""
62
- mock_run.return_value = subprocess.CompletedProcess(args=[], returncode=0, stdout=NVIDIA_SMI_OUTPUT, stderr="")
63
-
64
- info = query_gpu_info("1.2.3.4", "ubuntu", Path("/home/user/.ssh/id_ed25519"))
65
- assert info is not None
66
- assert isinstance(info, GpuInfo)
67
- assert info.driver_version == "560.35.03"
68
- assert info.cuda_driver_version == "12.8"
69
- assert info.cuda_toolkit_version == "12.6"
70
- assert info.gpu_name == "Tesla T4"
71
- assert info.compute_capability == "7.5"
72
- assert info.architecture == "Turing"
73
-
74
-
75
- @patch("aws_bootstrap.ssh.subprocess.run")
76
- def test_query_gpu_info_no_nvcc(mock_run):
77
- """When nvcc is unavailable, cuda_toolkit_version is None."""
78
- output = "560.35.03, Tesla T4, 7.5\n12.8\nN/A\n"
79
- mock_run.return_value = subprocess.CompletedProcess(args=[], returncode=0, stdout=output, stderr="")
80
-
81
- info = query_gpu_info("1.2.3.4", "ubuntu", Path("/home/user/.ssh/id_ed25519"))
82
- assert info is not None
83
- assert info.cuda_driver_version == "12.8"
84
- assert info.cuda_toolkit_version is None
85
-
86
-
87
- @patch("aws_bootstrap.ssh.subprocess.run")
88
- def test_query_gpu_info_ssh_failure(mock_run):
89
- """Non-zero exit code returns None."""
90
- mock_run.return_value = subprocess.CompletedProcess(args=[], returncode=255, stdout="", stderr="Connection refused")
91
-
92
- info = query_gpu_info("1.2.3.4", "ubuntu", Path("/home/user/.ssh/id_ed25519"))
93
- assert info is None
94
-
95
-
96
- @patch("aws_bootstrap.ssh.subprocess.run", side_effect=subprocess.TimeoutExpired(cmd="ssh", timeout=15))
97
- def test_query_gpu_info_timeout(mock_run):
98
- """TimeoutExpired returns None."""
99
- info = query_gpu_info("1.2.3.4", "ubuntu", Path("/home/user/.ssh/id_ed25519"))
100
- assert info is None
101
-
102
-
103
- @patch("aws_bootstrap.ssh.subprocess.run")
104
- def test_query_gpu_info_malformed_output(mock_run):
105
- """Garbage output returns None."""
106
- mock_run.return_value = subprocess.CompletedProcess(
107
- args=[], returncode=0, stdout="not valid gpu output\n", stderr=""
108
- )
109
-
110
- info = query_gpu_info("1.2.3.4", "ubuntu", Path("/home/user/.ssh/id_ed25519"))
111
- assert info is None
112
-
113
-
114
- # ---------------------------------------------------------------------------
115
- # GPU architecture mapping
116
- # ---------------------------------------------------------------------------
117
-
118
-
119
- def test_gpu_architecture_mapping():
120
- """Known compute capabilities map to correct architecture names."""
121
- assert _GPU_ARCHITECTURES["7.5"] == "Turing"
122
- assert _GPU_ARCHITECTURES["8.0"] == "Ampere"
123
- assert _GPU_ARCHITECTURES["8.6"] == "Ampere"
124
- assert _GPU_ARCHITECTURES["8.9"] == "Ada Lovelace"
125
- assert _GPU_ARCHITECTURES["9.0"] == "Hopper"
126
- assert _GPU_ARCHITECTURES["7.0"] == "Volta"
127
-
128
-
129
- @patch("aws_bootstrap.ssh.subprocess.run")
130
- def test_query_gpu_info_unknown_architecture(mock_run):
131
- """Unknown compute capability produces a fallback architecture string."""
132
- mock_run.return_value = subprocess.CompletedProcess(
133
- args=[], returncode=0, stdout="550.00.00, Future GPU, 10.0\n13.0\n13.0\n", stderr=""
134
- )
135
-
136
- info = query_gpu_info("1.2.3.4", "ubuntu", Path("/home/user/.ssh/id_ed25519"))
137
- assert info is not None
138
- assert info.architecture == "Unknown (10.0)"
@@ -1,13 +1,16 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aws-bootstrap-g4dn
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: Bootstrap AWS EC2 GPU instances for hybrid local-remote development
5
5
  Author: Adam Ever-Hadani
6
6
  License-Expression: MIT
7
7
  Project-URL: Homepage, https://github.com/promptromp/aws-bootstrap-g4dn
8
8
  Project-URL: Issues, https://github.com/promptromp/aws-bootstrap-g4dn/issues
9
9
  Keywords: aws,ec2,gpu,cuda,deep-learning,spot-instances,cli
10
- Requires-Python: >=3.14
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Programming Language :: Python :: 3.14
13
+ Requires-Python: >=3.12
11
14
  Description-Content-Type: text/markdown
12
15
  License-File: LICENSE
13
16
  Requires-Dist: boto3>=1.35
@@ -46,7 +49,7 @@ ssh aws-gpu1 # You're in, venv activated, PyTorch works
46
49
  ### 🎯 Target Workflows
47
50
 
48
51
  1. **Jupyter server-client** — Jupyter runs on the instance, connect from your local browser
49
- 2. **VSCode Remote SSH** — `ssh aws-gpu1` just works with the Remote SSH extension
52
+ 2. **VSCode Remote SSH** — opens `~/workspace` with pre-configured CUDA debug/build tasks and an example `.cu` file
50
53
  3. **NVIDIA Nsight remote debugging** — GPU debugging over SSH
51
54
 
52
55
  ---
@@ -55,7 +58,7 @@ ssh aws-gpu1 # You're in, venv activated, PyTorch works
55
58
 
56
59
  1. AWS profile configured with relevant permissions (profile name can be passed via `--profile` or read from `AWS_PROFILE` env var)
57
60
  2. AWS CLI v2 — see [here](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)
58
- 3. Python 3.14+ and [uv](https://github.com/astral-sh/uv)
61
+ 3. Python 3.12+ and [uv](https://github.com/astral-sh/uv)
59
62
  4. An SSH key pair (see below)
60
63
 
61
64
  ## Installation
@@ -123,6 +126,12 @@ aws-bootstrap launch --on-demand --instance-type g5.xlarge --region us-east-1
123
126
  # Launch without running the remote setup script
124
127
  aws-bootstrap launch --no-setup
125
128
 
129
+ # Use a specific Python version in the remote venv
130
+ aws-bootstrap launch --python-version 3.13
131
+
132
+ # Use a non-default SSH port
133
+ aws-bootstrap launch --ssh-port 2222
134
+
126
135
  # Use a specific AWS profile
127
136
  aws-bootstrap launch --profile my-aws-profile
128
137
  ```
@@ -146,13 +155,14 @@ The setup script runs automatically on the instance after SSH becomes available:
146
155
  |------|------|
147
156
  | **GPU verify** | Confirms `nvidia-smi` and `nvcc` are working |
148
157
  | **Utilities** | Installs `htop`, `tmux`, `tree`, `jq` |
149
- | **Python venv** | Creates `~/venv` with `uv`, auto-activates in `~/.bashrc` |
158
+ | **Python venv** | Creates `~/venv` with `uv`, auto-activates in `~/.bashrc`. Use `--python-version` to pin a specific Python (e.g. `3.13`) |
150
159
  | **CUDA-aware PyTorch** | Detects CUDA toolkit version → installs PyTorch from the matching `cu{TAG}` wheel index |
151
160
  | **CUDA smoke test** | Runs `torch.cuda.is_available()` + GPU matmul to verify the stack |
152
161
  | **GPU benchmark** | Copies `gpu_benchmark.py` to `~/gpu_benchmark.py` |
153
162
  | **GPU smoke test notebook** | Copies `gpu_smoke_test.ipynb` to `~/gpu_smoke_test.ipynb` (open in JupyterLab) |
154
163
  | **Jupyter** | Configures and starts JupyterLab as a systemd service on port 8888 |
155
164
  | **SSH keepalive** | Configures server-side keepalive to prevent idle disconnects |
165
+ | **VSCode workspace** | Creates `~/workspace/.vscode/` with `launch.json` and `tasks.json` (auto-detected `cuda-gdb` path and GPU arch), plus an example `saxpy.cu` |
156
166
 
157
167
  ### 📊 GPU Benchmark
158
168
 
@@ -191,6 +201,28 @@ ssh -i ~/.ssh/id_ed25519 -NL 8888:localhost:8888 ubuntu@<public-ip>
191
201
 
192
202
  A **GPU smoke test notebook** (`~/gpu_smoke_test.ipynb`) is pre-installed on every instance. Open it in JupyterLab to interactively verify the CUDA stack, run FP32/FP16 matmuls, train a small CNN on MNIST, and visualise training loss and GPU memory usage.
193
203
 
204
+ ### 🖥️ VSCode Remote SSH
205
+
206
+ The remote setup creates a `~/workspace` folder with pre-configured CUDA debug and build tasks:
207
+
208
+ ```
209
+ ~/workspace/
210
+ ├── .vscode/
211
+ │ ├── launch.json # CUDA debug configs (cuda-gdb path auto-detected)
212
+ │ └── tasks.json # nvcc build tasks (GPU arch auto-detected, e.g. sm_75)
213
+ └── saxpy.cu # Example CUDA source — open and press F5 to debug
214
+ ```
215
+
216
+ Connect directly from your terminal:
217
+
218
+ ```bash
219
+ code --folder-uri vscode-remote://ssh-remote+aws-gpu1/home/ubuntu/workspace
220
+ ```
221
+
222
+ Then install the [Nsight VSCE extension](https://marketplace.visualstudio.com/items?itemName=NVIDIA.nsight-vscode-edition) on the remote when prompted. Open `saxpy.cu`, set a breakpoint, and press F5.
223
+
224
+ See [Nsight remote profiling guide](docs/nsight-remote-profiling.md) for more details on CUDA debugging and profiling workflows.
225
+
194
226
  ### 📋 Listing Resources
195
227
 
196
228
  ```bash
@@ -220,6 +252,9 @@ aws-bootstrap status
220
252
  # Include GPU info (CUDA toolkit + driver version, GPU name, architecture) via SSH
221
253
  aws-bootstrap status --gpu
222
254
 
255
+ # Hide connection commands (shown by default for each running instance)
256
+ aws-bootstrap status --no-instructions
257
+
223
258
  # List instances in a specific region
224
259
  aws-bootstrap status --region us-east-1
225
260
 
@@ -310,7 +345,7 @@ aws-bootstrap launch --instance-type t3.medium --ami-filter "ubuntu/images/hvm-s
310
345
  | GPU instance pricing | [instances.vantage.sh](https://instances.vantage.sh/aws/ec2/g4dn.xlarge) |
311
346
  | Spot instance quotas | [AWS docs](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-spot-limits.html) |
312
347
  | Deep Learning AMIs | [AWS docs](https://docs.aws.amazon.com/dlami/latest/devguide/what-is-dlami.html) |
313
- | Nvidia Nsight remote debugging | [Nvidia docs](https://docs.nvidia.com/nsight-visual-studio-edition/3.2/Content/Setup_Remote_Debugging.htm) |
348
+ | Nsight remote GPU profiling | [Guide](docs/nsight-remote-profiling.md) — Nsight Compute, Nsight Systems, and Nsight VSCE on EC2 |
314
349
 
315
350
  Tutorials on setting up a CUDA environment on EC2 GPU instances:
316
351
 
@@ -0,0 +1,27 @@
1
+ aws_bootstrap/__init__.py,sha256=kl_jvrunGyIyizdRqAP6ROb5P1BBrXX5PTq5gq1ipU0,82
2
+ aws_bootstrap/cli.py,sha256=XqCKxyc294krVtggrsqm2cYrHR6DWaqQeuzrRAN5u_c,20501
3
+ aws_bootstrap/config.py,sha256=TeCOYDlijT-KD5SFIzc-VvBhOqcq9YCgen9NK63rka8,895
4
+ aws_bootstrap/ec2.py,sha256=LHpzW91ayK45gsWV_B4LanSZIhWggqTsL31qHUceiaA,12274
5
+ aws_bootstrap/gpu.py,sha256=WTnHR0s3mQHDlnzqRgqAC6omWz7nT5YtGpcs0Bf88jk,692
6
+ aws_bootstrap/ssh.py,sha256=UFRDgNR8cljV-lwMvCy_BAJQBz7gj4a_cQIulf-2A10,19226
7
+ aws_bootstrap/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ aws_bootstrap/resources/gpu_benchmark.py,sha256=1eFt_3MXvoLhs9HahrRPhbxvtdjFaXG2Ty3GEg7Gud0,29366
9
+ aws_bootstrap/resources/gpu_smoke_test.ipynb,sha256=XvAOEIPa5H9ri5mRZqOdknmwOwKNvCME6DzBGuhRYfg,10698
10
+ aws_bootstrap/resources/launch.json,sha256=ZOcvHLy3-zBOqRTtFzuyn-_2tB64yuEn8PrJOoZ-PgE,1484
11
+ aws_bootstrap/resources/remote_setup.sh,sha256=z_YGdzwEHWInkE3dZVbBNa0F_joTeVhnOpCYOj1CK30,8331
12
+ aws_bootstrap/resources/requirements.txt,sha256=gpYl1MFCfWXiAhbIUgAjuTHONz3MKci25msIyOkMmUk,75
13
+ aws_bootstrap/resources/saxpy.cu,sha256=1BSESEwGGCx3KWx9ZJ8jiPHQ42KzQN6i2aP0I28bPsA,1178
14
+ aws_bootstrap/resources/tasks.json,sha256=6U8pB1N8YIWgUCfFet4ne3nYnI92tWv5D5kPiQG3Zlg,1576
15
+ aws_bootstrap/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ aws_bootstrap/tests/test_cli.py,sha256=Lwzpdovq_iJFB6qZ8NuySqzHFkQ_2Q8AAGXdITXi1Vo,32564
17
+ aws_bootstrap/tests/test_config.py,sha256=arvET6KNl4Vqsz0zFrSdhciXGU688bfsvCr3dSpziN0,1050
18
+ aws_bootstrap/tests/test_ec2.py,sha256=Jmqsjv973hxXbZWfGgECtm6aa2156Lzji227sYMBuMg,10547
19
+ aws_bootstrap/tests/test_gpu.py,sha256=rbMuda_sIVbaCzkWXoLv9YIfnWztgRoP7NuVL8XHrUY,3871
20
+ aws_bootstrap/tests/test_ssh_config.py,sha256=iQDd3hJ8to-2-QHW26Brtglfl0q0P6sCE6U_itxoNyY,11609
21
+ aws_bootstrap/tests/test_ssh_gpu.py,sha256=dRp86Og-8GqiATSff3rxhu83mBZdGgqI4UOnoC00Ln0,1454
22
+ aws_bootstrap_g4dn-0.4.0.dist-info/licenses/LICENSE,sha256=Hen77Mt8sazSQJ9DgrmZuAvDwo2vc5JAkR_avuFV-CM,1067
23
+ aws_bootstrap_g4dn-0.4.0.dist-info/METADATA,sha256=0OQsG5kVwsbfT7dfaZoNrkOlfNRUrKr9NwljtLBKj1I,13483
24
+ aws_bootstrap_g4dn-0.4.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
25
+ aws_bootstrap_g4dn-0.4.0.dist-info/entry_points.txt,sha256=T8FXfOgmLEvFi8DHaFJ3tCzId9J3_d2Y6qT98OXxCjA,57
26
+ aws_bootstrap_g4dn-0.4.0.dist-info/top_level.txt,sha256=mix9gZRs8JUv0OMSB_rwdGcRnTKzsKgHrE5fyAn5zJw,14
27
+ aws_bootstrap_g4dn-0.4.0.dist-info/RECORD,,
@@ -1,22 +0,0 @@
1
- aws_bootstrap/__init__.py,sha256=kl_jvrunGyIyizdRqAP6ROb5P1BBrXX5PTq5gq1ipU0,82
2
- aws_bootstrap/cli.py,sha256=3PWGU4djqCvABNpLvYTk473Nmmmrad3JQ3iQtg5YmnE,17917
3
- aws_bootstrap/config.py,sha256=bOADtpujEacED0pu9m7D781UFlMhZrmtHQ7eqI6ySjk,834
4
- aws_bootstrap/ec2.py,sha256=-yEyGMCycY4ccsmbgqHnLK2FRFWX2kr7nLfYWXSKeaY,12242
5
- aws_bootstrap/ssh.py,sha256=-8F0PAkl7CCY1b9n46ZhWJ6faIMlSvA26BleeIp-rMA,17533
6
- aws_bootstrap/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- aws_bootstrap/resources/gpu_benchmark.py,sha256=2uoss2bZGhg7c3D7Hg1-EJlOVDtzAH4co1ahSvF_lVU,29080
8
- aws_bootstrap/resources/gpu_smoke_test.ipynb,sha256=XvAOEIPa5H9ri5mRZqOdknmwOwKNvCME6DzBGuhRYfg,10698
9
- aws_bootstrap/resources/remote_setup.sh,sha256=FzpXEw-LvlXROi-PmO72yEyDWWi-3Tul6D7-vFDubXQ,5460
10
- aws_bootstrap/resources/requirements.txt,sha256=gpYl1MFCfWXiAhbIUgAjuTHONz3MKci25msIyOkMmUk,75
11
- aws_bootstrap/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- aws_bootstrap/tests/test_cli.py,sha256=xGCC07aPZMc5pExo__qz7X1Tm2v9Z1Xn4K99JocESas,23627
13
- aws_bootstrap/tests/test_config.py,sha256=arvET6KNl4Vqsz0zFrSdhciXGU688bfsvCr3dSpziN0,1050
14
- aws_bootstrap/tests/test_ec2.py,sha256=Jmqsjv973hxXbZWfGgECtm6aa2156Lzji227sYMBuMg,10547
15
- aws_bootstrap/tests/test_ssh_config.py,sha256=Rt3e7B22d8kK0PzFgXB4gwpF4HvIseiqzcpouCwMo5M,10333
16
- aws_bootstrap/tests/test_ssh_gpu.py,sha256=W6GQzILCy_qPrvWQlCC8Ris-vuTzTGiyNXEyMzwD1kM,5154
17
- aws_bootstrap_g4dn-0.2.0.dist-info/licenses/LICENSE,sha256=Hen77Mt8sazSQJ9DgrmZuAvDwo2vc5JAkR_avuFV-CM,1067
18
- aws_bootstrap_g4dn-0.2.0.dist-info/METADATA,sha256=XqtBIr0EdnRvsy83usvZ5n-B9WNpfLCGoVaVR9_1eaI,11927
19
- aws_bootstrap_g4dn-0.2.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
20
- aws_bootstrap_g4dn-0.2.0.dist-info/entry_points.txt,sha256=T8FXfOgmLEvFi8DHaFJ3tCzId9J3_d2Y6qT98OXxCjA,57
21
- aws_bootstrap_g4dn-0.2.0.dist-info/top_level.txt,sha256=mix9gZRs8JUv0OMSB_rwdGcRnTKzsKgHrE5fyAn5zJw,14
22
- aws_bootstrap_g4dn-0.2.0.dist-info/RECORD,,