Skip to content

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-drivenlayer-change, layer-ready, role-change CustomEvents 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-theme attribute

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_REGISTRY with 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).