import { StrictMode, useEffect, useRef, useState } from 'react' import { createRoot } from 'react-dom/client' import { createVaeEncoderSliceManifest } from './models/triposplat/manifests' import { compositeRgbaOnBlack, imageBitmapToRgba } from './models/triposplat/preprocess' import { compareFloat32, type TensorComparison } from './models/triposplat/tensorMath' import { TripoSplatWebGPUModel } from './models/triposplat/TripoSplatWebGPUModel' import type { OrtRunTimings, OrtWorkerStatus } from './runtime/OrtWorkerClient' const DEFAULT_MODEL = '/models/triposplat/flux2_vae_encoder.onnx' const DEFAULT_FIXTURE = '/fixtures/generated/flux2-vae-fp32' interface EncoderLabResult { passed: boolean executionProvider: string modelLoadMs: number endToEndEncodeMs: number runtime: OrtRunTimings comparison: TensorComparison preprocessing: ByteComparison runs: EncoderLabRun[] medianInferenceMs: number medianEndToEndEncodeMs: number modelTransferBytes?: number memory: { beforeLoad: BrowserMemorySnapshot afterLoad: BrowserMemorySnapshot afterRuns: BrowserMemorySnapshot } environment: BrowserEnvironment tolerance: { maxAbsoluteError: number; minimumCosineSimilarity: number } } interface EncoderLabRun { run: number endToEndEncodeMs: number runtime: OrtRunTimings comparison: TensorComparison } interface ByteComparison { count: number mismatched: number maxAbsoluteError: number meanAbsoluteError: number passed: boolean } interface BrowserMemorySnapshot { source: 'measureUserAgentSpecificMemory' | 'performance.memory' | 'unavailable' bytes?: number usedJsHeapBytes?: number totalJsHeapBytes?: number jsHeapLimitBytes?: number } interface BrowserEnvironment { userAgent: string platform: string crossOriginIsolated: boolean webgpu: boolean adapter?: { vendor?: string architecture?: string device?: string description?: string } } type PerformanceWithMemory = Performance & { memory?: { usedJSHeapSize: number totalJSHeapSize: number jsHeapSizeLimit: number } } type NavigatorWithGpu = Navigator & { gpu?: { requestAdapter: () => Promise<{ info?: { vendor?: string architecture?: string device?: string description?: string } } | null> } } const BENCHMARK_RUNS = 3 declare global { interface Window { __TRIPOSPLAT_ENCODER_RESULT__?: EncoderLabResult } } async function fetchFloat32(url: string): Promise { const response = await fetch(url) if (!response.ok) throw new Error(`Could not fetch ${url}: HTTP ${response.status}`) const buffer = await response.arrayBuffer() if (buffer.byteLength % 4 !== 0) throw new Error(`${url} is not aligned float32 data.`) return new Float32Array(buffer) } function median(values: readonly number[]): number { const sorted = [...values].sort((left, right) => left - right) const middle = Math.floor(sorted.length / 2) return sorted.length % 2 === 0 ? (sorted[middle - 1] + sorted[middle]) / 2 : sorted[middle] } function compareBytes(reference: Uint8ClampedArray, candidate: Uint8ClampedArray): ByteComparison { if (reference.length !== candidate.length) { throw new Error(`Prepared RGB length ${candidate.length} does not match Python ${reference.length}.`) } let mismatched = 0 let maxAbsoluteError = 0 let absoluteErrorSum = 0 for (let index = 0; index < reference.length; index += 1) { const error = Math.abs(reference[index] - candidate[index]) if (error !== 0) mismatched += 1 maxAbsoluteError = Math.max(maxAbsoluteError, error) absoluteErrorSum += error } const meanAbsoluteError = absoluteErrorSum / reference.length return { count: reference.length, mismatched, maxAbsoluteError, meanAbsoluteError, passed: mismatched === 0, } } async function measureBrowserMemory(): Promise { const browserPerformance = performance as PerformanceWithMemory // `measureUserAgentSpecificMemory()` can interrupt a live ORT worker in // Chromium. Keep the parity gate non-invasive and report the narrower main // realm heap counter with its limitation instead. if (browserPerformance.memory) { return { source: 'performance.memory', usedJsHeapBytes: browserPerformance.memory.usedJSHeapSize, totalJsHeapBytes: browserPerformance.memory.totalJSHeapSize, jsHeapLimitBytes: browserPerformance.memory.jsHeapSizeLimit, } } return { source: 'unavailable' } } async function inspectBrowserEnvironment(): Promise { const gpu = (navigator as NavigatorWithGpu).gpu const adapter = gpu ? await gpu.requestAdapter() : null const info = adapter?.info return { userAgent: navigator.userAgent, platform: navigator.platform, crossOriginIsolated: self.crossOriginIsolated, webgpu: Boolean(gpu), adapter: info ? { vendor: info.vendor, architecture: info.architecture, device: info.device, description: info.description, } : undefined, } } async function contentLength(url: string): Promise { try { const response = await fetch(url, { method: 'HEAD' }) if (!response.ok) return undefined const value = Number(response.headers.get('content-length')) return Number.isFinite(value) && value >= 0 ? value : undefined } catch { return undefined } } export function EncoderLab() { const modelRef = useRef(null) const [modelUrl, setModelUrl] = useState(DEFAULT_MODEL) const [fixtureUrl, setFixtureUrl] = useState(DEFAULT_FIXTURE) const [status, setStatus] = useState('Ready to run the recorded PyTorch fixture.') const [busy, setBusy] = useState(false) const [result, setResult] = useState(null) const [error, setError] = useState(null) useEffect(() => () => { const model = modelRef.current modelRef.current = null if (model) void model.dispose() }, []) const run = async () => { if (busy) return setBusy(true) setError(null) setResult(null) delete window.__TRIPOSPLAT_ENCODER_RESULT__ let executionProvider = 'unknown' const onRuntimeStatus = (event: OrtWorkerStatus) => { setStatus(event.message) if (event.executionProvider) executionProvider = event.executionProvider } try { const previous = modelRef.current modelRef.current = null if (previous) await previous.dispose() const model = new TripoSplatWebGPUModel({ graphs: createVaeEncoderSliceManifest(modelUrl, 'float32'), allowWasmFallback: false, onRuntimeStatus, }) modelRef.current = model setStatus('Fetching the prepared image, explicit epsilon and PyTorch reference…') const [imageResponse, preparedResponse, epsilon, reference, environment] = await Promise.all([ fetch(`${fixtureUrl}/source.png`), fetch(`${fixtureUrl}/prepared.png`), fetchFloat32(`${fixtureUrl}/epsilon.f32`), fetchFloat32(`${fixtureUrl}/feature2.f32`), inspectBrowserEnvironment(), ]) if (!imageResponse.ok) throw new Error(`Could not fetch fixture image: HTTP ${imageResponse.status}`) if (!preparedResponse.ok) throw new Error(`Could not fetch prepared reference: HTTP ${preparedResponse.status}`) const imageBlob = await imageResponse.blob() const preparedBitmap = await createImageBitmap(await preparedResponse.blob()) const preparedReference = (() => { try { return compositeRgbaOnBlack(imageBitmapToRgba(preparedBitmap)).data } finally { preparedBitmap.close() } })() const manifest = model.graphs.vaeEncoder?.manifest const transferLengths = manifest ? await Promise.all([ contentLength(manifest.graphUrl), ...(manifest.externalData ?? []).map(({ url }) => contentLength(url)), ]) : [] const modelTransferBytes = transferLengths.length > 0 && transferLengths.every((value) => value !== undefined) ? transferLengths.reduce((total, value) => total + (value ?? 0), 0) : undefined const beforeLoad = await measureBrowserMemory() const loadStarted = performance.now() await model.load() const modelLoadMs = performance.now() - loadStarted const afterLoad = await measureBrowserMemory() const runs: EncoderLabRun[] = [] let preparedCandidate: Uint8ClampedArray | undefined for (let run = 1; run <= BENCHMARK_RUNS; run += 1) { const bitmap = await createImageBitmap(imageBlob) const encodeStarted = performance.now() const encoded = await (async () => { try { return await model.encode(bitmap, { vaeNoise: epsilon, onProgress: (progress) => setStatus(`Run ${run}/${BENCHMARK_RUNS}: ${progress.message}`), }) } finally { bitmap.close() } })() const endToEndEncodeMs = performance.now() - encodeStarted if (!encoded.feature2 || !encoded.timings.vaeEncoder) { throw new Error('The VAE slice returned no feature2 tensor or runtime timings.') } if (!preparedCandidate) preparedCandidate = new Uint8ClampedArray(encoded.preparedImage.data) runs.push({ run, endToEndEncodeMs, runtime: encoded.timings.vaeEncoder, comparison: compareFloat32(reference, encoded.feature2), }) } const afterRuns = await measureBrowserMemory() const finalRun = runs[runs.length - 1] if (!preparedCandidate) throw new Error('Browser preprocessing returned no prepared RGB image.') const preprocessing = compareBytes(preparedReference, preparedCandidate) const tolerance = { maxAbsoluteError: 0.006, minimumCosineSimilarity: 0.99999 } const nextResult: EncoderLabResult = { passed: preprocessing.passed && runs.every(({ comparison }) => comparison.finite && comparison.maxAbsoluteError <= tolerance.maxAbsoluteError && comparison.cosineSimilarity >= tolerance.minimumCosineSimilarity), executionProvider, modelLoadMs, endToEndEncodeMs: finalRun.endToEndEncodeMs, runtime: finalRun.runtime, comparison: finalRun.comparison, preprocessing, runs, medianInferenceMs: median(runs.map(({ runtime }) => runtime.inferenceMs)), medianEndToEndEncodeMs: median(runs.map(({ endToEndEncodeMs }) => endToEndEncodeMs)), modelTransferBytes, memory: { beforeLoad, afterLoad, afterRuns }, environment, tolerance, } window.__TRIPOSPLAT_ENCODER_RESULT__ = nextResult setResult(nextResult) setStatus(nextResult.passed ? 'PASS: WebGPU output matches the recorded PyTorch tensor.' : 'FAIL: tensor drift exceeds tolerance.') } catch (caught) { const message = caught instanceof Error ? caught.message : String(caught) setError(message) setStatus('Encoder validation failed.') } finally { setBusy(false) } } return (

TripoSplat Flux VAE · WebGPU parity

This page runs the recorded alpha-present source image through browser preprocessing, then sends the resulting 1024² tensor and explicit epsilon through ONNX Runtime WebGPU.

{status}

{error ?
{error}
: null} {result ?
{JSON.stringify(result, null, 2)}
: null}
) } const style = document.createElement('style') style.textContent = ` :root { color: #ececf3; background: #101014; font: 15px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; } body { margin: 0; } main { max-width: 920px; margin: 0 auto; padding: 48px 24px; } h1 { font: 600 28px/1.2 system-ui, sans-serif; } label { display: grid; gap: 6px; margin: 18px 0; } input { box-sizing: border-box; width: 100%; padding: 10px; color: inherit; background: #1b1b22; border: 1px solid #3a3a48; border-radius: 6px; } button { padding: 10px 16px; color: #08080a; background: #f8cf00; border: 0; border-radius: 6px; font-weight: 700; cursor: pointer; } button:disabled { opacity: .55; cursor: wait; } pre { overflow: auto; padding: 16px; background: #18181f; border-radius: 8px; } .error { color: #ff9b9b; } ` document.head.append(style) createRoot(document.getElementById('root')!).render( , )