flopscope-server 0.4.3__tar.gz → 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {flopscope_server-0.4.3 → flopscope_server-0.6.0}/PKG-INFO +2 -2
- {flopscope_server-0.4.3 → flopscope_server-0.6.0}/pyproject.toml +2 -2
- {flopscope_server-0.4.3 → flopscope_server-0.6.0}/src/flopscope_server/__init__.py +1 -1
- {flopscope_server-0.4.3 → flopscope_server-0.6.0}/src/flopscope_server/_request_handler.py +20 -4
- {flopscope_server-0.4.3 → flopscope_server-0.6.0}/src/flopscope_server/_server.py +2 -2
- {flopscope_server-0.4.3 → flopscope_server-0.6.0}/tests/test_server.py +41 -0
- {flopscope_server-0.4.3 → flopscope_server-0.6.0}/tests/test_session.py +18 -0
- {flopscope_server-0.4.3 → flopscope_server-0.6.0}/.gitignore +0 -0
- {flopscope_server-0.4.3 → flopscope_server-0.6.0}/README.md +0 -0
- {flopscope_server-0.4.3 → flopscope_server-0.6.0}/src/flopscope_server/__main__.py +0 -0
- {flopscope_server-0.4.3 → flopscope_server-0.6.0}/src/flopscope_server/_array_store.py +0 -0
- {flopscope_server-0.4.3 → flopscope_server-0.6.0}/src/flopscope_server/_comms_tracker.py +0 -0
- {flopscope_server-0.4.3 → flopscope_server-0.6.0}/src/flopscope_server/_protocol.py +0 -0
- {flopscope_server-0.4.3 → flopscope_server-0.6.0}/src/flopscope_server/_session.py +0 -0
- {flopscope_server-0.4.3 → flopscope_server-0.6.0}/tests/test_array_store.py +0 -0
- {flopscope_server-0.4.3 → flopscope_server-0.6.0}/tests/test_bugfixes_round2.py +0 -0
- {flopscope_server-0.4.3 → flopscope_server-0.6.0}/tests/test_bugfixes_round3.py +0 -0
- {flopscope_server-0.4.3 → flopscope_server-0.6.0}/tests/test_comms_tracker.py +0 -0
- {flopscope_server-0.4.3 → flopscope_server-0.6.0}/tests/test_new_types.py +0 -0
- {flopscope_server-0.4.3 → flopscope_server-0.6.0}/tests/test_protocol.py +0 -0
- {flopscope_server-0.4.3 → flopscope_server-0.6.0}/tests/test_request_handler.py +0 -0
- {flopscope_server-0.4.3 → flopscope_server-0.6.0}/tests/test_version_handshake.py +0 -0
- {flopscope_server-0.4.3 → flopscope_server-0.6.0}/uv.lock +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flopscope-server
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Backend server for flopscope client-server architecture
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Requires-Python: >=3.10
|
|
7
|
-
Requires-Dist: flopscope==0.
|
|
7
|
+
Requires-Dist: flopscope==0.6.0
|
|
8
8
|
Requires-Dist: msgpack>=1.0.0
|
|
9
9
|
Requires-Dist: numpy<2.5.0,>=2.0.0
|
|
10
10
|
Requires-Dist: pyzmq>=26.0.0
|
|
@@ -4,13 +4,13 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "flopscope-server"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.6.0"
|
|
8
8
|
description = "Backend server for flopscope client-server architecture"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
11
|
license = "MIT"
|
|
12
12
|
dependencies = [
|
|
13
|
-
"flopscope==0.
|
|
13
|
+
"flopscope==0.6.0",
|
|
14
14
|
"numpy>=2.0.0,<2.5.0",
|
|
15
15
|
"pyzmq>=26.0.0",
|
|
16
16
|
"msgpack>=1.0.0",
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
6
|
import re
|
|
7
|
+
from time import perf_counter_ns
|
|
7
8
|
from typing import Any
|
|
8
9
|
|
|
9
10
|
import numpy as np
|
|
@@ -86,6 +87,20 @@ class RequestHandler:
|
|
|
86
87
|
|
|
87
88
|
def __init__(self, session: Session) -> None:
|
|
88
89
|
self._session = session
|
|
90
|
+
self._kernel_ns: int = 0
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def kernel_ns(self) -> int:
|
|
94
|
+
"""Pure numpy-kernel nanoseconds accumulated during the last handle()."""
|
|
95
|
+
return self._kernel_ns
|
|
96
|
+
|
|
97
|
+
def _run_kernel(self, fn, *args, **kwargs):
|
|
98
|
+
"""Invoke a numpy compute call, attributing only its wall to kernel time."""
|
|
99
|
+
t0 = perf_counter_ns()
|
|
100
|
+
try:
|
|
101
|
+
return fn(*args, **kwargs)
|
|
102
|
+
finally:
|
|
103
|
+
self._kernel_ns += perf_counter_ns() - t0
|
|
89
104
|
|
|
90
105
|
# ------------------------------------------------------------------
|
|
91
106
|
# Public entry point
|
|
@@ -96,6 +111,7 @@ class RequestHandler:
|
|
|
96
111
|
|
|
97
112
|
The ``request["op"]`` field determines which handler is invoked.
|
|
98
113
|
"""
|
|
114
|
+
self._kernel_ns = 0
|
|
99
115
|
try:
|
|
100
116
|
op = request["op"]
|
|
101
117
|
|
|
@@ -249,7 +265,7 @@ class RequestHandler:
|
|
|
249
265
|
raise ValueError("__getitem__ requires [handle, key]")
|
|
250
266
|
arr = self._resolve_arg(args[0])
|
|
251
267
|
key = self._decode_index_key(args[1])
|
|
252
|
-
result = arr[key]
|
|
268
|
+
result = self._run_kernel(lambda: arr[key])
|
|
253
269
|
return self._pack_result(result)
|
|
254
270
|
|
|
255
271
|
# ------------------------------------------------------------------
|
|
@@ -267,7 +283,7 @@ class RequestHandler:
|
|
|
267
283
|
dtype = raw_args[1] if len(raw_args) > 1 else kwargs.get("dtype")
|
|
268
284
|
if isinstance(dtype, bytes):
|
|
269
285
|
dtype = dtype.decode("utf-8")
|
|
270
|
-
result = arr.astype
|
|
286
|
+
result = self._run_kernel(arr.astype, dtype)
|
|
271
287
|
return self._pack_result(result)
|
|
272
288
|
|
|
273
289
|
# Generator method calls: op is "Generator.<method>" with the remote
|
|
@@ -285,14 +301,14 @@ class RequestHandler:
|
|
|
285
301
|
gen = self._resolve_arg(raw_args[0])
|
|
286
302
|
rest = [self._resolve_arg(a) for a in raw_args[1:]]
|
|
287
303
|
resolved_kwargs = {k: self._resolve_arg(v) for k, v in kwargs.items()}
|
|
288
|
-
result = getattr(gen, method)
|
|
304
|
+
result = self._run_kernel(getattr(gen, method), *rest, **resolved_kwargs)
|
|
289
305
|
return self._pack_result(result)
|
|
290
306
|
|
|
291
307
|
func = _get_flopscope_func(op)
|
|
292
308
|
resolved_args = [self._resolve_arg(a) for a in raw_args]
|
|
293
309
|
resolved_kwargs = {k: self._resolve_arg(v) for k, v in kwargs.items()}
|
|
294
310
|
|
|
295
|
-
result = func
|
|
311
|
+
result = self._run_kernel(func, *resolved_args, **resolved_kwargs)
|
|
296
312
|
|
|
297
313
|
return self._pack_result(result)
|
|
298
314
|
|
|
@@ -172,7 +172,7 @@ class FlopscopeServer:
|
|
|
172
172
|
|
|
173
173
|
# --- Record comms overhead ---
|
|
174
174
|
comms_ns = (t1 - t0) + (t3 - t2)
|
|
175
|
-
compute_ns =
|
|
175
|
+
compute_ns = self._handler.kernel_ns
|
|
176
176
|
|
|
177
177
|
self._session.comms_tracker.record_request(
|
|
178
178
|
bytes_received=len(raw),
|
|
@@ -222,7 +222,7 @@ class FlopscopeServer:
|
|
|
222
222
|
t3 = perf_counter_ns()
|
|
223
223
|
|
|
224
224
|
comms_ns = (t1 - t0) + (t3 - t2)
|
|
225
|
-
compute_ns =
|
|
225
|
+
compute_ns = 0 # session creation is not a numpy kernel
|
|
226
226
|
self._session.comms_tracker.record_request(
|
|
227
227
|
bytes_received=0,
|
|
228
228
|
bytes_sent=len(response_bytes),
|
|
@@ -308,3 +308,44 @@ def test_normalize_msg_kwargs_handle_dict():
|
|
|
308
308
|
assert isinstance(out_val, dict)
|
|
309
309
|
assert "__handle__" in out_val
|
|
310
310
|
assert out_val["__handle__"] == "a5"
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
# ---------------------------------------------------------------------------
|
|
314
|
+
# kernel_ns timing tests (pure numpy kernel attribution)
|
|
315
|
+
# ---------------------------------------------------------------------------
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def test_compute_time_is_kernel_only():
|
|
319
|
+
"""A compute op records kernel time that does not exceed the full handle() wall."""
|
|
320
|
+
import time
|
|
321
|
+
|
|
322
|
+
import numpy as np
|
|
323
|
+
from flopscope_server._request_handler import RequestHandler
|
|
324
|
+
from flopscope_server._session import Session
|
|
325
|
+
|
|
326
|
+
session = Session(flop_budget=10**12)
|
|
327
|
+
handler = RequestHandler(session)
|
|
328
|
+
h = session.store_array(np.ones((256, 256)))
|
|
329
|
+
|
|
330
|
+
t0 = time.perf_counter_ns()
|
|
331
|
+
handler.handle({"op": "dot", "args": [h, h], "kwargs": None})
|
|
332
|
+
handle_ns = time.perf_counter_ns() - t0
|
|
333
|
+
|
|
334
|
+
assert handler.kernel_ns > 0
|
|
335
|
+
assert handler.kernel_ns <= handle_ns
|
|
336
|
+
session.close()
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def test_fetch_contributes_no_kernel():
|
|
340
|
+
"""A fetch op is data movement, not a numpy kernel — kernel_ns stays 0."""
|
|
341
|
+
import numpy as np
|
|
342
|
+
from flopscope_server._request_handler import RequestHandler
|
|
343
|
+
from flopscope_server._session import Session
|
|
344
|
+
|
|
345
|
+
session = Session(flop_budget=10**12)
|
|
346
|
+
handler = RequestHandler(session)
|
|
347
|
+
h = session.store_array(np.ones((8, 8)))
|
|
348
|
+
|
|
349
|
+
handler.handle({"op": "fetch", "id": h})
|
|
350
|
+
assert handler.kernel_ns == 0
|
|
351
|
+
session.close()
|
|
@@ -253,3 +253,21 @@ def test_budget_remaining_reflects_context():
|
|
|
253
253
|
s = Session(flop_budget=100_000)
|
|
254
254
|
assert s.budget_remaining == 100_000
|
|
255
255
|
s.close()
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
# ---------------------------------------------------------------------------
|
|
259
|
+
# Server contract: close() surfaces recorded compute time
|
|
260
|
+
# ---------------------------------------------------------------------------
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def test_close_surfaces_recorded_compute_time(session):
|
|
264
|
+
"""close()'s comms_summary carries the compute time the client reads as backend."""
|
|
265
|
+
session.comms_tracker.record_request(
|
|
266
|
+
bytes_received=10,
|
|
267
|
+
bytes_sent=20,
|
|
268
|
+
comms_overhead_ns=100,
|
|
269
|
+
compute_time_ns=5000,
|
|
270
|
+
is_fetch=False,
|
|
271
|
+
)
|
|
272
|
+
summary = session.close()
|
|
273
|
+
assert summary["comms_summary"]["total_compute_time_ns"] == 5000
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|