fuel_dispatch/variant_bake.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! **Optimize-time kernel-variant baking** — the same-device arm selector.
3//!
4//! ## The gap this closes
5//!
6//! `optimize_graph` records `Op::Branch` decision points whose arms are
7//! competing routes. Two *kinds* of arm coexist under one representation:
8//!
9//! - **Placement arms** — the same op on *different* `(backend, device)`
10//! pairs (CPU vs. CUDA). These stay live `Op::Branch` points, resolved at
11//! dispatch by the runtime route picker on live load / VRAM
12//! ([`crate::ranker::route_picker`], 06-runtime).
13//! - **Kernel-variant arms** — *different implementations of the same op on
14//! the SAME device* (the decomposed attention region vs. a fused flash
15//! kernel, both on CUDA). Per 04-optimization these are **"largely baked
16//! at optimize time"**, NOT deferred to the runtime picker.
17//!
18//! The shipped runtime picker resolves only placement arms: it ranks arms by
19//! their `(backend, device)` keyed on arm 0's op, so two same-device arms map
20//! to the same candidate and always resolve to **arm 0** (the finding in
21//! commit `c7bfc1b5`). A same-device fused variant — e.g. the CUDA flash
22//! decode arm from [`crate::decode_flash::offer_decode_flash_arm`] — can be
23//! *offered* but never *selected*.
24//!
25//! ## What this module does (the bake)
26//!
27//! [`bake_variant_branches`] runs at optimize time (Picker 1, where costs +
28//! caps + the Judge oracle are in hand — NOT the executor). For every
29//! reachable **same-device** variant branch it:
30//!
31//! 1. costs arm 0 (the decomposed-region oracle) and each variant arm;
32//! 2. picks the **unique** variant that is **strictly cheaper** than arm 0;
33//! 3. **collapses** the branch to that winner via
34//! [`fuel_graph::Graph::collapse_variant_branch`] (the merge is rewired to
35//! the winner; the branch becomes an inert single arm — "the Branch
36//! collapses" of 04-optimization).
37//!
38//! **Conservative defaults (the oracle stands):** a tie, an unknown/sentinel
39//! cost, or a variant whose kernel is not available on the placed device all
40//! resolve to **arm 0** — the decomposed correctness oracle. **Placement
41//! branches (arms on ≥2 devices) are never touched** — they are left live for
42//! the runtime picker. A graph with no same-device variant branch (every
43//! CPU/Vulkan build today, since no CUDA flash arm is offered there) is a
44//! **no-op**, so the byte-exact suites are unaffected by construction.
45//!
46//! ## Where it rides
47//!
48//! The bake mutates the graph **in place** during `optimize_graph`, so the
49//! baked choice rides [`crate::optimize::OptimizedGraph`] (which derives its
50//! dispatch order from the graph) with no extra state — plan-once persistent
51//! decode bakes once per generation and every realize lowers the collapsed
52//! graph.
53//!
54//! ## What the full version adds
55//!
56//! This is the smallest constitutional version. The full version adds:
57//! **Judge-refined variant ranking** (a Layer-2 measurement overriding the
58//! Layer-1 composed cost — plugs into the cost closure); **Pareto retention
59//! of both arms** (keep the variant as a runtime-selectable path instead of
60//! pruning it, once the picker can distinguish same-device variants); and, if
61//! it ever becomes constitutional, **runtime load-adaptive variant
62//! switching**. Today variant choice is baked and the loser is pruned.
63
64use std::collections::HashSet;
65
66use fuel_graph::registry::{FusedOpParams, FusedOps};
67use fuel_graph::{Graph, NodeId, Op, branches_in_topo_order};
68use fuel_ir::backend::{BackendCapabilities, SubstrateClass, TransferPath};
69use fuel_ir::dispatch::SizeClass;
70use fuel_ir::probe::BackendId;
71use fuel_ir::{DType, DeviceLocation, DynScalar, Shape};
72
73use crate::cost::{cost_flash_decoding_cuda, cost_matmul_cpu, default_cost_for_op_kind};
74use crate::fused::CostEstimate;
75use crate::kernel::OpParams;
76use crate::pipelined::op_to_op_kind;
77use crate::ranker::cost::{composite_ns, default_backend_rates};
78use crate::ranker::judge::JudgeOracle;
79use crate::runtime_fused_kernels::fused_kernel_available;
80
81/// The composite-nanosecond cost of a branch arm, or `None` when it is
82/// **unknown or inadmissible** — an unpriced/sentinel-cost op, or a variant
83/// whose kernel is not available on the placed device. `None` means "this arm
84/// cannot win", so the conservative arm-0 oracle stands.
85///
86/// Arguments: `(graph, branch, arm_index, arm_interior)` where `arm_interior`
87/// is the arm's interior node set (the nodes on paths `diverge → exit`, arm 0's
88/// being the decomposed region and a fused arm's being the single fused node).
89pub type ArmCostFn<'a> = dyn Fn(&Graph, NodeId, usize, &[NodeId]) -> Option<u64> + 'a;
90
91std::thread_local! {
92 /// Per-thread cumulative count of same-device variant branches baked to a
93 /// **variant** arm (arm > 0) — i.e. a fused variant (e.g. the CUDA
94 /// flash-decode arm) that won on cost and collapsed the branch. Bumped by
95 /// [`bake_variant_branches`]'s return value. Lets a downstream realize
96 /// consumer (a live bench / test) confirm the flash arm was actually
97 /// **picked**, not merely offered — the `optimize_graph` call site discards
98 /// the return value, so this is the only observation seam without widening
99 /// that signature. Mirrors `pipelined_bridge::OPTIMIZE_CALLS_TL`.
100 static VARIANT_BAKES_TL: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
101}
102
103/// The current thread's cumulative variant-bake count.
104/// Read it before and after an `optimize_graph`/realize call and diff to learn
105/// how many branches this pass collapsed to a fused variant winner — e.g. a
106/// BF16 CUDA decode step should bake one flash-decode arm per layer once the
107/// flash-arm capability seam is bridged. Thread-local so a single-threaded
108/// realize sequence isn't polluted by concurrent test threads.
109pub fn variant_bakes_thread_local() -> usize {
110 VARIANT_BAKES_TL.with(|c| c.get())
111}
112
113/// Bake same-device kernel-variant branches to their cost winner (see the
114/// module docs). Returns the number of branches collapsed **to a variant arm**
115/// (arm > 0) — a diagnostic the caller / tests assert on, also accumulated into
116/// the thread-local [`variant_bakes_thread_local`] counter. Never panics: a
117/// structural surprise from [`Graph::collapse_variant_branch`] is ignored
118/// rather than crashing (never-panic on a production path).
119pub fn bake_variant_branches(
120 graph: &mut Graph,
121 roots: &[NodeId],
122 arm_cost: &ArmCostFn<'_>,
123) -> usize {
124 // Decide first (immutable analysis over the whole set), then mutate — a
125 // collapse rewires edges, so we must not analyse against a half-mutated
126 // graph. Topo order over `reconverge_at` is the picker's order; it does
127 // not matter for independent same-device bakes but keeps parity.
128 let branches = branches_in_topo_order(graph, roots);
129 let mut decisions: Vec<(NodeId, usize)> = Vec::new();
130
131 for branch in branches {
132 let arms = graph.node(branch).inputs.clone();
133 if arms.len() < 2 {
134 continue; // already inert — no decision
135 }
136 // Same-device gate: every arm must resolve to the SAME backend. A
137 // placement branch (arms on ≥2 backends) is left LIVE for the runtime
138 // route picker; an unstamped arm (no `target_backend`) is not a
139 // determinate variant branch, so skip it conservatively.
140 let Some(backend0) = graph.target_backend(arms[0]) else {
141 continue;
142 };
143 if !arms
144 .iter()
145 .all(|&a| graph.target_backend(a) == Some(backend0))
146 {
147 continue; // placement branch → runtime picker owns it
148 }
149
150 // Cost the oracle (arm 0). An unknown oracle cost ⇒ leave arm 0.
151 let interiors = arm_interiors(graph, branch);
152 let Some(c0) = arm_cost(graph, branch, 0, &interiors[0]) else {
153 continue;
154 };
155
156 // The winner is the UNIQUE variant strictly cheaper than the oracle.
157 // A tie at the cheapest cost, or no cheaper admissible variant, ⇒
158 // arm 0 (the conservative oracle default).
159 let mut best: Option<(usize, u64)> = None;
160 let mut tie_at_best = false;
161 // Iterating `interiors` rather than `0..arms.len()` is behaviour-
162 // identical because both derive from `graph.node(branch).inputs`:
163 // `arms` clones it and `arm_interiors` maps over it, so the lengths
164 // are equal by construction. Worth stating — if they could differ,
165 // this rewrite would silently truncate where the index form panicked.
166 for (w, interior) in interiors.iter().enumerate().skip(1) {
167 let Some(cw) = arm_cost(graph, branch, w, interior) else {
168 continue; // unknown / capability-missing ⇒ cannot win
169 };
170 if cw >= c0 {
171 continue; // not strictly cheaper than the oracle
172 }
173 match best {
174 None => best = Some((w, cw)),
175 Some((_, bc)) if cw < bc => {
176 best = Some((w, cw));
177 tie_at_best = false;
178 }
179 Some((_, bc)) if cw == bc => tie_at_best = true,
180 _ => {}
181 }
182 }
183 let winner = if tie_at_best {
184 0
185 } else {
186 best.map(|(w, _)| w).unwrap_or(0)
187 };
188 decisions.push((branch, winner));
189 }
190
191 // Apply: collapse every same-device variant branch — to its winning
192 // variant, else to arm 0 (dropping the offered-but-unpicked variant arms).
193 // Either way no same-device variant branch survives optimize; only
194 // placement branches remain for the runtime picker.
195 let mut baked_to_variant = 0usize;
196 for (branch, winner) in decisions {
197 if winner != 0 {
198 baked_to_variant += 1;
199 }
200 let _ = graph.collapse_variant_branch(branch, winner);
201 }
202 if baked_to_variant > 0 {
203 VARIANT_BAKES_TL.with(|c| c.set(c.get() + baked_to_variant));
204 }
205 baked_to_variant
206}
207
208/// The per-arm **interior** node sets of a branch — the nodes on paths
209/// `diverge → exit`, excluding the shared diverge prefix. Mirrors
210/// `fuel_graph::run`'s cone/shared-prefix derivation (the op carries only
211/// `reconverge_at`, so the diverge is recovered as the intersection of the
212/// arm exits' backward cones). Index `i` is arm `i`'s interior; a fused arm's
213/// interior is typically the single fused node, arm 0's is the decomposed
214/// region.
215fn arm_interiors(graph: &Graph, branch: NodeId) -> Vec<Vec<NodeId>> {
216 let arm_exits = graph.node(branch).inputs.clone();
217 let cones: Vec<HashSet<NodeId>> = arm_exits.iter().map(|&e| backward_cone(graph, e)).collect();
218 if cones.is_empty() {
219 return Vec::new();
220 }
221 let mut shared: HashSet<NodeId> = cones[0].clone();
222 for c in &cones[1..] {
223 shared = shared.intersection(c).copied().collect();
224 }
225 cones
226 .iter()
227 .map(|cone| {
228 cone.iter()
229 .copied()
230 .filter(|n| !shared.contains(n))
231 .collect()
232 })
233 .collect()
234}
235
236/// Backward-reachable cone of `from` (the node and all transitive inputs).
237fn backward_cone(graph: &Graph, from: NodeId) -> HashSet<NodeId> {
238 let mut seen: HashSet<NodeId> = HashSet::new();
239 let mut stack = vec![from];
240 while let Some(n) = stack.pop() {
241 if !seen.insert(n) {
242 continue;
243 }
244 for &inp in &graph.node(n).inputs {
245 if !seen.contains(&inp) {
246 stack.push(inp);
247 }
248 }
249 }
250 seen
251}
252
253/// The **production cost provider** (04-optimization "cost-from-decompose" +
254/// the region composed-Layer-1 sum). Folds a Layer-1 [`CostEstimate`] over the
255/// arm's interior and converts it to a composite-ns figure on the placed
256/// `backend`'s throughput roofline. This is the defensible v1 the session
257/// prompt blesses: **compare the fused arm's declared cost against the region's
258/// composed Layer-1 sum over its nodes.**
259///
260/// - a `Op::Fused(FLASH_ATTN, …)` arm is priced by [`cost_flash_decoding_cuda`]
261/// (its declared decode cost — work scales with the live prefix `k_len`,
262/// not the physical capacity), gated on the CUDA flash kernel being bound
263/// (capability: absent ⇒ `None` ⇒ the oracle stands);
264/// - an `Op::MatMul` interior node (the dominant decode-region cost) is priced
265/// with its geometry derived from operand shapes;
266/// - any other op with an `op_to_op_kind` mapping is priced by its Layer-1
267/// family at `OpParams::None` (the shape-derivable floor — the same
268/// documented approximation `fused_cost` uses; a param-carrying interior op
269/// under-prices here and is refined by the Judge, biasing conservatively
270/// toward the oracle);
271/// - the region's arms are summed as **sequential** launches
272/// (`Σ composite_ns`), so the fused arm's single-launch win over an N-launch
273/// region is visible.
274///
275/// Symbolic `k_len` (persistent decode): the flash arm is priced at the KV
276/// **capacity** as the representative prefix. Both the flash cost and the
277/// decomposed region scale ~linearly in the prefix, so the winner is stable
278/// across the concrete prefix — the capacity is a sound representative extent
279/// for the plan-once bake.
280///
281/// Returns `None` (⇒ arm 0) when the arm prices to zero (nothing costable —
282/// an unknown/sentinel region) so a zero never spuriously "wins".
283pub fn decode_arm_composite_ns(
284 graph: &Graph,
285 _branch: NodeId,
286 _arm_idx: usize,
287 interior: &[NodeId],
288 backend: BackendId,
289) -> Option<u64> {
290 let (compute_rate, mem_bw) = default_backend_rates(backend);
291 let caps = neutral_caps(backend, compute_rate, mem_bw);
292 let mut total_ns: u64 = 0;
293 let mut any = false;
294 for &nid in interior {
295 let Some(cost) = node_layer1_cost(graph, nid, backend, &caps) else {
296 continue;
297 };
298 any = true;
299 total_ns = total_ns.saturating_add(composite_ns(&cost, compute_rate, mem_bw));
300 }
301 if !any || total_ns == 0 {
302 return None; // nothing costable ⇒ oracle stands (no spurious zero win)
303 }
304 Some(total_ns)
305}
306
307/// The **Layer-2-aware** cost provider — the slice-4 payoff. Consults the Judge
308/// oracle (empirical measured latency, 06-optimization Layer-2) **first** per
309/// interior node, falling back to the [`decode_arm_composite_ns`] Layer-1
310/// composite for any node the Judge hasn't measured. Summed per arm exactly as
311/// the Layer-1 provider does, so the comparison stays apples-to-apples:
312///
313/// - **arm 0** (the decomposed region) = `Σ` over its primitives of
314/// `measured(op, dtype, size_class, backend) ?? layer1(op)`;
315/// - **arm 1** (the fused flash node) = `measured(FlashAttn, …) ?? layer1(flash)`.
316///
317/// A measured latency is used **directly** (it is already a wall-clock ns, the
318/// same convention [`crate::ranker::cost::compute_static_costs`] uses when it
319/// folds the Judge cell into `kernel_overhead_ns`), so flash's real
320/// algorithm-changing win (no materialized attention matrix, one launch —
321/// smaller measured latency than the region's `N` measured launches) becomes
322/// visible where Layer-1 alone saw a FLOP-for-FLOP tie and the conservative
323/// bake kept arm 0.
324///
325/// **A Judge measurement is evidence of capability.** A cell exists only
326/// because the kernel was actually profiled (it ran), so a measured flash cell
327/// is used even when the Layer-1 capability gate ([`fused_kernel_available`])
328/// would decline it — the gate only guards the *fallback* Layer-1 path (no
329/// measurement + unbound kernel ⇒ `None` ⇒ the oracle stands, unchanged).
330///
331/// **Hybrid sum.** When some of an arm's nodes are Judge-covered and others are
332/// not, measured cells and Layer-1 estimates are summed together — directionally
333/// better than an all-Layer-1 fold, and the conservative tie-break in
334/// [`bake_variant_branches`] (strictly-cheaper-or-arm-0) still protects against
335/// a bad partial.
336///
337/// **`kernel_source`.** The bake has no per-node candidate in hand, so it
338/// queries the legacy / default (`""`) cell — where a single-impl flash/matmul
339/// kernel registers. Threading a per-arm `kernel_source` is a follow-up; `""`
340/// keeps the born-red and single-impl production apples-to-apples.
341///
342/// With `judge == None` this is **byte-identical** to [`decode_arm_composite_ns`]
343/// (it delegates), so the no-Judge production path and the persistent
344/// byte-exact suites are unaffected by construction.
345pub fn decode_arm_composite_ns_judged(
346 graph: &Graph,
347 branch: NodeId,
348 arm_idx: usize,
349 interior: &[NodeId],
350 backend: BackendId,
351 judge: Option<&dyn JudgeOracle>,
352) -> Option<u64> {
353 // No oracle ⇒ the Layer-1-only provider, byte-identical to today. The
354 // no-Judge production path + the persistent byte-exact suites are
355 // unaffected by construction.
356 let Some(judge) = judge else {
357 return decode_arm_composite_ns(graph, branch, arm_idx, interior, backend);
358 };
359
360 // Layer-2 FIRST, per interior node, with a Layer-1 fallback (the hybrid
361 // sum): a measured wall-clock latency is used directly (same convention as
362 // `compute_static_costs`'s `kernel_overhead_ns` fold); an unmeasured node
363 // falls back to its Layer-1 composite. Summed as sequential launches so the
364 // fused arm's single measured launch is compared against the region's `N`
365 // measured/Layer-1 launches apples-to-apples.
366 let (compute_rate, mem_bw) = default_backend_rates(backend);
367 let caps = neutral_caps(backend, compute_rate, mem_bw);
368 let mut total_ns: u64 = 0;
369 let mut any = false;
370 for &nid in interior {
371 // A Judge measurement is evidence of capability (a cell exists only
372 // because the kernel ran), so a measured node is admissible even if the
373 // Layer-1 capability gate would decline it. The gate guards only the
374 // fallback below.
375 let ns = match node_measured_ns(graph, nid, backend, judge) {
376 Some(measured) => Some(measured),
377 None => node_layer1_cost(graph, nid, backend, &caps)
378 .map(|cost| composite_ns(&cost, compute_rate, mem_bw)),
379 };
380 if let Some(ns) = ns {
381 any = true;
382 total_ns = total_ns.saturating_add(ns);
383 }
384 }
385 if !any || total_ns == 0 {
386 return None; // nothing costable ⇒ oracle stands (no spurious zero win)
387 }
388 Some(total_ns)
389}
390
391/// The measured (Layer-2) latency for one interior node's Judge cell, keyed the
392/// SAME way the ranker + Judge producer key it: `(op, principal_dtype,
393/// SizeClass::for_op(op, input_shapes), backend, "")`. `None` when the node has
394/// no [`op_to_op_kind`] mapping (a view/leaf) or the Judge hasn't measured that
395/// cell. The principal dtype is the first operand's dtype (the
396/// [`crate::ranker::cost::compute_static_costs`] convention), so a matmul /
397/// flash cell the Judge profiled is found by the SAME `for_op` key here.
398fn node_measured_ns(
399 graph: &Graph,
400 nid: NodeId,
401 backend: BackendId,
402 judge: &dyn JudgeOracle,
403) -> Option<u64> {
404 let node = graph.node(nid);
405 let kind = op_to_op_kind(&node.op)?;
406 let principal_dtype = match node.inputs.first() {
407 Some(&i) => graph.node(i).dtype,
408 None => node.dtype, // nullary — has no OpKind mapping anyway
409 };
410 let input_shapes: Vec<Shape> = node
411 .inputs
412 .iter()
413 .map(|&i| graph.node(i).shape.clone())
414 .collect();
415 let size_class = SizeClass::for_op(kind, &input_shapes);
416 judge.measured_latency_ns(kind, principal_dtype, size_class, backend, "", 0)
417}
418
419/// Layer-1 [`CostEstimate`] for one interior node (see [`decode_arm_composite_ns`]).
420/// `None` for a node that carries no priceable compute (a view/leaf, or a
421/// flash arm whose kernel is not bound — the capability gate).
422fn node_layer1_cost(
423 graph: &Graph,
424 nid: NodeId,
425 backend: BackendId,
426 caps: &BackendCapabilities,
427) -> Option<CostEstimate> {
428 let node = graph.node(nid);
429 match &node.op {
430 // The fused flash decode arm: priced by its declared decode cost,
431 // gated on the CUDA flash kernel being bound (capability).
432 Op::Fused(fid, params) if *fid == FusedOps::FLASH_ATTN => {
433 if !fused_kernel_available(FusedOps::FLASH_ATTN, backend) {
434 return None; // capability-missing ⇒ arm cannot win
435 }
436 let op_params = flash_op_params(graph, node, params)?;
437 let dtypes = [node.dtype, node.dtype];
438 Some(cost_flash_decoding_cuda(&[], &dtypes, &op_params, caps))
439 }
440 // MatMul — the dominant decode-region cost; geometry from shapes.
441 Op::MatMul => {
442 let op_params = matmul_op_params(graph, node)?;
443 let dtypes = [node.dtype, node.dtype, node.dtype];
444 Some(cost_matmul_cpu(&[], &dtypes, &op_params, caps))
445 }
446 // Any other primitive with an OpKind mapping: its Layer-1 family at
447 // the shape-derivable floor (OpParams::None), per the fused_cost
448 // precedent. Shapes follow `[input1, .., inputN, output]`.
449 other => {
450 let kind = op_to_op_kind(other)?;
451 let mut shapes: Vec<Shape> = node
452 .inputs
453 .iter()
454 .map(|&i| graph.node(i).shape.clone())
455 .collect();
456 shapes.push(node.shape.clone());
457 let mut dtypes: Vec<DType> = node.inputs.iter().map(|&i| graph.node(i).dtype).collect();
458 dtypes.push(node.dtype);
459 Some(default_cost_for_op_kind(kind)(
460 &shapes,
461 &dtypes,
462 &OpParams::None,
463 caps,
464 ))
465 }
466 }
467}
468
469/// Derive `OpParams::Matmul` geometry from a `MatMul` node's operand shapes:
470/// `A[.., m, k] · B[.., k, n] → [.., m, n]`. `None` if either operand is
471/// rank < 2 (nothing to cost).
472fn matmul_op_params(graph: &Graph, node: &fuel_graph::Node) -> Option<OpParams> {
473 let a = graph.node(*node.inputs.first()?).shape.clone();
474 let b = graph.node(*node.inputs.get(1)?).shape.clone();
475 let ad = a.dims();
476 let bd = b.dims();
477 if ad.len() < 2 || bd.len() < 2 {
478 return None;
479 }
480 let m = ad[ad.len() - 2];
481 let k = ad[ad.len() - 1];
482 let n = bd[bd.len() - 1];
483 let lhs_batch_dims: Vec<usize> = ad[..ad.len() - 2].to_vec();
484 let rhs_batch_dims: Vec<usize> = bd[..bd.len() - 2].to_vec();
485 // Cost/bake geometry only — the row count is the capacity `m` (a
486 // dynamic-M matmul costs at its capacity, a safe over-estimate).
487 Some(OpParams::Matmul {
488 lhs_batch_dims,
489 rhs_batch_dims,
490 m,
491 n,
492 k,
493 m_compute: crate::kernel::MatmulM::All,
494 })
495}
496
497/// Derive `OpParams::FlashAttn` for the decode cost from the flash node's
498/// `[q, k, v, ..]` inputs + its `FusedOpParams::FlashAttn`. `q = [b, hq, sq, d]`,
499/// `k = [b, hkv, sk, d]`. A symbolic `k_len` prices at the KV capacity `sk`
500/// (the representative prefix — see [`decode_arm_composite_ns`]).
501fn flash_op_params(
502 graph: &Graph,
503 node: &fuel_graph::Node,
504 params: &FusedOpParams,
505) -> Option<OpParams> {
506 let FusedOpParams::FlashAttn {
507 softmax_scale,
508 causal,
509 window_size_left,
510 window_size_right,
511 softcap,
512 k_len,
513 } = params
514 else {
515 return None;
516 };
517 let q = graph.node(*node.inputs.first()?).shape.clone();
518 let kv = graph.node(*node.inputs.get(1)?).shape.clone();
519 let qd = q.dims();
520 let kvd = kv.dims();
521 if qd.len() != 4 || kvd.len() != 4 {
522 return None;
523 }
524 let (b, hq, sq, d) = (qd[0], qd[1], qd[2], qd[3]);
525 let hkv = kvd[1];
526 let sk = kvd[2];
527 // Symbolic prefix ⇒ price at the capacity (representative extent).
528 let k_len_val = match k_len {
529 Some(DynScalar::Concrete(v)) => *v,
530 _ => sk,
531 };
532 Some(OpParams::FlashAttn {
533 b,
534 hq,
535 hkv,
536 sq,
537 sk,
538 d,
539 k_len: k_len_val,
540 softmax_scale: *softmax_scale,
541 causal: *causal,
542 window_size_left: *window_size_left,
543 window_size_right: *window_size_right,
544 softcap: *softcap,
545 })
546}
547
548/// A minimal [`BackendCapabilities`] carrying only the backend id + its
549/// throughput rates — the Layer-1 cost families ignore the rest of the struct;
550/// the roofline rates flow through [`composite_ns`] separately.
551fn neutral_caps(backend: BackendId, compute: f64, mem_bw: f64) -> BackendCapabilities {
552 BackendCapabilities {
553 backend_id: backend,
554 device_location: DeviceLocation::Cpu,
555 op_dtype_support: HashSet::new(),
556 required_alignment: 1,
557 access_granularity_bits: 8,
558 transfer_paths: vec![(DeviceLocation::Cpu, TransferPath::SameDevice)],
559 storage_substrate: SubstrateClass::HostBytes,
560 compute_throughput_flops_per_ns: compute,
561 mem_bandwidth_bytes_per_ns: mem_bw,
562 }
563}
564
565#[cfg(test)]
566mod tests {
567 use super::*;
568 use crate::ranker::judge::HashMapJudge;
569 use fuel_graph::{Node, lower_runs_arm0};
570 use fuel_ir::Shape;
571 use fuel_ir::dispatch::OpKind;
572
573 fn node(g: &mut Graph, op: Op, inputs: Vec<NodeId>, dims: &[usize], dt: DType) -> NodeId {
574 g.push(Node {
575 op,
576 inputs,
577 shape: Shape::from_dims(dims),
578 dtype: dt,
579 })
580 }
581
582 /// Build a same-device 2-arm variant branch mirroring the decode flash
583 /// shape: arm 0 is a 2-node "decomposed region" (Relu→Silu over the
584 /// diverge), arm 1 is a single "fused variant" node (Gelu). Both arms are
585 /// stamped `backend`. Returns `(graph, branch, arm0_exit, arm1, reconverge, post)`.
586 fn variant_diamond(backend: BackendId) -> (Graph, NodeId, NodeId, NodeId, NodeId, NodeId) {
587 let dt = DType::F32;
588 let mut g = Graph::new();
589 let pre = node(&mut g, Op::Const, vec![], &[4], dt);
590 let diverge = node(&mut g, Op::Relu, vec![pre], &[4], dt);
591 // arm 0 = a 2-node decomposed region.
592 let a0_mid = node(&mut g, Op::Relu, vec![diverge], &[4], dt);
593 let arm0 = node(&mut g, Op::Silu, vec![a0_mid], &[4], dt);
594 // arm 1 = a single fused-variant node.
595 let arm1 = node(&mut g, Op::GeluTanh, vec![diverge], &[4], dt);
596 g.set_target_backend(a0_mid, backend);
597 g.set_target_backend(arm0, backend);
598 g.set_target_backend(arm1, backend);
599 let reconverge = node(&mut g, Op::Relu, vec![arm0], &[4], dt);
600 let mut b = g.open_branch(diverge);
601 b.add_arm(arm0);
602 b.add_arm(arm1);
603 let branch = b
604 .finalize_branches(&mut g, reconverge)
605 .expect("well-formed 2-arm branch")
606 .expect("2 arms survive");
607 let post = node(&mut g, Op::Tanh, vec![reconverge], &[4], dt);
608 (g, branch, arm0, arm1, reconverge, post)
609 }
610
611 /// A cost closure that returns fixed per-arm costs by arm index.
612 fn fixed_costs(
613 costs: Vec<Option<u64>>,
614 ) -> impl Fn(&Graph, NodeId, usize, &[NodeId]) -> Option<u64> {
615 move |_g, _b, arm, _interior| costs.get(arm).copied().flatten()
616 }
617
618 // ===================================================================
619 // BORN-RED: a same-device variant arm is NEVER selected today (the
620 // runtime picker resolves same-device arms to arm 0). The bake makes
621 // the cheaper variant SELECTABLE by collapsing the branch to it.
622 // ===================================================================
623 #[test]
624 fn cheaper_variant_is_baked_and_becomes_the_route() {
625 let (mut g, branch, arm0, arm1, reconverge, post) = variant_diamond(BackendId::Cuda);
626
627 // (RED anchor) Before the bake, the default lowering realizes arm 0 —
628 // the same-device variant (arm 1) is offered but never selected.
629 let before = lower_runs_arm0(&g, &[post]);
630 assert!(
631 before.contains(&arm0) && !before.contains(&arm1),
632 "before the bake the same-device variant is never selected (arm 0 wins)",
633 );
634
635 // (GREEN) The variant (arm 1) is strictly cheaper ⇒ the bake collapses
636 // the branch to it.
637 let cost = fixed_costs(vec![Some(1000), Some(400)]); // arm0=1000, arm1=400
638 let baked = bake_variant_branches(&mut g, &[post], &cost);
639 assert_eq!(baked, 1, "one variant branch baked to its winner");
640
641 // The merge now reads the winner; the branch is collapsed.
642 assert!(
643 g.node(reconverge).inputs.contains(&arm1),
644 "merge rewired to the variant"
645 );
646 assert_eq!(
647 g.node(branch).inputs,
648 vec![arm1],
649 "branch collapsed to the winner"
650 );
651
652 // The default lowering now follows the baked variant (arm 1) and prunes
653 // arm 0's region — selectable with NO runtime pick.
654 let after = lower_runs_arm0(&g, &[post]);
655 assert!(
656 after.contains(&arm1),
657 "after the bake the variant is realized; order={after:?}"
658 );
659 assert!(
660 !after.contains(&arm0),
661 "arm 0's region is pruned; order={after:?}"
662 );
663 }
664
665 /// The `variant_bakes_thread_local` telemetry tracks the return value: a
666 /// winning-variant bake bumps the counter by 1, an arm-0 (oracle-wins) bake
667 /// leaves it unchanged. This is the seam a live BF16-CUDA-decode bench/test
668 /// reads to confirm the flash-decode arm was actually PICKED (baked to the
669 /// fused variant), not merely offered.
670 #[test]
671 fn variant_bakes_thread_local_tracks_the_return() {
672 // A winning variant ⇒ +1.
673 let before = variant_bakes_thread_local();
674 let (mut g1, _b1, _a0, _a1, _r1, post1) = variant_diamond(BackendId::Cuda);
675 let win = fixed_costs(vec![Some(1000), Some(400)]); // arm1 strictly cheaper
676 let baked = bake_variant_branches(&mut g1, &[post1], &win);
677 assert_eq!(baked, 1);
678 assert_eq!(
679 variant_bakes_thread_local() - before,
680 1,
681 "a winning-variant bake bumps the thread-local by its return count",
682 );
683
684 // Arm 0 wins (costlier variant) ⇒ counter unchanged.
685 let mid = variant_bakes_thread_local();
686 let (mut g2, _b2, _a0b, _a1b, _r2, post2) = variant_diamond(BackendId::Cuda);
687 let lose = fixed_costs(vec![Some(400), Some(1000)]); // arm1 costlier
688 let baked2 = bake_variant_branches(&mut g2, &[post2], &lose);
689 assert_eq!(baked2, 0);
690 assert_eq!(
691 variant_bakes_thread_local(),
692 mid,
693 "an oracle-wins bake leaves the thread-local unchanged",
694 );
695 }
696
697 /// GUARD: a costlier variant ⇒ arm 0 (the oracle). The branch collapses to
698 /// arm 0 (variant pruned), realize is byte-identical to today.
699 #[test]
700 fn costlier_variant_keeps_arm0() {
701 let (mut g, branch, arm0, arm1, _recon, post) = variant_diamond(BackendId::Cuda);
702 let cost = fixed_costs(vec![Some(400), Some(1000)]); // arm1 costlier
703 let baked = bake_variant_branches(&mut g, &[post], &cost);
704 assert_eq!(baked, 0, "no variant wins ⇒ nothing baked to a variant");
705 assert_eq!(
706 g.node(branch).inputs,
707 vec![arm0],
708 "branch collapsed to arm 0"
709 );
710 let after = lower_runs_arm0(&g, &[post]);
711 assert!(
712 after.contains(&arm0) && !after.contains(&arm1),
713 "arm 0 realized (oracle)"
714 );
715 }
716
717 /// GUARD: an unknown / sentinel variant cost ⇒ arm 0. `None` cannot win.
718 #[test]
719 fn unknown_variant_cost_keeps_arm0() {
720 let (mut g, _branch, arm0, arm1, _recon, post) = variant_diamond(BackendId::Cuda);
721 let cost = fixed_costs(vec![Some(1000), None]); // arm1 unknown
722 let baked = bake_variant_branches(&mut g, &[post], &cost);
723 assert_eq!(baked, 0, "unknown variant cost ⇒ arm 0 (conservative)");
724 let after = lower_runs_arm0(&g, &[post]);
725 assert!(
726 after.contains(&arm0) && !after.contains(&arm1),
727 "arm 0 realized"
728 );
729 }
730
731 /// GUARD: a tie between the oracle and the variant ⇒ arm 0.
732 #[test]
733 fn tie_keeps_arm0() {
734 let (mut g, _branch, arm0, arm1, _recon, post) = variant_diamond(BackendId::Cuda);
735 let cost = fixed_costs(vec![Some(500), Some(500)]); // equal ⇒ not strictly cheaper
736 let baked = bake_variant_branches(&mut g, &[post], &cost);
737 assert_eq!(baked, 0, "tie ⇒ arm 0 (variant not strictly cheaper)");
738 let after = lower_runs_arm0(&g, &[post]);
739 assert!(
740 after.contains(&arm0) && !after.contains(&arm1),
741 "arm 0 realized"
742 );
743 }
744
745 /// GUARD: capability-missing (modelled as a `None` variant cost, the way
746 /// [`decode_arm_composite_ns`] surfaces an unbound flash kernel) ⇒ arm 0.
747 #[test]
748 fn capability_missing_keeps_arm0() {
749 let (mut g, _branch, arm0, arm1, _recon, post) = variant_diamond(BackendId::Cuda);
750 // A provider that declines the variant arm (capability gate) but prices
751 // the oracle — exactly the None the flash capability gate returns.
752 let cost = |_g: &Graph, _b: NodeId, arm: usize, _i: &[NodeId]| -> Option<u64> {
753 if arm == 0 { Some(1000) } else { None }
754 };
755 let baked = bake_variant_branches(&mut g, &[post], &cost);
756 assert_eq!(baked, 0, "capability-missing variant ⇒ arm 0");
757 let after = lower_runs_arm0(&g, &[post]);
758 assert!(
759 after.contains(&arm0) && !after.contains(&arm1),
760 "arm 0 realized"
761 );
762 }
763
764 /// GUARD: a **placement** branch (arms on DIFFERENT backends) is UNTOUCHED
765 /// — it stays a live 2-arm decision point for the runtime picker, even
766 /// when the variant-shaped cost would prefer arm 1. The bake only ever
767 /// collapses same-device branches.
768 #[test]
769 fn placement_branch_is_untouched() {
770 let (mut g, branch, arm0, arm1, _recon, post) = variant_diamond(BackendId::Cuda);
771 // Re-stamp arm 1 onto a DIFFERENT backend ⇒ placement branch.
772 g.set_target_backend(arm1, BackendId::Cpu);
773 let cost = fixed_costs(vec![Some(1000), Some(1)]); // arm1 "cheaper", but placement
774 let baked = bake_variant_branches(&mut g, &[post], &cost);
775 assert_eq!(baked, 0, "a placement branch is never baked");
776 assert_eq!(
777 g.node(branch).inputs,
778 vec![arm0, arm1],
779 "placement branch stays a live 2-arm decision point (runtime picker owns it)",
780 );
781 }
782
783 /// GUARD: an unstamped branch (arms with no `target_backend`) is skipped —
784 /// not a determinate same-device variant branch.
785 #[test]
786 fn unstamped_branch_is_skipped() {
787 let dt = DType::F32;
788 let mut g = Graph::new();
789 let pre = node(&mut g, Op::Const, vec![], &[4], dt);
790 let diverge = node(&mut g, Op::Relu, vec![pre], &[4], dt);
791 let a0_mid = node(&mut g, Op::Relu, vec![diverge], &[4], dt);
792 let arm0 = node(&mut g, Op::Silu, vec![a0_mid], &[4], dt);
793 let arm1 = node(&mut g, Op::GeluTanh, vec![diverge], &[4], dt);
794 // NO target_backend stamps on the arms.
795 let reconverge = node(&mut g, Op::Relu, vec![arm0], &[4], dt);
796 let mut b = g.open_branch(diverge);
797 b.add_arm(arm0);
798 b.add_arm(arm1);
799 let branch = b.finalize_branches(&mut g, reconverge).unwrap().unwrap();
800 let post = node(&mut g, Op::Tanh, vec![reconverge], &[4], dt);
801 let cost = fixed_costs(vec![Some(1000), Some(1)]);
802 let baked = bake_variant_branches(&mut g, &[post], &cost);
803 assert_eq!(baked, 0, "unstamped branch is skipped");
804 assert_eq!(g.node(branch).inputs, vec![arm0, arm1], "branch untouched");
805 }
806
807 // ===================================================================
808 // The PRODUCTION cost provider: a decomposed attention region vs. the
809 // fused flash arm — flash WINS on the region-vs-fused Layer-1 fold.
810 // ===================================================================
811
812 /// The **region-vs-fused costing approach**, priced honestly. The
813 /// production provider folds a real Layer-1 cost over BOTH arm kinds — the
814 /// decomposed region (matmul-dominated fold) and the fused flash arm
815 /// (declared decode cost from derived `OpParams`). This proves the costing
816 /// path end-to-end; it deliberately does NOT force flash to "win" on
817 /// Layer-1: `cost_flash_decoding_cuda` gives flash the SAME FLOPs as the
818 /// region's two matmuls (they compute the same attention), so on the
819 /// coarse Layer-1 model the two are ~cost-equal and the conservative bake
820 /// keeps the oracle (arm 0). Flash's real win is an *algorithm-changing*
821 /// fusion (no materialized attention matrix, one launch) that the Judge's
822 /// Layer-2 measurement corrects downward — "a missed win until measured,
823 /// not an optimistic irreversible commit" (04-optimization).
824 #[test]
825 fn region_and_flash_arms_are_both_priced_by_the_provider() {
826 let (b, hq, d, sk) = (1usize, 8, 128, 512);
827 let dt = DType::F16;
828 let mut g = Graph::new();
829 let q = node(&mut g, Op::Const, vec![], &[b, hq, 1, d], dt);
830 let k = node(&mut g, Op::Const, vec![], &[b, hq, sk, d], dt);
831 let v = node(&mut g, Op::Const, vec![], &[b, hq, sk, d], dt);
832
833 // Decomposed region interior (two matmuls dominate).
834 let kt = node(
835 &mut g,
836 Op::Permute(vec![0, 1, 3, 2]),
837 vec![k],
838 &[b, hq, d, sk],
839 dt,
840 );
841 let scores = node(&mut g, Op::MatMul, vec![q, kt], &[b, hq, 1, sk], dt);
842 let attn = node(&mut g, Op::MatMul, vec![scores, v], &[b, hq, 1, d], dt);
843 let region: Vec<NodeId> = vec![kt, scores, attn];
844
845 // (1) The region folds to a real, nonzero composite cost — no binding
846 // needed (pure shape/FLOP fold).
847 let region_ns = decode_arm_composite_ns(&g, NodeId(0), 0, ®ion, BackendId::Cuda)
848 .expect("the decomposed region has costable compute (its two matmuls)");
849 assert!(region_ns > 0, "region prices to a real nonzero cost");
850
851 // (2) The fused flash arm's DECLARED decode cost, priced directly from
852 // derived OpParams (the provider's capability gate is bypassed here so
853 // the costing path is exercised on any build, incl. no-CUDA).
854 let flash_node = Node {
855 op: Op::Fused(
856 FusedOps::FLASH_ATTN,
857 FusedOpParams::FlashAttn {
858 softmax_scale: 0.1,
859 causal: true,
860 window_size_left: None,
861 window_size_right: None,
862 softcap: None,
863 k_len: Some(DynScalar::Concrete(sk)),
864 },
865 ),
866 inputs: vec![q, k, v],
867 shape: Shape::from_dims(&[b, hq, 1, d]),
868 dtype: dt,
869 };
870 let (cr, bw) = default_backend_rates(BackendId::Cuda);
871 let caps = neutral_caps(BackendId::Cuda, cr, bw);
872 let fp_full = decode_flash_cost_at(&g, &flash_node, sk, &caps, cr, bw);
873 let fp_small = decode_flash_cost_at(&g, &flash_node, 32, &caps, cr, bw);
874 assert!(
875 fp_full > 0 && fp_small > 0,
876 "flash decode cost is real + nonzero"
877 );
878 assert!(
879 fp_full > fp_small,
880 "the flash decode cost scales with the live prefix k_len \
881 ({fp_full} @512 > {fp_small} @32) — decode work is O(prefix)",
882 );
883
884 // (3) The provider's capability gate: without a bound CUDA flash kernel
885 // the flash ARM is inadmissible (None) ⇒ the oracle stands. (In a live
886 // CUDA build the kernel is bound and the arm prices instead.)
887 let flash = node(
888 &mut g,
889 Op::Fused(
890 FusedOps::FLASH_ATTN,
891 FusedOpParams::FlashAttn {
892 softmax_scale: 0.1,
893 causal: true,
894 window_size_left: None,
895 window_size_right: None,
896 softcap: None,
897 k_len: Some(DynScalar::Concrete(sk)),
898 },
899 ),
900 vec![q, k, v],
901 &[b, hq, 1, d],
902 dt,
903 );
904 let gated = decode_arm_composite_ns(&g, NodeId(0), 1, &[flash], BackendId::Cuda);
905 if fused_kernel_available(FusedOps::FLASH_ATTN, BackendId::Cuda) {
906 assert!(gated.is_some(), "bound flash kernel ⇒ arm priced");
907 } else {
908 assert!(
909 gated.is_none(),
910 "unbound flash kernel ⇒ arm inadmissible (oracle stands)"
911 );
912 }
913 }
914
915 /// Helper: the flash decode composite-ns at a given prefix (bypasses the
916 /// capability gate to exercise the declared-cost path on any build).
917 fn decode_flash_cost_at(
918 g: &Graph,
919 flash_node: &Node,
920 k_len: usize,
921 caps: &BackendCapabilities,
922 cr: f64,
923 bw: f64,
924 ) -> u64 {
925 let params = FusedOpParams::FlashAttn {
926 softmax_scale: 0.1,
927 causal: true,
928 window_size_left: None,
929 window_size_right: None,
930 softcap: None,
931 k_len: Some(DynScalar::Concrete(k_len)),
932 };
933 let op_params = flash_op_params(g, flash_node, ¶ms).expect("derivable");
934 let dtypes = [flash_node.dtype, flash_node.dtype];
935 let cost = cost_flash_decoding_cuda(&[], &dtypes, &op_params, caps);
936 composite_ns(&cost, cr, bw)
937 }
938
939 /// The flash `OpParams` derivation prices a symbolic `k_len` at the KV
940 /// capacity (the representative prefix), so a plan-once persistent-decode
941 /// bake still produces a concrete, stable cost.
942 #[test]
943 fn symbolic_k_len_prices_at_capacity() {
944 let (b, hq, d, sk) = (1usize, 4, 64, 256);
945 let dt = DType::F16;
946 let mut g = Graph::new();
947 let q = node(&mut g, Op::Const, vec![], &[b, hq, 1, d], dt);
948 let k = node(&mut g, Op::Const, vec![], &[b, hq, sk, d], dt);
949 let v = node(&mut g, Op::Const, vec![], &[b, hq, sk, d], dt);
950 let flash = node(
951 &mut g,
952 Op::Fused(
953 FusedOps::FLASH_ATTN,
954 FusedOpParams::FlashAttn {
955 softmax_scale: 0.1,
956 causal: true,
957 window_size_left: None,
958 window_size_right: None,
959 softcap: None,
960 // Symbolic prefix (persistent decode).
961 k_len: Some(DynScalar::Sym(fuel_ir::symbol::SymId(0))),
962 },
963 ),
964 vec![q, k, v],
965 &[b, hq, 1, d],
966 dt,
967 );
968 let flash_node = g.node(flash).clone();
969 let params_in = match &flash_node.op {
970 Op::Fused(_, p) => p.clone(),
971 _ => unreachable!(),
972 };
973 let params = flash_op_params(&g, &flash_node, ¶ms_in).expect("derivable");
974 match params {
975 OpParams::FlashAttn {
976 k_len,
977 sk: sk_out,
978 sq,
979 ..
980 } => {
981 assert_eq!(k_len, sk, "symbolic k_len prices at the capacity");
982 assert_eq!(sk_out, sk);
983 assert_eq!(sq, 1, "decode sq == 1");
984 }
985 _ => panic!("expected FlashAttn params"),
986 }
987 }
988
989 // ===================================================================
990 // SLICE 4 (THE PAYOFF): the variant bake consults the Judge's MEASURED
991 // latency (Layer-2) per arm, so the CUDA flash arm WINS the
992 // decode-flash-vs-decomposed bake where it merely TIES on Layer-1. With
993 // an injected oracle this proves the SELECTION logic on a CPU build (no
994 // live GPU): the born-red flips arm 0 → arm 1 the moment the provider
995 // reads the Judge.
996 // ===================================================================
997
998 /// A same-device (CUDA) 2-arm DECODE variant branch mirroring the real
999 /// decode-flash bake: arm 0 is the decomposed attention region
1000 /// (`Permute` → `MatMul` QKᵀ → `MatMul` PV), arm 1 is the fused
1001 /// `FlashAttn` node. `q` is a `Relu` over a const so a single Op::Branch
1002 /// diverge point dominates both arm exits; both arm exits are stamped
1003 /// CUDA (same-device). Returns
1004 /// `(graph, branch, region_exit, flash_exit, reconverge, post)`.
1005 fn flash_variant_diamond() -> (Graph, NodeId, NodeId, NodeId, NodeId, NodeId) {
1006 let (b, hq, d, sk) = (1usize, 8, 128, 512);
1007 let dt = DType::F16;
1008 let mut g = Graph::new();
1009 let qc = node(&mut g, Op::Const, vec![], &[b, hq, 1, d], dt);
1010 let k = node(&mut g, Op::Const, vec![], &[b, hq, sk, d], dt);
1011 let v = node(&mut g, Op::Const, vec![], &[b, hq, sk, d], dt);
1012 // The single DIVERGE point both arms read as their `q` operand.
1013 let q = node(&mut g, Op::Relu, vec![qc], &[b, hq, 1, d], dt);
1014 // arm 0 — the decomposed attention region (two matmuls dominate).
1015 let kt = node(
1016 &mut g,
1017 Op::Permute(vec![0, 1, 3, 2]),
1018 vec![k],
1019 &[b, hq, d, sk],
1020 dt,
1021 );
1022 let scores = node(&mut g, Op::MatMul, vec![q, kt], &[b, hq, 1, sk], dt);
1023 let attn = node(&mut g, Op::MatMul, vec![scores, v], &[b, hq, 1, d], dt);
1024 // arm 1 — the fused flash node (same exit shape & dtype as the region).
1025 let flash = node(
1026 &mut g,
1027 Op::Fused(
1028 FusedOps::FLASH_ATTN,
1029 FusedOpParams::FlashAttn {
1030 softmax_scale: 0.1,
1031 causal: true,
1032 window_size_left: None,
1033 window_size_right: None,
1034 softcap: None,
1035 k_len: Some(DynScalar::Concrete(sk)),
1036 },
1037 ),
1038 vec![q, k, v],
1039 &[b, hq, 1, d],
1040 dt,
1041 );
1042 // Same-device stamps — the bake's same-device gate reads the arm exits.
1043 g.set_target_backend(kt, BackendId::Cuda);
1044 g.set_target_backend(scores, BackendId::Cuda);
1045 g.set_target_backend(attn, BackendId::Cuda);
1046 g.set_target_backend(flash, BackendId::Cuda);
1047 let reconverge = node(&mut g, Op::Relu, vec![attn], &[b, hq, 1, d], dt);
1048 let mut br = g.open_branch(q);
1049 br.add_arm(attn);
1050 br.add_arm(flash);
1051 let branch = br
1052 .finalize_branches(&mut g, reconverge)
1053 .expect("well-formed 2-arm decode branch")
1054 .expect("2 arms survive");
1055 let post = node(&mut g, Op::Tanh, vec![reconverge], &[b, hq, 1, d], dt);
1056 (g, branch, attn, flash, reconverge, post)
1057 }
1058
1059 /// The decode-shape Judge keys, single-sourced through the SAME helpers
1060 /// the bake's `for_op` derivation and the fuel-core Judge producer use:
1061 /// scores QKᵀ = `matmul(m=1, n=sk, k=d)`, attn PV = `matmul(m=1, n=d,
1062 /// k=sk)`, flash = `attention(hq, k_len=sk, d)`.
1063 fn decode_keys() -> (SizeClass, SizeClass, SizeClass) {
1064 let (hq, d, sk) = (8usize, 128, 512);
1065 (
1066 SizeClass::matmul(1, sk, d), // scores
1067 SizeClass::matmul(1, d, sk), // attn
1068 SizeClass::attention(hq, sk, d),
1069 )
1070 }
1071
1072 /// **BORN-RED (the arc headline).** A measured flash cell that is strictly
1073 /// cheaper than the region's summed measured primitives flips the bake from
1074 /// arm 0 to arm 1. RED before the Layer-2 wiring (the flash arm ties / is
1075 /// gated on Layer-1 ⇒ arm 0 kept); GREEN after (measured flash wins).
1076 #[test]
1077 fn judge_measured_flash_wins_the_decode_bake() {
1078 let (mut g, branch, attn, flash, reconverge, post) = flash_variant_diamond();
1079 let dt = DType::F16;
1080 let (k_scores, k_attn, k_flash) = decode_keys();
1081
1082 // (RED anchor) the default lowering realizes the decomposed region.
1083 let before = lower_runs_arm0(&g, &[post]);
1084 assert!(
1085 before.contains(&attn) && !before.contains(&flash),
1086 "before the bake the decomposed region wins (arm 0); order={before:?}",
1087 );
1088
1089 // Injected Judge: flash measures CHEAPER (400 ns) than the region's two
1090 // measured matmuls (500 + 500 = 1000 ns) — flash's algorithm-changing
1091 // win Layer-1 can't see (FLOP tie / CPU-build capability gate).
1092 let mut judge = HashMapJudge::new();
1093 judge.insert(OpKind::MatMul, dt, k_scores, BackendId::Cuda, "", 500, 0);
1094 judge.insert(OpKind::MatMul, dt, k_attn, BackendId::Cuda, "", 500, 0);
1095 judge.insert(OpKind::FlashAttn, dt, k_flash, BackendId::Cuda, "", 400, 0);
1096
1097 let cost = |g: &Graph, b: NodeId, arm: usize, i: &[NodeId]| -> Option<u64> {
1098 decode_arm_composite_ns_judged(g, b, arm, i, BackendId::Cuda, Some(&judge))
1099 };
1100 let baked = bake_variant_branches(&mut g, &[post], &cost);
1101
1102 assert_eq!(
1103 baked, 1,
1104 "measured flash is strictly cheaper ⇒ the branch bakes to it"
1105 );
1106 assert_eq!(
1107 g.node(branch).inputs,
1108 vec![flash],
1109 "branch collapsed to the flash arm"
1110 );
1111 assert!(
1112 g.node(reconverge).inputs.contains(&flash),
1113 "merge rewired to flash"
1114 );
1115
1116 let after = lower_runs_arm0(&g, &[post]);
1117 assert!(
1118 after.contains(&flash),
1119 "flash realized after the bake; order={after:?}"
1120 );
1121 assert!(
1122 !after.contains(&attn),
1123 "the decomposed region is pruned; order={after:?}"
1124 );
1125 }
1126
1127 /// GUARD: the Judge says flash is SLOWER than the region ⇒ arm 0 (the
1128 /// conservative oracle default holds — a measurement never optimistically
1129 /// commits a losing variant).
1130 #[test]
1131 fn judge_measured_flash_slower_keeps_the_region() {
1132 let (mut g, branch, attn, flash, _recon, post) = flash_variant_diamond();
1133 let dt = DType::F16;
1134 let (k_scores, k_attn, k_flash) = decode_keys();
1135 let mut judge = HashMapJudge::new();
1136 judge.insert(OpKind::MatMul, dt, k_scores, BackendId::Cuda, "", 500, 0);
1137 judge.insert(OpKind::MatMul, dt, k_attn, BackendId::Cuda, "", 500, 0);
1138 judge.insert(OpKind::FlashAttn, dt, k_flash, BackendId::Cuda, "", 2000, 0);
1139 let cost = |g: &Graph, b: NodeId, arm: usize, i: &[NodeId]| -> Option<u64> {
1140 decode_arm_composite_ns_judged(g, b, arm, i, BackendId::Cuda, Some(&judge))
1141 };
1142 let baked = bake_variant_branches(&mut g, &[post], &cost);
1143 assert_eq!(
1144 baked, 0,
1145 "measured flash is slower ⇒ arm 0 (the region) stands"
1146 );
1147 assert_eq!(
1148 g.node(branch).inputs,
1149 vec![attn],
1150 "branch collapsed to arm 0"
1151 );
1152 let after = lower_runs_arm0(&g, &[post]);
1153 assert!(
1154 after.contains(&attn) && !after.contains(&flash),
1155 "region realized (oracle)"
1156 );
1157 }
1158
1159 /// GUARD: with `judge == None` the judged provider is **byte-identical** to
1160 /// the Layer-1-only provider per arm (the no-Judge production path + the
1161 /// persistent byte-exact suites stay untouched by construction), and the
1162 /// bake keeps arm 0.
1163 #[test]
1164 fn no_oracle_is_byte_identical_to_layer1() {
1165 let (mut g, branch, attn, flash, _recon, post) = flash_variant_diamond();
1166 let interiors = arm_interiors(&g, branch);
1167 for (arm, interior) in interiors.iter().enumerate() {
1168 assert_eq!(
1169 decode_arm_composite_ns_judged(&g, branch, arm, interior, BackendId::Cuda, None),
1170 decode_arm_composite_ns(&g, branch, arm, interior, BackendId::Cuda),
1171 "judged(None) must equal the Layer-1 provider for arm {arm}",
1172 );
1173 }
1174 let cost = |g: &Graph, b: NodeId, arm: usize, i: &[NodeId]| -> Option<u64> {
1175 decode_arm_composite_ns_judged(g, b, arm, i, BackendId::Cuda, None)
1176 };
1177 let baked = bake_variant_branches(&mut g, &[post], &cost);
1178 assert_eq!(
1179 baked, 0,
1180 "no oracle ⇒ Layer-1 path ⇒ arm 0 (flash unbound on a CPU build)"
1181 );
1182 assert_eq!(g.node(branch).inputs, vec![attn]);
1183 let _ = flash;
1184 }
1185
1186 /// GUARD (capability-missing, via the hybrid fallback): the Judge measured
1187 /// the region's matmuls but NOT the flash cell, so the flash arm falls back
1188 /// to Layer-1 where the capability gate (no bound CUDA flash kernel on a CPU
1189 /// build) makes it inadmissible ⇒ arm 0. A measurement is required to make
1190 /// an unbound-on-this-build variant admissible.
1191 #[test]
1192 fn judge_without_flash_cell_falls_to_capability_gate() {
1193 let (mut g, branch, attn, flash, _recon, post) = flash_variant_diamond();
1194 let dt = DType::F16;
1195 let (k_scores, k_attn, _k_flash) = decode_keys();
1196 let mut judge = HashMapJudge::new();
1197 judge.insert(OpKind::MatMul, dt, k_scores, BackendId::Cuda, "", 1, 0);
1198 judge.insert(OpKind::MatMul, dt, k_attn, BackendId::Cuda, "", 1, 0);
1199 let cost = |g: &Graph, b: NodeId, arm: usize, i: &[NodeId]| -> Option<u64> {
1200 decode_arm_composite_ns_judged(g, b, arm, i, BackendId::Cuda, Some(&judge))
1201 };
1202 let baked = bake_variant_branches(&mut g, &[post], &cost);
1203 if !fused_kernel_available(FusedOps::FLASH_ATTN, BackendId::Cuda) {
1204 assert_eq!(baked, 0, "no flash measurement + unbound kernel ⇒ arm 0");
1205 assert_eq!(g.node(branch).inputs, vec![attn]);
1206 }
1207 let _ = flash;
1208 }
1209
1210 /// GUARD (the hybrid sum): when only SOME of an arm's nodes are
1211 /// Judge-covered, measured cells and Layer-1 estimates are summed together.
1212 /// Here only the `scores` matmul is measured (500 ns); `attn` falls back to
1213 /// Layer-1. The region cost is exactly `500 + Layer-1(attn)`, and a cheaper
1214 /// measured flash still wins.
1215 #[test]
1216 fn hybrid_sum_mixes_measured_and_layer1_for_the_region() {
1217 let (mut g, branch, attn, flash, _recon, post) = flash_variant_diamond();
1218 let dt = DType::F16;
1219 let (k_scores, _k_attn, k_flash) = decode_keys();
1220 let mut judge = HashMapJudge::new();
1221 judge.insert(OpKind::MatMul, dt, k_scores, BackendId::Cuda, "", 500, 0); // scores only
1222 judge.insert(OpKind::FlashAttn, dt, k_flash, BackendId::Cuda, "", 400, 0);
1223
1224 // The region arm's hybrid cost == measured scores (500) + Layer-1 attn.
1225 let region = arm_interiors(&g, branch)[0].clone();
1226 let hybrid =
1227 decode_arm_composite_ns_judged(&g, branch, 0, ®ion, BackendId::Cuda, Some(&judge))
1228 .expect("region prices");
1229 let layer1_attn = decode_arm_composite_ns(&g, branch, 0, &[attn], BackendId::Cuda)
1230 .expect("attn prices on Layer-1");
1231 assert_eq!(
1232 hybrid,
1233 500 + layer1_attn,
1234 "hybrid region = measured scores (500) + Layer-1 attn ({layer1_attn})",
1235 );
1236
1237 // Flash (measured 400) is cheaper than the hybrid region ⇒ it wins.
1238 let cost = |g: &Graph, b: NodeId, arm: usize, i: &[NodeId]| -> Option<u64> {
1239 decode_arm_composite_ns_judged(g, b, arm, i, BackendId::Cuda, Some(&judge))
1240 };
1241 let baked = bake_variant_branches(&mut g, &[post], &cost);
1242 assert_eq!(
1243 baked, 1,
1244 "measured flash beats the hybrid region ⇒ bakes to flash"
1245 );
1246 assert_eq!(g.node(branch).inputs, vec![flash]);
1247 }
1248
1249 /// GUARD: a **placement** branch (arm exits on DIFFERENT backends) is never
1250 /// touched by the bake — even with a flash-loving Judge — because the
1251 /// same-device gate in `bake_variant_branches` skips it BEFORE the cost
1252 /// provider (and thus the Judge) is ever consulted. It stays a live 2-arm
1253 /// decision point for the runtime route picker.
1254 #[test]
1255 fn judge_does_not_touch_a_placement_branch() {
1256 let (mut g, branch, attn, flash, _recon, post) = flash_variant_diamond();
1257 // Re-stamp the flash arm onto a DIFFERENT backend ⇒ placement branch.
1258 g.set_target_backend(flash, BackendId::Cpu);
1259 let dt = DType::F16;
1260 let (_ks, _ka, k_flash) = decode_keys();
1261 let mut judge = HashMapJudge::new();
1262 judge.insert(OpKind::FlashAttn, dt, k_flash, BackendId::Cpu, "", 1, 0);
1263 let cost = |g: &Graph, b: NodeId, arm: usize, i: &[NodeId]| -> Option<u64> {
1264 let backend = g.target_backend(g.node(b).inputs.first().copied()?)?;
1265 decode_arm_composite_ns_judged(g, b, arm, i, backend, Some(&judge))
1266 };
1267 let baked = bake_variant_branches(&mut g, &[post], &cost);
1268 assert_eq!(
1269 baked, 0,
1270 "a placement branch is never baked, even with a flash-loving judge"
1271 );
1272 assert_eq!(
1273 g.node(branch).inputs,
1274 vec![attn, flash],
1275 "placement branch stays a live 2-arm decision point (runtime picker owns it)",
1276 );
1277 }
1278}