Client-Server Model
This page covers the client-server architecture used for competition evaluation, where participant code runs in an isolated container. For how Flopscope wraps NumPy internally, see How Flopscope Works.
Use this page to understand how Flopscope's client-server architecture works and why it exists.
You will learn:
- Why Flopscope uses a client-server model for competition evaluation
- How arrays, operations, and budgets flow between client and server
- How to choose between the local library and client-server packages
Why client-server?
In competition evaluation, participant code runs in an isolated container that cannot import NumPy directly. This prevents participants from bypassing FLOP counting by calling NumPy functions outside flopscope.
The client-server model enforces this isolation:
How it works
-
Server runs the real flopscope library backed by NumPy. It stores all arrays, enforces budgets, and counts FLOPs.
-
Client exposes the same public imports (
import flopscope as flopsplusimport flopscope.numpy as fnp) and proxies every operation to the server over ZMQ (msgpack-encoded messages). -
Arrays stay on the server. The client holds lightweight
RemoteArrayhandles that reference server-side data. When you callfnp.einsum(...), the client sends the operation and handle IDs to the server, which executes it and returns a new handle. -
Budget enforcement happens server-side. The client cannot manipulate FLOP counts.
Communication protocol
- Transport: ZMQ (REQ/REP pattern)
- Serialization: msgpack with binary-safe array payloads
- Default endpoint:
ipc:///tmp/flopscope.sock(configurable viaFLOPSCOPE_SERVER_URL) - Timeout: 30 seconds per request
Authoritative budget summaries
Local and remote summary dictionaries have the same public schema and accounting meaning. Remote summaries are read-only snapshots of authoritative server state. The global remote view is the current server summary epoch. Unsupported peers raise an actionable capability error rather than returning partial or zero-filled data.
The one-participant-process epoch is a WHEST evaluator deployment contract. A
conforming evaluator must launch the server with --token-fd, retain the minted
token in its trusted control channel, perform a post-smoke, pre-scoring reset,
and couple server replacement to participant-process replacement. Under that
contract, smoke work is excluded and participant code cannot reset the epoch
because it never receives the token. A standalone tokenless local/development
server intentionally permits lifecycle control operations and is not a hardened
participant boundary.
The server builds a summary in O(K) time for the returned operation and
optional namespace buckets, independent of historical call count. Flat and
namespaced requests have identical totals; by_namespace=True adds only the
namespace mapping. Plain and Rich budget_summary() output render the same
mapping returned by budget_summary_dict().
A summary call's measured inspection overhead is committed after its snapshot and appears in the next snapshot or a later final-close snapshot. A final close response cannot include its own post-boundary serialization recursively.
BudgetContext.summary_dict() returns the canonical empty mapping before first
entry, performs an authoritative resource RPC while live, and returns a
defensive copy of its cached final mapping after close. Live scalar timing
properties deliberately preserve their established None/zero-until-close
behavior. After close, all scalar timing properties are read from that same
cached mapping.
Global budget_summary_dict()/budget_summary() returns the unchanged
authoritative server snapshot. A client-owned BudgetContext preserves its
existing end-to-end timing meaning by replacing only the four top-level timing
fields with a decomposition of library-owned local wall/dispatch spans and the
server's cumulative compute metadata. The closed scalar timing properties are
read from that same mapping, so the two public views cannot diverge. These
client-only measurements are never sent to the server, cannot alter FLOPs,
operations, namespaces, budgets, the session summary, or scoring, and are not
participant claims accepted by the authority.
API compatibility
Code written for the local library works unchanged with the client:
# This code works with BOTH the local library and the client
import flopscope as flops
import flopscope.numpy as fnp
with flops.BudgetContext(flop_budget=10**6) as budget:
x = fnp.zeros((256,))
W = fnp.random.randn(256, 256)
h = fnp.einsum('ij,j->i', W, x)
print(budget.summary())When to use which
| Use case | Package | Install path |
|---|---|---|
| Development, testing, research | flopscope (local library) | uv add flopscope (or uv sync from repo for dev) |
| Competition evaluation, sandboxed environments | flopscope-client + flopscope-server | Docker containers |
Three packages in this repo
| Package | Location | Description |
|---|---|---|
flopscope | src/flopscope/ | Local library — full NumPy backend, direct execution |
flopscope-client | flopscope-client/ | Client proxy — no NumPy dependency, forwards ops to server |
flopscope-server | flopscope-server/ | Server — runs real flopscope, manages sessions and arrays |
Related pages
- Running with Docker — set up client-server locally
- Contributor Guide — source-checkout commands for local development
- Quickstart — getting started with the local library