Free for researchers
Anyone with a verifiable institutional affiliation — university, public lab, K-12 school — runs free with no monthly cap. Class projects, replication studies, and grad work all qualify.
Everything you need to submit compute jobs, embed the worker widget on your site, and integrate with the coordinator API. Write kernels in plain Python with py2wgsl, submit from a terminal over SSH, or embed the worker on your site. Guides for submitters and site owners, plus a WebSocket protocol reference.
Add the Warppool worker to any site in three steps. Visitors see a consent prompt before any compute runs. See live demos to preview how it looks on a real page.
The coordinator exposes your site-specific snippet at /api/embed-config. Fetch it with curl or visit the URL in a browser.
curl https://warppool.tech/api/embed-config
Drop this into your page's <body>, ideally near the closing tag. The script loads async and never blocks rendering.
<!-- in your <body>, ideally near the end --> <script src="https://warppool.tech/w.js" api-key="2835ef562fcdfd84eeb061d9c7e9d1ad" data-budget="3" data-priority="idle" data-jobs="research-grants,acme" async ></script> <!-- defaults to a consent strip in the corner; swap for inline placement: --> <div id="warppool-consent"></div>
Tune the worker via data attributes on the script tag.
3. The worker measures real utilization and backs off.idle (default) runs only when the visitor isn't interacting. active runs continuously. visible-only pauses in background tabs.research-grants is the default pool. Add your site's slug to spend earned credits on your own jobs.Embed widgets require an API key to connect workers to the coordinator. Keys are tied to a domain and validated on every WebSocket connection.
Keys are generated via the admin API. You need the ADMIN_TOKEN to create one.
# Generate a key for your domain $ curl -X POST https://warppool.tech/api/admin/keys \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"site": "example.com"}' > { "key": "a1b2c3d4e5f6...", "site": "example.com", "created_at": "2026-06-15T10:00:00" } # List all keys (values redacted) $ curl https://warppool.tech/api/admin/keys \ -H "Authorization: Bearer $ADMIN_TOKEN"
Add the key as an api-key attribute on the embed script tag. The loader script (w.js) reads it and passes it to the iframe.
<script src="https://warppool.tech/w.js" api-key="a1b2c3d4e5f6..." async ></script>
On each WebSocket connection, the coordinator checks that the request origin matches the domain registered to the key. Subdomains are supported: www.example.com matches a key registered to example.com. Requests from non-matching origins are rejected.
worker.html) connect without a key for internal use and testing. Only the embed widget path requires an API key.
Submit a WGSL compute kernel and a chunk manifest. The coordinator distributes chunks across available browser workers and returns the aggregated result. No SDK — everything is HTTPS and JSON.
A compute shader that reads chunk parameters from a uniform buffer and writes output to a storage buffer. If you've written a CUDA kernel, the mental model is identical; the syntax is closer to Rust.
A JSON document describing how the job splits into chunks — one parameter set per chunk. The coordinator hands one entry to each worker.
How to combine per-chunk results into a single answer. Built-ins: sum, concat, max, min, argmax, topk. Or supply a second WGSL kernel for custom reductions.
Default k=2 with a third opinion on disagreement. Higher k for safety-critical runs; k=1 for cheap exploratory sweeps.
Without a deadline the coordinator picks the cheapest workers. Add one to recruit faster nodes and finish by a wall-clock target.
@group(0) @binding(0) var<uniform> params: Params; @group(0) @binding(1) var<storage, read_write> hits: atomic<u32>; struct Params { seed_start: u32, num_samples: u32, samples_per_thread: u32, } // PCG hash — fast, good statistical properties fn pcg(x: u32) -> u32 { var s = x * 747796405u + 2891336453u; var w = ((s >> ((s >> 28u) + 4u)) ^ s) * 277803737u; return (w >> 22u) ^ w; } @compute @workgroup_size(256) fn main(@builtin(global_invocation_id) gid: vec3<u32>) { let tid = gid.x; var state = pcg(pcg(params.seed_start + tid)); var local_hits: u32 = 0u; for (var i: u32 = 0u; i < params.samples_per_thread; i++) { state = pcg(state); let x = f32(state) / 4294967296.0; state = pcg(state); let y = f32(state) / 4294967296.0; if (x*x + y*y < 1.0) { local_hits += 1u; } } atomicAdd(&hits, local_hits); }
# 1. Upload the kernel $ curl https://warppool.tech/api/kernels \ -F "src=@monte_carlo_pi.wgsl" > { "kernel_id": "wgsl-pi-7f3" } # 2. Build a chunk manifest — 100 chunks of # 100M samples each, distinct seed ranges $ python -c " import json print(json.dumps({ 'chunks': [ {'seed_start': i * (1<<24), 'num_samples': 100_000_000, 'samples_per_thread': 256} for i in range(100) ], }))" > manifest.json # 3. Submit the job $ curl https://warppool.tech/api/jobs \ -H "Authorization: Bearer $WARPPOOL_TOKEN" \ -F "kernel_id=wgsl-pi-7f3" \ -F "manifest=@manifest.json" \ -F "reducer=sum" \ -F "redundancy=2" > { "job_id": "job-9a2c", "queued_at": "2026-05-27T14:02:11Z", "chunks": 100, "est_seconds": 38 } # 4. Stream results back $ curl https://warppool.tech/api/jobs/job-9a2c/stream event: chunk_done data: {"chunk":0,"hits":78539701} event: chunk_done data: {"chunk":1,"hits":78540234} ... event: job_done data: {"pi":3.14159265}
Anyone with a verifiable institutional affiliation — university, public lab, K-12 school — runs free with no monthly cap. Class projects, replication studies, and grad work all qualify.
Commercial workloads are metered per millisecond of completed, verified compute. Failed and retried chunks cost nothing. Indicative rate: ~$0.04 / GPU-hour.
Don't want to hand-write WGSL? py2wgsl compiles a restricted subset of Python into complete WGSL compute modules. Your kernel is an ordinary Python function — it is parsed with the ast module and translated, never executed. It's a standalone package on PyPI with zero runtime dependencies, usable anywhere WGSL runs.
Install from PyPI. The base package is pure stdlib; the optional [gpu] extra pulls in wgpu so you can execute compiled kernels straight from Python.
$ pip install py2wgsl # zero runtime dependencies $ pip install "py2wgsl[gpu]" # + wgpu, to run kernels from Python
Annotate a function's parameters with the provided types, write the body in plain Python, and call compile_kernel. You get back the full WGSL module plus the binding layout metadata you need to set up bind groups.
from py2wgsl import ( Array, f32, compile_kernel, global_id, array_length, ) # Each scalar param becomes a uniform field; # each Array becomes a storage buffer. def scale_add( scale: f32, offset: f32, values: Array[f32], result: Array[f32], ): i = global_id() if i < array_length(result): result[i] = values[i] * scale + offset kernel = compile_kernel(scale_add, workgroup_size=64)
# The complete WGSL module — feed straight # to device.createShaderModule() >>> print(kernel.wgsl) @group(0) @binding(0) var<uniform> params: Params; @group(0) @binding(1) var<storage, read> values: ... @group(0) @binding(2) var<storage, read_write> result: ... @compute @workgroup_size(64) fn main(...) { ... } # Binding layout metadata for your bind group >>> kernel.buffers [UniformField('scale', ...), BufferBinding('result', read_write=True), ...]
There is also compile_kernel_source(src) to compile from a source string, and python -m py2wgsl.demo prints two worked examples — including a Monte Carlo π kernel like the one this network runs.
Parameters map to bindings by their type annotation, in declaration order. Buffers the kernel writes to get read_write access; the rest are read. The entry point is always fn main with @builtin(global_invocation_id).
Params uniform struct at @binding(0). (float/int also accepted.)var), annotated and augmented assignmentsif/elif/else, while, for i in range(...) with constant step, break, continue, bare returna if c else b (compiles to select)math.*: sqrt, sin, cos, floor, min, max, clamp, pow, abs, … plus math.pi / math.e / math.tauf32(x), i32(x), u32(x)global_id(), local_id(), workgroup_id(), array_length(buf), barrier(), storage_barrier()/ on two integers is a compile error (use // or cast with f32()); there are no implicit int/float conversions; conditions must be bool (no truthiness); and there are no atomics, vectors, textures, or user-defined function calls yet. Don't put secrets in kernel source — the workers that run it can read it.
Prefer a terminal to REST? Register a public key from your dashboard and submit jobs over SSH with the warp CLI, the way you'd queue work on an HPC cluster. You keep your private key; the server only ever stores the public half.
Open the SSH Access tab on your dashboard, give the key a name, and paste the contents of your public key file (e.g. ~/.ssh/id_ed25519.pub). Accepted types: ssh-ed25519, ssh-rsa, and ecdsa-sha2-nistp256/384/521. Keys are identified by their SHA256: fingerprint and can be revoked any time. Only public keys are stored — private key material never touches the server.
Each account gets a stable gateway username of the form wp-<8 chars>, shown in the SSH Access tab alongside the host. Connect with your registered key:
$ ssh wp-3f9a2c1b@ssh.warppool.tech
warp CLI.
Run warp submit over the connection — inline as a one-shot command, or interactively once you're on the gateway.
# One-shot: submit a Monte Carlo pi job $ ssh wp-3f9a2c1b@ssh.warppool.tech \ warp submit --samples 100000000 --name my-job # Or bring your own compiled kernel + manifest $ warp submit --kernel kernel.wgsl \ --manifest manifest.json --reducer sum > job-9a2c queued · 100 chunks · est 38s
Job products land under jobs/<job-id>/ on the gateway. Pull them down with scp once the run completes:
$ scp wp-3f9a2c1b@ssh.warppool.tech:jobs/job-9a2c/output.json .
All endpoints are served by the coordinator at your deployment's base URL. Responses are JSON.
Create a new compute job. For Monte Carlo pi estimation, send total_samples. For Q-learning grid world, send job_type: "q_learning" with grid parameters.
curl -X POST https://warppool.tech/api/jobs \
-H "Content-Type: application/json" \
-d '{"total_samples": 1000000}'
{
"job_id": "a1b2c3d4",
"status": "running"
}
List all jobs with status, chunk progress, and pi estimate (if applicable).
[
{
"job_id": "a1b2c3d4",
"total_samples": 1000000,
"status": "completed",
"pi_estimate": 3.14182,
"chunks_total": 4,
"chunks_completed": 4,
"created_at": "2026-05-30T12:00:00",
"completed_at": "2026-05-30T12:00:14"
}
]
Get full details for a single job, including per-chunk status and worker assignments.
{
"job_id": "a1b2c3d4",
"job_type": "pi",
"total_samples": 1000000,
"status": "completed",
"pi_estimate": 3.14182,
"chunks": [
{
"chunk_id": "c001",
"worker_id": "w1a2b3c4",
"num_samples": 250000,
"status": "completed"
}
],
"created_at": "2026-05-30T12:00:00",
"completed_at": "2026-05-30T12:00:14"
}
List all connected workers with GPU info and chunk completion counts.
[
{
"worker_id": "w1a2b3c4",
"status": "idle",
"gpu_vendor": "apple",
"gpu_device": "Apple M2",
"gpu_architecture": "apple-7",
"chunks_completed": 14,
"connected_at": "2026-05-30T11:58:00"
}
]
Returns the embed script tag snippet configured for your coordinator's URL.
{
"embed_html": "<script src=\"https://warppool.tech/w.js\" api-key=\"YOUR_API_KEY\" async></script>"
}
Workers connect to the coordinator at /ws. All messages are JSON with a type field. Here's the full message flow.
| Direction | Type | Payload |
|---|---|---|
| Server → Worker | welcome | {worker_id} — assigned unique 8-char hex ID |
| Worker → Server | gpu_capabilities | {data: {vendor, device, architecture, ...}} |
| Server → Worker | compute_chunk | {job_id, chunk_id, seed_start, num_samples, samples_per_thread} |
| Worker → Server | chunk_result | {job_id, chunk_id, hits} |
| Server → Worker | ping | Keepalive, sent every 30 seconds |
| Worker → Server | pong | Keepalive reply |
{
"type": "compute_chunk",
"job_id": "a1b2c3d4",
"chunk_id": "c001",
"seed_start": 0,
"num_samples": 250000,
"samples_per_thread": 1000
}
{
"type": "chunk_result",
"job_id": "a1b2c3d4",
"chunk_id": "c001",
"hits": 196382
}
Submitters can't trust volunteer machines; volunteers can't trust submitter kernels. The protocol is built so neither side has to.
Every chunk goes to 2–3 independent workers. Results are compared within numeric tolerance. Disagreement triggers re-runs on disjoint nodes; persistent disagreement quarantines the kernel for human review.
A fraction of dispatched chunks have known answers seeded by the coordinator. Workers don't know which. Returning the wrong answer to a canary downranks the worker silently.
Workers run inside the browser's own GPU sandbox. No filesystem access, no native APIs, no persistent state across reloads. Bounded memory per chunk. Visitor closes the tab — the worker is gone.
Every dispatch and result for your job is appended to a log keyed by worker ID and chunk hash. You can replay it, verify the reduction, and check that no worker's output was silently dropped.
Warppool runs on any browser with modern GPU-compute support — Chrome 113+, Edge 113+, and Firefox (Nightly today). Safari support is on the way. Warppool automatically detects whether a visitor's browser and device can contribute and silently skips those that can't — no errors, no broken pages.
Yes. Warppool taps your browser's GPU, which browsers only expose over a secure connection, so production sites need HTTPS. During local development, localhost is exempt from this requirement.
Yes. The coordinator is a Python/FastAPI app. Run it with uvicorn server.main:app --host 0.0.0.0 --port 8000 or deploy the provided Docker image. Workers connect to whatever URL you point them at.
The coordinator assigns non-overlapping seed ranges to each chunk. The WGSL shader uses a PCG hash PRNG seeded from each range, so results are deterministic and reproducible given the same seed.
The coordinator detects the dropped WebSocket and marks the chunk as unfinished. It gets reassigned to the next available worker. No results are lost.
The open-source coordinator has no built-in rate limiting. In a production deployment, add rate limiting at the reverse proxy layer (nginx, Cloud Run, etc.) based on your capacity.
Sites running the tag today see consent rates of 45–65% — higher than any cookie banner. The ask is small and the reward is legible ("we fund research grants" rather than "we sell your data"). Anonymized aggregate consent stats are published by category so publishers can benchmark.
The script weighs 11 KB gzipped, loads async, and doesn't run a kernel until consent is granted and the page has been idle for at least 4 seconds. By default the worker uses 3% GPU on a 16ms budget — well below the threshold where users notice.
The worker only attaches if the device's calibration benchmark clears a minimum threshold (roughly: modern phone or anything with discrete graphics). Slow devices never see the consent strip. Battery-powered devices that aren't on AC get a lower default budget.
The tag collects no personal data and processes no identifiable signals — so the regulations that apply to ad networks don't apply here. We still surface a one-click stop and a transparency page because that's the right thing to do.