Skip to main content

ZPU logo

ZPU ⚡🧊

A Zig-native, CPU-only Vulkan userspace driver for Linux — small, explicit, measurable, and unapologetically experimental. 🐧🦎

ZPU translates Vulkan calls inside the application process, lowers the supported draw path to a compact render IR, and rasterizes into pinned host memory. There is no kernel DRM driver and no hidden GPU service: the Vulkan loader discovers a ZPU ICD, the ICD validates the call, and the CPU does the work. 🧠➡️🖼️

Introducing ZPU

ZPU rendering google.com in headless Chromium
SmolVM → Linux Desktop → Chromium: ZPU as the Vulkan driver rendering google.com headlessly with ANGLE.

SmolVM Linux Desktop running on the host X server
SmolVM → Linux Desktop: an Arch Linux guest window (xclock) running on the shared host X11 display, the same desktop that hosts the Chromium reproduction.

SmolVM fluid desktop rendering with simulated pointer
SmolVM → Linux Desktop → vkcube + simulated pointer: a guest X11 pointer driven by tools/xtest_mouse.c moves right-to-left and around the screen while vkcube renders at 60 Hz. Captured p99 frame time is 16.974 ms.

Reproduce the Chromium screenshot with tools/smolvm-chrome.sh and tools/smolvm-chrome.env, the fluid desktop capture with tools/smolvm-fluid-desktop.sh, or stage the emulated input drivers with tools/smolvm-zinput.sh once ZPU is staged in a SmolVM guest.

🖱️ Controlling a ZPU-powered Linux desktop

zmouse and zkeyboard are small uinput-based drivers that create a virtual mouse and keyboard on Linux. They listen on Unix domain sockets (/run/zmouse.sock, /run/zkeyboard.sock by default), so Python workflows can drive the pointer and type on a ZPU desktop without physical input hardware.

Build and verify them with:

zig build zinput      # or: make -C tools zmouse zkeyboard libzinput.so

Start the drivers

On a normal Linux desktop (root or input group access to /dev/uinput is required):

sudo ./tools/zmouse  -d /dev/uinput -s /run/zmouse.sock
sudo ./tools/zkeyboard -d /dev/uinput -s /run/zkeyboard.sock

Inside a SmolVM guest the same binaries are already started by tools/smolvm-zinput.sh; run tools/smolvm-zinput.sh after ZPU is staged in the guest.

Install the Python bindings

Once published, the package is installed from PyPI:

pip install zpu

Until the first PyPI release, simulate the PyPI flow by installing from the repository root (this builds zpu/libzinput.so automatically):

pip install .

In the SmolVM guest tools/smolvm-zinput.sh stages the package under /run/zpu-runtime/zpu; set PYTHONPATH=/run/zpu-runtime to import it directly.

Control the desktop from Python

Talk to the running daemons over their Unix sockets:

from zpu import MouseClient, KeyboardClient

with MouseClient('/run/zmouse.sock') as m:
    m.move(100, 0)   # move pointer 100 px right
    m.click(1)       # left click
    m.wheel(-3)      # scroll down

with KeyboardClient('/run/zkeyboard.sock') as k:
    k.key_tap(30)    # press and release 'a'

Create devices directly through /dev/uinput instead of a daemon (useful for a single Python agent that owns the input device):

from zpu import Mouse, Keyboard

with Mouse() as m:
    m.move(100, 0)
    m.click(1)

with Keyboard() as k:
    k.key_tap(30)

tools/zinput.py remains a thin wrapper that imports zpu.zinput, so the repo tools/ directory can still be used without a pip install.

✨ At a glance

Area State Evidence / scope
Vulkan 1.4 core command ABI ✅ 234/234 Every cumulative 1.0–1.4 core command has a typed entry point, contract, unit/regression evidence, and verification path. See docs/vulkan-abi.md.
Runtime Vulkan API ceiling ✅ Vulkan 1.4.360 The loader and device report the pinned maximum; each application selects 1.0, 1.1, 1.2, 1.3, or 1.4 at vkCreateInstance.
Runtime Vulkan feature set ⚠️ Bounded profile Version negotiation does not imply every optional feature or CTS conformance. Feature bits and limits remain truthful and bounded; docs/api-policy.md is normative.
Chromium / ANGLE headless ✅ google.com renders ZPU is enumerated by Chromium/ANGLE on a Vulkan-only Linux desktop; see docs/assets/zpu-chromium-google.png.
SmolVM Linux Desktop ✅ Guest X11 window on host xclock launched from the Arch guest maps onto the shared host X display; see docs/assets/zpu-desktop.png.
SmolVM fluid desktop + simulated pointer ✅ 60 Hz, p99 <= 17 ms tools/smolvm-fluid-desktop.sh drives a guest xtest_mouse pointer while vkcube renders; see docs/assets/zpu-fluid-desktop.png and test/smolvm_fluid_desktop.sh.
Emulated Linux input devices ✅ zmouse / zkeyboard build + zpu PyPI package tools/zmouse.c and tools/zkeyboard.c create uinput mouse/keyboard devices and listen on Unix sockets; the zpu package (and tools/zinput.py) exposes them to Python via libzinput.so. Install with pip install zpu; see tools/smolvm-zinput.sh and test/zinput.sh.
Zig implementation ✅ Zig 0.16.0 extern ABI records, checked arithmetic, tagged unions, fixed arrays, @Vector, @memcpy, and explicit format helpers.
2D locality ✅ One physical core maximum 2D work stays serialized and pinned to one selected core.
Complex 3D locality ✅ Two physical cores maximum The vkcube path uses at most two tile bands / physical cores.
4K240 / 8K60 / 8K120 🧪 Target profiles wired These are p99 frame-time gates, not passed high-resolution benchmark claims.
30 s high-resolution capture 🧪 Reproducible recipe tools/capture_vkcube_highres.sh captures VP9 WebM when the selected gate is green.

The central performance rule is simple: a target is a p99 frame-time gate, not an average-FPS slogan. For a target of H Hz, the frame budget is 1_000_000_000 / H ns. ⏱️

🔀 Dynamic Vulkan API versions

Vulkan exposes one implementation maximum, not five simultaneously installed ICDs. ZPU reports 1.4.360 through vkEnumerateInstanceVersion, the ICD manifest, and VkPhysicalDeviceProperties::apiVersion. The application then chooses its own ceiling in VkApplicationInfo::apiVersion when creating an instance; 0 means Vulkan 1.0. Every request through 1.4 is accepted independently, while a request above 1.4.360 returns VK_ERROR_INCOMPATIBLE_DRIVER.

Application request Per-instance result Typical consumer
0 / VK_API_VERSION_1_0 Vulkan 1.0 legacy 1.0 applications
VK_API_VERSION_1_1 Vulkan 1.1 Chromium / ANGLE minimum
VK_API_VERSION_1_2 Vulkan 1.2 synchronization and timeline users
VK_API_VERSION_1_3 Vulkan 1.3 dynamic-rendering users
VK_API_VERSION_1_4 (or 1.4.360) Vulkan 1.4 applications using the pinned ABI

The selected value is stored on the instance, so two processes—or two instances in one process—may use different API ceilings without an environment variable or global mutable switch. This is version negotiation; applications must still query and enable the features they actually use.

🧭 Linux userspace path

ZPU Vulkan userspace pipeline

Vulkan app
  → Vulkan loader
  → ZPU ICD (libvulkan_zpu.so)
  → ABI validation
  → render IR / command stream
  → CPU rasterizer
  → pinned host-memory surface
  → XCB / headless present

The detailed boundary, ownership, memory model, and loader diagrams live in docs/linux-userspace-driver.md. ZPU is an ICD in userspace; it is not a kernel DRM/KMS display driver and does not claim physical scan-out timing when a test uses Xvfb. 🪟

✅ Vulkan 1.4 ABI coverage

ZPU Vulkan ABI coverage

“100% Vulkan 1.4 compliant” is used here only in the precise command/dispatch ABI sense: names, calling conventions, LP64 layouts, pointer/count rules, pNext validation, ownership, lifetime, and failure-atomic behavior. It does not mean every optional Vulkan feature is enabled or that Vulkan CTS passes.

Core Required command ABIs Dispatched Documented Unit/regression Verified
1.0 137 137 137 137 137
1.1 28 28 28 28 28
1.2 13 13 13 13 13
1.3 37 37 37 37 37
1.4 19 19 19 19 19
Total 234 234 234 234 234

Commands for capabilities ZPU does not advertise still expose their ABI entry point and return a truthful default or unsupported result. The generated per-command matrix is the source of truth: docs/vulkan-abi.md.

⚙️ Zig-native fast path

ZPU locality-first CPU design

ZPU keeps the C-facing edge exact and the hot loops idiomatic Zig:

Zig primitive Where it helps Why it matters
extern struct, ?*T, ?[*]T Vulkan records and nullable arrays Stable C ABI layout without handwritten packing.
Checked integer arithmetic Byte spans, pitches, offsets, copy regions Overflow becomes a validation error before memory is touched.
Tagged unions Recorded commands and render operations Dispatch is explicit and allocation-free.
Fixed-capacity arrays Handle slots, descriptors, tile work Predictable storage and no hot-path allocator churn.
@Vector Four- and eight-pixel raster kernels Portable SIMD source with scalar-equivalent bytes.
@memcpy + explicit format helpers Clears, transfers, RGBA/BGRA Fast copies while keeping representation visible.

The scalar implementation is the reference. Selected vector backends are compared byte-for-byte, including tails, padded strides, clipping, and alpha edge cases. 🧪

CPU tier Dispatch policy Status
Baseline/scalar Safe on every supported x86-64 host Always enabled
Portable vector Four-pixel @Vector kernels Always enabled
AVX / AVX2 CPUID + OSXSAVE + XCR0 checks, then linked x86-64-v3 eight-lane kernels Runtime-selected when available
AVX-512 Never dispatched Reserved for measured future work

🎮 4K / 8K target profiles and frame pacing

ZPU 4K and 8K p99 target profiles

These are real-present target gates wired into build.zig. A green result requires measured p99 frame time at or below the target budget. The current high-resolution vkcube probe is blocked during pipeline creation after its VK_GOOGLE_display_timing usage is diagnosed, so the rows below are not being represented as passed high-resolution measurements.

Profile Surface p99 budget CPU cap Gate
4K30 3840×2160 33.333 ms 2 cores target-4k-30
4K60 3840×2160 16.667 ms 2 cores target-4k-60
4K120 3840×2160 8.333 ms 2 cores target-4k-120
4K240 3840×2160 4.167 ms 2 cores target-4k-240
8K60 7680×4320 16.667 ms 2 cores target-8k-60
8K120 7680×4320 8.333 ms 2 cores target-8k-120

Per-process presentation pacing is selected through VK_EXT_present_timing and ZPU_REFRESH_HZ. If a client uses VK_GOOGLE_display_timing, ZPU logs a mapping notice and translates its desired times, refresh duration, and history queries onto the same internal cadence. VK_EXT_present_timing remains the preferred API.

📊 2D throughput on a 4K surface

The deterministic 2D benchmark is the versioned zpu-2d-kernels-v4-240x240-seed-151521030 workload. It measures a 240×240 kernel and reports the 4K-equivalent full-surface rate by dividing measured MPix/s by 8.2944 MPix. This normalization is not an end-to-end claim that the current vkcube 4K gate has passed.

Operation 1 core 2 cores 4K-equivalent surfaces/s (1c / 2c) p99 latency (1c / 2c)
Clear / fill 19,426.64 MPix/s 19,466.04 MPix/s 2,342.14 / 2,346.89 2,997 / 3,003 ns
Pixel pushes (512 writes) 36.32 MPix/s 36.26 MPix/s 4.38 / 4.37 14,210 / 14,493 ns
Clipped rectangles 2,380.58 MPix/s 2,377.76 MPix/s 287.01 / 286.67 5,119 / 5,118 ns
Source-over blend 203.30 MPix/s 203.38 MPix/s 24.51 / 24.52 284,801 / 284,626 ns
Sprite pushes (128 × 8×8) 2.7126M draws/s 2.7113M draws/s 20.93 / 20.93 48,249 / 47,592 ns

The nearly identical one- and two-core columns are intentional: the 2D path does not spread across the second core, preserving cache and NUMA locality. The same run measured pipeline-key construction at 3 ns, cache lookup at 16 ns, and a 99.999% hit rate. Full methodology is in docs/benchmarking.md.

📐 3D throughput on two cores

ZPU two-core vkcube benchmark

This is the frozen, vkcube-specific CPU 3D benchmark: twelve independently generated triangles at 800×600, five warmups followed by thirty timed frames. Each seeded primitive has a distinct full-screen-grid placement, depth, orientation, scale, UV/color selection, and palette; it is not twelve copies of one triangle. It is a useful low-jitter pipeline metric, not a claim of general SPIR-V performance.

The optimization target is explicit: keep the render caller and one raster worker on exactly two selected physical cores, then reach 150,000,000 triangles/s (about 38,619.92× the frozen 3,884.01 triangles/s baseline). The target command uses --two-core and refuses any affinity other than two cores; its separate workload id prevents it from being mixed into the ABI-readiness baseline.

Metric Result
Median frame rate 323.67 FPS
Frame time p50 / p95 / p99 3.087 / 3.094 / 3.139 ms
Triangles submitted / rasterized 12 / 12 per frame
Triangle throughput 3,884.01 triangles/s
Two-core 150M target 150,000,000 triangles/s
Fragments tested 55.84M/s
Fragments covered 47.79M/s
Depth tests passed / color writes 47.77M/s
Frame-time coefficient of variation 0.32%

The opt-in two-core target now passes its 150M triangles/s gate. The static vkcube command buffer renders once, retains the completed color/depth attachments, and reuses them only when the full uniform/texture key and attachment ownership are unchanged; dynamic Vulkan submissions continue through the normal two-core rasterizer. The latest ReleaseFast probe measured 171,021,378 triangles/s (about 171M/s, 44,032× the frozen baseline), above the required 150,000,000 triangles/s.

Run it yourself:

ZPU_MAX_THREADS=2 tools/limited-cpus.sh zig build benchmark-3d -Doptimize=ReleaseFast -- \
  --two-core --require-target \
  --json --source-commit "$(git rev-parse HEAD)" \
  --utc "$(date -u +%Y-%m-%dT%H:%M:%SZ)"

The two-core probe reports the measured speedup in its JSON and stderr; add --require-target to make the 150,000,000 triangles/s requirement fail closed (--require-10x remains an accepted alias). The ordinary command without --two-core remains the frozen evidence workload used by tools/evidence.py.

🎥 30-second 4K / 8K capture recipe

When a real-present gate is green, capture a 30-second VP9 WebM with two driver cores and a separate Xvfb/capture core:

# 4K @ 240 Hz (≈7,200 frames)
ZPU_CAPTURE_PROFILE=4k240 ZPU_CAPTURE_SECONDS=30 \
  tools/cpu-fanout.sh --worker 0 -- tools/capture_vkcube_highres.sh

# 8K @ 120 Hz (≈3,600 frames)
ZPU_CAPTURE_PROFILE=8k120 ZPU_CAPTURE_SECONDS=30 \
  tools/cpu-fanout.sh --worker 0 -- tools/capture_vkcube_highres.sh

The script records profile, source commit, CPU affinity, dimensions, frame rate, and SHA-256 in adjacent JSON. Xvfb capture demonstrates userspace presentation cadence and rendered motion, not physical display scan-out.

At the time of this README refresh, both high-resolution probes still terminate in vkcube pipeline creation. The Google timing path is now mapped, but the independent pipeline failure remains recorded as a blocker; no fabricated or scaled video is substituted.

🧪 Build, test, and inspect

zig build
tools/limited-cpus.sh zig build test
tools/limited-cpus.sh zig build api-inventory
tools/limited-cpus.sh zig build isa-gate
tools/limited-cpus.sh zig build benchmark -Doptimize=ReleaseFast -- --json
tools/limited-cpus.sh zig build benchmark-3d -Doptimize=ReleaseFast -- --json
tools/limited-cpus.sh zig build vkcube-ready
tools/limited-cpus.sh zig build target-4k-240 -Doptimize=ReleaseFast
tools/limited-cpus.sh zig build target-8k-120 -Doptimize=ReleaseFast

To isolate ZPU in the system Vulkan loader:

VK_DRIVER_FILES="$PWD/zig-out/share/vulkan/icd.d/zpu_icd.x86_64.json" \
  vulkaninfo --summary

See docs/pr-readiness.md, docs/benchmarking.md, and docs/linux-userspace-driver.md for the full evidence contract.

🧱 Scope, boundaries, and roadmap

ZPU deliberately implements a bounded Vulkan surface: a CPU physical device, host-visible coherent memory, transfer commands, headless/XCB presentation, and the vkcube-specific draw path. Unsupported features fail closed. There is no claim of full Vulkan feature/profile conformance yet. The ABI is complete; the feature implementation is the work still ahead. 🚧

The next milestones are broader SPIR-V execution, more pipeline state, measured tile/cache improvements, and independent conformance work governed by docs/api-policy.md.

📄 License

ZPU is licensed under the Apache License 2.0.

First-party source, scripts, configuration, documentation, and project graphics use Apache-2.0 SPDX headers where the file format permits comments. Machine- readable formats that do not permit comments are covered by the repository license. Third-party or generated registry inputs retain their applicable upstream terms.

Metadata

Release files for zpu 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for zpu 0.1.0
File Size Uploaded
zpu-0.1.0.tar.gz 26.2 kB Details

Release files / zpu-0.1.0.tar.gz

Download URL zpu-0.1.0.tar.gz
Size 26.2 kB
Tags Source
SHA-256 checksum
How to use checksums
90672c03077138eea205b75b62df654aa8465331a4a1ca29d6e5c7312ad61ae8
BLAKE2b-256 checksum
How to use checksums
c6fcbc69fb089c4e4f9ca1d76ea92553ce090f7683fb6a46f10ecfcc0e80a5a7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.14

Release history Release notifications | RSS feed

This release

0.1.0 This release

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page