borkiss's picture
Upload folder using huggingface_hub
fa498e8 verified
Raw
History Blame Contribute Delete
17.7 kB
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Falcon-Perception · бенчмарк</title>
<link rel="icon" href="favicon.svg" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet" />
<style>
:root {
--bg: #0b0908;
--bg2: #12100d;
--line: #262019;
--text: #e8e3db;
--dim: #8b857b;
--faint: #55504a;
--amber: #ffb454;
--amber-dim: #96703a;
--ok: #9ece6a;
--err: #e0645c;
--mono: "IBM Plex Mono", ui-monospace, monospace;
--serif: "Instrument Serif", serif;
}
* { box-sizing: border-box; margin: 0; }
body {
background: var(--bg); color: var(--text);
font-family: var(--mono); font-size: 13px; line-height: 1.6;
min-height: 100vh; padding: 2.2rem 1.2rem 4rem;
display: flex; justify-content: center;
}
body::before {
content: "";
position: fixed; inset: 0; z-index: 10; pointer-events: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)' opacity='0.05'/%3E%3C/svg%3E");
}
body::after {
content: "";
position: fixed; inset: 0; z-index: 9; pointer-events: none;
background: radial-gradient(ellipse 120% 90% at 50% 0%, transparent 60%, rgba(0,0,0,0.5));
}
main { width: min(760px, 100%); position: relative; z-index: 1; }
h1 { font-family: var(--serif); font-style: italic; font-weight: 400; font-size: 28px; margin-bottom: 4px; }
.sub { font-size: 11.5px; color: var(--faint); letter-spacing: 0.04em; margin-bottom: 1.6rem; }
.sub a { color: var(--dim); }
.sub a:hover { color: var(--amber); }
.card { border: 1px solid var(--line); background: var(--bg2); padding: 1rem 1.1rem; margin-bottom: 1.2rem; }
.card h2 {
font-size: 10.5px; letter-spacing: 0.14em; text-transform: uppercase;
color: var(--faint); margin-bottom: 0.7rem;
}
table { width: 100%; border-collapse: collapse; font-size: 12px; }
th, td { text-align: left; padding: 0.25rem 0.6rem 0.25rem 0; }
th { color: var(--faint); font-weight: 400; }
td { color: var(--text); font-variant-numeric: tabular-nums; }
td small, th small { color: var(--faint); }
.controls { display: flex; gap: 0.9rem; align-items: center; flex-wrap: wrap; margin-bottom: 0.9rem; }
button {
background: none; border: 1px solid var(--line); color: var(--dim);
font: inherit; font-size: 12px; letter-spacing: 0.12em; text-transform: uppercase;
padding: 0.5rem 1.3rem; cursor: pointer; transition: all 0.2s;
}
button:hover:not(:disabled) { color: var(--amber); border-color: var(--amber-dim); }
button:disabled { opacity: 0.35; cursor: default; }
label { font-size: 11.5px; color: var(--faint); display: inline-flex; gap: 0.45rem; align-items: center; cursor: pointer; }
label input { accent-color: var(--amber); }
#console {
height: 260px; overflow-y: auto;
font-size: 11.5px; line-height: 1.55; color: var(--dim);
white-space: pre-wrap;
border: 1px solid var(--line); padding: 0.7rem 0.8rem;
background: rgba(0, 0, 0, 0.3);
scrollbar-width: thin; scrollbar-color: var(--line) transparent;
}
#console .ok { color: var(--ok); }
#console .hl { color: var(--amber); }
#console .err { color: var(--err); }
#results { display: none; }
.dl { display: flex; gap: 0.8rem; margin-top: 0.9rem; }
#stagewrap { height: 0; overflow: hidden; }
</style>
</head>
<body>
<main>
<h1>Falcon-Perception · бенчмарк</h1>
<p class="sub">
Стандартный перф-набор движка на <em>вашем</em> GPU + шарабельный .log:
загрузка весов (724&nbsp;МБ, кешируются), полная фото-детекция,
видео-кейфреймы со спекулятивным декодом и трекер. Нужен WebGPU с
shader-f16 + subgroups (Chrome/Edge&nbsp;125+).
&nbsp;·&nbsp; <a href="index.html">демо</a>
&nbsp;·&nbsp; <a href="https://huggingface.co/borkiss/falcon-perception-webgpu">веса</a>
</p>
<div class="card">
<h2>Референс — Mac mini M4, 10-core GPU, Chrome</h2>
<table>
<tr><th></th><th>фото-запрос</th><th>декод</th><th>кейфреймы статика / пан</th><th>трек</th></tr>
<tr><td>q8 + q4-lm</td><td>~1.25 с (8 боксов)</td><td>~80 ток/с</td><td>~2.5 / ~1.8 Гц @256</td><td>~28 fps</td></tr>
</table>
</div>
<div class="card">
<h2>Запуск</h2>
<div class="controls">
<button id="run">▶ бенчмарк</button>
<label><input type="checkbox" id="withVideo" checked /> видеофазы (статика + пан, ~25 с)</label>
</div>
<div id="console">ожидание — нажмите «бенчмарк»</div>
</div>
<div class="card" id="results">
<h2>Результаты</h2>
<table id="rtable"></table>
<div class="dl">
<button id="download">↓ скачать .log</button>
<button id="copy">копировать JSON</button>
</div>
</div>
<div id="stagewrap"><video id="v" muted playsinline></video></div>
</main>
<script type="module">
const $ = (id) => document.getElementById(id);
const median = (a) => a.slice().sort((x, y) => x - y)[a.length >> 1];
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
function log(text, cls = '') {
const el = document.createElement('div');
if (cls) el.className = cls;
el.textContent = text;
if ($('console').textContent === 'ожидание — нажмите «бенчмарк»') $('console').textContent = '';
$('console').appendChild(el);
$('console').scrollTop = $('console').scrollHeight;
}
const HF_WEIGHTS = 'https://huggingface.co/borkiss/falcon-perception-webgpu/resolve/main/falcon-q8-lm4.bin';
async function resolveWeights() {
try {
const local = new URL('engine/weights/falcon-q8-lm4.bin', location.href).href;
if ((await fetch(local, { method: 'HEAD' })).ok) return { url: local, source: 'local' };
} catch {}
return { url: HF_WEIGHTS, source: 'huggingface' };
}
let worker = null, onResult = null, onLoad = null, onProgress = null;
function initWorker() {
worker = new Worker('engine/src/worker.js', { type: 'module' });
worker.onmessage = (e) => {
const d = e.data;
if (d.type === 'progress') { onProgress?.(d); return; }
if (d.type === 'loaded') { onLoad?.(); return; }
if (d.type === 'error') { log('ошибка воркера: ' + d.message, 'err'); onLoad?.(new Error(d.message)); return; }
if (d.type === 'result') onResult?.(d);
};
}
let reqId = 0;
const detect = (src, query, video) => createImageBitmap(src).then((bmp) =>
worker.postMessage({ type: 'detect', image: bmp, query, video,
requestId: reqId++, capturedAtMs: performance.now() }, [bmp]));
const detectAwait = (src, query) => new Promise((res) => {
onResult = (d) => { if (d.source === 'detect' && !d.partial) { onResult = null; res(d); } };
detect(src, query, false);
});
const config = (o) => worker.postMessage({ type: 'config',
spec: true, specTol: 6, gate: false, videoRes: 256, decodeCap: 0, ...o });
// видеофаза: гоним кадры 30 fps из canvas-пана по сцене, собираем метрики
async function videoPhase(img, { seconds, panSpeed, label }) {
const c = document.createElement('canvas'); c.width = 512; c.height = 288;
const cx = c.getContext('2d');
const k = img.width / 1280, span = 1280 - 512;
const draw = (t) => {
const x = panSpeed ? Math.min(t * panSpeed, span) : span / 2;
cx.drawImage(img, x * k, (img.height - 288 * k) / 2, 512 * k, 288 * k, 0, 0, 512, 288);
};
draw(0);
const M = { det: 0, trk: 0, boxes: [], lat: [], prefill: [], decode: [],
specOk: 0, specRuns: 0, seq: 0 };
onResult = (d) => {
if (d.partial) return;
if (d.source === 'track') { M.trk++; M.lat.push(performance.now() - d.capturedAtMs); }
else if (d.source === 'detect') {
M.det++; M.boxes.push(d.detections.length);
if (d.stats) {
M.prefill.push(d.stats.prefillMs || 0); M.decode.push(d.stats.decodeMs || 0);
M.specOk += d.stats.specAccepted || 0; M.specRuns += d.stats.specRuns || 0;
M.seq += d.stats.seqSteps || 0;
}
}
};
const t0 = performance.now();
const pump = setInterval(() => {
draw((performance.now() - t0) / 1000);
detect(c, 'person', true);
}, 33);
// прогресс раз в 5 с
for (let s = 5; s < seconds; s += 5) {
await sleep(5000);
log(` ${label}: ${s} с — кейфреймов ${M.det}, трек-кадров ${M.trk}`);
}
await sleep((seconds % 5 || 5) * 1000);
clearInterval(pump);
await sleep(1500); // дожидаемся хвоста
onResult = null;
const dur = seconds;
return {
hz: +(M.det / dur).toFixed(2),
trkFps: +(M.trk / dur).toFixed(1),
latMs: M.lat.length ? Math.round(median(M.lat)) : null,
boxes: M.boxes.length ? median(M.boxes) : 0,
prefillMs: M.prefill.length ? Math.round(median(M.prefill)) : null,
decodeMs: M.decode.length ? Math.round(median(M.decode)) : null,
specAccepted: M.specOk, specRuns: M.specRuns, seqSteps: M.seq,
};
}
async function bench() {
const R = { meta: {}, results: {} };
R.meta.date = new Date().toISOString();
R.meta.ua = navigator.userAgent;
const adapter = await navigator.gpu?.requestAdapter({ powerPreference: 'high-performance' });
if (!adapter) throw new Error('в этом браузере нет WebGPU');
R.meta.gpu = `${adapter.info?.vendor ?? ''} ${adapter.info?.architecture ?? ''} ${adapter.info?.device ?? ''}`.trim() || 'unknown';
R.meta.features = ['shader-f16', 'subgroups', 'chromium-experimental-subgroup-matrix']
.filter((f) => adapter.features.has(f));
log(`gpu: ${R.meta.gpu}`, 'hl');
log(`features: ${R.meta.features.join(', ')}`);
if (!adapter.features.has('shader-f16') || !adapter.features.has('subgroups'))
throw new Error('движку нужны shader-f16 + subgroups');
const w = await resolveWeights();
R.meta.weights_source = w.source;
log(`веса: falcon-q8-lm4.bin (${w.source})`);
// --- загрузка: скачивание отдельно от инициализации движка ---
initWorker();
let dlBytes = 0, dlEnd = 0, lastPct = -10;
const t0 = performance.now();
onProgress = (d) => {
dlBytes = d.loaded; dlEnd = performance.now();
const pct = d.progress || 0;
if (pct >= lastPct + 10) { lastPct = pct; log(` скачивание ${pct}%`); }
};
await new Promise((res, rej) => { onLoad = (e) => e ? rej(e) : res(); worker.postMessage({ type: 'load', weightsUrl: w.url }); });
onLoad = null; onProgress = null;
const loadS = (performance.now() - t0) / 1000;
const dlS = (dlEnd - t0) / 1000;
R.results.load_s = +loadS.toFixed(1);
R.results.download_mb_s = dlS > 0.5 ? +(dlBytes / 1048576 / dlS).toFixed(0) : null;
log(`загрузка: ${R.results.load_s} с всего` +
(R.results.download_mb_s ? ` (скачивание ${R.results.download_mb_s} МБ/с, инициализация ${(loadS - dlS).toFixed(1)} с)` : ''), 'ok');
config({ spec: false }); // фото — канонический последовательный декод
// --- фото-детекция: канонический путь, референс-сцена ---
const img = new Image(); img.crossOrigin = 'anonymous'; img.src = 'examples/sprinters.png';
await img.decode();
log('фото-детекция «athletes» на референс-сцене (1 прогрев + 3 замера)…');
await detectAwait(img, 'athletes'); // прогрев: компиляция пайплайнов
const photo = [];
for (let r = 0; r < 3; r++) photo.push(await detectAwait(img, 'athletes'));
const q = photo.map((d) => d.inferenceMs);
const st = photo[photo.length - 1].stats || {};
R.results.photo_query_ms = Math.round(median(q));
R.results.photo_boxes = median(photo.map((d) => d.detections.length));
R.results.photo_prefill_ms = st.prefillMs ?? null;
R.results.photo_decode_ms = st.decodeMs ?? null;
R.results.photo_decode_steps = st.seqSteps ?? null;
if (st.decodeMs && st.seqSteps)
R.results.decode_tok_s = +(st.seqSteps / st.decodeMs * 1000).toFixed(1);
log(`фото: ${R.results.photo_query_ms} мс/запрос · ${R.results.photo_boxes} боксов · ` +
`префилл ${st.prefillMs} мс · декод ${st.decodeMs} мс (${st.seqSteps} шагов` +
(R.results.decode_tok_s ? ` = ${R.results.decode_tok_s} ток/с` : '') + ')', 'ok');
// --- видеофазы ---
if ($('withVideo').checked) {
// decodeCap 60: бенч-сцена ≤8 объектов (25 токенов); гасим редкие
// разносы декода на пан-артефактах, не искажая нормальные кейфреймы
config({ spec: true, decodeCap: 60 });
log('видео: статичная сцена 10 с @256 (спекуляция по драфту трекера)…');
const stat = await videoPhase(img, { seconds: 10, panSpeed: 0, label: 'статика' });
R.results.video_static = stat;
log(`статика: ${stat.hz} Гц кейфреймы · трек ${stat.trkFps} fps (латентность ${stat.latMs} мс) · ` +
`спекуляция приняла ${stat.specAccepted} токенов за ${stat.specRuns} verify`, 'ok');
log('видео: пан 10 с @256 (спекуляция сама отключается)…');
const pan = await videoPhase(img, { seconds: 10, panSpeed: 40, label: 'пан' });
R.results.video_pan = pan;
log(`пан: ${pan.hz} Гц кейфреймы · трек ${pan.trkFps} fps · префилл ${pan.prefillMs} мс · декод ${pan.decodeMs} мс`, 'ok');
}
return R;
}
function render(R) {
const rows = [
['gpu', R.meta.gpu],
['загрузка', `${R.results.load_s} с` + (R.results.download_mb_s ? ` <small>(${R.results.download_mb_s} МБ/с)</small>` : '')],
['фото-запрос', `${R.results.photo_query_ms} мс <small>(${R.results.photo_boxes} боксов)</small>`],
['префилл (фото)', `${R.results.photo_prefill_ms} мс`],
['декод', R.results.decode_tok_s ? `${R.results.decode_tok_s} ток/с` : `${R.results.photo_decode_ms} мс`],
];
if (R.results.video_static) {
rows.push(['кейфреймы (статика)', `${R.results.video_static.hz} Гц <small>· спекуляция ${R.results.video_static.specAccepted} ток</small>`]);
rows.push(['кейфреймы (пан)', `${R.results.video_pan.hz} Гц`]);
rows.push(['трек', `${R.results.video_pan.trkFps} fps <small>· ${R.results.video_pan.latMs} мс</small>`]);
}
$('rtable').innerHTML = rows
.map(([k, v]) => `<tr><th>${k}</th><td>${v}</td></tr>`).join('');
$('results').style.display = 'block';
}
function makeLog(R) {
const flat = (o, p = '') => Object.entries(o).flatMap(([k, v]) =>
v && typeof v === 'object' ? flat(v, `${p}${k}.`) : [[p + k, v]]);
return [
'falcon-perception-webgpu benchmark',
'==================================',
`date: ${R.meta.date}`,
`gpu: ${R.meta.gpu}`,
`ua: ${R.meta.ua}`,
`features: ${R.meta.features.join(', ')}`,
`weights: falcon-q8-lm4.bin 724MB q8+q4lm (${R.meta.weights_source})`,
'',
'results',
'-------',
...flat(R.results).map(([k, v]) => `${k.padEnd(28)} ${v}`),
'',
'=== RAW JSON ===',
JSON.stringify(R, null, 2),
'',
].join('\n');
}
let lastLog = null;
$('run').addEventListener('click', async () => {
$('run').disabled = true;
try {
const R = await bench();
render(R);
lastLog = { text: makeLog(R), R };
log('готово — скачайте .log ниже и поделитесь', 'hl');
window.__done = { ok: true, ...R.results, gpu: R.meta.gpu };
} catch (e) {
log(`ОШИБКА: ${e.message}`, 'err');
window.__done = { ok: false, error: String(e.message ?? e) };
}
$('run').disabled = false;
});
$('download').addEventListener('click', () => {
if (!lastLog) return;
const arch = (lastLog.R.meta.gpu || 'gpu').replace(/[^a-z0-9-]+/gi, '-');
const a = document.createElement('a');
a.href = URL.createObjectURL(new Blob([lastLog.text], { type: 'text/plain' }));
a.download = `falcon-bench-${arch}-${lastLog.R.meta.date.slice(0, 10)}.log`;
a.click();
URL.revokeObjectURL(a.href);
});
$('copy').addEventListener('click', () => {
if (lastLog) navigator.clipboard.writeText(JSON.stringify(lastLog.R, null, 2));
});
// ?auto=1: старт сразу (headless-приёмка)
if (new URLSearchParams(location.search).has('auto')) $('run').click();
</script>
</body>
</html>