diff --git a/antenna/DIAGRAM_INTEGRATION_NOTES.md b/antenna/DIAGRAM_INTEGRATION_NOTES.md new file mode 100644 index 0000000..25158c7 --- /dev/null +++ b/antenna/DIAGRAM_INTEGRATION_NOTES.md @@ -0,0 +1,42 @@ +# Antenna Diagram Integration Notes + +Created for `UWS-124` as reusable SVG assets for the Antenna SciPy presentation and A0 poster. + +## Asset Path + +Use the SVGs from `antenna/assets/diagrams/`: + +- `antenna-a-push-model-before.svg` — place with the architecture problem statement for the original push flow. +- `antenna-b-pull-model-after.svg` — place immediately after A to explain the pull-based target architecture and horizontal scaling. +- `antenna-c-gpu-utilization-before.svg` — place before GPU optimization details to make idle time concrete. +- `antenna-d-cpu-gpu-data-flow.svg` — place where the PIL/CPU materialization bottleneck is introduced. +- `antenna-e-cuda-stream-pipeline.svg` — place with CUDA stream/overlap explanation. +- `antenna-f-gpu-utilization-after.svg` — place after E as the visual mirror of C and the payoff slide. + +## Design Notes + +- SVGs use `viewBox="0 0 1600 900"` for 16:9 slides and scalable poster placement. +- Primary diagram color is the requested deep blue `#1A5276`; accents are UW SSEC-compatible gold `#85754d`/`#b7a57a` and teal `#2ad2c9`. +- Typography follows UW SSEC presentation conventions: Encode Sans for headings/UI labels and Open Sans for supporting text, with system fallbacks. +- Each SVG includes `title`, `desc`, and `role="img"` for accessible embedding. + +## Implementation Guidance + +For Reveal.js slides, prefer direct image embedding at full content width: + +```html +Antenna push model before optimization +``` + +Suggested CSS: + +```css +.diagram-full { + display: block; + inline-size: 100%; + max-block-size: 72vh; + object-fit: contain; +} +``` + +No content placeholders remain in the SVGs. The only dependency is deck-owner confirmation that `antenna/assets/diagrams/` is the final path for the Antenna deck. diff --git a/antenna/assets/antenna-screenshot-crop.png b/antenna/assets/antenna-screenshot-crop.png new file mode 100644 index 0000000..b88c386 Binary files /dev/null and b/antenna/assets/antenna-screenshot-crop.png differ diff --git a/antenna/assets/antenna-screenshot.png b/antenna/assets/antenna-screenshot.png new file mode 100644 index 0000000..8e152c4 Binary files /dev/null and b/antenna/assets/antenna-screenshot.png differ diff --git a/antenna/assets/diagrams/after-simple.png b/antenna/assets/diagrams/after-simple.png new file mode 100644 index 0000000..0d505b4 Binary files /dev/null and b/antenna/assets/diagrams/after-simple.png differ diff --git a/antenna/assets/diagrams/antenna-a-push-model-before.eraserdiagram b/antenna/assets/diagrams/antenna-a-push-model-before.eraserdiagram new file mode 100644 index 0000000..5edd140 --- /dev/null +++ b/antenna/assets/diagrams/antenna-a-push-model-before.eraserdiagram @@ -0,0 +1,16 @@ +// A. Push Model Before: Synchronous Endpoint +// Antenna initiates work and blocks while the public processing service runs. + +colorMode pastel +typeface clean + +Antenna [color: blue, label: "Antenna\njob orchestration"] +Public Endpoint [color: orange, label: "Public Endpoint\nProcessing Service"] +Worker Runtime [color: blue, label: "Worker Runtime\nservice-owned"] + +Antenna > Public Endpoint: POST job payload +activate Antenna +Public Endpoint > Worker Runtime: Start processing +Worker Runtime > Public Endpoint: Result returned +Public Endpoint > Antenna: Synchronous response +deactivate Antenna diff --git a/antenna/assets/diagrams/antenna-a-push-model-before.png b/antenna/assets/diagrams/antenna-a-push-model-before.png new file mode 100644 index 0000000..d000776 Binary files /dev/null and b/antenna/assets/diagrams/antenna-a-push-model-before.png differ diff --git a/antenna/assets/diagrams/antenna-a-push-model-before.svg b/antenna/assets/diagrams/antenna-a-push-model-before.svg new file mode 100644 index 0000000..499fd2f --- /dev/null +++ b/antenna/assets/diagrams/antenna-a-push-model-before.svg @@ -0,0 +1,29 @@ + + + + + eraser.io + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/antenna/assets/diagrams/antenna-a-push-model-simple.svg b/antenna/assets/diagrams/antenna-a-push-model-simple.svg new file mode 100644 index 0000000..75f341a --- /dev/null +++ b/antenna/assets/diagrams/antenna-a-push-model-simple.svg @@ -0,0 +1,29 @@ + + + + + eraser.io + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/antenna/assets/diagrams/antenna-b-pull-model-after.eraserdiagram b/antenna/assets/diagrams/antenna-b-pull-model-after.eraserdiagram new file mode 100644 index 0000000..4068bf0 --- /dev/null +++ b/antenna/assets/diagrams/antenna-b-pull-model-after.eraserdiagram @@ -0,0 +1,21 @@ +// B. Pull Model After: Workers Scale Horizontally +// Antenna exposes job state; workers pull batches when they have local capacity. + +direction right +colorMode pastel +typeface clean + +Antenna [color: blue, label: "Antenna\njob source of truth"] + +Jobs API [color: orange, label: "Jobs API\npoll, lease, update"] { + Queue [shape: rectangle, color: orange, label: "batched work queue"] +} + +Worker A [color: blue, label: "Worker A\nlocal processing"] +Worker B [color: blue, label: "Worker B\nlocal processing"] + +Antenna > Jobs API: publish jobs +Worker A > Jobs API: poll + pull batch +Jobs API > Worker A: post results +Worker B > Jobs API: poll + pull batch +Jobs API > Worker B: post results diff --git a/antenna/assets/diagrams/antenna-b-pull-model-after.svg b/antenna/assets/diagrams/antenna-b-pull-model-after.svg new file mode 100644 index 0000000..060bca1 --- /dev/null +++ b/antenna/assets/diagrams/antenna-b-pull-model-after.svg @@ -0,0 +1,29 @@ + + + + + eraser.io + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/antenna/assets/diagrams/antenna-c-gpu-utilization-before.eraserdiagram b/antenna/assets/diagrams/antenna-c-gpu-utilization-before.eraserdiagram new file mode 100644 index 0000000..899a8fa --- /dev/null +++ b/antenna/assets/diagrams/antenna-c-gpu-utilization-before.eraserdiagram @@ -0,0 +1,31 @@ +// C. GPU Utilization Before Optimization +// CPU-side image conversion and transfer setup leave the GPU waiting between kernels. + +direction right +colorMode bold +typeface clean + +CPU [color: blue, label: "CPU"] { + PIL convert [color: blue, label: "PIL convert"] + Host copy 1 [color: blue, label: "host copy"] + Prepare [color: blue, label: "prepare"] + Host copy 2 [color: blue, label: "host copy"] + + PIL convert > Host copy 1 > Prepare > Host copy 2 +} + +GPU [label: "GPU"] { + GPU idle 1 [color: orange, label: "idle"] + GPU kernel 1 [color: "#2ad2c9", label: "kernel"] + GPU idle 2 [color: orange, label: "idle"] + GPU kernel 2 [color: "#2ad2c9", label: "kernel"] + GPU idle 3 [color: orange, label: "idle"] + + GPU idle 1 > GPU kernel 1 > GPU idle 2 > GPU kernel 2 > GPU idle 3 +} + +legend [position: bottom-left] { + [color: blue, label: CPU setup] + [color: "#2ad2c9", label: GPU work] + [color: orange, label: GPU idle] +} diff --git a/antenna/assets/diagrams/antenna-c-gpu-utilization-before.svg b/antenna/assets/diagrams/antenna-c-gpu-utilization-before.svg new file mode 100644 index 0000000..9f6fb30 --- /dev/null +++ b/antenna/assets/diagrams/antenna-c-gpu-utilization-before.svg @@ -0,0 +1,39 @@ + + GPU utilization before optimization + CPU and GPU timeline showing large GPU idle gaps before optimization. + + + + + + C. GPU Utilization Before Optimization + CPU-side image conversion and transfer setup leave the GPU waiting between kernels. + + + CPU + GPU + + + + + 020406080100 ms + + + PIL converthost copypreparehost copy + + + idlekernelidlekernelidle + + + + + Low duty cycle, high wait time + + + CPU setup + GPU work + GPU idle + + diff --git a/antenna/assets/diagrams/antenna-d-cpu-gpu-data-flow.eraserdiagram b/antenna/assets/diagrams/antenna-d-cpu-gpu-data-flow.eraserdiagram new file mode 100644 index 0000000..76fcb84 --- /dev/null +++ b/antenna/assets/diagrams/antenna-d-cpu-gpu-data-flow.eraserdiagram @@ -0,0 +1,22 @@ +// D. CPU-GPU Data Flow +// The optimized path removes CPU image materialization from the hot loop. + +direction right +colorMode pastel +typeface clean + +Before [color: orange, label: "Before: device data leaves the GPU"] { + GPU array before [color: "#2ad2c9", label: "GPU array"] + PIL Image [color: orange, label: "PIL Image\n(CPU memory)"] + GPU kernel before [color: "#2ad2c9", label: "GPU\nkernel input"] + + GPU array before > PIL Image: extra copies + PIL Image > GPU kernel before: synchronization +} + +After [color: blue, label: "After: direct GPU-to-GPU path"] { + GPU source [color: "#2ad2c9", label: "GPU\nsource array"] + GPU kernel after [color: "#2ad2c9", label: "GPU\nkernel input"] + + GPU source > GPU kernel after: no CPU materialization +} diff --git a/antenna/assets/diagrams/antenna-d-cpu-gpu-data-flow.svg b/antenna/assets/diagrams/antenna-d-cpu-gpu-data-flow.svg new file mode 100644 index 0000000..eff15f9 --- /dev/null +++ b/antenna/assets/diagrams/antenna-d-cpu-gpu-data-flow.svg @@ -0,0 +1,56 @@ + + CPU-GPU data flow before and after optimization + Before path routes through PIL Image on CPU before returning to GPU; after path keeps data on GPU directly. + + + + + + + + + + + + + + + + + Before: device data leaves the GPU + + GPU + array + + PIL Image + CPU memory + + GPU + kernel input + + + + extra copies + synchronization + + + + + After: direct GPU-to-GPU path + + GPU + source array + + GPU + kernel input + + + no CPU materialization + + + + diff --git a/antenna/assets/diagrams/antenna-e-cuda-stream-pipeline.eraserdiagram b/antenna/assets/diagrams/antenna-e-cuda-stream-pipeline.eraserdiagram new file mode 100644 index 0000000..ee31415 --- /dev/null +++ b/antenna/assets/diagrams/antenna-e-cuda-stream-pipeline.eraserdiagram @@ -0,0 +1,40 @@ +// E. CUDA Stream Pipeline +// Streams overlap transfer and compute so later batches begin before earlier batches finish. + +direction right +colorMode bold +typeface clean + +Before [label: "Before: sequential work"] { + Single stream [label: "Single stream"] { + T1 before [color: blue, label: "T1\nTransfer"] + C1 before [color: "#2ad2c9", label: "C1\nCompute"] + T2 before [color: blue, label: "T2\nTransfer"] + C2 before [color: "#2ad2c9", label: "C2\nCompute"] + Wait [color: orange, label: "wait"] + + T1 before > C1 before > T2 before > C2 before > Wait + } +} + +After [label: "After: overlapped streams"] { + Stream 1 [label: "Stream 1"] { + S1 T1 [color: blue, label: "T1"] + S1 C1 [color: "#2ad2c9", label: "C1"] + S1 T3 [color: blue, label: "T3"] + + S1 T1 > S1 C1 > S1 T3 + } + + Stream 2 [label: "Stream 2"] { + S2 T2 [color: blue, label: "T2"] + S2 C2 [color: "#2ad2c9", label: "C2"] + + S2 T2 > S2 C2 + } +} + +legend [position: bottom-left] { + [color: blue, label: Transfer] + [color: "#2ad2c9", label: Compute] +} diff --git a/antenna/assets/diagrams/antenna-e-cuda-stream-pipeline.svg b/antenna/assets/diagrams/antenna-e-cuda-stream-pipeline.svg new file mode 100644 index 0000000..a56bbf0 --- /dev/null +++ b/antenna/assets/diagrams/antenna-e-cuda-stream-pipeline.svg @@ -0,0 +1,58 @@ + + CUDA stream pipeline before and after optimization + Comparison of sequential transfer and compute against overlapped transfer and compute across CUDA streams. + + + + + + E. CUDA Stream Pipeline + Streams overlap transfer and compute so later batches begin before earlier batches finish. + + + + Before: sequential work + Single stream + + + T1 + C1 + T2 + C2 + wait + + + + GPU occupancy + + idlecomputeidlecompute + + + + After: overlapped streams + Stream 1 + Stream 2 + + + + T1 + C1 + T2 + C2 + T3 + + + + GPU occupancy + + + near-continuous compute window + + + + Transfer + Compute + + diff --git a/antenna/assets/diagrams/antenna-f-gpu-utilization-after.eraserdiagram b/antenna/assets/diagrams/antenna-f-gpu-utilization-after.eraserdiagram new file mode 100644 index 0000000..d02c116 --- /dev/null +++ b/antenna/assets/diagrams/antenna-f-gpu-utilization-after.eraserdiagram @@ -0,0 +1,33 @@ +// F. GPU Utilization After Optimization +// Direct GPU data flow and stream overlap keep compute work queued with only small gaps. + +direction right +colorMode bold +typeface clean + +CPU [color: blue, label: "CPU"] { + Enqueue 1 [color: blue, label: "enqueue"] + Enqueue 2 [color: blue, label: "enqueue"] + Enqueue 3 [color: blue, label: "enqueue"] + Enqueue 4 [color: blue, label: "enqueue"] + + Enqueue 1 > Enqueue 2 > Enqueue 3 > Enqueue 4 +} + +GPU [label: "GPU"] { + Kernel batch 1 [color: "#2ad2c9", label: "kernel batch 1"] + Handoff 1 [color: blue, label: "gap"] + Kernel batch 2 [color: "#2ad2c9", label: "kernel batch 2"] + Handoff 2 [color: blue, label: "gap"] + Kernel batch 3 [color: "#2ad2c9", label: "kernel batch 3"] + Handoff 3 [color: blue, label: "gap"] + Batch 4 [color: "#2ad2c9", label: "batch 4"] + + Kernel batch 1 > Handoff 1 > Kernel batch 2 > Handoff 2 > Kernel batch 3 > Handoff 3 > Batch 4 +} + +legend [position: bottom-left] { + [color: blue, label: "CPU enqueue"] + [color: "#2ad2c9", label: "GPU work"] + [color: blue, label: "handoff gap"] +} diff --git a/antenna/assets/diagrams/antenna-f-gpu-utilization-after.svg b/antenna/assets/diagrams/antenna-f-gpu-utilization-after.svg new file mode 100644 index 0000000..cea66f0 --- /dev/null +++ b/antenna/assets/diagrams/antenna-f-gpu-utilization-after.svg @@ -0,0 +1,39 @@ + + GPU utilization after optimization + CPU and GPU timeline showing near-continuous GPU utilization after optimization. + + + + + + F. GPU Utilization After Optimization + Direct GPU data flow and stream overlap keep compute work queued with only small gaps. + + + CPU + GPU + + + + + 020406080100 ms + + + enqueueenqueueenqueueenqueue + + + kernel batch 1kernel batch 2kernel batch 3batch 4 + + + + + High duty cycle, small gaps + + + CPU enqueue + GPU work + handoff gap + + diff --git a/antenna/assets/diagrams/antenna-orchestration.html b/antenna/assets/diagrams/antenna-orchestration.html new file mode 100644 index 0000000..0d88535 --- /dev/null +++ b/antenna/assets/diagrams/antenna-orchestration.html @@ -0,0 +1,126 @@ + + + + + +Antenna — model orchestration + + + +
+ + + + + + + + + + + + + + + + + + Antenna + + + + + + + + + + + + + + + + + + + + + + + University A + hosted model · REST API + + + + Institute B + hosted model · REST API + + + + University C + hosted model · REST API + + + + +
+ + diff --git a/antenna/assets/diagrams/before-simple.png b/antenna/assets/diagrams/before-simple.png new file mode 100644 index 0000000..106fabd Binary files /dev/null and b/antenna/assets/diagrams/before-simple.png differ diff --git a/antenna/assets/diagrams/dataloader-diagram.html b/antenna/assets/diagrams/dataloader-diagram.html new file mode 100644 index 0000000..ff5df66 --- /dev/null +++ b/antenna/assets/diagrams/dataloader-diagram.html @@ -0,0 +1,167 @@ + + + + + +PyTorch DataLoader — inference pipeline + + + +
+ + + + + PyTorch DataLoader inference pipeline + Diagram showing multiple CPU worker processes loading and preprocessing data in parallel, building a prefetch queue of pinned-memory batches that the GPU consumes one at a time for inference. + + + + + + + + + + Dataset + + + + + + + images + + + + + CPU workers (num_workers=N) + + + + Worker 1 + load · decode · transform + + + + + Worker 2 + load · decode · transform + + + + + + + + + Prefetch queue (prefetch_factor) + + + + Batch N + tensor · pinned memory + + + Batch N+1 + tensor · pinned memory + + + main thread blocks only + if queue is empty + + + + + + + + + + + GPU + model forward pass + inference + + + processing batch N + + VRAM + + + + + + + + + + + + + + CPU workers + + GPU / queue + + dataset + +
+ + diff --git a/antenna/assets/intro.webp b/antenna/assets/intro.webp new file mode 100644 index 0000000..18e86d7 Binary files /dev/null and b/antenna/assets/intro.webp differ diff --git a/antenna/assets/timeline-after.html b/antenna/assets/timeline-after.html new file mode 100644 index 0000000..05a1fd3 --- /dev/null +++ b/antenna/assets/timeline-after.html @@ -0,0 +1,137 @@ + + + + + +After — GPU pipeline timeline + + + + + After — pipelined, GPU always busy + Four horizontal timelines showing dataloader workers, main thread, async , and GPU after the optimization. GPU runs continuously with no idle time. + + + + After — pipelined, GPU always busy + + + Dataloader workers + async + Main thread (CPU) + GPU + + + + fetch + collate + + + fetch + collate + + + fetch + collate + + + fetch + collate + + ··· + + + + + to(cuda) + + + + to(cuda) + + + + post + + + + to(cuda) + + + + post + + + + post + + + + model() + + + model() + + + model() + + ··· + + + + detect + + + classify + + + detect + + + classify + + + detect + + ··· + + + + + + GPU busy continuously — no idle + + + + + time → + + + + dataloader workers + + + main thread + + + to(cuda) + + + post + + + GPU detect + + + GPU classify + + + + diff --git a/antenna/assets/timeline-before.html b/antenna/assets/timeline-before.html new file mode 100644 index 0000000..cef9944 --- /dev/null +++ b/antenna/assets/timeline-before.html @@ -0,0 +1,111 @@ + + + + + + + + + + + + Three horizontal timelines showing dataloader workers, main thread, and GPU before the optimization. GPU and main thread have large red idle segments. + + + + + + + + + + + + + Dataloader workers + Main thread (CPU) + GPU + + + + fetch + download B1 + + + idle — not yet asked for B2 + + + + idle — waiting + + + collate + + + .to('cuda') + + + idle + + + PIL round-trip ⚠ + + + idle + + + post + + + idle + + + + idle — waiting for CPU + + + detect B1 + + + idle — PIL copy + + + classify B1 + + + idle — waiting for B2 + + + + + time → + + + + idle / wasted + + + dataloader workers + + + main thread + + + GPU active + + + PIL round-trip + + + + diff --git a/antenna/index.html b/antenna/index.html new file mode 100644 index 0000000..bc216b0 --- /dev/null +++ b/antenna/index.html @@ -0,0 +1,537 @@ + + + + + + Efficient Federated Inference on Entomology Images with PyTorch + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+

SciPy 2026

+

Efficient Federated Inference on Entomology Images with PyTorch

+

+ Carlos Garcia Jurado Suarez
+ UW eScience Institute · Mila Institute +

+ +
+ + +
+

What is Antenna?

+
+

Mila Institute's open-source platform — classifying insects from automated wildlife camera-trap images to support biodiversity monitoring at scale.

+
+
+ Antenna web platform showing project navigation, image review, and classification workflow panels +
+ Antenna platform interface +
+
+
+ + +
+

The Scaling Problem

+
+ +
+
+

+   Integration barrier +

+

+ Public HTTP endpoint required, a blocker for HPC clusters and university firewalls. +

+
+
+

+   Throughput ceiling +

+

+ Synchronous design prevented scaling to large jobs: >300 image jobs would stall. +

+
+
+ +
+
+ + + + + +
+

Architecture: Original Push Model

+
+ Diagram A — Push model: Antenna pushes to a public Processing Service endpoint and waits on a synchronous blocking response + Diagram A — Push model: Antenna pushes to a public Processing Service endpoint and waits on a synchronous blocking response +
+
+ + +
+

Architecture: Redesigned Pull Model

+
+

Inverting the direction: PS workers poll Antenna's job queue — no public endpoint needed, and each additional worker adds capacity automatically.

+ Diagram B — Pull model: PS workers poll the Antenna job queue, pull batches, and post results back — no public endpoint required +
+
+ + + + + +
+

Architecture: Results

+
+
+ +
+

+   Deploy anywhere +

+

Workers run anywhere with outbound HTTP: HPC clusters, firewalled networks, local machines.

+
+
+

+   Multi-institution +

+

Workers can span institutions and different hardware, no uniformity needed.

+
+
+

+   Horizontal scaling +

+

Add workers without changing Antenna; each independently polls and claims batches.

+
+ +
+
+

+ Inverting push/pull → workers control the speed. +

+
+
+
+ + + + + +
+

Optimization: Data Loading

+
+ + + + +
+
+ + +
+

Optimization: Baseline

+
+
+
1,674s
+
for 1,000 images · RTX A6000 · GPU idle from CPU transfers, sync posting, and suboptimal data loading
+
+ +
+
+
+

Optimization: Fixes

+
+ +
+
+
class RESTDataset(torch.utils.data.IterableDataset):
+# pulls rows from /jobs/{job_id} until exhausted
+# downloads images from S3
+# assembles batches of stacked tensors
+ + + +
return torch.utils.data.DataLoader(
+  dataset,
+  batch_size=1, # batches built in the dataset workers, not by DataLoader
+  pin_memory=True, # DMA transfer to GPU
+  collate_fn=_no_op_collate_fn # nothing to collate since each dataset item is already a batch
+  ...)
+
+
torch.cuda.Stream()
+  ...
+  with torch.cuda.stream(self.stream):
+  self.next_batch = v.to(self.device, non_blocking=True)                    
+
+
torchvision.transforms.ToPILImage()
+
+
ThreadPoolExecutor.submit(...)
+
+
+
+ + + + + + + + + + + + + + + +
+

Optimization: Results

+
+
+
15×
+
+
faster on a single GPU · RTX A6000
+
+ 1,674s + + 108s + per 1,000 images · 42K-image job in 77 min +
+
+
+ + +
+
+ + + + + + + + + + +
+
+ + + + + + + + diff --git a/antenna/scipy_presentation_outline.md b/antenna/scipy_presentation_outline.md new file mode 100644 index 0000000..f2345cc --- /dev/null +++ b/antenna/scipy_presentation_outline.md @@ -0,0 +1,228 @@ +# Presentation Outline + +**Efficient Federated Inference on Entomology Images with PyTorch** + +SciPy 2026 · 30-minute talk · Author: Carlos Garcia Jurado Suarez + +--- + +## Talk Overview + +| Time | Section | +| ------- | ---------------------------------------------------------------- | +| ~3 min | Introduction — Antenna platform, problem statement, talk roadmap | +| ~11 min | Part 1 — Async Pull Architecture (Processing Service v2) | +| ~11 min | Part 2 — PyTorch GPU Inference Optimization | +| ~5 min | Results, takeaways, Q&A | + +--- + +## Section 1 — Introduction + +Goal: orient a generalist Python/data science audience to the domain and motivate both engineering problems. Keep it tight — 3 minutes. + +### Slide 1: Title slide `[30 sec]` + +- Talk title, speaker name, affiliation (UW eScience Institute / Mila Institute) +- Conference name and date + +> 📝 Clean title slide. No heavy graphics needed. Placeholder for: small Antenna logo or a single camera-trap insect photo as background. +> Website for context/logo/images: https://www.insectai.org/ + +--- + +### Slide 2: What is Antenna? `[1.5 min]` + +- Open-source platform from the Mila Institute +- Processes images from automated wildlife camera traps in the field +- Classifies insect species using deep learning — supporting biodiversity monitoring at scale +- Used by multiple research institutions across different deployment environments + +> 📝 **PLACEHOLDER:** Screenshot of the Antenna web interface (image catalog view or monitoring dashboard). One clear screenshot is enough. Caption: "Antenna web platform — researchers submit image batches and review classification results." +> +> Keep the explanation brief. One sentence on biodiversity monitoring is sufficient — this is context, not the talk topic. + +--- + +### Slide 3: The scaling problem `[1 min]` + +- As adoption grew: jobs went from hundreds to millions of images +- Two interconnected problems emerged: + - Third-party researchers couldn't easily integrate their own ML pipelines + - The platform couldn't scale processing to large jobs or multiple models +- This talk: two engineering changes that solve both + +> 📝 This slide sets up the two-part structure. Use a simple two-column or two-row layout with the two problems side by side, visually foreshadowing the two parts. + +--- + +## Section 2 — Part 1: Async Pull Architecture + +Goal: explain what was broken in the original design and how inverting the communication model fixed it. Conceptual, no deep code. + +### Slide 4: How the original system worked `[2 min]` + +- Antenna pushed image batches to external ML processing services (PS) over HTTP +- Each PS had to expose a public HTTP endpoint — hard to deploy in university HPC environments, behind firewalls, etc. +- Processing was synchronous: Antenna waited for each response before moving on +- Scalability was entirely the PS implementor's problem + +> 📝 **DIAGRAM A — Push model (before).** Mermaid sequence diagram. Flow: Antenna → HTTP push → Processing Service → synchronous response → Antenna. Show that PS requires a public endpoint. Highlight two pain points: (1) arrow from internet to PS labeled "requires public endpoint", (2) blocking return arrow labeled "synchronous — waits for response." + +--- + +### Slide 5: The redesign — pull model `[3 min]` + +- Invert the direction: PS workers poll Antenna's job queue and pull task batches on their own schedule +- PS no longer needs a public endpoint — it initiates all outbound connections +- Workers can run behind a firewall, on a university HPC cluster, or on a local machine +- Multiple workers can poll the same queue independently — horizontal scaling falls out naturally +- Different ML pipelines register with Antenna and pull only jobs matching their pipeline + +> 📝 **DIAGRAM B — Pull model (after).** Mermaid sequence diagram. Flow: PS Worker polls Antenna jobs API → Antenna returns task batch → PS processes locally → PS posts results back. Show two worker instances to illustrate horizontal scale. Key contrast: PS worker is now the initiator, not the receiver. + +--- + +### Slide 6: Worker implementation — `ami worker` `[2 min]` + +- Ships as `ami worker` CLI command in the `ami-data-companion` package +- Configuration via environment variables or `.env` file (API URL, auth token, batch size) +- Worker loop: poll `/jobs` endpoint → fetch task batch → run ML pipeline → POST results back +- Supports multiple pipelines via; any number of workers can run in parallel + +> 📝 **OPTIONAL CODE SNIPPET:** Show the 3-line usage pattern (`export` env vars, `ami worker register`, `ami worker`). Illustrative only — keep font large. +> +> No deep architecture diagram needed here. The sequence diagram from Slide 5 carries the conceptual weight. This slide makes it concrete for practitioners. + +--- + +### Slide 7: Part 1 results `[2 min]` + +- PS workers can now run anywhere with outbound HTTP — no public endpoint required +- Horizontal scaling: add more workers without changing Antenna +- Extensibility: new ML pipelines register and pull independently, researchers can build their own workers +- Workers can run across multiple research institutions running different hardware configurations + +> 📝 If a before/after deployment diagram is available (e.g., showing a university HPC setup that was previously impossible), this is a good place for it. Otherwise keep as a clean bullet summary. + +--- + +## Section 3 — Part 2: PyTorch GPU Inference Optimization + +Goal: show the performance problem, walk through the four concrete fixes, and land the 15x number. Balanced between conceptual explanation and practical takeaways. + +### Slide 8: The performance baseline `[2 min]` + +- Original sync-mode processing service: **1,674 seconds** for 1,000 images on an RTX A6000 +- GPU utilization was poor — significant idle gaps visible in profiling +- Root causes: CPU-GPU transfer bottlenecks, synchronous result posting, suboptimal data loading + +> 📝 **DIAGRAM C — GPU utilization before optimization.** Horizontal timeline (Gantt style). Two rows: GPU and CPU. Show large idle gaps on the GPU row. If a real `nvidia-smi` or PyTorch profiler screenshot is available from PR #122, use that as reference or include directly. +> +> The 1,674 second number is the anchor. Make it large and typographically prominent on this slide. + +--- + +### Slide 9: Fix 1 — Keep data on the GPU between stages `[2 min]` + +- Detection and classification are two sequential model passes +- Original code converted intermediate tensors back to PIL images between passes +- This forced a GPU → CPU → GPU round-trip transfer for every image in the batch +- Fix: keep tensors on GPU and pass them directly from detector to classifier + +> 📝 **DIAGRAM D — CPU-GPU data flow before/after.** Two rows. Before: `GPU → PIL Image (CPU) → GPU`. After: `GPU → GPU` (direct). Use color to highlight the unnecessary CPU hop in the before row. + +--- + +### Slide 10: Fix 2 — Pinned memory and batch collation `[2 min]` + +- `pin_memory=True` in the DataLoader enables DMA transfers — CPU memory is page-locked, avoiding an extra copy on the way to the GPU +- Batch collation (stacking images into a tensor) was happening in the main process +- For large images this stacking is slow and blocks the GPU pipeline +- Fix: move collation into DataLoader workers so it runs in parallel with GPU computation + +> 📝 No diagram needed — bullet points are sufficient. Optional: one-line code snippet showing `pin_memory=True` in the DataLoader constructor. Keep font large. + +--- + +### Slide 11: Fix 3 — CUDA streams for prefetching `[2 min]` + +- GPU was idle waiting for the next batch to transfer from CPU +- CUDA streams allow concurrent execution: stream A processes batch N while stream B prefetches batch N+1 into GPU memory +- Result: near-continuous GPU utilization instead of alternating compute/transfer cycles + +> 📝 **DIAGRAM E — CUDA stream pipeline before/after.** Two timelines. Before: Transfer → Compute → Transfer → Compute (sequential, with gaps). After: overlapping Transfer and Compute using two streams. A Gantt-style diagram works well. + +--- + +### Slide 12: Fix 4 — Async result posting `[1.5 min]` + +- After each batch, results were posted to Antenna synchronously — GPU sat idle during the HTTP request +- Fix: `ResultPoster` runs in a background thread +- Worker loads and processes batch N+1 while batch N results are being posted + +> 📝 This fix is the same principle as CUDA streams applied at the API layer — overlap I/O with compute. No separate diagram needed if Diagram E already illustrates the concept. + +--- + +### Slide 13: Combined results `[2 min]` + +- After all four optimizations: **108 seconds** for 1,000 images — a **15x improvement** on a single GPU +- 42,000-image job completed in 77 minutes with no interruptions +- Async architecture adds a further multiplier: multiple workers scale linearly +- Key insight: The system has multiple resources: GPU, CPU, Network and GPU/CPU bus - keep them all busy + +> 📝 **DIAGRAM F — GPU utilization after optimization.** Mirror of Diagram C. Near-continuous GPU utilization. Use a real profiler screenshot from PR #122 if available. +> +> **OPTIONAL:** Side-by-side bar chart showing 1,674s vs. 108s. +> +> This is the climax slide of the talk. Give the **15x** number maximum visual weight — consider large-text treatment with the two raw numbers below it. + +--- + +## Section 4 — Conclusion & Takeaways + +### Slide 14: Summary and generalization `[2 min]` + +- Part 1 takeaway: inverting push/pull makes distributed ML inference dramatically easier to deploy and scale +- Part 2 takeaway: four concrete PyTorch optimizations — each addressing a different bottleneck — compound to a 15x gain +- Both contributions generalize: applicable to any image classification workload, not just entomology +- Antenna is open source — links on next slide + +> 📝 Optional: a single architectural overview diagram showing the full system (Antenna + multiple async workers) as a clean visual summary. + +--- + +### Slide 15: Links and Q&A `[3 min]` + +- Antenna platform: [insectai.org](https://www.insectai.org/) +- Worker + optimizations: [github.com/RolnickLab/ami-data-companion](https://github.com/RolnickLab/ami-data-companion) +- Antenna backend (async job queue API): [github.com/RolnickLab/antenna](https://github.com/RolnickLab/antenna) + +> 📝 Keep this slide simple. White background, links, optional QR codes. Leave space for Q&A discussion. + +--- + +## Diagram Summary + +All six diagrams should be created as SVG for reuse across slides and poster. + +| # | Name | Type | Key content | +| --- | ------------------------ | ------------------------ | -------------------------------------------------------------------------------------- | +| A | Push model — before | Mermaid sequence diagram | Antenna pushes to PS; PS requires public endpoint; synchronous blocking response | +| B | Pull model — after | Mermaid sequence diagram | PS worker polls job queue; pulls batches; posts results; two workers shown for scale | +| C | GPU utilization — before | Gantt/timeline | GPU and CPU rows; large idle gaps on GPU; reference nvidia-smi screenshot if available | +| D | CPU-GPU data flow | Horizontal flow diagram | Before: GPU→CPU→GPU; After: GPU→GPU direct; color-code the unnecessary CPU hop | +| E | CUDA stream pipeline | Gantt/timeline | Before: sequential transfer/compute; After: overlapping streams | +| F | GPU utilization — after | Gantt/timeline | Mirror of Diagram C; near-continuous GPU utilization | + +--- + +## Design Notes + +- **Style:** Clean and minimal. White or off-white background. One or two accent colors (suggestion: deep blue `#1A5276` with a light complementary tone). No decorative elements. +- **Font:** Single sans-serif family throughout (e.g. Inter, Source Sans Pro). Body text no smaller than 24pt on slides. +- **Diagrams:** All as SVG. Consistent line weight and color palette across all six. Must also render legibly on an A0 conference poster. +- **Code snippets:** Monospace font with syntax highlighting. Limit to 5–8 lines per slide. Only include where they add concrete value. +- **Key numbers:** 1,674s, 108s, 15x, 42K images, 77 min — typographically prominent wherever they appear. Consider large-text treatment on results slides. +- **Placeholders:** Slides 2 and 13 require screenshots from the Antenna platform. These will be provided separately. Leave clearly labeled placeholder rectangles in the layout. diff --git a/antenna/styles.css b/antenna/styles.css new file mode 100644 index 0000000..10e5ad7 --- /dev/null +++ b/antenna/styles.css @@ -0,0 +1,1033 @@ +:root { + /* SSEC global tokens from uw-ssec.github.io/design-system */ + --color-purple-950: #1a0038; + --color-purple-900: #32006e; + --color-purple-800: #3d1a7a; + --color-purple-700: #4b2e83; + --color-purple-600: #5a3d94; + --color-purple-200: #c5b4e3; + --color-purple-100: #e0d7f0; + --color-purple-50: #f0ecf7; + + --color-gold-700: #85754d; + --color-gold-500: #b7a57a; + --color-gold-200: #e8e3d3; + --color-gold-100: #f2efe6; + --color-gold-50: #f9f7f2; + + --color-teal-600: #1fb8b0; + --color-teal-500: #2ad2c9; + --color-teal-300: #8fe9e4; + --color-green-600: #2e7d32; + --color-green-100: #e8f5e9; + --color-pink-600: #c6287c; + --color-pink-100: #fde8f4; + + --color-neutral-950: #0d0f10; + --color-neutral-900: #1a1d1f; + --color-neutral-700: #373a3c; + --color-neutral-600: #555a5e; + --color-neutral-500: #72777c; + --color-neutral-300: #c2c6ca; + --color-neutral-200: #d5d8de; + --color-neutral-100: #eceef0; + --color-neutral-50: #f6f7f8; + --color-white: #ffffff; + + --heading-font: "Encode Sans", Helvetica, sans-serif; + --ui-font: "Encode Sans Compressed", "Encode Sans", Helvetica, sans-serif; + --body-font: "Open Sans", Helvetica, sans-serif; + --mono-font: "JetBrains Mono", "Fira Code", "Source Code Pro", Consolas, monospace; + + --base-font-size: 32px; + --text-size: 15pt; + --h1-size: 46pt; + --h2-size: 29pt; + --h3-size: 20pt; + --footnote-size: 10pt; + + --slide-padding: 56px; + --slide-padding-top: 54px; + --content-gap: 24px; + --radius-md: 8px; + --angle-brand: 15deg; + + /* Backward-compatible aliases used by inline slide content. */ + --background-color: var(--color-gold-50); + --section-divider-bg: var(--color-purple-950); + --primary-color: var(--color-purple-700); + --secondary-color: var(--color-gold-700); + --accent-blue: var(--color-teal-600); + --text-color: var(--color-neutral-900); + --muted-color: var(--color-neutral-600); + --line-color: var(--color-neutral-200); + --card-bg: var(--color-white); + --card-border: var(--color-neutral-200); +} + +.reveal { + font-family: var(--body-font); + font-size: var(--base-font-size); + color: var(--text-color); +} + +.reveal-viewport { + background: var(--color-purple-950); +} + +.reveal strong, +.reveal b { + font-weight: 700; +} + +.reveal h1, +.reveal h2, +.reveal h3, +.reveal h4, +.reveal h5, +.reveal h6 { + color: var(--text-color); + font-family: var(--heading-font); + font-weight: 700; + letter-spacing: 0; + line-height: 1.15; + text-transform: none; +} + +.reveal h1 { + font-size: var(--h1-size); + font-weight: 800; +} + +.reveal h2 { + font-size: var(--h2-size); + margin: 0; +} + +.reveal h3 { + font-size: var(--h3-size); +} + +.reveal p, +.reveal li, +.reveal td, +.reveal th, +.reveal blockquote { + color: var(--text-color); + font-size: var(--text-size); + line-height: 1.52; +} + +.reveal ul { + list-style: none; + margin: 12px 0; + padding-left: 0; +} + +.reveal ul li { + margin-bottom: 8px; + padding-left: 22px; + position: relative; +} + +.reveal ul li::before { + background: var(--color-purple-700); + content: ""; + height: 7px; + left: 0; + position: absolute; + top: 0.68em; + transform: skewX(-15deg); + width: 10px; +} + +.reveal ul ul li::before { + background: var(--color-gold-700); + height: 5px; + width: 7px; +} + +.reveal .slides section { + background: + linear-gradient(90deg, rgba(75, 46, 131, 0.06), transparent 34%), + var(--color-white); + box-sizing: border-box; + display: flex !important; + flex-direction: column !important; + height: 100%; + overflow: hidden; + padding: var(--slide-padding-top) var(--slide-padding) var(--slide-padding) var(--slide-padding) !important; + text-align: left; +} + +.reveal .slides section:nth-child(4n + 2):not(.section-divider):not(.stack), +.reveal .slides section.warm-slide { + background: + linear-gradient(105deg, rgba(183, 165, 122, 0.18), transparent 36%), + var(--color-gold-50); +} + +.reveal .slides section.stack { + background: transparent; + padding: 0 !important; +} + +.reveal .slides section:not(.stack)::before { + color: var(--color-gold-700); + content: "UW SSEC"; + font-family: var(--ui-font); + font-size: 10pt; + font-weight: 700; + left: var(--slide-padding); + letter-spacing: 0.12em; + line-height: 1; + position: absolute; + text-transform: uppercase; + top: 24px; + z-index: 2; +} + +.reveal .slides section:not(.section-divider):not(.stack)::after { + background: repeating-linear-gradient( + 105deg, + transparent, + transparent 9px, + rgba(75, 46, 131, 0.07) 9px, + rgba(75, 46, 131, 0.07) 10px + ); + bottom: -120px; + content: ""; + height: 220px; + pointer-events: none; + position: absolute; + right: -80px; + transform: rotate(var(--angle-brand)); + width: 300px; +} + +.reveal .slides section > .content { + display: flex; + flex: 1; + flex-direction: column; + justify-content: flex-start; + min-height: 0; + overflow: hidden; + padding-top: var(--content-gap); + text-align: left; + z-index: 1; +} + +.reveal .slides section > .footnote { + bottom: 14px; + color: var(--muted-color); + font-size: var(--footnote-size); + left: var(--slide-padding); + position: absolute; + right: var(--slide-padding); + z-index: 2; +} + +.reveal .slides section > h2 { + border-bottom: 3px solid var(--color-gold-500); + color: var(--color-purple-700); + flex-shrink: 0; + padding-bottom: 12px; + position: relative; + z-index: 1; +} + +.reveal .slides section > h2::before { + background: var(--color-purple-700); + content: ""; + display: inline-block; + height: 24px; + margin-right: 12px; + transform: skewX(-15deg); + vertical-align: -3px; + width: 9px; +} + +.reveal .slides section.section-divider { + align-items: flex-start !important; + background: + repeating-linear-gradient( + 105deg, + transparent, + transparent 10px, + rgba(255, 255, 255, 0.035) 10px, + rgba(255, 255, 255, 0.035) 11px + ), + radial-gradient(circle at 82% 26%, rgba(42, 210, 201, 0.16), transparent 25%), + linear-gradient(135deg, var(--color-purple-950), var(--color-purple-900)); + color: var(--color-white); + justify-content: center !important; + text-align: left; +} + +.reveal .slides section.section-divider::before { + color: var(--color-gold-500); +} + +.reveal .slides section.section-divider::after { + background: var(--color-gold-500); + bottom: 0; + clip-path: polygon(0 35%, 100% 0, 100% 100%, 0 100%); + content: ""; + height: 72px; + left: 0; + position: absolute; + right: 0; +} + +.reveal .slides section.section-divider h1 { + color: var(--color-white); + font-size: 52pt; + letter-spacing: 0; + max-width: 940px; + text-align: left; + z-index: 1; +} + +.reveal .slides section.section-divider h1::after { + background: var(--color-teal-500); + content: ""; + display: block; + height: 4px; + margin: 16px 0 0; + transform: skewX(-15deg); + width: 112px; +} + +.reveal .slides section.section-divider p { + color: var(--color-gold-200); + font-size: 20pt; + z-index: 1; +} + +.reveal .slides section.section-divider .subtitle { + color: var(--color-gold-200); + font-size: 18pt; + margin-top: 16px; + max-width: 760px; +} + +.reveal .slides section.section-divider .event-tag { + color: var(--color-teal-300); + font-family: var(--ui-font); + font-size: 12pt; + font-weight: 700; + letter-spacing: 0.12em; + margin-bottom: 20px; + text-transform: uppercase; +} + +.text-lg { font-size: 18pt !important; } +.text-xl { font-size: 20pt !important; } +.text-2xl { font-size: 24pt !important; } +.text-3xl { font-size: 28pt !important; } +.text-4xl { font-size: 32pt !important; } +.text-muted { color: var(--muted-color) !important; } +.text-red { color: var(--color-purple-700) !important; } +.text-green { color: var(--color-gold-700) !important; } +.text-blue { color: var(--color-teal-600) !important; } +.text-center { text-align: center !important; } +.text-uppercase { + font-family: var(--ui-font); + letter-spacing: 0.1em; + text-transform: uppercase; +} +.font-mono { font-family: var(--mono-font) !important; } + +.reveal blockquote { + background: var(--color-gold-50); + border: 0; + box-shadow: inset 7px 0 0 var(--color-gold-500); + margin: 16px 0; + max-width: none; + padding: 18px 22px 18px 28px; + width: 100%; +} + +.reveal blockquote p { + color: var(--color-neutral-900); + font-size: 16pt; + font-style: italic; +} + +.reveal blockquote cite { + color: var(--color-neutral-600); + display: block; + font-style: normal; + margin-top: 8px; +} + +.reveal table { + border-collapse: collapse; + box-shadow: 0 1px 3px rgba(13, 15, 16, 0.08); + font-size: 12pt; + width: 100%; +} + +.reveal table th { + background: var(--color-purple-700); + border: none; + color: var(--color-white); + font-family: var(--ui-font); + font-size: 11pt; + font-weight: 700; + letter-spacing: 0.08em; + padding: 9px 12px; + text-align: left; + text-transform: uppercase; +} + +.reveal table td { + border-bottom: 1px solid var(--color-neutral-200); + color: var(--color-neutral-900); + font-size: 12pt; + padding: 8px 12px; + vertical-align: top; +} + +.reveal table tr:nth-child(even) td { + background: var(--color-gold-50); +} + +.card, +.card-red, +.card-green, +.card-blue { + background: var(--color-white); + border: 1px solid var(--color-neutral-200); + border-radius: var(--radius-md); + box-shadow: 0 1px 3px rgba(13, 15, 16, 0.08); + overflow: hidden; + padding: 18px 20px; + position: relative; +} + +.card::before, +.card-red::before, +.card-green::before, +.card-blue::before { + background: var(--color-purple-700); + content: ""; + height: 4px; + left: 0; + position: absolute; + right: 0; + top: 0; +} + +.card-red { + background: var(--color-purple-50); + border-color: var(--color-purple-200); +} + +.card-red::before { + background: var(--color-purple-700); +} + +.card-green { + background: var(--color-gold-50); + border-color: var(--color-gold-200); +} + +.card-green::before { + background: var(--color-gold-500); +} + +.card-blue { + background: #effcfc; + border-color: var(--color-teal-300); +} + +.card-blue::before { + background: var(--color-teal-500); +} + +.reveal .badge { + background: var(--color-purple-700); + border-radius: 0; + color: var(--color-white); + display: inline-block; + font-family: var(--ui-font); + font-size: 10pt; + font-weight: 700; + letter-spacing: 0.09em; + margin-bottom: 8px; + padding: 4px 10px; + text-transform: uppercase; + transform: skewX(-15deg); +} + +.reveal .badge-green { + background: var(--color-gold-700); +} + +.reveal .badge-blue { + background: var(--color-teal-500); + color: var(--color-purple-950); +} + +.check-item { + align-items: flex-start; + display: flex; + gap: 12px; + margin-bottom: 8px; +} + +.check-item i { + color: var(--color-purple-700) !important; + flex-shrink: 0; + margin-top: 4px; +} + +.reveal code { + background: var(--color-purple-50); + border: 1px solid var(--color-purple-100); + border-radius: 4px; + color: var(--color-purple-700); + font-family: var(--mono-font); + font-size: 0.86em; + padding: 2px 6px; +} + +.section-divider code { + background: rgba(255, 255, 255, 0.08); + border-color: rgba(42, 210, 201, 0.22); + color: var(--color-teal-300); +} + +/* ── Antenna-specific extensions ────────────────────────────────── */ + +/* Large stat/metric numbers */ +.stat-number { + font-family: var(--heading-font); + font-size: 56pt; + font-weight: 800; + color: var(--color-purple-700); + line-height: 1; + letter-spacing: -0.02em; +} + +.stat-number-hero { + font-size: 72pt; + color: var(--color-teal-600); +} + +.stat-number-before { + font-size: 36pt; + color: var(--color-purple-600); +} + +.stat-number-after { + font-size: 36pt; + color: var(--color-teal-600); +} + +.stat-label { + font-family: var(--ui-font); + font-size: 11pt; + font-weight: 700; + letter-spacing: 0.1em; + text-transform: uppercase; + color: var(--muted-color); + margin-top: 4px; +} + +/* Screenshot / diagram placeholders */ +.diagram-placeholder { + background: var(--color-neutral-50); + border: 2px dashed var(--color-neutral-300); + border-radius: var(--radius-md); + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + gap: 6px; + min-height: 160px; + padding: 20px; +} + +.diagram-placeholder .diagram-label { + font-family: var(--ui-font); + font-size: 11pt; + letter-spacing: 0.1em; + text-transform: uppercase; + color: var(--color-neutral-500); + font-weight: 700; +} + +.screenshot-frame { + background: var(--color-white); + border: 1px solid var(--color-neutral-200); + border-radius: var(--radius-md); + box-shadow: 0 14px 30px rgba(26, 0, 56, 0.14); + box-sizing: border-box; + display: flex; + height: 280px; + margin: 0; + overflow: hidden; + padding: 8px; +} + +.screenshot-frame img { + border-radius: calc(var(--radius-md) - 2px); + display: block; + height: 100%; + object-fit: contain; + width: 100%; +} + +/* Architecture / flow diagrams */ +.diag-flow { + display: flex; + align-items: center; + gap: 0; + background: var(--color-neutral-50); + border: 1px solid var(--color-neutral-200); + border-radius: var(--radius-md); + padding: 14px 16px; +} + +.diag-box { + flex-shrink: 0; + padding: 10px 14px; + border-radius: 6px; + font-size: 13pt; + font-weight: 600; + font-family: var(--heading-font); + text-align: center; + line-height: 1.3; + min-width: 90px; + border: 2px solid; +} + +.diag-box small { + display: block; + font-size: 9pt; + font-weight: 400; + opacity: 0.75; +} + +.diag-sm { + font-size: 12pt; + padding: 7px 12px; +} + +.diag-primary { + border-color: var(--color-purple-700); + background: var(--color-purple-50); + color: var(--color-purple-700); +} + +.diag-neutral { + border-color: var(--color-neutral-400); + background: var(--color-white); + color: var(--color-neutral-700); +} + +.diag-success { + border-color: var(--color-teal-500); + background: #effcfc; + color: var(--color-teal-600); +} + +.diag-tag { + display: block; + margin-top: 5px; + background: var(--color-gold-100); + color: var(--color-gold-700); + font-size: 9pt; + padding: 2px 6px; + border-radius: 3px; + font-weight: 700; + font-family: var(--ui-font); + letter-spacing: 0.05em; + text-transform: uppercase; +} + +.diag-conn { + flex: 1; + display: flex; + flex-direction: column; + gap: 6px; + padding: 0 10px; +} + +.diag-arrow { + display: flex; + align-items: center; + gap: 4px; + font-size: 10pt; + color: var(--muted-color); +} + +.diag-arrow .diag-line { + flex: 1; + height: 2px; + background: var(--color-neutral-400); + position: relative; +} + +.diag-arrow.right .diag-line::after { + content: ''; + position: absolute; + right: -1px; + top: -4px; + border-left: 7px solid var(--color-neutral-400); + border-top: 4px solid transparent; + border-bottom: 4px solid transparent; +} + +.diag-arrow.left .diag-line::before { + content: ''; + position: absolute; + left: -1px; + top: -4px; + border-right: 7px solid var(--color-neutral-400); + border-top: 4px solid transparent; + border-bottom: 4px solid transparent; +} + +.diag-arrow.blocking .diag-line { + background: var(--color-purple-400); +} + +.diag-arrow.blocking .diag-line::before { + border-right-color: var(--color-purple-400); +} + +.diag-arrow.blocking .diag-arrow-lbl { + color: var(--color-purple-600); + font-weight: 600; +} + +.diag-arrow-lbl { + font-size: 9pt; + color: var(--muted-color); + white-space: nowrap; +} + +/* CPU-GPU data flow (before/after rows) */ +.data-flow { + display: flex; + flex-direction: column; + gap: 10px; + background: var(--color-neutral-50); + border: 1px solid var(--color-neutral-200); + border-radius: var(--radius-md); + padding: 12px 14px; +} + +.data-flow-row { + display: flex; + align-items: center; + gap: 6px; +} + +.df-row-label { + font-size: 9pt; + font-weight: 700; + font-family: var(--ui-font); + letter-spacing: 0.07em; + text-transform: uppercase; + width: 40px; + flex-shrink: 0; + color: var(--muted-color); +} + +.df-node { + padding: 7px 12px; + border-radius: 5px; + font-size: 11pt; + font-weight: 600; + font-family: var(--heading-font); + text-align: center; + flex-shrink: 0; + line-height: 1.2; +} + +.df-node small { + display: block; + font-size: 9pt; + font-weight: 400; +} + +.df-gpu { + background: var(--color-teal-300); + color: var(--color-purple-950); +} + +.df-cpu { + background: var(--color-purple-100); + color: var(--color-purple-700); + border: 1px dashed var(--color-purple-400); +} + +.df-arrow-seg { + flex: 1; + height: 2px; + position: relative; +} + +.df-arrow-seg::after { + content: ''; + position: absolute; + right: -1px; + top: -3px; + border-left: 6px solid; + border-top: 3px solid transparent; + border-bottom: 3px solid transparent; +} + +.df-arrow-seg.cpu-path { + background: var(--color-purple-400); +} + +.df-arrow-seg.cpu-path::after { + border-left-color: var(--color-purple-400); +} + +.df-arrow-seg.gpu-path { + background: var(--color-teal-500); +} + +.df-arrow-seg.gpu-path::after { + border-left-color: var(--color-teal-500); +} + +/* Gantt-style timelines (GPU utilization, CUDA streams) */ +.timeline-container { + border: 1px solid var(--color-neutral-200); + border-radius: var(--radius-md); + overflow: hidden; +} + +.timeline-row { + display: flex; + align-items: stretch; +} + +.timeline-row + .timeline-row { + border-top: 1px solid var(--color-neutral-200); +} + +.timeline-label { + width: 48px; + padding: 6px 8px; + font-size: 10pt; + font-weight: 700; + font-family: var(--ui-font); + color: var(--muted-color); + border-right: 1px solid var(--color-neutral-200); + flex-shrink: 0; + text-align: right; + background: var(--color-neutral-50); + display: flex; + align-items: center; + justify-content: flex-end; +} + +.timeline-track { + flex: 1; + height: 36px; + display: flex; +} + +.timeline-active { + background: var(--color-teal-500); + height: 100%; +} + +.timeline-active-b { + background: var(--color-teal-600); + height: 100%; +} + +.timeline-cpu { + background: var(--color-gold-500); + height: 100%; +} + +.timeline-idle { + background: var(--color-neutral-100); + height: 100%; + border-left: 1px dashed var(--color-neutral-300); + border-right: 1px dashed var(--color-neutral-300); +} + +/* CUDA stream bars */ +.stream-timeline { + display: flex; + align-items: center; + gap: 0; +} + +.stream-label { + width: 40px; + font-size: 9pt; + font-weight: 700; + font-family: var(--ui-font); + color: var(--muted-color); + text-align: right; + padding-right: 8px; + flex-shrink: 0; + letter-spacing: 0.05em; + text-transform: uppercase; +} + +.stream-track { + flex: 1; + height: 40px; + display: flex; + border: 1px solid var(--color-neutral-200); + border-radius: 3px; + overflow: hidden; +} + +.stream-bar { + height: 100%; + display: flex; + align-items: center; + justify-content: center; + font-size: 11pt; + font-weight: 700; + font-family: var(--ui-font); + letter-spacing: 0.04em; +} + +.stream-transfer { + background: var(--color-purple-600); + color: var(--color-white); +} + +.stream-compute { + background: var(--color-teal-500); + color: var(--color-purple-950); +} + +.stream-transfer-b { + background: var(--color-purple-200); + color: var(--color-purple-800); +} + +.stream-compute-b { + background: var(--color-teal-300); + color: var(--color-purple-950); +} + +.stream-gap { + background: var(--color-neutral-100); + height: 100%; +} + +/* Code blocks */ +pre.code-block { + background: var(--color-neutral-950); + border-radius: var(--radius-md); + padding: 14px 18px; + margin: 0; + overflow: auto; +} + +pre.code-block code { + background: transparent; + border: none; + color: var(--color-teal-300); + font-family: var(--mono-font); + font-size: 13pt; + padding: 0; + white-space: pre; + line-height: 1.5; +} + +/* Diagram section label */ +.diag-label { + font-family: var(--ui-font); + font-size: 10pt; + font-weight: 700; + letter-spacing: 0.1em; + text-transform: uppercase; + color: var(--muted-color); + margin-bottom: 8px; +} + +/* Full-width SVG diagram images (from UXDesigner SVG assets) */ +.diagram-full { + display: block; + inline-size: 100%; + max-block-size: 55vh; + min-height: 0; + object-fit: contain; +} + +/* Let the batch data-loader iframe use the full slide content area. */ +#batch-data-loader .diagram-full { + block-size: 100%; + max-block-size: none; +} + +#fix1-batches .diagram-full { + block-size: 100%; + max-block-size: none; +} + +#batch-data-loader .content, +#baseline .content { + padding-top: 12px; +} + +#baseline .stat-banner { + margin-bottom: 6px; + padding: 4px 0 8px; +} + +#baseline .diagram-full { + max-block-size: none; +} + +/* Intro sentence above full-width visual (pattern A / C slides) */ +.slide-intro { + color: var(--color-neutral-700); + font-size: 15pt; + font-weight: 600; + line-height: 1.4; + margin-bottom: 14px; + max-width: 960px; +} + +/* Stat banner strip (pattern B — Slides 9 and 14) */ +.stat-banner { + align-items: baseline; + border-bottom: 1px solid var(--color-neutral-200); + display: flex; + gap: 16px; + margin-bottom: 14px; + padding: 6px 0 12px; +} + +.stat-banner .stat-number { + font-size: 40pt; +} + +.stat-banner-desc { + color: var(--muted-color); + font-family: var(--ui-font); + font-size: 13pt; + font-weight: 700; + letter-spacing: 0.06em; +} + +/* Full-height screenshot frame variant (Slide 2) */ +.screenshot-frame.screenshot-flex { + flex: 1; + height: auto; +} + +@media print { + .reveal .slides section:not(.stack)::before { + print-color-adjust: exact; + } +} diff --git a/index.html b/index.html index a08735e..4739ead 100644 --- a/index.html +++ b/index.html @@ -152,7 +152,7 @@ transition: box-shadow 0.15s ease, transform 0.15s ease; } - .card:hover { + .card:not(.card--coming-soon):hover { box-shadow: 0 4px 20px rgba(75, 46, 131, 0.13); transform: translateY(-2px); } @@ -192,6 +192,19 @@ text-transform: uppercase; } + .status-badge { + background: var(--color-neutral-200); + border: 1px solid var(--color-neutral-600); + border-radius: 4px; + color: var(--color-neutral-900); + font-family: var(--ui-font); + font-size: 10px; + font-weight: 700; + letter-spacing: 0.07em; + padding: 2px 8px; + text-transform: uppercase; + } + .card-title { font-family: var(--heading-font); font-size: 20px; @@ -246,6 +259,38 @@ display: inline; } + .card-status { + color: var(--color-neutral-700); + font-family: var(--heading-font); + font-size: 13px; + font-weight: 700; + letter-spacing: 0.01em; + } + + .card--coming-soon { + background: var(--color-gold-100); + border-color: var(--color-neutral-200); + box-shadow: none; + } + + .card--coming-soon::before { + background: var(--color-neutral-600); + } + + .card--coming-soon .tag { + background: var(--color-white); + color: var(--color-neutral-700); + } + + .card--coming-soon .card-title { + color: var(--color-neutral-700); + } + + .card--coming-soon .card-footer { + background: var(--color-gold-100); + border-top-color: var(--color-neutral-200); + } + /* ── Footer ────────────────────────────────────── */ .site-footer { border-top: 1px solid var(--color-neutral-200); @@ -285,19 +330,20 @@

Presentations

- +