Model transforms & fusion
the graph rewrite before proving
The algorithm
Before proving, DeepProve runs a graph-rewrite pass over the model — the concrete mechanism behind the "operator fusion" our docs only mentioned in the abstract. Rewrites fold constants into neighbouring layers so an expensive operator becomes a cheaper one, without changing the numbers.
Why it resists a proof
This page also corrects a framing error: DeepProve is not LLM-only. The Layer enum carries CNN/MLP operators (Convolution, Pooling, Activation, Flatten) beside the transformer ops, all behind the same Evaluate/ProveInfo/ProvableOp traits, so the transform, quantization and proving passes are uniform across architectures. LLM models are loaded from GGUF and safetensors, not only ONNX.
Cryptographic toolbox
- ModelTransform trait + apply_transformations driver
- GPT2RMSNorm: LayerNorm → RMSNorm, absorb γ/β into neighbours
- Gemma3Transform: absorb the RMSNorm α scale
- SoftmaxMaskTransform: set the quantized −∞ sentinel
How each scheme proves it
Three named graph rewrites fold arithmetic into neighbours: LayerNorm becomes RMSNorm, RMSNorm's scale is absorbed, and the attention mask's quantized −∞ is fixed up post-quantization.
- GPT2RMSNorm replaces every
LayerNormwith a cheaperRMSNorm. It right-multiplies the preceding matrix by a "mean-subtraction" matrix so every output row already has mean 0, and absorbs $\gamma,\beta$ into the followingEinSum. It pattern-matches the GPT-2 graph shape (and detects the final projection by the magic numbervocab == 50257). - Gemma3Transform absorbs an RMSNorm's $\alpha$ scale into the next layer, inserting a new scaling
EinSumnode when the successor can't absorb it. SoftmaxMaskTransform is a post-quantization fixup that writes the correct quantized $-\infty$ sentinel onto the attention mask feeding a softmax. - Correctness is guarded by trace-equivalence tests: each rewrite runs the model before and after and asserts the inputs to the final logits node match within tolerance; the GPT-2 rewrite is additionally proved end-to-end.
pub trait ModelTransform<N>: Debug
where
N: TensorTypeParam,
{
/// This applies the transformation returning a new model with the changes applied.
fn apply(&self, model: Model<N>) -> Result<Model<N>>;
}
pub fn apply_transformations<N>(
mut model: Model<N>,
transforms: Vec<Box<dyn ModelTransform<N>>>,
) -> Result<Model<N>>
where
N: TensorTypeParam,
{
for transform in transforms {
model = transform.apply(model)?;
}
Ok(model)
}Convolution(Convolution<T>), Activation(Activation<T>), Requant(Requant), Pooling(Pooling), Flatten(Flatten), EinSum(EinSum<T>), LayerNorm(LayerNorm<T>), RMSNorm(RMSNorm<T>), Softmax(Softmax<T>), Add(Add<T>), Reshape(Reshape), Embeddings(Embeddings<T>), Positional(Positional<T>), AttentionMask(AttentionMask<T>), Logits(Logits), Split(SplitLayer), Recombination(RecombinationLayer),