Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
4cb33d1
feat: support local systems roots in predict and recommend
Arsene12358 Sep 14, 2026
dcb73f9
feat: add independent guided FPM onboarding workflow
Arsene12358 Sep 14, 2026
3cb22fd
Merge main into guided FPM support and preserve local hardware lookups
Arsene12358 Sep 16, 2026
ecb6ee5
fix: guard guided FPM collection and recover interrupted plans
Arsene12358 Sep 16, 2026
b4af59d
fix: report unexpected FPM collection failures cleanly
Arsene12358 Sep 16, 2026
d6650fd
test: assert local FPM timing for worker overrides
Arsene12358 Sep 16, 2026
29a19ae
fix: preserve installed-wheel imports in recommendation tests
Arsene12358 Sep 16, 2026
dffb80d
feat: name model and hardware FPM onboarding explicitly
Arsene12358 Sep 16, 2026
949c0df
Merge main to align CI and release contracts
Arsene12358 Sep 16, 2026
24486a9
fix: use onboard as the sole model onboarding command
Arsene12358 Sep 16, 2026
396af2d
fix: reject nested symlinks in onboarding collection outputs
Arsene12358 Sep 16, 2026
661fd97
docs: rename onboarding guide to FPM self-service
Arsene12358 Sep 16, 2026
f5a3ac0
docs: align FPM onboarding with planned model decoupling
Arsene12358 Sep 16, 2026
4393596
feat: decouple FPM workflows from analytical model classes
Arsene12358 Sep 16, 2026
081eb51
fix: preserve FPM profile identity across SDK namespaces
Arsene12358 Sep 16, 2026
cdafa00
ci: assign ownership for FPM profile workflow tests
Arsene12358 Sep 16, 2026
1e92af9
Merge canonical estimator APIs into guided FPM onboarding
Arsene12358 Sep 18, 2026
13e4290
Merge canonical estimator API into FPM decoupling workflow
Arsene12358 Sep 18, 2026
fd2d20f
Keep FPM configuration parsing inside the lightweight supervision bou…
Arsene12358 Sep 18, 2026
05bf614
Merge main into guided FPM onboarding
Arsene12358 Sep 19, 2026
8d798b1
Merge updated onboarding foundation into FPM decoupling
Arsene12358 Sep 19, 2026
7f7cd55
Merge main into guided FPM onboarding
Arsene12358 Sep 22, 2026
145aaf3
Merge updated onboarding foundation into FPM decoupling
Arsene12358 Sep 22, 2026
ae44d00
fix: verify profile runtime versions in Slurm collection
Arsene12358 Sep 22, 2026
2e02467
test: permit lightweight FPM metadata before resource admission
Arsene12358 Sep 22, 2026
45fa257
fix: address FPM onboarding review feedback
Arsene12358 Sep 28, 2026
8f31886
fix: address FPM profile review feedback
Arsene12358 Sep 28, 2026
3e7d1d2
fix: complete the detailed FPM API declaration
Arsene12358 Sep 28, 2026
bd96d2f
Merge main into feat/guided-fpm-cli
simone-chen Sep 29, 2026
6236b65
fix: preserve worker data roots in performance metadata
simone-chen Sep 29, 2026
a7c1ccd
docs: fix renamed FPM collection guide link
simone-chen Sep 29, 2026
020e4bb
Merge updated FPM foundation into decoupling
Arsene12358 Sep 30, 2026
ab0587b
Merge main into feat/fpm-model-decoupling
simone-chen Sep 30, 2026
d81b256
ci: assign ownership for FPM query evidence tests
simone-chen Sep 30, 2026
8a4a804
fix: enforce profile decode collection bounds
simone-chen Sep 30, 2026
0774f85
Merge maintainer updates into FPM decoupling integration
Arsene12358 Sep 30, 2026
c164565
fix: preserve FPM options and enforce qualified API boundaries
simone-chen Sep 30, 2026
b6c60fd
Merge decode collection bounds into FPM decoupling integration
Arsene12358 Sep 30, 2026
27f3f27
Merge qualified FPM API guards into decoupling integration
Arsene12358 Sep 30, 2026
b946545
test: include FPM profile in positional config expectations
Arsene12358 Sep 30, 2026
9d8b695
docs: clarify FPM engine spec schema migration
simone-chen Sep 30, 2026
29223c7
Merge main into FPM decoupling before merge
simone-chen Sep 30, 2026
4d4e9b4
Merge latest CI pipeline fixes before FPM merge
simone-chen Sep 30, 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
6 changes: 6 additions & 0 deletions .github/codeowners/areas.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ areas:
- python/aisimulate/
- python/aisimulate/src/aisimulate/capacity.py
- tests/test_aic.py
- tests/test_fpm_profile_workflow.py
- tests/test_fpm_query_evidence.py
- label: sweeper
github_team: "@ai-dynamo/aisimulate-sweeper-codeowners"
path_globs:
Expand Down Expand Up @@ -158,6 +160,10 @@ shared:
owners: [forward-pass-engine, maintainers]
- glob: tests/test_aic.py
owners: [forward-pass-engine, maintainers]
- glob: tests/test_fpm_profile_workflow.py
owners: [forward-pass-engine, maintainers]
- glob: tests/test_fpm_query_evidence.py
owners: [forward-pass-engine, replay, maintainers]
- glob: python/aisimulate/src/aisimulate/sweeper/
owners: [sweeper, maintainers]
- glob: docs/sweeper/
Expand Down
4 changes: 4 additions & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
/crates/tests/public-api/ @ai-dynamo/aisimulate-forward-pass-engine-codeowners
/crates/core/src/perfmodel/ @ai-dynamo/aisimulate-forward-pass-engine-codeowners
/crates/core/tests/perfmodel/ @ai-dynamo/aisimulate-forward-pass-engine-codeowners
/tests/test_fpm_query_evidence.py @ai-dynamo/aisimulate-forward-pass-engine-codeowners
/tests/test_fpm_profile_workflow.py @ai-dynamo/aisimulate-forward-pass-engine-codeowners
/crates/core/parity_tests/perfmodel/ @ai-dynamo/aisimulate-forward-pass-engine-codeowners
/python/aisimulate/src/aisimulate/capacity.py @ai-dynamo/aisimulate-forward-pass-engine-codeowners

Expand Down Expand Up @@ -179,12 +181,14 @@
/crates/core/src/engine/timing.rs @ai-dynamo/aisimulate-mocker-codeowners @ai-dynamo/aisimulate-forward-pass-engine-codeowners @ai-dynamo/access-aisimulate-maintain
/python/aisimulate/.gitattributes @ai-dynamo/aisimulate-infra-codeowners @ai-dynamo/access-aisimulate-maintain
/python/aisimulate/pyproject.toml @ai-dynamo/aisimulate-forward-pass-engine-codeowners @ai-dynamo/aisimulate-sweeper-codeowners @ai-dynamo/aisimulate-replay-codeowners @ai-dynamo/aisimulate-mocker-codeowners @ai-dynamo/aisimulate-infra-codeowners @ai-dynamo/access-aisimulate-maintain
/tests/test_fpm_query_evidence.py @ai-dynamo/aisimulate-forward-pass-engine-codeowners @ai-dynamo/aisimulate-replay-codeowners @ai-dynamo/access-aisimulate-maintain
/tests/test_resource_scheduler.py @ai-dynamo/aisimulate-replay-codeowners @ai-dynamo/aisimulate-sweeper-codeowners @ai-dynamo/access-aisimulate-maintain
/tests/test_state_cache_config.py @ai-dynamo/aisimulate-replay-codeowners @ai-dynamo/aisimulate-mocker-codeowners @ai-dynamo/access-aisimulate-maintain
/crates/tests/public-api/Cargo.toml @ai-dynamo/aisimulate-forward-pass-engine-codeowners @ai-dynamo/aisimulate-infra-codeowners @ai-dynamo/access-aisimulate-maintain
/python/aisimulate/.coderabbit.yaml @ai-dynamo/aisimulate-infra-codeowners @ai-dynamo/access-aisimulate-maintain
/python/aisimulate/eslint.config.js @ai-dynamo/aisimulate-infra-codeowners @ai-dynamo/access-aisimulate-maintain
/tests/test_e2e_accuracy_nightly.py @ai-dynamo/aisimulate-forward-pass-engine-codeowners @ai-dynamo/aisimulate-replay-codeowners @ai-dynamo/aisimulate-infra-codeowners @ai-dynamo/access-aisimulate-maintain
/tests/test_fpm_profile_workflow.py @ai-dynamo/aisimulate-forward-pass-engine-codeowners @ai-dynamo/access-aisimulate-maintain
/tests/test_resource_supervision.py @ai-dynamo/aisimulate-replay-codeowners @ai-dynamo/aisimulate-sweeper-codeowners @ai-dynamo/access-aisimulate-maintain
/crates/core/parity_tests/perfmodel/ @ai-dynamo/aisimulate-forward-pass-engine-codeowners @ai-dynamo/access-aisimulate-maintain
/tests/test_ci_workflow_contracts.py @ai-dynamo/aisimulate-infra-codeowners @ai-dynamo/access-aisimulate-maintain
Expand Down
16 changes: 16 additions & 0 deletions crates/core/src/engine/timing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,11 @@ impl TimingOperationEvidence {
left.fallbacks.push(fallback);
}
}
crate::perfmodel::fpm::accumulate_fpm_estimates(
&mut left.fpm_estimates,
right.fpm_estimates,
)
.map_err(anyhow::Error::msg)?;
Some(left)
}
(Some(mut details), None) | (None, Some(mut details)) => {
Expand Down Expand Up @@ -734,6 +739,12 @@ pub trait TimingModel: Send + Sync {
None
}

/// Latency-only whole-model FPM evidence is separate from energy evidence.
/// It must not change the provider's existing power publication contract.
fn fpm_evidence_summary(&self) -> Option<TimingEvidenceSummary> {
None
}

/// Start a new measurement epoch without changing timing predictions or
/// provider caches. Called only after preparation work has settled.
///
Expand Down Expand Up @@ -912,6 +923,7 @@ mod tests {
sol: None,
sol_unavailable_reason: Some("unsupported".into()),
fallbacks: Vec::new(),
fpm_estimates: Vec::new(),
})
}
}
Expand Down Expand Up @@ -1021,6 +1033,7 @@ mod tests {
TimingOperationEvidence::new("test", 2.0, None, TimingEvidenceSource::Estimated)
.unwrap();
op.details = Some(OperationDetails {
fpm_estimates: Vec::new(),
sol: has_sol.then_some(SolDiagnostics {
latency_ms: 1.0,
math_ms: 0.0,
Expand Down Expand Up @@ -1079,6 +1092,7 @@ mod tests {
)
.unwrap();
operation.details = Some(OperationDetails {
fpm_estimates: Vec::new(),
sol: None,
sol_unavailable_reason: Some("unsupported operation".into()),
fallbacks: vec![valid.clone(), fallback],
Expand All @@ -1100,6 +1114,7 @@ mod tests {
.unwrap();
let mut detailed = plain.clone();
detailed.details = Some(OperationDetails {
fpm_estimates: Vec::new(),
sol: Some(SolDiagnostics {
latency_ms: 1.0,
math_ms: 0.0,
Expand Down Expand Up @@ -1455,6 +1470,7 @@ mod tests {
}),
sol_unavailable_reason: None,
fallbacks: Vec::new(),
fpm_estimates: Vec::new(),
});
}
let incoming = ValidatedTimingPhase::from_operations(vec![op]).unwrap();
Expand Down
9 changes: 7 additions & 2 deletions crates/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ pub use perfmodel::{
SpeculativeConfig,
};
pub use perfmodel::{
CorrectionFactorBounds, CorrectionFeatureSpace, FpmInterpolationConfig, OpLevelConfig,
RegressionFitConfig, RegressionFitKind, UnrecordedFpmQuantMode,
CorrectionFactorBounds, CorrectionFeatureSpace, FpmInterpolationConfig, FpmInterpolationMethod,
OpLevelConfig, RegressionFitConfig, RegressionFitKind, UnrecordedFpmQuantMode,
};

#[cfg(feature = "python")]
Expand All @@ -75,3 +75,8 @@ pub(crate) use perfmodel::{
#[cfg(feature = "python")]
#[allow(unused_imports)]
pub(crate) use perfmodel::{py, py_ops};

pub use perfmodel::{
DirectFpmQueryEvidence, ForwardPassEstimate, FpmCoordinates, FpmEstimateEvidence,
FpmMeasurementSupport, FpmQueryResolution, FpmRankEstimate,
};
5 changes: 4 additions & 1 deletion crates/core/src/perfmodel/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,10 @@ pub const ENGINE_CONFIG_SCHEMA_VERSION: u32 = 1;
// JSON defaults preserve unrecorded DCP.
// - 24 (typed FPM DCP): FpmForwardOp carries dcp_size separately from the
// base matching identity, so control flow never parses the string tuple.
pub const ENGINE_SPEC_SCHEMA_VERSION: u32 = 24;
// - 25 (FPM decoupling): FpmForwardOp also carries the SOL/direct interpolation
// selector. The decoupling branch independently used 21 for this positional
// field; the combined layout differs from every prior schema, including 24.
pub const ENGINE_SPEC_SCHEMA_VERSION: u32 = 25;

/// Static engine identity and setup information carried by an
/// [`crate::perfmodel::engine::spec::EngineSpec`].
Expand Down
3 changes: 3 additions & 0 deletions crates/core/src/perfmodel/engine/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ pub struct OperationDetails {
pub sol_unavailable_reason: Option<String>,
/// Executed measurement substitutions; empty means no recorded substitution.
pub fallbacks: Vec<ExecutedFallback>,
/// Whole-forward estimates, counted by identical result and replay scale.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub fpm_estimates: Vec<crate::FpmEstimateEvidence>,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
Expand Down
88 changes: 80 additions & 8 deletions crates/core/src/perfmodel/engine/readiness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::AicError;
use crate::common::enums::{DatabaseMode, GemmQuantMode, MoeQuantMode, TransferKind};
use crate::config::PerfSource;
use crate::operators::Op;
use crate::operators::fpm_forward::{FpmForwardOp, FpmInterpolation, FpmPhase};
use crate::perf_database::moe::MoeKernel;
use crate::perf_database::{PerfDatabase, kernel_source_ok, parquet_loader::PerfReader};

Expand All @@ -27,6 +28,28 @@ pub(super) fn validate<'a>(
Ok(())
}

pub(super) fn validate_fpm(db: &PerfDatabase, fpm: &FpmForwardOp) -> Result<(), AicError> {
let cell = db
.fpm_forward
.select_cell(&fpm.match_identity, &fpm.model_path, fpm.dcp_size)?;
if fpm.interpolation == FpmInterpolation::Direct
&& match fpm.phase {
FpmPhase::Prefill => cell.direct_prefill.is_empty(),
FpmPhase::Decode => cell.direct_decode.is_empty(),
}
{
let phase = fpm.phase.as_str();
return Err(AicError::PerfDatabase(format!(
"direct FPM {phase} interpolation has no genuine measurements for {:?} at {}. \
Collect genuine {phase} FPM rows for this model and identity, or provide a \
systems root containing them.",
fpm.model_path,
db.data_root.display(),
)));
}
Ok(())
}

struct Availability<'a> {
db: &'a PerfDatabase,
tables: HashMap<String, bool>,
Expand Down Expand Up @@ -162,14 +185,7 @@ impl Availability<'_> {
}
}
TokenScale(scale) => return self.op(&scale.op),
FpmForward(fpm) => {
self.db.fpm_forward.select_cell(
&fpm.match_identity,
&fpm.model_path,
fpm.dcp_size,
)?;
return Ok(());
}
FpmForward(fpm) => return validate_fpm(self.db, fpm),
// These families have no analytic/empirical implementation.
MoeAllToAll(_) | MoeExpertCompute(_) | Dsv4MegaMoe(_)
if !matches!(
Expand Down Expand Up @@ -446,6 +462,62 @@ mod tests {
write_parquet(path, &[Col::Str("kernel_source", vec!["fixture"])]);
}

#[test]
fn direct_fpm_readiness_requires_genuine_rows_only_for_requested_phases() {
use crate::perf_database::fpm_forward::tests::{RowSpec, default_identity, write_pair};

for missing_phase in [FpmPhase::Prefill, FpmPhase::Decode] {
for fake_only in [false, true] {
let root = systems();
let data = root.path().join("data/b200_sxm/vllm/0.25.1");
std::fs::create_dir_all(&data).unwrap();
let present_phase = match missing_phase {
FpmPhase::Prefill => FpmPhase::Decode,
FpmPhase::Decode => FpmPhase::Prefill,
};
let row = |phase: FpmPhase, regime| RowSpec {
workload_kind: phase.as_str(),
total_prefill_tokens: if phase == FpmPhase::Prefill { 8 } else { 0 },
kv_seed_regime: Some(regime),
..RowSpec::default()
};
let mut rows = vec![row(present_phase, "real_kv")];
if fake_only {
rows.push(row(missing_phase, "fake_fallback"));
}
write_pair(&data, &rows);
let db = PerfDatabase::load(root.path(), "b200_sxm", "vllm", "0.25.1").unwrap();
let op = |phase: FpmPhase| {
Op::FpmForward(FpmForwardOp {
dcp_size: None,
name: format!("fpm_forward_{}", phase.as_str()),
phase,
model_path: "org/model-a".into(),
match_identity: default_identity(4),
weight_bytes: 0.0,
verify_width: 1,
interpolation: FpmInterpolation::Direct,
sol_ops: vec![],
original_fmha_quant_mode: None,
})
};
let present = op(present_phase);
let missing = op(missing_phase);
// A single-phase operation requires only its own measurements.
validate(&db, [&present].into_iter()).unwrap();
for requested in [vec![&missing], vec![&present, &missing]] {
let error = validate(&db, requested.into_iter())
.unwrap_err()
.to_string();
assert!(error.contains(missing_phase.as_str()), "{error}");
assert!(error.contains("no genuine measurements"), "{error}");
assert!(error.contains("Collect genuine"), "{error}");
assert!(error.contains(data.to_str().unwrap()), "{error}");
}
}
}
}

#[test]
fn dsv41_stage_requires_measured_module_for_silicon() {
use crate::operators::dsv41::{Dsv41LinearOp, Dsv41StageOp};
Expand Down
Loading
Loading