use std::collections::{HashMap, HashSet};
use rlx_fusion::control_flow::{LowerControlFlow, LowerScan};
use rlx_fusion::fusion::UnfuseElementwiseRegions;
use rlx_fusion::lower_axial_rope2d::LowerAxialRope2d;
use rlx_fusion::lower_backward_ops::LowerBackwardOps;
use rlx_fusion::lower_conv_transpose2d::LowerConvTranspose2d;
use rlx_fusion::lower_cumulative::LowerCumulative;
use rlx_fusion::lower_dot_general::LowerDotGeneral;
use rlx_fusion::lower_fake_quantize::LowerFakeQuantize;
use rlx_fusion::lower_fma::LowerFma;
use rlx_fusion::lower_histogram::LowerHistogram;
use rlx_fusion::lower_kv_append::LowerKvAppend;
use rlx_fusion::lower_logical_kernels;
use rlx_fusion::lower_loss_ops::LowerSoftmaxCrossEntropy;
use rlx_fusion::lower_pad::LowerPad;
use rlx_fusion::lower_reduce_axes::LowerNonLastAxisReduce;
use rlx_fusion::lower_roll::LowerRoll;
use rlx_fusion::lower_scaled_grouped_matmul::LowerScaledGroupedMatMul;
use rlx_fusion::lower_scatter_add::LowerScatterAddAxis;
use rlx_fusion::lower_slice::LowerSlice;
use rlx_fusion::lower_softmax::LowerSoftmaxAxis;
use rlx_fusion::lower_spectral::LowerSpectral;
use rlx_fusion::lower_spline_activation::LowerSplineActivation;
use rlx_fusion::lower_spline_backward::LowerSplineActivationBackward;
use rlx_fusion::lower_structural::LowerStructural;
use rlx_fusion::lower_synth_matmul::LowerSynthMatMul;
use rlx_fusion::lower_synth_matmul_backward::LowerSynthMatMulBackward;
use rlx_fusion::lower_synth_reconstruct::LowerSynthReconstruct;
use rlx_fusion::lower_vae_ops::{LowerBatchNormInference, LowerGroupNorm, LowerResizeNearest2x};
use rlx_fusion::pass::Pass;
use rlx_fusion::unfuse::unfuse_fused_for_autodiff;
use rlx_ir::logical_kernel::{KernelDispatchConfig, KernelDispatchPolicy};
use rlx_ir::{Graph, LowerContext, Node, NodeId, Op, OpKind, lookup_op};
use crate::legalize::legalize_for_backend;
fn needs_unfuse(kinds: &HashSet<OpKind>) -> bool {
kinds.iter().any(|k| k.has(rlx_ir::OpCaps::FUSED))
}
fn unsupported_kinds(graph: &Graph, supported: &[OpKind]) -> HashSet<OpKind> {
legalize_for_backend(graph, supported)
.err()
.map(|bad| bad.into_iter().map(|(_, k)| k).collect())
.unwrap_or_default()
}
#[cfg(feature = "training")]
fn needs_backward_decompose(bad: &HashSet<OpKind>) -> bool {
use OpKind::*;
bad.iter().any(|k| {
matches!(
k,
Conv2dBackwardInput
| Conv2dBackwardWeight
| MaxPool2dBackward
| LayerNormBackwardInput
| LayerNormBackwardGamma
| RmsNormBackwardInput
| RmsNormBackwardGamma
| RmsNormBackwardBeta
| GroupNormBackwardInput
| GroupNormBackwardGamma
| GroupNormBackwardBeta
| RopeBackward
| AttentionBackward
| CumsumBackward
| GatherBackward
| BatchNormInferenceBackwardInput
| BatchNormInferenceBackwardGamma
| BatchNormInferenceBackwardBeta
| FakeQuantizeBackward
| SoftmaxCrossEntropyBackward
| ReluBackward
| ActivationBackward
| ScanBackward
| ScanBackwardXs
| AdaLayerNormBackward
| GatedResidualBackward
| GatedDeltaNetBackward
)
})
}
fn apply(graph: &mut Graph, changed: &mut bool, pass: &dyn Pass) {
let taken = std::mem::replace(graph, Graph::new(""));
let result = pass.run_detecting_change(taken);
*graph = result.graph;
*changed |= result.ir_changed.changed();
}
fn apply_fn(graph: &mut Graph, changed: &mut bool, lower: impl FnOnce(Graph) -> Graph) {
let taken = std::mem::replace(graph, Graph::new(""));
let before = taken.fingerprint();
let out = lower(taken);
*changed |= out.fingerprint() != before;
*graph = out;
}
fn copy_node(out: &mut Graph, node: &Node, inputs: Vec<NodeId>) -> NodeId {
let id = out.add_node(node.op.clone(), inputs, node.shape.clone());
let n = out.node_mut(id);
n.name = node.name.clone();
n.origin = node.origin.clone();
id
}
pub fn lower_custom_ops(graph: Graph) -> Graph {
if !graph
.nodes()
.iter()
.any(|n| matches!(n.op, Op::Custom { .. }))
{
return graph;
}
let mut out = Graph::new(&graph.name);
let mut id_map: HashMap<NodeId, NodeId> = HashMap::new();
for node in graph.nodes() {
let new_inputs: Vec<NodeId> = node.inputs.iter().map(|i| id_map[i]).collect();
let new_id = match &node.op {
Op::Custom { name, .. } => {
let lowered = lookup_op(name).and_then(|ext| {
let mut ctx = LowerContext {
inputs: &new_inputs,
out: &mut out,
};
ext.lower(node, &mut ctx)
});
match lowered {
Some(id) => id,
None => copy_node(&mut out, node, new_inputs),
}
}
_ => copy_node(&mut out, node, new_inputs),
};
id_map.insert(node.id, new_id);
}
out.set_outputs(graph.outputs.iter().map(|o| id_map[o]).collect());
out
}
pub fn rewrite_for_backend(graph: Graph, supported: &[OpKind]) -> Graph {
rewrite_for_backend_with_config(graph, supported, KernelDispatchConfig::default())
}
pub fn rewrite_for_backend_with_dispatch(
graph: Graph,
supported: &[OpKind],
dispatch: KernelDispatchPolicy,
) -> Graph {
rewrite_for_backend_with_config(graph, supported, KernelDispatchConfig::new(dispatch))
}
pub fn rewrite_for_backend_with_config(
mut graph: Graph,
supported: &[OpKind],
config: KernelDispatchConfig,
) -> Graph {
graph = lower_logical_kernels(graph, supported, config);
graph = LowerSpectral.run(graph);
graph = lower_custom_ops(graph);
graph = LowerScatterAddAxis.run(graph);
graph = LowerSoftmaxAxis.run(graph);
graph = LowerKvAppend::NON_ALIASABLE.run(graph);
if supported.is_empty() {
return graph;
}
for _ in 0..16 {
if legalize_for_backend(&graph, supported).is_ok() {
return graph;
}
let bad = unsupported_kinds(&graph, supported);
if bad.is_empty() {
break;
}
let mut changed = false;
if bad.contains(&OpKind::GroupNorm) {
apply(&mut graph, &mut changed, &LowerGroupNorm);
}
if bad.contains(&OpKind::BatchNormInference) {
apply(&mut graph, &mut changed, &LowerBatchNormInference);
}
if bad.contains(&OpKind::ResizeNearest2x) {
apply(&mut graph, &mut changed, &LowerResizeNearest2x);
}
if bad.contains(&OpKind::ConvTranspose2d) {
apply(&mut graph, &mut changed, &LowerConvTranspose2d);
}
if bad.contains(&OpKind::Pad) {
apply(&mut graph, &mut changed, &LowerPad);
}
if bad.contains(&OpKind::Slice) {
apply(&mut graph, &mut changed, &LowerSlice);
}
if bad.contains(&OpKind::KvAppend) {
apply(&mut graph, &mut changed, &LowerKvAppend::ALL);
}
if bad.contains(&OpKind::Roll) {
apply(&mut graph, &mut changed, &LowerRoll);
}
if bad.contains(&OpKind::AxialRope2d) {
apply(&mut graph, &mut changed, &LowerAxialRope2d);
}
if bad.contains(&OpKind::FakeQuantize) {
apply(&mut graph, &mut changed, &LowerFakeQuantize);
}
if bad.contains(&OpKind::Clamp)
|| bad.contains(&OpKind::Tile)
|| bad.contains(&OpKind::Trilu)
{
apply(&mut graph, &mut changed, &LowerStructural);
}
if bad.contains(&OpKind::CumProd) || bad.contains(&OpKind::CumMax) {
apply(&mut graph, &mut changed, &LowerCumulative);
}
if bad.contains(&OpKind::Histogram) {
apply(&mut graph, &mut changed, &LowerHistogram);
}
if bad.contains(&OpKind::DotGeneral) {
apply(&mut graph, &mut changed, &LowerDotGeneral);
}
if bad.contains(&OpKind::ScaledGroupedMatMul) {
apply(&mut graph, &mut changed, &LowerScaledGroupedMatMul);
}
if bad.contains(&OpKind::SynthMatMul) {
apply(&mut graph, &mut changed, &LowerSynthMatMul);
}
if bad.contains(&OpKind::SynthMatMulBackward) {
apply(&mut graph, &mut changed, &LowerSynthMatMulBackward);
}
if bad.contains(&OpKind::SynthReconstruct) {
apply(&mut graph, &mut changed, &LowerSynthReconstruct);
}
if bad.contains(&OpKind::SplineActivation) {
apply(&mut graph, &mut changed, &LowerSplineActivation);
}
if bad.contains(&OpKind::SplineActivationBackwardX)
|| bad.contains(&OpKind::SplineActivationBackwardCoeff)
{
apply(&mut graph, &mut changed, &LowerSplineActivationBackward);
}
if bad.contains(&OpKind::Fma) {
apply(&mut graph, &mut changed, &LowerFma);
}
if bad.contains(&OpKind::If) || bad.contains(&OpKind::While) {
apply(&mut graph, &mut changed, &LowerControlFlow);
}
if bad.contains(&OpKind::ReEig) || bad.contains(&OpKind::LogEig) {
apply(&mut graph, &mut changed, &LowerSpectral);
}
if bad.contains(&OpKind::Scan) {
apply(&mut graph, &mut changed, &LowerScan);
}
if bad.contains(&OpKind::ElementwiseRegion) {
apply(&mut graph, &mut changed, &UnfuseElementwiseRegions::FOR_CPU);
}
if bad.contains(&OpKind::SoftmaxCrossEntropy)
|| bad.contains(&OpKind::SoftmaxCrossEntropyWithLogits)
|| bad.contains(&OpKind::SoftmaxCrossEntropyBackward)
{
apply(&mut graph, &mut changed, &LowerSoftmaxCrossEntropy);
}
#[cfg(feature = "training")]
if needs_backward_decompose(&bad) {
apply_fn(&mut graph, &mut changed, |g| {
rlx_autodiff::decompose_backward_ops_except(g, supported)
});
}
if bad.contains(&OpKind::ReluBackward)
|| bad.contains(&OpKind::ActivationBackward)
|| bad.contains(&OpKind::BatchNormInferenceBackwardInput)
|| bad.contains(&OpKind::BatchNormInferenceBackwardGamma)
|| bad.contains(&OpKind::BatchNormInferenceBackwardBeta)
{
apply(&mut graph, &mut changed, &LowerBackwardOps);
}
if bad.contains(&OpKind::Reduce)
|| graph.nodes().iter().any(|n| {
matches!(&n.op, Op::Reduce { axes, .. } if {
let rank = graph.shape(n.inputs[0]).rank();
axes.len() > 1 || (rank > 0 && axes.as_slice() != [rank - 1])
})
})
{
apply(&mut graph, &mut changed, &LowerNonLastAxisReduce);
}
if needs_unfuse(&bad) {
apply_fn(&mut graph, &mut changed, unfuse_fused_for_autodiff);
}
if !changed {
break;
}
}
graph
}
pub fn legalize_or_rewrite_for_backend(
graph: Graph,
supported: &[OpKind],
) -> Result<Graph, Vec<(rlx_ir::NodeId, OpKind)>> {
legalize_or_rewrite_for_backend_with_config(graph, supported, KernelDispatchConfig::default())
}
pub fn legalize_or_rewrite_for_backend_with_dispatch(
graph: Graph,
supported: &[OpKind],
dispatch: KernelDispatchPolicy,
) -> Result<Graph, Vec<(rlx_ir::NodeId, OpKind)>> {
legalize_or_rewrite_for_backend_with_config(
graph,
supported,
KernelDispatchConfig::new(dispatch),
)
}
pub fn legalize_or_rewrite_for_backend_with_config(
graph: Graph,
supported: &[OpKind],
config: KernelDispatchConfig,
) -> Result<Graph, Vec<(rlx_ir::NodeId, OpKind)>> {
if supported.is_empty() {
return Ok(graph);
}
let graph = rewrite_for_backend_with_config(graph, supported, config);
legalize_for_backend(&graph, supported).map(|()| graph)
}
#[cfg(test)]
mod tests {
use super::*;
use rlx_ir::infer::GraphExt;
use rlx_ir::*;
#[test]
fn lower_custom_op_decomposes_to_primitives_for_restricted_backend() {
use rlx_ir::op::BinaryOp;
use rlx_ir::{LowerContext, Node, NodeId, OpExtension, register_op};
use std::sync::Arc;
struct DoubleOp;
impl OpExtension for DoubleOp {
fn name(&self) -> &str {
"test_double_lower"
}
fn num_inputs(&self) -> usize {
1
}
fn infer_shape(&self, inputs: &[&Shape], _: &[u8]) -> Shape {
inputs[0].clone()
}
fn lower(&self, _node: &Node, ctx: &mut LowerContext) -> Option<NodeId> {
let x = ctx.inputs[0];
let shape = ctx.out.node(x).shape.clone();
Some(
ctx.out
.add_node(Op::Binary(BinaryOp::Add), vec![x, x], shape),
)
}
}
register_op(Arc::new(DoubleOp));
let f = DType::F32;
let mut g = Graph::new("cust");
let x = g.input("x", Shape::new(&[4], f));
let y = g.custom_op("test_double_lower", vec![], vec![x]);
g.set_outputs(vec![y]);
let prims = &[OpKind::Input, OpKind::Binary];
assert!(legalize_for_backend(&g, prims).is_err());
let lowered = rewrite_for_backend(g, prims);
assert!(
legalize_for_backend(&lowered, prims).is_ok(),
"custom op should legalize after lowering"
);
assert!(
!lowered
.nodes()
.iter()
.any(|n| matches!(n.op, Op::Custom { .. })),
"the Op::Custom node must be gone"
);
assert!(
lowered
.nodes()
.iter()
.any(|n| matches!(n.op, Op::Binary(BinaryOp::Add))),
"expected the decomposed Add primitive"
);
}
#[test]
fn rewrite_lowers_sce_for_cuda_primitives() {
let f = DType::F32;
let mut g = Graph::new("sce");
let logits = g.input("logits", Shape::new(&[4, 4], f));
let labels = g.input("labels", Shape::new(&[4], f));
let loss = g.softmax_cross_entropy_with_logits(logits, labels);
g.set_outputs(vec![loss]);
let cuda_like = &[
OpKind::Input,
OpKind::Constant,
OpKind::Reduce,
OpKind::Binary,
OpKind::Expand,
OpKind::Activation,
OpKind::Reshape,
OpKind::Compare,
OpKind::Where,
OpKind::Concat,
OpKind::Softmax,
];
assert!(legalize_for_backend(&g, cuda_like).is_err());
let lowered = rewrite_for_backend(g, cuda_like);
assert!(legalize_for_backend(&lowered, cuda_like).is_ok());
}
#[test]
fn unfuses_fused_matmul_for_minimal_cpu_set() {
let f = DType::F32;
let mut g = Graph::new("fused");
let x = g.input("x", Shape::new(&[2, 8], f));
let w = g.param("w", Shape::new(&[8, 4], f));
let b = g.param("b", Shape::new(&[4], f));
let out = g.fused_matmul_bias_act(x, w, b, None, Shape::new(&[2, 4], f));
g.set_outputs(vec![out]);
let supported = &[
OpKind::Input,
OpKind::Param,
OpKind::MatMul,
OpKind::Binary,
OpKind::Expand,
OpKind::Activation,
];
assert!(legalize_for_backend(&g, supported).is_err());
let rewritten = rewrite_for_backend(g, supported);
assert!(legalize_for_backend(&rewritten, supported).is_ok());
assert!(rewritten.nodes().iter().any(|n| matches!(n.op, Op::MatMul)));
assert!(
rewritten
.nodes()
.iter()
.all(|n| !matches!(n.op, Op::FusedMatMulBiasAct { .. }))
);
}
#[test]
fn rewrite_lowers_group_norm_for_minimal_set() {
let f = DType::F32;
let mut g = Graph::new("gn");
let x = g.input("x", Shape::new(&[1, 4, 2, 2], f));
let gamma = g.param("g", Shape::new(&[4], f));
let beta = g.param("b", Shape::new(&[4], f));
let out = g.add_node(
Op::GroupNorm {
num_groups: 2,
eps: 1e-6,
},
vec![x, gamma, beta],
Shape::new(&[1, 4, 2, 2], f),
);
g.set_outputs(vec![out]);
let supported = &[
OpKind::Input,
OpKind::Param,
OpKind::Constant,
OpKind::Reshape,
OpKind::Reduce,
OpKind::Binary,
OpKind::Expand,
OpKind::Activation,
OpKind::Concat,
];
let rewritten = rewrite_for_backend(g, supported);
assert!(legalize_for_backend(&rewritten, supported).is_ok());
assert!(
!rewritten
.nodes()
.iter()
.any(|n| matches!(n.op, Op::GroupNorm { .. }))
);
}
#[test]
fn rewrite_lowers_relu_backward_for_metal_primitives() {
use rlx_ir::{DType, Graph, Shape};
let f = DType::F32;
let mut g = Graph::new("rb");
let x = g.input("x", Shape::new(&[4], f));
let dy = g.input("dy", Shape::new(&[4], f));
let dx = g.relu_backward(x, dy);
g.set_outputs(vec![dx]);
let metal_supported = &[
OpKind::Input,
OpKind::Constant,
OpKind::Expand,
OpKind::Compare,
OpKind::Where,
OpKind::Binary,
OpKind::Activation,
];
assert!(legalize_for_backend(&g, metal_supported).is_err());
let lowered = rewrite_for_backend(g, metal_supported);
assert!(legalize_for_backend(&lowered, metal_supported).is_ok());
}
#[test]
fn legalize_or_rewrite_returns_graph_on_success() {
let g = {
let f = DType::F32;
let mut g = Graph::new("ok");
let x = g.input("x", Shape::new(&[2], f));
let y = g.input("y", Shape::new(&[2], f));
let s = g.add(x, y);
g.set_outputs(vec![s]);
g
};
let supported = &[OpKind::Input, OpKind::Binary];
let out = legalize_or_rewrite_for_backend(g, supported).expect("legal");
assert_eq!(out.len(), 3);
}
#[test]
fn logical_kernel_lowers_gaussian_splat_when_not_supported() {
use rlx_ir::ops::splat::{GaussianSplatInputs, GaussianSplatRenderParams};
let f = DType::F32;
let mut g = Graph::new("splat");
let n = 2usize;
let positions = g.input("pos", Shape::new(&[n * 3], f));
let scales = g.input("sc", Shape::new(&[n * 3], f));
let rotations = g.input("rot", Shape::new(&[n * 4], f));
let opacities = g.input("op", Shape::new(&[n], f));
let colors = g.input("col", Shape::new(&[n * 3], f));
let sh = g.input("sh", Shape::new(&[n * 3], f));
let meta = g.input("meta", Shape::new(&[23], f));
let out = g.gaussian_splat_render(
GaussianSplatInputs {
positions,
scales,
rotations,
opacities,
colors,
sh_coeffs: sh,
meta,
},
GaussianSplatRenderParams {
width: 4,
height: 4,
..Default::default()
},
);
g.set_outputs(vec![out]);
let primitive = &[
OpKind::Input,
OpKind::Param,
OpKind::Constant,
OpKind::Reshape,
OpKind::Reduce,
OpKind::Binary,
OpKind::Expand,
OpKind::Concat,
];
let rewritten = rewrite_for_backend_with_config(
g,
primitive,
KernelDispatchConfig::new(KernelDispatchPolicy::PreferNative),
);
assert!(legalize_for_backend(&rewritten, primitive).is_ok());
assert!(
!rewritten
.nodes()
.iter()
.any(|n| matches!(n.op, Op::GaussianSplatRender { .. }))
);
}
#[test]
fn logical_kernel_lowers_gaussian_splat_backward_when_not_supported() {
use rlx_ir::ops::splat::{
GaussianSplatBackwardParams, GaussianSplatInputs, GaussianSplatRenderParams,
};
let f = DType::F32;
let mut g = Graph::new("splat_bwd");
let n = 2usize;
let positions = g.input("pos", Shape::new(&[n * 3], f));
let scales = g.input("sc", Shape::new(&[n * 3], f));
let rotations = g.input("rot", Shape::new(&[n * 4], f));
let opacities = g.input("op", Shape::new(&[n], f));
let colors = g.input("col", Shape::new(&[n * 3], f));
let sh = g.input("sh", Shape::new(&[n * 3], f));
let meta = g.input("meta", Shape::new(&[23], f));
let d_loss = g.input("dloss", Shape::new(&[16 * 4], f));
let inputs = GaussianSplatInputs {
positions,
scales,
rotations,
opacities,
colors,
sh_coeffs: sh,
meta,
};
let bwd = GaussianSplatBackwardParams {
render: GaussianSplatRenderParams {
width: 4,
height: 4,
..Default::default()
},
..Default::default()
};
let packed = g.gaussian_splat_render_backward(inputs, d_loss, bwd);
g.set_outputs(vec![packed]);
let primitive = &[
OpKind::Input,
OpKind::Constant,
OpKind::Reshape,
OpKind::Reduce,
OpKind::Binary,
OpKind::Expand,
OpKind::Concat,
OpKind::Narrow,
];
let rewritten = rewrite_for_backend_with_config(
g,
primitive,
KernelDispatchConfig::new(KernelDispatchPolicy::PreferNative),
);
assert!(legalize_for_backend(&rewritten, primitive).is_ok());
assert!(
!rewritten
.nodes()
.iter()
.any(|n| matches!(n.op, Op::GaussianSplatRenderBackward { .. }))
);
}
#[test]
fn force_common_kinds_overrides_full_supported_set() {
use rlx_ir::ops::splat::{GaussianSplatInputs, GaussianSplatRenderParams};
let f = DType::F32;
let mut g = Graph::new("force_common");
let n = 1usize;
let positions = g.input("pos", Shape::new(&[n * 3], f));
let scales = g.input("sc", Shape::new(&[n * 3], f));
let rotations = g.input("rot", Shape::new(&[n * 4], f));
let opacities = g.input("op", Shape::new(&[n], f));
let colors = g.input("col", Shape::new(&[n * 3], f));
let sh = g.input("sh", Shape::new(&[n * 3], f));
let meta = g.input("meta", Shape::new(&[23], f));
let out = g.gaussian_splat_render(
GaussianSplatInputs {
positions,
scales,
rotations,
opacities,
colors,
sh_coeffs: sh,
meta,
},
GaussianSplatRenderParams {
width: 2,
height: 2,
..Default::default()
},
);
g.set_outputs(vec![out]);
let full = &[
OpKind::GaussianSplatRender,
OpKind::Input,
OpKind::Reshape,
OpKind::Reduce,
];
let config = KernelDispatchConfig {
policy: KernelDispatchPolicy::PreferNative,
force_common_kinds: &[OpKind::GaussianSplatRender],
force_native_kinds: &[],
};
let rewritten = rewrite_for_backend_with_config(g, full, config);
assert!(
!rewritten
.nodes()
.iter()
.any(|n| matches!(n.op, Op::GaussianSplatRender { .. }))
);
}
#[test]
fn compile_pipeline_lowers_splat_with_force_common_kinds() {
use crate::compiler::CompilePipeline;
use crate::fusion_pipeline::FusionTarget;
use rlx_ir::logical_kernel::{KernelDispatchConfig, KernelDispatchPolicy};
use rlx_ir::ops::splat::{GaussianSplatInputs, GaussianSplatRenderParams};
use rlx_ir::{Graph, MirModule};
let f = DType::F32;
let mut g = Graph::new("pipe");
let n = 2usize;
let positions = g.input("pos", Shape::new(&[n * 3], f));
let scales = g.input("sc", Shape::new(&[n * 3], f));
let rotations = g.input("rot", Shape::new(&[n * 4], f));
let opacities = g.input("op", Shape::new(&[n], f));
let colors = g.input("col", Shape::new(&[n * 3], f));
let sh = g.input("sh", Shape::new(&[n * 3], f));
let meta = g.input("meta", Shape::new(&[23], f));
let out = g.gaussian_splat_render(
GaussianSplatInputs {
positions,
scales,
rotations,
opacities,
colors,
sh_coeffs: sh,
meta,
},
GaussianSplatRenderParams {
width: 4,
height: 4,
..Default::default()
},
);
g.set_outputs(vec![out]);
let mut pipe = CompilePipeline::new(FusionTarget::Cpu);
pipe.kernel_dispatch = KernelDispatchConfig {
policy: KernelDispatchPolicy::PreferNative,
force_common_kinds: &[OpKind::GaussianSplatRender],
force_native_kinds: &[],
};
let config = KernelDispatchConfig {
policy: KernelDispatchPolicy::PreferNative,
force_common_kinds: &[OpKind::GaussianSplatRender],
force_native_kinds: &[],
};
let lowered = rewrite_for_backend_with_config(g.clone(), &[], config);
assert!(
!lowered
.nodes()
.iter()
.any(|n| matches!(n.op, Op::GaussianSplatRender { .. })),
"empty supported + force_common: {:?}",
lowered
.nodes()
.iter()
.map(|n| format!("{:?}", n.op.kind()))
.collect::<Vec<_>>()
);
let lowered_full = rewrite_for_backend_with_config(
g,
&[
OpKind::GaussianSplatRender,
OpKind::Input,
OpKind::Reshape,
OpKind::Reduce,
],
config,
);
assert!(
!lowered_full
.nodes()
.iter()
.any(|n| matches!(n.op, Op::GaussianSplatRender { .. }))
);
let (mir, _) = pipe.optimize_with_report(MirModule::from_graph(lowered));
assert!(!mir.as_graph().nodes().iter().any(|n| {
matches!(
n.op,
Op::GaussianSplatRender { .. } | Op::GaussianSplatRenderBackward { .. }
)
}));
}
}