Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3e40ee0
feat(devtools): materialize post-ASAP replacements for dag_export --p…
zzylol Aug 26, 2026
6981f5f
feat(dag-viewer): add Before/After mode for post-ASAP replacement sites
zzylol Aug 26, 2026
3174518
fix(devtools): make DagNode::hash Option<u64> to fix false shared-sub…
zzylol Aug 26, 2026
c1c6b84
fix(devtools): drop the now-redundant AvgToSumOverCountStrategy wiring
zzylol Aug 26, 2026
08b4f7b
fix(devtools): don't splice a trivial keep_pre_asap fallback into pos…
zzylol Aug 26, 2026
60102ef
feat: visualize explicit post-ASAP planner decisions
zzylol Aug 26, 2026
ff9032f
Merge remote-tracking branch 'origin/main' into feat/dag-post-asap-viz
zzylol Aug 26, 2026
358a7d7
fix: generate default DAG example with post-ASAP graphs
zzylol Aug 26, 2026
e1431e8
feat: stream planner pipeline progress from server
zzylol Aug 26, 2026
0327ad8
fix: prevent stale DAG viewer assets
zzylol Aug 26, 2026
0b246d0
fix: simplify post-ASAP node explanations
zzylol Aug 26, 2026
63a77e1
fix: keep pre-ASAP lanes free of strategy metadata
zzylol Aug 26, 2026
f2f12fe
fix: preserve shared inputs in post-ASAP DAG export
zzylol Aug 26, 2026
2c18480
feat: unify viewer around Pre/Post-ASAP workload DAGs
zzylol Aug 26, 2026
d29cb04
fix: show aggregate reductions in DAG nodes
zzylol Aug 26, 2026
216c013
feat: optionally display schemas on DAG edges
zzylol Aug 26, 2026
cd58a10
feat: add summary-sharing q4 demo and planner timings
zzylol Aug 26, 2026
c8c7e8f
feat: reuse larger top-k results and improve schema inspection
zzylol Aug 26, 2026
d46c7f8
feat: add editable input schemas and join CSE demo
zzylol Aug 26, 2026
f40119f
style: make table schemas easier to read
zzylol Aug 26, 2026
d2862a1
style: compact DAG labels and preload node details
zzylol Aug 26, 2026
999abc0
fix(devtools): clean up Pre/Post-ASAP DAG viewer (#284)
zzylol Aug 26, 2026
064f028
fix(devtools): explain empty file URL viewer
zzylol Aug 26, 2026
ade72c2
fix(devtools): serve self-contained viewer root
zzylol Aug 26, 2026
ccb465b
Revert "fix(devtools): serve self-contained viewer root"
zzylol Aug 26, 2026
a9e51fb
Revert "fix(devtools): explain empty file URL viewer"
zzylol Aug 26, 2026
411ecb2
Revert "fix(devtools): clean up Pre/Post-ASAP DAG viewer (#284)"
zzylol Aug 26, 2026
2c7b08e
Revert "Revert "fix(devtools): clean up Pre/Post-ASAP DAG viewer (#28…
zzylol Aug 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion crates/asap-aware-mapping/src/cost_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -676,7 +676,7 @@ mod tests {
SummaryNode {
expr: SummaryExpr::SummaryAgg {
child: std::rc::Rc::new(SummaryNode {
expr: SummaryExpr::KeepPreAsap(Box::new(scan())),
expr: SummaryExpr::KeepPreAsap(Rc::new(scan())),
schema: SummarySchema {
fields: vec![],
time_index: None,
Expand Down Expand Up @@ -868,6 +868,7 @@ mod tests {
let root = Rc::new(scan());
let target = TargetSubDAG::new(&root);
let candidate = ReplacementSubDAG {
strategy: "TestStrategy",
replacement: Replacement::Summary(Rc::new(summary_node(SummaryFamilyType::Plain(
asap_types::pre_asap::DataType::Float64,
)))),
Expand All @@ -891,13 +892,15 @@ mod tests {
let target = TargetSubDAG::new(&root);

let cheap = ReplacementSubDAG {
strategy: "TestStrategy",
replacement: Replacement::Summary(Rc::new(summary_node(
SummaryFamilyType::ExactAggregate(ExactKind::Sum, ExactParams::Sum),
))),
provenance: crate::replacement::ReplacementProvenance::SummaryImplementation,
rationale: "exact accumulator".into(),
};
let pricey = ReplacementSubDAG {
strategy: "TestStrategy",
replacement: Replacement::Summary(Rc::new(summary_node(SummaryFamilyType::StatModel(
asap_types::post_asap::StatModelKind::Parametric,
asap_types::post_asap::StatModelParams::Parametric {
Expand Down Expand Up @@ -936,11 +939,13 @@ mod tests {
let target = TargetSubDAG::with_consumer_count(&target_root, 20);

let share = ReplacementSubDAG {
strategy: "TestStrategy",
replacement: Replacement::Rewrite(Rc::clone(&target_root)),
provenance: crate::replacement::ReplacementProvenance::CseShare,
rationale: "build once and share".into(),
};
let recompute = ReplacementSubDAG {
strategy: "TestStrategy",
replacement: Replacement::Rewrite(Rc::new((*target_root).clone())),
provenance: crate::replacement::ReplacementProvenance::CseRecompute,
rationale: "build independently".into(),
Expand Down
3 changes: 2 additions & 1 deletion crates/asap-aware-mapping/src/explanation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,8 @@ mod tests {
.find(|f| f.kind == ExplanationKind::SketchApproximation)
.expect("expected a sketch finding");
assert_eq!(
sketch.node_hash, expected_hash,
Some(sketch.node_hash),
expected_hash,
"ReplacementExplanation::node_hash must match dag_export's DagNode::hash \
for the same QueryExpr subtree"
);
Expand Down
1 change: 1 addition & 0 deletions crates/asap-aware-mapping/src/grouping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ impl<'a> HydraGroupingStrategy<'a> {

let patched = with_grouping(node, grouping);
Some(ReplacementSubDAG {
strategy: "HydraGroupingStrategy",
replacement: Replacement::Summary(patched),
provenance: crate::replacement::ReplacementProvenance::SummaryImplementation,
rationale: format!(
Expand Down
2 changes: 2 additions & 0 deletions crates/asap-aware-mapping/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ pub mod grouping;
pub mod replacement;
pub mod rewrite;
pub mod rollup;
pub mod topk_reuse;

pub use cost_model::{CostModel, DefaultCostModel};
pub use explanation::{
Expand All @@ -189,3 +190,4 @@ pub use replacement::{
MAX_SEARCH_ITERATIONS,
};
pub use rewrite::AvgToSumOverCountStrategy;
pub use topk_reuse::TopKLimitReuseStrategy;
66 changes: 61 additions & 5 deletions crates/asap-aware-mapping/src/replacement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@ use thiserror::Error;
use crate::cost_model::{CostModel, CseCandidate, DefaultCostModel, ShareDecision};
use crate::grouping::HydraGroupingStrategy;
use crate::rollup::RollupStrategy;
use crate::topk_reuse::TopKLimitReuseStrategy;

/// Errors from the pre-ASAP → post-ASAP replacement/construction path
/// ([`realize_child`] and [`keep_pre_asap`]). Moved here from the former
Expand Down Expand Up @@ -446,6 +447,10 @@ pub enum Replacement {
#[derive(Debug, Clone)]
pub struct ReplacementSubDAG {
pub replacement: Replacement,
/// Name of the [`ReplacementStrategy`] that proposed this candidate.
/// Search fills this from `ReplacementStrategy::name`; consumers must not
/// infer it from the replacement's shape or provenance.
pub strategy: &'static str,
/// Machine-readable origin/role of this alternative. Selection uses this
/// instead of inferring strategy semantics from replacement shape or
/// pointer identity when several strategies contribute to one memo group.
Expand Down Expand Up @@ -476,6 +481,16 @@ pub enum ReplacementProvenance {
/// don't match, so a caller that skips the `matches` check first still gets a
/// safe (merely uninformative) answer instead of a crash.
pub trait ReplacementStrategy {
/// Stable, human-readable strategy name carried into every proposed
/// candidate and ultimately into planner diagnostics/visualizations.
fn name(&self) -> &'static str {
let short = std::any::type_name::<Self>()
.rsplit("::")
.next()
.expect("a Rust type name always has a final segment");
short.split_once('<').map_or(short, |(base, _)| base)
}

/// Does this strategy have any replacement to offer for `target`?
fn matches(&self, target: &TargetSubDAG<'_>) -> bool;

Expand Down Expand Up @@ -1093,6 +1108,7 @@ impl ReplacementStrategy for SketchAlgorithmStrategy<'_> {
let rationale = describe_implementation(intent, &implementation);
let node = construct_summary(target.root, implementation, self.cost_model).ok()?;
Some(ReplacementSubDAG {
strategy: "SketchAlgorithmStrategy",
replacement: Replacement::Summary(node),
provenance: ReplacementProvenance::SummaryImplementation,
rationale,
Expand Down Expand Up @@ -1219,10 +1235,14 @@ pub(crate) fn realize_child(
/// candidate for a target, or a deployment wants to force a node its own
/// runtime can't actually implement — through the same fallback this
/// crate's own dispatch uses, without duplicating the schema-lift logic.
pub fn keep_pre_asap(expr: &QueryExpr) -> Result<Rc<SummaryNode>, ImplementError> {
pub fn keep_pre_asap(expr: &Rc<QueryExpr>) -> Result<Rc<SummaryNode>, ImplementError> {
keep_pre_asap_rc(Rc::clone(expr))
}

fn keep_pre_asap_rc(expr: Rc<QueryExpr>) -> Result<Rc<SummaryNode>, ImplementError> {
let schema = expr.output_schema()?;
Ok(Rc::new(SummaryNode {
expr: SummaryExpr::KeepPreAsap(Box::new(expr.clone())),
expr: SummaryExpr::KeepPreAsap(expr),
schema: lift(&schema),
}))
}
Expand Down Expand Up @@ -1292,7 +1312,7 @@ pub(crate) fn construct_summary(
}
}
}
keep_pre_asap(expr)
keep_pre_asap_rc(Rc::new(expr.clone()))
}

/// Translate an [`Implementation`] into the `(family, needs a
Expand Down Expand Up @@ -1493,6 +1513,7 @@ impl ReplacementStrategy for SharedSubtreeStrategy {
let count = target.consumer_count;
vec![
ReplacementSubDAG {
strategy: "SharedSubtreeStrategy",
// The already-interned `Rc` itself: reusing it verbatim *is*
// "build once and share" — no new node to construct.
replacement: Replacement::Rewrite(Rc::clone(target.root)),
Expand All @@ -1504,6 +1525,7 @@ impl ReplacementStrategy for SharedSubtreeStrategy {
),
},
ReplacementSubDAG {
strategy: "SharedSubtreeStrategy",
// A structurally-identical but freshly-allocated `Rc`: same
// value (`PartialEq`), deliberately *not* the same pointer,
// representing "undo the sharing and recompute independently".
Expand Down Expand Up @@ -2528,6 +2550,14 @@ fn search_cse_workload_with<'s, Id>(
})
.collect();
let rollup_strategy = RollupStrategy::new(&siblings);
let limits: Vec<Rc<QueryExpr>> = order
.iter()
.filter_map(|ptr| {
let node = &nodes[ptr];
matches!(node.as_ref(), QueryExpr::Limit { .. }).then(|| Rc::clone(node))
})
.collect();
let topk_reuse_strategy = TopKLimitReuseStrategy::new(&limits);

let mut groups: HashMap<*const QueryExpr, MemoGroup> = HashMap::new();
for ptr in &order {
Expand Down Expand Up @@ -2565,11 +2595,32 @@ fn search_cse_workload_with<'s, Id>(
let mut proposed = Vec::new();
for strategy in strategies {
if strategy.matches(&target) {
proposed.extend(strategy.replacements(&target));
let name = strategy.name();
proposed.extend(strategy.replacements(&target).into_iter().map(
|mut candidate| {
candidate.strategy = name;
candidate
},
));
}
}
if rollup_strategy.matches(&target) {
proposed.extend(rollup_strategy.replacements(&target));
let name = rollup_strategy.name();
proposed.extend(rollup_strategy.replacements(&target).into_iter().map(
|mut candidate| {
candidate.strategy = name;
candidate
},
));
}
if topk_reuse_strategy.matches(&target) {
let name = topk_reuse_strategy.name();
proposed.extend(topk_reuse_strategy.replacements(&target).into_iter().map(
|mut candidate| {
candidate.strategy = name;
candidate
},
));
}

for candidate in &proposed {
Expand Down Expand Up @@ -4314,16 +4365,19 @@ mod tests {
let mut group = MemoGroup::new(Rc::clone(&target), 2);
group.candidates = vec![
ReplacementSubDAG {
strategy: "TestStrategy",
replacement: Replacement::Rewrite(Rc::clone(&target)),
provenance: ReplacementProvenance::CseShare,
rationale: "share".into(),
},
ReplacementSubDAG {
strategy: "TestStrategy",
replacement: Replacement::Rewrite(Rc::new(target.as_ref().clone())),
provenance: ReplacementProvenance::CseRecompute,
rationale: "recompute".into(),
},
ReplacementSubDAG {
strategy: "TestStrategy",
replacement: Replacement::Rewrite(Rc::new(QueryExpr::CurrentTimestamp)),
provenance: ReplacementProvenance::LogicalRewrite,
rationale: "different rewrite strategy".into(),
Expand Down Expand Up @@ -4578,6 +4632,7 @@ mod tests {

fn replacements(&self, _target: &TargetSubDAG<'_>) -> Vec<ReplacementSubDAG> {
vec![ReplacementSubDAG {
strategy: "ReplaceFilterChild",
replacement: Replacement::Rewrite(Rc::new(QueryExpr::Dedup {
cols: vec![0],
child: Rc::new(metric_scan(&["replacement"])),
Expand Down Expand Up @@ -4799,6 +4854,7 @@ mod tests {
child: Rc::new(fresh_inner_layer),
};
vec![ReplacementSubDAG {
strategy: "AlwaysGrowingStrategy",
replacement: Replacement::Rewrite(Rc::new(outer_wrapper)),
provenance: ReplacementProvenance::LogicalRewrite,
rationale: format!("pathological candidate #{n}"),
Expand Down
1 change: 1 addition & 0 deletions crates/asap-aware-mapping/src/rewrite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ impl ReplacementStrategy for AvgToSumOverCountStrategy {
return Vec::new();
};
vec![ReplacementSubDAG {
strategy: "AvgToSumOverCountStrategy",
replacement: Replacement::Rewrite(rewritten),
provenance: crate::replacement::ReplacementProvenance::LogicalRewrite,
rationale:
Expand Down
1 change: 1 addition & 0 deletions crates/asap-aware-mapping/src/rollup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,7 @@ fn build_rollup(
};

Some(ReplacementSubDAG {
strategy: "RollupStrategy",
replacement: Replacement::Rewrite(Rc::new(rewritten)),
provenance: crate::replacement::ReplacementProvenance::LogicalRewrite,
rationale: format!(
Expand Down
Loading
Loading