Skip to main content

memra_gguf/
execution_manifest.rs

1//! Static operation-level manifests for tuned execution programs.
2//!
3//! These tables describe implemented kernel programs, not model families. A new model can use a
4//! program when every operation in its compiled `ModelPlan` is present. Missing operations fail
5//! closed and remain visible as capability blockers.
6
7use crate::model_plan::{ModelPlan, OperationKind, OperationSupport, PlanCapabilities};
8use sha2::{Digest, Sha256};
9use std::collections::BTreeSet;
10use std::fmt::Write as _;
11use std::path::Path;
12
13#[derive(Clone, Copy)]
14pub struct KernelManifest {
15    pub name: &'static str,
16    support: fn(OperationKind) -> OperationSupport,
17}
18
19impl KernelManifest {
20    pub const fn new(name: &'static str, support: fn(OperationKind) -> OperationSupport) -> Self {
21        Self { name, support }
22    }
23
24    pub fn support(self, operation: OperationKind) -> OperationSupport {
25        (self.support)(operation)
26    }
27
28    pub fn capabilities(self, plan: &ModelPlan) -> PlanCapabilities {
29        plan.derive_capabilities(|operation| self.support(operation))
30    }
31
32    pub fn trunk_capabilities(self, plan: &ModelPlan) -> PlanCapabilities {
33        plan.derive_trunk_capabilities(|operation| self.support(operation))
34    }
35
36    pub fn multimodal_prefill_capabilities(self, plan: &ModelPlan) -> PlanCapabilities {
37        plan.derive_multimodal_prefill_capabilities(|operation| self.support(operation))
38    }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
42pub enum RewriteSurface {
43    CarriedPrime,
44    DecodeEager,
45    DecodeBatch,
46    DecodeGraph,
47    MtpSpec,
48    Pipeline,
49}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct RewriteQualifications {
53    pub plan_sha256: String,
54    passed: BTreeSet<RewriteSurface>,
55}
56
57impl RewriteQualifications {
58    pub fn load(bundle: &Path, plan: &ModelPlan) -> Result<Self, String> {
59        let expected = execution_rewrites(plan);
60        let artifact_lock = std::fs::read(bundle.join("artifact.lock"))
61            .map_err(|error| format!("read artifact.lock: {error}"))?;
62        let artifact_lock_sha256 = hex_sha256(&artifact_lock);
63        let index = std::fs::read_to_string(bundle.join("rewrite-receipts.tsv"))
64            .map_err(|error| format!("read rewrite receipt index: {error}"))?;
65        let mut passed = BTreeSet::new();
66        for line in index.lines().skip(1) {
67            let columns: Vec<_> = line.split('\t').collect();
68            if columns.len() != 4 || columns[3] != "passed" {
69                return Err(format!("malformed rewrite receipt index row {line:?}"));
70            }
71            let rewrite = expected
72                .iter()
73                .find(|rewrite| rewrite.id == columns[0])
74                .ok_or_else(|| format!("receipt names unknown rewrite {}", columns[0]))?;
75            if !rewrite.eligible() || columns[1] != rewrite.plan_sha256 {
76                return Err(format!(
77                    "receipt {} is not eligible for plan {}",
78                    rewrite.id, rewrite.plan_sha256
79                ));
80            }
81            let receipt_path = bundle
82                .join("rewrite-receipts")
83                .join(format!("{}.tsv", rewrite.id));
84            let receipt = std::fs::read(&receipt_path)
85                .map_err(|error| format!("read {}: {error}", receipt_path.display()))?;
86            if hex_sha256(&receipt) != columns[2] {
87                return Err(format!("rewrite receipt hash mismatch for {}", rewrite.id));
88            }
89            let text = std::str::from_utf8(&receipt)
90                .map_err(|error| format!("rewrite receipt is not UTF-8: {error}"))?;
91            for (key, value) in [
92                ("status", "passed"),
93                ("rewrite", rewrite.id),
94                ("surface", rewrite.surface.as_str()),
95                ("implementation", rewrite.implementation),
96                ("plan_sha256", rewrite.plan_sha256.as_str()),
97                ("artifact_lock_sha256", artifact_lock_sha256.as_str()),
98                ("first_violation", "none"),
99            ] {
100                if !text.lines().any(|line| line == format!("{key}\t{value}")) {
101                    return Err(format!(
102                        "rewrite receipt {} does not bind {key}={value}",
103                        rewrite.id
104                    ));
105                }
106            }
107            passed.insert(rewrite.surface);
108        }
109        Ok(Self {
110            plan_sha256: plan_sha256(plan),
111            passed,
112        })
113    }
114
115    pub fn allows(&self, surface: RewriteSurface) -> bool {
116        self.passed.contains(&surface)
117    }
118
119    pub fn all_eligible(&self, plan: &ModelPlan) -> bool {
120        execution_rewrites(plan)
121            .into_iter()
122            .filter(ExecutionRewrite::eligible)
123            .all(|rewrite| self.allows(rewrite.surface))
124    }
125}
126
127impl RewriteSurface {
128    pub const fn as_str(self) -> &'static str {
129        match self {
130            Self::CarriedPrime => "carried-prime",
131            Self::DecodeEager => "decode-eager",
132            Self::DecodeBatch => "decode-batch",
133            Self::DecodeGraph => "decode-graph",
134            Self::MtpSpec => "mtp-spec",
135            Self::Pipeline => "pipeline",
136        }
137    }
138}
139
140#[derive(Debug, Clone, PartialEq, Eq)]
141pub struct ExecutionRewrite {
142    pub id: &'static str,
143    pub surface: RewriteSurface,
144    pub implementation: &'static str,
145    pub plan_sha256: String,
146    pub canonical_operations: Vec<OperationKind>,
147    pub blockers: Vec<OperationKind>,
148}
149
150impl ExecutionRewrite {
151    pub fn eligible(&self) -> bool {
152        self.blockers.is_empty()
153    }
154
155    pub fn verify_logits(
156        &self,
157        implementation_sha256: &str,
158        reference: &[f32],
159        candidate: &[f32],
160        policy: RewriteParityPolicy,
161    ) -> Result<RewriteParityReceipt, String> {
162        if !self.eligible() {
163            return Err(format!(
164                "rewrite {} is blocked by {:?}",
165                self.id, self.blockers
166            ));
167        }
168        if !is_sha256(implementation_sha256) {
169            return Err("rewrite implementation identity must be a lowercase SHA-256".into());
170        }
171        if reference.len() != candidate.len() || reference.is_empty() {
172            return Err(format!(
173                "rewrite parity requires equal non-empty streams (reference={} candidate={})",
174                reference.len(),
175                candidate.len()
176            ));
177        }
178        let mut max_abs = 0.0f32;
179        let mut max_rel = 0.0f32;
180        let mut first_violation = None;
181        for (index, (&expected, &actual)) in reference.iter().zip(candidate).enumerate() {
182            if !expected.is_finite() || !actual.is_finite() {
183                return Err(format!("rewrite parity has a non-finite value at {index}"));
184            }
185            let absolute = (expected - actual).abs();
186            let relative = absolute / expected.abs().max(1e-6);
187            max_abs = max_abs.max(absolute);
188            max_rel = max_rel.max(relative);
189            let allowed = policy.max_abs + policy.max_rel * expected.abs();
190            if absolute > allowed && first_violation.is_none() {
191                first_violation = Some(index);
192            }
193        }
194        let reference_argmax = stable_argmax(reference);
195        let candidate_argmax = stable_argmax(candidate);
196        let passed = first_violation.is_none()
197            && (!policy.require_argmax || reference_argmax == candidate_argmax);
198        Ok(RewriteParityReceipt {
199            rewrite_id: self.id,
200            surface: self.surface,
201            implementation: self.implementation,
202            implementation_sha256: implementation_sha256.to_string(),
203            plan_sha256: self.plan_sha256.clone(),
204            artifact_lock_sha256: None,
205            reference_sha256: f32_stream_sha256(reference),
206            candidate_sha256: f32_stream_sha256(candidate),
207            value_kind: RewriteValueKind::LogitsF32,
208            values: reference.len(),
209            max_abs,
210            max_rel,
211            reference_argmax,
212            candidate_argmax,
213            policy,
214            passed,
215            first_violation,
216        })
217    }
218
219    pub fn verify_tokens(
220        &self,
221        implementation_sha256: &str,
222        reference: &[u32],
223        candidate: &[u32],
224    ) -> Result<RewriteParityReceipt, String> {
225        if !self.eligible() {
226            return Err(format!(
227                "rewrite {} is blocked by {:?}",
228                self.id, self.blockers
229            ));
230        }
231        if !is_sha256(implementation_sha256) {
232            return Err("rewrite implementation identity must be a lowercase SHA-256".into());
233        }
234        if reference.len() != candidate.len() || reference.is_empty() {
235            return Err(format!(
236                "rewrite parity requires equal non-empty token streams (reference={} candidate={})",
237                reference.len(),
238                candidate.len()
239            ));
240        }
241        let first_violation = reference
242            .iter()
243            .zip(candidate)
244            .position(|(expected, actual)| expected != actual);
245        let max_abs = reference
246            .iter()
247            .zip(candidate)
248            .map(|(&expected, &actual)| expected.abs_diff(actual) as f32)
249            .fold(0.0f32, f32::max);
250        Ok(RewriteParityReceipt {
251            rewrite_id: self.id,
252            surface: self.surface,
253            implementation: self.implementation,
254            implementation_sha256: implementation_sha256.to_string(),
255            plan_sha256: self.plan_sha256.clone(),
256            artifact_lock_sha256: None,
257            reference_sha256: u32_stream_sha256(reference),
258            candidate_sha256: u32_stream_sha256(candidate),
259            value_kind: RewriteValueKind::TokenIdsU32,
260            values: reference.len(),
261            max_abs,
262            max_rel: 0.0,
263            reference_argmax: 0,
264            candidate_argmax: 0,
265            policy: RewriteParityPolicy {
266                max_abs: 0.0,
267                max_rel: 0.0,
268                require_argmax: false,
269            },
270            passed: first_violation.is_none(),
271            first_violation,
272        })
273    }
274}
275
276#[derive(Debug, Clone, Copy, PartialEq)]
277pub struct RewriteParityPolicy {
278    pub max_abs: f32,
279    pub max_rel: f32,
280    pub require_argmax: bool,
281}
282
283#[derive(Debug, Clone, PartialEq)]
284pub struct RewriteParityReceipt {
285    pub rewrite_id: &'static str,
286    pub surface: RewriteSurface,
287    pub implementation: &'static str,
288    pub implementation_sha256: String,
289    pub plan_sha256: String,
290    pub artifact_lock_sha256: Option<String>,
291    pub reference_sha256: String,
292    pub candidate_sha256: String,
293    pub value_kind: RewriteValueKind,
294    pub values: usize,
295    pub max_abs: f32,
296    pub max_rel: f32,
297    pub reference_argmax: usize,
298    pub candidate_argmax: usize,
299    pub policy: RewriteParityPolicy,
300    pub passed: bool,
301    pub first_violation: Option<usize>,
302}
303
304#[derive(Debug, Clone, Copy, PartialEq, Eq)]
305pub enum RewriteValueKind {
306    LogitsF32,
307    TokenIdsU32,
308}
309
310impl RewriteValueKind {
311    pub const fn as_str(self) -> &'static str {
312        match self {
313            Self::LogitsF32 => "logits-f32",
314            Self::TokenIdsU32 => "token-ids-u32",
315        }
316    }
317}
318
319impl RewriteParityReceipt {
320    pub fn bind_artifact_lock(mut self, artifact_lock: &[u8]) -> Self {
321        self.artifact_lock_sha256 = Some(hex_sha256(artifact_lock));
322        self
323    }
324
325    pub fn validate_for(&self, rewrite: &ExecutionRewrite) -> Result<(), String> {
326        if self.rewrite_id != rewrite.id
327            || self.surface != rewrite.surface
328            || self.implementation != rewrite.implementation
329            || self.plan_sha256 != rewrite.plan_sha256
330        {
331            return Err(format!(
332                "rewrite receipt identity does not match {} for plan {}",
333                rewrite.id, rewrite.plan_sha256
334            ));
335        }
336        if !rewrite.eligible() {
337            return Err(format!("rewrite {} is no longer eligible", rewrite.id));
338        }
339        if !self.passed {
340            return Err(format!("rewrite {} parity receipt failed", rewrite.id));
341        }
342        Ok(())
343    }
344
345    pub fn to_tsv(&self) -> String {
346        let mut output = String::new();
347        writeln!(output, "format\tmemra-rewrite-parity-v1").unwrap();
348        writeln!(
349            output,
350            "status\t{}",
351            if self.passed { "passed" } else { "failed" }
352        )
353        .unwrap();
354        writeln!(output, "rewrite\t{}", self.rewrite_id).unwrap();
355        writeln!(output, "surface\t{}", self.surface.as_str()).unwrap();
356        writeln!(output, "implementation\t{}", self.implementation).unwrap();
357        writeln!(
358            output,
359            "implementation_sha256\t{}",
360            self.implementation_sha256
361        )
362        .unwrap();
363        writeln!(output, "plan_sha256\t{}", self.plan_sha256).unwrap();
364        if let Some(hash) = self.artifact_lock_sha256.as_ref() {
365            writeln!(output, "artifact_lock_sha256\t{hash}").unwrap();
366        }
367        writeln!(output, "reference_sha256\t{}", self.reference_sha256).unwrap();
368        writeln!(output, "candidate_sha256\t{}", self.candidate_sha256).unwrap();
369        writeln!(output, "value_kind\t{}", self.value_kind.as_str()).unwrap();
370        writeln!(output, "values\t{}", self.values).unwrap();
371        writeln!(output, "max_abs\t{}", self.max_abs).unwrap();
372        writeln!(output, "max_rel\t{}", self.max_rel).unwrap();
373        writeln!(output, "reference_argmax\t{}", self.reference_argmax).unwrap();
374        writeln!(output, "candidate_argmax\t{}", self.candidate_argmax).unwrap();
375        writeln!(output, "atol\t{}", self.policy.max_abs).unwrap();
376        writeln!(output, "rtol\t{}", self.policy.max_rel).unwrap();
377        writeln!(output, "require_argmax\t{}", self.policy.require_argmax).unwrap();
378        writeln!(
379            output,
380            "first_violation\t{}",
381            self.first_violation
382                .map_or_else(|| "none".to_string(), |index| index.to_string())
383        )
384        .unwrap();
385        output
386    }
387}
388
389pub fn execution_rewrites(plan: &ModelPlan) -> Vec<ExecutionRewrite> {
390    let plan_sha256 = plan_sha256(plan);
391    let trunk = plan.trunk_operations();
392    let mut spec_operations = plan
393        .draft_operations()
394        .unwrap_or_else(|| vec![OperationKind::DraftPlan]);
395    spec_operations.extend(plan.trunk_operations());
396    let selections = [
397        (
398            "carried-prime.v1",
399            RewriteSurface::CarriedPrime,
400            CARRIED_PRIME,
401            trunk.clone(),
402            CARRIED_PRIME.trunk_capabilities(plan).batch,
403        ),
404        (
405            "decode-eager.v1",
406            RewriteSurface::DecodeEager,
407            NATIVE_EAGER,
408            trunk.clone(),
409            NATIVE_EAGER.trunk_capabilities(plan).batch,
410        ),
411        (
412            "decode-batch.v1",
413            RewriteSurface::DecodeBatch,
414            DECODE_BATCH,
415            trunk.clone(),
416            DECODE_BATCH.trunk_capabilities(plan).batch,
417        ),
418        (
419            "decode-graph.v1",
420            RewriteSurface::DecodeGraph,
421            DECODE_GRAPH,
422            trunk.clone(),
423            DECODE_GRAPH.trunk_capabilities(plan).cuda_graph,
424        ),
425        (
426            "mtp-spec.v1",
427            RewriteSurface::MtpSpec,
428            MTP_SPEC,
429            spec_operations,
430            MTP_SPEC.capabilities(plan).speculative,
431        ),
432        (
433            "pipeline.v1",
434            RewriteSurface::Pipeline,
435            PIPELINE,
436            trunk,
437            PIPELINE.capabilities(plan).pipeline,
438        ),
439    ];
440    selections
441        .into_iter()
442        .map(
443            |(id, surface, manifest, canonical_operations, capability)| ExecutionRewrite {
444                id,
445                surface,
446                implementation: manifest.name,
447                plan_sha256: plan_sha256.clone(),
448                canonical_operations,
449                blockers: capability.blockers,
450            },
451        )
452        .collect()
453}
454
455fn plan_sha256(plan: &ModelPlan) -> String {
456    hex_sha256(format!("{plan:#?}\n").as_bytes())
457}
458
459fn f32_stream_sha256(values: &[f32]) -> String {
460    let mut bytes = Vec::with_capacity(values.len() * 4);
461    for value in values {
462        bytes.extend_from_slice(&value.to_bits().to_le_bytes());
463    }
464    hex_sha256(&bytes)
465}
466
467fn u32_stream_sha256(values: &[u32]) -> String {
468    let mut bytes = Vec::with_capacity(values.len() * 4);
469    for value in values {
470        bytes.extend_from_slice(&value.to_le_bytes());
471    }
472    hex_sha256(&bytes)
473}
474
475fn hex_sha256(bytes: &[u8]) -> String {
476    let digest = Sha256::digest(bytes);
477    digest.iter().map(|byte| format!("{byte:02x}")).collect()
478}
479
480fn is_sha256(value: &str) -> bool {
481    value.len() == 64
482        && value
483            .bytes()
484            .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
485}
486
487fn stable_argmax(values: &[f32]) -> usize {
488    values
489        .iter()
490        .enumerate()
491        .max_by(|(left_index, left), (right_index, right)| {
492            left.total_cmp(right)
493                .then_with(|| right_index.cmp(left_index))
494        })
495        .map(|(index, _)| index)
496        .unwrap_or(0)
497}
498
499fn carried_prime_support(operation: OperationKind) -> OperationSupport {
500    let mut support = OperationSupport::none();
501    support.batch = matches!(
502        operation,
503        OperationKind::Embedding
504            | OperationKind::RmsNorm
505            | OperationKind::FullAttention
506            | OperationKind::GatedDeltaNet
507            | OperationKind::FusedAttentionGate
508            | OperationKind::DenseMlp
509            | OperationKind::SiluActivation
510            | OperationKind::SerialResidual
511            | OperationKind::KvState
512            | OperationKind::RecurrentState
513            | OperationKind::LogitsMask
514            | OperationKind::OutputProjection
515    );
516    support
517}
518
519pub const CARRIED_PRIME: KernelManifest =
520    KernelManifest::new("carried-prime-batch", carried_prime_support);
521
522#[derive(Debug, Clone, Copy, PartialEq, Eq)]
523pub enum DecodeBatchProgram {
524    Generic,
525    SlidingGatedMoe,
526    Gemma,
527}
528
529pub fn decode_batch_program(plan: &ModelPlan) -> DecodeBatchProgram {
530    let operations = plan.trunk_operations();
531    if operations.contains(&OperationKind::GemmaResidual)
532        || operations.contains(&OperationKind::GemmaParallelMoeResidual)
533    {
534        DecodeBatchProgram::Gemma
535    } else if operations.contains(&OperationKind::SlidingWindowAttention)
536        && operations.contains(&OperationKind::SeparateAttentionGate)
537        && operations.contains(&OperationKind::MoeMlp)
538        && operations.contains(&OperationKind::SigmoidRouter)
539    {
540        DecodeBatchProgram::SlidingGatedMoe
541    } else {
542        DecodeBatchProgram::Generic
543    }
544}
545
546pub fn gdn_dspark_compatible(plan: &ModelPlan) -> bool {
547    let operations = plan.trunk_operations();
548    operations.contains(&OperationKind::GatedDeltaNet)
549        && operations.contains(&OperationKind::FusedAttentionGate)
550}
551
552fn decode_graph_support(operation: OperationKind) -> OperationSupport {
553    let mut support = OperationSupport::none();
554    support.cuda_graph = matches!(
555        operation,
556        OperationKind::Embedding
557            | OperationKind::RmsNorm
558            | OperationKind::FullAttention
559            | OperationKind::GatedDeltaNet
560            | OperationKind::FusedAttentionGate
561            | OperationKind::DenseMlp
562            | OperationKind::MoeMlp
563            | OperationKind::SharedMlp
564            | OperationKind::SoftmaxRouter
565            | OperationKind::SigmoidRouter
566            | OperationKind::SiluActivation
567            | OperationKind::SerialResidual
568            | OperationKind::KvState
569            | OperationKind::RecurrentState
570            | OperationKind::LogitsMask
571            | OperationKind::OutputProjection
572    );
573    support
574}
575
576pub const DECODE_GRAPH: KernelManifest =
577    KernelManifest::new("decode-cuda-graph", decode_graph_support);
578
579fn pipeline_support(operation: OperationKind) -> OperationSupport {
580    let mut support = OperationSupport::none();
581    support.pipeline = matches!(
582        operation,
583        OperationKind::Embedding
584            | OperationKind::RmsNorm
585            | OperationKind::FullAttention
586            | OperationKind::SlidingWindowAttention
587            | OperationKind::SeparateAttentionGate
588            | OperationKind::DenseMlp
589            | OperationKind::MoeMlp
590            | OperationKind::SharedMlp
591            | OperationKind::SigmoidRouter
592            | OperationKind::SiluActivation
593            | OperationKind::SwiGluClampedActivation
594            | OperationKind::SerialResidual
595            | OperationKind::KvState
596            | OperationKind::SlidingKvState
597            | OperationKind::Mtp
598            | OperationKind::MtpFusion
599            | OperationKind::MtpHead
600            | OperationKind::LogitsMask
601            | OperationKind::OutputProjection
602            | OperationKind::PipelineBoundary
603    );
604    support
605}
606
607pub const PIPELINE: KernelManifest =
608    KernelManifest::new("pipeline-state-transport", pipeline_support);
609
610fn decode_batch_support(operation: OperationKind) -> OperationSupport {
611    let mut support = OperationSupport::none();
612    support.batch = matches!(
613        operation,
614        OperationKind::Embedding
615            | OperationKind::RmsNorm
616            | OperationKind::FullAttention
617            | OperationKind::SlidingWindowAttention
618            | OperationKind::GatedDeltaNet
619            | OperationKind::FusedAttentionGate
620            | OperationKind::SeparateAttentionGate
621            | OperationKind::DenseMlp
622            | OperationKind::MoeMlp
623            | OperationKind::SharedMlp
624            | OperationKind::SoftmaxRouter
625            | OperationKind::SigmoidRouter
626            | OperationKind::SiluActivation
627            | OperationKind::GeluTanhActivation
628            | OperationKind::SwiGluClampedActivation
629            | OperationKind::SerialResidual
630            | OperationKind::GemmaResidual
631            | OperationKind::GemmaParallelMoeResidual
632            | OperationKind::KvState
633            | OperationKind::SlidingKvState
634            | OperationKind::RecurrentState
635            | OperationKind::LogitsSoftcap
636            | OperationKind::LogitsMask
637            | OperationKind::OutputProjection
638    );
639    support
640}
641
642pub const DECODE_BATCH: KernelManifest = KernelManifest::new("decode-batch", decode_batch_support);
643
644fn native_eager_support(operation: OperationKind) -> OperationSupport {
645    let mut support = OperationSupport::none();
646    support.batch = matches!(
647        operation,
648        OperationKind::Embedding
649            | OperationKind::RmsNorm
650            | OperationKind::FullAttention
651            | OperationKind::DenseMlp
652            | OperationKind::SiluActivation
653            | OperationKind::SerialResidual
654            | OperationKind::KvState
655            | OperationKind::LogitsSoftcap
656            | OperationKind::LogitsMask
657            | OperationKind::OutputProjection
658    );
659    support
660}
661
662pub const NATIVE_EAGER: KernelManifest = KernelManifest::new("native-eager", native_eager_support);
663
664fn mtp_spec_support(operation: OperationKind) -> OperationSupport {
665    let mut support = OperationSupport::none();
666    let common = matches!(
667        operation,
668        OperationKind::Embedding
669            | OperationKind::RmsNorm
670            | OperationKind::FullAttention
671            | OperationKind::SlidingWindowAttention
672            | OperationKind::GatedDeltaNet
673            | OperationKind::FusedAttentionGate
674            | OperationKind::SeparateAttentionGate
675            | OperationKind::DenseMlp
676            | OperationKind::MoeMlp
677            | OperationKind::SharedMlp
678            | OperationKind::SoftmaxRouter
679            | OperationKind::SigmoidRouter
680            | OperationKind::SiluActivation
681            | OperationKind::SwiGluClampedActivation
682            | OperationKind::SerialResidual
683            | OperationKind::KvState
684            | OperationKind::SlidingKvState
685            | OperationKind::RecurrentState
686            | OperationKind::LogitsMask
687            | OperationKind::OutputProjection
688    );
689    support.spec_draft = common
690        || matches!(
691            operation,
692            OperationKind::Mtp | OperationKind::MtpFusion | OperationKind::MtpHead
693        );
694    support.spec_verify = common;
695    support
696}
697
698pub const MTP_SPEC: KernelManifest = KernelManifest::new("mtp-spec", mtp_spec_support);
699
700#[cfg(test)]
701mod tests {
702    use super::*;
703    use crate::config::{HfConfig, ModelConfig};
704
705    fn plan(json: &str) -> ModelPlan {
706        ModelPlan::compile(&ModelConfig::from_hf(&HfConfig::parse(json))).unwrap()
707    }
708
709    #[test]
710    fn manifest_reports_operation_blockers_instead_of_model_names() {
711        let dense = plan(
712            r#"{"model_type":"qwen3","num_hidden_layers":2,"hidden_size":64,
713            "num_attention_heads":2,"num_key_value_heads":1,"head_dim":32,
714            "intermediate_size":128,"vocab_size":16,"max_position_embeddings":128}"#,
715        );
716        assert!(CARRIED_PRIME.trunk_capabilities(&dense).batch.supported);
717        assert!(DECODE_BATCH.trunk_capabilities(&dense).batch.supported);
718        assert!(DECODE_GRAPH.trunk_capabilities(&dense).cuda_graph.supported);
719
720        let gemma = plan(
721            r#"{"model_type":"gemma4","num_hidden_layers":2,"hidden_size":64,
722            "num_attention_heads":2,"num_key_value_heads":1,"head_dim":32,
723            "global_head_dim":32,"intermediate_size":128,"vocab_size":16,
724            "max_position_embeddings":128,"sliding_window":64,
725            "layer_types":["sliding_attention","full_attention"],
726            "rope_parameters":{"full_attention":{"rope_theta":10000},
727            "sliding_attention":{"rope_theta":10000}}}"#,
728        );
729        let capability = CARRIED_PRIME.trunk_capabilities(&gemma).batch;
730        assert!(!capability.supported);
731        assert!(
732            capability
733                .blockers
734                .contains(&OperationKind::SlidingWindowAttention)
735        );
736        assert!(capability.blockers.contains(&OperationKind::GemmaResidual));
737        assert_eq!(decode_batch_program(&gemma), DecodeBatchProgram::Gemma);
738        assert!(!DECODE_GRAPH.trunk_capabilities(&gemma).cuda_graph.supported);
739
740        let mut sliding_gated_moe = plan(
741            r#"{"model_type":"qwen3_moe","num_hidden_layers":2,"hidden_size":64,
742            "num_attention_heads":2,"num_key_value_heads":1,"head_dim":32,
743            "intermediate_size":128,"vocab_size":16,"max_position_embeddings":128,
744            "num_experts":4,"num_experts_per_tok":2,"moe_intermediate_size":32}"#,
745        );
746        let crate::model_plan::AttentionPlan::Full(mut attention) =
747            sliding_gated_moe.layers[0].attention.clone()
748        else {
749            unreachable!()
750        };
751        attention.output_gate = crate::config::AttentionGateKind::SeparateHead;
752        sliding_gated_moe.layers[0].attention = crate::model_plan::AttentionPlan::SlidingWindow {
753            attention,
754            window: 64,
755        };
756        let crate::model_plan::MlpPlan::Moe(moe) = &mut sliding_gated_moe.layers[0].mlp else {
757            unreachable!()
758        };
759        moe.router = crate::model_plan::RouterPlan::Sigmoid {
760            normalize_selected: true,
761            scaling_factor: 1.0,
762            selection_bias: false,
763        };
764        assert_eq!(
765            decode_batch_program(&sliding_gated_moe),
766            DecodeBatchProgram::SlidingGatedMoe
767        );
768        assert!(
769            !DECODE_GRAPH
770                .trunk_capabilities(&sliding_gated_moe)
771                .cuda_graph
772                .supported
773        );
774        assert_eq!(decode_batch_program(&dense), DecodeBatchProgram::Generic);
775        assert_eq!(
776            MTP_SPEC.capabilities(&dense).speculative.blockers,
777            vec![OperationKind::DraftPlan]
778        );
779
780        let qwen35 = crate::model_packs::by_alias("qwen35")
781            .unwrap()
782            .compile_tiny_plan()
783            .unwrap();
784        assert!(MTP_SPEC.capabilities(&qwen35).speculative.supported);
785
786        let dsv4 = crate::model_packs::by_alias("deepseek_v4_dspark")
787            .unwrap()
788            .compile_tiny_plan()
789            .unwrap();
790        let dsv4_batch = DECODE_BATCH.trunk_capabilities(&dsv4).batch;
791        assert!(!dsv4_batch.supported);
792        assert!(
793            dsv4_batch
794                .blockers
795                .contains(&OperationKind::CompressedMlaAttention)
796        );
797        assert!(!MTP_SPEC.capabilities(&dsv4).speculative.supported);
798
799        let root = std::env::temp_dir().join(format!(
800            "memra-plan-backend-external-draft-{}",
801            std::process::id()
802        ));
803        std::fs::create_dir_all(&root).unwrap();
804        let trunk_path = root.join("trunk.gguf");
805        let draft_path = root.join("draft.gguf");
806        crate::micro_gguf::write_step35_meta_only(&trunk_path).unwrap();
807        crate::micro_gguf::write_step35_mtp_meta_only(&draft_path).unwrap();
808        let trunk_cfg = ModelConfig::from_gguf(&crate::GgufFile::open(&trunk_path).unwrap());
809        let draft_cfg = ModelConfig::from_gguf(&crate::GgufFile::open(&draft_path).unwrap());
810        let mut external = crate::model_packs::for_config(&trunk_cfg)
811            .unwrap()
812            .compile_plan(&trunk_cfg)
813            .unwrap();
814        let draft = crate::model_packs::for_config(&draft_cfg)
815            .unwrap()
816            .compile_plan(&draft_cfg)
817            .unwrap();
818        assert_eq!(
819            external.draft_source,
820            crate::model_plan::DraftSourcePlan::ExternalArtifact
821        );
822        external.attach_external_draft(&draft).unwrap();
823        assert!(MTP_SPEC.capabilities(&external).speculative.supported);
824        std::fs::remove_dir_all(root).unwrap();
825    }
826
827    #[test]
828    fn rewrite_receipts_bind_plan_program_binary_and_outputs() {
829        let dense = plan(
830            r#"{"model_type":"qwen3","num_hidden_layers":2,"hidden_size":64,
831            "num_attention_heads":2,"num_key_value_heads":1,"head_dim":32,
832            "intermediate_size":128,"vocab_size":16,"max_position_embeddings":128}"#,
833        );
834        let rewrites = execution_rewrites(&dense);
835        let batch = rewrites
836            .iter()
837            .find(|rewrite| rewrite.surface == RewriteSurface::DecodeBatch)
838            .unwrap();
839        assert!(batch.eligible());
840        let spec = rewrites
841            .iter()
842            .find(|rewrite| rewrite.surface == RewriteSurface::MtpSpec)
843            .unwrap();
844        assert_eq!(spec.blockers, vec![OperationKind::DraftPlan]);
845
846        let policy = RewriteParityPolicy {
847            max_abs: 0.01,
848            max_rel: 0.01,
849            require_argmax: true,
850        };
851        let implementation = "00".repeat(32);
852        let artifact_lock = b"format_version=2\nfamily=qwen3\n";
853        let receipt = batch
854            .verify_logits(
855                &implementation,
856                &[0.0, 1.0, -1.0],
857                &[0.0, 1.001, -1.001],
858                policy,
859            )
860            .unwrap()
861            .bind_artifact_lock(artifact_lock);
862        assert!(receipt.passed);
863        receipt.validate_for(batch).unwrap();
864        let tsv = receipt.to_tsv();
865        assert!(tsv.contains("rewrite\tdecode-batch.v1"));
866        assert!(tsv.contains(&format!("plan_sha256\t{}", batch.plan_sha256)));
867        assert!(tsv.contains(&format!("implementation_sha256\t{implementation}")));
868
869        let root = std::env::temp_dir().join(format!(
870            "memra-rewrite-qualification-{}",
871            std::process::id()
872        ));
873        let receipts = root.join("rewrite-receipts");
874        std::fs::create_dir_all(&receipts).unwrap();
875        std::fs::write(root.join("artifact.lock"), artifact_lock).unwrap();
876        std::fs::write(receipts.join("decode-batch.v1.tsv"), &tsv).unwrap();
877        std::fs::write(
878            root.join("rewrite-receipts.tsv"),
879            format!(
880                "rewrite\tplan_sha256\treceipt_sha256\tstatus\n\
881                 decode-batch.v1\t{}\t{}\tpassed\n",
882                batch.plan_sha256,
883                hex_sha256(tsv.as_bytes())
884            ),
885        )
886        .unwrap();
887        let qualifications = RewriteQualifications::load(&root, &dense).unwrap();
888        assert!(qualifications.allows(RewriteSurface::DecodeBatch));
889        assert!(!qualifications.allows(RewriteSurface::DecodeGraph));
890        std::fs::write(receipts.join("decode-batch.v1.tsv"), "tampered").unwrap();
891        assert!(RewriteQualifications::load(&root, &dense).is_err());
892        std::fs::remove_dir_all(root).unwrap();
893
894        let failed = batch
895            .verify_logits(
896                &implementation,
897                &[0.0, 1.0, -1.0],
898                &[2.0, 1.0, -1.0],
899                policy,
900            )
901            .unwrap();
902        assert!(!failed.passed);
903        assert!(failed.validate_for(batch).is_err());
904
905        let other = plan(
906            r#"{"model_type":"qwen3","num_hidden_layers":3,"hidden_size":64,
907            "num_attention_heads":2,"num_key_value_heads":1,"head_dim":32,
908            "intermediate_size":128,"vocab_size":16,"max_position_embeddings":128}"#,
909        );
910        let other_batch = execution_rewrites(&other)
911            .into_iter()
912            .find(|rewrite| rewrite.surface == RewriteSurface::DecodeBatch)
913            .unwrap();
914        assert!(receipt.validate_for(&other_batch).is_err());
915
916        let qwen35 = crate::model_packs::by_alias("qwen35")
917            .unwrap()
918            .compile_tiny_plan()
919            .unwrap();
920        let qwen35_spec = execution_rewrites(&qwen35)
921            .into_iter()
922            .find(|rewrite| rewrite.surface == RewriteSurface::MtpSpec)
923            .unwrap();
924        assert!(qwen35_spec.eligible());
925    }
926}