lib/accy/src/artifact/fingerprint.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const gpu = @import("gpu");
3 const choir_abi = @import("choir_abi");
4 const choir = @import("choir");
5 const model = @import("model/root.zig");
6 const plan_mod = @import("plan.zig");
7 const preparation = @import("../preparation/root.zig");
8
9 pub fn artifactPlan(value: *const plan_mod.BackendArtifactPlan) u64 {
10 var hasher = choir.product.incremental.FingerprintBuilder{};
11 hasher.updateBytes("accy.artifact.plan");
12 hashBackendTargetProfile(&hasher, value.target_profile);
13 hashU64(&hasher, @backingInt(value.backend_kind));
14 hashU64(&hasher, @backingInt(value.format));
15 hashU64(&hasher, value.slots.len);
16 for (value.slots) |slot| hashPlannedSlot(&hasher, slot);
17 hashU64(&hasher, value.input_slot_ids.len);
18 for (value.input_slot_ids) |slot_id| hashU64(&hasher, slot_id);
19 hashU64(&hasher, value.output_slot_ids.len);
20 for (value.output_slot_ids) |slot_id| hashU64(&hasher, slot_id);
21 hashU64(&hasher, value.kernels.items.len);
22 for (value.kernels.items) |kernel| hashPlannedKernel(&hasher, kernel);
23 hashU64(&hasher, value.total_kernel_ops);
24 hashU64(&hasher, value.total_static_bytes);
25 return hasher.finish();
26 }
27
28 pub fn kernelCallRegistry(value: ?*const model.KernelCallRegistry) u64 {
29 var hasher = choir.product.incremental.FingerprintBuilder{};
30 hasher.updateBytes("accy.artifact.kernel_call_registry");
31 hashU64(&hasher, @intFromBool(value != null));
32 const registry = value orelse return hasher.finish();
33 hashU64(&hasher, registry.entries.len);
34 for (registry.entries) |entry| hashKernelCallArtifact(&hasher, entry);
35 return hasher.finish();
36 }
37
38 pub fn launchResourcePlan(value: plan_mod.LaunchResourcePlan) u64 {
39 var hasher = choir.product.incremental.FingerprintBuilder{};
40 hasher.updateBytes("accy.artifact.launch_resource_plan");
41 hashLaunchResourcePlan(&hasher, value);
42 return hasher.finish();
43 }
44
45 fn hashBackendTargetProfile(hasher: *choir.product.incremental.FingerprintBuilder, profile: anytype) void {
46 hashU64(hasher, @backingInt(profile.backend_kind));
47 hashU64(hasher, @backingInt(profile.artifact_format));
48 hashU64(hasher, @backingInt(profile.math_tier));
49 hashU64(hasher, profile.dtype_bits);
50 hashU64(hasher, profile.feature_bits);
51 }
52
53 fn hashPlannedSlot(hasher: *choir.product.incremental.FingerprintBuilder, slot: plan_mod.PlannedSlot) void {
54 hashU64(hasher, slot.slot_id);
55 hashBufferRole(hasher, slot.role);
56 hashU64(hasher, @backingInt(slot.dtype));
57 hashU64(hasher, @backingInt(slot.memory_space));
58 hashU64(hasher, @backingInt(slot.memory_access));
59 hashU64(hasher, @backingInt(slot.boundary_transfer));
60 hashU64(hasher, @backingInt(slot.layout_kind));
61 hashU64(hasher, slot.dims.len);
62 for (slot.dims) |dim| hashU64(hasher, @bitCast(dim));
63 hashOptionalU64Slice(hasher, slot.element_strides);
64 hashU64(hasher, slot.minor_to_major.len);
65 for (slot.minor_to_major) |minor| hashU64(hasher, minor);
66 hashOptionalU64(hasher, slot.element_count);
67 hashOptionalU64(hasher, slot.byte_size);
68 hashU64(hasher, slot.alignment);
69 hashU64(hasher, @intFromBool(slot.contiguous));
70 hashU64(hasher, @intFromBool(slot.static_layout));
71 hashU64(hasher, slot.layout_fingerprint);
72 hashBytes(hasher, slot.constant_payload);
73 }
74
75 fn hashBufferRole(hasher: *choir.product.incremental.FingerprintBuilder, role: anytype) void {
76 hashU64(hasher, @intFromBool(role.input));
77 hashU64(hasher, @intFromBool(role.output));
78 hashU64(hasher, @intFromBool(role.temporary));
79 hashU64(hasher, @intFromBool(role.constant));
80 }
81
82 fn hashPlannedKernel(hasher: *choir.product.incremental.FingerprintBuilder, kernel: plan_mod.PlannedKernel) void {
83 hashPlannedKernelCompile(hasher, kernel.compile);
84 hashU64(hasher, kernel.kernel_id);
85 hashU64(hasher, kernel.work_item_id);
86 hashU64(hasher, kernel.output_slot_id);
87 hashU64(hasher, kernel.input_slot_ids.len);
88 for (kernel.input_slot_ids) |slot_id| hashU64(hasher, slot_id);
89 hashU64(hasher, kernel.output_layout_fingerprint);
90 hashU64(hasher, kernel.input_layout_fingerprint);
91 hashU64(hasher, kernel.element_count);
92 hashU64(hasher, kernel.op_count);
93 hashScheduleResourceEstimate(hasher, kernel.resources);
94 hashKernelArtifact(hasher, kernel.artifact);
95 hashLaunchResourcePlan(hasher, kernel.launch_resources);
96 hashOptionalKernelCallLaunch(hasher, kernel.kernel_call_launch);
97 hashU64(hasher, @backingInt(kernel.element_count_argument));
98 hashU64(hasher, kernel.element_count_argument_value);
99 hashU64(hasher, kernel.runtime_scalar_argument_count);
100 hashU64(hasher, kernel.runtime_scalar_defaults.len);
101 for (kernel.runtime_scalar_defaults) |argument| hashKernelScalarArgument(hasher, argument);
102 hashU64(hasher, kernel.static_arguments.len);
103 for (kernel.static_arguments) |argument| hashKernelScalarArgument(hasher, argument);
104 }
105
106 fn hashPlannedKernelCompile(hasher: *choir.product.incremental.FingerprintBuilder, compile: plan_mod.PlannedKernelCompileContract) void {
107 hashU64(hasher, @backingInt(compile.source));
108 hashU64(hasher, @backingInt(compile.launch));
109 hashU64(hasher, @backingInt(compile.format));
110 hashBytes(hasher, compile.entry_name);
111 hashU64(hasher, compile.argument_count);
112 hashU64(hasher, compile.required_dtypes.bits);
113 hashAcceleratorFeatures(hasher, compile.required_features);
114 hashSubgroupRequirements(hasher, compile.required_subgroup);
115 hashOptionalU64(hasher, compile.shape_family_fingerprint);
116 hashU64(hasher, @backingInt(compile.payload));
117 hashU64(hasher, compile.payload_byte_count);
118 }
119
120 fn hashAcceleratorFeatures(hasher: *choir.product.incremental.FingerprintBuilder, features: choir_abi.Features) void {
121 hashU64(hasher, @intFromBool(features.atomic_i32));
122 hashU64(hasher, @intFromBool(features.atomic_u32));
123 hashU64(hasher, @intFromBool(features.atomic_index));
124 hashU64(hasher, @intFromBool(features.atomic_f32_add_device));
125 hashU64(hasher, @intFromBool(features.atomic_f32_add_shared));
126 hashU64(hasher, @intFromBool(features.unsupported_atomic));
127 hashU64(hasher, @intFromBool(features.async_copy));
128 hashU64(hasher, @intFromBool(features.tensor_cores));
129 hashU64(hasher, @intFromBool(features.cooperative_matrix));
130 hashU64(hasher, @intFromBool(features.dynamic_shared_memory));
131 hashU64(hasher, @intFromBool(features.indirect_launch));
132 }
133
134 fn hashSubgroupRequirements(hasher: *choir.product.incremental.FingerprintBuilder, requirements: choir_abi.SubgroupRequirements) void {
135 hashU64(hasher, @intFromBool(requirements.supported));
136 hashU64(hasher, requirements.size_min);
137 hashU64(hasher, requirements.size_max);
138 hashU64(hasher, @intFromBool(requirements.shuffle));
139 hashU64(hasher, @intFromBool(requirements.ballot));
140 hashU64(hasher, @intFromBool(requirements.vote));
141 hashU64(hasher, @intFromBool(requirements.arithmetic));
142 hashU64(hasher, @intFromBool(requirements.scan));
143 }
144
145 fn hashScheduleResourceEstimate(hasher: *choir.product.incremental.FingerprintBuilder, resources: anytype) void {
146 hashU64(hasher, resources.element_count);
147 hashU64(hasher, resources.element_size);
148 hashU64(hasher, resources.op_count);
149 hashU64(hasher, resources.external_input_value_count);
150 hashU64(hasher, resources.external_operand_count);
151 hashU64(hasher, resources.chain_operand_count);
152 hashU64(hasher, resources.estimated_element_ops);
153 hashU64(hasher, resources.static_read_bytes);
154 hashU64(hasher, resources.static_write_bytes);
155 hashU64(hasher, resources.static_total_bytes);
156 hashU64(hasher, @intFromBool(resources.static_bytes_complete));
157 }
158
159 fn hashKernelArtifact(hasher: *choir.product.incremental.FingerprintBuilder, artifact: gpu.KernelArtifact) void {
160 hashU64(hasher, @backingInt(artifact.backend));
161 hashU64(hasher, @backingInt(artifact.format));
162 hashBytes(hasher, artifact.entry_name);
163 hashU64(hasher, artifact.argument_count);
164 hashOptionalBytes(hasher, artifact.diagnostic_id);
165 hashArtifactPayload(hasher, artifact.payload);
166 hashU64(hasher, @backingInt(artifact.payload_ownership));
167 }
168
169 fn hashArtifactPayload(hasher: *choir.product.incremental.FingerprintBuilder, payload: gpu.ArtifactPayload) void {
170 hashU64(hasher, @backingInt(payload));
171 switch (payload) {
172 .none => {},
173 .bytes => |bytes| hashBytes(hasher, bytes),
174 .text => |text| hashBytes(hasher, text),
175 .words_u32 => |words| {
176 hashU64(hasher, words.len);
177 for (words) |word| hashU64(hasher, word);
178 },
179 .external => |external| {
180 hashU64(hasher, @intFromPtr(external.ptr));
181 hashU64(hasher, @intFromBool(external.deinit_fn != null));
182 },
183 }
184 }
185
186 fn hashCompilePayload(hasher: *choir.product.incremental.FingerprintBuilder, payload: gpu.CompilePayload) void {
187 hashU64(hasher, @backingInt(payload));
188 switch (payload) {
189 .none => {},
190 .bytes => |bytes| hashBytes(hasher, bytes),
191 .text => |text| hashBytes(hasher, text),
192 .words_u32 => |words| {
193 hashU64(hasher, words.len);
194 for (words) |word| hashU64(hasher, word);
195 },
196 }
197 }
198
199 fn hashKernelCallArtifact(hasher: *choir.product.incremental.FingerprintBuilder, artifact: model.KernelCallArtifact) void {
200 hashBytes(hasher, artifact.target);
201 hashU64(hasher, artifact.version);
202 hashU64(hasher, @backingInt(artifact.format));
203 hashBytes(hasher, artifact.entry_name);
204 hashU64(hasher, artifact.argument_count);
205 hashOptionalU64(hasher, artifact.shape_family_fingerprint);
206 hashOptionalKernelCallShapeProfile(hasher, artifact.shape_profile);
207 hashU64(hasher, artifact.required_dtypes.bits);
208 hashAcceleratorFeatures(hasher, artifact.required_features);
209 hashSubgroupRequirements(hasher, artifact.required_subgroup);
210 hashCompilePayload(hasher, artifact.payload);
211 hashKernelCallLaunch(hasher, artifact.launch);
212 hashU64(hasher, @backingInt(artifact.element_count_argument));
213 hashU64(hasher, artifact.runtime_scalar_argument_count);
214 hashU64(hasher, artifact.static_arguments.len);
215 for (artifact.static_arguments) |argument| hashKernelScalarArgument(hasher, argument);
216 }
217
218 fn hashOptionalKernelCallShapeProfile(hasher: *choir.product.incremental.FingerprintBuilder, profile: ?model.KernelCallShapeProfile) void {
219 hashU64(hasher, @intFromBool(profile != null));
220 if (profile) |value| {
221 hashBytes(hasher, value.name);
222 hashU64(hasher, value.fingerprint);
223 hashU64(hasher, value.dimensions.len);
224 for (value.dimensions) |dimension| {
225 hashBytes(hasher, dimension.name);
226 hashU64(hasher, dimension.runtime_scalar_argument_index);
227 hashShapeBounds(hasher, dimension.bounds);
228 }
229 }
230 }
231
232 fn hashShapeBounds(hasher: *choir.product.incremental.FingerprintBuilder, bounds: anytype) void {
233 hashOptionalU64(hasher, bounds.min);
234 hashOptionalU64(hasher, bounds.opt);
235 hashOptionalU64(hasher, bounds.max);
236 }
237
238 fn hashOptionalKernelCallLaunch(hasher: *choir.product.incremental.FingerprintBuilder, launch: ?model.KernelCallLaunch) void {
239 hashU64(hasher, @intFromBool(launch != null));
240 if (launch) |value| hashKernelCallLaunch(hasher, value);
241 }
242
243 fn hashKernelCallLaunch(hasher: *choir.product.incremental.FingerprintBuilder, launch: model.KernelCallLaunch) void {
244 hashU64(hasher, @backingInt(launch));
245 switch (launch) {
246 .derived => |derived| hashKernelCallDerivedLaunch(hasher, derived),
247 .fixed => |geometry| hashLaunchGeometry(hasher, geometry),
248 }
249 }
250
251 fn hashKernelCallDerivedLaunch(hasher: *choir.product.incremental.FingerprintBuilder, launch: model.KernelCallDerivedLaunch) void {
252 for (launch.grid) |axis| hashKernelCallDerivedLaunchAxis(hasher, axis);
253 for (launch.threadgroup) |extent| hashU64(hasher, extent);
254 hashU64(hasher, launch.dynamic_shared_memory_bytes);
255 }
256
257 fn hashKernelCallDerivedLaunchAxis(hasher: *choir.product.incremental.FingerprintBuilder, axis: model.KernelCallDerivedLaunchAxis) void {
258 hashU64(hasher, @backingInt(axis));
259 switch (axis) {
260 .fixed => |extent| hashU64(hasher, extent),
261 .runtime_u32_ceil_div => |runtime| {
262 hashU64(hasher, runtime.argument_index);
263 hashU64(hasher, runtime.divisor);
264 },
265 }
266 }
267
268 fn hashLaunchResourcePlan(hasher: *choir.product.incremental.FingerprintBuilder, plan: plan_mod.LaunchResourcePlan) void {
269 hashU64(hasher, @backingInt(plan.format));
270 hashU64(hasher, plan.element_count);
271 hashLaunchGeometry(hasher, plan.geometry);
272 hashOptionalU32(hasher, plan.subgroup_size);
273 hashU64(hasher, @intFromBool(plan.subgroup_aligned));
274 hashU64(hasher, @intFromBool(plan.fixed_threadgroup));
275 hashU64(hasher, @backingInt(plan.resource_class));
276 hashU64(hasher, plan.element_ops_per_kib);
277 hashU64(hasher, plan.estimated_static_bytes_per_threadgroup);
278 hashU64(hasher, plan.estimated_element_ops_per_threadgroup);
279 hashU64(hasher, @intFromBool(plan.static_bytes_complete));
280 hashLaunchTilePlan(hasher, plan.tile);
281 hashU64(hasher, plan.candidate_count);
282 for (plan.candidates[0..plan.candidate_count]) |candidate| hashLaunchResourceCandidate(hasher, candidate);
283 }
284
285 fn hashLaunchResourceCandidate(hasher: *choir.product.incremental.FingerprintBuilder, candidate: plan_mod.LaunchResourceCandidate) void {
286 hashLaunchGeometry(hasher, candidate.geometry);
287 hashU64(hasher, candidate.score);
288 hashU64(hasher, candidate.estimated_static_bytes_per_threadgroup);
289 hashU64(hasher, candidate.estimated_element_ops_per_threadgroup);
290 hashLaunchTilePlan(hasher, candidate.tile);
291 }
292
293 fn hashLaunchGeometry(hasher: *choir.product.incremental.FingerprintBuilder, geometry: choir_abi.LaunchGeometry) void {
294 for (geometry.grid) |value| hashU64(hasher, value);
295 for (geometry.threadgroup) |value| hashU64(hasher, value);
296 hashU64(hasher, geometry.dynamic_shared_memory_bytes);
297 }
298
299 fn hashLaunchTilePlan(hasher: *choir.product.incremental.FingerprintBuilder, tile: plan_mod.LaunchTilePlan) void {
300 hashU64(hasher, @backingInt(tile.kind));
301 hashU64(hasher, tile.m);
302 hashU64(hasher, tile.n);
303 hashU64(hasher, tile.k);
304 hashU64(hasher, tile.batch);
305 hashOptionalDType(hasher, tile.input_dtype);
306 hashOptionalDType(hasher, tile.output_dtype);
307 hashU64(hasher, tile.input_tile_bytes);
308 hashU64(hasher, tile.output_tile_bytes);
309 hashU64(hasher, tile.scratch_memory_bytes);
310 hashU64(hasher, @backingInt(tile.reduction_kind));
311 hashU64(hasher, tile.reduction_rank);
312 hashU64(hasher, tile.reduction_axis);
313 hashU64(hasher, tile.reduction_extent);
314 }
315
316 fn hashKernelScalarArgument(hasher: *choir.product.incremental.FingerprintBuilder, argument: choir_abi.ScalarArgument) void {
317 hashU64(hasher, @backingInt(argument));
318 switch (argument) {
319 .i32 => |value| hashU64(hasher, @as(u32, @bitCast(value))),
320 .u32 => |value| hashU64(hasher, value),
321 .i64 => |value| hashU64(hasher, @bitCast(value)),
322 .u64 => |value| hashU64(hasher, value),
323 .f32 => |value| hashU64(hasher, @as(u32, @bitCast(value))),
324 .f64 => |value| hashU64(hasher, @bitCast(value)),
325 }
326 }
327
328 fn hashOptionalDType(hasher: *choir.product.incremental.FingerprintBuilder, dtype: ?choir_abi.DType) void {
329 hasher.updateBool(dtype != null);
330 if (dtype) |value| hashU64(hasher, @backingInt(value));
331 }
332
333 fn hashOptionalU32(hasher: *choir.product.incremental.FingerprintBuilder, value: ?u32) void {
334 hasher.updateBool(value != null);
335 if (value) |payload| hasher.updateU32(payload);
336 }
337
338 fn hashOptionalU64(hasher: *choir.product.incremental.FingerprintBuilder, value: ?u64) void {
339 hasher.updateOptionalU64(value);
340 }
341
342 fn hashOptionalU64Slice(hasher: *choir.product.incremental.FingerprintBuilder, value: ?[]const u64) void {
343 hasher.updateOptionalU64Slice(value);
344 }
345
346 fn hashOptionalBytes(hasher: *choir.product.incremental.FingerprintBuilder, value: ?[]const u8) void {
347 hasher.updateBool(value != null);
348 if (value) |bytes| hashBytes(hasher, bytes);
349 }
350
351 fn hashBytes(hasher: *choir.product.incremental.FingerprintBuilder, bytes: []const u8) void {
352 hasher.updateBytes(bytes);
353 }
354
355 fn hashU64(hasher: *choir.product.incremental.FingerprintBuilder, value: u64) void {
356 hasher.updateU64(value);
357 }
358
359 test "artifact plan fingerprint includes backend target profile" {
360 const allocator = std.testing.allocator;
361
362 var f32_plan = plan_mod.BackendArtifactPlan.init(allocator, .{
363 .backend_kind = .cuda,
364 .artifact_format = .cuda_ptx,
365 .dtype_bits = gpu.DTypeSet.init(&.{.f32}).bits,
366 });
367 defer f32_plan.deinit();
368
369 var f16_f32_plan = plan_mod.BackendArtifactPlan.init(allocator, .{
370 .backend_kind = .cuda,
371 .artifact_format = .cuda_ptx,
372 .dtype_bits = gpu.DTypeSet.init(&.{ .f16, .f32 }).bits,
373 });
374 defer f16_f32_plan.deinit();
375
376 const tf32_profile = try preparation.BackendTargetProfile.initWithMathTier(.{
377 .identity = .{
378 .backend = .cuda,
379 .family = .nvidia_cuda,
380 },
381 .dtypes = gpu.DTypeSet.init(&.{.f32}),
382 .features = .{ .tensor_cores = true },
383 .artifact_formats = gpu.ArtifactFormatSet.init(&.{.cuda_ptx}),
384 }, .cuda, .cuda_ptx, .tf32_tensor);
385 var tf32_plan = plan_mod.BackendArtifactPlan.init(allocator, tf32_profile);
386 defer tf32_plan.deinit();
387
388 try std.testing.expect(artifactPlan(&f32_plan) != artifactPlan(&f16_f32_plan));
389 try std.testing.expect(artifactPlan(&f32_plan) != artifactPlan(&tf32_plan));
390 }
391
392 test "artifact plan fingerprint includes shape family compile identity" {
393 const allocator = std.testing.allocator;
394
395 var first = try standalonePlanWithShapeFamilyFingerprint(allocator, 0x1111);
396 defer first.deinit();
397 var same = try standalonePlanWithShapeFamilyFingerprint(allocator, 0x1111);
398 defer same.deinit();
399 var changed = try standalonePlanWithShapeFamilyFingerprint(allocator, 0x2222);
400 defer changed.deinit();
401
402 try std.testing.expectEqual(artifactPlan(&first), artifactPlan(&same));
403 try std.testing.expect(artifactPlan(&first) != artifactPlan(&changed));
404 }
405
406 fn standalonePlanWithShapeFamilyFingerprint(allocator: std.mem.Allocator, family_fingerprint: u64) !plan_mod.BackendArtifactPlan {
407 var plan = plan_mod.BackendArtifactPlan.init(allocator, .{
408 .backend_kind = .cuda,
409 .artifact_format = .cuda_ptx,
410 .dtype_bits = gpu.DTypeSet.init(&.{.f32}).bits,
411 });
412 errdefer plan.deinit();
413
414 var artifact = try gpu.KernelArtifact.init(allocator, .{
415 .backend = .cuda,
416 .format = .cuda_ptx,
417 .entry_name = "kernel_call",
418 .argument_count = 3,
419 });
420 var artifact_owned = true;
421 errdefer if (artifact_owned) artifact.deinit();
422 try artifact.setOwnedText("// ptx");
423
424 var compile = try plan_mod.PlannedKernelCompileContract.init(
425 allocator,
426 .kernel_call,
427 .kernel_call,
428 .cuda_ptx,
429 "kernel_call",
430 3,
431 gpu.DTypeSet.init(&.{.f32}),
432 .{},
433 .{},
434 family_fingerprint,
435 .{ .text = "// ptx" },
436 );
437 var compile_owned = true;
438 errdefer if (compile_owned) compile.deinit(allocator);
439
440 try plan.addStandaloneKernel(
441 artifact,
442 .{
443 .format = .cuda_ptx,
444 .element_count = 1,
445 .geometry = .{
446 .grid = .{ 1, 1, 1 },
447 .threadgroup = .{ 1, 1, 1 },
448 },
449 },
450 compile,
451 .{},
452 );
453 artifact_owned = false;
454 compile_owned = false;
455
456 return plan;
457 }
458
459 test "kernel call registry fingerprint includes payload and launch contract" {
460 const ptx = ".visible .entry first() { ret; }";
461 const other_ptx = ".visible .entry second() { ret; }";
462 const first_entries = [_]model.KernelCallArtifact{.{
463 .target = "accy.custom.scale",
464 .version = 1,
465 .format = .cuda_ptx,
466 .entry_name = "first",
467 .argument_count = 5,
468 .required_dtypes = gpu.DTypeSet.init(&.{.f32}),
469 .payload = .{ .text = ptx },
470 .launch = .{ .fixed = .{
471 .grid = .{ 1, 1, 1 },
472 .threadgroup = .{ 8, 1, 1 },
473 .dynamic_shared_memory_bytes = 0,
474 } },
475 .element_count_argument = .scalar_u32,
476 .shape_family_fingerprint = 0xaaaa,
477 .static_arguments = &.{.{ .u32 = 7 }},
478 }};
479 const first = model.KernelCallRegistry{ .entries = &first_entries };
480 const first_again = model.KernelCallRegistry{ .entries = &first_entries };
481 const changed_payload_entries = [_]model.KernelCallArtifact{.{
482 .target = "accy.custom.scale",
483 .version = 1,
484 .format = .cuda_ptx,
485 .entry_name = "first",
486 .argument_count = 5,
487 .required_dtypes = gpu.DTypeSet.init(&.{.f32}),
488 .payload = .{ .text = other_ptx },
489 .launch = .{ .fixed = .{
490 .grid = .{ 1, 1, 1 },
491 .threadgroup = .{ 8, 1, 1 },
492 .dynamic_shared_memory_bytes = 0,
493 } },
494 .element_count_argument = .scalar_u32,
495 .shape_family_fingerprint = 0xaaaa,
496 .static_arguments = &.{.{ .u32 = 7 }},
497 }};
498 const changed_payload = model.KernelCallRegistry{ .entries = &changed_payload_entries };
499 const changed_launch_entries = [_]model.KernelCallArtifact{.{
500 .target = "accy.custom.scale",
501 .version = 1,
502 .format = .cuda_ptx,
503 .entry_name = "first",
504 .argument_count = 5,
505 .required_dtypes = gpu.DTypeSet.init(&.{.f32}),
506 .payload = .{ .text = ptx },
507 .launch = .{ .fixed = .{
508 .grid = .{ 2, 1, 1 },
509 .threadgroup = .{ 8, 1, 1 },
510 .dynamic_shared_memory_bytes = 0,
511 } },
512 .element_count_argument = .scalar_u32,
513 .shape_family_fingerprint = 0xaaaa,
514 .static_arguments = &.{.{ .u32 = 7 }},
515 }};
516 const changed_launch = model.KernelCallRegistry{ .entries = &changed_launch_entries };
517 const changed_shape_family_entries = [_]model.KernelCallArtifact{.{
518 .target = "accy.custom.scale",
519 .version = 1,
520 .format = .cuda_ptx,
521 .entry_name = "first",
522 .argument_count = 5,
523 .required_dtypes = gpu.DTypeSet.init(&.{.f32}),
524 .payload = .{ .text = ptx },
525 .launch = .{ .fixed = .{
526 .grid = .{ 1, 1, 1 },
527 .threadgroup = .{ 8, 1, 1 },
528 .dynamic_shared_memory_bytes = 0,
529 } },
530 .element_count_argument = .scalar_u32,
531 .shape_family_fingerprint = 0xbbbb,
532 .static_arguments = &.{.{ .u32 = 7 }},
533 }};
534 const changed_shape_family = model.KernelCallRegistry{ .entries = &changed_shape_family_entries };
535
536 try std.testing.expectEqual(kernelCallRegistry(&first), kernelCallRegistry(&first_again));
537 try std.testing.expect(kernelCallRegistry(&first) != kernelCallRegistry(null));
538 try std.testing.expect(kernelCallRegistry(&first) != kernelCallRegistry(&changed_payload));
539 try std.testing.expect(kernelCallRegistry(&first) != kernelCallRegistry(&changed_launch));
540 try std.testing.expect(kernelCallRegistry(&first) != kernelCallRegistry(&changed_shape_family));
541 }