-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathspeedLab.html
More file actions
197 lines (182 loc) · 9.44 KB
/
Copy pathspeedLab.html
File metadata and controls
197 lines (182 loc) · 9.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Speed Lab - pyproc</title>
<link rel="icon" href="../assets/logo.svg">
<meta name="theme-color" content="#0a0f1c">
<link rel="stylesheet" href="demo.css">
<script type="module" src="siteChrome.js"></script>
</head>
<body>
<div class="wrap">
<header class="site">
<a class="logo" href="../"><img class="logoMark" src="../assets/logo.svg" width="22" height="22" alt=""> <span><span class="py">py</span>proc</span></a>
<nav>
<a href="agentSandbox.html">Agent</a>
<a href="machine.html">Machine</a>
<a href="serverDev.html">Server</a>
<a href="speedLab.html" class="on">Speed</a>
<a href="terminal.html">Terminal</a>
<a href="processOs.html">Process OS</a>
<a href="basic.html">Basics</a>
</nav>
<sns-links></sns-links>
</header>
<h1>Web Python speed, measured in the tab</h1>
<p class="lead">This page uses the public surface only: boot a machine, open its worker pool
with <code>machine.proc()</code>, run a single-worker numpy matrix multiply, then run the same
matrix multiply sharded across 4 Python workers. Three warmed samples are summarized as median speedup and p95 latency
before the speed number is accepted.</p>
<div class="panel">
<div class="status" id="st">Booting 4 Python workers with numpy...</div>
<div class="row" style="margin:.6rem 0 .2rem">
<span class="badge ok">4 interpreters</span>
<span class="badge info">4 independent GILs</span>
<span class="badge">Float64 numpy matmul</span>
</div>
<div class="kv" id="stats" style="display:none">
<div class="k">workers</div><div id="workers"></div>
<div class="k">samples</div><div id="samples"></div>
<div class="k">single worker median</div><div><div class="barWrap"><div class="bar" id="barSingle"></div></div><span id="singleMs"></span></div>
<div class="k">sharded median</div><div><div class="barWrap"><div class="bar ok" id="barParallel"></div></div><span id="parallelMs"></span></div>
<div class="k">median speedup</div><div id="speedup"></div>
<div class="k">p95 envelope</div><div id="p95"></div>
<div class="k">correctness</div><div id="correctness"></div>
</div>
<div class="term" id="out" style="margin-top:.7rem">The API under test:
const machine = await boot();
const pool = await machine.proc({ lanes: 4, packages: ["numpy"], setup: "import numpy" });
await pool.matmul(a, b, { parts: 1 });
await pool.matmul(a, b, { parts: 4 });</div>
</div>
<footer class="site">Chromium/Edge. Source: <a href="https://github.com/eddmpython/pyproc">github.com/eddmpython/pyproc</a> 쨌 <a href="https://www.npmjs.com/package/pyproc">npm install pyproc</a></footer>
</div>
<script type="module">
import { boot } from "../index.js";
import { isShardedSpeedBenchGreen, summarizePairedLatencyBench } from "./benchStats.js";
const st = document.getElementById("st"), out = document.getElementById("out");
const el = (id) => document.getElementById(id);
const params = new URLSearchParams(location.search);
const gateMode = params.has("gate");
const INDEX = params.get("indexURL") ? new URL(params.get("indexURL"), location.href).href : undefined;
const log = (m) => { out.textContent += "\n" + m; out.scrollTop = out.scrollHeight; };
const readIntParam = (name, fallback, { min, max }) => {
const raw = params.get(name);
if (raw === null || raw.trim() === "") return fallback;
const value = Number(raw);
if (!Number.isInteger(value) || value < min || value > max) {
throw new Error(`${name} must be an integer from ${min} to ${max}`);
}
return value;
};
const gateReport = (ok, detail = {}) => {
if (!gateMode) return;
fetch("/gateReport", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
ok: !!ok,
scenario: "S1",
checks: [{ name: document.title, pass: !!ok, info: (st.textContent + " | " + out.textContent).slice(-220) }],
...detail,
}),
}).catch(() => {});
};
if (!crossOriginIsolated && "serviceWorker" in navigator && !sessionStorage.getItem("pyprocSpeedCoiTried")) {
try {
sessionStorage.setItem("pyprocSpeedCoiTried", "1");
st.textContent = "Unlocking SharedArrayBuffer (one-time reload)...";
await navigator.serviceWorker.register("../pyprocSw.js?coi=1");
await navigator.serviceWorker.ready;
location.reload();
await new Promise(() => {});
} catch (e) {
log("COI bootstrap failed: " + e);
}
}
function makeMatrix(rows, cols, mul, mod) {
const data = new Float64Array(rows * cols);
for (let i = 0; i < data.length; i++) data[i] = (((i * mul + 3) % mod) / mod) - 0.5;
return { data, rows, cols };
}
function sampleMaxErr(a, b) {
let max = 0;
const stride = Math.max(1, Math.floor(a.length / 2048));
for (let i = 0; i < a.length; i += stride) max = Math.max(max, Math.abs(a[i] - b[i]));
max = Math.max(max, Math.abs(a[a.length - 1] - b[b.length - 1]));
return max;
}
try {
const workers = readIntParam("workers", 4, { min: 1, max: 8 });
const size = readIntParam("size", 768, { min: 128, max: 1536 });
const benchSamples = readIntParam("samples", 3, { min: 3, max: 9 });
st.textContent = `Booting ${workers} Python workers with numpy...`;
const machine = await boot({ indexURL: INDEX });
const timings = {};
let t = performance.now();
const pool = await machine.proc({ lanes: workers, packages: ["numpy"], setup: "import numpy" });
const bootMs = Math.round(performance.now() - t);
timings.bootMs = bootMs;
const readyWorkers = pool.ps().length;
st.textContent = `Ready: ${readyWorkers} workers forked from one snapshot.`;
log(`pool: ${readyWorkers} workers forked from one snapshot in ${bootMs}ms`);
const a = makeMatrix(size, size, 7, 17);
const b = makeMatrix(size, size, 5, 13);
log(`matrix: ${size}x${size} @ ${size}x${size}, Float64`);
log("warming single and sharded lanes...");
const warmA = makeMatrix(128, 128, 7, 17);
const warmB = makeMatrix(128, 128, 5, 13);
await pool.matmul(warmA, warmB, { parts: 1 });
await pool.matmul(warmA, warmB, { parts: workers });
const rows = [];
for (let i = 0; i < benchSamples; i++) {
log(`sample ${i + 1}/${benchSamples}: single worker baseline...`);
t = performance.now();
const single = await pool.matmul(a, b, { parts: 1 });
const singleMs = Math.round(performance.now() - t);
log(`sample ${i + 1}/${benchSamples}: ${workers}-worker sharded run...`);
t = performance.now();
const parallel = await pool.matmul(a, b, { parts: workers });
const parallelMs = Math.round(performance.now() - t);
const maxErr = sampleMaxErr(single.data, parallel.data);
const speedup = +(singleMs / parallelMs).toFixed(2);
rows.push({ singleMs, parallelMs, speedup, maxErr });
log(`sample ${i + 1}: ${speedup}x (${singleMs}ms -> ${parallelMs}ms), max error ${maxErr.toExponential(2)}`);
}
const bench = summarizePairedLatencyBench(rows);
const width = Math.max(8, Math.min(100, Math.round((bench.parallelMedian / bench.singleMedian) * 100)));
el("stats").style.display = "grid";
el("workers").textContent = `${readyWorkers} (forked from one snapshot)`;
el("samples").textContent = `${bench.sampleCount} warmed samples`;
el("singleMs").textContent = ` ${bench.singleMedian}ms (p95 ${bench.singleP95}ms)`;
el("parallelMs").textContent = ` ${bench.parallelMedian}ms (p95 ${bench.parallelP95}ms)`;
el("barSingle").style.width = "100%";
el("barParallel").style.width = `${width}%`;
el("speedup").innerHTML = `<span class="badge ok">${bench.medianSpeedup.toFixed(2)}x</span>`;
el("p95").textContent = `single ${bench.singleP95}ms, shard ${bench.parallelP95}ms`;
el("correctness").textContent = `max sample error ${bench.maxErr.toExponential(2)}`;
st.textContent = `Done. Median ${bench.medianSpeedup.toFixed(2)}x faster across ${bench.sampleCount} samples.`;
log(`median speedup: ${bench.medianSpeedup.toFixed(2)}x (${bench.singleMedian}ms -> ${bench.parallelMedian}ms)`);
log(`p95 envelope: single ${bench.singleP95}ms, shard ${bench.parallelP95}ms`);
log(`correctness: max sample error ${bench.maxErr.toExponential(2)}`);
pool.terminate();
// 게이트 문턱: 완주 게이트는 ?minSpeedup=로 조정 가능하다(기본 2.0). 속도 주장 인증은
// 이 페이지가 아니라 tracked artifact 계약(bench:speed, S1 green 기준 2.0)의 몫이고,
// 공유 CI 러너(4 vCPU)는 물리 코어가 부족해 2.0을 신뢰성 있게 넘지 못한다.
const minSpeedup = Number(new URLSearchParams(location.search).get("minSpeedup") || 2.0);
gateReport(isShardedSpeedBenchGreen(bench, { minMedianSpeedup: minSpeedup }), {
config: { workers, size, benchSamples, indexURL: INDEX || null },
timings,
pool: { workers: readyWorkers },
bench,
});
} catch (e) {
st.textContent = "Failed";
log("Error: " + e);
gateReport(false);
}
</script>
</body>
</html>