libmtlc has two GPU code generators, NVIDIA PTX and SPIR-V (OpenCL),
both emitted from the same IR with no nvcc, no cudart, and no LLVM. Through
the reference frontend, kernels are written in Mettle, compiled to a .ptx
module with --emit-ptx (or a .spv module with --emit-spirv), and, for the
CUDA path, launched from a normal Mettle host program via the
std/gpu bindings and the dispatch statement.
(A frontend driving libmtlc directly reaches the same generators through
mtlc_emit; see Writing a frontend for libmtlc.)
The model is two-stage and explicit: kernels live in their own file, the
host manages device memory itself, and dispatch only performs the launch. This
mirrors how real GPU code manages persistent VRAM.
A kernel file is compiled with mettle --emit-ptx. Use the kernel keyword for
GPU entry points (it parses like fn and is emitted as a PTX .entry):
// kernels.mettle -> mettle --emit-ptx kernels.mettle -o kernels.ptx
kernel vadd(a: float32*, b: float32*, c: float32*, n: int32) {
var i: int32 = block.x * block_dim.x + thread.x;
if (i < n) {
c[i] = a[i] + b[i];
}
}
Inside --emit-ptx compiles, the GPU thread/block indices are built-in member
expressions that mirror CUDA:
| Mettle | CUDA | PTX special register |
|---|---|---|
thread.x |
threadIdx.x |
%tid.x |
block.x |
blockIdx.x |
%ctaid.x |
block_dim.x |
blockDim.x |
%ntid.x |
grid_dim.x |
gridDim.x |
%nctaid.x |
.x, .y, and .z are all available. The canonical global-thread index is:
var i: int32 = block.x * block_dim.x + thread.x;
These built-ins are only active during GPU-module compiles, so member access on
an ordinary struct named block in a CPU program is unaffected.
Kernels use the same syntax as CPU code: arithmetic, comparisons, if/while,
pointer indexing, casts, and a set of GPU math intrinsics declared as extern:
sqrtf, rsqrtf, fabsf, sinf, cosf, logf, expf (lowered to PTX
sqrt.rn / rsqrt.approx / ex2.approx etc.), plus h2f / f2h for fp16
conversion. The PTX backend is validated structurally by round-tripping emitted
PTX through ptxas; a CUDA Driver differential suite executes correctness and
sanitizer cases on development hardware and has a stricter native GB10 mode.
See the GPU architecture and acceptance contract.
An ordinary function called by a kernel is emitted as a non-entry device helper
in both PTX and SPIR-V. Reachability is transitive and unrelated host functions
are omitted; kernel remains the only launch-entry marker. Direct calls with
scalar or pointer parameters/results are supported. Recursion, indirect calls,
external calls, host launches from device code, and calling a kernel as a normal
function are rejected by a shared IR call-graph verifier, so the rule is the
same for every frontend and GPU backend.
Kernels can allocate statically sized scalar arrays in semantic address spaces:
kernel staged(x: float32*, out: float32*, n: int32) {
workgroup var tile: float32[256]; // one array shared by the workgroup
private var scratch: int32[4]; // one array per work-item
var lane: int32 = thread.x;
if (lane < 256 && lane < n) { tile[lane] = x[lane]; }
barrier(workgroup, global, acq_rel);
if (lane < 256 && lane < n) { out[lane] = tile[lane]; }
}
These declarations are kernel-only, require a nonzero static array extent and a
scalar numeric element type, cannot have declaration initializers, and cannot be
rebound (their elements remain mutable). PTX maps them to .shared / .local;
SPIR-V maps them to real Workgroup / Private OpVariable arrays. They enter
both emitters as one target-neutral address-space allocation IR operation.
A pointer-shaped workgroup binding is an unbounded typed view of the dynamic arena whose byte size is supplied by the host launch:
kernel dynamic_staged(x: float32*, out: float32*, n: int32) {
workgroup var values: float32*;
workgroup var metadata: uint32*; // aliases the same arena base
var lane: int32 = thread.x;
if (lane < n) {
values[lane] = x[lane];
metadata[256 + lane] = (uint32)lane;
}
barrier(workgroup, acq_rel);
if (lane < n) { out[lane] = values[lane]; }
}
Every dynamic view in a kernel aliases the same base. Kernels partition it with
ordinary element offsets, must preserve each view's natural alignment, and must
keep accesses within the launch's dynamic byte count; the kernel receives no
implicit bounds value. PTX emits one module-scope external shared array per
kernel. SPIR-V appends one legal Workgroup pointer kernel argument, choosing
the most strictly aligned view type, and materializes all views from it. The
OpenCL host adapter must bind the launch byte count to that hidden local-memory
argument. PTX execution is sanitizer-tested; SPIR-V is spirv-val-validated,
but that host-binding path has not yet been execution-tested here.
barrier(regions..., order) is a uniform workgroup collective. Regions are
workgroup and/or global; the order is acquire, release, acq_rel, or
seq_cst. With no arguments it defaults to workgroup memory and sequential
consistency. Every live work-item in the workgroup must reach the statement.
SPIR-V receives the exact order and memory-class bits. PTX bar.sync is a full
CTA barrier and therefore deliberately strengthens acquire/release-only forms.
The legacy gpu_barrier() intrinsic remains accepted as a workgroup/seq-cst
compatibility alias.
Mettle exposes asynchronous staging as a memory operation, not as a PTX spelling:
kernel stage(src: uint32*, out: uint32*) {
workgroup var tile: uint32[128];
var lane: int32 = thread.x;
var base: int32 = lane * 4;
async_copy_workgroup(&tile[base], src + base, 4,
transaction: 16, cache: global);
async_copy_commit();
async_copy_wait(0);
barrier(workgroup, acq_rel);
out[base] = tile[base];
}
Each copy moves matching scalar elements from global to workgroup storage for
the issuing work-item. transaction is 4, 8, or 16 bytes; the total byte span
must be a transaction multiple, and both addresses have that runtime alignment
precondition. cache: all|global is a performance hint. A commit closes the
current group, and async_copy_wait(n) waits until at most n newer groups
remain. The public pending range is 0..7 and every function exit must have no
outstanding or uncommitted group. The shared verifier checks this over the
whole CFG, including branches and loops.
A wait makes completion visible only to the issuing work-item. Other work-items
must not consume staged data until a uniform workgroup barrier publishes it;
acq_rel is the normal handoff. Reading an overlapping destination before its
wait is invalid. PTX 7.0/sm_80 and newer lower eligible operations to
cp.async.{ca,cg}.shared.global, commit_group, and wait_group. Portable
PTX and OpenCL 2.0 SPIR-V replay typed copies synchronously while preserving
commit/wait/barrier ordering. Compiler-owned PTX workgroup allocations are at
least 32-byte aligned, covering 16-byte transactions and WMMA tile loads; raw
global pointers retain their documented runtime precondition.
With -O, the shared optimizer can form the same neutral region from ordinary
typed global loads followed by their single-use workgroup stores. It proves
address-space provenance, scalar width, natural transaction alignment,
single-use connectivity, straight-line control, a later acq-rel/seq-cst
workgroup barrier, and absence of intervening memory effects. Commit is placed
before independent scalar work and wait immediately before publication. A
function containing explicit async groups is left alone, so generated staging
never splices into user-managed pending-group state. Source Mettle and public
libmtlc builders use the identical pass; the backend chooses native versus
synchronous realization only after neutral legality succeeds.
Atomics are native, type-directed kernel operations over uint32*, uint64*,
or matching workgroup arrays. They are not extern calls and carry a target-
neutral address space, memory order, and scope into shared IR:
var ticket: uint64 = atomic_fetch_add(queue_head, index, 1,
order: acq_rel, scope: device);
atomic_store(ready, index, 1, order: release, scope: device);
var published: uint32 = atomic_load(ready, index,
order: acquire, scope: device);
var old: uint32 = atomic_compare_exchange(
state, index, expected, desired,
success_order: acq_rel, failure_order: acquire, scope: device);
The u32/u64 family includes load, store, fetch add/sub/min/max/and/or/xor,
exchange, and compare-exchange. Value-returning operations return the observed
old value; store returns void. Element indices remain 64-bit through backend
address generation. Storage provenance is inferred as global or workgroup and
may be stated with space:. Global operations default to device scope;
workgroup operations default to workgroup scope. All default to seq-cst. Loads
permit relaxed/acquire/seq-cst, stores relaxed/release/seq-cst, and RMWs all
five C/C++ orders. CAS carries separate success and failure orders; failure may
not be release/acq-rel or stronger than success.
PTX emits scoped ordered ld/st/atom instructions and NVIDIA's required
fence.sc sequences for sequential consistency. SPIR-V emits OpAtomicLoad,
OpAtomicStore, and the matching RMW/CAS operations with exact Scope, ordering,
and memory-class operands. Optional u64 SPIR-V atomics require the OpenCL
cl_khr_int64_base_atomics and cl_khr_int64_extended_atomics extensions.
The real-device message-passing test uses an actual release store paired with
an acquire load; contended RMW/CAS and workgroup atomics are checked separately.
Mettle/libmtlc defines a subgroup as an implementation-sized set of work-items, not as a CUDA warp. The initial portable collective surface is:
| Reference Mettle alias | Contract |
|---|---|
subgroup_local_id() -> uint32 |
caller's zero-based lane within its subgroup |
subgroup_size() -> uint32 |
number of lanes in the current subgroup |
subgroup_broadcast(value, source_lane) |
u32/f32 value from one uniform, valid source lane, returned to every lane |
subgroup_reduce_add(value) |
u32/f32 subgroup sum, returned to every lane |
subgroup_reduce_min(value) / subgroup_reduce_max(value) |
u32/f32 subgroup extrema, returned to every lane |
subgroup_scan_inclusive_add(value) |
u32/f32 prefix sum including the caller's value |
subgroup_scan_exclusive_add(value) |
u32/f32 prefix sum preceding the caller, with zero identity |
subgroup_shuffle(value, source_lane) |
u32/f32 value from a per-lane source; an inactive or out-of-range source returns the caller's value |
subgroup_ballot(predicate, word) |
32-bit word of the active-lane predicate mask; words outside the implementation mask are zero |
subgroup_any(predicate) / subgroup_all(predicate) |
subgroup-wide boolean vote over active lanes |
These are native, type-checked Mettle kernel built-ins; no declaration or CUDA
header is required. Another frontend uses the corresponding typed
MTLC_INTRINSIC_GPU_SUBGROUP_* identities directly. Every live lane must
execute a collective in uniform control flow.
The broadcast source lane must be identical and in range for the whole subgroup;
shuffle source lanes may vary. Ballot is word-addressed so a backend is never
forced to narrow its implementation-sized mask into one scalar. The current
portable mask contract exposes words 0 through 3 and returns zero for any
larger word index.
Unsigned addition wraps modulo 2^32. Floating-point reduction order is
implementation-defined, so it is not bit-reproducible across backends.
The shared IR call-graph verifier enforces statically visible uniformity before either GPU backend runs. Its lattice distinguishes values uniform across a workgroup, values uniform only within a subgroup, and work-item-varying values. Kernel arguments and group topology are workgroup-uniform; broadcast and reduction and vote results are subgroup-uniform, while shuffle, scan, and a ballot selected with a varying word index vary by lane. Local IDs, memory loads, and opaque call results are conservatively varying. Helper parameter ranks are joined from every reachable call site. Collectives under control that is insufficiently uniform for their scope, varying-trip loops, and work-item-varying broadcast lanes are rejected without putting backend reconvergence rules in the frontend.
PTX maps a subgroup to the 32-lane NVIDIA warp and uses activemask,
shfl.sync, and vote.sync. The reduction guards every tree edge with the active mask, so a
uniform partial final warp need not have a power-of-two lane count. SPIR-V uses
SubgroupLocalInvocationId, SubgroupSize, the native SPIR-V 1.0 Groups
broadcast/add/min/max reduce and scan operations, and the standardized
SPV_KHR_shader_ballot / SPV_KHR_subgroup_vote operations. An OpenCL consumer
must support the corresponding subgroup extensions. The current OpenCL 2.0
profile cannot represent a varying-source shuffle; it reports that capability
gap rather than miscompiling it as uniform broadcast.
tensor_mma is a native Mettle kernel operation for one collective
D = A * B + C tile. It is deliberately a whole-tile memory operation: source
code and shared IR describe the arithmetic, element formats, shape, layouts,
leading dimensions, rounding, overflow, sparsity, scaling, and collective
scope. They never expose PTX register fragments, lane-to-fragment mappings, or
an NVIDIA instruction name.
kernel gemm_tile(a: uint16*, b: uint16*, c: float32*, d: float32*) {
tensor_mma(a, b, c, d,
shape: m16n16k16,
input_type: f16,
output_type: f32,
a_layout: row, b_layout: col,
c_layout: row, d_layout: row);
}
For whole problems, tensor_matmul applies that same target-neutral descriptor
to one bounded output region. A typical 2-D launch assigns one subgroup to one
logical region:
kernel gemm_region(a: uint16*, b: uint16*, c: float32*, d: float32*,
problem_m: uint32, problem_n: uint32, problem_k: uint32,
lda: uint32, ldb: uint32, ldc: uint32, ldd: uint32) {
var row: uint32 = (uint32)block.y * (uint32)16;
var column: uint32 = (uint32)block.x * (uint32)16;
tensor_matmul(a, b, c, d, row, column,
problem_m, problem_n, problem_k,
shape: m16n16k16,
input_type: f16, output_type: f32,
lda: lda, ldb: ldb, ldc: ldc, ldd: ldd);
}
// Host side: one 32-lane subgroup per 16x16 output region.
dispatch gemm_region[
grid: ((n + 15) / 16, (m + 15) / 16, 1),
block: (32, 1, 1)
](a, b, c, d, m, n, k, lda, ldb, ldc, ldd);
The exact region semantics are, for every row+local_row < problem_m and
column+local_column < problem_n,
D[r,c] = C[r,c] + sum(q=0..problem_k-1) A[r,q]*B[q,c] under the descriptor's
numeric rules. Out-of-range output coordinates do nothing; problem_k == 0
copies C to D for the active region. The descriptor M/N are the region size and
descriptor K is a preferred exact native chunk, not permission to round the
problem down. Origins and M/N/K are unsigned, collective-uniform runtime
values. Whole matrices require explicit lda/ldb/ldc/ldd; runtime
leading dimensions are uniform unsigned values in the descriptor's uint32
range. Pointer slicing remains the backend-neutral way to select a batch, so a
grid.z batch does not add a vendor-specific tensor ABI.
tensor_matmul has its own shared-IR operation. Its operands are ordinary
matrix pointers, optional neutral metadata/scale/stride values, and the five
unsigned region controls; it does not lower into a frontend loop or expose a
fragment. Shared verification checks exact pointer/control types and collective
uniformity. It is intentionally not eligible for the single-tile chain/loop
optimizer.
The current PTX backend supports exact dense, subgroup-scoped f16/bf16-to-f32, f64, i8/u8-to-i32-wrap, unscaled E4M3/E5M2-to-f32, and block-scaled FP8/FP6/FP4-to-f32 regions, plus matching f16 or bf16 structured 2:4 A with f32 accumulation/result. Independent A/B transpose is supported under either stored layout. For stable-WMMA families, a full M/N interior is native when the tuple budget permits it: PTX normalizes a transposed stored layout into the opposite backend-local logical view, C is loaded once, all complete runtime-K chunks remain in the accumulator, and D is stored once. A K remainder then continues cooperatively from that exact-width D value. Partial M/N regions, K smaller than one native chunk, partial warps, and tuple-budget refusals use complete cooperative scalar replay with 64-bit address arithmetic. In that path transpose swaps logical coordinates before applying the selected stored layout; it never materializes a frontend or shared-IR transpose. Unscaled FP8 edges use PTX's architectural packed-FP8-to-f16 conversion followed by f32 widening; complete interiors retain backend-private direct-MMA accumulators across runtime K.
Scaled regions use the same neutral scale contract as tensor_mma:
scale_A is row-major [M, ceil(K/block)], scale_B is column-major
[ceil(K/block), N], and both scale leading dimensions are explicit. Scale
coordinates stay logical even when A or B is transposed. FP6/FP4 dense storage
is the neutral least-significant-bit-first stream including logical stride
padding. Exact replay gathers values across byte boundaries, decodes
E2M1/E2M3/E3M2 and UE8M0 with backend-local integer arithmetic, decodes UE4M3
through the supported positive E4M3 conversion, applies both block scales, and
continues with f32 FMA. This deliberately avoids the narrower alternate-format
conversion instructions that PTX 8.8 does not qualify for sm_121a.
Complete scaled interiors retain native mxf8f6f4, MXFP4, or NVFP4
accumulators across runtime K and advance both scale grids per block. Canonical
row-A/column-B packed matrices use exact contiguous fragment loads when the
runtime logical stride is byte aligned (multiple of four values for FP6,
multiple of two for FP4). A uniform origin/stride guard sends every other case
to exact cooperative replay.
Structured 2:4 regions use the existing neutral compressed-A contract. Each
logical four-wide K group owns two stored A values in increasing selected-index
order and one uint8 mask; metadata is compact row-major
[M,ceil(problem_k/4)]. A partial final group still stores two values, while
logical positions outside runtime K do not contribute. A transpose changes
only compressed A's stored coordinates; metadata remains in logical row/group
order. PTX rebases complete K16 chunks by eight stored A values and four
metadata bytes, translates those masks to ordered mma.sp metadata, and keeps
the accumulator resident across runtime K. M/N/K edges and forced fallback
decode the same masks directly with 64-bit addresses and f32 FMA. Invalid masks
remain outside the source contract; both paths clamp them to the safe 0011
mask before use as defensive hardening.
TF32 tails, reduced-precision accumulator/result, saturating integer tails, and other sparse ratios/types remain explicit capability errors. Native transpose, scaled paths, and bounded structured sparsity are offline-assembler qualified, not device-numerically qualified. The OpenCL 2.0 SPIR-V profile also rejects the operation explicitly.
CUDA 12.9 offline assembly accepts the five native-interior fixtures, three
exact-transpose fixtures, two unscaled direct-MMA FP8 fixtures, and four scaled
FP8/FP6/FP4 fixtures plus two structured-2:4 fixtures for sm_121a with zero
spills and zero stack. The
stable-WMMA modules peak at 40 registers, unscaled FP8 at 64, and the scaled
fixtures at 48 (mixed FP6), 48 (MXFP4), 56 (NVFP4), and 72 (scaled transposed
FP8); structured 2:4 uses 48 registers natively and 40 under forced replay. The
CPU oracle covers 19x23x21 unscaled, 19x23x67 scaled, and 19x23x19 sparse M/N/K tails,
E4M3/E5M2/E2M1/E2M3/E3M2/UE8M0/UE4M3 decoding, exact LSB-first streams,
independent A/B transpose, compact mask indexing, compressed selected-value
order, mixed layouts and padded strides, integer wrap, K=0, and out-of-range
no-op semantics. Neither result is GPU/GB10 numerical execution or performance
evidence.
The first four arguments are positional base pointers. Arithmetic configuration
is named and compile-time: shape (or separate m/n/k), independent
a_type/b_type/accumulator_type/result_type, four layouts,
transpose_a/transpose_b, math, sparsity, rounding, overflow, A/B
packing and scale modes/types, scale-table leading dimensions, and subgroup
or workgroup scope. Each of
lda/ldb/ldc/ldd may instead be a scalar integer expression, so one
compiled kernel can operate on padded, sliced, or batched matrices. Runtime
strides and all pointer operands must be uniform at the collective scope;
positive layout-compatible values are runtime preconditions.
input_type and output_type are conveniences, not a restricted type model.
Sparse/scaled descriptors take matching named metadata, a_scale, and
b_scale pointer operands. The public equivalent is MtlcTensorMmaDesc plus
mtlc_tensor_mma / mtlc_tensor_mma_ex; a frontend that already owns a tiled
operation can emit the same neutral accumulator chain with
mtlc_tensor_mma_chain. Dense sub-byte packing is target-neutral: consecutive
logical values occupy each byte from least- to most-significant bits, while all
matrix leading dimensions and pointer offsets remain logical element counts.
Block-scale metadata is canonical rather than vendor-fragment-shaped: A scales
are row-major M x ceil(K/block) and B scales are column-major
ceil(K/block) x N.
Post-processing is a separate tensor_epilogue collective, so nonlinear
activation never changes an MMA chain's exact sequential semantics:
kernel finish_tile(d: float32*, bias: float32*, alpha: float32,
beta: float32) {
tensor_epilogue(d, shape: m32n16, element_type: f32,
bias_mode: column, bias: bias,
alpha: alpha, beta: beta, activation: relu);
}
Its exact operation is
D[row,col] = activation(alpha*D[row,col] + beta*bias[row,col]).
Omitted scales are one. Bias may be absent, per-row, per-column, or a matrix
with an independent row/column layout and static or uniform runtime leading
dimension. D has the same layout/stride choices; activation is identity, ReLU,
or clamp with uniform clamp_min/clamp_max. f16/bf16 storage computes in f32;
f32/f64 compute in their own format. Every pointer, scale, bound, and runtime
stride is uniform at subgroup or workgroup scope. ReLU replaces only ordered
negative values with positive zero. Clamp performs ordered lower- then
upper-bound replacement; unordered values survive either activation. An active
bias region must not overlap the destination region.
PTX always has entry-ordered, exit-ordered cooperative logical-memory replay. It supports generic/global/workgroup storage, arbitrary nonzero M/N, both layouts, every bias mode, and the four formats above.
For an exactly compatible adjacent MMA/commit and epilogue, PTX may instead retain the accumulator. A verified loop commit can make the same handoff across its exit jump only when the epilogue label has that jump as its sole incoming edge and cannot be reached by fallthrough. An outer guard or any other bypass forces the complete memory operation at the shared label. Compatibility in all cases requires the same D pointer, M/N, result format, layout, static or identical runtime stride, and subgroup scope. Stable WMMA permits bias-free scale/ReLU/clamp because applying one scalar operation to every accumulator register needs no fragment-coordinate assumption; any bias forces replay because stable WMMA's coordinate mapping is opaque. Direct m16n8 native MMA has an explicit backend-owned coordinate mapping and supports every bias mode. Straight-line chains, verified staged pipeline commits, and uniquely reached loop exits are covered. Tuple-budget refusal, incompatible operands, unsupported mappings, and ambiguous control flow fall back to the complete memory operation.
CUDA 12.9 ptxas -arch=sm_121a accepts the standalone
f16/bf16/f32/f64 matrix with zero spills and 14--21 registers. The resident
suite assembles with zero spills at 40 registers for a two-MMA stable-WMMA
scale/ReLU chain, 58 for a two-MMA FP8 matrix-bias/clamp chain, and 31 for a
staged commit handoff. Both the fused loop exit and guarded-loop replay use 40
registers; the forced memory-replay module peaks at 66 registers.
These are offline structural/resource results, not GB10 execution or
performance evidence. The current OpenCL 2.0 SPIR-V profile rejects the
operation explicitly until it has exact collective ordering and lowering.
Structured sparsity has an equally backend-neutral memory contract. A is the
sparse operand; logical K is divided into consecutive groups of 2, 4, or 8 for
1:2, 2:4, or 4:8. Each group stores exactly half of its values, in increasing
logical-index order, so A's leading dimension counts K/2 stored elements.
metadata is a uint8* row-major [M][K/group] occupancy matrix: only the low
group bits are used and exactly group/2 bits must be set. This is a Mettle
data format, not PTX sparse metadata; each backend must translate it or reject
the requested profile.
The target-neutral format vocabulary includes f16, bf16, tf32, f32, f64,
FP8 E4M3/E5M2, FP6 E2M3/E3M2, FP4 E2M1, UE8M0/UE4M3 scales, signed/unsigned
i8 and i4, b1, and i32. Until Mettle has first-class low-precision scalars,
f16/bf16 storage is passed as uint16*; byte FP8 and packed FP6/FP4/i4/b1
storage use uint8*. The descriptor, rather than that carrier spelling,
defines arithmetic meaning.
The PTX backend currently implements the complete stable PTX WMMA family below as load/MMA/store sequences. The listed shapes are physical instructions. For byte-addressable f16, bf16, tf32, f64, and i8/u8, a larger logical M/N shape up to 256x256 may be an exact grid of the listed square physical shape: 16x16 for K=16 f16/bf16/i8/u8, 16x16 for K=8 tf32, or 8x8 for K=4 f64. PTX computes every subtile address from the neutral layout and leading dimension, then chooses an A-outer or B-outer traversal to reuse the fragment that removes more loads. The logical descriptor, shared IR, and result remain one whole tile.
A capability check either selects an exact physical shape/grid or rejects the descriptor; it never truncates the logical tile or silently changes precision, layout, sparsity, scale, rounding, or scope. The native and public-builder 32x32 f16 chain/loop gates retain four accumulator subtiles at a logical tuple peak of 56, while a budget of 55 produces exact replay. CUDA 12.9 assembles the f16, bf16, tf32, f64, i8, and u8 grid matrix, including f16-result, row/column-layout, and runtime-leading-dimension variants, with zero spills; this is structural evidence, not a GB10 performance or numerical-execution claim.
| A/B format | Accumulator/result | Shapes | Additional rule |
|---|---|---|---|
| f16 | f16 or f32 | m16n16k16, m8n32k16, m32n8k16; 16x16 logical grids | row/column A and B |
| bf16 | f32 | m16n16k16, m8n32k16, m32n8k16; 16x16 logical grids | PTX 7.0 / sm_80+ |
| tf32 | f32 | m16n16k8; 16x16 logical grids | PTX 7.0 / sm_80+ |
| f64 | f64 | m8n8k4; 8x8 logical grids | default/RN rounding |
| i8 or u8 | i32 | m16n16k16, m8n32k16, m32n8k16; 16x16 logical grids | wrap or finite saturation |
| i4 or u4 | i32 | m8n8k32 | A row-major, B column-major |
| b1 | i32 | m8n8k128 | A row-major, B column-major; XOR/AND popcount |
On architecture-specific Blackwell targets, the same whole-tile operation also selects native warp-level narrow floating-point MMA. Larger neutral M/N tiles are backend-owned m16n8 subdivisions; accumulators stay resident across proved chains and exact runtime-K loops.
| A/B format | Scale contract | Native PTX profile |
|---|---|---|
| FP8 E4M3/E5M2 | unscaled, byte storage | mixed types, K=16 or 32 |
| any FP8 E4M3/E5M2, FP6 E2M3/E3M2, or FP4 E2M1 A/B pair | UE8M0 block32 | mxf8f6f4 m16n8k32, scale vector 1 |
| packed FP4 E2M1 | UE8M0 block32 | MXFP4 m16n8k64, scale vector 2 |
| packed FP4 E2M1 | UE4M3 block16 | NVFP4 m16n8k64, scale vector 4 |
| structured 2:4 f16 or bf16 | compressed A + canonical uint8 masks | mma.sp m16n8k16, f32 accumulator/result |
The block-scaled profiles require PTX 8.8 and an architecture- or family-specific
sm_120a/sm_121a target. Raw sm_121 is deliberately rejected because it
does not promise architecture-specific instructions.
The sparse f16/bf16 profile requires PTX 7.1 and sm_80+, subgroup scope, M divisible by 16, N divisible by 8, and K=16. PTX translates each neutral mask to its lane metadata and uses ordered metadata from PTX 8.5 onward. Invalid dynamic masks are contract violations; the generated translation nevertheless clamps them to a defined safe selector before issuing the tensor instruction, rather than feeding an architecturally undefined metadata encoding to hardware. For adjacent N subtiles, compressed A and translated metadata are prepared once per M/K fragment and reused.
All live lanes in the subgroup must execute the operation uniformly with the same descriptor, pointer values, and runtime leading dimensions. The pointed-to tiles, leading dimensions, and base addresses must satisfy the selected PTX WMMA layout and alignment requirements; those are runtime preconditions and cannot generally be proved from a raw pointer. Generic, global, and workgroup tile memory are accepted. The shared verifier rejects pointer or runtime-stride expressions known to vary at the declared subgroup/workgroup scope.
The GB10 family is emitted at PTX 8.8 / sm_121a and assembled by CUDA 12.9
ptxas -arch=sm_121a in the validation suite. That proves syntax, register
tuples, target capability declarations, and zero-spill register ceilings. A
compute-12.0 development GPU additionally passes poisoned-padding
runtime-stride MMA and a 32x32x64
multi-tile f16/f32 GEMM against CPU oracles. The GEMM's runtime K loop loads C
once, executes one initial and three loop-carried WMMA updates in the resident
accumulator, and stores D once. A K=15 guard case proves that the commit is
bypassed when no complete K tile exists. A separate four-K-tile straight-line
chain has the same one-load/one-store property. Staged-pipeline tests cover
both a double-buffered two-tile schedule and a four-group schedule. The latter
issues eight native 16-byte copies, drains pending groups with waits 3 through
0, retains one accumulator through all four wait/publication handoffs, and
executes four MMAs with one C load and one D store. Distinct per-tile numerical
oracles pass on the same device. Native mixed FP8, mixed E3M2/E2M3 dense FP6,
MXFP4/UE8M0 block32, and NVFP4/UE4M3 block16 each pass direct whole-tile,
resident-chain, and runtime-K residency oracles with independently varied rows,
columns, scale chunks, logical strides, and poisoned padding. The public-builder
gate additionally emits and assembles all 25 documented mxf8f6f4 FP8/FP6/FP4
A/B type pairs for both whole-tile and exact whole-matrix operations. Canonical
byte-aligned FP6 fragments use an exact three-byte load per four values;
transpose/noncanonical cases retain a boundary-safe bit gather or exact region
replay.
A separate earlier source-expressed tail-complete GEMM keeps the resident WMMA path for every full
16x16 output tile and full K prefix, then uses typed f16-to-f32 scalar work for
M, N, and K edges in the same kernel. A 19x23x21 launch simultaneously proves
tensor interior, row/column edges, K-tail publication, runtime padded strides,
and untouched poison padding; a 16x16x7 launch proves the no-tensor scalar path.
That fixture remains useful device evidence for explicit composition. The new
tensor_matmul operation now makes native full-K residency plus exact M/N/K
edge selection backend-generated for each bounded region. Generating the 2-D
launch grid, batch pointer slicing, layout transforms, and architecture-tuned
region size for an arbitrary higher-level GEMM is still open.
Structured 2:4 f16/bf16 source and public-builder whole-tile kernels emit the
expected ordered mma.sp form for sm_121a; bounded tensor_matmul regions
now combine that native K16 interior with exact sparse M/N/K replay. CUDA 12.9
ptxas accepts whole tiles at 40 registers and bounded regions at 48 registers,
with zero spills and no tcgen05 dependency. A-fragment coordinates and
the lower-row/upper-row halves of the metadata word have also been checked
against the PTX 8.8 fragment equations and Figure 119. A CPU oracle checks
compressed selected-value order, compact masks, transpose, odd K, K=0, and
no-op bounds. This remains CPU semantic plus offline structural evidence only;
GPU sparse numerics, malformed-input behavior beyond defensive clamping,
performance, and actual GB10 execution remain unqualified gates.
With -O, adjacent exact compositions of the form D=A0*B0+C; D=A1*B1+D; ... become one backend-neutral tensor-chain instruction. Fusion is
legal only when descriptor semantics, result/accumulator type, C/D layout,
output pointer, and output leading dimension agree and no observable IR
instruction intervenes. PTX uses an inspectable tuple-pressure model (64
logical registers before sm_90, 96 from sm_90) and annotates its choice as
mtlc.tensor_chain resident ... or replay .... A resident chain performs one
C load and one D store; an over-budget chain replays the exact component
operations. Another backend may always replay without changing the frontend or
shared IR.
--gpu-tensor-tuple-budget=N overrides that PTX ceiling for variant generation;
N=0 restores the architecture default. The equivalent public-context policy
is mtlc_context_set_ptx_tensor_tuple_budget. This is deliberately backend-only:
the optimized neutral chain/loop/pipeline remains identical, while PTX chooses
resident or replay. It is a hook for measurement-driven tuning, not an
occupancy or performance claim by itself; final registers/spills come from
ptxas, and useful thresholds require real workload timing.
tools/gpu/ptxas_profile.py --arch sm_121a --require-zero-spills -o resources.json kernel.ptx turns that offline assembler evidence into stable
JSON: input hashes plus per-function registers, spill traffic, stack, barriers,
shared memory, constant memory, and lexical PTX instruction-class counts.
--max-registers=N makes the same command a reproducible resource gate. The
tool deletes its temporary cubin and never creates a CUDA context, queries a
device, loads a module, or launches code; its output is offline evidence, not
runtime evidence.
tools/gpu/ptxas_select.py compares two or more such profiles under an explicit
resource model: threads per block, registers/shared memory per execution unit,
warp and block limits, and allocation granularities. It computes register-,
shared-memory-, and warp-limited residency bounds, rejects spills by default,
and emits a per-entry Pareto frontier over occupancy, resource headroom, and
static global-memory instruction count. The default pareto policy deliberately
leaves a resident-versus-replay tradeoff unresolved; --policy=occupancy or
--policy=traffic makes that policy explicit, while --require-selection
fails closed on ambiguity. The JSON always records performance_claim: false:
static counts and modeled occupancy are inputs to real autotuning, not a
substitute for timed GB10 workloads.
For the checked-in DGX workflow, the explicit 12.x upper-limit model comes from NVIDIA's compute-capability resource tables: 64K 32-bit registers, 100KB shared memory, 48 resident warps, 24 resident blocks, and 32 threads per warp. The workflow deliberately uses allocation unit 1 because that table does not specify the allocation granularity; its result is therefore an upper bound and the archived JSON records the assumption.
For measured selection, pass --policy=measured --measurements=timings.json.
The input follows tools/gpu/variant_measurements.schema.json and binds every
candidate to the SHA-256 of its exact resource profile, every entry to a
workload hash and launch shape, and the run to a device UUID, host architecture,
compute capability, integrated/discrete-memory fact, and driver version.
samples_ns[i] across candidates is one paired round; collectors must warm up
separately and interleave/randomize candidate order so index-aligned samples do
not encode drift. The selector never collects those samples itself.
The default measured gate requires at least 21 positive pairs, chooses the
lowest median only provisionally, then compares it against every eligible
candidate using a two-sided paired sign test with Bonferroni-corrected
alpha=0.01 and a median paired speedup of at least 1.01. The thresholds are
recorded and configurable. If either statistical significance or practical
effect is missing, selection remains null; even a passing local selection keeps
performance_claim: false because it is evidence for that bound workload and
device, not a general competitive claim.
The same shared optimizer also recognizes an exact linear runtime loop after an
initial tile. It requires one connected MMA update, identical descriptor and
output-stride semantics, a single loop entry/backedge, and a body containing
only register-local address/counter arithmetic. Loads, stores, calls, atomics,
barriers, a second MMA, or any other observable body operation refuse the
transform. The optimizer inserts a neutral tensor_commit on only the loop's
exit edge; outer guards that share the original end label bypass that commit.
The shared GPU verifier independently rebuilds the CFG and rechecks the unique
start/update/commit shape before either backend may consume the metadata.
PTX applies the same tuple budget and emits mtlc.tensor_loop resident ... or
replay ...; replay executes the original load/MMA/store semantics, while the
resident path loads C once and delays D until the commit. A libmtlc frontend
gets this result from ordinary builder control flow and tensor descriptors—no
Mettle-frontend marker exists. --dump-ir exposes the neutral residency roles,
and --explain reports the legality decision plus the actual PTX/SPIR-V backend
boundary.
The optimizer also recognizes a finite straight-line staged composition:
two or more connected tensor MMAs, with every pair separated by neutral async
completion followed by an acq-rel/seq-cst workgroup publication barrier. It
marks the maximal proved group with pipeline scope and inserts one neutral
commit after the last MMA. Shared verification requires the start, every
update, and commit in one basic block; an ordered WAIT-to-BARRIER handoff at
every transition; no intervening observation of D; identical
descriptor/output/stride connectivity; and only pure scalar work around each
handoff. PTX reports
mtlc.tensor_pipeline resident ... or replay ...; the resident form carries
the accumulator tuple through wait and barrier. The public builder test creates
the complete copy/tensor pipeline without frontend-private metadata and
receives the same optimization.
This accepts an arbitrary explicit N-stage sequence within the asynchronous group contract; it does not hard-code double buffering. The compiler does not yet generate the tile loop or buffer rotation, unroll an arbitrary MMA schedule, or model occupancy from assembler feedback. Native FP8/FP6/MXFP4/NVFP4 selection is implemented, but broader generated scheduling remains compiler work, not implied by this legality foundation.
Native mixed FP8, dense mixed FP6, scaled MXFP4/NVFP4, and the canonical structured-2:4 f16/bf16 profile are lowered by PTX without exposing fragments to the frontend. Other sparse ratios and unsupported type/transpose/shape combinations remain represented but are rejected with a capability diagnostic. The current SPIR-V OpenCL 2.0 profile likewise rejects tensor MMA because it has no enabled cooperative-matrix capability; adding a newer SPIR-V device profile is separate backend work, not a frontend redesign.
tensor_transfer_workgroup moves one complete rank-1 through rank-5 rectangular
tile between global and workgroup storage. Its source contract contains only
logical extents, byte strides, tile extents, per-dimension element strides,
signed coordinates, zero-fill/discard bounds, element format, direction, and
workgroup scope. Dimension 0 is contiguous and fastest-changing. No CUDA tensor
map, async proxy, transaction barrier, warp, or bank layout appears in source or
shared IR.
An optional view: uint8* is an opaque provider acceleration token. The raw
pointer plus logical descriptor remain authoritative: no view or a null view
selects cooperative replay, and a target that cannot preserve the descriptor
must replay or reject. The PTX backend additionally falls back at runtime when
the view is not 64-byte aligned or the workgroup address is not 16-byte aligned.
Valid neutral operations with a
dimension-0 element stride, a sub-16-byte inner TMA box, an encoded box above
256, a stride above 8, or incompatible global geometry never enter the native
path.
For the current NVIDIA provider, a non-null view points to an immutable
CUtensorMap in global memory whose address, rank, dimensions, and strides
exactly match the raw operand and descriptor. It uses tiled encoding, no
interleave, no shared-memory swizzle, boxDim[d] = tile_extent[d] * element_stride[d] (with dimension-0 stride equal to one), and the corresponding
element strides. PTX acquires a host-copied map through the tensor-map proxy.
Global-to-workgroup TMA initializes a compiler-owned transaction barrier,
publishes that initialization to the async proxy, issues the tensor copy,
arrives with the expected byte count, waits for phase completion, publishes the
tile, and invalidates the barrier. Workgroup-to-global makes every producer
proxy-fence its own shared writes before the workgroup rendezvous, then the
elected thread issues and drains one bulk group.
Portable PTX replays both directions cooperatively with rank-aware coordinate
unravelling and exact out-of-bounds behavior. The OpenCL 2.0 SPIR-V profile
currently rejects the operation explicitly because its exact cooperative
lowering is not implemented. Native TMA PTX assembles for sm_121a with exact
ordering and zero-spill gates, but device execution is quarantined and remains
unqualified; see the GPU validation tiers. Offline
assembly is not a hardware-safety or correctness claim.
The real-device suite also expresses numerically stable f32 row softmax and affine layer normalization directly in Mettle. One subgroup owns each row while each lane loops over a strided slice, so runtime column counts may exceed the subgroup width and need not be multiples of it. Both kernels accept independent padded row strides and use 64-bit address arithmetic. Along with the multi-output/multi-K-tile and tail-complete tensor/scalar f16/f32 GEMMs above, they pass CPU numerical oracles on the compute-12.0 development GPU.
These are correctness/expressiveness baselines plus three exact accumulator
residency forms—straight-line chains, a single-update runtime K loop, and
explicit N-stage asynchronous pipelines—not claims of general fusion or peak
performance. Bounded-region M/N/K tail selection is now backend-generated for
the documented tensor_matmul families; automatic launch-grid/batch tiling,
pipeline rotation, multi-exit and opaque-fragment biased epilogue residency,
attention, general quantized matmul
generation, low-precision scalar types, measurement collection/autotuning, and
GB10 benchmark evidence remain required.
The host is a normal Mettle program. Import std/gpu, set up device buffers
explicitly, then launch with dispatch:
import "std/io";
import "std/mem";
import "std/gpu";
fn main() -> int32 {
if (gpu_init() == 0) { println(cstr("GPU init failed")); return 1; }
// load the emitted PTX and resolve the kernel
var fp: cstring = fopen(cstr("kernels.ptx"), cstr("rb"));
var ptx: uint8* = (uint8*)malloc(65536);
var len: int64 = fread((cstring)ptx, 1, 65535, fp); fclose(fp); ptx[len] = 0;
var mod: int64 = gpu_module(ptx);
var vadd: int64 = gpu_func(mod, cstr("vadd"));
var n: int32 = 1 << 20;
var bytes: int64 = (int64)n * 4;
var ha: float32* = (float32*)malloc(bytes);
var hb: float32* = (float32*)malloc(bytes);
var hc: float32* = (float32*)malloc(bytes);
var i: int32 = 0;
while (i < n) { ha[i] = (float32)i; hb[i] = (float32)(2 * i); i = i + 1; }
// device buffers (you own VRAM)
var da: int64 = gpu_malloc(bytes);
var db: int64 = gpu_malloc(bytes);
var dc: int64 = gpu_malloc(bytes);
gpu_to_device(da, (uint8*)ha, bytes);
gpu_to_device(db, (uint8*)hb, bytes);
// launch: one line replaces param-packing + cuLaunchKernel + sync
dispatch vadd[(n + 255) / 256, 256](da, db, dc, n);
gpu_to_host((uint8*)hc, dc, bytes);
gpu_free(da); gpu_free(db); gpu_free(dc);
return 0;
}
dispatch KERNEL[grid, block](arg0, arg1, ...);
dispatch KERNEL[
grid: (grid_x, grid_y, grid_z),
block: (block_x, block_y, block_z),
shared: dynamic_shared_bytes,
stream: stream_handle
](arg0, arg1, ...);
KERNELis a handle (theint64returned bygpu_func).- The compact form supplies one integer grid and block dimension; the other axes are one and dynamic shared bytes/stream are zero.
- The named form requires exactly three integer dimensions in both
gridandblock.sharedandstreamare optional, default to zero, and named controls may be reordered. Every statically known dimension must be positive; shared bytes are an integer and a stream is an integer or pointer handle. - The arguments are passed by value. Device pointers are
int64handles; scalars (int32,float32, ...) are forwarded with their natural width.
dispatch becomes a typed, target-neutral launch operation in IR. Only host
lowering marshals the argument cells and calls the stable
mtlc_gpu_launch_checked runtime-provider ABI. The bundled CUDA provider maps
that ABI to cuLaunchKernel; another frontend uses mtlc_gpu_launch to build
the same operation without depending on Mettle syntax. A failed enqueue is not
silently discarded: the checked statement contract terminates the process with
the provider error code. Use gpu_launch_3d directly when code needs to inspect
and recover from the returned status. Allocation and copies remain explicit.
For explicit concurrency, std/gpu also exposes nonblocking streams, events,
asynchronous copies, stream-ordered allocation/free, managed memory, and
gpu_launch_3d / gpu_launch_on. Those are runtime foundations; they do not
yet constitute a hardware-validated scheduler or graph implementation.
# 1. compile optimized kernels for the local GPU (auto-detected via the
# driver; falls back to the DGX Spark GB10 profile when no GPU is visible)
mettle -O --emit-ptx kernels.mettle -o kernels.ptx
# Or pin a profile explicitly, for example DGX Spark GB10:
mettle -O --emit-ptx --gpu-arch=gb10 kernels.mettle -o kernels.ptx
# A forward-compatible development baseline is also available:
mettle -O --emit-ptx --gpu-arch=portable kernels.mettle -o kernels.ptx
# 2. build the host, linking the CUDA driver import stub (build-time only)
mettle --build host.mettle -o host.exe \
--link-arg "<CUDA>/lib/x64/cuda.lib" # Windows x86-64The host links nvcuda, the OS driver, as an explicit vendor API. There is no
bundled CUDA DLL. At run time the driver JITs
the PTX to SASS for the installed GPU.
GPU -O is intentionally backend-agnostic. It runs shared scalar/CFG
transformations only over kernel-reachable device functions and retains
address spaces, memory order/scope, barriers, subgroup collectives, and tensor
descriptors as semantic IR. It may combine an exact straight-line tensor
accumulation into a neutral chain or license the exact verified loop-carried
residency region described above; backend selection remains separate. The x86
SIMD/idiom pipeline and ML optimizer are not allowed to shape PTX or SPIR-V
modules.
On DGX Spark, build and run the Mettle compiler natively. Ordinary compile and
--build commands emit and link AArch64 ELF64 objects. AAPCS64 external calls,
global and string addresses, owned runtime linkage, and the eleven argument
cuLaunchKernel call shape are CI-gated on native Arm. --emit-arm64 remains a
separate self-contained smoke executable for bring-up, while
mtlc_emit(MTLC_ARCH_ARM64) is the relocatable cross-host product.
The repository CI uses a hardware-free CUDA-shaped provider to validate the
complete host call ABI. Real driver loading, PTX JIT, launch, and result checks
remain part of the DGX acceptance suite because generic Arm runners do not have
GB10 hardware or libcuda.
Build the compiler itself for the Spark CPU with
make DGX_SPARK=1; with GCC 15 or LLVM 21, prefer
make DGX_SPARK=1 DGX_SPARK_CFLAGS=-mcpu=gb10.
Before treating a build as a Spark release, run the strict native gate:
bash tests/gpu/run_hardware_tests.sh --require-gb10 --sanitizerIt compiles optimized kernels, executes CPU-oracle comparisons through the
CUDA Driver, then runs compute-sanitizer memcheck and racecheck. Strict mode
refuses to pass unless the harness itself is AArch64 and CUDA reports an
integrated compute-12.1 device. The exact cases and non-claims are documented
in tests/gpu/README.md.
The strict release gate intentionally excludes experimental TMA. Its separate three-way opt-in is documented with the recovery requirements in that validation guide; do not enable it on a workstation.
The same kernels compile to SPIR-V with --emit-spirv, targeting the
OpenCL 2.0 execution environment (Physical64 addressing, the Kernel
capability, the OpenCL memory model). This is the flavor that fits Mettle's
kernel ABI unchanged: kernels take raw typed pointers and do pointer arithmetic
plus loads/stores, which is the OpenCL/CUDA model, not the Vulkan
descriptor-buffer model.
mettle --emit-spirv kernels.mettle -o kernels.spvThe output is a binary SPIR-V module (one OpEntryPoint ... Kernel per kernel)
that an OpenCL runtime consumes with clCreateProgramWithIL. The same source
constructs as the PTX path are supported: arithmetic, comparisons, if/while
(including &&/|| and nesting), pointer indexing, casts, the gpu_* index
built-ins (mapped to the OpenCL work-item built-ins, so thread reads
LocalInvocationId, block reads WorkgroupId, block_dim reads
WorkgroupSize, and grid_dim reads NumWorkgroups), static workgroup/private
arrays, launch-sized workgroup views, explicit barrier(...) (an
OpControlBarrier), the f32 math intrinsics
(an OpExtInst from OpenCL.std),
h2f/f2h, the unsigned u32/u64 atomic load/store/RMW/CAS family, and the portable subgroup subset above. SPIR-V
subgroup modules require the OpenCL cl_khr_subgroups device extension.
Modules using u64 atomics declare Int64Atomics and require both
cl_khr_int64_base_atomics and cl_khr_int64_extended_atomics; u32-only
modules remain valid against the mandatory OpenCL 2.0 capability set.
The current OpenCL 2.0 profile does not enable a cooperative-matrix extension,
so it rejects tensor_mma explicitly rather than scalarizing it invisibly.
libmtlc frontends can make pointer address spaces and atomic order/scope
explicit with mtlc_type_pointer_in, mtlc_intrinsic_memory, and
mtlc_atomic_compare_exchange. PTX preserves
global/workgroup/generic spaces and scoped C/C++ atomic ordering (including the
specified sequentially-consistent fence sequence); SPIR-V emits the matching
OpenCL storage class, Scope, ordering, and memory-class bits. Mettle source now
exposes static workgroup/private storage, launch-sized workgroup views,
configurable workgroup barriers, and native type-directed atomics without
extern declarations. The operations are load, store, fetch
add/sub/min/max/and/or/xor, exchange, and compare-exchange for uint32/uint64;
named options select address space, order, failure order, and scope.
Control flow maps directly onto SPIR-V basic blocks (OpBranch /
OpBranchConditional), exactly as the PTX path maps it onto bra. SPIR-V's
structured-control-flow rules (OpSelectionMerge/OpLoopMerge) are mandated
only by the Shader capability, so Kernel (OpenCL) modules may branch freely,
which spirv-val --target-env opencl2.0 confirms.
- Without
--gpu-arch, the CLI targets the local GPU: it queries the driver (nvidia-smi) for the device's compute capability and selects the matchingsm_NNtarget, using the architecture-specificavariant fromsm_90onward so the full tensor instruction surface is available. When no GPU or driver is visible (headless or cross-compiling hosts), the default is.version 8.8/.target sm_121afor GB10.--gpu-archalways wins:portableemits PTX 6.4 /compute_75; rawsm_NNandcompute_NNtargets plus--ptx-version=M.mare available for integration. dispatchis a checked enqueue: provider failure terminates the process. Status-returninggpu_launch_3dremains available when recovery is required. Provider-neutral launch attributes beyond geometry, dynamic shared bytes, and stream are not represented yet.- Kernels and host code live in separate files (the kernel file is compiled
with
--emit-ptx; the host with--build).
See examples/gpu_vadd/ for the complete x86-64 CUDA host example and
tests/gpu/compute_kernels.mettle for the assembler/validator kernel matrix.
There is not yet a production LLM kernel library in this repository.