lib/accy/src/executable/test.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

   1 const namespace = @import("root.zig");
   2 const std = @import("std");
   3 const gpu = @import("gpu");
   4 const choir = @import("choir");
   5 const accy_root = @import("../root.zig");
   6 const exec_product = @import("plan.zig");
   7 const tuning_mod = @import("tuning.zig");
   8 const binding_mod = @import("binding.zig");
   9 const loaded_mod = @import("loaded.zig");
  10 const fixture = @import("fixture.zig");
  11 const passes = choir.passes;
  12 const LaunchCandidateMeasurement = tuning_mod.LaunchCandidateMeasurement;
  13 const LaunchTuningSelection = tuning_mod.LaunchTuningSelection;
  14 const launch_tuning_cache_record_version = tuning_mod.launch_tuning_cache_record_version;
  15 const LaunchTuningCacheRecord = tuning_mod.LaunchTuningCacheRecord;
  16 const encodeLaunchTuningArtifact = tuning_mod.encodeLaunchTuningArtifact;
  17 const decodeLaunchTuningArtifact = tuning_mod.decodeLaunchTuningArtifact;
  18 const LaunchTuningCacheKey = tuning_mod.LaunchTuningCacheKey;
  19 const LaunchTuningCache = tuning_mod.LaunchTuningCache;
  20 const LaunchGraphNode = exec_product.LaunchGraphNode;
  21 const LaunchGraphDependency = exec_product.LaunchGraphDependency;
  22 const LaunchGraphLoopCarry = exec_product.LaunchGraphLoopCarry;
  23 const LaunchGraphLoop = exec_product.LaunchGraphLoop;
  24 const createDataflowLaunchGraphPlan = exec_product.createDataflowLaunchGraphPlan;
  25 const loadKernels = loaded_mod.loadKernels;
  26 const RecordingBackendState = gpu.recording.BackendState;
  27 const addChoirModule = fixture.addChoirModule;
  28 const escapedTwoKernelChoirModule = fixture.escapedTwoKernelChoirModule;
  29 const createTestBackendArtifactPlan = fixture.createTestBackendArtifactPlan;
  30 const slotBindingsForKernel = fixture.slotBindingsForKernel;
  31 const slotBindingsForPlan = fixture.slotBindingsForPlan;
  32 const elementCountBindingsForPlan = fixture.elementCountBindingsForPlan;
  33 const firstElementCountBinding = fixture.firstElementCountBinding;
  34 const testing = std.testing;
  35 
  36 test {
  37     _ = @import("composition/test.zig");
  38     @import("test_discovery").discover(namespace);
  39     @import("test_discovery").discover(namespace.fragment);
  40     @import("test_discovery").discover(namespace.composition);
  41     @import("test_discovery").discover(namespace.compiler);
  42     @import("test_discovery").discover(namespace.invocation);
  43     @import("test_discovery").discover(namespace.schedule);
  44 }
  45 
  46 test "accy executable declaration coverage" {
  47     std.testing.refAllDecls(namespace);
  48     std.testing.refAllDecls(namespace.fragment);
  49     std.testing.refAllDecls(namespace.composition);
  50     std.testing.refAllDecls(namespace.compiler);
  51     std.testing.refAllDecls(namespace.invocation);
  52     std.testing.refAllDecls(namespace.schedule);
  53 }
  54 
  55 test "Choir executable launch graph forwards per-kernel stream event dependencies" {
  56     const allocator = testing.allocator;
  57 
  58     var owned = try escapedTwoKernelChoirModule(allocator, "choir_cuda_executable_launch_graph");
  59     defer owned.deinit();
  60 
  61     var cache = passes.AnalysisCache.init(allocator, null);
  62     defer cache.deinit();
  63     var pass_ctx = passes.PassContext.init(owned.choir_module, owned.ctx, allocator, &cache);
  64     defer pass_ctx.deinit();
  65 
  66     var state = RecordingBackendState{
  67         .allocator = allocator,
  68         .kind = .cuda,
  69         .format = .cuda_ptx,
  70     };
  71     const handle = state.handle();
  72 
  73     var artifact_plan = try createTestBackendArtifactPlan(allocator, handle, &pass_ctx, owned.choir_module, .{});
  74     defer artifact_plan.deinit();
  75     try testing.expectEqual(@as(usize, 2), artifact_plan.kernelCount());
  76 
  77     var executable = try loadKernels(allocator, handle, &artifact_plan);
  78     defer executable.deinit();
  79 
  80     const slot_bindings = try slotBindingsForPlan(allocator, &artifact_plan, .cuda);
  81     defer allocator.free(slot_bindings);
  82     const count_bindings = try elementCountBindingsForPlan(allocator, &artifact_plan, .cuda);
  83     defer allocator.free(count_bindings);
  84 
  85     const stream_a = gpu.StreamHandle{ .id = 71, .backend = .cuda };
  86     const stream_b = gpu.StreamHandle{ .id = 72, .backend = .cuda };
  87     const dependency = gpu.EventHandle{ .id = 91, .backend = .cuda };
  88     const done = gpu.EventHandle{ .id = 92, .backend = .cuda };
  89     const second_waits = [_]gpu.EventHandle{dependency};
  90     const nodes = [_]LaunchGraphNode{
  91         .{
  92             .kernel_index = 0,
  93             .stream = stream_a,
  94             .signal_event = dependency,
  95         },
  96         .{
  97             .kernel_index = 1,
  98             .stream = stream_b,
  99             .wait_events = &second_waits,
 100             .signal_event = done,
 101         },
 102     };
 103     const dependencies = [_]LaunchGraphDependency{.{
 104         .producer_node_index = 0,
 105         .consumer_node_index = 1,
 106         .slot_id = artifact_plan.kernels.items[0].output_slot_id,
 107     }};
 108 
 109     try executable.launchGraph(
 110         allocator,
 111         &artifact_plan,
 112         slot_bindings,
 113         count_bindings,
 114         .{
 115             .nodes = &nodes,
 116             .dependencies = &dependencies,
 117         },
 118     );
 119 
 120     const lowered_dependency = state.created_events[0];
 121     try testing.expectEqual(@as(usize, 2), state.launch_count);
 122     try testing.expectEqual(@as(usize, 1), state.created_event_count);
 123     try testing.expectEqual(@as(gpu.BackendObjectId, 71), state.launch_streams[0].?);
 124     try testing.expectEqual(@as(gpu.BackendObjectId, 91), state.launch_signal_events[0].?);
 125     try testing.expectEqual(@as(gpu.BackendObjectId, 72), state.launch_streams[1].?);
 126     try testing.expectEqual(@as(usize, 2), state.launch_wait_counts[1]);
 127     try testing.expectEqual(@as(gpu.BackendObjectId, 91), state.launch_wait_events[1][0]);
 128     try testing.expectEqual(lowered_dependency, state.launch_wait_events[1][1]);
 129     try testing.expectEqual(@as(gpu.BackendObjectId, 92), state.launch_signal_events[1].?);
 130 }
 131 
 132 test "Choir executable launch graph host loops remap carry slots" {
 133     const allocator = testing.allocator;
 134 
 135     var owned = try escapedTwoKernelChoirModule(allocator, "choir_cuda_executable_launch_graph_host_loop");
 136     defer owned.deinit();
 137 
 138     var cache = passes.AnalysisCache.init(allocator, null);
 139     defer cache.deinit();
 140     var pass_ctx = passes.PassContext.init(owned.choir_module, owned.ctx, allocator, &cache);
 141     defer pass_ctx.deinit();
 142 
 143     var state = RecordingBackendState{
 144         .allocator = allocator,
 145         .kind = .cuda,
 146         .format = .cuda_ptx,
 147     };
 148     const handle = state.handle();
 149 
 150     var artifact_plan = try createTestBackendArtifactPlan(allocator, handle, &pass_ctx, owned.choir_module, .{});
 151     defer artifact_plan.deinit();
 152     try testing.expectEqual(@as(usize, 2), artifact_plan.kernelCount());
 153 
 154     var executable = try loadKernels(allocator, handle, &artifact_plan);
 155     defer executable.deinit();
 156 
 157     const slot_bindings = try slotBindingsForPlan(allocator, &artifact_plan, .cuda);
 158     defer allocator.free(slot_bindings);
 159     const count_bindings = try elementCountBindingsForPlan(allocator, &artifact_plan, .cuda);
 160     defer allocator.free(count_bindings);
 161 
 162     var graph = try createDataflowLaunchGraphPlan(allocator, &artifact_plan, .{});
 163     defer graph.deinit();
 164 
 165     const initial_slot_id = artifact_plan.kernels.items[0].input_slot_ids[0];
 166     const output_slot_id = artifact_plan.kernels.items[1].output_slot_id;
 167     const carry = [_]LaunchGraphLoopCarry{.{
 168         .initial_slot_id = initial_slot_id,
 169         .input_slot_id = initial_slot_id,
 170         .output_slot_id = output_slot_id,
 171         .final_slot_id = output_slot_id,
 172     }};
 173     const loops = [_]LaunchGraphLoop{.{
 174         .first_node_index = 0,
 175         .node_count = graph.nodes.len,
 176         .trip_count = 3,
 177         .carries = &carry,
 178     }};
 179 
 180     try executable.launchGraph(
 181         allocator,
 182         &artifact_plan,
 183         slot_bindings,
 184         count_bindings,
 185         .{
 186             .nodes = graph.nodes,
 187             .dependencies = graph.dependencies,
 188             .loops = &loops,
 189         },
 190     );
 191 
 192     const initial_binding = try binding_mod.bindingForSlot(slot_bindings, initial_slot_id, .read_only);
 193     const output_binding = try binding_mod.bindingForSlot(slot_bindings, output_slot_id, .read_only);
 194 
 195     try testing.expectEqual(@as(usize, 6), state.launch_count);
 196     try testing.expect(state.launch_buffer_counts[0] >= 2);
 197     try testing.expect(state.launch_buffer_counts[1] >= 1);
 198     try testing.expectEqual(initial_binding.handle.id, state.launch_buffer_ids[0][1]);
 199     try testing.expectEqual(output_binding.handle.id, state.launch_buffer_ids[1][0]);
 200     try testing.expectEqual(output_binding.handle.id, state.launch_buffer_ids[2][1]);
 201     try testing.expectEqual(initial_binding.handle.id, state.launch_buffer_ids[3][0]);
 202     try testing.expectEqual(initial_binding.handle.id, state.launch_buffer_ids[4][1]);
 203     try testing.expectEqual(output_binding.handle.id, state.launch_buffer_ids[5][0]);
 204 }
 205 
 206 test "Choir executable launch graph lowers dependencies to backend events" {
 207     const allocator = testing.allocator;
 208 
 209     var owned = try escapedTwoKernelChoirModule(allocator, "choir_cuda_executable_launch_graph_event_lowering");
 210     defer owned.deinit();
 211 
 212     var cache = passes.AnalysisCache.init(allocator, null);
 213     defer cache.deinit();
 214     var pass_ctx = passes.PassContext.init(owned.choir_module, owned.ctx, allocator, &cache);
 215     defer pass_ctx.deinit();
 216 
 217     var state = RecordingBackendState{
 218         .allocator = allocator,
 219         .kind = .cuda,
 220         .format = .cuda_ptx,
 221     };
 222     const handle = state.handle();
 223 
 224     var artifact_plan = try createTestBackendArtifactPlan(allocator, handle, &pass_ctx, owned.choir_module, .{});
 225     defer artifact_plan.deinit();
 226     try testing.expectEqual(@as(usize, 2), artifact_plan.kernelCount());
 227 
 228     var executable = try loadKernels(allocator, handle, &artifact_plan);
 229     defer executable.deinit();
 230 
 231     const slot_bindings = try slotBindingsForPlan(allocator, &artifact_plan, .cuda);
 232     defer allocator.free(slot_bindings);
 233     const count_bindings = try elementCountBindingsForPlan(allocator, &artifact_plan, .cuda);
 234     defer allocator.free(count_bindings);
 235 
 236     var graph = try createDataflowLaunchGraphPlan(allocator, &artifact_plan, .{});
 237     defer graph.deinit();
 238     graph.nodes[0].stream = .{ .id = 71, .backend = .cuda };
 239     graph.nodes[1].stream = .{ .id = 72, .backend = .cuda };
 240 
 241     try executable.launchGraphWithDependencyEvents(
 242         allocator,
 243         &artifact_plan,
 244         slot_bindings,
 245         count_bindings,
 246         graph.plan(),
 247     );
 248 
 249     try testing.expectEqual(@as(usize, 1), state.created_event_count);
 250     const dependency_event = state.created_events[0];
 251     try testing.expectEqual(@as(usize, 2), state.launch_count);
 252     try testing.expectEqual(@as(gpu.BackendObjectId, 71), state.launch_streams[0].?);
 253     try testing.expectEqual(@as(gpu.BackendObjectId, 72), state.launch_streams[1].?);
 254     try testing.expectEqual(@as(usize, 1), state.launch_wait_counts[1]);
 255     try testing.expectEqual(dependency_event, state.launch_wait_events[1][0]);
 256     try testing.expectEqual(@as(usize, 1), state.record_event_count);
 257     try testing.expectEqual(@as(gpu.BackendObjectId, 71), state.record_streams[0].?);
 258     try testing.expectEqual(dependency_event, state.record_events[0].?);
 259 }
 260 
 261 test "Choir executable launch graph automatically lowers cross-stream dependencies" {
 262     const allocator = testing.allocator;
 263 
 264     var owned = try escapedTwoKernelChoirModule(allocator, "choir_cuda_executable_launch_graph_auto_events");
 265     defer owned.deinit();
 266 
 267     var cache = passes.AnalysisCache.init(allocator, null);
 268     defer cache.deinit();
 269     var pass_ctx = passes.PassContext.init(owned.choir_module, owned.ctx, allocator, &cache);
 270     defer pass_ctx.deinit();
 271 
 272     var state = RecordingBackendState{
 273         .allocator = allocator,
 274         .kind = .cuda,
 275         .format = .cuda_ptx,
 276     };
 277     const handle = state.handle();
 278 
 279     var artifact_plan = try createTestBackendArtifactPlan(allocator, handle, &pass_ctx, owned.choir_module, .{});
 280     defer artifact_plan.deinit();
 281     try testing.expectEqual(@as(usize, 2), artifact_plan.kernelCount());
 282 
 283     var executable = try loadKernels(allocator, handle, &artifact_plan);
 284     defer executable.deinit();
 285 
 286     const slot_bindings = try slotBindingsForPlan(allocator, &artifact_plan, .cuda);
 287     defer allocator.free(slot_bindings);
 288     const count_bindings = try elementCountBindingsForPlan(allocator, &artifact_plan, .cuda);
 289     defer allocator.free(count_bindings);
 290 
 291     var graph = try createDataflowLaunchGraphPlan(allocator, &artifact_plan, .{});
 292     defer graph.deinit();
 293     graph.nodes[0].stream = .{ .id = 71, .backend = .cuda };
 294     graph.nodes[1].stream = .{ .id = 72, .backend = .cuda };
 295 
 296     try executable.launchGraph(
 297         allocator,
 298         &artifact_plan,
 299         slot_bindings,
 300         count_bindings,
 301         graph.plan(),
 302     );
 303 
 304     try testing.expectEqual(@as(usize, 1), state.created_event_count);
 305     const dependency_event = state.created_events[0];
 306     try testing.expectEqual(@as(usize, 2), state.launch_count);
 307     try testing.expectEqual(@as(gpu.BackendObjectId, 71), state.launch_streams[0].?);
 308     try testing.expectEqual(@as(gpu.BackendObjectId, 72), state.launch_streams[1].?);
 309     try testing.expectEqual(@as(usize, 1), state.launch_wait_counts[1]);
 310     try testing.expectEqual(dependency_event, state.launch_wait_events[1][0]);
 311     try testing.expectEqual(@as(usize, 1), state.record_event_count);
 312     try testing.expectEqual(@as(gpu.BackendObjectId, 71), state.record_streams[0].?);
 313     try testing.expectEqual(dependency_event, state.record_events[0].?);
 314 }
 315 
 316 test "Choir executable dependency event lowering requires producer streams" {
 317     const allocator = testing.allocator;
 318 
 319     var owned = try escapedTwoKernelChoirModule(allocator, "choir_cuda_executable_launch_graph_event_stream");
 320     defer owned.deinit();
 321 
 322     var cache = passes.AnalysisCache.init(allocator, null);
 323     defer cache.deinit();
 324     var pass_ctx = passes.PassContext.init(owned.choir_module, owned.ctx, allocator, &cache);
 325     defer pass_ctx.deinit();
 326 
 327     var state = RecordingBackendState{
 328         .allocator = allocator,
 329         .kind = .cuda,
 330         .format = .cuda_ptx,
 331     };
 332     const handle = state.handle();
 333 
 334     var artifact_plan = try createTestBackendArtifactPlan(allocator, handle, &pass_ctx, owned.choir_module, .{});
 335     defer artifact_plan.deinit();
 336 
 337     var executable = try loadKernels(allocator, handle, &artifact_plan);
 338     defer executable.deinit();
 339 
 340     const slot_bindings = try slotBindingsForPlan(allocator, &artifact_plan, .cuda);
 341     defer allocator.free(slot_bindings);
 342 
 343     var graph = try createDataflowLaunchGraphPlan(allocator, &artifact_plan, .{});
 344     defer graph.deinit();
 345     graph.nodes[1].stream = .{ .id = 72, .backend = .cuda };
 346 
 347     try testing.expectError(
 348         error.LaunchArgumentMismatch,
 349         executable.launchGraphWithDependencyEvents(
 350             allocator,
 351             &artifact_plan,
 352             slot_bindings,
 353             &.{},
 354             graph.plan(),
 355         ),
 356     );
 357     try testing.expectEqual(@as(usize, 0), state.created_event_count);
 358     try testing.expectEqual(@as(usize, 0), state.launch_count);
 359 }
 360 
 361 test "Choir executable launch graph validates complete unique kernel nodes before launch" {
 362     const allocator = testing.allocator;
 363 
 364     var owned = try escapedTwoKernelChoirModule(allocator, "choir_cuda_executable_launch_graph_invalid");
 365     defer owned.deinit();
 366 
 367     var cache = passes.AnalysisCache.init(allocator, null);
 368     defer cache.deinit();
 369     var pass_ctx = passes.PassContext.init(owned.choir_module, owned.ctx, allocator, &cache);
 370     defer pass_ctx.deinit();
 371 
 372     var state = RecordingBackendState{
 373         .allocator = allocator,
 374         .kind = .cuda,
 375         .format = .cuda_ptx,
 376     };
 377     const handle = state.handle();
 378 
 379     var artifact_plan = try createTestBackendArtifactPlan(allocator, handle, &pass_ctx, owned.choir_module, .{});
 380     defer artifact_plan.deinit();
 381     try testing.expectEqual(@as(usize, 2), artifact_plan.kernelCount());
 382 
 383     var executable = try loadKernels(allocator, handle, &artifact_plan);
 384     defer executable.deinit();
 385 
 386     const slot_bindings = try slotBindingsForPlan(allocator, &artifact_plan, .cuda);
 387     defer allocator.free(slot_bindings);
 388 
 389     const duplicate_nodes = [_]LaunchGraphNode{
 390         .{ .kernel_index = 0 },
 391         .{ .kernel_index = 0 },
 392     };
 393     try testing.expectError(
 394         error.InvalidArtifact,
 395         executable.launchGraph(
 396             allocator,
 397             &artifact_plan,
 398             slot_bindings,
 399             &.{},
 400             .{ .nodes = &duplicate_nodes },
 401         ),
 402     );
 403 
 404     const missing_nodes = [_]LaunchGraphNode{.{ .kernel_index = 0 }};
 405     try testing.expectError(
 406         error.InvalidArtifact,
 407         executable.launchGraph(
 408             allocator,
 409             &artifact_plan,
 410             slot_bindings,
 411             &.{},
 412             .{ .nodes = &missing_nodes },
 413         ),
 414     );
 415     try testing.expectEqual(@as(usize, 0), state.launch_count);
 416 }
 417 
 418 test "Choir executable launch graph validates dependency indices before launch" {
 419     const allocator = testing.allocator;
 420 
 421     var owned = try escapedTwoKernelChoirModule(allocator, "choir_cuda_executable_launch_graph_bad_dependency");
 422     defer owned.deinit();
 423 
 424     var cache = passes.AnalysisCache.init(allocator, null);
 425     defer cache.deinit();
 426     var pass_ctx = passes.PassContext.init(owned.choir_module, owned.ctx, allocator, &cache);
 427     defer pass_ctx.deinit();
 428 
 429     var state = RecordingBackendState{
 430         .allocator = allocator,
 431         .kind = .cuda,
 432         .format = .cuda_ptx,
 433     };
 434     const handle = state.handle();
 435 
 436     var artifact_plan = try createTestBackendArtifactPlan(allocator, handle, &pass_ctx, owned.choir_module, .{});
 437     defer artifact_plan.deinit();
 438     try testing.expectEqual(@as(usize, 2), artifact_plan.kernelCount());
 439 
 440     var executable = try loadKernels(allocator, handle, &artifact_plan);
 441     defer executable.deinit();
 442 
 443     const slot_bindings = try slotBindingsForPlan(allocator, &artifact_plan, .cuda);
 444     defer allocator.free(slot_bindings);
 445 
 446     const nodes = [_]LaunchGraphNode{
 447         .{ .kernel_index = 0 },
 448         .{ .kernel_index = 1 },
 449     };
 450     const dependencies = [_]LaunchGraphDependency{.{
 451         .producer_node_index = 0,
 452         .consumer_node_index = 2,
 453         .slot_id = artifact_plan.kernels.items[0].output_slot_id,
 454     }};
 455 
 456     try testing.expectError(
 457         error.InvalidArtifact,
 458         executable.launchGraph(
 459             allocator,
 460             &artifact_plan,
 461             slot_bindings,
 462             &.{},
 463             .{
 464                 .nodes = &nodes,
 465                 .dependencies = &dependencies,
 466             },
 467         ),
 468     );
 469 
 470     const mismatched_dependencies = [_]LaunchGraphDependency{.{
 471         .producer_node_index = 0,
 472         .consumer_node_index = 1,
 473         .slot_id = artifact_plan.kernels.items[1].output_slot_id,
 474     }};
 475     try testing.expectError(
 476         error.InvalidArtifact,
 477         executable.launchGraph(
 478             allocator,
 479             &artifact_plan,
 480             slot_bindings,
 481             &.{},
 482             .{
 483                 .nodes = &nodes,
 484                 .dependencies = &mismatched_dependencies,
 485             },
 486         ),
 487     );
 488 
 489     const reversed_nodes = [_]LaunchGraphNode{
 490         .{ .kernel_index = 1 },
 491         .{ .kernel_index = 0 },
 492     };
 493     const reversed_dependencies = [_]LaunchGraphDependency{.{
 494         .producer_node_index = 1,
 495         .consumer_node_index = 0,
 496         .slot_id = artifact_plan.kernels.items[0].output_slot_id,
 497     }};
 498     try testing.expectError(
 499         error.InvalidArtifact,
 500         executable.launchGraph(
 501             allocator,
 502             &artifact_plan,
 503             slot_bindings,
 504             &.{},
 505             .{
 506                 .nodes = &reversed_nodes,
 507                 .dependencies = &reversed_dependencies,
 508             },
 509         ),
 510     );
 511     try testing.expectEqual(@as(usize, 0), state.launch_count);
 512 }
 513 
 514 test "Choir executable plan launches fastest measured candidate" {
 515     const allocator = testing.allocator;
 516 
 517     var owned = try addChoirModule(allocator, "choir_cuda_executable_measured_candidate");
 518     defer owned.deinit();
 519 
 520     var cache = passes.AnalysisCache.init(allocator, null);
 521     defer cache.deinit();
 522     var pass_ctx = passes.PassContext.init(owned.choir_module, owned.ctx, allocator, &cache);
 523     defer pass_ctx.deinit();
 524 
 525     var state = RecordingBackendState{
 526         .allocator = allocator,
 527         .kind = .cuda,
 528         .format = .cuda_ptx,
 529     };
 530     const handle = state.handle();
 531 
 532     var artifact_plan = try createTestBackendArtifactPlan(allocator, handle, &pass_ctx, owned.choir_module, .{});
 533     defer artifact_plan.deinit();
 534 
 535     var executable = try loadKernels(allocator, handle, &artifact_plan);
 536     defer executable.deinit();
 537 
 538     const kernel = artifact_plan.kernels.items[0];
 539     try testing.expect(kernel.launch_resources.candidate_count > 1);
 540     const slot_bindings = try slotBindingsForKernel(allocator, kernel, .cuda);
 541     defer allocator.free(slot_bindings);
 542     const count_bindings = try elementCountBindingsForPlan(allocator, &artifact_plan, .cuda);
 543     defer allocator.free(count_bindings);
 544 
 545     const measurements = [_]LaunchCandidateMeasurement{
 546         .{
 547             .kernel_id = kernel.kernel_id,
 548             .candidate_index = 0,
 549             .median_ns = 500,
 550             .sample_count = 8,
 551         },
 552         .{
 553             .kernel_id = kernel.kernel_id,
 554             .candidate_index = 1,
 555             .median_ns = 200,
 556             .sample_count = 4,
 557         },
 558     };
 559 
 560     try executable.launchAllWithOptions(allocator, &artifact_plan, slot_bindings, count_bindings, .{
 561         .tuning = .{ .measurements = &measurements },
 562     });
 563 
 564     const selected = kernel.launch_resources.candidates[1];
 565     try testing.expectEqual(@as(usize, 1), state.launch_count);
 566     try testing.expectEqual(selected.geometry.grid[0], state.last_launch_grid[0]);
 567     try testing.expectEqual(selected.geometry.threadgroup[0], state.last_launch_threadgroup[0]);
 568 }
 569 
 570 test "Choir executable launch tuning selections override measurements" {
 571     const allocator = testing.allocator;
 572 
 573     var owned = try addChoirModule(allocator, "choir_cuda_executable_selected_candidate");
 574     defer owned.deinit();
 575 
 576     var cache = passes.AnalysisCache.init(allocator, null);
 577     defer cache.deinit();
 578     var pass_ctx = passes.PassContext.init(owned.choir_module, owned.ctx, allocator, &cache);
 579     defer pass_ctx.deinit();
 580 
 581     var state = RecordingBackendState{
 582         .allocator = allocator,
 583         .kind = .cuda,
 584         .format = .cuda_ptx,
 585     };
 586     const handle = state.handle();
 587 
 588     var artifact_plan = try createTestBackendArtifactPlan(allocator, handle, &pass_ctx, owned.choir_module, .{});
 589     defer artifact_plan.deinit();
 590 
 591     var executable = try loadKernels(allocator, handle, &artifact_plan);
 592     defer executable.deinit();
 593 
 594     const kernel = artifact_plan.kernels.items[0];
 595     try testing.expect(kernel.launch_resources.candidate_count > 1);
 596     const slot_bindings = try slotBindingsForKernel(allocator, kernel, .cuda);
 597     defer allocator.free(slot_bindings);
 598     const count_bindings = try elementCountBindingsForPlan(allocator, &artifact_plan, .cuda);
 599     defer allocator.free(count_bindings);
 600 
 601     const measurements = [_]LaunchCandidateMeasurement{.{
 602         .kernel_id = kernel.kernel_id,
 603         .candidate_index = 0,
 604         .median_ns = 1,
 605         .sample_count = 8,
 606     }};
 607     const selections = [_]LaunchTuningSelection{.{
 608         .kernel_id = kernel.kernel_id,
 609         .candidate_index = 1,
 610         .median_ns = 200,
 611         .sample_count = 4,
 612     }};
 613 
 614     try executable.launchAllWithOptions(allocator, &artifact_plan, slot_bindings, count_bindings, .{
 615         .tuning = .{
 616             .selections = &selections,
 617             .measurements = &measurements,
 618         },
 619     });
 620 
 621     const selected = kernel.launch_resources.candidates[1];
 622     try testing.expectEqual(@as(usize, 1), state.launch_count);
 623     try testing.expectEqual(selected.geometry.grid[0], state.last_launch_grid[0]);
 624     try testing.expectEqual(selected.geometry.threadgroup[0], state.last_launch_threadgroup[0]);
 625 
 626     const duplicate_selections = [_]LaunchTuningSelection{ selections[0], selections[0] };
 627     try testing.expectError(
 628         error.LaunchArgumentMismatch,
 629         executable.launchAllWithOptions(allocator, &artifact_plan, slot_bindings, count_bindings, .{
 630             .tuning = .{ .selections = &duplicate_selections },
 631         }),
 632     );
 633     try testing.expectEqual(@as(usize, 1), state.launch_count);
 634 }
 635 
 636 test "Choir executable launch graph persists measured tuning selections" {
 637     const allocator = testing.allocator;
 638 
 639     var owned = try addChoirModule(allocator, "choir_cuda_executable_graph_tuning");
 640     defer owned.deinit();
 641 
 642     var cache = passes.AnalysisCache.init(allocator, null);
 643     defer cache.deinit();
 644     var pass_ctx = passes.PassContext.init(owned.choir_module, owned.ctx, allocator, &cache);
 645     defer pass_ctx.deinit();
 646 
 647     var state = RecordingBackendState{
 648         .allocator = allocator,
 649         .kind = .cuda,
 650         .format = .cuda_ptx,
 651     };
 652     const handle = state.handle();
 653 
 654     var artifact_plan = try createTestBackendArtifactPlan(allocator, handle, &pass_ctx, owned.choir_module, .{});
 655     defer artifact_plan.deinit();
 656 
 657     var executable = try loadKernels(allocator, handle, &artifact_plan);
 658     defer executable.deinit();
 659 
 660     const kernel = artifact_plan.kernels.items[0];
 661     try testing.expect(kernel.launch_resources.candidate_count > 1);
 662     const slot_bindings = try slotBindingsForKernel(allocator, kernel, .cuda);
 663     defer allocator.free(slot_bindings);
 664     const count_bindings = try elementCountBindingsForPlan(allocator, &artifact_plan, .cuda);
 665     defer allocator.free(count_bindings);
 666 
 667     var graph = try createDataflowLaunchGraphPlan(allocator, &artifact_plan, .{});
 668     defer graph.deinit();
 669 
 670     const measurements = [_]LaunchCandidateMeasurement{
 671         .{
 672             .kernel_id = kernel.kernel_id,
 673             .candidate_index = 0,
 674             .median_ns = 500,
 675             .sample_count = 8,
 676         },
 677         .{
 678             .kernel_id = kernel.kernel_id,
 679             .candidate_index = 1,
 680             .median_ns = 200,
 681             .sample_count = 4,
 682         },
 683     };
 684 
 685     try graph.applyMeasuredLaunchTuning(&artifact_plan, &measurements);
 686 
 687     try testing.expectEqual(@as(usize, 1), graph.tuning_selections.len);
 688     try testing.expectEqual(kernel.kernel_id, graph.tuning_selections[0].kernel_id);
 689     try testing.expectEqual(@as(usize, 1), graph.tuning_selections[0].candidate_index);
 690     try testing.expectEqual(@as(u64, 200), graph.tuning_selections[0].median_ns);
 691     try testing.expectEqual(@as(u32, 4), graph.tuning_selections[0].sample_count);
 692     try testing.expectEqual(@as(usize, 1), graph.nodes[0].tuning.selections.len);
 693     try testing.expectEqual(@as(usize, 0), graph.nodes[0].tuning.measurements.len);
 694     try testing.expectEqual(@intFromPtr(graph.tuning_selections.ptr), @intFromPtr(graph.nodes[0].tuning.selections.ptr));
 695 
 696     try executable.launchGraph(
 697         allocator,
 698         &artifact_plan,
 699         slot_bindings,
 700         count_bindings,
 701         graph.plan(),
 702     );
 703 
 704     const selected = kernel.launch_resources.candidates[1];
 705     try testing.expectEqual(@as(usize, 1), state.launch_count);
 706     try testing.expectEqual(selected.geometry.grid[0], state.last_launch_grid[0]);
 707     try testing.expectEqual(selected.geometry.threadgroup[0], state.last_launch_threadgroup[0]);
 708 }
 709 
 710 test "Choir executable launch tuning cache applies measured graph selections" {
 711     const allocator = testing.allocator;
 712 
 713     var owned = try addChoirModule(allocator, "choir_cuda_executable_graph_tuning_cache");
 714     defer owned.deinit();
 715 
 716     var cache = passes.AnalysisCache.init(allocator, null);
 717     defer cache.deinit();
 718     var pass_ctx = passes.PassContext.init(owned.choir_module, owned.ctx, allocator, &cache);
 719     defer pass_ctx.deinit();
 720 
 721     var state = RecordingBackendState{
 722         .allocator = allocator,
 723         .kind = .cuda,
 724         .format = .cuda_ptx,
 725     };
 726     const handle = state.handle();
 727     const caps = try handle.queryCapabilities();
 728 
 729     var artifact_plan = try createTestBackendArtifactPlan(allocator, handle, &pass_ctx, owned.choir_module, .{});
 730     defer artifact_plan.deinit();
 731 
 732     var executable = try loadKernels(allocator, handle, &artifact_plan);
 733     defer executable.deinit();
 734 
 735     const kernel = artifact_plan.kernels.items[0];
 736     try testing.expect(kernel.launch_resources.candidate_count > 1);
 737     const slot_bindings = try slotBindingsForKernel(allocator, kernel, .cuda);
 738     defer allocator.free(slot_bindings);
 739     const count_bindings = try elementCountBindingsForPlan(allocator, &artifact_plan, .cuda);
 740     defer allocator.free(count_bindings);
 741 
 742     var tuning_cache = LaunchTuningCache.init(allocator);
 743     defer tuning_cache.deinit();
 744 
 745     const measurements = [_]LaunchCandidateMeasurement{
 746         .{
 747             .kernel_id = kernel.kernel_id,
 748             .candidate_index = 0,
 749             .median_ns = 500,
 750             .sample_count = 8,
 751         },
 752         .{
 753             .kernel_id = kernel.kernel_id,
 754             .candidate_index = 1,
 755             .median_ns = 200,
 756             .sample_count = 4,
 757         },
 758     };
 759     try tuning_cache.recordMeasurements(caps, &artifact_plan, &measurements);
 760     try testing.expectEqual(@as(usize, 1), tuning_cache.count());
 761 
 762     const slower_measurements = [_]LaunchCandidateMeasurement{.{
 763         .kernel_id = kernel.kernel_id,
 764         .candidate_index = 0,
 765         .median_ns = 600,
 766         .sample_count = 16,
 767     }};
 768     try tuning_cache.recordMeasurements(caps, &artifact_plan, &slower_measurements);
 769     try testing.expectEqual(@as(usize, 1), tuning_cache.count());
 770 
 771     var graph = try createDataflowLaunchGraphPlan(allocator, &artifact_plan, .{});
 772     defer graph.deinit();
 773     try graph.applyCachedLaunchTuning(caps, &artifact_plan, &tuning_cache);
 774 
 775     try testing.expectEqual(@as(usize, 1), graph.tuning_selections.len);
 776     try testing.expectEqual(kernel.kernel_id, graph.tuning_selections[0].kernel_id);
 777     try testing.expectEqual(@as(usize, 1), graph.tuning_selections[0].candidate_index);
 778     try testing.expectEqual(@as(u64, 200), graph.tuning_selections[0].median_ns);
 779     try testing.expectEqual(@as(u32, 4), graph.tuning_selections[0].sample_count);
 780     try testing.expectEqual(@as(usize, 1), graph.nodes[0].tuning.selections.len);
 781     try testing.expectEqual(@as(usize, 0), graph.nodes[0].tuning.measurements.len);
 782 
 783     try executable.launchGraph(
 784         allocator,
 785         &artifact_plan,
 786         slot_bindings,
 787         count_bindings,
 788         graph.plan(),
 789     );
 790 
 791     const selected = kernel.launch_resources.candidates[1];
 792     try testing.expectEqual(@as(usize, 1), state.launch_count);
 793     try testing.expectEqual(selected.geometry.grid[0], state.last_launch_grid[0]);
 794     try testing.expectEqual(selected.geometry.threadgroup[0], state.last_launch_threadgroup[0]);
 795 }
 796 
 797 test "Choir executable launch tuning cache exports and imports records" {
 798     const allocator = testing.allocator;
 799 
 800     var owned = try addChoirModule(allocator, "choir_cuda_executable_graph_tuning_records");
 801     defer owned.deinit();
 802 
 803     var cache = passes.AnalysisCache.init(allocator, null);
 804     defer cache.deinit();
 805     var pass_ctx = passes.PassContext.init(owned.choir_module, owned.ctx, allocator, &cache);
 806     defer pass_ctx.deinit();
 807 
 808     var state = RecordingBackendState{
 809         .allocator = allocator,
 810         .kind = .cuda,
 811         .format = .cuda_ptx,
 812     };
 813     const handle = state.handle();
 814     const caps = try handle.queryCapabilities();
 815 
 816     var artifact_plan = try createTestBackendArtifactPlan(allocator, handle, &pass_ctx, owned.choir_module, .{});
 817     defer artifact_plan.deinit();
 818 
 819     var executable = try loadKernels(allocator, handle, &artifact_plan);
 820     defer executable.deinit();
 821 
 822     const kernel = artifact_plan.kernels.items[0];
 823     try testing.expect(kernel.launch_resources.candidate_count > 1);
 824     const slot_bindings = try slotBindingsForKernel(allocator, kernel, .cuda);
 825     defer allocator.free(slot_bindings);
 826     const count_bindings = try elementCountBindingsForPlan(allocator, &artifact_plan, .cuda);
 827     defer allocator.free(count_bindings);
 828 
 829     var tuning_cache = LaunchTuningCache.init(allocator);
 830     defer tuning_cache.deinit();
 831 
 832     const measurements = [_]LaunchCandidateMeasurement{
 833         .{
 834             .kernel_id = kernel.kernel_id,
 835             .candidate_index = 0,
 836             .median_ns = 500,
 837             .sample_count = 8,
 838         },
 839         .{
 840             .kernel_id = kernel.kernel_id,
 841             .candidate_index = 1,
 842             .median_ns = 200,
 843             .sample_count = 4,
 844         },
 845     };
 846     try tuning_cache.recordMeasurements(caps, &artifact_plan, &measurements);
 847 
 848     const records = try tuning_cache.exportRecords(allocator);
 849     defer allocator.free(records);
 850     try testing.expectEqual(@as(usize, 1), records.len);
 851     try testing.expectEqual(launch_tuning_cache_record_version, records[0].version);
 852     try testing.expectEqual(@as(u32, @intCast(kernel.launch_resources.candidate_count)), records[0].key.candidate_count);
 853     try testing.expect(records[0].key.launch_resource_fingerprint != 0);
 854     try testing.expectEqual(kernel.output_layout_fingerprint, records[0].key.output_layout_fingerprint);
 855     try testing.expectEqual(kernel.input_layout_fingerprint, records[0].key.input_layout_fingerprint);
 856     try testing.expectEqual(kernel.kernel_id, records[0].selection.kernel_id);
 857     try testing.expectEqual(@as(usize, 1), records[0].selection.candidate_index);
 858 
 859     var imported = LaunchTuningCache.init(allocator);
 860     defer imported.deinit();
 861     try imported.importRecords(records);
 862     try testing.expectEqual(@as(usize, 1), imported.count());
 863 
 864     var graph = try createDataflowLaunchGraphPlan(allocator, &artifact_plan, .{});
 865     defer graph.deinit();
 866     try graph.applyCachedLaunchTuning(caps, &artifact_plan, &imported);
 867 
 868     try testing.expectEqual(@as(usize, 1), graph.tuning_selections.len);
 869     try testing.expectEqual(kernel.kernel_id, graph.tuning_selections[0].kernel_id);
 870     try testing.expectEqual(@as(usize, 1), graph.tuning_selections[0].candidate_index);
 871     try testing.expectEqual(@as(u64, 200), graph.tuning_selections[0].median_ns);
 872 
 873     try executable.launchGraph(
 874         allocator,
 875         &artifact_plan,
 876         slot_bindings,
 877         count_bindings,
 878         graph.plan(),
 879     );
 880 
 881     const selected = kernel.launch_resources.candidates[1];
 882     try testing.expectEqual(@as(usize, 1), state.launch_count);
 883     try testing.expectEqual(selected.geometry.grid[0], state.last_launch_grid[0]);
 884     try testing.expectEqual(selected.geometry.threadgroup[0], state.last_launch_threadgroup[0]);
 885 }
 886 
 887 test "Choir executable launch tuning artifact round-trips records" {
 888     const allocator = testing.allocator;
 889 
 890     var owned = try addChoirModule(allocator, "choir_cuda_executable_graph_tuning_artifact");
 891     defer owned.deinit();
 892 
 893     var cache = passes.AnalysisCache.init(allocator, null);
 894     defer cache.deinit();
 895     var pass_ctx = passes.PassContext.init(owned.choir_module, owned.ctx, allocator, &cache);
 896     defer pass_ctx.deinit();
 897 
 898     var state = RecordingBackendState{
 899         .allocator = allocator,
 900         .kind = .cuda,
 901         .format = .cuda_ptx,
 902     };
 903     const handle = state.handle();
 904     const caps = try handle.queryCapabilities();
 905 
 906     var artifact_plan = try createTestBackendArtifactPlan(allocator, handle, &pass_ctx, owned.choir_module, .{});
 907     defer artifact_plan.deinit();
 908 
 909     var executable = try loadKernels(allocator, handle, &artifact_plan);
 910     defer executable.deinit();
 911 
 912     const kernel = artifact_plan.kernels.items[0];
 913     try testing.expect(kernel.launch_resources.candidate_count > 1);
 914     const slot_bindings = try slotBindingsForKernel(allocator, kernel, .cuda);
 915     defer allocator.free(slot_bindings);
 916     const count_bindings = try elementCountBindingsForPlan(allocator, &artifact_plan, .cuda);
 917     defer allocator.free(count_bindings);
 918 
 919     var tuning_cache = LaunchTuningCache.init(allocator);
 920     defer tuning_cache.deinit();
 921 
 922     const measurements = [_]LaunchCandidateMeasurement{
 923         .{
 924             .kernel_id = kernel.kernel_id,
 925             .candidate_index = 0,
 926             .median_ns = 500,
 927             .sample_count = 8,
 928         },
 929         .{
 930             .kernel_id = kernel.kernel_id,
 931             .candidate_index = 1,
 932             .median_ns = 200,
 933             .sample_count = 4,
 934         },
 935     };
 936     try tuning_cache.recordMeasurements(caps, &artifact_plan, &measurements);
 937 
 938     const records = try tuning_cache.exportRecords(allocator);
 939     defer allocator.free(records);
 940     const artifact_bytes = try encodeLaunchTuningArtifact(allocator, records);
 941     defer allocator.free(artifact_bytes);
 942     const decoded = try decodeLaunchTuningArtifact(allocator, artifact_bytes);
 943     defer allocator.free(decoded);
 944     const encoded_again = try encodeLaunchTuningArtifact(allocator, decoded);
 945     defer allocator.free(encoded_again);
 946 
 947     try testing.expectEqualSlices(u8, artifact_bytes, encoded_again);
 948     try testing.expectEqual(@as(usize, 1), decoded.len);
 949     try testing.expectEqual(launch_tuning_cache_record_version, decoded[0].version);
 950     try testing.expectEqual(records[0].key, decoded[0].key);
 951     try testing.expectEqual(kernel.output_layout_fingerprint, decoded[0].key.output_layout_fingerprint);
 952     try testing.expectEqual(kernel.input_layout_fingerprint, decoded[0].key.input_layout_fingerprint);
 953     try testing.expectEqual(records[0].selection, decoded[0].selection);
 954 
 955     var imported = LaunchTuningCache.init(allocator);
 956     defer imported.deinit();
 957     try imported.importRecords(decoded);
 958 
 959     var graph = try createDataflowLaunchGraphPlan(allocator, &artifact_plan, .{});
 960     defer graph.deinit();
 961     try graph.applyCachedLaunchTuning(caps, &artifact_plan, &imported);
 962 
 963     try testing.expectEqual(@as(usize, 1), graph.tuning_selections.len);
 964     try testing.expectEqual(kernel.kernel_id, graph.tuning_selections[0].kernel_id);
 965     try testing.expectEqual(@as(usize, 1), graph.tuning_selections[0].candidate_index);
 966     try testing.expectEqual(@as(u64, 200), graph.tuning_selections[0].median_ns);
 967 
 968     try executable.launchGraph(
 969         allocator,
 970         &artifact_plan,
 971         slot_bindings,
 972         count_bindings,
 973         graph.plan(),
 974     );
 975 
 976     const selected = kernel.launch_resources.candidates[1];
 977     try testing.expectEqual(@as(usize, 1), state.launch_count);
 978     try testing.expectEqual(selected.geometry.grid[0], state.last_launch_grid[0]);
 979     try testing.expectEqual(selected.geometry.threadgroup[0], state.last_launch_threadgroup[0]);
 980 }
 981 
 982 test "Choir executable launch tuning artifact rejects invalid bytes" {
 983     const allocator = testing.allocator;
 984     const record = LaunchTuningCacheRecord{
 985         .key = .{
 986             .backend = .cuda,
 987             .family = .nvidia_cuda,
 988             .format = .cuda_ptx,
 989             .candidate_count = 2,
 990         },
 991         .selection = .{
 992             .kernel_id = 0,
 993             .candidate_index = 1,
 994             .median_ns = 200,
 995             .sample_count = 4,
 996         },
 997     };
 998     const artifact_bytes = try encodeLaunchTuningArtifact(allocator, &.{record});
 999     defer allocator.free(artifact_bytes);
1000 
1001     var bad_magic = try allocator.dupe(u8, artifact_bytes);
1002     defer allocator.free(bad_magic);
1003     bad_magic[0] ^= 0xff;
1004     try testing.expectError(error.InvalidArtifact, decodeLaunchTuningArtifact(allocator, bad_magic));
1005 
1006     var bad_version = try allocator.dupe(u8, artifact_bytes);
1007     defer allocator.free(bad_version);
1008     bad_version[4] ^= 0xff;
1009     try testing.expectError(error.InvalidArtifact, decodeLaunchTuningArtifact(allocator, bad_version));
1010 
1011     try testing.expectError(error.InvalidArtifact, decodeLaunchTuningArtifact(allocator, artifact_bytes[0 .. artifact_bytes.len - 1]));
1012 
1013     var extra = std.ArrayListUnmanaged(u8).empty;
1014     defer extra.deinit(allocator);
1015     try extra.appendSlice(allocator, artifact_bytes);
1016     try extra.append(allocator, 0);
1017     try testing.expectError(error.InvalidArtifact, decodeLaunchTuningArtifact(allocator, extra.items));
1018 }
1019 
1020 test "Choir executable launch tuning cache rejects invalid records before import" {
1021     const allocator = testing.allocator;
1022 
1023     var owned = try addChoirModule(allocator, "choir_cuda_executable_graph_tuning_bad_records");
1024     defer owned.deinit();
1025 
1026     var cache = passes.AnalysisCache.init(allocator, null);
1027     defer cache.deinit();
1028     var pass_ctx = passes.PassContext.init(owned.choir_module, owned.ctx, allocator, &cache);
1029     defer pass_ctx.deinit();
1030 
1031     var state = RecordingBackendState{
1032         .allocator = allocator,
1033         .kind = .cuda,
1034         .format = .cuda_ptx,
1035     };
1036     const handle = state.handle();
1037     const caps = try handle.queryCapabilities();
1038 
1039     var artifact_plan = try createTestBackendArtifactPlan(allocator, handle, &pass_ctx, owned.choir_module, .{});
1040     defer artifact_plan.deinit();
1041 
1042     const kernel = artifact_plan.kernels.items[0];
1043     const key = LaunchTuningCacheKey.init(caps, kernel);
1044     const valid = LaunchTuningCacheRecord{
1045         .key = key,
1046         .selection = .{
1047             .kernel_id = kernel.kernel_id,
1048             .candidate_index = 0,
1049             .median_ns = 500,
1050             .sample_count = 8,
1051         },
1052     };
1053     const invalid = LaunchTuningCacheRecord{
1054         .key = key,
1055         .selection = .{
1056             .kernel_id = kernel.kernel_id,
1057             .candidate_index = @intCast(key.candidate_count),
1058             .median_ns = 1,
1059             .sample_count = 1,
1060         },
1061     };
1062     const records = [_]LaunchTuningCacheRecord{ valid, invalid };
1063 
1064     var tuning_cache = LaunchTuningCache.init(allocator);
1065     defer tuning_cache.deinit();
1066     try testing.expectError(error.LaunchArgumentMismatch, tuning_cache.importRecords(&records));
1067     try testing.expectEqual(@as(usize, 0), tuning_cache.count());
1068 
1069     try tuning_cache.importRecords(&.{valid});
1070     try testing.expectEqual(@as(usize, 1), tuning_cache.count());
1071 
1072     var stale = valid;
1073     stale.version = launch_tuning_cache_record_version + 1;
1074     try testing.expectError(error.InvalidArtifact, tuning_cache.importRecords(&.{stale}));
1075     try testing.expectEqual(@as(usize, 1), tuning_cache.count());
1076 }
1077 
1078 test "Choir executable launch tuning cache keys reuse by launch resource identity" {
1079     const allocator = testing.allocator;
1080 
1081     var owned = try addChoirModule(allocator, "choir_cuda_executable_graph_tuning_cache_key");
1082     defer owned.deinit();
1083 
1084     var cache = passes.AnalysisCache.init(allocator, null);
1085     defer cache.deinit();
1086     var pass_ctx = passes.PassContext.init(owned.choir_module, owned.ctx, allocator, &cache);
1087     defer pass_ctx.deinit();
1088 
1089     var state = RecordingBackendState{
1090         .allocator = allocator,
1091         .kind = .cuda,
1092         .format = .cuda_ptx,
1093     };
1094     const handle = state.handle();
1095     const caps = try handle.queryCapabilities();
1096 
1097     var artifact_plan = try createTestBackendArtifactPlan(allocator, handle, &pass_ctx, owned.choir_module, .{});
1098     defer artifact_plan.deinit();
1099 
1100     const kernel = artifact_plan.kernels.items[0];
1101     try testing.expect(kernel.launch_resources.candidate_count > 1);
1102 
1103     var tuning_cache = LaunchTuningCache.init(allocator);
1104     defer tuning_cache.deinit();
1105     try tuning_cache.recordMeasuredSelection(caps, kernel, .{
1106         .kernel_id = kernel.kernel_id,
1107         .candidate_index = 1,
1108         .median_ns = 200,
1109         .sample_count = 4,
1110     });
1111 
1112     var equivalent_kernel = kernel;
1113     equivalent_kernel.kernel_id += 1000;
1114     const cached = (try tuning_cache.selectionForKernel(caps, equivalent_kernel)).?;
1115     try testing.expectEqual(equivalent_kernel.kernel_id, cached.kernel_id);
1116     try testing.expectEqual(@as(usize, 1), cached.candidate_index);
1117 
1118     var different_caps = caps;
1119     different_caps.threadgroup.max_threads = 512;
1120     try testing.expectEqual(@as(?LaunchTuningSelection, null), try tuning_cache.selectionForKernel(different_caps, kernel));
1121 }
1122 
1123 test "Choir executable launch graph rejects invalid measured tuning selection" {
1124     const allocator = testing.allocator;
1125 
1126     var owned = try addChoirModule(allocator, "choir_cuda_executable_graph_bad_tuning");
1127     defer owned.deinit();
1128 
1129     var cache = passes.AnalysisCache.init(allocator, null);
1130     defer cache.deinit();
1131     var pass_ctx = passes.PassContext.init(owned.choir_module, owned.ctx, allocator, &cache);
1132     defer pass_ctx.deinit();
1133 
1134     var state = RecordingBackendState{
1135         .allocator = allocator,
1136         .kind = .cuda,
1137         .format = .cuda_ptx,
1138     };
1139     const handle = state.handle();
1140 
1141     var artifact_plan = try createTestBackendArtifactPlan(allocator, handle, &pass_ctx, owned.choir_module, .{});
1142     defer artifact_plan.deinit();
1143 
1144     const kernel = artifact_plan.kernels.items[0];
1145     var graph = try createDataflowLaunchGraphPlan(allocator, &artifact_plan, .{});
1146     defer graph.deinit();
1147 
1148     const measurements = [_]LaunchCandidateMeasurement{.{
1149         .kernel_id = kernel.kernel_id,
1150         .candidate_index = kernel.launch_resources.candidate_count,
1151         .median_ns = 1,
1152     }};
1153 
1154     try testing.expectError(
1155         error.LaunchArgumentMismatch,
1156         graph.applyMeasuredLaunchTuning(&artifact_plan, &measurements),
1157     );
1158     try testing.expectEqual(@as(usize, 0), graph.tuning_selections.len);
1159     try testing.expectEqual(@as(usize, 0), graph.nodes[0].tuning.selections.len);
1160     try testing.expectEqual(@as(usize, 0), graph.nodes[0].tuning.measurements.len);
1161 }
1162 
1163 test "Choir executable plan rejects invalid measured candidate" {
1164     const allocator = testing.allocator;
1165 
1166     var owned = try addChoirModule(allocator, "choir_cuda_executable_invalid_candidate");
1167     defer owned.deinit();
1168 
1169     var cache = passes.AnalysisCache.init(allocator, null);
1170     defer cache.deinit();
1171     var pass_ctx = passes.PassContext.init(owned.choir_module, owned.ctx, allocator, &cache);
1172     defer pass_ctx.deinit();
1173 
1174     var state = RecordingBackendState{
1175         .allocator = allocator,
1176         .kind = .cuda,
1177         .format = .cuda_ptx,
1178     };
1179     const handle = state.handle();
1180 
1181     var artifact_plan = try createTestBackendArtifactPlan(allocator, handle, &pass_ctx, owned.choir_module, .{});
1182     defer artifact_plan.deinit();
1183 
1184     var executable = try loadKernels(allocator, handle, &artifact_plan);
1185     defer executable.deinit();
1186 
1187     const kernel = artifact_plan.kernels.items[0];
1188     const slot_bindings = try slotBindingsForKernel(allocator, kernel, .cuda);
1189     defer allocator.free(slot_bindings);
1190 
1191     const measurements = [_]LaunchCandidateMeasurement{.{
1192         .kernel_id = kernel.kernel_id,
1193         .candidate_index = kernel.launch_resources.candidate_count,
1194         .median_ns = 1,
1195     }};
1196 
1197     try testing.expectError(
1198         error.LaunchArgumentMismatch,
1199         executable.launchAllWithOptions(allocator, &artifact_plan, slot_bindings, &.{}, .{
1200             .tuning = .{ .measurements = &measurements },
1201         }),
1202     );
1203     try testing.expectEqual(@as(usize, 0), state.launch_count);
1204 }
1205 
1206 test "Choir executable plan measures launch candidates" {
1207     const allocator = testing.allocator;
1208 
1209     var owned = try addChoirModule(allocator, "choir_cuda_executable_measure_candidates");
1210     defer owned.deinit();
1211 
1212     var cache = passes.AnalysisCache.init(allocator, null);
1213     defer cache.deinit();
1214     var pass_ctx = passes.PassContext.init(owned.choir_module, owned.ctx, allocator, &cache);
1215     defer pass_ctx.deinit();
1216 
1217     var state = RecordingBackendState{
1218         .allocator = allocator,
1219         .kind = .cuda,
1220         .format = .cuda_ptx,
1221     };
1222     const handle = state.handle();
1223 
1224     var artifact_plan = try createTestBackendArtifactPlan(allocator, handle, &pass_ctx, owned.choir_module, .{});
1225     defer artifact_plan.deinit();
1226 
1227     var executable = try loadKernels(allocator, handle, &artifact_plan);
1228     defer executable.deinit();
1229 
1230     const kernel = artifact_plan.kernels.items[0];
1231     try testing.expect(kernel.launch_resources.candidate_count > 1);
1232     const slot_bindings = try slotBindingsForKernel(allocator, kernel, .cuda);
1233     defer allocator.free(slot_bindings);
1234     const count_bindings = try elementCountBindingsForPlan(allocator, &artifact_plan, .cuda);
1235     defer allocator.free(count_bindings);
1236 
1237     const measurements = try executable.measureLaunchCandidates(
1238         allocator,
1239         allocator,
1240         &artifact_plan,
1241         0,
1242         slot_bindings,
1243         firstElementCountBinding(count_bindings),
1244         .{ .warmup = 1, .samples = 2 },
1245     );
1246     defer allocator.free(measurements);
1247 
1248     try testing.expectEqual(kernel.launch_resources.candidate_count, measurements.len);
1249     try testing.expectEqual(kernel.launch_resources.candidate_count * 3, state.launch_count);
1250     for (measurements, 0..) |measurement, index| {
1251         try testing.expectEqual(kernel.kernel_id, measurement.kernel_id);
1252         try testing.expectEqual(index, measurement.candidate_index);
1253         try testing.expectEqual(@as(u32, 2), measurement.sample_count);
1254     }
1255 }
1256 
1257 test "Choir executable launch candidate measurement requires samples" {
1258     const allocator = testing.allocator;
1259 
1260     var owned = try addChoirModule(allocator, "choir_cuda_executable_measure_no_samples");
1261     defer owned.deinit();
1262 
1263     var cache = passes.AnalysisCache.init(allocator, null);
1264     defer cache.deinit();
1265     var pass_ctx = passes.PassContext.init(owned.choir_module, owned.ctx, allocator, &cache);
1266     defer pass_ctx.deinit();
1267 
1268     var state = RecordingBackendState{
1269         .allocator = allocator,
1270         .kind = .cuda,
1271         .format = .cuda_ptx,
1272     };
1273     const handle = state.handle();
1274 
1275     var artifact_plan = try createTestBackendArtifactPlan(allocator, handle, &pass_ctx, owned.choir_module, .{});
1276     defer artifact_plan.deinit();
1277 
1278     var executable = try loadKernels(allocator, handle, &artifact_plan);
1279     defer executable.deinit();
1280 
1281     const kernel = artifact_plan.kernels.items[0];
1282     const slot_bindings = try slotBindingsForKernel(allocator, kernel, .cuda);
1283     defer allocator.free(slot_bindings);
1284     const count_bindings = try elementCountBindingsForPlan(allocator, &artifact_plan, .cuda);
1285     defer allocator.free(count_bindings);
1286 
1287     try testing.expectError(
1288         error.LaunchArgumentMismatch,
1289         executable.measureLaunchCandidates(
1290             allocator,
1291             allocator,
1292             &artifact_plan,
1293             0,
1294             slot_bindings,
1295             firstElementCountBinding(count_bindings),
1296             .{ .samples = 0 },
1297         ),
1298     );
1299     try testing.expectEqual(@as(usize, 0), state.launch_count);
1300 }