Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions zig/build/cuda.zig
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@ pub fn hostTests(b: *std.Build, draft_ids: *std.Build.Module, build_options: *st
step.dependOn(&b.addRunArtifact(b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("zig/build/check_fatbins.zig"), .target = host }) })).step);
const cuda = runtime(b, host, .debug, &.{});
const mods = family(b, host, .debug, cuda, draft_ids);
const contract_arch = b.createModule(.{ .root_source_file = b.path("zig/tests/cuda/contract_arch_test.zig"), .target = host, .optimize = .debug, .link_libc = true, .imports = &.{ .{ .name = "cuda", .module = cuda }, .{ .name = "core", .module = mods.core } } });
step.dependOn(&b.addRunArtifact(b.addTest(.{ .root_module = contract_arch })).step);
const native_modules = engines(b, host, .debug, cuda, mods.core, mods.lanes, mods.nemotron, mods.heat);
const native = native_modules.engines;
// the HTTP server's unit tests (zig/src/server/root.zig), which the macOS build runs, on Linux hosts too
Expand Down
123 changes: 123 additions & 0 deletions zig/tests/cuda/contract_arch_test.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
//! Host-only checks of the CUDA contract's architecture selection and its real gate-3 routing.
const std = @import("std");
const mm = @import("contract_mm.zig");
const contract = @import("core").contract;
const g = contract.gates;

test "contract architecture: older SMs omit only the standalone cluster kernel" {
for ([_]u32{ 80, 86, 89 }) |sm| {
var count: usize = 0;
for (std.enums.values(mm.Kind)) |kind| {
var m: mm.Mm = .{ .rig = undefined, .kind = kind, .sm = sm };
const k = m.kernel();
try std.testing.expectEqual(kind == .qmm_group, k == null);
if (k != null) count += 1;
}
try std.testing.expectEqual(@as(usize, 6), count);
}
}

test "contract architecture: SM90 and Blackwell retain every kernel and the GEMV bit comparisons" {
for ([_]u32{ 90, 120, 121 }) |sm| {
var count: usize = 0;
for (std.enums.values(mm.Kind)) |kind| {
var m: mm.Mm = .{ .rig = undefined, .kind = kind, .sm = sm };
const k = m.kernel().?;
count += 1;
try std.testing.expectEqual(kind == .lane_gemv or kind == .lane_gemv_split, k.spec.replaces != null);
}
try std.testing.expectEqual(@as(usize, 7), count);
}
}

test "contract architecture: legacy GEMV keeps every field except the unavailable replacement" {
for ([_]u32{ 80, 86, 89 }) |sm| for ([_]mm.Kind{ .lane_gemv, .lane_gemv_split }) |kind| {
var modern: mm.Mm = .{ .rig = undefined, .kind = kind, .sm = 121 };
var legacy: mm.Mm = .{ .rig = undefined, .kind = kind, .sm = sm };
var want = modern.kernel().?.spec;
want.replaces = null;
try std.testing.expectEqualDeep(want, legacy.kernel().?.spec);
};
}

test "contract architecture: the legacy FP4 lane names the reduction fallback it actually selects" {
for ([_]u32{ 80, 86, 89 }) |sm| {
var m: mm.Mm = .{ .rig = undefined, .kind = .lane_f32, .sm = sm };
const k = m.kernel().?;
try std.testing.expect(std.mem.indexOf(u8, k.spec.name, "reduce_kernel") != null);
}
}

const Cpu = struct {
bytes: [2]u8 = .{ 0x80, 0x3f }, // bf16 1
baseline: [2]u8 = .{ 0x80, 0x3f },
references: usize = 0,
replacements: usize = 0,
const vt: g.VTable = .{ .prepare = prepare, .run = run, .output = output, .inputs = inputs, .intact = intact, .overlap = overlap, .count = count, .reference = reference, .replaced = replaced };

fn self(ctx: *anyopaque) *Cpu {
return @ptrCast(@alignCast(ctx));
}
fn prepare(_: *anyopaque, _: contract.spec.Shape, _: g.Fill, _: u64) anyerror!bool {
return true;
}
fn run(_: *anyopaque) anyerror!void {}
fn output(ctx: *anyopaque) anyerror![]const u8 {
return &self(ctx).bytes;
}
fn inputs(_: *anyopaque) anyerror![]const u8 {
return &.{};
}
fn intact(_: *anyopaque) anyerror!bool {
return true;
}
fn overlap(_: *anyopaque) bool {
return false;
}
fn count(_: *anyopaque) usize {
return 1;
}
fn reference(ctx: *anyopaque, want: []f64, mag: []f64) anyerror!void {
self(ctx).references += 1;
want[0] = 1;
mag[0] = 1;
}
fn replaced(ctx: *anyopaque) anyerror![]const u8 {
self(ctx).replacements += 1;
return &self(ctx).baseline;
}
};

test "contract architecture: legacy gate 3 really checks FP64 and rejects a planted bad output" {
const old = g.quiet;
g.quiet = true;
defer g.quiet = old;
for ([_]mm.Kind{ .lane_gemv, .lane_gemv_split }) |kind| {
var m: mm.Mm = .{ .rig = undefined, .kind = kind, .sm = 89 };
var cpu: Cpu = .{};
const k: g.Kernel = .{ .spec = m.kernel().?.spec, .ctx = &cpu, .vt = &Cpu.vt };
try std.testing.expectEqual(g.Verdict.pass, try g.shapes(std.testing.allocator, k, 1));
try std.testing.expect(cpu.references > 0);
try std.testing.expectEqual(@as(usize, 0), cpu.replacements);
cpu.bytes = .{ 0, 0 };
try std.testing.expectEqual(g.Verdict.fail, try g.shapes(std.testing.allocator, k, 1));
try std.testing.expectEqual(@as(usize, 0), cpu.replacements);
}
}

test "contract architecture: modern gate 3 retains bit equality and rejects a planted baseline difference" {
const old = g.quiet;
g.quiet = true;
defer g.quiet = old;
for ([_]mm.Kind{ .lane_gemv, .lane_gemv_split }) |kind| {
var m: mm.Mm = .{ .rig = undefined, .kind = kind, .sm = 121 };
var cpu: Cpu = .{};
const k: g.Kernel = .{ .spec = m.kernel().?.spec, .ctx = &cpu, .vt = &Cpu.vt };
try std.testing.expectEqual(g.Verdict.pass, try g.shapes(std.testing.allocator, k, 1));
try std.testing.expect(cpu.replacements > 0);
try std.testing.expectEqual(@as(usize, 0), cpu.references);
cpu.baseline = .{ 0, 0 };
try std.testing.expectEqual(g.Verdict.fail, try g.shapes(std.testing.allocator, k, 1));
try std.testing.expectEqual(@as(usize, 0), cpu.references);
}
}
11 changes: 9 additions & 2 deletions zig/tests/cuda/contract_mm.zig
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ pub fn spec(kind: Kind) c.Spec {
pub const Mm = struct {
rig: rig_.Rig,
kind: Kind,
sm: u32,
affine4: ?*const qlinear.Affine4Kernels = null,
split: ?*const qlinear.Split = null,
prompt: ?*const nvfp4.Prompt = null,
Expand All @@ -75,8 +76,14 @@ pub const Mm = struct {
self.rig.deinit();
}

pub fn kernel(self: *Mm) g.Kernel {
return .{ .spec = spec(self.kind), .ctx = self, .vt = &vt };
pub fn kernel(self: *Mm) ?g.Kernel {
if (self.sm < 90 and self.kind == .qmm_group) return null;
var declared = spec(self.kind);
if (self.sm < 90) {
declared.replaces = null;
if (self.kind == .lane_f32) declared.name = "cuda qmmf FP4 fp32 out (slices by reduce_kernel<true>, architecture fallback)";
}
return .{ .spec = declared, .ctx = self, .vt = &vt };
}

const vt = g.hooks(Mm);
Expand Down
12 changes: 10 additions & 2 deletions zig/tests/cuda/contract_tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,18 @@ pub fn run(gpu: check.Gpu) !void {
.lane_f32_reduce => &lane_reduce,
else => null,
};
m.* = .{ .rig = .{ .gpu = gpu, .s = s }, .kind = kind, .affine4 = &affine4, .split = &split, .prompt = &prompt, .lane = by_lane };
m.* = .{ .rig = .{ .gpu = gpu, .s = s }, .kind = kind, .sm = cap, .affine4 = &affine4, .split = &split, .prompt = &prompt, .lane = by_lane };
}
defer for (&mm) |*m| m.deinit();
for (&mm) |*m| try kernels.append(a, m.kernel());
for (&mm) |*m| {
const k = m.kernel() orelse {
std.debug.print("N/A cuda qmm_group (cluster tile): needs sm_90 or newer, this GPU is sm_{d}\n", .{cap});
continue;
};
if (cap < 90 and (m.kind == .lane_gemv or m.kind == .lane_gemv_split))
std.debug.print("N/A {s} replacement bits: cluster baseline needs sm_90; gate 3 uses the float64 bound\n", .{k.spec.name});
try kernels.append(a, k);
}
var rank = rank_k.Rank{ .rig = .{ .gpu = gpu, .s = s }, .sum = &rank_sum };
defer rank.deinit();
try kernels.append(a, rank.kernel());
Expand Down
Loading