Documentation
¶
Overview ¶
Package batch is the host side of a batched GPU command stream, without a transport: the table of a guest's object ids, the creation and destruction of its objects, the replay of its streams and, for streams that read back (wire.ReadBuffer), the bounded read window.
A guest records with a remote device (webgpu.NewRemoteDevice) or a webgpu.Recorder and sends three kinds of message: create an object from a wire descriptor, destroy objects, run a stream. A transport — go-virtio's webgpu-batch frames over virtio-gpu, a WebAssembly host module — decodes its own framing and calls Context's three methods; the semantics are here once. The ids a guest uses are private to its Context and are translated (wire.Remap, wire.RemapDescriptor) to the ids the Backend returned before the Backend sees anything, so a stream can only name objects its own context created.
The package is portable: the Backend that runs the work is what needs a GPU (a browser device, a native one, or a test's recording fake).
Index ¶
- Constants
- Variables
- type Backend
- type Config
- type Context
- func (c *Context) Close()
- func (c *Context) Create(id, kind uint32, desc []byte) error
- func (c *Context) Destroy(ids ...uint32) error
- func (c *Context) Live() int
- func (c *Context) Lookup(id uint32) (uint32, bool)
- func (c *Context) Lost() error
- func (c *Context) PeakDispatchInvocations() uint64
- func (c *Context) PeakInvocations() uint64
- func (c *Context) Stream(words []uint32, data []byte, window []byte) error
- type DeviceBackend
- func (b *DeviceBackend) Close()
- func (b *DeviceBackend) Create(kind uint32, desc []byte) (uint32, error)
- func (b *DeviceBackend) Destroy(id uint32)
- func (b *DeviceBackend) LoopBudgetStats() (modules, instrumented, fallbacks int, spent time.Duration)
- func (b *DeviceBackend) Objects() []any
- func (b *DeviceBackend) Read(id uint32, offset uint64, dst []byte) error
- func (b *DeviceBackend) Replay(words []uint32, data []byte) error
- func (b *DeviceBackend) SetLoopBudget(lb LoopBudget) error
- type LoopBudget
- type Reader
Constants ¶
const DefaultMaxRead = 1 << 20
DefaultMaxRead is the read window a Config without MaxRead grants, 1 MiB.
Variables ¶
var ( // ErrRefused: a message the context's own state refuses (an id in use, // an unknown kind, an id not live). ErrRefused = errors.New("batch: message refused") // ErrLost: the context failed earlier and accepts nothing more. ErrLost = errors.New("batch: context lost") // ErrRead: a read-back the context refuses: no window, a buffer that is // unknown or lacks MAP_READ, a range outside the buffer or the window, // a window or reads above the quota. ErrRead = errors.New("batch: read-back refused") // ErrInvalid: the GPU refused an object the guest described: WGSL that // does not compile, a pipeline or bind group that fails WebGPU validation. // The error carries the browser's message (the WGSL line and column). ErrInvalid = errors.New("batch: the GPU refused the object") )
Errors. Every refusal wraps one; after any error the Context is lost.
var ( // ErrReserved: the guest used the bind group the host reserved for its // loop-budget flag. ErrReserved = errors.New("batch: the guest uses the bind group reserved by the host") // ErrTruncated: at least one shader invocation hit the loop budget and // was cut; the stream's results are partial and are not returned. ErrTruncated = errors.New("batch: a shader exceeded its loop budget and was truncated") )
Errors of the loop budget.
Functions ¶
This section is empty.
Types ¶
type Backend ¶
type Backend interface {
// Create makes an object of a wire kind from its descriptor (object
// references already translated) and returns the id the backend's
// replayer knows it by (nonzero).
Create(kind uint32, desc []byte) (id uint32, err error)
// Destroy releases an object Create returned.
Destroy(id uint32)
// Replay executes one stream whose ids are all ids Create returned, and
// returns once the GPU has executed it.
Replay(words []uint32, data []byte) error
// Close releases everything the backend holds for the context.
Close()
}
Backend runs a context's work. A Context has checked and translated everything it passes: ids are the ones Create returned.
type Config ¶
type Config struct {
// MaxObjects, if nonzero, bounds the guest ids: a guest id is in
// [1, MaxObjects).
MaxObjects uint32
// MaxRead bounds the read window of one stream: the window a caller
// passes, and the bytes the stream's reads return together. 0 is
// DefaultMaxRead; it is capped at wire.MaxReadWindow.
MaxRead uint32
// MaxWorkgroups bounds a Dispatch's counts per dimension; 0 is
// wire.DefaultMaxWorkgroups (WebGPU's default limit).
MaxWorkgroups uint32
// WorkgroupInvocations is the most invocations one workgroup can hold,
// the device's maxComputeInvocationsPerWorkgroup (0: 256, WebGPU's
// default). A Dispatch is counted at its workgroup counts times this.
WorkgroupInvocations uint32
// MaxDispatchInvocations bounds one Dispatch, and MaxStreamInvocations
// the Dispatches of one stream together, in invocations (0: no bound);
// see wire.CheckInvocations. A DispatchIndirect is not counted.
MaxDispatchInvocations, MaxStreamInvocations uint64
}
Config bounds a Context.
type Context ¶
type Context struct {
// contains filtered or unexported fields
}
Context is one guest's objects on a Backend. It is not safe for concurrent use.
func (*Context) Close ¶
func (c *Context) Close()
Close forgets every object and closes the backend, which releases whatever it holds for the context.
func (*Context) Create ¶
Create makes guest object id of a wire kind from a descriptor (which is translated in place: pass a copy the caller does not keep).
func (*Context) Lookup ¶
Lookup returns the backend's id for guest object id, for a transport that gives an object more than the three messages do (virtio's BIND_RESOURCE).
func (*Context) PeakDispatchInvocations ¶ added in v0.11.0
func (*Context) PeakInvocations ¶ added in v0.11.0
PeakInvocations is the most invocations one stream of this context asked for (wire.CheckInvocations' total): what a stream quota would have to allow. PeakDispatchInvocations is the largest single Dispatch: what a dispatch quota would have to allow.
func (*Context) Stream ¶
Stream runs a guest's stream: its ids are translated in place, so words must be memory the caller owns (a copy of the guest's). Its ReadBuffer commands, checked before anything runs, are served after the replay into window, which the caller owns and the stream writes only inside; a stream that reads needs a window, and a Backend that is a Reader.
type DeviceBackend ¶
type DeviceBackend struct {
// contains filtered or unexported fields
}
DeviceBackend is a Reader on a browser device: objects are made from wire descriptors (webgpu.DecodeDescriptor), streams run with Device.Replay and are waited for, and reads are mapAsync, getMappedRange, copy, unmap. It is the one implementation both transports (go-virtio's gpudev, the WebAssembly host module) share. The device's Quota bounds the memory and pipelines its contexts create; create it with the grant of the tenant.
func NewDeviceBackend ¶
func NewDeviceBackend(dev *webgpu.Device) *DeviceBackend
NewDeviceBackend returns a backend whose objects live on dev. webgpu's replay.js must be installed (webgpu.InstallReplay).
func (*DeviceBackend) Close ¶
func (b *DeviceBackend) Close()
Close implements Backend: it destroys what is left.
func (*DeviceBackend) Create ¶
func (b *DeviceBackend) Create(kind uint32, desc []byte) (uint32, error)
Create implements Backend.
func (*DeviceBackend) Destroy ¶
func (b *DeviceBackend) Destroy(id uint32)
Destroy implements Backend.
func (*DeviceBackend) LoopBudgetStats ¶ added in v0.12.0
func (b *DeviceBackend) LoopBudgetStats() (modules, instrumented, fallbacks int, spent time.Duration)
LoopBudgetStats reports how many shader modules were seen, how many were rewritten, how many the browser refused after the rewrite (they were kept as the guest wrote them, bounded by the host's time budget only), and the time spent parsing, rewriting and printing them.
func (*DeviceBackend) Objects ¶
func (b *DeviceBackend) Objects() []any
Objects returns the handles the backend holds (for inspection by tests).
func (*DeviceBackend) Read ¶
func (b *DeviceBackend) Read(id uint32, offset uint64, dst []byte) error
Read implements Reader: GPUBuffer.mapAsync(READ), getMappedRange, copy, unmap, on a buffer of this backend.
func (*DeviceBackend) Replay ¶
func (b *DeviceBackend) Replay(words []uint32, data []byte) error
Replay implements Backend: the stream may name this backend's objects only, and Replay returns once the GPU has executed it.
func (*DeviceBackend) SetLoopBudget ¶ added in v0.12.0
func (b *DeviceBackend) SetLoopBudget(lb LoopBudget) error
SetLoopBudget turns the loop budget on for the objects this backend creates from now on (call it before the first Create). See LoopBudget.
type LoopBudget ¶ added in v0.12.0
type LoopBudget struct {
Work uint64 // invocation-iterations per dispatch (> 0)
MinIterations uint32 // the smallest B (default 1024)
// MaxIterations is the largest B (default 2^24): Work/invocations alone
// would give a one-workgroup dispatch billions of iterations, and a loop
// that serial takes seconds, since an invocation's iterations are not
// parallel (the first Firefox run of the hostile test found this).
MaxIterations uint32
WorkgroupInvocations uint32 // invocations one workgroup counts as (default 256)
IndirectInvocations uint64 // a DispatchIndirect counts as this many (default 2^26)
Group uint32 // the reserved bind group
}
LoopBudget bounds the GPU time one dispatch can burn inside a loop, which WebGPU cannot cancel once submitted (layer 4 of go-wasmcontainers/gpu's scheduling). A DeviceBackend with a LoopBudget rewrites every guest WGSL module that has a compute entry point (go-gfx/wgsl's transform.LoopBudget): each outermost loop nest of each function activation runs at most B iterations, then the invocation leaves the loop and stores 1 into a host-owned flag. After every stream that ran such a pipeline the backend reads the flag; a set flag fails the stream with ErrTruncated, so a partial result is never mistaken for a complete one.
The budget B of a dispatch is Work divided by its invocations (its workgroup counts times WorkgroupInvocations), rounded down to a power of two and at least MinIterations: a dispatch's loops cost at most about Work invocation-iterations whatever its size, so a big dispatch gets short loops and a small one long loops. A DispatchIndirect, whose counts are in GPU memory, is budgeted as a dispatch of IndirectInvocations.
Reserved slots. In bind group Group, binding 0 is the flag (`var<storage, read_write> atomic<u32>`) and binding 1 the budget (`var<uniform> u32`, bound with a dynamic offset into a host buffer holding 2^0 ... 2^32-1, one per 256 bytes). Group is reserved on the whole device: a guest pipeline layout with more than Group bind group layouts, a WGSL module that declares a binding in @group(Group), and a SetBindGroup of index Group or more are refused with ErrReserved. Group is normally the device's maxBindGroups-1 (3 by default).
func (LoopBudget) Instrument ¶ added in v0.12.0
func (lb LoopBudget) Instrument(code string) (string, bool, error)
Instrument returns the module text to hand to the browser, and whether it was rewritten. It parses only: a module that does not parse is returned unchanged and un-instrumented (the browser stays the validation authority and reports its own error), as is a module without a compute entry point or one that also has a vertex entry (a vertex stage cannot write storage). A module that declares a binding in the reserved group is refused with ErrReserved.
func (LoopBudget) Iterations ¶ added in v0.12.0
func (lb LoopBudget) Iterations(inv uint64) uint32
Iterations returns the budget B of a dispatch of inv invocations: the per-nest iteration bound its loops get. lb must have its defaults filled.
func (LoopBudget) Validate ¶ added in v0.12.0
func (lb LoopBudget) Validate() error
Validate checks the configuration.