lib/accy/src/kernel/library/random/test.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const gpu = @import("gpu");
  3 const choir_abi = @import("choir_abi");
  4 const base = @import("base.zig");
  5 const random = @import("root.zig");
  6 
  7 test {
  8     @import("test_discovery").discover(random);
  9     @import("test_discovery").discover(@This());
 10 }
 11 
 12 const entry = base.entry;
 13 const kernel = base.kernel;
 14 const tuning = base.tuning;
 15 
 16 const Philox = random.Philox;
 17 const Threefry = random.Threefry;
 18 const Squares = random.Squares;
 19 const Feistel = random.Feistel;
 20 const PhiloxFold = random.PhiloxFold;
 21 const ThreefryFold = random.ThreefryFold;
 22 const SquaresFold = random.SquaresFold;
 23 const PhiloxKeySplit = random.PhiloxKeySplit;
 24 const PhiloxKeyUniform = random.PhiloxKeyUniform;
 25 const PhiloxKeyCounterUniform = random.PhiloxKeyCounterUniform;
 26 const Philox8F32 = random.Philox8F32;
 27 const Threefry8F32 = random.Threefry8F32;
 28 const PhiloxRuntimeFamilyI32 = random.PhiloxRuntimeFamilyI32;
 29 const ThreefryRuntimeFamilyI32 = random.ThreefryRuntimeFamilyI32;
 30 const SquaresRuntimeFamilyI32 = random.SquaresRuntimeFamilyI32;
 31 const FeistelRuntimeFamilyI32 = random.FeistelRuntimeFamilyI32;
 32 const PhiloxKeySplitRuntimeFamily = random.PhiloxKeySplitRuntimeFamily;
 33 const PhiloxKeyUniformRuntimeFamilyF32 = random.PhiloxKeyUniformRuntimeFamilyF32;
 34 const PhiloxKeyCounterUniformRuntimeFamilyF32 = random.PhiloxKeyCounterUniformRuntimeFamilyF32;
 35 const PhiloxFoldRuntimeFamilyF32 = random.PhiloxFoldRuntimeFamilyF32;
 36 const ThreefryFoldRuntimeFamilyI32 = random.ThreefryFoldRuntimeFamilyI32;
 37 const SquaresFoldRuntimeFamilyF32 = random.SquaresFoldRuntimeFamilyF32;
 38 const philox_lanes = random.philox_lanes;
 39 const philox_default_rounds = random.philox_default_rounds;
 40 const philox_family_version = random.philox_family_version;
 41 const threefry_lanes = random.threefry_lanes;
 42 const threefry_default_rounds = random.threefry_default_rounds;
 43 const squares_default_key = random.squares_default_key;
 44 const philoxBlock = random.philoxBlock;
 45 const threefryBlock = random.threefryBlock;
 46 const squaresBlock = random.squaresBlock;
 47 const uniformFromBits = random.uniformFromBits;
 48 const philoxInstanceTarget = random.philoxInstanceTarget;
 49 const philoxInstanceEntryName = random.philoxInstanceEntryName;
 50 const philoxFamilyTarget = random.philoxFamilyTarget;
 51 const threefryInstanceTarget = random.threefryInstanceTarget;
 52 const threefryFamilyTarget = random.threefryFamilyTarget;
 53 const philoxFamilyTuningKey = random.philoxFamilyTuningKey;
 54 const threefryFamilyTuningKey = random.threefryFamilyTuningKey;
 55 const squaresFamilyTuningKey = random.squaresFamilyTuningKey;
 56 const createPhiloxFamilyArtifact = random.createPhiloxFamilyArtifact;
 57 const createPhiloxKeySplitFamilyArtifact = random.createPhiloxKeySplitFamilyArtifact;
 58 const createPhiloxKeyUniformFamilyArtifact = random.createPhiloxKeyUniformFamilyArtifact;
 59 const createPhiloxKeyCounterUniformFamilyArtifact = random.createPhiloxKeyCounterUniformFamilyArtifact;
 60 const philoxFoldUniformReference = random.philoxFoldUniformReference;
 61 const threefryFoldBitsReference = random.threefryFoldBitsReference;
 62 const createPhiloxFoldFamilyArtifact = random.createPhiloxFoldFamilyArtifact;
 63 const philoxFoldRuntimeArguments = random.philoxFoldRuntimeArguments;
 64 const philoxFoldFamilyTuningKey = random.philoxFoldFamilyTuningKey;
 65 const threefryFoldFamilyTuningKey = random.threefryFoldFamilyTuningKey;
 66 const squaresFoldFamilyTuningKey = random.squaresFoldFamilyTuningKey;
 67 const squaresFoldUniformReference = random.squaresFoldUniformReference;
 68 const createSquaresFamilyArtifact = random.createSquaresFamilyArtifact;
 69 const squaresFamilySpecialization = random.squaresFamilySpecialization;
 70 const squaresInstanceFromSpecialization = random.squaresInstanceFromSpecialization;
 71 const philoxInstanceFromSpecialization = random.philoxInstanceFromSpecialization;
 72 const threefryInstanceFromSpecialization = random.threefryInstanceFromSpecialization;
 73 const philoxFamilySpecialization = random.philoxFamilySpecialization;
 74 const threefryFamilySpecialization = random.threefryFamilySpecialization;
 75 const feistelFamilyTarget = random.feistelFamilyTarget;
 76 const feistelFamilyEntryName = random.feistelFamilyEntryName;
 77 const feistelPermuteReference = random.feistelPermuteReference;
 78 const feistelInstanceValid = random.feistelInstanceValid;
 79 const feistelDomainBits = random.feistelDomainBits;
 80 const feistelRuntimeArguments = random.feistelRuntimeArguments;
 81 const Key = choir_abi.Key;
 82 
 83 fn randomFamilyTuningTestCapabilities(device_id: u32) gpu.BackendCapabilities {
 84     return .{ .identity = .{
 85         .backend = .cuda,
 86         .family = .nvidia_cuda,
 87         .name = "random-family-tuning-test-device",
 88         .vendor_id = 0x10de,
 89         .device_id = device_id,
 90     } };
 91 }
 92 
 93 test "random philox reference matches Random123 known answers" {
 94     try std.testing.expectEqual(
 95         [4]u32{ 0x6627e8d5, 0xe169c58d, 0xbc57ac4c, 0x9b00dbd8 },
 96         philoxBlock(10, .{ 0, 0, 0, 0 }, .{ 0, 0 }),
 97     );
 98     try std.testing.expectEqual(
 99         [4]u32{ 0x408f276d, 0x41c83b0e, 0xa20bc7c6, 0x6d5451fd },
100         philoxBlock(10, .{ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff }, .{ 0xffffffff, 0xffffffff }),
101     );
102     try std.testing.expectEqual(
103         [4]u32{ 0xd16cfe09, 0x94fdcceb, 0x5001e420, 0x24126ea1 },
104         philoxBlock(10, .{ 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344 }, .{ 0xa4093822, 0x299f31d0 }),
105     );
106     try std.testing.expectEqual(
107         [4]u32{ 0x5f6fb709, 0x0d893f64, 0x4f121f81, 0x4f730a48 },
108         philoxBlock(7, .{ 0, 0, 0, 0 }, .{ 0, 0 }),
109     );
110     try std.testing.expectEqual(
111         [4]u32{ 0x5207ddc2, 0x45165e59, 0x4d8ee751, 0x8c52f662 },
112         philoxBlock(7, .{ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff }, .{ 0xffffffff, 0xffffffff }),
113     );
114     try std.testing.expectEqual(
115         [4]u32{ 0x4dfccaba, 0x190a87f0, 0xc47362ba, 0xb6b5242a },
116         philoxBlock(7, .{ 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344 }, .{ 0xa4093822, 0x299f31d0 }),
117     );
118 }
119 
120 test "random threefry reference matches Random123 known answers" {
121     try std.testing.expectEqual(
122         [2]u32{ 0x6b200159, 0x99ba4efe },
123         threefryBlock(20, .{ 0, 0 }, .{ 0, 0 }),
124     );
125     try std.testing.expectEqual(
126         [2]u32{ 0x1cb996fc, 0xbb002be7 },
127         threefryBlock(20, .{ 0xffffffff, 0xffffffff }, .{ 0xffffffff, 0xffffffff }),
128     );
129     try std.testing.expectEqual(
130         [2]u32{ 0xc4923a9c, 0x483df7a0 },
131         threefryBlock(20, .{ 0x243f6a88, 0x85a308d3 }, .{ 0x13198a2e, 0x03707344 }),
132     );
133     try std.testing.expectEqual(
134         [2]u32{ 0x9d1c5ec6, 0x8bd50731 },
135         threefryBlock(13, .{ 0, 0 }, .{ 0, 0 }),
136     );
137     try std.testing.expectEqual(
138         [2]u32{ 0xfd36d048, 0x2d17272c },
139         threefryBlock(13, .{ 0xffffffff, 0xffffffff }, .{ 0xffffffff, 0xffffffff }),
140     );
141     try std.testing.expectEqual(
142         [2]u32{ 0xba3e4725, 0xf27d669e },
143         threefryBlock(13, .{ 0x243f6a88, 0x85a308d3 }, .{ 0x13198a2e, 0x03707344 }),
144     );
145 }
146 
147 test "random uniform conversion stays in the unit interval" {
148     try std.testing.expectEqual(@as(f32, 0.0), uniformFromBits(0));
149     const top = uniformFromBits(0xffffffff);
150     try std.testing.expect(top < 1.0);
151     try std.testing.expect(top >= 0.0);
152     const mid = uniformFromBits(0x80000000);
153     try std.testing.expectApproxEqAbs(@as(f32, 0.5), mid, 0.0000002);
154 }
155 
156 test "random philox entry writes Random123 words through the uniform map on CPU" {
157     var dst = @as([8]f32, @splat(0));
158 
159     try Philox8F32.runCpu(std.testing.allocator, Philox8F32.Limits.testing, &.{
160         kernel.argumentBuffer(f32, dst[0..]),
161         kernel.argumentI32(0),
162         kernel.argumentI32(0),
163     });
164 
165     var expected: [8]f32 = undefined;
166     for (0..2) |generator| {
167         const words = philoxBlock(philox_default_rounds, .{ @intCast(generator), 0, 0, 0 }, .{ 0, 0 });
168         for (words, 0..) |word, lane| expected[generator * 4 + lane] = uniformFromBits(word);
169     }
170     try std.testing.expectEqualSlices(f32, expected[0..], dst[0..]);
171 }
172 
173 test "random threefry entry matches reference blocks on CPU" {
174     var dst = @as([8]f32, @splat(0));
175 
176     try Threefry8F32.runCpu(std.testing.allocator, Threefry8F32.Limits.testing, &.{
177         kernel.argumentBuffer(f32, dst[0..]),
178         kernel.argumentI32(7),
179         kernel.argumentI32(11),
180     });
181 
182     var expected: [8]f32 = undefined;
183     for (0..4) |generator| {
184         const words = threefryBlock(threefry_default_rounds, .{ @intCast(generator), 0 }, .{ 7, 11 });
185         for (words, 0..) |word, lane| expected[generator * 2 + lane] = uniformFromBits(word);
186     }
187     try std.testing.expectEqualSlices(f32, expected[0..], dst[0..]);
188 }
189 
190 test "random philox runtime family emits raw bits with tail guard" {
191     const allocator = std.testing.allocator;
192     const compiled = Philox{ .count = 1, .dtype = .i32, .threads = 4 };
193     const runtime = Philox{ .count = 10, .dtype = .i32, .threads = 4, .seed = 0xdeadbeef00c0ffee };
194 
195     var graph = try PhiloxRuntimeFamilyI32.build(allocator, PhiloxRuntimeFamilyI32.Limits.testing, compiled);
196     defer graph.deinit();
197 
198     var dst = @as([12]i32, @splat(0));
199 
200     const launch_value = try entry.runtimeLaunch1D(runtime.generators(), runtime.threads);
201     try graph.runCpuWithLaunch(allocator, &.{
202         kernel.argumentBuffer(i32, dst[0..]),
203         kernel.argumentI32(@intCast(runtime.count)),
204         kernel.argumentI32(@bitCast(runtime.seedLo())),
205         kernel.argumentI32(@bitCast(runtime.seedHi())),
206     }, .{
207         .grid = launch_value.grid,
208         .block = launch_value.threadgroup,
209     });
210 
211     var expected = @as([12]i32, @splat(0));
212     for (0..3) |generator| {
213         const words = philoxBlock(
214             philox_default_rounds,
215             .{ @intCast(generator), 0, 0, 0 },
216             .{ runtime.seedLo(), runtime.seedHi() },
217         );
218         for (words, 0..) |word, lane| {
219             const element = generator * 4 + lane;
220             if (element < runtime.count) expected[element] = @bitCast(word);
221         }
222     }
223     try std.testing.expectEqualSlices(i32, expected[0..], dst[0..]);
224 }
225 
226 test "random philox key families split keys and consume key tensors on CPU" {
227     const allocator = std.testing.allocator;
228     const split_instance = PhiloxKeySplit{ .count = 8, .threads = 4, .rounds = 10 };
229     const uniform_instance = PhiloxKeyUniform{ .count = 8, .threads = 4, .rounds = 10 };
230     const seed = Key.init(0x01234567_89abcdef);
231 
232     var split_graph = try PhiloxKeySplitRuntimeFamily.build(allocator, PhiloxKeySplitRuntimeFamily.Limits.testing, split_instance);
233     defer split_graph.deinit();
234     var uniform_graph = try PhiloxKeyUniformRuntimeFamilyF32.build(allocator, PhiloxKeyUniformRuntimeFamilyF32.Limits.testing, uniform_instance);
235     defer uniform_graph.deinit();
236 
237     var src = [_]Key{seed};
238     var keys = @as([8]Key, @splat(Key.init(0)));
239     const split_launch = try entry.runtimeLaunch1D(split_instance.generators(), split_instance.threads);
240     try split_graph.runCpuWithLaunch(allocator, &.{
241         kernel.argumentBuffer(Key, keys[0..]),
242         kernel.argumentBuffer(Key, src[0..]),
243         kernel.argumentI32(@intCast(split_instance.count)),
244     }, .{
245         .grid = split_launch.grid,
246         .block = split_launch.threadgroup,
247     });
248 
249     for (keys, 0..) |key_value, index| {
250         const words = philoxBlock(
251             split_instance.rounds,
252             .{ @intCast(index), 0, 0, 0 },
253             .{ seed.lo(), seed.hi() },
254         );
255         try std.testing.expectEqual(Key.fromWords(words[0], words[1]), key_value);
256     }
257 
258     var dst = @as([8]f32, @splat(-1.0));
259     const uniform_launch = try entry.runtimeLaunch1D(uniform_instance.generators(), uniform_instance.threads);
260     try uniform_graph.runCpuWithLaunch(allocator, &.{
261         kernel.argumentBuffer(f32, dst[0..]),
262         kernel.argumentBuffer(Key, keys[0..]),
263         kernel.argumentI32(@intCast(uniform_instance.count)),
264     }, .{
265         .grid = uniform_launch.grid,
266         .block = uniform_launch.threadgroup,
267     });
268 
269     for (keys, 0..) |key_value, index| {
270         const words = philoxBlock(
271             uniform_instance.rounds,
272             .{ 0, 0, 0, 0 },
273             .{ key_value.lo(), key_value.hi() },
274         );
275         try std.testing.expectEqual(uniformFromBits(words[0]), dst[index]);
276         try std.testing.expect(dst[index] >= 0.0);
277         try std.testing.expect(dst[index] < 1.0);
278     }
279 }
280 
281 test "random threefry runtime family emits raw bits with tail guard" {
282     const allocator = std.testing.allocator;
283     const compiled = Threefry{ .count = 1, .dtype = .i32, .threads = 4 };
284     const runtime = Threefry{ .count = 7, .dtype = .i32, .threads = 4, .seed = 0x0123456789abcdef };
285 
286     var graph = try ThreefryRuntimeFamilyI32.build(allocator, ThreefryRuntimeFamilyI32.Limits.testing, compiled);
287     defer graph.deinit();
288 
289     var dst = @as([8]i32, @splat(0));
290 
291     const launch_value = try entry.runtimeLaunch1D(runtime.generators(), runtime.threads);
292     try graph.runCpuWithLaunch(allocator, &.{
293         kernel.argumentBuffer(i32, dst[0..]),
294         kernel.argumentI32(@intCast(runtime.count)),
295         kernel.argumentI32(@bitCast(runtime.seedLo())),
296         kernel.argumentI32(@bitCast(runtime.seedHi())),
297     }, .{
298         .grid = launch_value.grid,
299         .block = launch_value.threadgroup,
300     });
301 
302     var expected = @as([8]i32, @splat(0));
303     for (0..4) |generator| {
304         const words = threefryBlock(
305             threefry_default_rounds,
306             .{ @intCast(generator), 0 },
307             .{ runtime.seedLo(), runtime.seedHi() },
308         );
309         for (words, 0..) |word, lane| {
310             const element = generator * 2 + lane;
311             if (element < runtime.count) expected[element] = @bitCast(word);
312         }
313     }
314     try std.testing.expectEqualSlices(i32, expected[0..], dst[0..]);
315 }
316 
317 test "random feistel validates only balanced power-of-two i32 domains" {
318     try std.testing.expectEqual(@as(?u6, 6), feistelDomainBits(64));
319     try std.testing.expectEqual(@as(?u6, null), feistelDomainBits(63));
320     try std.testing.expectEqual(@as(?u6, null), feistelDomainBits(128));
321     try std.testing.expect(feistelInstanceValid(.{ .count = 64, .threads = 32 }));
322     try std.testing.expect(!feistelInstanceValid(.{ .count = 128, .threads = 32 }));
323     try std.testing.expect(!feistelInstanceValid(.{ .count = 64, .threads = 0 }));
324     try std.testing.expect(!feistelInstanceValid(.{ .count = 64, .rounds = 0, .threads = 32 }));
325     try std.testing.expect(!feistelInstanceValid(.{ .count = 64, .dtype = .f32, .threads = 32 }));
326 }
327 
328 test "random feistel reference is bijective on balanced domain" {
329     const count = 64;
330     const bits = feistelDomainBits(count).?;
331     var seen = @as([count]bool, @splat(false));
332     for (0..count) |index| {
333         const mapped = feistelPermuteReference(@intCast(index), 6, 0x10203040_55667788, bits);
334         try std.testing.expect(mapped < count);
335         try std.testing.expect(!seen[mapped]);
336         seen[mapped] = true;
337     }
338     for (seen) |hit| try std.testing.expect(hit);
339 }
340 
341 test "random feistel runtime family emits permutation stream on CPU" {
342     const allocator = std.testing.allocator;
343     const instance = Feistel{ .count = 64, .rounds = 6, .seed = 0x10203040_55667788, .threads = 32 };
344 
345     var graph = try FeistelRuntimeFamilyI32.build(allocator, FeistelRuntimeFamilyI32.Limits.testing, instance);
346     defer graph.deinit();
347 
348     var dst = @as([65]i32, @splat(-1));
349 
350     const launch_value = try entry.runtimeLaunch1D(instance.count, instance.threads);
351     try graph.runCpuWithLaunch(allocator, &.{
352         kernel.argumentBuffer(i32, dst[0..]),
353         kernel.argumentI32(@intCast(instance.count)),
354         kernel.argumentI32(@bitCast(instance.seedLo())),
355         kernel.argumentI32(@bitCast(instance.seedHi())),
356     }, .{
357         .grid = launch_value.grid,
358         .block = launch_value.threadgroup,
359     });
360 
361     var expected = @as([65]i32, @splat(-1));
362     const bits = instance.domainBits().?;
363     for (0..instance.count) |element| {
364         expected[element] = @intCast(feistelPermuteReference(@intCast(element), instance.rounds, instance.seed, bits));
365     }
366     try std.testing.expectEqualSlices(i32, expected[0..], dst[0..]);
367 }
368 
369 test "random feistel family identity is stable" {
370     const instance = Feistel{ .count = 64, .rounds = 6, .seed = 0x10203040_55667788, .threads = 32 };
371 
372     const target = try feistelFamilyTarget(std.testing.allocator, instance);
373     defer std.testing.allocator.free(target);
374     try std.testing.expectEqualStrings("accy.kernel.random.feistel_family_6b_6r_32_i32", target);
375 
376     const entry_name = try feistelFamilyEntryName(std.testing.allocator, instance);
377     defer std.testing.allocator.free(entry_name);
378     try std.testing.expectEqualStrings("accy_kernel_random_feistel_family_6b_6r_32_i32", entry_name);
379 
380     const args = try feistelRuntimeArguments(instance);
381     try std.testing.expectEqual(@as(u32, 64), args[0].u32);
382     try std.testing.expectEqual(instance.seedLo(), args[1].u32);
383     try std.testing.expectEqual(instance.seedHi(), args[2].u32);
384 
385     try std.testing.expectEqual(@as(u32, 1), random.feistel_family_version);
386 }
387 
388 test "random family instance identity matches fixed entry strings" {
389     const philox_instance = Philox{ .count = 8, .threads = 2 };
390 
391     const philox_target = try philoxInstanceTarget(std.testing.allocator, philox_instance);
392     defer std.testing.allocator.free(philox_target);
393     try std.testing.expectEqualStrings(Philox8F32.target, philox_target);
394 
395     const philox_entry_name = try philoxInstanceEntryName(std.testing.allocator, philox_instance);
396     defer std.testing.allocator.free(philox_entry_name);
397     try std.testing.expectEqualStrings(Philox8F32.name, philox_entry_name);
398 
399     try std.testing.expectEqual(Philox8F32.version, philox_family_version);
400 
401     const fresh = Philox{ .count = 1 << 20, .rounds = 7, .threads = 128 };
402     const family_target = try philoxFamilyTarget(std.testing.allocator, fresh);
403     defer std.testing.allocator.free(family_target);
404     try std.testing.expectEqualStrings("accy.kernel.random.philox_family_7r_128_f32", family_target);
405 
406     const threefry_instance = Threefry{ .count = 8, .threads = 4 };
407     const threefry_target = try threefryInstanceTarget(std.testing.allocator, threefry_instance);
408     defer std.testing.allocator.free(threefry_target);
409     try std.testing.expectEqualStrings(Threefry8F32.target, threefry_target);
410 
411     const threefry_family_target = try threefryFamilyTarget(std.testing.allocator, .{ .count = 64, .rounds = 13, .threads = 64, .dtype = .i32 });
412     defer std.testing.allocator.free(threefry_family_target);
413     try std.testing.expectEqualStrings("accy.kernel.random.threefry_family_13r_64_i32", threefry_family_target);
414 }
415 
416 test "random family tuning keys distinguish generators rounds and device" {
417     const allocator = std.testing.allocator;
418     const device = tuning.deviceFingerprint(randomFamilyTuningTestCapabilities(0x2684));
419 
420     const keys = [_]tuning.FamilyTuningKey{
421         try philoxFamilyTuningKey(allocator, device, .{ .count = 1024 }),
422         try threefryFamilyTuningKey(allocator, device, .{ .count = 1024 }),
423         try squaresFamilyTuningKey(allocator, device, .{ .count = 1024 }),
424     };
425     for (keys, 0..) |key, index| {
426         for (keys[index + 1 ..]) |other| {
427             try std.testing.expect(!key.eql(other));
428         }
429     }
430 
431     const fewer_rounds = try philoxFamilyTuningKey(allocator, device, .{ .count = 1024, .rounds = 7 });
432     try std.testing.expect(!fewer_rounds.eql(keys[0]));
433     try std.testing.expectEqual(keys[0].family_fingerprint, fewer_rounds.family_fingerprint);
434     try std.testing.expect(keys[0].operation_fingerprint != fewer_rounds.operation_fingerprint);
435 
436     const other_device = try philoxFamilyTuningKey(
437         allocator,
438         tuning.deviceFingerprint(randomFamilyTuningTestCapabilities(0x1b80)),
439         .{ .count = 1024 },
440     );
441     try std.testing.expect(!other_device.eql(keys[0]));
442     try std.testing.expectEqual(keys[0].family_fingerprint, other_device.family_fingerprint);
443     try std.testing.expectEqual(keys[0].operation_fingerprint, other_device.operation_fingerprint);
444 }
445 
446 test "random philox family artifact carries runtime launch contract" {
447     const allocator = std.testing.allocator;
448     var state = gpu.recording.BackendState{
449         .allocator = allocator,
450         .kind = .cuda,
451         .format = .cuda_ptx,
452     };
453     const instance = Philox{ .count = 1024, .threads = 64 };
454 
455     var family_artifact = try createPhiloxFamilyArtifact(allocator, state.handle(), instance, .{ .limits = .testing });
456     defer family_artifact.deinit();
457 
458     const family_entry = family_artifact.entry();
459     try std.testing.expectEqualStrings("accy.kernel.random.philox_family_10r_64_f32", family_entry.target);
460     try std.testing.expectEqualStrings("accy_kernel_random_philox_family_10r_64_f32", family_entry.entry_name);
461     try std.testing.expectEqual(@as(u32, 4), family_entry.argument_count);
462     try std.testing.expectEqual(@as(u32, 3), family_entry.runtime_scalar_argument_count);
463     try std.testing.expect(family_entry.required_dtypes.contains(.f32));
464     try std.testing.expect(family_entry.shape_family_fingerprint != null);
465     const profile = family_entry.shape_profile orelse return error.TestExpectedShapeProfile;
466     try std.testing.expectEqualStrings("philox", profile.name);
467     try std.testing.expectEqual(@as(usize, 1), profile.dimensions.len);
468     switch (family_entry.launch) {
469         .derived => |launch| {
470             try std.testing.expectEqual(@as(u32, 64), launch.threadgroup[0]);
471             switch (launch.grid[0]) {
472                 .runtime_u32_ceil_div => |term| {
473                     try std.testing.expectEqual(@as(usize, 0), term.argument_index);
474                     try std.testing.expectEqual(@as(u32, 256), term.divisor);
475                 },
476                 else => return error.TestExpectedDerivedLaunch,
477             }
478         },
479         else => return error.TestExpectedDerivedLaunch,
480     }
481 }
482 
483 test "random philox key family artifacts carry key dtype contracts" {
484     const allocator = std.testing.allocator;
485     var state = gpu.recording.BackendState{
486         .allocator = allocator,
487         .kind = .cuda,
488         .format = .cuda_ptx,
489     };
490 
491     var split_artifact = try createPhiloxKeySplitFamilyArtifact(
492         allocator,
493         state.handle(),
494         .{ .count = 1024, .threads = 64 },
495         .{ .limits = .testing },
496     );
497     defer split_artifact.deinit();
498     const split_entry = split_artifact.entry();
499     try std.testing.expectEqualStrings("accy.kernel.random.philox_key_split_family_10r_64_key", split_entry.target);
500     try std.testing.expectEqual(@as(u32, 3), split_entry.argument_count);
501     try std.testing.expectEqual(@as(u32, 1), split_entry.runtime_scalar_argument_count);
502     try std.testing.expect(split_entry.required_dtypes.contains(.key));
503 
504     var uniform_artifact = try createPhiloxKeyUniformFamilyArtifact(
505         allocator,
506         state.handle(),
507         .{ .count = 1024, .threads = 64, .dtype = .f32 },
508         .{ .limits = .testing },
509     );
510     defer uniform_artifact.deinit();
511     const uniform_entry = uniform_artifact.entry();
512     try std.testing.expectEqualStrings("accy.kernel.random.philox_key_uniform_family_10r_64_f32", uniform_entry.target);
513     try std.testing.expectEqual(@as(u32, 3), uniform_entry.argument_count);
514     try std.testing.expectEqual(@as(u32, 1), uniform_entry.runtime_scalar_argument_count);
515     try std.testing.expect(uniform_entry.required_dtypes.contains(.key));
516     try std.testing.expect(uniform_entry.required_dtypes.contains(.f32));
517 
518     var counter_artifact = try createPhiloxKeyCounterUniformFamilyArtifact(
519         allocator,
520         state.handle(),
521         .{ .count = 1024, .threads = 64, .dtype = .f32 },
522         .{ .limits = .testing },
523     );
524     defer counter_artifact.deinit();
525     const counter_entry = counter_artifact.entry();
526     try std.testing.expectEqualStrings("accy.kernel.random.philox_key_counter_uniform_family_10r_64_f32", counter_entry.target);
527     try std.testing.expectEqual(@as(u32, 4), counter_entry.argument_count);
528     try std.testing.expectEqual(@as(u32, 1), counter_entry.runtime_scalar_argument_count);
529     try std.testing.expect(counter_entry.required_dtypes.contains(.key));
530     try std.testing.expect(counter_entry.required_dtypes.contains(.i32));
531     try std.testing.expect(counter_entry.required_dtypes.contains(.f32));
532 }
533 
534 test "random philox key counter uniform samples from scalar key and counter on CPU" {
535     const allocator = std.testing.allocator;
536     const compiled = PhiloxKeyCounterUniform{ .count = 1, .threads = 4 };
537     const runtime = PhiloxKeyCounterUniform{ .count = 6, .threads = 4 };
538     const seed = Key.init(0x01234567_89abcdef);
539     var counter = [_]i32{3};
540 
541     var graph = try PhiloxKeyCounterUniformRuntimeFamilyF32.build(allocator, PhiloxKeyCounterUniformRuntimeFamilyF32.Limits.testing, compiled);
542     defer graph.deinit();
543 
544     var dst = @as([8]f32, @splat(-1.0));
545     var keys = [_]Key{seed};
546 
547     const launch_value = try entry.runtimeLaunch1D(runtime.count, runtime.threads);
548     try graph.runCpuWithLaunch(allocator, &.{
549         kernel.argumentBuffer(f32, dst[0..]),
550         kernel.argumentBuffer(Key, keys[0..]),
551         kernel.argumentBuffer(i32, counter[0..]),
552         kernel.argumentI32(@intCast(runtime.count)),
553     }, .{
554         .grid = launch_value.grid,
555         .block = launch_value.threadgroup,
556     });
557 
558     for (0..runtime.count) |index| {
559         const words = philoxBlock(
560             philox_default_rounds,
561             .{ @intCast(index), @bitCast(counter[0]), 0, 0 },
562             .{ seed.lo(), seed.hi() },
563         );
564         try std.testing.expectApproxEqAbs(uniformFromBits(words[0]), dst[index], 0.0);
565     }
566     try std.testing.expectEqual(@as(f32, -1.0), dst[6]);
567 }
568 
569 test "random philox fold family accumulates uniform samples on CPU" {
570     const allocator = std.testing.allocator;
571     const compiled = PhiloxFold{ .count = 1, .threads = 4 };
572     const runtime = PhiloxFold{ .count = 6, .samples = 5, .threads = 4, .seed = 0x00c0ffee };
573 
574     var graph = try PhiloxFoldRuntimeFamilyF32.build(allocator, PhiloxFoldRuntimeFamilyF32.Limits.testing, compiled);
575     defer graph.deinit();
576 
577     var dst = @as([8]f32, @splat(-1.0));
578 
579     const launch_value = try entry.runtimeLaunch1D(runtime.count, runtime.threads);
580     try graph.runCpuWithLaunch(allocator, &.{
581         kernel.argumentBuffer(f32, dst[0..]),
582         kernel.argumentI32(@intCast(runtime.count)),
583         kernel.argumentI32(@intCast(runtime.samples)),
584         kernel.argumentI32(@bitCast(runtime.seedLo())),
585         kernel.argumentI32(@bitCast(runtime.seedHi())),
586     }, .{
587         .grid = launch_value.grid,
588         .block = launch_value.threadgroup,
589     });
590 
591     for (0..6) |output| {
592         const expected = philoxFoldUniformReference(
593             philox_default_rounds,
594             @intCast(output),
595             runtime.samples,
596             .{ runtime.seedLo(), runtime.seedHi() },
597         );
598         try std.testing.expectEqual(expected, dst[output]);
599         try std.testing.expect(dst[output] >= 0.0);
600         try std.testing.expect(dst[output] < @as(f32, @floatFromInt(runtime.samples * philox_lanes)));
601     }
602     try std.testing.expectEqual(@as(f32, -1.0), dst[6]);
603     try std.testing.expectEqual(@as(f32, -1.0), dst[7]);
604 }
605 
606 test "random threefry fold family xors raw words on CPU" {
607     const allocator = std.testing.allocator;
608     const compiled = ThreefryFold{ .count = 1, .dtype = .i32, .threads = 4 };
609     const runtime = ThreefryFold{ .count = 5, .samples = 7, .dtype = .i32, .threads = 4, .seed = 0xfeedface12345678 };
610 
611     var graph = try ThreefryFoldRuntimeFamilyI32.build(allocator, ThreefryFoldRuntimeFamilyI32.Limits.testing, compiled);
612     defer graph.deinit();
613 
614     var dst = @as([8]i32, @splat(0));
615 
616     const launch_value = try entry.runtimeLaunch1D(runtime.count, runtime.threads);
617     try graph.runCpuWithLaunch(allocator, &.{
618         kernel.argumentBuffer(i32, dst[0..]),
619         kernel.argumentI32(@intCast(runtime.count)),
620         kernel.argumentI32(@intCast(runtime.samples)),
621         kernel.argumentI32(@bitCast(runtime.seedLo())),
622         kernel.argumentI32(@bitCast(runtime.seedHi())),
623     }, .{
624         .grid = launch_value.grid,
625         .block = launch_value.threadgroup,
626     });
627 
628     for (0..5) |output| {
629         const expected = threefryFoldBitsReference(
630             threefry_default_rounds,
631             @intCast(output),
632             runtime.samples,
633             .{ runtime.seedLo(), runtime.seedHi() },
634         );
635         try std.testing.expectEqual(@as(i32, @bitCast(expected)), dst[output]);
636     }
637 }
638 
639 test "random fold family artifact carries samples scalar in launch contract" {
640     const allocator = std.testing.allocator;
641     var state = gpu.recording.BackendState{
642         .allocator = allocator,
643         .kind = .cuda,
644         .format = .cuda_ptx,
645     };
646     const instance = PhiloxFold{ .count = 4096, .samples = 16, .rounds = 7, .threads = 128 };
647 
648     var family_artifact = try createPhiloxFoldFamilyArtifact(allocator, state.handle(), instance, .{ .limits = .testing });
649     defer family_artifact.deinit();
650 
651     const family_entry = family_artifact.entry();
652     try std.testing.expectEqualStrings("accy.kernel.random.philox_fold_family_7r_128_f32", family_entry.target);
653     try std.testing.expectEqual(@as(u32, 5), family_entry.argument_count);
654     try std.testing.expectEqual(@as(u32, 4), family_entry.runtime_scalar_argument_count);
655     const profile = family_entry.shape_profile orelse return error.TestExpectedShapeProfile;
656     try std.testing.expectEqual(@as(usize, 2), profile.dimensions.len);
657     try std.testing.expectEqual(@as(usize, 1), profile.dimensions[1].runtime_scalar_argument_index);
658     switch (family_entry.launch) {
659         .derived => |launch| {
660             try std.testing.expectEqual(@as(u32, 128), launch.threadgroup[0]);
661             switch (launch.grid[0]) {
662                 .runtime_u32_ceil_div => |term| {
663                     try std.testing.expectEqual(@as(usize, 0), term.argument_index);
664                     try std.testing.expectEqual(@as(u32, 128), term.divisor);
665                 },
666                 else => return error.TestExpectedDerivedLaunch,
667             }
668         },
669         else => return error.TestExpectedDerivedLaunch,
670     }
671 
672     const arguments = try philoxFoldRuntimeArguments(instance);
673     try std.testing.expectEqual(@as(u32, 4096), arguments[0].u32);
674     try std.testing.expectEqual(@as(u32, 16), arguments[1].u32);
675 }
676 
677 test "random fold family tuning keys separate fills from folds" {
678     const allocator = std.testing.allocator;
679     const caps = randomFamilyTuningTestCapabilities(0x2684);
680     const device = tuning.deviceFingerprint(caps);
681 
682     const fill_key = try philoxFamilyTuningKey(allocator, device, .{ .count = 4096 });
683     const fold_key = try philoxFoldFamilyTuningKey(allocator, device, .{ .count = 4096, .samples = 1 });
684     try std.testing.expect(!fill_key.eql(fold_key));
685     try std.testing.expect(fill_key.operation_fingerprint != fold_key.operation_fingerprint);
686 
687     const more_samples = try philoxFoldFamilyTuningKey(allocator, device, .{ .count = 4096, .samples = 8 });
688     try std.testing.expect(!fold_key.eql(more_samples));
689     try std.testing.expectEqual(fold_key.family_fingerprint, more_samples.family_fingerprint);
690     try std.testing.expectEqual(fold_key.operation_fingerprint, more_samples.operation_fingerprint);
691 
692     const fewer_rounds = try philoxFoldFamilyTuningKey(allocator, device, .{ .count = 4096, .samples = 1, .rounds = 7 });
693     try std.testing.expect(fold_key.operation_fingerprint != fewer_rounds.operation_fingerprint);
694 
695     const threefry_key = try threefryFoldFamilyTuningKey(allocator, device, .{ .count = 4096 });
696     const squares_key = try squaresFoldFamilyTuningKey(allocator, device, .{ .count = 4096 });
697     try std.testing.expect(!threefry_key.eql(squares_key));
698 
699     const records = [_]tuning.FamilyTuningRecord{.{
700         .key = fold_key,
701         .target = "accy.kernel.random.philox_fold_family_256_f32",
702         .winner_median_ns = 700,
703         .runner_up_median_ns = 1000,
704         .sample_count = 30,
705     }};
706     const reader = tuning.FamilyTuningReader.init(caps, .{ .records = records[0..] });
707     const hit_key = try philoxFoldFamilyTuningKey(allocator, reader.device_fingerprint, .{ .count = 4096, .samples = 1 });
708     const hit = reader.table.find(hit_key) orelse return error.TestExpectedTuningRecord;
709     try std.testing.expectEqualStrings("accy.kernel.random.philox_fold_family_256_f32", hit.target);
710     try std.testing.expect(reader.table.find(fill_key) == null);
711 }
712 
713 test "random squares reference matches computed anchors for the documented key" {
714     try std.testing.expectEqual(@as(u32, 0x58db6965), squaresBlock(0, squares_default_key));
715     try std.testing.expectEqual(@as(u32, 0x3b63969b), squaresBlock(1, squares_default_key));
716     try std.testing.expectEqual(@as(u32, 0xbcd3723e), squaresBlock(2, squares_default_key));
717     try std.testing.expectEqual(@as(u32, 0xe617d09b), squaresBlock(0xffffffffffffffff, squares_default_key));
718 }
719 
720 test "random squares runtime family emits raw bits with tail guard on CPU" {
721     const allocator = std.testing.allocator;
722     const compiled = Squares{ .count = 1, .dtype = .i32, .threads = 4 };
723     const runtime = Squares{ .count = 6, .dtype = .i32, .threads = 4 };
724 
725     var graph = try SquaresRuntimeFamilyI32.build(allocator, SquaresRuntimeFamilyI32.Limits.testing, compiled);
726     defer graph.deinit();
727 
728     var dst = @as([8]i32, @splat(0));
729 
730     const launch_value = try entry.runtimeLaunch1D(runtime.count, runtime.threads);
731     try graph.runCpuWithLaunch(allocator, &.{
732         kernel.argumentBuffer(i32, dst[0..]),
733         kernel.argumentI32(@intCast(runtime.count)),
734         kernel.argumentI32(@bitCast(runtime.keyLo())),
735         kernel.argumentI32(@bitCast(runtime.keyHi())),
736     }, .{
737         .grid = launch_value.grid,
738         .block = launch_value.threadgroup,
739     });
740 
741     for (0..6) |element| {
742         const expected = squaresBlock(@intCast(element), runtime.key);
743         try std.testing.expectEqual(@as(i32, @bitCast(expected)), dst[element]);
744     }
745     try std.testing.expectEqual(@as(i32, 0), dst[6]);
746     try std.testing.expectEqual(@as(i32, 0), dst[7]);
747 }
748 
749 test "random squares fold family accumulates uniform samples on CPU" {
750     const allocator = std.testing.allocator;
751     const compiled = SquaresFold{ .count = 1, .threads = 4 };
752     const runtime = SquaresFold{ .count = 5, .samples = 6, .threads = 4 };
753 
754     var graph = try SquaresFoldRuntimeFamilyF32.build(allocator, SquaresFoldRuntimeFamilyF32.Limits.testing, compiled);
755     defer graph.deinit();
756 
757     var dst = @as([8]f32, @splat(-1.0));
758 
759     const launch_value = try entry.runtimeLaunch1D(runtime.count, runtime.threads);
760     try graph.runCpuWithLaunch(allocator, &.{
761         kernel.argumentBuffer(f32, dst[0..]),
762         kernel.argumentI32(@intCast(runtime.count)),
763         kernel.argumentI32(@intCast(runtime.samples)),
764         kernel.argumentI32(@bitCast(runtime.keyLo())),
765         kernel.argumentI32(@bitCast(runtime.keyHi())),
766     }, .{
767         .grid = launch_value.grid,
768         .block = launch_value.threadgroup,
769     });
770 
771     for (0..5) |output| {
772         const expected = squaresFoldUniformReference(@intCast(output), runtime.samples, runtime.key);
773         try std.testing.expectEqual(expected, dst[output]);
774     }
775     try std.testing.expectEqual(@as(f32, -1.0), dst[5]);
776 }
777 
778 test "random squares family artifact carries runtime launch contract" {
779     const allocator = std.testing.allocator;
780     var state = gpu.recording.BackendState{
781         .allocator = allocator,
782         .kind = .cuda,
783         .format = .cuda_ptx,
784     };
785     const instance = Squares{ .count = 4096, .threads = 128 };
786 
787     var family_artifact = try createSquaresFamilyArtifact(allocator, state.handle(), instance, .{ .limits = .testing });
788     defer family_artifact.deinit();
789 
790     const family_entry = family_artifact.entry();
791     try std.testing.expectEqualStrings("accy.kernel.random.squares_family_128_f32", family_entry.target);
792     try std.testing.expectEqual(@as(u32, 4), family_entry.argument_count);
793     try std.testing.expectEqual(@as(u32, 3), family_entry.runtime_scalar_argument_count);
794 
795     var owned = try squaresFamilySpecialization(allocator, instance);
796     defer owned.deinit();
797     const recovered = squaresInstanceFromSpecialization(owned.value) orelse return error.TestExpectedSquaresInstance;
798     try std.testing.expectEqual(instance.count, recovered.count);
799     try std.testing.expectEqual(instance.threads, recovered.threads);
800     try std.testing.expectEqual(@as(?Philox, null), philoxInstanceFromSpecialization(owned.value));
801 }
802 
803 test "random reference streams keep every bit position near balance" {
804     const words_per_algorithm: u32 = 1 << 18;
805     var ones = @as([32]u64, @splat(0));
806 
807     var counter: u32 = 0;
808     var produced: u32 = 0;
809     while (produced < words_per_algorithm) : (counter += 1) {
810         const words = philoxBlock(philox_default_rounds, .{ counter, 0, 0, 0 }, .{ 0x9e3779b9, 0x7f4a7c15 });
811         for (words) |word| {
812             for (0..32) |bit| ones[bit] += (word >> @intCast(bit)) & 1;
813         }
814         produced += philox_lanes;
815     }
816     try expectBitBalance(&ones, produced);
817 
818     ones = @as([32]u64, @splat(0));
819     counter = 0;
820     produced = 0;
821     while (produced < words_per_algorithm) : (counter += 1) {
822         const words = threefryBlock(threefry_default_rounds, .{ counter, 0 }, .{ 0x243f6a88, 0x85a308d3 });
823         for (words) |word| {
824             for (0..32) |bit| ones[bit] += (word >> @intCast(bit)) & 1;
825         }
826         produced += threefry_lanes;
827     }
828     try expectBitBalance(&ones, produced);
829 
830     ones = @as([32]u64, @splat(0));
831     counter = 0;
832     while (counter < words_per_algorithm) : (counter += 1) {
833         const word = squaresBlock(counter, squares_default_key);
834         for (0..32) |bit| ones[bit] += (word >> @intCast(bit)) & 1;
835     }
836     try expectBitBalance(&ones, words_per_algorithm);
837 }
838 
839 fn expectBitBalance(ones: *const [32]u64, words: u32) !void {
840     const half: f64 = @as(f64, @floatFromInt(words)) / 2.0;
841     const tolerance = half * 0.02;
842     for (ones) |count| {
843         const deviation = @abs(@as(f64, @floatFromInt(count)) - half);
844         try std.testing.expect(deviation < tolerance);
845     }
846 }
847 
848 test "random instances round-trip through specializations" {
849     const philox_instance = Philox{ .count = 4096, .rounds = 7, .dtype = .i32, .threads = 128 };
850     var philox_owned = try philoxFamilySpecialization(std.testing.allocator, philox_instance);
851     defer philox_owned.deinit();
852 
853     const philox_recovered = philoxInstanceFromSpecialization(philox_owned.value) orelse return error.TestExpectedPhiloxInstance;
854     try std.testing.expectEqual(philox_instance.count, philox_recovered.count);
855     try std.testing.expectEqual(philox_instance.rounds, philox_recovered.rounds);
856     try std.testing.expectEqual(philox_instance.dtype, philox_recovered.dtype);
857     try std.testing.expectEqual(philox_instance.threads, philox_recovered.threads);
858     try std.testing.expectEqual(@as(?Threefry, null), threefryInstanceFromSpecialization(philox_owned.value));
859 
860     const threefry_instance = Threefry{ .count = 1000, .rounds = 13, .threads = 64 };
861     var threefry_owned = try threefryFamilySpecialization(std.testing.allocator, threefry_instance);
862     defer threefry_owned.deinit();
863 
864     const threefry_recovered = threefryInstanceFromSpecialization(threefry_owned.value) orelse return error.TestExpectedThreefryInstance;
865     try std.testing.expectEqual(threefry_instance.count, threefry_recovered.count);
866     try std.testing.expectEqual(threefry_instance.rounds, threefry_recovered.rounds);
867     try std.testing.expectEqual(threefry_instance.dtype, threefry_recovered.dtype);
868     try std.testing.expectEqual(threefry_instance.threads, threefry_recovered.threads);
869     try std.testing.expectEqual(@as(?Philox, null), philoxInstanceFromSpecialization(threefry_owned.value));
870 
871     try std.testing.expectEqual(@as(?Philox, null), philoxInstanceFromSpecialization(.{}));
872 }
873 
874 test "accy kernel library random declaration coverage" {
875     std.testing.refAllDecls(random);
876     std.testing.refAllDecls(@This());
877 }