1mod caps_map;
43pub(crate) mod cost_compile;
44pub(crate) mod cost_expr;
45mod cpu_link;
46#[cfg(feature = "cuda")]
47mod cuda_link;
48mod error;
49mod lower;
50mod parse;
51mod precision;
52mod register;
53mod return_check;
54mod revhash;
55mod schema;
56mod shape_constraint;
57pub mod shape_expr;
62pub(crate) mod shape_expr_parse;
63mod validate;
64pub(crate) mod verify;
67#[cfg(any(test, feature = "dev-tools"))]
72pub(crate) mod contract_audit_flip;
73#[cfg(feature = "vulkan")]
74mod vulkan_link;
75mod warn;
76
77pub use caps_map::{ResolvedLayout, Tri, is_generic_contract};
78pub use cost_expr::{
79 CompiledCostExpr, CostEvalError, CostNode, bind_cost_symbols, cost_estimate, eval as eval_cost,
80};
81pub use cpu_link::{
82 CPU_AFFINE_CLAMP_POWI_ENTRY_POINTS, CPU_ATTENTION_ENTRY_POINTS, CPU_BINARY_ENTRY_POINTS,
83 CPU_COMPARE_ENTRY_POINTS, CPU_CONV_ENTRY_POINTS, CPU_FUSED_CONV_ROPE_ENTRY_POINTS,
84 CPU_FUSED_LINEAR_QUANT_ENTRY_POINTS, CPU_FUSED_NORM_ENTRY_POINTS, CPU_INPLACE_ENTRY_POINTS,
85 CPU_MATMUL_ENTRY_POINTS, CPU_NORM_BACKWARD_ENTRY_POINTS, CPU_NORM_ENTRY_POINTS,
86 CPU_PADDING_ENTRY_POINTS, CPU_REDUCE_ENTRY_POINTS, CPU_REDUCE_TO_ENTRY_POINTS,
87 CPU_ROPE_ENTRY_POINTS, CPU_SHAPE_OPS_ENTRY_POINTS, CPU_SSM_ENTRY_POINTS,
88 CPU_UNARY_ENTRY_POINTS, CPU_WHERE_ENTRY_POINTS, CpuLinkRegistry,
89};
90#[cfg(feature = "cuda")]
91pub use cuda_link::{
92 CUDA_AFFINE_ENTRY_POINTS, CUDA_ARG_REDUCE_ENTRY_POINTS, CUDA_BINARY_ENTRY_POINTS,
93 CUDA_CAST_ENTRY_POINTS, CUDA_CAUSAL_CONV1D_ENTRY_POINTS, CUDA_CLAMP_ENTRY_POINTS,
94 CUDA_CLAMP_INPLACE_ENTRY_POINTS, CUDA_CONCAT_ENTRY_POINTS, CUDA_COST_FNS,
95 CUDA_CUMSUM_ENTRY_POINTS, CUDA_FLASH_DECODING_ENTRY_POINTS, CUDA_FLIP_ENTRY_POINTS,
96 CUDA_GEMM_DENSE_ENTRY_POINTS, CUDA_GEMM_INT_ENTRY_POINTS, CUDA_INDEXING_ENTRY_POINTS,
97 CUDA_INPLACE_AFFINE_ENTRY_POINTS, CUDA_INPLACE_UNARY_ENTRY_POINTS, CUDA_NORM_ENTRY_POINTS,
98 CUDA_PAD_BACKWARD_ENTRY_POINTS, CUDA_PAD_ENTRY_POINTS, CUDA_POWI_BACKWARD_ENTRY_POINTS,
99 CUDA_POWI_ENTRY_POINTS, CUDA_POWI_INPLACE_ENTRY_POINTS, CUDA_REDUCE_ENTRY_POINTS,
100 CUDA_REDUCE_TO_ENTRY_POINTS, CUDA_ROLL_ENTRY_POINTS, CUDA_ROPE_ENTRY_POINTS,
101 CUDA_SOFTMAX_ENTRY_POINTS, CUDA_TRIANGULAR_ENTRY_POINTS, CUDA_UNARY_ENTRY_POINTS,
102 CUDA_WRITE_SLICE_ENTRY_POINTS, CUDA_WRITE_SLICE_ROTATING_ENTRY_POINTS, CudaLinkRegistry,
103};
104pub use error::FkcError;
105pub use lower::{LinkRegistry, Resolved, ResolvedFused, ResolvedPrimitive, lower_file};
106pub use parse::{parse_file, parse_path};
107pub use register::{
108 ImportedProvider, fused_unknown_cost, import_bundle, import_bundle_str, import_glob,
109};
110pub use revhash::compute_revision;
111pub use schema::{
112 AcceptBlock, CapsBlock, CostBlock, CostMemory, FdxSpec, FkcFile, FkcFrontMatter, FkcKernel,
113 FkcProvider, GatherSpec, LayoutSpec, OpParamFieldSpec, OpParamsSchema, OutputDesc,
114 PrecisionBlock, QuantSpec, ReturnBlock, TensorDesc,
115};
116pub use shape_constraint::{
117 AxisIndex, ProbeCombo, RankSpec, ShapeAtom, ShapeConstraint, parse_rank_spec,
118 parse_shape_constraint, solve_probe_shapes,
119};
120pub use validate::{FKC_SUPPORTED_VERSIONS, validate_file, validate_kernel};
121#[cfg(feature = "vulkan")]
122pub use vulkan_link::{
123 VULKAN_CAST_ENTRY_POINTS, VULKAN_ELEMENTWISE_ENTRY_POINTS, VulkanLinkRegistry,
124};
125pub use warn::ImportWarning;
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 const ELEMENTWISE_BINARY: &str =
135 include_str!("../../kernel-contracts/cpu/elementwise-binary.fkc.md");
136 const QUANT_MATMUL: &str = include_str!("../../kernel-contracts/cpu/quant-matmul.fkc.md");
140 const ROPE_APPLY: &str = include_str!("../../kernel-contracts/cuda/rope-apply.fkc.md");
152
153 #[test]
158 fn parses_real_elementwise_binary_contract() {
159 let file =
160 parse_file(ELEMENTWISE_BINARY).expect("authored elementwise-binary.fkc.md must parse");
161
162 assert_eq!(file.front_matter.fkc_version, 1);
164 assert_eq!(file.front_matter.provider.name, "fuel-cpu-backend");
165 assert_eq!(file.front_matter.provider.backend, "Cpu");
166 assert_eq!(file.front_matter.provider.kernel_source, "portable-cpu");
167 assert_eq!(
168 file.front_matter.provider.link_registry.as_deref(),
169 Some("fuel_cpu_backend::fkc::ENTRY_POINTS")
170 );
171
172 assert!(
174 !file.kernels.is_empty(),
175 "elementwise-binary must yield kernels"
176 );
177
178 let names: Vec<&str> = file.kernels.iter().map(|k| k.kernel.as_str()).collect();
180 assert!(
181 names.contains(&"binary"),
182 "expected the `binary` chassis section; got {names:?}"
183 );
184 assert!(
185 names.contains(&"add_f32"),
186 "expected `add_f32`; got {names:?}"
187 );
188
189 let add_f32 = file
191 .kernels
192 .iter()
193 .find(|k| k.kernel == "add_f32")
194 .expect("add_f32 present");
195 assert_eq!(add_f32.op_kind.as_deref(), Some("AddElementwise"));
196 assert!(add_f32.fused_op.is_none());
197
198 let accept = add_f32.accept.as_ref().expect("add_f32 has accept");
200 assert_eq!(accept.inputs.len(), 2);
201 assert_eq!(accept.inputs[0].name.as_deref(), Some("lhs"));
202 assert_eq!(accept.inputs[0].dtypes, vec!["F32".to_string()]);
203 let layout = accept.inputs[0].layout.as_ref().expect("lhs has layout");
205 assert_eq!(layout.contiguous.as_deref(), Some("required"));
206 assert_eq!(layout.strided.as_deref(), Some("rejected"));
207
208 let ret = add_f32.return_.as_ref().expect("add_f32 has return");
210 assert_eq!(ret.outputs.len(), 1);
211 assert_eq!(
212 ret.outputs[0].dtype_rule.as_deref(),
213 Some("passthrough(lhs)")
214 );
215
216 let cost = add_f32.cost.as_ref().expect("add_f32 has cost");
218 assert_eq!(cost.provenance.as_deref(), Some("judge_measured"));
219 assert_eq!(cost.flops.as_deref(), Some("n"));
220 assert_eq!(cost.bytes_moved.as_deref(), Some("3 * n * 4"));
221
222 assert_eq!(
224 add_f32.determinism.as_deref(),
225 Some("same_hardware_bitwise")
226 );
227 }
228
229 #[test]
230 fn parses_real_quant_matmul_contract() {
231 let file = parse_file(QUANT_MATMUL).expect("authored quant-matmul.fkc.md must parse");
232
233 assert_eq!(file.front_matter.provider.name, "fuel-cpu-backend");
234 assert!(!file.kernels.is_empty());
235
236 let names: Vec<&str> = file.kernels.iter().map(|k| k.kernel.as_str()).collect();
237 assert!(
238 names.contains(&"qmatmul_q4_0_f32"),
239 "expected qmatmul_q4_0_f32; got {names:?}"
240 );
241 assert!(
242 names.contains(&"nf4_matmul_f32"),
243 "expected nf4_matmul_f32; got {names:?}"
244 );
245
246 let q40 = file
249 .kernels
250 .iter()
251 .find(|k| k.kernel == "qmatmul_q4_0_f32")
252 .unwrap();
253 assert_eq!(q40.op_kind.as_deref(), Some("QMatMul"));
254 let weight = q40
255 .accept
256 .as_ref()
257 .unwrap()
258 .inputs
259 .iter()
260 .find(|d| d.name.as_deref() == Some("weight"))
261 .expect("q4_0 has a weight operand");
262 let quant = weight
263 .fdx
264 .as_ref()
265 .unwrap()
266 .quant
267 .as_ref()
268 .expect("weight has fdx.quant");
269 assert_eq!(quant.family.as_deref(), Some("GGML_BLOCK"));
270 assert_eq!(quant.ggml_dtype.as_deref(), Some("Q4_0"));
271 assert_eq!(quant.role.as_deref(), Some("weight"));
272 assert!(
273 quant.scale_operand.is_none(),
274 "INLINE scale: no separate operand"
275 );
276
277 let op_params = q40.accept.as_ref().unwrap().op_params.as_ref().unwrap();
279 assert_eq!(op_params.variant.as_deref(), Some("QMatMul"));
280 assert!(op_params.fields.contains_key("k"));
281
282 let nf4 = file
284 .kernels
285 .iter()
286 .find(|k| k.kernel == "nf4_matmul_f32")
287 .unwrap();
288 let nf4_inputs = &nf4.accept.as_ref().unwrap().inputs;
289 assert!(
290 nf4_inputs
291 .iter()
292 .any(|d| d.name.as_deref() == Some("absmax")),
293 "NF4 has a separate absmax operand"
294 );
295 let w_packed = nf4_inputs
296 .iter()
297 .find(|d| d.name.as_deref() == Some("w_packed"))
298 .unwrap();
299 let nf4_quant = w_packed.fdx.as_ref().unwrap().quant.as_ref().unwrap();
300 assert_eq!(nf4_quant.family.as_deref(), Some("AFFINE_BLOCK"));
301 assert_eq!(nf4_quant.scale_operand.as_deref(), Some("absmax"));
302 }
303
304 #[test]
311 fn parses_and_lowers_real_rope_apply_contract() {
312 use fuel_ir::DType;
313 use fuel_ir::dispatch::OpKind;
314 use fuel_ir::probe::BackendId;
315
316 let file = parse_file(ROPE_APPLY).expect("rope-apply.fkc.md must parse");
317 validate_file(&file).expect("rope-apply.fkc.md must pass the V-FKC-* validators");
318
319 assert_eq!(file.front_matter.provider.name, "fuel-cuda-backend");
320 assert_eq!(file.front_matter.provider.backend, "Cuda");
321 assert_eq!(file.front_matter.provider.kernel_source, "baracuda");
322
323 let names: Vec<&str> = file.kernels.iter().map(|k| k.kernel.as_str()).collect();
324 assert!(
325 names.contains(&"rope_apply"),
326 "expected a `rope_apply` section; got {names:?}"
327 );
328
329 let kernel = file
330 .kernels
331 .iter()
332 .find(|k| k.kernel == "rope_apply")
333 .unwrap();
334 assert_eq!(kernel.op_kind.as_deref(), Some("Rope"));
335 assert!(
336 kernel.fused_op.is_none(),
337 "rope_apply is a primitive op_kind contract, not fused"
338 );
339
340 let accept = kernel.accept.as_ref().expect("rope_apply has accept");
343 assert_eq!(accept.inputs.len(), 3);
344 assert_eq!(accept.inputs[0].name.as_deref(), Some("x"));
345 assert_eq!(
346 accept.inputs[0].dtypes,
347 vec![
348 "F32".to_string(),
349 "F16".to_string(),
350 "BF16".to_string(),
351 "F64".to_string()
352 ],
353 );
354 assert_eq!(accept.inputs[1].name.as_deref(), Some("cos"));
355 assert_eq!(accept.inputs[1].dtypes, vec!["F32".to_string()]);
356 assert_eq!(accept.inputs[2].name.as_deref(), Some("sin"));
357 assert_eq!(accept.inputs[2].dtypes, vec!["F32".to_string()]);
358
359 fn dummy_kernel(
363 _inputs: &[std::sync::Arc<std::sync::RwLock<fuel_memory::Storage>>],
364 _outputs: &mut [std::sync::Arc<std::sync::RwLock<fuel_memory::Storage>>],
365 _layouts: &[fuel_ir::Layout],
366 _params: &crate::kernel::OpParams,
367 ) -> fuel_ir::Result<()> {
368 Ok(())
369 }
370 struct StubRopeApplyLink;
371 impl LinkRegistry for StubRopeApplyLink {
372 fn resolve_primitive(&self, symbol: &str) -> Option<crate::kernel::KernelRef> {
373 const KNOWN: &[&str] = &[
374 "baracuda_kernels_rope_apply_f32",
375 "baracuda_kernels_rope_apply_f16",
376 "baracuda_kernels_rope_apply_bf16",
377 "baracuda_kernels_rope_apply_f64",
378 ];
379 if KNOWN.contains(&symbol) {
380 Some(dummy_kernel)
381 } else {
382 None
383 }
384 }
385 fn resolve_fused(&self, _symbol: &str) -> Option<crate::kernel::KernelRef> {
386 None
387 }
388 }
389
390 let mut warnings = Vec::new();
391 let resolved = lower_file(&file, &StubRopeApplyLink, &mut warnings)
392 .expect("rope-apply.fkc.md must lower against a registry resolving its 4 real symbols");
393 let primitives: Vec<_> = resolved
394 .iter()
395 .filter_map(|r| match r {
396 Resolved::Primitive(p) => Some(p),
397 Resolved::Fused(_) => None,
398 })
399 .collect();
400 assert_eq!(
401 primitives.len(),
402 4,
403 "expected 4 dtype variants (F32/F16/BF16/F64); got {}",
404 primitives.len(),
405 );
406 for p in &primitives {
407 assert_eq!(p.op, OpKind::Rope);
408 assert_eq!(p.backend, BackendId::Cuda);
409 assert_eq!(
411 p.dtypes.len(),
412 4,
413 "expected [x, cos, sin, out] key; got {:?}",
414 p.dtypes
415 );
416 assert_eq!(p.dtypes[1], DType::F32, "cos is always F32");
417 assert_eq!(p.dtypes[2], DType::F32, "sin is always F32");
418 assert_eq!(
419 p.dtypes[0], p.dtypes[3],
420 "output passthrough(x) must match x's dtype"
421 );
422 }
423 let x_dtypes: std::collections::HashSet<DType> =
424 primitives.iter().map(|p| p.dtypes[0]).collect();
425 assert_eq!(
426 x_dtypes,
427 [DType::F32, DType::F16, DType::BF16, DType::F64]
428 .into_iter()
429 .collect(),
430 "expected exactly the F32/F16/BF16/F64 fan",
431 );
432 }
433
434 const VALID_MINIMAL: &str = "\
439---
440fkc_version: 1
441provider:
442 name: test-provider
443 backend: Cpu
444 kernel_source: \"test-cpu\"
445---
446
447# test bundle
448
449## demo
450
451A blurb.
452
453```fkc
454kernel: demo
455op_kind: AddElementwise
456blurb: \"demo\"
457entry_point: \"x::y\"
458```
459";
460
461 #[test]
462 fn valid_minimal_parses() {
463 let file = parse_file(VALID_MINIMAL).expect("minimal valid file parses");
464 assert_eq!(file.kernels.len(), 1);
465 assert_eq!(file.kernels[0].kernel, "demo");
466 }
467
468 #[test]
469 fn tab_indentation_in_block_is_rejected() {
470 let src = "\
472---
473fkc_version: 1
474provider:
475 name: p
476 backend: Cpu
477 kernel_source: \"c\"
478---
479
480## demo
481
482b
483
484```fkc
485kernel: demo
486accept:
487\tinputs: []
488```
489";
490 let err = parse_file(src).expect_err("tab must be rejected");
491 assert!(
492 matches!(err, FkcError::TabIndentation { .. }),
493 "got {err:?}"
494 );
495 }
496
497 #[test]
498 fn anchor_is_rejected() {
499 let src = "\
500---
501fkc_version: 1
502provider:
503 name: p
504 backend: Cpu
505 kernel_source: \"c\"
506---
507
508## demo
509
510b
511
512```fkc
513kernel: &base demo
514op_kind: AddElementwise
515```
516";
517 let err = parse_file(src).expect_err("anchor must be rejected");
518 assert!(
519 matches!(err, FkcError::AnchorDisallowed { .. }),
520 "got {err:?}"
521 );
522 }
523
524 #[test]
525 fn alias_is_rejected() {
526 let src = "\
527---
528fkc_version: 1
529provider:
530 name: p
531 backend: Cpu
532 kernel_source: \"c\"
533---
534
535## demo
536
537b
538
539```fkc
540kernel: demo
541blurb: *base
542```
543";
544 let err = parse_file(src).expect_err("alias must be rejected");
545 assert!(
546 matches!(err, FkcError::AliasDisallowed { .. }),
547 "got {err:?}"
548 );
549 }
550
551 #[test]
552 fn merge_key_is_rejected() {
553 let src = "\
554---
555fkc_version: 1
556provider:
557 name: p
558 backend: Cpu
559 kernel_source: \"c\"
560---
561
562## demo
563
564b
565
566```fkc
567kernel: demo
568caps:
569 <<: defaults
570```
571";
572 let err = parse_file(src).expect_err("merge key must be rejected");
573 assert!(
574 matches!(err, FkcError::MergeKeyDisallowed { .. }),
575 "got {err:?}"
576 );
577 }
578
579 #[test]
580 fn unquoted_norway_token_is_rejected() {
581 let src = "\
584---
585fkc_version: 1
586provider:
587 name: p
588 backend: Cpu
589 kernel_source: \"c\"
590---
591
592## demo
593
594b
595
596```fkc
597kernel: demo
598family: no
599```
600";
601 let err = parse_file(src).expect_err("unquoted Norway token must be rejected");
602 assert!(
603 matches!(err, FkcError::NorwayToken { ref token, .. } if token == "no"),
604 "got {err:?}"
605 );
606 }
607
608 #[test]
609 fn quoted_norway_token_is_accepted_as_string() {
610 let src = "\
613---
614fkc_version: 1
615provider:
616 name: p
617 backend: Cpu
618 kernel_source: \"c\"
619---
620
621## demo
622
623b
624
625```fkc
626kernel: demo
627accept:
628 inputs:
629 - name: lhs
630 dtypes: [F32]
631 fdx:
632 quant:
633 family: \"none\"
634```
635";
636 let file = parse_file(src).expect("quoted token parses");
637 let fam = file.kernels[0].accept.as_ref().unwrap().inputs[0]
638 .fdx
639 .as_ref()
640 .unwrap()
641 .quant
642 .as_ref()
643 .unwrap()
644 .family
645 .as_deref();
646 assert_eq!(fam, Some("none"));
647 }
648
649 #[test]
654 fn missing_fkc_block_is_rejected() {
655 let src = "\
656---
657fkc_version: 1
658provider:
659 name: p
660 backend: Cpu
661 kernel_source: \"c\"
662---
663
664## demo
665
666Just prose, no fkc block.
667";
668 let err = parse_file(src).expect_err("section with no fkc block must error");
669 assert!(
670 matches!(err, FkcError::MissingFkcBlock { ref section } if section == "demo"),
671 "got {err:?}"
672 );
673 }
674
675 #[test]
676 fn duplicate_fkc_block_is_rejected() {
677 let src = "\
678---
679fkc_version: 1
680provider:
681 name: p
682 backend: Cpu
683 kernel_source: \"c\"
684---
685
686## demo
687
688b
689
690```fkc
691kernel: demo
692```
693
694more prose
695
696```fkc
697kernel: demo2
698```
699";
700 let err = parse_file(src).expect_err("section with two fkc blocks must error");
701 assert!(
702 matches!(err, FkcError::MultipleFkcBlocks { ref section, count } if section == "demo" && count == 2),
703 "got {err:?}"
704 );
705 }
706
707 #[test]
708 fn missing_front_matter_is_rejected() {
709 let src = "## demo\n\n```fkc\nkernel: demo\n```\n";
710 let err = parse_file(src).expect_err("file without front-matter must error");
711 assert!(
712 matches!(err, FkcError::MalformedFrontMatter(_)),
713 "got {err:?}"
714 );
715 }
716
717 #[test]
725 fn orphan_fkc_block_outside_a_section_is_rejected() {
726 let src = "---\nfkc_version: 1\nprovider:\n name: p\n backend: Cpu\n kernel_source: \"ks\"\n---\n\n# Title\n\n```fkc\nkernel: add_f32\nop_kind: AddElementwise\n```\n";
728 let err = parse_file(src).expect_err(
729 "an fkc block outside any `## ` section must be rejected, not silently dropped",
730 );
731 assert!(
732 matches!(err, FkcError::OrphanFkcBlock { .. }),
733 "got {err:?}"
734 );
735 }
736
737 #[test]
738 fn fkc_block_with_no_sections_at_all_is_rejected() {
739 let src = "---\nfkc_version: 1\nprovider:\n name: p\n backend: Cpu\n kernel_source: \"ks\"\n---\n\n```fkc\nkernel: add_f32\nop_kind: AddElementwise\n```\n";
740 assert!(matches!(
741 parse_file(src).unwrap_err(),
742 FkcError::OrphanFkcBlock { .. }
743 ));
744 }
745
746 #[test]
747 fn non_fkc_fence_before_first_heading_is_not_flagged() {
748 let src = "---\nfkc_version: 1\nprovider:\n name: p\n backend: Cpu\n kernel_source: \"ks\"\n---\n\n# Title\n\n```yaml\nexample: true\n```\n\n## add_f32\n\n```fkc\nkernel: add_f32\nop_kind: AddElementwise\n```\n";
750 parse_file(src).expect("a non-fkc intro fence + a real section parses fine");
751 }
752
753 #[test]
754 fn indented_pseudo_heading_does_not_hide_a_following_orphan_fkc_block() {
755 let src = "---\nfkc_version: 1\nprovider:\n name: p\n backend: Cpu\n kernel_source: \"ks\"\n---\n\n# Title\n\n ## not-a-real-heading (indented)\n\n```fkc\nkernel: add_f32\nop_kind: AddElementwise\n```\n";
759 let err = parse_file(src)
760 .expect_err("an indented pseudo-heading must not hide a following orphan fkc block");
761 assert!(
762 matches!(err, FkcError::OrphanFkcBlock { .. }),
763 "got {err:?}"
764 );
765 }
766}