Architecture¶
The prototype implements a clean separation of concerns across six services, with the Pipeline containing the user-configurable layers. Visually represented in the UI via a color-coded layer bar.
graph TD
A[🧠 ML Browser Benchmark] --> B[Router]
B --> C[Tutorial]
B --> D[Playground]
C --> E[Pipeline]
D --> E
E --> F[Execution]
F --> G[Benchmarking]
G --> H[📊 Results Dashboard]
style A fill:#6366f1,color:#fff
style B fill:#3b82f6,color:#fff
style C fill:#22c55e,color:#fff
style D fill:#eab308,color:#000
style E fill:#8b5cf6,color:#fff
style F fill:#ef4444,color:#fff
style G fill:#ec4899,color:#fff
style H fill:#6366f1,color:#fff
Services¶
| Service | Color | Key Files | Responsibility |
|---|---|---|---|
| Router | Blue | shell.js, router.js, app.js |
Hash-based SPA routing, advanced mode, light/dark theme, mobile nav drawer |
| Tutorial | Green | tutorial.js |
7-step guided walkthrough: what is classification → model → runtime → framework → backend → input data → run & see results |
| Playground | Yellow | playground.js |
Free-form pipeline configuration, single-run + "benchmark all combinations" mode |
| Pipeline | Purple | pipeline.js |
Configuration layers the user builds up: LayerRegistry (layer definitions), PipelineState (state graph with cascade validation), PipelineCanvas (pipeline UI with click-to-configure) |
| Execution | Red | ml-adapter.js |
AdapterFactory with per-backend caching, 6 adapter classes (TFJS, ONNX, LiteRT, MediaPipe, Transformers, ML5), ImageNet preprocessing, shared runBenchmark() orchestrator |
| Benchmarking | Pink | bench.js |
MetricsCollector (performance.now() timing, Chromium performance.memory delta), RunRecorder (localStorage persistence with UUID), DiffComparator (comparison table with per-column best-value highlighting) |
Pipeline layers¶
The Pipeline is the only service that contains user-configurable steps. Users build up a configuration pipeline layer by layer, ordered by dependency:
Task → Model → Runtime → Framework → Backend → Input Data → Run
| Layer | What the user picks | Constrains |
|---|---|---|
| Task | Image Classification (only enabled) | Available models |
| Model | MobileNet v2, SqueezeNet 1.0, EfficientNet-Lite0 | Available runtimes |
| Runtime | ORT Web, TF.js, LiteRT.js | Available frameworks + backends |
| Framework | "Direct" or wrapper (Transformers.js, MediaPipe Tasks, ml5.js) | Backend options |
| Backend | WASM, WebGPU, WebGL, CPU | Execution hardware |
| Input Data | Upload or sample image | Nothing — independent |
Cascade rules¶
When a user changes a layer, downstream layers are cleared:
| Changed | Clears |
|---|---|
| Task | model, runtime, framework, backend, inputData |
| Model | runtime, framework, backend |
| Runtime | framework, backend |
| Framework | backend |
| Backend | — |
| Input Data | — |
Model × Runtime availability¶
| Model | ORT Web | TF.js | LiteRT.js | MediaPipe Tasks |
|---|---|---|---|---|
| MobileNet v2 | ✅ HuggingFace | ✅ CDN | ✅ TF Hub | — |
| SqueezeNet 1.0 | ✅ HuggingFace | — | ✅ TF Hub | — |
| EfficientNet-Lite0 | — | — | ✅ GCS | ✅ GCS |
Framework overhead measurement¶
| Runtime | Direct (no wrapper) | With wrapper |
|---|---|---|
| ORT Web | ort.InferenceSession.create() |
Transformers.js pipeline() |
| TF.js | TF.js mobilenet.load() |
ml5.js ml5.imageClassifier() |
| LiteRT.js | LiteRT.js direct API | MediaPipe Tasks ImageClassifier |
Layer bar (UI)¶
┌────────┬──────────┬─────────────┬──────────┬───────────┬──────────────┐
│ Router │ Tutorial │ Playground │ Pipeline │ Execution │ Benchmarking │
│ (Blue) │ (Green) │ (Yellow) │ (Purple) │ (Red) │ (Pink) │
└────────┴──────────┴─────────────┴──────────┴───────────┴──────────────┘
Services are infrastructure. "Pipeline" is the only one that contains user-configurable layers.
Data flow¶
sequenceDiagram
participant U as User
participant R as Router
participant P as Page Fragment
participant E as Engine
participant Pl as Pipeline
participant M as ML Adapter
participant C as Benchmarking
U->>R: Click nav link (#playground)
R->>P: fetch(pages/playground.html)
P-->>R: HTML fragment
R->>E: dynamic import(playground.js)
E->>Pl: new PipelineState() + PipelineCanvas()
U->>Pl: Click layers (task → model → runtime → framework → backend → inputData)
Pl->>E: layer-ready event
U->>E: Click "Run"
E->>M: runBenchmark(config, nRuns)
M->>M: adapter.load() → download model
M->>M: adapter.infer() → cold + warm runs
M->>C: MetricsCollector + RunRecorder.save()
C-->>U: Result card with prediction + metrics
Module dependency graph¶
app.js
├── shell.js → utils.js
└── router.js → utils.js, shell.js
Lazy imports:
tutorial.js → utils.js, pipeline.js, ml-adapter.js → bench.js
playground.js → utils.js, pipeline.js, ml-adapter.js, shell.js
bench.js → utils.js
Key design decisions¶
- Zero build step — all ES modules loaded natively by the browser; no bundler, no TypeScript, no framework
- Lazy loading — pages AND CDN libraries (TF.js, ONNX, etc.) loaded on-demand via dynamic
import()/ script injection - Event-driven —
layer-change,layer-ready,role-changeCustomEvents decouple UI from state - Advanced mode — N-runs slider, raw logs
- localStorage as DB — run records persisted as JSON array with UUIDs, CSV export available
- CSS custom properties — design system with per-layer colors, light/dark theme via
data-themeattribute
Terminology guide¶
| Term | Definition | Examples |
|---|---|---|
| Task | The ML problem type | Image Classification, Object Detection |
| Model | The neural network architecture | MobileNet v2, SqueezeNet, EfficientNet-Lite0 |
| Runtime | Low-level inference engine | ORT Web, TF.js, LiteRT.js |
| Framework | Optional wrapper around a runtime | Transformers.js (wraps ORT), MediaPipe Tasks (wraps LiteRT), ml5.js (wraps TF.js) |
| Backend | Hardware accelerator | WASM, WebGPU, WebGL, CPU |
| Input Data | The input for inference | Upload image, sample image |
Previous architecture (first iteration)¶
First iteration naming (May 2026)
The original prototype used different terminology that was refined during development.
What changed¶
| First iteration | Current | Why |
|---|---|---|
block-runtime.js |
pipeline.js |
The file defines the configuration pipeline, not "blocks" |
BlockRegistry |
LayerRegistry |
Each entry is a configuration layer, not a generic block |
BlockCanvas |
PipelineCanvas |
Renders the pipeline of layers, not a canvas of blocks |
DataFlowGraph |
PipelineState |
Manages state between pipeline layers |
block-change / block-ready events |
layer-change / layer-ready |
Events fire on layer state changes |
Dataset layer |
Input Data layer |
It's a single input, not a dataset collection |
| "Shell / Router" | "Router" | The shell is primarily a router with role/theme |
| "Block Runtime" (layer bar) | "Pipeline" | The layer bar showed services + pipeline mixed together |
.block-card, .block-icon, etc. |
.layer-card, .layer-icon, etc. |
CSS classes follow the layer terminology |
--color-block-* CSS vars |
--color-layer-* |
CSS variables follow the layer terminology |
What was also corrected¶
- Services vs Pipeline: Shell, Tutorial, Benchmarking are infrastructure services. Only the Pipeline contains user-configurable layers. The layer bar now reflects this distinction.
- ONNX re-enabled: Originally disabled due to GitHub LFS CDN issues. Now uses HuggingFace
/resolve/main/CDN URLs directly. - Model selection restored: Originally hardcoded per runtime. Now user-interactive via
MODEL_REGISTRYwith runtime filtering. - LiteRT.js added: New Google runtime (released July 2026) as a 3rd runtime option alongside TF.js and ORT Web.
- ml5.js added: TF.js wrapper for symmetric overhead measurement across all 3 runtimes.
- Framework block added: Optional layer for measuring wrapper overhead (Direct vs Transformers.js vs MediaPipe Tasks vs ml5.js).