Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
b40a798
Tutorials/Accelerated Python: Add n_warmup to benchmarks and adjust m…
brycelelbach Mar 9, 2026
86a657b
Tutorials/Accelerated Python: Clarify memory and device explanations …
brycelelbach Mar 9, 2026
e99dc49
Tutorials/Accelerated Python: Use binary units (2**30) for byte-to-GB…
brycelelbach Mar 9, 2026
1402dc8
Tutorials/Accelerated Python: Clarify ndarray properties and expand V…
brycelelbach Mar 9, 2026
901c430
Tutorials/Accelerated Python: Fix inconsistencies between notebooks a…
brycelelbach Mar 9, 2026
0f0f10c
Add pre-commit hooks documentation to CONTRIBUTING.md
brycelelbach Mar 9, 2026
4694b46
Tutorials/Accelerated Python: Label eigenvalue prints in notebook 05.
brycelelbach Mar 9, 2026
d4eaf4b
Tutorials/Accelerated Python: Fix inconsistencies in libraries and ke…
brycelelbach Mar 9, 2026
248179a
Tutorials/Accelerated Python: Replace hardcoded JPEG file size with d…
brycelelbach Mar 9, 2026
ada696a
Tutorials/Accelerated Python: Normalize single quotes to double quote…
brycelelbach Mar 9, 2026
c3ca5b9
Tutorials/Accelerated Python: Improve formatting in notebook 05 print…
brycelelbach Mar 9, 2026
01026d5
Tutorials/Accelerated Python: Add analytic solution assertions and tu…
brycelelbach Mar 9, 2026
dc0df09
Tutorials/Accelerated Python: Write checkpoints to /tmp in notebooks …
brycelelbach Mar 9, 2026
8b95f1f
Tutorials/Accelerated Python: Fix missing metadata/execution_count in…
brycelelbach Mar 9, 2026
e15afce
Tutorials/Accelerated Python/NumPy to CuPy: Output formatting consist…
brycelelbach Mar 9, 2026
b43a40a
Tutorials/Accelerated Python/cuda-cccl: Remove extra newline from sol…
brycelelbach Mar 9, 2026
798267b
Tutorials/Accelerated Python: Regenerate output cells on L4 GPUs for …
brycelelbach Mar 9, 2026
178f246
Tutorials: Add parentheses to function name references in markdown an…
brycelelbach Mar 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,60 @@ You can use the following scripts to help with development:

* **`brev/dev-test.bash <tutorial-name|docker-compose-file>`** - Tests a Docker Compose file with the local repository mounted. Calls `test-docker-compose.bash` to run the tutorial's tests.

## Pre-Commit Hooks

This repository uses [pre-commit](https://pre-commit.com/) to run automated
checks before commits and pushes. Install the hooks after cloning:

```bash
pip install pre-commit
pre-commit install
pre-commit install --hook-type pre-push
```

The following hooks are configured:

### `notebook-format` (pre-commit)

Checks that Jupyter notebooks are in **canonical format**: 1-space JSON
indentation, sorted keys, standard metadata (kernelspec, colab settings,
language_info), nbformat 4.5, and cell IDs present. Non-SOLUTION notebooks
must have clean outputs (no outputs or execution counts). To auto-fix
formatting issues:

```bash
python3 brev/test-notebook-format.py --fix
```

### `git-lfs` (pre-commit)

Checks that binary files (`.pptx`, `.pdf`, `.png`, `.jpg`, `.jpeg`, `.gif`,
`.webp`) are properly tracked by Git LFS. Also verifies that no
`.gitattributes` files exist in subdirectories. If a file isn't tracked, fix it
with:

```bash
git rm --cached <file>
git add <file>
```

### `git-signatures` (pre-push)

Checks that all commits on your branch (compared to `origin/main`) are
cryptographically signed. Runs on `git push`, not on commit. See
[GitHub's documentation on commit signature verification](https://docs.github.com/en/authentication/managing-commit-signature-verification)
for setup instructions.

### `test-links` (manual)

Checks that links in Markdown files and Jupyter notebooks are valid using
[lychee](https://github.com/lycheeverse/lychee). This hook is configured as
a **manual** stage and won't run automatically. Run it explicitly with:

```bash
pre-commit run test-links --hook-stage manual --all-files
```

## License

The preferred license for contributions to this project is the detailed in the
Expand Down
23 changes: 19 additions & 4 deletions brev/test-notebook-format.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,16 +208,26 @@ def canonicalize_notebook(notebook_path: Path) -> tuple[str, list[str]]:
if cells_missing_ids:
problems.append(f" {cells_missing_ids} cell(s) missing id field")

# Check for malformed stream outputs (SOLUTION notebooks keep outputs)
# Check for malformed outputs (SOLUTION notebooks keep outputs)
if solution:
for i, cell in enumerate(raw.get("cells", [])):
if cell.get("cell_type") != "code":
continue
for j, out in enumerate(cell.get("outputs", [])):
if out.get("output_type") == "stream" and "name" not in out:
otype = out.get("output_type")
if otype == "stream" and "name" not in out:
problems.append(
f" Cell {i} output {j}: stream output missing 'name'"
)
if otype in ("execute_result", "display_data"):
if "metadata" not in out:
problems.append(
f" Cell {i} output {j}: {otype} missing 'metadata'"
)
if otype == "execute_result" and "execution_count" not in out:
problems.append(
f" Cell {i} output {j}: execute_result missing 'execution_count'"
)

# -- Build canonical form via nbformat -----------------------------------
with warnings.catch_warnings():
Expand All @@ -229,13 +239,18 @@ def canonicalize_notebook(notebook_path: Path) -> tuple[str, list[str]]:
nb.nbformat = STANDARD_NBFORMAT
nb.nbformat_minor = STANDARD_NBFORMAT_MINOR

# Fix malformed stream outputs
# Fix malformed outputs
for cell in nb.cells:
if cell.get("cell_type") != "code":
continue
for out in cell.get("outputs", []):
if out.get("output_type") == "stream" and "name" not in out:
otype = out.get("output_type")
if otype == "stream" and "name" not in out:
out["name"] = "stdout"
if otype in ("execute_result", "display_data") and "metadata" not in out:
out["metadata"] = {}
if otype == "execute_result" and "execution_count" not in out:
out["execution_count"] = cell.get("execution_count")

# Strip outputs for non-SOLUTION notebooks
if not solution:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,16 @@
"\n",
"- **Data**: A pointer to the memory location holding the elements.\n",
"- **dtype**: The data type (e.g., `int32`, `float64`) which is uniform across all elements.\n",
"- **Shape**: A tuple defining the size along each dimension (e.g., $(100, 50)$ for 100 rows and 50 columns).\n",
"- **Strides**: The number of bytes to step in memory to reach the next element along each dimension—this is how NumPy efficiently handles different shapes and views.\n",
"- **Shape**: A tuple defining the size along each dimension (e.g., `(100, 50)` for 100 rows and 50 columns).\n",
"- **Strides**: The number of bytes to step in memory to reach the next element along each dimension. This is how NumPy efficiently handles different shapes and views.\n",
"\n",
"Let's explore these properties by creating a large dataset.\n",
"\n",
"---\n",
"\n",
"**Quick Docs**\n",
"- `np.arange(start, stop, step)`: Returns evenly spaced values in the half-open interval $[\\text{start}, \\text{stop})$.\n",
"- `arr.nbytes`: Total bytes consumed by the array's elements (in bytes).\n",
"- `arr.nbytes`: Total bytes of storage for the array's elements.\n",
"- `arr.ndim`: The number of array dimensions (integer).\n",
"- `arr.size`: The total number of elements in the array (integer).\n",
"- `arr.shape`: The tuple of array dimensions.\n"
Expand Down Expand Up @@ -97,7 +97,8 @@
"source": [
"# TODO: Create the input data array with the numbers 1 to 50_000_000 (inclusive).\n",
"# Hint: np.arange generates values within a half-open interval [start, stop)\n",
"arr = ..."
"arr = ...\n",
"arr"
]
},
{
Expand All @@ -110,7 +111,7 @@
"outputs": [],
"source": [
"# TODO: Calculate how large the array is in GB with nbytes.\n",
"# Hint: GB is 1e9 bytes. The .nbytes attribute returns the total bytes consumed by the elements.\n",
"# Hint: GB is 2**30 bytes. The .nbytes attribute returns the total bytes consumed by the elements.\n",
"# Note: This demonstrates that arrays are dense memory blocks, unlike pointer-heavy Python lists.\n",
"arr..."
]
Expand All @@ -124,7 +125,7 @@
},
"outputs": [],
"source": [
"# TODO: How many dimensions does the array have? (ndim)\n",
"# TODO: How many dimensions does the array have?\n",
"arr..."
]
},
Expand All @@ -137,7 +138,7 @@
},
"outputs": [],
"source": [
"# TODO: How many elements does the array have? (size)\n",
"# TODO: How many elements does the array have?\n",
"arr..."
]
},
Expand All @@ -163,7 +164,11 @@
"\n",
"Arrays can logically represent data in many ways (e.g., 1D signal, 2D image, 4D video batch) independent of the underlying physical memory block.\n",
"\n",
"A critical performance feature is that operations like transposing or `reshape` often return a **View** instead of a **Copy**. A View only changes the metadata (`shape` and `strides`) without duplicating the physical data, making these operations nearly instantaneous.\n",
"Most operations, like adding two arrays together, returns a **Copy**, which requires allocating a new array, which can negatively impact performance.\n",
"\n",
"Some operations, like transposing or `reshape()` often return a **View** instead of a **Copy**. A View only changes the metadata (`shape` and `strides`) without duplicating the physical data, making these operations nearly instantaneous.\n",
"\n",
"Most Copy operations take an `out` parameter that takes an array; if it provided, the result is written to that array instead of allocating a new one. For example, `A + B` or `np.add(A, B)` will return a new array with the result, but `np.add(A + B, out=A)` will place the result in `A` without an allocation.\n",
"\n",
"---\n",
"\n",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"import cv2\n",
"import urllib.request"
"import urllib.request\n",
"import os"
]
},
{
Expand All @@ -52,7 +53,10 @@
"source": [
"urllib.request.urlretrieve(\n",
" \"https://drive.usercontent.google.com/download?id=1ClKrHt4-SIHaeBJdF0K3MG64jyVnt62L&export=download\",\n",
" \"loonie.jpg\")"
" \"loonie.jpg\")\n",
"\n",
"jpeg_size = os.path.getsize(\"loonie.jpg\")\n",
"print(f\"JPEG file size: {jpeg_size} bytes\")"
]
},
{
Expand Down Expand Up @@ -242,7 +246,7 @@
"id": "spCXu88mw-mT"
},
"source": [
"**TODO: Print the compression ratio for the values of `n` used above. This is the number of bytes of the reduced arrays added together and divided by the size of the original grayscale image array. How does this compare to the size of the original color JPEG, which is 756473 bytes?** _Hint: `.nbytes` works on sliced arrays!_"
"**TODO: Print the compression ratio for the values of `n` used above. This is the number of bytes of the reduced arrays added together and divided by the size of the original grayscale image array. How does this compare to the size of the original color JPEG (`jpeg_size`)?** _Hint: `.nbytes` works on sliced arrays!_"
]
},
{
Expand Down
Loading
Loading