live scene: per-model lanes (A100 inference), not per-host
The scene's framing was wrong. It's about the A100 doing live model predictions, not about per-host telemetry collection. Lanes now key on `model` instead of `host_id`; the callout leads with model name + A100 latency, demoting host/profile to secondary metadata. Stats line reads "N models · X infer/sec · last window from <host>" instead of "N hosts · model: X". Demo synthesis updated to match: 5 trained models cycle through predictions on rotating fleet windows, each model with its own accuracy + latency profile (KNN fast/loose, BERT slow/precise) so the lanes visually differ. Article prose reframes the scene as side-by-side model agreement, the natural read of per-model lanes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
804220d7f6
commit
5533043b02
2 changed files with 53 additions and 45 deletions
|
|
@ -2121,19 +2121,20 @@ def train_nn(*, model, X_train, y_train, X_val, y_val,
|
|||
rebuildLegend();
|
||||
})();
|
||||
|
||||
// ── Live detections (scene: live) ─────────────────────────────
|
||||
// Per-host swim lanes of recent prediction cells, plus a "latest
|
||||
// detection" callout. New cells push in from the right; lane caps
|
||||
// at MAX_CELLS so memory stays bounded across long sessions. When
|
||||
// ── A100 live inference (scene: live) ─────────────────────────
|
||||
// Per-MODEL swim lanes of recent prediction cells. Each row is a
|
||||
// model the A100 is running inference with; each cell is one
|
||||
// ten-second-window prediction colored by predicted phase. When
|
||||
// the producer also sends `actual`, mismatch cells get a hatched
|
||||
// overlay and the running hit-rate updates.
|
||||
// overlay and the running hit-rate updates. The "latest" callout
|
||||
// below the lanes leads with the model name + predicted phase.
|
||||
(function () {
|
||||
const lanesEl = document.getElementById('live-lanes');
|
||||
const latestEl = document.getElementById('live-latest');
|
||||
const statsHosts = document.getElementById('live-stats-hosts');
|
||||
const statsRate = document.getElementById('live-stats-rate');
|
||||
const statsModel = document.getElementById('live-stats-model');
|
||||
const statsAcc = document.getElementById('live-stats-acc');
|
||||
const statsModels = document.getElementById('live-stats-hosts'); // id kept for HTML compat
|
||||
const statsRate = document.getElementById('live-stats-rate');
|
||||
const statsHost = document.getElementById('live-stats-model'); // id kept; meaning flipped
|
||||
const statsAcc = document.getElementById('live-stats-acc');
|
||||
if (!lanesEl) return;
|
||||
|
||||
const MAX_CELLS = 60;
|
||||
|
|
@ -2142,27 +2143,27 @@ def train_nn(*, model, X_train, y_train, X_val, y_val,
|
|||
const lanes = new Map();
|
||||
const eventTimes = [];
|
||||
let totalCorrect = 0, totalLabeled = 0;
|
||||
let lastModel = null;
|
||||
let lastHost = null;
|
||||
|
||||
function ensureLane(hostId) {
|
||||
if (lanes.has(hostId)) return lanes.get(hostId);
|
||||
function ensureLane(modelName) {
|
||||
if (lanes.has(modelName)) return lanes.get(modelName);
|
||||
const row = document.createElement('div');
|
||||
row.className = 'live-lane';
|
||||
row.innerHTML = `
|
||||
<div class="live-lane-host" title="${hostId}">${hostId}</div>
|
||||
<div class="live-lane-host" title="${modelName}">${modelName}</div>
|
||||
<div class="live-lane-cells"></div>`;
|
||||
lanesEl.appendChild(row);
|
||||
const lane = { row, cellsEl: row.querySelector('.live-lane-cells'), cells: [] };
|
||||
lanes.set(hostId, lane);
|
||||
lanes.set(modelName, lane);
|
||||
return lane;
|
||||
}
|
||||
|
||||
function paintCell(d) {
|
||||
const lane = ensureLane(d.host_id);
|
||||
const lane = ensureLane(d.model || 'unknown');
|
||||
const cell = document.createElement('div');
|
||||
cell.className = `live-cell ${d.predicted}`;
|
||||
if (d.actual && d.actual !== d.predicted) cell.classList.add('miss');
|
||||
cell.title = `${d.host_id} · w${d.window_idx ?? '?'} · ${d.predicted}` +
|
||||
cell.title = `${d.model ?? '?'} · ${d.host_id ?? '?'} w${d.window_idx ?? '?'} · ${d.predicted}` +
|
||||
(d.actual ? ` (truth: ${d.actual})` : '') +
|
||||
(d.confidence != null ? ` · conf ${d.confidence.toFixed(2)}` : '');
|
||||
lane.cellsEl.appendChild(cell);
|
||||
|
|
@ -2187,10 +2188,11 @@ def train_nn(*, model, X_train, y_train, X_val, y_val,
|
|||
latestEl.innerHTML = `
|
||||
<div class="live-phase-block ${d.predicted}">${phaseLabel}</div>
|
||||
<div class="live-meta">
|
||||
<div class="live-meta-host">${d.host_id}</div>
|
||||
<div class="live-meta-line">profile: <code>${d.profile ?? '—'}</code></div>
|
||||
<div class="live-meta-line">model: <code>${d.model ?? '—'}</code>${
|
||||
d.latency_ms != null ? ` · ${d.latency_ms.toFixed(0)} ms` : ''
|
||||
<div class="live-meta-host">model: ${d.model ?? '—'}${
|
||||
d.latency_ms != null ? ` · A100 ${d.latency_ms.toFixed(1)} ms` : ''
|
||||
}</div>
|
||||
<div class="live-meta-line">window from: <code>${d.host_id ?? '—'}</code>${
|
||||
d.profile ? ` · profile <code>${d.profile}</code>` : ''
|
||||
}</div>
|
||||
<div class="live-meta-line">${truthLine}</div>
|
||||
</div>
|
||||
|
|
@ -2204,10 +2206,10 @@ def train_nn(*, model, X_train, y_train, X_val, y_val,
|
|||
const now = Date.now();
|
||||
while (eventTimes.length && now - eventTimes[0] > RATE_WINDOW_MS) eventTimes.shift();
|
||||
const rate = (eventTimes.length / (RATE_WINDOW_MS / 1000)).toFixed(1);
|
||||
statsHosts.textContent = `${lanes.size} host${lanes.size === 1 ? '' : 's'}`;
|
||||
statsRate.textContent = `${rate} / sec`;
|
||||
statsModel.textContent = `model: ${lastModel ?? '—'}`;
|
||||
statsAcc.textContent = totalLabeled > 0
|
||||
statsModels.textContent = `${lanes.size} model${lanes.size === 1 ? '' : 's'}`;
|
||||
statsRate.textContent = `${rate} infer / sec`;
|
||||
statsHost.textContent = `last window: ${lastHost ?? '—'}`;
|
||||
statsAcc.textContent = totalLabeled > 0
|
||||
? `hit-rate: ${(100 * totalCorrect / totalLabeled).toFixed(0)}% (${totalCorrect}/${totalLabeled})`
|
||||
: `hit-rate: —`;
|
||||
}
|
||||
|
|
@ -2224,13 +2226,13 @@ def train_nn(*, model, X_train, y_train, X_val, y_val,
|
|||
lanes.clear();
|
||||
eventTimes.length = 0;
|
||||
totalCorrect = 0; totalLabeled = 0;
|
||||
lastModel = null;
|
||||
latestEl.innerHTML = '<div class="live-latest-empty">awaiting <code>live_detection</code> events from the inference loop</div>';
|
||||
lastHost = null;
|
||||
latestEl.innerHTML = '<div class="live-latest-empty">awaiting <code>live_detection</code> events from the A100 inference loop</div>';
|
||||
updateStats();
|
||||
}
|
||||
function paintDetection(d) {
|
||||
eventTimes.push(Date.now());
|
||||
if (d.model) lastModel = d.model;
|
||||
if (d.host_id) lastHost = d.host_id;
|
||||
if (d.actual) {
|
||||
totalLabeled++;
|
||||
if (d.actual === d.predicted) totalCorrect++;
|
||||
|
|
@ -2239,47 +2241,53 @@ def train_nn(*, model, X_train, y_train, X_val, y_val,
|
|||
paintLatest(d);
|
||||
}
|
||||
function handleDetection(d) {
|
||||
if (!d.host_id || !d.predicted) return;
|
||||
if (!d.model || !d.predicted) return;
|
||||
paintDetection(d);
|
||||
}
|
||||
|
||||
on('live_detection', m => {
|
||||
if (!m.host_id || !m.predicted) return;
|
||||
if (!m.model || !m.predicted) return;
|
||||
cachedReal.push(m);
|
||||
if (cachedReal.length > REAL_CACHE_CAP) cachedReal.shift();
|
||||
if (!demoActive) paintDetection(m);
|
||||
});
|
||||
|
||||
// Synthetic demo: 5 hosts, walk through phases, ~92% accuracy.
|
||||
// Synthetic demo: A100 cycling through 5 trained models, scoring
|
||||
// windows from rotating fleet hosts. Each model has its own
|
||||
// accuracy + latency profile (KNN faster but less accurate;
|
||||
// BERT slower but more accurate) so the lanes look distinct.
|
||||
let demoTimer = null;
|
||||
function demoStart() {
|
||||
demoActive = true;
|
||||
clearLanes();
|
||||
if (demoTimer) clearInterval(demoTimer);
|
||||
const HOSTS = [
|
||||
{ id: 'elliott-lab', profile: 'cpu-saturate', phaseIdx: 0 },
|
||||
{ id: 'elliott-thinkpad', profile: 'ransomware-lite', phaseIdx: 0 },
|
||||
{ id: 'k-gamingcom', profile: 'bursty-c2', phaseIdx: 1 },
|
||||
{ id: 'smelliott', profile: 'fork-bomb', phaseIdx: 0 },
|
||||
{ id: 'gosling', profile: 'crypto-miner', phaseIdx: 2 },
|
||||
const MODELS = [
|
||||
{ name: 'knn', acc: 0.78, latMs: 0.4, phaseIdx: 0 },
|
||||
{ name: 'rnn', acc: 0.86, latMs: 1.8, phaseIdx: 0 },
|
||||
{ name: 'gru', acc: 0.91, latMs: 2.4, phaseIdx: 1 },
|
||||
{ name: 'lstm', acc: 0.93, latMs: 3.2, phaseIdx: 2 },
|
||||
{ name: 'bert', acc: 0.95, latMs: 8.6, phaseIdx: 0 },
|
||||
];
|
||||
const HOSTS = ['elliott-lab', 'elliott-thinkpad', 'k-gamingcom'];
|
||||
const PROFILES = ['cpu-saturate', 'ransomware-lite', 'bursty-c2', 'fork-bomb', 'crypto-miner'];
|
||||
const PHASES = ['clean', 'armed', 'infecting', 'infected_running', 'dormant'];
|
||||
let counter = 0;
|
||||
demoTimer = setInterval(() => {
|
||||
const host = HOSTS[counter % HOSTS.length];
|
||||
const m = MODELS[counter % MODELS.length];
|
||||
counter++;
|
||||
if (Math.random() < 0.18) host.phaseIdx = (host.phaseIdx + 1) % PHASES.length;
|
||||
const truth = PHASES[host.phaseIdx];
|
||||
const right = Math.random() < 0.92;
|
||||
if (Math.random() < 0.18) m.phaseIdx = (m.phaseIdx + 1) % PHASES.length;
|
||||
const truth = PHASES[m.phaseIdx];
|
||||
const right = Math.random() < m.acc;
|
||||
const predicted = right
|
||||
? truth
|
||||
: PHASES[(host.phaseIdx + 1 + Math.floor(Math.random() * 4)) % PHASES.length];
|
||||
: PHASES[(m.phaseIdx + 1 + Math.floor(Math.random() * 4)) % PHASES.length];
|
||||
handleDetection({
|
||||
host_id: host.id, profile: host.profile,
|
||||
host_id: HOSTS[counter % HOSTS.length],
|
||||
profile: PROFILES[counter % PROFILES.length],
|
||||
predicted, actual: truth,
|
||||
confidence: 0.62 + Math.random() * 0.36,
|
||||
model: 'lstm',
|
||||
latency_ms: 3 + Math.random() * 4,
|
||||
model: m.name,
|
||||
latency_ms: m.latMs + (Math.random() - 0.5) * m.latMs * 0.3,
|
||||
episode_id: 'demo',
|
||||
window_idx: counter,
|
||||
t_wall: Date.now() / 1000,
|
||||
|
|
|
|||
|
|
@ -1314,6 +1314,6 @@
|
|||
</article>
|
||||
</div>
|
||||
|
||||
<script src="/static/dashboard.js?v=2a8b8c03"></script>
|
||||
<script src="/static/dashboard.js?v=48576a1a"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue