Skip to content

Build a vendor-neutral Vulkan GPU backend #126

Description

@SnowCheetos

Goal

Add a production-oriented, vendor-neutral GPU backend that implements
virtio_accel_core::Accelerator over Vulkan compute, executing device-neutral TOSA 1.0 programs on
any conformant Vulkan 1.x implementation without leaking Vulkan types into the portable crates.

The crate name and support row are already staked: README.md carries a Vulkan / Planned row
(added in 4d1b6638), and the workspace table reserves virtio-accel-vulkan.

Why this backend is different from the existing four

Core ML, OpenVINO, Hexagon, and XDNA each bind one vendor's runtime to one vendor's silicon. Vulkan
is the first target where one adapter covers many vendors — RADV (AMD), ANV (Intel), NVIDIA,
Mali, and software implementations — because the vendor-specific part lives below a standard API
that the host OS already ships. Three consequences worth stating up front, because they change the
shape of the work relative to the XDNA map (#78):

  1. A hardware-free CI lane is possible. Mesa's lavapipe is a conformant software Vulkan
    implementation. Unlike every other backend, the conformance suite and the numerical corpus can
    plausibly run in CI on a GPU-less runner, in the same spirit as the OpenVINO CPU plugin lane
    (.github/workflows/ci.yml, job openvino-host-test). This should be a first-class goal, not an
    afterthought — it is the strongest verification asset this backend has.
  2. Nonblocking completion is native. vkGetFenceStatus / vkGetSemaphoreCounterValue are
    genuinely nonblocking status reads. Both HRX (Decide the execution-model mapping and event-bridge spec #85) and QAIRT needed a dedicated worker thread to
    bridge a blocking synchronize into poll_event; Vulkan may not need one at all. That has to be
    proven, not assumed, but if it holds it removes the single most delicate piece of machinery the
    other two backends carry.
  3. Device loss is specified. VK_ERROR_DEVICE_LOST is an explicit, normative signal with
    defined propagation, which maps directly onto the instance-poisoning / BackendDiscardRequired
    contract instead of relying on a watchdog whose timeout must be reasoned against a kernel TDR.

Note the deliberate overlap with OpenVINO: it already enumerates Intel GPUs. This backend's claim is
vendor neutrality and no vendor runtime dependency, not "the first GPU." On an Intel host both
may be present and OpenVINO will likely be faster; that is fine and should be documented rather than
arbitrated in code.

Scope

  • Add a separate host-native crate virtio-accel-vulkan, structurally following
    virtio-accel-openvino (the template named by Wayfinder map: AMD XDNA NPU backend #78) — lib.rs / lower.rs / native.rs /
    ffi.rs, with the portable admission surface always compiled.
  • Probe Vulkan at build time and compile a RuntimeUnavailable placeholder otherwise, following the
    va_openvino / va_hexagon / va_xdna cfg pattern (va_vulkan). Explicit configuration only;
    no scanning of standard locations.
  • Enumerate physical devices and report an honest identity, advertising AcceleratorClass::GPU
    — which already exists (crates/virtio-accel-core/src/lib.rs:47), so no portable-crate change
    is required
    for device class.
  • Accept virtio-accel-tosa artifacts, validate/analyze with the shared TOSA layer, and lower a
    documented initial operator/dtype subset to SPIR-V compute.
  • Implement contexts, provider-owned buffers, programs, queues, submissions, events, transfers,
    release, and recovery through the shared Accelerator contract.
  • Bind the exact provider allocation for program-visible buffers. No submission-time bounce
    buffers; read_buffer/write_buffer remain the only copy boundaries.
  • Preserve rejected-vs-indeterminate submission/release semantics, stable nonblocking polling,
    timeout behavior, and device-loss reporting. Advertise EVENT_CANCELLATION only if it can be
    implemented honestly (Vulkan has no cancel primitive — expect not to advertise it, matching
    Hexagon and XDNA).
  • Confine unsafe FFI to an audited boundary; add SAFETY.md and register the audited exception in
    ci/check-release-policy.py (which today lists the coreml/openvino/hexagon/xdna confinement
    markers at lines 79-91).
  • Add a backend-local end-to-end example; update the workspace table, support matrix, portability
    guide, architecture notes, performance evidence, and release policy.

Open decisions (candidate wayfinder tickets)

Unlike #75, whose implementation tips were decided constraints backed by prior research, the
items below are genuinely open. They are the natural decision tickets for a wayfinder map in the
shape of #78. Recommended framing for each is given, but none is settled.

  1. Vulkan binding strategy. The workspace convention is a small hand-written ffi.rs declaring
    only the validated subset (OpenVINO's is 171 lines; XDNA's Decide crate layout, HRX FFI boundary, and build-time probe #83 decided "declare only the
    hardware-validated HRX subset"). Vulkan's surface is far larger than any prior target, so the
    honest options are: a hand-written minimal subset (convention-consistent, more work), ash
    (thin, unsafe, widely used, adds a dependency to a std adapter crate only), or a safe wrapper
    like vulkano (heavier, more opinionated). This is the highest-leverage decision in the map
    because it sets the size of the audited unsafe surface and therefore the shape of SAFETY.md.
  2. Program representation: TOSA→SPIR-V, and whether SPIR-V is also accepted directly.
    ArtifactFormat is an opaque NonZeroU32 that each backend may define for itself — TOSA is
    0x544f_5341 (crates/virtio-accel-tosa/src/artifact.rs:7), Core ML defines its own
    0x434d_4c50, the mock backend 0x5641_4d4b. So accepting SPIR-V under a backend-defined
    format id is architecturally permitted. Recommendation: TOSA-only for v1 (it is what the
    shared corpus and conformance oracles are built on, and it keeps the guest device-neutral); treat
    a SPIR-V-direct format as a deliberate follow-on, decided on its own merits, not smuggled in.
  3. Lowering mechanism — and whether a compiler subprocess is needed at all. XDNA required a
    bounded out-of-process compiler (Decide the compiler-helper subprocess contract #84) because aiecc is Python. Vulkan likely does not:
    per-operator SPIR-V compute shaders can be authored once, checked into the crate as precompiled
    SPIR-V, and specialized at load_program via specialization constants for shapes/tiling —
    no toolchain on the serving host, no subprocess, no Python. If that holds it is a significant
    simplification over both XDNA and Hexagon, and it should be validated early because it determines
    whether this backend has a build-time toolchain dependency at all.
  4. First numerical tier. Candidate: FP32 as the base (universally supported), FP16 gated on
    VK_KHR_shader_float16_int8, INT8 gated on VK_KHR_shader_integer_dot_product. FP8 has no
    portable Vulkan path and should be rejected loudly at admission. Per the principle recorded in
    Decide the first advertised numerical tier #82: guest-chosen tiers, never host knobs — and reject rather than silently widen.
  5. Memory-domain mapping. Vulkan memory types map unusually cleanly onto the three domains:
    HostHOST_VISIBLE | HOST_COHERENT, DeviceDEVICE_LOCAL, Shared
    DEVICE_LOCAL | HOST_VISIBLE (resizable-BAR). Worth confirming that ReBAR availability is
    probed and SHARED_MEMORY advertised only when the memory type actually exists, rather than
    assumed.
  6. Execution/event model. Whether vkGetFenceStatus alone satisfies nonblocking poll_event
    without a worker thread, how a bounded preallocated command-buffer/fence pool is sized so
    guest-controlled work cannot create unbounded host queue depth, and how finite timeouts are
    handled pre-admission.
  7. CI strategy. Which software implementation (lavapipe vs SwiftShader), which distro
    packages pin it, and how much of the mandatory conformance set plus the numerical corpus can run
    without a GPU. Also: whether a documented manual hardware lane is enough for real-GPU evidence,
    as Build the AMD XDNA NPU backend #75 blessed for XDNA rather than exposing a self-hosted runner on a public repo.

Acceptance criteria

  • The workspace builds and tests on hosts with no Vulkan SDK; the native path enables only when
    detected or explicitly requested.
  • On a Vulkan host, the backend enumerates a device as AcceleratorClass::GPU and completes the
    documented TOSA example end to end.
  • Every mandatory case in virtio-accel-conformance passes; capability-conditional skips carry
    explicit reasons.
  • Every advertised operator/dtype case from the shared numerical corpus executes and matches its
    oracle.
  • Tests prove exact direct binding of caller-owned allocations, with no submission-time staging.
  • Tests cover malformed/unsupported artifact rejection, incompatible bindings, finite timeouts,
    stable terminal polling, VK_ERROR_DEVICE_LOST handling, and rejected/indeterminate ownership
    paths.
  • Native resources are released exactly once across teardown, completion, failure, and discard;
    diagnostics return to baseline after each conformance case.
  • A GPU-less CI lane runs the mandatory conformance set against a software Vulkan
    implementation
    , and the required driver/runtime versions plus reproducible hardware commands
    are documented for the real-GPU lane.
  • The README Vulkan row changes from Planned only for capabilities demonstrated by tests.

Non-goals

  • Changes to the wire ABI or the portable Accelerator contract to mirror a Vulkan concept.
  • External-memory / DMA-BUF import, cross-process sharing, or new synchronization primitives.
    The negotiated external-memory handoff is designed but deliberately unassigned (Design a negotiated external-memory handoff extension for heterogeneous schedules #113, closed;
    docs/plans/issue-113-external-memory-handoff.md — "design complete; protocol values and
    implementation intentionally unassigned") and its implementation is tracked separately by protocol: implement the negotiated external-memory handoff extension #119.
    A Vulkan backend is the most natural eventual consumer of that extension — VK_KHR_external_memory_fd
    is exactly the mechanism it anticipates — but v1 of this backend must ride the Protocol 1.0
    baseline (device-owned buffers plus bounded explicit transfers) and must not front-run it.
  • Graphics/rendering, swapchains, or scanout. This is a compute backend; anything display-facing
    is separate work with its own ownership and synchronization questions.
  • Full TOSA operator coverage, or low-precision tiers beyond what is implemented and tested.
  • CPU fallback inside the Vulkan backend. A software Vulkan implementation is a test target
    selected by the host's ICD, never a silent fallback chosen by this crate.

Suggested phasing

The XDNA precedent is to land a scaffold first and grow it under decision tickets. Mirroring
crates/virtio-accel-xdna (landed dd3edf1b / 06954b77 for #86):

  1. ScaffoldCargo.toml, build.rs probe setting va_vulkan, always-compiled lower.rs
    holding the advertised Target constants, an InitError::RuntimeUnavailable placeholder,
    README.md, SAFETY.md outline, #![forbid(unsafe_code)] while no FFI exists.
  2. FFI + lifecycle — instance/device/queue creation, buffers, and a passthrough/identity program
    proving artifact load, direct binding, coherence, nonblocking completion, and teardown.
  3. First operator tier — MATMUL, then the rest of the advertised subset, with golden
    hardware-free lowering tests.
  4. Conformance + CI — the software-Vulkan lane, then documented hardware evidence.

Motivation beyond this repository

A vendor-neutral Vulkan backend is what lets a virtio-accel consumer reach an AMD GPU without
anyone porting AMD's driver stack. In the kore microkernel's case the intended shape is: real
Linux in a guest with the GPU passed through, stock amdgpu + Mesa/RADV inside it, and this crate
as an ordinary host process terminating the accelerator protocol — no AMD-specific adapter, and the
same crate covering Intel and Mali hosts unchanged. That consumer is out of scope here and imposes
no requirement on this design; it is recorded only as the reason vendor neutrality is worth paying
for over an AMD-specific backend.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area: backendAccelerator traits, mock backend, and provider conformancearea: verificationSecurity, fuzzing, model tests, and performance evidenceenhancementNew feature or requestepicParent issue grouping a coherent body of workhelp wantedExtra attention is neededkind: designSpecification or architectural design work

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions