pub struct WgpuComputeContext {
pub device: Device,
pub queue: Queue,
pub adapter: AdapterIdentity,
pub constraints: AdapterConstraints,
pub profile: HardwareProfile,
pub allocator: QualiaSlabAllocator,
pub slab: Buffer,
pub out_slab: Buffer,
pub weight_slab: Buffer,
pub timestamp_supported: bool,
pub timestamp_period_ns: f32,
/* private fields */
}Fields§
§device: Device§queue: Queue§adapter: AdapterIdentity§constraints: AdapterConstraints§profile: HardwareProfileRich topology description for profile-hardware and cache keying.
allocator: QualiaSlabAllocator§slab: BufferBacks read-only storage and uniform views (both non-exclusive usages, so they may share one buffer).
out_slab: BufferBacks read-write storage outputs. wgpu treats read-write storage as an exclusive usage, so it cannot share a buffer with the read-only inputs in the same dispatch.
weight_slab: BufferBacks the persistent weight region (BindingUsage::StorageReadResident): big,
upload-once matrices (a decode layer’s projection / FFN weights) that are referenced by
offset across many runs instead of being re-uploaded each call. Separate buffer from the
transient ring so Self::clear_transient_allocations never recycles it.
timestamp_supported: bool§timestamp_period_ns: f32Implementations§
Source§impl WgpuComputeContext
impl WgpuComputeContext
pub fn new(capacity_bytes: usize) -> Result<Self, ForgeError>
Sourcepub fn new_for_coopmat(capacity_bytes: usize) -> Result<Self, ForgeError>
pub fn new_for_coopmat(capacity_bytes: usize) -> Result<Self, ForgeError>
Like [new] but tries the Vulkan backend first to find a cooperative-matrix-
capable adapter. On Windows/DX12, EXPERIMENTAL_COOPERATIVE_MATRIX is not
advertised, but the same NVIDIA GPU exposes VK_KHR_cooperative_matrix via
the Vulkan driver. This constructor:
- Creates a Vulkan-only instance (unless
QUALIA_WGPU_BACKENDoverrides). - Enumerates adapters, looking for one with
EXPERIMENTAL_COOPERATIVE_MATRIX. - If found, builds the context on that adapter (un-gating coopmat).
- If not found, falls back to [
new] (which uses the default backend).
This is the primary un-gating path for the HLSL WaveMatrix / WGSL coopmat tensor-core emitters on NVIDIA hardware where DX12 doesn’t expose coopmat.
Sourcepub fn from_device(
device: Device,
queue: Queue,
caps: &GpuAdapterCaps,
capacity_bytes: usize,
) -> Result<Self, ForgeError>
pub fn from_device( device: Device, queue: Queue, caps: &GpuAdapterCaps, capacity_bytes: usize, ) -> Result<Self, ForgeError>
Build a forge context on an already-existing wgpu::Device + Queue (e.g. the
process-wide crate::gpu_context::shared_gpu) instead of requesting a second adapter
and device the way Self::new does. wgpu Device/Queue are cheap Arc clones, so the
forge then runs on the same device that owns the resident LLM weights + KV cache — the
device-unification keystone for running decode on the forge (LLM-on-forge plan, Phase 1a).
Adapter identity / constraints / hardware profile are reconstructed from the live
device.limits() + device.features() plus the caller’s GpuAdapterCaps snapshot,
because the original wgpu::Adapter is consumed at shared-gpu init and not retained.
Honest boundary: this inherits the host device’s negotiated features and limits verbatim.
In particular, if the shared device was created without the ray-tracing acceleration-structure
limits raised (as shared_gpu currently does), RT-core Neighbor cannot create BLAS/TLAS on
this context even when supports_rt_cores is true — from_device does not silently widen the
host device. The decode path (matmul/elementwise/reduce) needs none of that.
Sourcepub fn allocate_and_write(
&mut self,
data: &[u8],
binding: u32,
group: u32,
usage: BindingUsage,
) -> Result<BufferView, ForgeError>
pub fn allocate_and_write( &mut self, data: &[u8], binding: u32, group: u32, usage: BindingUsage, ) -> Result<BufferView, ForgeError>
Allocate a transient slab sub-range and upload data into it.
§Topology note (honest scope, plan §2)
This upload uses queue.write_buffer uniformly on every topology
(unified and discrete alike); readback in Self::read_buffer_f32
likewise uses copy_buffer_to_buffer uniformly. The
MemoryTopology::{Unified, Discrete} classification on the allocator is
recorded but not yet acted upon here: the plan-§2 differentiated paths
(zero-copy persistent-mapped slabs for unified memory; a pinned staging ring
with async copy_buffer for discrete PCIe) are NOT implemented. The current
uniform path is correct on both topologies but unoptimised; the unified
zero-copy benefit cannot be measured on this discrete-only host (RTX A2000),
so it is left as documented future work rather than shipped unverified. See
MemoryTopology for the full rationale.
Sourcepub fn allocate_weight(
&mut self,
data: &[u8],
binding: u32,
group: u32,
) -> Result<BufferView, ForgeError>
pub fn allocate_weight( &mut self, data: &[u8], binding: u32, group: u32, ) -> Result<BufferView, ForgeError>
Bump-allocate data into the persistent weight region (weight_slab) and upload it
once, returning a BufferView tagged BindingUsage::StorageReadResident. Unlike
Self::allocate_and_write (transient ring), this view survives
Self::clear_transient_allocations, so a decode layer’s projection / FFN matrices are
uploaded a single time and referenced by offset across every token’s run — eliminating
the per-call weight re-upload. Offsets are 256-aligned for direct bind-group use.
Sourcepub fn clear_weights(&mut self)
pub fn clear_weights(&mut self)
Reset the persistent weight region so it can be reused for a different model/layer set.
Any BufferViews previously returned by Self::allocate_weight become stale — drop
the corresponding handles and re-load. (Weights are write-once; no per-tensor free.)
Sourcepub fn resident_weight_bytes(&self) -> usize
pub fn resident_weight_bytes(&self) -> usize
Bytes currently consumed in the persistent weight region (for tests / introspection).
pub fn allocate_transient( &mut self, size_bytes: usize, binding: u32, group: u32, usage: BindingUsage, ) -> Result<BufferView, ForgeError>
pub fn advance_read_head(&mut self, offset: usize)
pub fn clear_transient_allocations(&mut self)
Sourcepub fn build_triangle_scene(
&self,
vertices: &[f32],
) -> Result<(Blas, Tlas), ForgeError>
pub fn build_triangle_scene( &self, vertices: &[f32], ) -> Result<(Blas, Tlas), ForgeError>
Builds a bottom-level (BLAS) + top-level (TLAS) acceleration structure for a
triangle soup and returns both, ready to bind to a ray-query shader. vertices
is a flat list of f32 triples (3 per vertex, 3 vertices per triangle),
row-major. The BLAS geometry is marked OPAQUE (required — naga’s ray-query
has no candidate/any-hit path, so non-opaque geometry yields no committed hits),
and the single TLAS instance uses the identity transform. Both structures are
built and the queue drained before returning. Requires the adapter to support
(and the device to have enabled) EXPERIMENTAL_RAY_QUERY.
The returned Blas must be kept alive alongside the Tlas for the lifetime of
any bind group referencing the TLAS (the TlasInstance borrows the BLAS).
Sourcepub fn compile_pipeline(
&self,
source: &str,
entry_point: &str,
) -> Result<ComputePipeline, ForgeError>
pub fn compile_pipeline( &self, source: &str, entry_point: &str, ) -> Result<ComputePipeline, ForgeError>
Compile a WGSL compute pipeline and return the owned wgpu::ComputePipeline
(no borrow of self), wrapped in a validation error scope. This is the building
block the multi-node graph executor uses to compile every node’s kernel up front
before recording them into a single command encoder (Self::submit_graph).
WgpuPipeline::compile delegates here.
Sourcepub fn compile_pipeline_spirv(
&self,
spirv: &[u8],
entry_point: &str,
) -> Result<ComputePipeline, ForgeError>
pub fn compile_pipeline_spirv( &self, spirv: &[u8], entry_point: &str, ) -> Result<ComputePipeline, ForgeError>
Like [compile_pipeline] but accepts pre-compiled SPIR-V bytes instead of
WGSL source. This is the execution bridge for native shader profiles that
compile to SPIR-V (notably HLSL via DXC –spirv): the forge emits HLSL,
DXC produces a SPIR-V binary, and this method feeds it into the same wgpu
pipeline (bind groups, slab, dispatch — all unchanged).
Sourcepub fn compile_pipeline_cached(
&self,
source: &str,
entry_point: &str,
) -> Result<ComputePipeline, ForgeError>
pub fn compile_pipeline_cached( &self, source: &str, entry_point: &str, ) -> Result<ComputePipeline, ForgeError>
compile_pipeline with a process-lifetime cache keyed by
entry\0source — the same (source, entry) returns the previously-built pipeline
(a cheap Arc-clone) instead of recompiling. This is what makes a re-run of a fixed
graph (e.g. one decode block per generated token, via a held [ForgeGraphExecutor])
pay shader compilation once, not per call. The graph executor records its nodes
through this path; one-shot callers see a cold cache (built + dropped with the context).
Sourcepub fn cached_pipeline_count(&self) -> usize
pub fn cached_pipeline_count(&self) -> usize
Number of distinct pipelines currently cached (for tests / introspection).
Sourcepub fn create_compute_bind_group(
&self,
pipeline: &ComputePipeline,
buffers: &[BufferView],
) -> BindGroup
pub fn create_compute_bind_group( &self, pipeline: &ComputePipeline, buffers: &[BufferView], ) -> BindGroup
Build a bind group binding each BufferView at its binding slot, choosing the
physical slab per the view’s usage (Self::slab_for). Shared by the per-node
WgpuPipeline::dispatch path and the deferred-submit graph path.
Sourcepub fn submit_graph(&self, passes: &[GraphPass]) -> Result<(), ForgeError>
pub fn submit_graph(&self, passes: &[GraphPass]) -> Result<(), ForgeError>
Record all of a graph’s node dispatches — and the GPU→GPU hand-off copies
between them — into ONE [wgpu::CommandEncoder] and submit it once, instead of
one queue.submit() per node. This is the single-encoder deferred-submit fusion
(plan §8.1 “Option B”): within one command buffer wgpu preserves command order and
inserts the necessary buffer hazard barriers, so a producer’s compute pass, its
copy_buffer_to_buffer hand-off, and the consumer’s dispatch are correctly ordered
with no host round-trip and no per-node submit latency. The caller (the executor)
has already encoded each node’s data dependencies in passes (insertion/topological
order) and built each bind group, so this loop is pure recording. Blocks on device
completion and surfaces any validation error.
Sourcepub fn copy_view(
&self,
src: &BufferView,
dst: &BufferView,
) -> Result<(), ForgeError>
pub fn copy_view( &self, src: &BufferView, dst: &BufferView, ) -> Result<(), ForgeError>
Copy src’s bytes to dst on the device (GPU→GPU, no host readback), honouring
each view’s slab. Used by the multi-node graph executor to move a node’s output out
of the read_write slab into the read slab, so a downstream node can bind it as a
read-only input without aliasing its own read_write output (wgpu forbids the same
buffer being bound read-write and read-only within one dispatch). Submits on the
shared queue, so it is ordered before any later dispatch that reads dst.
pub fn read_buffer_f32(&self, view: &BufferView) -> Result<Vec<f32>, ForgeError>
Trait Implementations§
Source§impl OracleContext for WgpuComputeContext
impl OracleContext for WgpuComputeContext
Source§fn run_kernel(
&mut self,
kernel: &KernelSpec,
schedule: &Schedule,
buffers: &[BufferView],
element_count: usize,
warmups: usize,
samples: usize,
) -> Result<Vec<u64>, ForgeError>
fn run_kernel( &mut self, kernel: &KernelSpec, schedule: &Schedule, buffers: &[BufferView], element_count: usize, warmups: usize, samples: usize, ) -> Result<Vec<u64>, ForgeError>
Emit the kernel’s WGSL, compile it, then run the warmup + timed-sample
dispatch loop — byte-for-byte the loop the wgpu oracle evaluators ran inline
(warmups untimed, then samples timed dispatches via QualiaCompute::dispatch).
Source§fn allocate_and_write(
&mut self,
data: &[u8],
binding: u32,
group: u32,
usage: BindingUsage,
) -> Result<BufferView, ForgeError>
fn allocate_and_write( &mut self, data: &[u8], binding: u32, group: u32, usage: BindingUsage, ) -> Result<BufferView, ForgeError>
data into it. usage
selects the backing slab on wgpu (read-only/uniform vs read-write); the
CUDA backend addresses one slab and ignores it.Source§fn allocate_transient(
&mut self,
size_bytes: usize,
binding: u32,
group: u32,
usage: BindingUsage,
) -> Result<BufferView, ForgeError>
fn allocate_transient( &mut self, size_bytes: usize, binding: u32, group: u32, usage: BindingUsage, ) -> Result<BufferView, ForgeError>
usage is honoured by
the wgpu backend and ignored by CUDA, as for Self::allocate_and_write.Source§fn read_buffer_f32(&self, view: &BufferView) -> Result<Vec<f32>, ForgeError>
fn read_buffer_f32(&self, view: &BufferView) -> Result<Vec<f32>, ForgeError>
f32s.Source§fn clear_transient_allocations(&mut self)
fn clear_transient_allocations(&mut self)
Source§fn adapter(&self) -> &AdapterIdentity
fn adapter(&self) -> &AdapterIdentity
Source§fn constraints(&self) -> &AdapterConstraints
fn constraints(&self) -> &AdapterConstraints
Source§fn timestamp_supported(&self) -> bool
fn timestamp_supported(&self) -> bool
false there).Auto Trait Implementations§
impl !Freeze for WgpuComputeContext
impl !RefUnwindSafe for WgpuComputeContext
impl Send for WgpuComputeContext
impl !Sync for WgpuComputeContext
impl Unpin for WgpuComputeContext
impl UnsafeUnpin for WgpuComputeContext
impl !UnwindSafe for WgpuComputeContext
Blanket Implementations§
§impl<S, A> Aggregate<Result<S, Error>> for Awhere
A: Aggregate<S>,
impl<S, A> Aggregate<Result<S, Error>> for Awhere
A: Aggregate<S>,
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more