From 1ebfb8111067a5ec07447e5f0f45a42308da7012 Mon Sep 17 00:00:00 2001 From: Jaya Venkatesh Date: Mon, 13 Jul 2026 10:31:45 -0500 Subject: [PATCH 1/2] add html slides and edits --- introduction/introduction-to-gpu-stack.html | 335 ++++++++++++++++++++ introduction/introduction-to-gpu-stack.md | 26 +- 2 files changed, 348 insertions(+), 13 deletions(-) create mode 100644 introduction/introduction-to-gpu-stack.html diff --git a/introduction/introduction-to-gpu-stack.html b/introduction/introduction-to-gpu-stack.html new file mode 100644 index 0000000..c484ed9 --- /dev/null +++ b/introduction/introduction-to-gpu-stack.html @@ -0,0 +1,335 @@ +Understanding GPUs
+

Understanding GPUs

+

From the device to RAPIDS : how the pieces fit together

+

Jaya Venkatesh and Naty Clementi · NVIDIA
+SciPy 2026 · GPU Deployment & Debugging Tutorial ·

+
+
+

Why bring data science to the GPU?

+
+

Bigger data

Datasets keep outgrowing what's comfortable on a CPU

+

Parallel by nature

Filtering, aggregating, and math over millions of rows are independent

+

Familiar APIs

You can get there without leaving Python

+
+
+
+

But how does a GPU actually help you accelerate your code?

+
+
+
+

1 · The GPU vs the CPU

+
+

Both process data, just with a different philosophy

+
+
+
+

A CPU and a GPU are built for different jobs

+
+ +Source: NVIDIA CUDA C++ Programming Guide +
+
+
+

CPU: handles many different tasks, finishing each one fastlow latency.

+
+
+

GPU: runs the same task across lots of data all at once → high throughput.

+
+
+
+
+

A few specialists vs many simple workers

+
+
CPU
+ +
a few powerful cores
+
+
GPU
+ +
many simple cores
+
+
+
    +
  • CPU cores are few but powerful, great at complex, branchy logic
  • +
  • GPU cores are many but simple, built to all do the same operation on different data, in parallel
  • +
  • The more your work splits into independent pieces, the more using the GPU pays off
  • +
+
+
+

Two machines, two separate memories

+
+
CPU
System RAM
large, general-purpose
+ +
GPU
VRAM
its own dedicated memory
+
+
+
    +
  • The GPU is a separate device with its own memory: it cannot read data sitting in system RAM
  • +
  • Before the GPU can work on your data, that data must be copied into the GPU's memory
  • +
+
+

So step one of "run it on the GPU" is always: get the data there.

+
+
+
+

The slow part is getting data across

+
+
CPU + RAM
high memory bandwidth
+ +
GPU + VRAM
high memory bandwidth
+
+
+
    +
  • Inside each device, memory bandwidth is high: RAM and VRAM are built to feed their cores fast
  • +
  • The interconnect between them (PCIe) carries data at far lower bandwidth
  • +
  • The expensive part often isn't the computing; it's the host-to-device transfer across PCIe
  • +
+
+
+

2 · How a GPU processes data

+
+

Many small tasks at once, not one big task in order

+
+
+
+

Parallel work: threads, blocks, grids

+

The GPU runs one operation across thousands of elements at once, one thread per item.

+
+ +Source: NVIDIA CUDA C++ Programming Guide +
+
    +
  • Threads are grouped into blocks; blocks together form a grid
  • +
  • You describe the work once; the GPU runs it across the whole grid in parallel
  • +
  • Perfect for independent work, not optimal for sequential, step-by-step logic
  • +
+
+
+

Why GPU is better for parallel than sequential

+
+
Sequential
+ +
one after another → time
+
+
Parallel
+ +
all at once → done sooner
+
+
+
+
    +
  • When work items are independent, their order doesn't matter i.e item B doesn't need item A's result
  • +
  • A GPU runs a huge number of independent items at the same time
  • +
  • Step-by-step work that must run in order can't use that; this isn't where a GPU shines
  • +
+
+
+

SMs and warps: where the work runs

+
+
+
    +
  • A GPU is made of Streaming Multiprocessors (SMs), its parallel engines
  • +
  • Your blocks are distributed across the SMs; more SMs → more work at once, automatically
  • +
  • Inside an SM, threads run in lock-step groups of 32, called a warp (SIMT: Single Instruction, Multiple Threads)
  • +
+
+
+ +Source: NVIDIA CUDA C++ Programming Guide +
+
+
+
+

Compute-bound vs IO-bound

+
+
+

✓ Compute-bound: GPU shines

+
    +
  • A matrix multiply, training a model, transforming millions of rows
  • +
  • Lots of math → the cores stay saturated
  • +
+
+
+

✗ IO-bound: GPU waits

+
    +
  • Reading a file off disk, waiting on a network call
  • +
  • The bottleneck is waiting, not math
  • +
+
+
+
+
+

Adding compute units only helps when computation is the bottleneck. If you're waiting on data, more cores don't make the wait shorter.

+
+
+
+

So what actually fits a GPU?

+
+

✓ Parallel

The same operation over millions of elements

+

✗ Sequential

Each step depends on the previous one

+

✓ Compute-bound

Lots of math per byte of data

+

✗ IO-bound

Time spent waiting on disk / network

+
+
+

Takeaway: a GPU shines on big, math-heavy, parallel work, so the goal is to match your workload to its strengths.

+
+
+
+

3 · CUDA

+
+

The bridge that lets software use the GPU

+
+
+
+

What is CUDA, and why C?

+
+
+

CUDA is NVIDIA's parallel-computing platform + programming model, built on C/C++ for its low-level speed and control over the hardware.

+
    +
  • You write GPU code as kernels in CUDA C/C++: standard C++/C plus a few extensions, compiled by nvcc
  • +
  • The host (CPU) runs serial code and launches kernels on the device (GPU)
  • +
  • Serial host code and parallel kernels alternate (see diagram →)
  • +
+
+
+ +Source: NVIDIA CUDA C++ Programming Guide +
+
+
+
+

A CUDA kernel is just C, with a twist

+
// __global__ marks a function that runs ON the GPU, once per thread
+__global__ void add(float *a, float *b, float *c) {
+    int i = blockIdx.x * blockDim.x + threadIdx.x;  // which element am I?
+    c[i] = a[i] + b[i];                             // one thread, one element
+}
+
+// The host launches it across a grid:  <<< blocks, threads-per-block >>>
+add<<<blocks, threads>>>(a, b, c);   // every element added in parallel
+
+
+

The __global__ keyword and the <<< >>> launch syntax are the C extensions CUDA adds. A sum, a sort, a join all become kernels like this, but you'll rarely write one: the layers above ship them ready-made.

+
+
+
+

4 · CUDA Python & the CUDA Toolkit

+
+

The building blocks above raw CUDA

+
+
+
+

The CUDA Toolkit: the foundation libraries

+

Everything above the driver is built on the Toolkit. It provides:

+
+

Compiler & runtime

nvcc, libcudart, headers

+

Math libraries

cuBLAS, cuFFT, cuSPARSE, cuSOLVER, cuRAND

+

Communication

NCCL, NVSHMEM for multi-GPU

+ +
+
+

These hand-tuned libraries are the ready-made kernels: the sorts, joins, and math behind your data work come straight from here.

+
+
+
+

CUDA Python: pick your entrypoint

+

You don't have to write C++ to use CUDA.

+
+

CuPy

"I have NumPy code" → swap in GPU arrays

+

Numba (CUDA)

"I need a custom kernel" → write it in Python

+

cuda.core / cuda.bindings

"I'm building a library / need low-level control"

+
+
+
+

All three reach the GPU underneath.

+
+
+
+

5 · RAPIDS & CUDA-X

+
+

Predefined kernels for data science

+
+
+
+

CUDA-X & RAPIDS: kernels you don't have to write

+
+

CUDA-X

NVIDIA's library collection on top of CUDA: math, deep learning (cuDNN, TensorRT), comms, data science

+

RAPIDS

The data-science slice of CUDA-X, open-source, with familiar Python APIs

+
+

RAPIDS packages thousands of optimized GPU kernels behind APIs you already know:

+ + + + + + + + + + + + + + + +
You already knowRAPIDS gives youfor
pandas · scikit-learn · NumPy · NetworkXcuDF · cuML · CuPy · cuGraphdataframes · ML · arrays · graphs
+
+
+

You stand on thousands of tuned kernels

+
+
+sortjoingroup-byfiltermatmulFFTreducescank-meansPCArandomSVDrollinghistogram +
+
+
one Python callcuDF · cuML · CuPy
+
+
+
    +
  • Each tile is a GPU kernel that experts wrote and tuned over years
  • +
  • RAPIDS bundles them behind the APIs you already know
  • +
  • You write familiar Python; RAPIDS runs the CUDA for you
  • +
+
+

How much faster on real data? We'll explore that in this tutorial

+
+
+
+

Putting it all together

+
+
Your code & notebooks pandas, scikit-learn, NumPy, your scripts
+
RAPIDS and CUDA-X cuDF · cuML · cuGraph, the data-science slice of CUDA-X
+
CUDA Python cuda.core · cuda.bindings · Numba · CuPy
+
CUDA Toolkit cuBLAS · cuSPARSE · NCCL · libcudart · nvcc
+
NVIDIA driver libcuda, the system layer that talks to the hardware
+
GPU hardware SMs · warps · thousands of cores · VRAM
+
+
+

Each layer builds on the one below it; your Python sits at the very top.

+
+
+
+

The mental model for today

+
    +
  • A GPU is many simple cores + its own fast memory → built for parallel, math-heavy work
  • +
  • Getting data onto the GPU is the slow part → keep it there
  • +
  • CUDA (C-based) exposes that power, but you rarely touch it
  • +
  • CUDA Python and the CUDA Toolkit are the building blocks
  • +
  • RAPIDS / CUDA-X hand you ready-made kernels, so familiar Python code just runs on the GPU
  • +
+
+
+

Next: let's get on a GPU

+
+
\ No newline at end of file diff --git a/introduction/introduction-to-gpu-stack.md b/introduction/introduction-to-gpu-stack.md index 72e60f0..acf64c7 100644 --- a/introduction/introduction-to-gpu-stack.md +++ b/introduction/introduction-to-gpu-stack.md @@ -8,17 +8,17 @@ paginate: true # Understanding GPUs -## From the *device* to *RAPIDS* : how the pieces fit together +## From the *GPU* to *RAPIDS* : how the pieces fit together **Jaya Venkatesh and Naty Clementi · NVIDIA** -SciPy 2026 · GPU Deployment & Debugging Tutorial · +SciPy 2026 · GPU Deployment & Debugging Tutorial --- # Why bring data science to the GPU?
-

Bigger data

Datasets keep outgrowing what's comfortable on a CPU

+

Bigger data

Datasets keep outgrowing the power of CPUs

Parallel by nature

Filtering, aggregating, and math over millions of rows are independent

Familiar APIs

You can get there without leaving Python

@@ -72,7 +72,7 @@ SciPy 2026 · GPU Deployment & Debugging Tutorial · - CPU cores are **few but powerful**, great at complex, branchy logic -- GPU cores are **many but simple**, built to all do the **same operation** on different data, together +- GPU cores are **many but simple**, built to all do the **same operation** on different data, in parallel - The more your work splits into **independent pieces**, the more **using the GPU pays off** --- @@ -139,7 +139,7 @@ The GPU runs **one operation across thousands of elements at once**, one **threa
Sequential
-
one after another → time →
+
one after another → time
Parallel
@@ -149,7 +149,7 @@ The GPU runs **one operation across thousands of elements at once**, one **threa
-- When work items are **independent**, their order doesn't matter: item B doesn't need item A's result +- When work items are **independent**, their order doesn't matter i.e item B doesn't need item A's result - A GPU runs a **huge number of independent items at the same time** - Step-by-step work that must run **in order** can't use that; this isn't where a GPU shines @@ -227,7 +227,7 @@ The GPU runs **one operation across thousands of elements at once**, one **threa **CUDA** is NVIDIA's parallel-computing **platform + programming model**, built on **C/C++** for its low-level speed and control over the hardware. -- You write GPU code as **kernels** in **CUDA C/C++**: standard C/C++ plus a few extensions, compiled by `nvcc` +- You write GPU code as **kernels** in **CUDA C/C++**: standard C++/C plus a few extensions, compiled by `nvcc` - The **host** (CPU) runs serial code and **launches kernels** on the **device** (GPU) - Serial host code and parallel kernels **alternate** (see diagram →) @@ -272,16 +272,16 @@ Everything above the driver is **built on the Toolkit**. It provides:

Compiler & runtime

nvcc, libcudart, headers

Math libraries

cuBLAS, cuFFT, cuSPARSE, cuSOLVER, cuRAND

Communication

NCCL, NVSHMEM for multi-GPU

-

Why it's central

cuDF, cuML & CuPy don't reinvent math, they call these

+
> These hand-tuned libraries are the **ready-made kernels**: the sorts, joins, and math behind your data work come straight from here. --- -# CUDA Python: pick the right door +# CUDA Python: pick your entrypoint -You don't have to write C++ to use CUDA. Match the door to the need: +You don't have to write C++ to use CUDA.

CuPy

"I have NumPy code" → swap in GPU arrays

@@ -291,7 +291,7 @@ You don't have to write C++ to use CUDA. Match the door to the need:
-> All three reach the **same CUDA underneath**. RAPIDS sits on top of them, so most of the time you won't pick a door at all. +> All three reach the **GPU underneath**. --- @@ -343,9 +343,9 @@ RAPIDS packages thousands of **optimized GPU kernels** behind APIs you already k
Your code & notebooks pandas, scikit-learn, NumPy, your scripts
-
RAPIDS cuDF · cuML · cuGraph, the data-science slice of CUDA-X
+
RAPIDS and CUDA-X cuDF · cuML · cuGraph, the data-science slice of CUDA-X
CUDA Python cuda.core · cuda.bindings · Numba · CuPy
-
CUDA-X + CUDA Toolkit cuBLAS · cuSPARSE · NCCL · libcudart · nvcc
+
CUDA Toolkit cuBLAS · cuSPARSE · NCCL · libcudart · nvcc
NVIDIA driver libcuda, the system layer that talks to the hardware
GPU hardware SMs · warps · thousands of cores · VRAM
From 35d73d5ac5ba26d74a9a8a907f6118da2a4dbf11 Mon Sep 17 00:00:00 2001 From: Jaya Venkatesh Date: Mon, 13 Jul 2026 10:41:59 -0500 Subject: [PATCH 2/2] run precommit --- introduction/introduction-to-gpu-stack.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/introduction/introduction-to-gpu-stack.html b/introduction/introduction-to-gpu-stack.html index c484ed9..2a7083a 100644 --- a/introduction/introduction-to-gpu-stack.html +++ b/introduction/introduction-to-gpu-stack.html @@ -235,7 +235,7 @@

The CUDA Toolkit: the foundat

Compiler & runtime

nvcc, libcudart, headers

Math libraries

cuBLAS, cuFFT, cuSPARSE, cuSOLVER, cuRAND

Communication

NCCL, NVSHMEM for multi-GPU

- +

These hand-tuned libraries are the ready-made kernels: the sorts, joins, and math behind your data work come straight from here.

@@ -332,4 +332,4 @@

Next: let's get on a GPU

\ No newline at end of file +!function(){"use strict";function e(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var t,n,r=(n||(n=1,t={from:function(e,t){var n,r=1===(e.parent||e).nodeType?e.parent||e:document.querySelector(e.parent||e),o=[].filter.call("string"==typeof e.slides?r.querySelectorAll(e.slides):e.slides||r.children,function(e){return"SCRIPT"!==e.nodeName}),i={},a=function(e,t){return(t=t||{}).index=o.indexOf(e),t.slide=e,t},s=function(e,t){i[e]=(i[e]||[]).filter(function(e){return e!==t})},l=function(e,t){return(i[e]||[]).reduce(function(e,n){return e&&!1!==n(t)},!0)},c=function(e,t){o[e]&&(n&&l("deactivate",a(n,t)),n=o[e],l("activate",a(n,t)))},d=function(e,t){var r=o.indexOf(n)+e;l(e>0?"next":"prev",a(n,t))&&c(r,t)},u={off:s,on:function(e,t){return(i[e]||(i[e]=[])).push(t),s.bind(null,e,t)},fire:l,slide:function(e,t){if(!arguments.length)return o.indexOf(n);l("slide",a(o[e],t))&&c(e,t)},next:d.bind(null,1),prev:d.bind(null,-1),parent:r,slides:o,destroy:function(e){l("destroy",a(n,e)),i={}}};return(t||[]).forEach(function(e){e(u)}),n||c(0),u}}),t),o=e(r);const i=document.body,a=(...e)=>history.replaceState(...e),s="",l="presenter",c="next",d="overview",u=["",l,d,c],f="bespoke-marp-",m=`data-${f}`,g=(e,{protocol:t,host:n,pathname:r,hash:o}=location)=>{const i=e.toString();return`${t}//${n}${r}${i?"?":""}${i}${o}`},p=()=>i.dataset.bespokeView,v=e=>new URLSearchParams(location.search).get(e),h=(e,t={})=>{const n={location,setter:a,...t},r=new URLSearchParams(n.location.search);for(const t of Object.keys(e)){const n=e[t];"string"==typeof n?r.set(t,n):r.delete(t)}try{n.setter({...window.history.state??{}},"",g(r,n.location))}catch(e){console.error(e)}},w=(()=>{const e="bespoke-marp";try{return localStorage.setItem(e,e),localStorage.removeItem(e),!0}catch{return!1}})(),y=e=>{try{return localStorage.getItem(e)}catch{return null}},b=(e,t)=>{try{return localStorage.setItem(e,t),!0}catch{return!1}},k=e=>{try{return localStorage.removeItem(e),!0}catch{return!1}},x=(e,t)=>{const n="aria-hidden";t?e.setAttribute(n,"true"):e.removeAttribute(n)},E=e=>{e.parent.classList.add(`${f}parent`),e.slides.forEach(e=>e.classList.add(`${f}slide`)),e.on("activate",t=>{const n=`${f}active`,r=t.slide,o=r.classList,i=!o.contains(n);if(e.slides.forEach(e=>{e.classList.remove(n),x(e,!0)}),o.add(n),x(r,!1),i){const e=`${n}-ready`;o.add(e),document.body.clientHeight,o.remove(e)}})},$=e=>{let t=0,n=0;Object.defineProperty(e,"fragments",{enumerable:!0,value:e.slides.map(e=>[null,...e.querySelectorAll("[data-marpit-fragment]")])});const r=r=>void 0!==e.fragments[t][n+r],o=(r,o)=>{t=r,n=o,e.fragments.forEach((e,t)=>{e.forEach((e,n)=>{if(null==e)return;const i=t{if(i){if(r(1))return o(t,n+1),!1;const i=t+1;e.fragments[i]&&o(i,0)}else{const r=e.fragments[t].length;if(n+1{if(r(-1)&&i)return o(t,n-1),!1;const a=t-1;e.fragments[a]&&o(a,e.fragments[a].length-1)}),e.on("slide",({index:t,fragment:n})=>{let r=0;if(void 0!==n){const o=e.fragments[t];if(o){const{length:e}=o;r=-1===n?e-1:Math.min(Math.max(n,0),e-1)}}o(t,r)}),o(0,0)},L=document,S=()=>!(!L.fullscreenEnabled&&!L.webkitFullscreenEnabled),P=()=>!(!L.fullscreenElement&&!L.webkitFullscreenElement),_=e=>{e.fullscreen=()=>{S()&&(async()=>{P()?(L.exitFullscreen||L.webkitExitFullscreen)?.call(L):((e=L.body)=>{(e.requestFullscreen||e.webkitRequestFullscreen)?.call(e)})()})()},document.addEventListener("keydown",t=>{"f"!==t.key&&"F11"!==t.key||t.altKey||t.ctrlKey||t.metaKey||!S()||(e.fullscreen(),t.preventDefault())})},O=`${f}inactive`,T=(e=2e3)=>({parent:t,fire:n})=>{const r=t.classList,o=e=>n(`marp-${e?"":"in"}active`);let i;const a=()=>{i&&clearTimeout(i),i=setTimeout(()=>{r.add(O),o()},e),r.contains(O)&&(r.remove(O),o(!0))};for(const e of["mousedown","mousemove","touchend"])document.addEventListener(e,a);setTimeout(a,0)},I=["AUDIO","BUTTON","INPUT","SELECT","TEXTAREA","VIDEO"],M=e=>{e.parent.addEventListener("keydown",e=>{if(!e.target)return;const t=e.target;(I.includes(t.nodeName)||"true"===t.contentEditable)&&e.stopPropagation()})},A=e=>{window.addEventListener("load",()=>{for(const t of e.slides){const e=t.querySelector("marp-auto-scaling, [data-auto-scaling], [data-marp-fitting]");t.setAttribute(`${m}load`,e?"":"hideable")}})},C=({interval:e=250}={})=>t=>{document.addEventListener("keydown",e=>{if(" "===e.key&&e.shiftKey)t.prev();else if("ArrowLeft"===e.key||"ArrowUp"===e.key||"PageUp"===e.key)t.prev({fragment:!e.shiftKey});else if(" "!==e.key||e.shiftKey)if("ArrowRight"===e.key||"ArrowDown"===e.key||"PageDown"===e.key)t.next({fragment:!e.shiftKey});else if("End"===e.key)t.slide(t.slides.length-1,{fragment:-1});else{if("Home"!==e.key)return;t.slide(0)}else t.next();e.preventDefault()});let n,r,o=0;t.parent.addEventListener("wheel",i=>{let a=!1;const s=(e,t)=>{e&&(a=a||((e,t)=>((e,t)=>{const n="X"===t?"Width":"Height";return e[`client${n}`]{const{overflow:n}=e,r=e[`overflow${t}`];return"auto"===n||"scroll"===n||"auto"===r||"scroll"===r})(getComputedStyle(e),t))(e,t)),e?.parentElement&&s(e.parentElement,t)};if(0!==i.deltaX&&s(i.target,"X"),0!==i.deltaY&&s(i.target,"Y"),a)return;i.preventDefault();const l=Math.sqrt(i.deltaX**2+i.deltaY**2);if(void 0!==i.wheelDelta){if(void 0===i.webkitForce&&Math.abs(i.wheelDelta)<40)return;if(i.deltaMode===i.DOM_DELTA_PIXEL&&l<4)return}else if(i.deltaMode===i.DOM_DELTA_PIXEL&&l<12)return;r&&clearTimeout(r),r=setTimeout(()=>{n=0},e);const c=Date.now()-o0||i.deltaY>0)&&(u="next"),(i.deltaX<0||i.deltaY<0)&&(u="prev"),u&&(t[u](),o=Date.now())})},D=(e=`.${f}osc`)=>{const t=document.querySelector(e);if(!t)return()=>{};const n=(e,n)=>{t.querySelectorAll(`[${m}osc=${JSON.stringify(e)}]`).forEach(n)};if(S()||n("fullscreen",e=>e.style.display="none"),!w){const e=(e,t)=>{e.disabled=!0,e.title=`${t} is disabled due to restricted localStorage.`};n("presenter",t=>e(t,"Presenter view")),n("overview",t=>e(t,"Overview view"))}return e=>{t.addEventListener("click",t=>{if(t.target instanceof HTMLElement){const{bespokeMarpOsc:n}=t.target.dataset;n&&t.target.blur();const r={fragment:!t.shiftKey};"next"===n?e.next(r):"prev"===n?e.prev(r):"fullscreen"===n?e?.fullscreen():"presenter"===n?e.openPresenterView():"overview"===n&&e.toggleOverviewView()}}),e.parent.appendChild(t),e.on("activate",({index:t})=>{n("page",n=>n.textContent=`Page ${t+1} of ${e.slides.length}`)}),e.on("fragment",({index:t,fragments:r,fragmentIndex:o})=>{n("prev",e=>e.disabled=0===t&&0===o),n("next",n=>n.disabled=t===e.slides.length-1&&o===r.length-1)}),e.on("marp-active",()=>x(t,!1)),e.on("marp-inactive",()=>x(t,!0)),S()&&(e=>{for(const t of["","webkit"])L.addEventListener(t+"fullscreenchange",e)})(()=>n("fullscreen",e=>e.classList.toggle("exit",S()&&P())))}},N=e=>{if(e.syncKey&&"string"==typeof e.syncKey)return!0;throw new Error("The current instance of Bespoke.js is incompatible with Marp bespoke sync plugin.")},B=(e={})=>{const t=e.key||window.history.state?.marpBespokeSyncKey||Math.random().toString(36).slice(2),n=`bespoke-marp-sync-${t}`;var r;r={marpBespokeSyncKey:t},h({},{setter:(e,...t)=>a({...e,...r},...t)});const o=()=>{const e=y(n);return e?JSON.parse(e):Object.create(null)},i=e=>{const t=o(),r={...t,...e(t)};return b(n,JSON.stringify(r)),r},s=()=>{window.removeEventListener("pageshow",s),i(e=>({reference:(e.reference||0)+1}))};return e=>{s(),Object.defineProperty(e,"syncKey",{value:t,enumerable:!0});let r=!0;setTimeout(()=>{e.on("fragment",e=>{r&&i(()=>({index:e.index,fragmentIndex:e.fragmentIndex}))})},0),window.addEventListener("storage",t=>{if(t.key===n&&t.oldValue&&t.newValue){const n=JSON.parse(t.oldValue),o=JSON.parse(t.newValue);if(n.index!==o.index||n.fragmentIndex!==o.fragmentIndex)try{r=!1,e.slide(o.index,{fragment:o.fragmentIndex,forSync:!0})}finally{r=!0}}});const a=()=>{const{reference:e}=o();void 0===e||e<=1?k(n):i(()=>({reference:e-1}))};window.addEventListener("pagehide",e=>{e.persisted&&window.addEventListener("pageshow",s),a()}),e.on("destroy",a)}},K=e=>{w&&document.addEventListener("keydown",t=>{("o"!==t.key||t.altKey||t.ctrlKey||t.metaKey)&&"Escape"!==t.key||(t.preventDefault(),e())})};let q=null;const V=({closeOnSelect:e=!0}={})=>t=>{N(t);const n=n=>{const r=(()=>{if(!q||!q.isConnected){q=document.createElement("div"),q.className=`${f}overview`,q.inert=!0;const n=document.createElement("iframe");n.src=(()=>{const n=new URLSearchParams(location.search);return n.set("view","overview"),n.set("sync",t.syncKey),e&&n.set("closeOnSelect","1"),g(n)})(),q.append(n),document.body.append(q)}return q})(),o=n??!r.dataset.open;setTimeout(()=>{if(r.dataset.open=o?"1":"",r.inert=!o,t.skipTransition=o,o){const e=r.querySelector("iframe");try{e?.contentWindow?.focus()}catch{e?.focus()}}else window.focus()},0)};Object.defineProperties(t,{toggleOverviewView:{enumerable:!0,value:n}}),window.addEventListener("message",e=>{e.origin===window.origin&&"closeOverview"===e.data&&n(!1)}),K(n)},j=e=>{const{title:t}=document;document.title="[Overview]"+(t?` - ${t}`:"");const n=!!v("closeOnSelect"),r=()=>{window.parent.postMessage("closeOverview","null"===window.origin?"*":window.origin)};if(e.slides.forEach((t,o)=>{t.tabIndex=0;const i=()=>{e.slide(o,{fragment:-1}),n&&r()};t.addEventListener("click",i),t.addEventListener("keydown",e=>{"Enter"!==e.key&&" "!==e.key||(e.preventDefault(),i())})}),window.addEventListener("keydown",t=>{const n=e.slide(),r=t=>{const r=t=>{const n=e.slides[t].getBoundingClientRect();return[Math.round((n.left+n.right)/2),Math.round((n.top+n.bottom)/2)]};let o=null,i=n;do{const e=r(i);if(o){if(Math.abs(e[0]-o[0])<2)return i}else o=e;i+=t}while(i>=0&&i{e.slides[t]?.scrollIntoView?.({behavior:"smooth",block:"nearest"})}),window.parent!==window){K(r);const e=document.createElement("header");e.className=`${f}overview-header`;const t=document.createElement("button");t.className=`${f}overview-close`,t.type="button",t.title="Close overview",t.textContent=t.title,t.addEventListener("click",r),e.append(t),document.body.prepend(e)}},F=()=>{const e=p();return{[s]:V(),[l]:V({closeOnSelect:!1}),[d]:j}[e]},U=e=>{window.addEventListener("message",t=>{if(t.origin!==window.origin)return;const[n,r]=t.data.split(":");if("navigate"===n){const[t,n]=r.split(",");let o=Number.parseInt(t,10),i=Number.parseInt(n,10)+1;i>=e.fragments[o].length&&(o+=1,i=0),e.slide(o,{fragment:i})}})};var R,X,H,W,J,Y,z,G={exports:{}},Q=(R||(R=1,G.exports=(X=["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr"],H=function(e){return String(e).replace(/[&<>"']/g,function(e){return"&"+W[e]+";"})},W={"&":"amp","<":"lt",">":"gt",'"':"quot","'":"apos"},J="dangerouslySetInnerHTML",Y={className:"class",htmlFor:"for"},z={},function(e,t){var n=[],r="";t=t||{};for(var o=arguments.length;o-- >2;)n.push(arguments[o]);if("function"==typeof e)return t.children=n.reverse(),e(t);if(e){if(r+="<"+e,t)for(var i in t)!1!==t[i]&&null!=t[i]&&i!==J&&(r+=" "+(Y[i]?Y[i]:H(i))+'="'+H(t[i])+'"');r+=">"}if(-1===X.indexOf(e)){if(t[J])r+=t[J].__html;else for(;n.length;){var a=n.pop();if(a)if(a.pop)for(var s=a.length;s--;)n.push(a[s]);else r+=!0===z[a]?a:H(a)}r+=e?"":""}return z[r]=!0,r})),G.exports),Z=e(Q);const ee=({children:e})=>Z(null,null,...e),te=`${f}presenter-`,ne={container:`${te}container`,dragbar:`${te}dragbar-container`,next:`${te}next`,nextContainer:`${te}next-container`,noteContainer:`${te}note-container`,noteWrapper:`${te}note-wrapper`,noteButtons:`${te}note-buttons`,infoContainer:`${te}info-container`,infoPageArea:`${te}info-page-area`,infoPage:`${te}info-page`,infoPagePrev:`${te}info-page-prev`,infoPageNext:`${te}info-page-next`,noteButtonsBigger:`${te}note-bigger`,noteButtonsSmaller:`${te}note-smaller`,infoTime:`${te}info-time`,infoTimer:`${te}info-timer`},re=e=>{const{title:t}=document;document.title="[Presenter view]"+(t?` - ${t}`:"");const n={},r=e=>(n[e]=n[e]||document.querySelector(`.${e}`),n[e]);document.body.appendChild((e=>{const t=document.createElement("div");return t.className=ne.container,t.appendChild(e),t.insertAdjacentHTML("beforeend",Z(ee,null,Z("div",{class:ne.nextContainer},Z("iframe",{class:ne.next,src:"?view=next"})),Z("div",{class:ne.dragbar}),Z("div",{class:ne.noteContainer},Z("div",{class:ne.noteWrapper}),Z("div",{class:ne.noteButtons},Z("button",{class:ne.noteButtonsSmaller,tabindex:"-1",title:"Smaller notes font size"},"Smaller notes font size"),Z("button",{class:ne.noteButtonsBigger,tabindex:"-1",title:"Bigger notes font size"},"Bigger notes font size"))),Z("div",{class:ne.infoContainer},Z("div",{class:ne.infoPageArea},Z("button",{class:ne.infoPagePrev,tabindex:"-1",title:"Previous"},"Previous"),Z("button",{class:ne.infoPage}),Z("button",{class:ne.infoPageNext,tabindex:"-1",title:"Next"},"Next")),Z("time",{class:ne.infoTime,title:"Current time"}),Z("time",{class:ne.infoTimer,title:"Timer"})))),t})(e.parent)),(e=>{let t=!1;r(ne.dragbar).addEventListener("mousedown",()=>{t=!0,r(ne.dragbar).classList.add("active")}),window.addEventListener("mouseup",()=>{t=!1,r(ne.dragbar).classList.remove("active")}),window.addEventListener("mousemove",e=>{if(!t)return;const n=e.clientX/document.documentElement.clientWidth*100;r(ne.container).style.setProperty("--bespoke-marp-presenter-split-ratio",`${Math.max(0,Math.min(100,n))}%`)}),r(ne.nextContainer).addEventListener("click",()=>e.next());const n=r(ne.next),o=(i=n,(e,t)=>i.contentWindow?.postMessage(`navigate:${e},${t}`,"null"===window.origin?"*":window.origin));var i;n.addEventListener("load",()=>{r(ne.nextContainer).classList.add("active"),o(e.slide(),e.fragmentIndex),e.on("fragment",({index:e,fragmentIndex:t})=>o(e,t))});const a=document.querySelectorAll(".bespoke-marp-note");a.forEach(e=>{e.addEventListener("keydown",e=>e.stopPropagation()),r(ne.noteWrapper).appendChild(e)}),e.on("activate",()=>a.forEach(t=>t.classList.toggle("active",t.dataset.index==e.slide())));let s=0;const l=e=>{s=Math.max(-5,s+e),r(ne.noteContainer).style.setProperty("--bespoke-marp-note-font-scale",(1.2**s).toFixed(4))},c=()=>l(1),d=()=>l(-1),u=r(ne.noteButtonsBigger),f=r(ne.noteButtonsSmaller);u.addEventListener("click",()=>{u.blur(),c()}),f.addEventListener("click",()=>{f.blur(),d()}),document.addEventListener("keydown",e=>{"+"===e.key&&c(),"-"===e.key&&d()},!0);const m=r(ne.infoPage);e.on("activate",({index:t})=>{m.textContent=`${t+1} / ${e.slides.length}`}),m.addEventListener("click",()=>{m.blur(),e.toggleOverviewView()});const g=r(ne.infoPagePrev),p=r(ne.infoPageNext);g.addEventListener("click",t=>{g.blur(),e.prev({fragment:!t.shiftKey})}),p.addEventListener("click",t=>{p.blur(),e.next({fragment:!t.shiftKey})}),e.on("fragment",({index:t,fragments:n,fragmentIndex:r})=>{g.disabled=0===t&&0===r,p.disabled=t===e.slides.length-1&&r===n.length-1});let v=new Date;const h=()=>{const e=new Date,t=e=>`${Math.floor(e)}`.padStart(2,"0"),n=e.getTime()-v.getTime(),o=t(n/1e3%60),i=t(n/1e3/60%60),a=t(n/36e5%24);r(ne.infoTime).textContent=e.toLocaleTimeString(),r(ne.infoTimer).textContent=`${a}:${i}:${o}`};h(),setInterval(h,250),r(ne.infoTimer).addEventListener("click",()=>{v=new Date})})(e)},oe=e=>{N(e),Object.defineProperties(e,{openPresenterView:{enumerable:!0,value:ie},presenterUrl:{enumerable:!0,get:ae}}),w&&document.addEventListener("keydown",t=>{"p"!==t.key||t.altKey||t.ctrlKey||t.metaKey||(t.preventDefault(),e.openPresenterView())})};function ie(){const{max:e,floor:t}=Math,n=e(t(.85*window.innerWidth),640),r=e(t(.85*window.innerHeight),360);return window.open(this.presenterUrl,te+this.syncKey,`width=${n},height=${r},menubar=no,toolbar=no`)}function ae(){const e=new URLSearchParams(location.search);return e.set("view","presenter"),e.set("sync",this.syncKey),g(e)}const se=e=>{const t=p();return t===c&&e.appendChild(document.createElement("span")),{[s]:oe,[l]:re,[c]:U}[t]},le=e=>{e.on("activate",t=>{document.querySelectorAll(".bespoke-progress-parent > .bespoke-progress-bar").forEach(n=>{n.style.flexBasis=100*t.index/(e.slides.length-1)+"%"})})},ce=e=>{const t=Number.parseInt(e,10);return Number.isNaN(t)?null:t},de=(e={})=>{const t={history:!0,...e};return e=>{let n=!0;const r=e=>{const t=n;try{return n=!0,e()}finally{n=t}},o=(t={fragment:!0})=>{let n=t.fragment?ce(v("f")||""):null;((t,n)=>{const{min:r,max:o}=Math,{fragments:i,slides:a}=e,s=o(0,r(t,a.length-1)),l=o(0,r(n||0,i[s].length-1));s===e.slide()&&l===e.fragmentIndex||e.slide(s,{fragment:l})})((()=>{if(location.hash){const[t]=location.hash.slice(1).split(":~:");if(/^\d+$/.test(t))return(ce(t)??1)-1;const r=document.getElementById(t)||document.querySelector(`a[name="${CSS.escape(t)}"]`);if(r){const{length:t}=e.slides;for(let o=0;o=0&&(n=e)}return o}}}return 0})(),n)};e.on("fragment",({index:e,fragmentIndex:r})=>{n||h({f:0===r||r.toString()},{location:{...location,hash:`#${e+1}`},setter:(...e)=>t.history?history.pushState(...e):history.replaceState(...e)})}),setTimeout(()=>{o(),window.addEventListener("hashchange",()=>r(()=>{o({fragment:!1}),h({f:void 0})})),window.addEventListener("popstate",()=>{n||r(()=>o())}),n=!1},0)}},{PI:ue,abs:fe,sqrt:me,atan2:ge}=Math,pe={passive:!0},ve=({slope:e=-.7,swipeThreshold:t=30}={})=>n=>{let r;const o=n.parent,i=e=>{const t=o.getBoundingClientRect();return{x:e.pageX-(t.left+t.right)/2,y:e.pageY-(t.top+t.bottom)/2}};o.addEventListener("touchstart",({touches:e})=>{r=1===e.length?i(e[0]):void 0},pe),o.addEventListener("touchmove",e=>{if(r)if(1===e.touches.length){e.preventDefault();const t=i(e.touches[0]),n=t.x-r.x,o=t.y-r.y;r.delta=me(fe(n)**2+fe(o)**2),r.radian=ge(n,o)}else r=void 0}),o.addEventListener("touchend",o=>{if(r){if(r.delta&&r.delta>=t&&r.radian){const t=(r.radian-e+ue)%(2*ue)-ue;n[t<0?"next":"prev"](),o.stopPropagation()}r=void 0}},pe)},he=new Map;he.clear(),he.set("none",{backward:{both:void 0,incoming:void 0,outgoing:void 0},forward:{both:void 0,incoming:void 0,outgoing:void 0}});const we={both:"",outgoing:"outgoing-",incoming:"incoming-"},ye={forward:"",backward:"-backward"},be=e=>`--marp-bespoke-transition-animation-${e}`,ke=e=>`--marp-transition-${e}`,xe=be("name"),Ee=be("duration"),$e=e=>new Promise(t=>{const n={},r=document.createElement("div"),o=e=>{r.remove(),t(e)};r.addEventListener("animationstart",()=>o(n)),Object.assign(r.style,{animationName:e,animationDuration:"1s",animationFillMode:"both",animationPlayState:"paused",position:"absolute",pointerEvents:"none"}),document.body.appendChild(r);const i=getComputedStyle(r).getPropertyValue(ke("duration"));i&&Number.parseFloat(i)>=0&&(n.defaultDuration=i),((e,t)=>{requestAnimationFrame(()=>{e.style.animationPlayState="running",requestAnimationFrame(()=>t(void 0))})})(r,o)}),Le=async e=>he.has(e)?he.get(e):(e=>{const t={},n=[];for(const[r,o]of Object.entries(we))for(const[i,a]of Object.entries(ye)){const s=`marp-${o}transition${a}-${e}`;n.push($e(s).then(e=>{t[i]=t[i]||{},t[i][r]=e?{...e,name:s}:void 0}))}return Promise.all(n).then(()=>t)})(e).then(t=>(he.set(e,t),t)),Se=e=>Object.values(e).flatMap(Object.values).every(e=>!e),Pe=(e,{type:t,backward:n})=>{const r=e[n?"backward":"forward"],o=(()=>{const e=r[t],n=e=>({[xe]:e.name});if(e)return n(e);if(r.both){const e=n(r.both);return"incoming"===t&&(e[be("direction")]="reverse"),e}})();return!o&&n?Pe(e,{type:t,backward:!1}):o||{[xe]:"__bespoke_marp_transition_no_animation__"}},_e=e=>{if(e)try{const t=JSON.parse(e);if((e=>{if("object"!=typeof e)return!1;const t=e;return"string"==typeof t.name&&(void 0===t.duration||"string"==typeof t.duration)})(t))return t}catch{}},Oe="_tSId",Te="_tA",Ie="bespoke-marp-transition-warming-up",Me=window.matchMedia("(prefers-reduced-motion: reduce)"),Ae="__bespoke_marp_transition_reduced_outgoing__",Ce="__bespoke_marp_transition_reduced_incoming__",De={forward:{both:void 0,incoming:{name:Ce},outgoing:{name:Ae}},backward:{both:void 0,incoming:{name:Ce},outgoing:{name:Ae}}},Ne=e=>{if(!document.startViewTransition)return;const t=t=>(void 0!==t&&(e._tD=t),e._tD);let n;t(!1),((...e)=>{CSS.registerProperty({name:ke("duration"),syntax:"