lift-opt 0.4.2

LIFT-OPT: Pass manager and optimisation passes — constant folding, DCE, tensor fusion, gate cancellation
Documentation
use lift_core::context::Context;
use lift_core::pass::{AnalysisCache, Pass, PassResult};

/// FlashAttention pass: replaces standard attention with FlashAttention
/// when sequence length > threshold (default 512).
/// Same FLOPs, O(n) memory instead of O(n²).
#[derive(Debug)]
pub struct FlashAttentionPass {
    pub seq_len_threshold: u64,
}

impl Default for FlashAttentionPass {
    fn default() -> Self {
        Self {
            seq_len_threshold: 512,
        }
    }
}

impl Pass for FlashAttentionPass {
    fn name(&self) -> &str {
        "flash-attention"
    }

    fn run(&self, ctx: &mut Context, _cache: &mut AnalysisCache) -> PassResult {
        let mut replaced = 0usize;
        let flash_name = ctx.strings.intern("tensor.flash_attention");

        let op_keys: Vec<_> = ctx.ops.keys().collect();
        let attention_ops: Vec<_> = op_keys
            .into_iter()
            .filter(|&ok| {
                if let Some(op) = ctx.ops.get(ok) {
                    let name = ctx.strings.resolve(op.name);
                    name == "tensor.attention" || name == "tensor.multi_head_attention"
                } else {
                    false
                }
            })
            .collect();

        for op_key in attention_ops {
            // Check if seq_len attribute exceeds threshold
            let should_replace = if let Some(op) = ctx.ops.get(op_key) {
                let seq_len = op
                    .attrs
                    .get_integer("seq_len")
                    .map(|v| v as u64)
                    .unwrap_or(0);

                seq_len > self.seq_len_threshold
            } else {
                false
            };

            if should_replace {
                if let Some(op) = ctx.ops.get_mut(op_key) {
                    op.name = flash_name;
                    let causal = op.attrs.get_bool("causal").unwrap_or(false);
                    op.attrs
                        .set("causal", lift_core::attributes::Attribute::Bool(causal));
                    replaced += 1;
                }
            }
        }

        if replaced > 0 {
            tracing::info!(
                pass = "flash-attention",
                replaced = replaced,
                threshold = self.seq_len_threshold,
                "FlashAttention replacement applied"
            );
            PassResult::Changed
        } else {
            PassResult::Unchanged
        }
    }

    fn invalidates(&self) -> Vec<&str> {
        vec!["analysis"]
    }
}