Skip to content

Wayfinder map: vendor-neutral Vulkan GPU backend #154

Description

@SnowCheetos

Destination

Issue #126 closed: virtio-accel-vulkan merged to main, passing every mandatory case in
virtio-accel-conformance against a software Vulkan implementation in a GPU-less CI lane, executing
its advertised TOSA tier end-to-end on at least one real GPU with results matching the shared
oracles, and README/docs/CI claiming exactly what tests demonstrate. A crates.io release is not part
of this map.

Notes

  • Domain: Rust workspace implementing the virtio-accel protocol; the backend implements
    virtio_accel_core::Accelerator. Read docs/backend-implementer-guide.md, docs/portability.md,
    docs/architecture.md, docs/threat-model.md, and CONTRIBUTING.md before resolving any ticket.
    virtio-accel-openvino is the structural template (lib.rs / lower.rs / ffi.rs /
    native.rs); virtio-accel-xdna is the scaffold precedent (build probe, placeholder constructors,
    uninhabited handle enums, a SAFETY.md that grows with the FFI).
  • Issue Build a vendor-neutral Vulkan GPU backend #126's scope, acceptance criteria, and non-goals are decided constraints for this map. In
    particular: TOSA-only artifacts for v1, direct binding with no submission-time bounce buffers,
    compile at load_program and never during submit, reject-don't-fallback, no advertised
    EVENT_CANCELLATION (Vulkan has no cancel primitive), software Vulkan as a test target only, and
    no wire-ABI or portable-contract changes.
  • Two constraints seeded at map creation, each ratified by its ticket below:
    1. The data plane stays graph-shaped (lean). GPUs operate NPU-like — whole-program artifacts,
      flat slot bindings, one event per submission — implemented in the most performant way the API
      allows, not awkwardly forced. Ticket 1 ratifies or falsifies this.
    2. Vulkan is bound through a vetted crate, not raw Vulkan-Headers and not a hand-written
      ffi.rs.
      Vulkan's surface is far larger than any prior target's validated subset, and a
      maintained generated binding is exactly the borrow-then-indigenize case. Scouting evidence
      below; ticket 2 ratifies the pick and pins versions.
  • Execution-carrying map: a ticket may carry implementation when it might as well ("prove by
    building"). Code lands as branch-per-ticket PRs to main through the standard CI gates.
  • Reference hardware for the real-GPU lane is not yet chosen (see fog).

Vulkan FFI scouting (evidence recorded 2026-08-27)

Constraint: no link-time dependency on Vulkan-Headers or the loader; probe and load at runtime.
Gates from deny.toml: licenses within {Apache-2.0, BSD-2-Clause, MIT, Unicode-3.0},
multiple-versions = "deny", unmaintained direct dependencies denied, crates.io only.

Crate Latest Verdict
ash 0.38.0+1.3.281 (2024-04); repo active, master tracks Vulkan 1.1–1.4 Adopt. Thin unsafe bindings generated from vk.xml; the default loaded feature dynamically loads the platform Vulkan loader at runtime via libloading — no Vulkan-Headers, no link-time dependency, no SDK at build time. no_std-capable, MIT OR Apache-2.0, dependency-light (survives multiple-versions = "deny"), the ecosystem-standard raw binding (wgpu sits on it), 33M+ downloads. Caveat to manage in ticket 2: crates.io release cadence is slow; pin exactly.
vulkanalia 0.35.0 (2026-02) Viable fallback with the same thin philosophy and daily auto-generated vk.xml tracking; smaller ecosystem than ash. Apache-2.0. The named alternative if ash vetting fails.
vulkano 0.35.2 (2025-08) Reject. Safe high-level wrapper with its own object/synchronization model and a large dependency and audit surface; its automatic synchronization duplicates decisions this backend must own explicitly (direct binding, bounded pools, submission admission).
erupt discontinued Reject. Unmaintained; fails the deny.toml advisories gate.
wgpu Reject. WebGPU abstraction, not Vulkan control; hides the memory-type and queue decisions the contract requires; large transitive tree against multiple-versions = "deny".
gpu-allocator 0.28.0 (2025-09) Companion candidate, decided in tickets 2–3: pure-Rust VMA-style suballocator (MIT OR Apache-2.0) if maxMemoryAllocationCount forces suballocation; not adopted by default.
vk-mem Reject for v1: binds C++ VMA; gpu-allocator covers the need without a C++ build.
rspirv 0.13.0 (2026-03, gfx-rs) Candidate, decided in ticket 4: SPIR-V builder if runtime emission is chosen; authoring-time-only otherwise. Apache-2.0.
spirv-tools / naga Dev-dependency candidates only, for CI validation of generated or checked-in SPIR-V (ticket 4).

Route

Tickets are spun out as sub-issues when claimed; until then this list is the map order.

  • 1. Grilling: data-plane posture for GPU-class hardware (kind: design). The protocol data
    plane is graph-shaped: one submission = one opaque program + flat slot-to-range bindings + a
    relative timeout, yielding one event; no command buffers, descriptor sets, barriers, fences,
    dispatch geometry, or cross-submission edges are guest-visible. Decide between (a) reshaping
    the data plane toward GPU-native concepts to better accommodate GPUs and other novel hardware,
    or (b) betting on the graph shape: GPUs operate NPU-like and the graph-shaped data plane is
    ratified as the spec's permanent shape rather than an NPU-era provisional. Lean recorded at
    map creation: (b).
    Evidence for the lean: changing the submission payload is a category-3
    wire change (new protocol major, docs/wire-abi.md section 9); the GPU-flavored primitives
    already have deliberately reserved, unassigned feature bits (MULTI_QUEUE, EVENT_QUEUE,
    EXTERNAL_MEMORY, TIMELINE_FENCES); Design a negotiated external-memory handoff extension for heterogeneous schedules #113 is the sanctioned — deliberately expensive — path
    when reshaping is warranted, and it chose completion-gating over fences; Hexagon (Build the Qualcomm Hexagon NPU backend #77, Qualcomm - Broaden numeric types coverage #95,
    Qualcomm Hexagon: reach shared TOSA operator parity #96) is the precedent for making unlike hardware operate graph-shaped with zero protocol
    change; and nothing in Vulkan compute requires guest-visible command primitives for
    steady-state efficiency (pre-recorded command buffers, specialization constants, pooled
    descriptors, and fence polling are all provider-internal). Falsifier, so the bet stays honest:
    if tickets 6/8 prove steady-state submission cannot meet the performance budgets without
    guest-visible batching or dependency edges, that evidence feeds a protocol-change proposal
    through the reserved bits — never a backend workaround. Resolution: an ADR-style design note;
    any 1.0-document wording it needs is erratum-class (category 1).
  • 2. Grilling: ratify the Vulkan binding strategy (kind: design). Ratify ash per the
    scouting above: pin the exact version; loaded feature only (runtime libloading, no
    link-time Vulkan); run the full deny.toml and feature-powerset gates; decide whether
    gpu-allocator enters now or waits for evidence of allocation-count pressure. Decide the
    audit posture for third-party unsafe: existing UNSAFE_AUDITS entries in
    ci/check-release-policy.py cover in-crate ffi.rs/native.rs; ash moves the raw
    declarations out of tree, so SAFETY.md must instead pin the crate version, the entry points
    actually used, and the invariants this crate owns around them (one owner + Drop per handle,
    status checked before out-parameters are trusted). docs/release-policy.md names three unsafe
    exceptions today; adding the fourth needs the discussion and evidence CONTRIBUTING requires,
    before a patch.
  • 3. Research: minimum Vulkan baseline and honest capability probe (wayfinder:research).
    Choose the minimum API version (1.2 vs 1.3 — lavapipe and current Mesa support 1.3;
    synchronization2 simplifies barriers) and the required-feature set. Map memory domains:
    Host maps to HOST_VISIBLE|HOST_COHERENT persistently mapped; Device maps to
    DEVICE_LOCAL with staging confined to write_buffer/read_buffer (this would be the first
    real backend to advertise DEVICE_LOCAL_MEMORY — today only the mock does; Hexagon and XDNA
    reject Device); Shared maps to a DEVICE_LOCAL|HOST_VISIBLE memory type, advertised only
    when it actually exists (ReBAR/UMA probe), never assumed. Reconcile
    maxMemoryAllocationCount (spec minimum 4096) with advertised DeviceLimits — limits must
    stay aggregation-safe (XDNA lesson: advertised_limits_are_aggregation_safe); dedicated
    allocations with honest low limits vs suballocation (still DIRECT_BINDING-legal: binding at
    an offset copies nothing). Report actual alignment (minStorageBufferOffsetAlignment,
    nonCoherentAtomSize).
  • 4. Grilling: program representation and lowering mechanism (kind: design). TOSA-only for
    v1 (Build a vendor-neutral Vulkan GPU backend #126 decision 2). Decide between per-operator SPIR-V compute shaders checked into the
    crate and specialized at load_program via specialization constants (no toolchain on the
    serving host, no subprocess) versus rspirv emission at load. Either way: VkShaderModule
    and VkComputePipeline creation happens at load_program, never at submit; retained
    pipelines, command buffers, and descriptor pools are charged against
    ArtifactRef::resident_bytes; decide the VkPipelineCache policy. Security property to
    preserve and record: guest bytes never reach the driver's shader compiler — it consumes only
    crate-authored SPIR-V parameterized by validated shapes (threat-model
    transient-compile-budget clause).
  • 5. Grilling: first advertised numerical tier (kind: design). Candidate: FP32 base
    (universal), FP16 gated on shaderFloat16 (VK_KHR_shader_float16_int8), INT8 gated on
    shaderInt8 (plus VK_KHR_shader_integer_dot_product for MATMUL), FP8 rejected loudly. Probe
    VK_KHR_shader_float_controls per device: the shared corpus checks non-finite, subnormal, and
    signed-zero preservation (IDENTITY_EDGES_*), so denorm/NaN behavior must be proven per tier,
    not assumed. The Decide the first advertised numerical tier #82 principle holds: guest-chosen tiers, never host knobs; reject rather than
    silently widen. Output: the Target consts, CapabilityDescriptors, and the operator subset
    table.
  • 6. Grilling: execution and event model (kind: design). Validate that vkGetFenceStatus
    alone satisfies bounded nonblocking poll_event with no worker thread (the single most
    delicate machinery in Hexagon/XDNA, likely unnecessary here — prove it, don't assume it).
    Size the bounded preallocated command-buffer/fence/descriptor pools against DeviceLimits so
    guest-controlled work cannot create unbounded host queue depth; vkQueueSubmit success is the
    admission boundary (rejected before it, indeterminate only on ambiguous failure after it).
    Decide finite timeouts given Vulkan has no cancel: the Hexagon precedent rejects finite
    timeouts pre-admission with DeadlineExpired; OpenVINO's deadline-latched poll needs the
    native cancel half Vulkan lacks — an event may latch Failed(DeadlineExpired) only if its
    resources stay retained until the fence actually signals. VK_ERROR_DEVICE_LOST maps to
    Failed(DeviceLost) plus sticky instance poisoning and whole-instance discard. No
    EVENT_CANCELLATION.
  • 7. Task: scaffold virtio-accel-vulkan (wayfinder:task). Crate with deps
    virtio-accel-core + virtio-accel-tosa (dev-dep virtio-accel-conformance), no Cargo
    features; always-compiled lower.rs holding the Target consts; placeholder constructors
    returning InitError::RuntimeUnavailable; uninhabited handle enums (XDNA trick) until real
    handles land; README and SAFETY.md outline; workspace and release plumbing
    (check-release-policy.py counts, publication.py, publish-dry-run.py, portability tier
    row). One convention question to settle with ticket 2: with ash under loaded there is
    nothing to detect at build time — the va_vulkan cfg cannot probe an SDK the way
    va_openvino/va_hexagon/va_xdna do. Options: compile the native path unconditionally on
    supported targets and let runtime loader discovery return RuntimeUnavailable, or keep a
    VIRTIO_ACCEL_VULKAN=1|0 forced cfg for symmetry. Either way the three-state env control and
    loud force-on failure semantics must survive, and docs/portability.md's "detected at build
    time" tier wording needs a reviewed Vulkan-specific note.
  • 8. Task: FFI + lifecycle, identity end-to-end (wayfinder:task). Instance and
    physical-device enumeration reporting honest DeviceIdentity (AcceleratorClass::GPU already
    exists at crates/virtio-accel-core/src/lib.rs:47 — no portable change), device and queue
    creation, the three memory domains from ticket 3, write_buffer/read_buffer, and a TOSA
    IDENTITY program proving artifact load, direct binding, coherence (flush/invalidate for
    non-coherent types), nonblocking completion, teardown, and release-exactly-once. Diagnostics
    counters (direct_binding_admissions, explicit_transfer_bytes) from day one.
  • 9. Task: first operator tier (wayfinder:task). MATMUL first, then the advertised subset
    from ticket 5, with hardware-free golden lowering tests on the always-compiled lower.rs
    path and per-operator shared-corpus execution.
  • 10. Task: conformance and the GPU-less CI lane (wayfinder:task). ConformanceHooks plus
    submission_path_diagnostics; every mandatory case passing; the numerical corpus for every
    advertised operator/dtype. CI: a lavapipe lane mirroring openvino-host-test (pinned Mesa
    package, VK_ICD_FILENAMES pinned so the ICD is explicit, clippy + tests + example). Decide
    lavapipe vs SwiftShader by what pins cleanly. A software ICD is a test target selected by the
    host's ICD configuration, never a fallback chosen by the crate (Build a vendor-neutral Vulkan GPU backend #126 non-goal).
  • 11. Task: real-GPU evidence, docs, and release plumbing (wayfinder:task). Documented
    manual hardware commands (per the Build the AMD XDNA NPU backend #75 precedent — no self-hosted runner on a public repo) on
    the chosen reference GPU; a docs/performance.md section with warm-latency measurements and
    copy-path diagnostics; the README support-matrix row flipped only for capabilities tests
    demonstrate; docs/portability.md, docs/architecture.md, and the release-policy
    unsafe-exception entry updated.

Decisions so far

None resolved. The two leans seeded in Notes await tickets 1 and 2.

Not yet specified

  • Reference hardware and driver stack for the real-GPU lane (RADV? ANV? which distro and kernel
    pin) — sharpens as ticket 11 approaches. "On metal" remains the acceptance bar; the software-ICD
    lane is verification infrastructure, not the destination.
  • Performance evidence: what docs/performance.md owes this backend (the warm-submission shape
    differs from the NPU backends: descriptor update plus vkQueueSubmit steady state) — sharpens
    after ticket 8.
  • Descriptor strategy (pooled sets vs push descriptors) and whether command buffers are recorded
    once per (program, queue) or per submission from a pooled ring — inside tickets 6/8.
  • Float-controls variance across ICDs (lavapipe vs RADV vs ANV) and what it does to the shared
    edge-case oracles — inside ticket 5, verified per lane in ticket 10.

Out of scope

Metadata

Metadata

Labels

area: backendAccelerator traits, mock backend, and provider conformancearea: verificationSecurity, fuzzing, model tests, and performance evidenceepicParent issue grouping a coherent body of workhelp wantedExtra attention is neededkind: implementationProduction implementation workwayfinder:mapWayfinder decision map

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions