CI / Python Bindings (maturin) (ubuntu-latest) (push) Blocked by required conditions
CI / WASM Build + Size Check (push) Blocked by required conditions
CI / Distributed Training Tests (push) Blocked by required conditions
CI / CI Success (push) Blocked by required conditions
CI / Build (macos-latest) (push) Waiting to run
CI / Test (macos-latest) (push) Blocked by required conditions
CI / Test (ubuntu-latest) (push) Blocked by required conditions
CI / Python Bindings (maturin) (macos-latest) (push) Blocked by required conditions
CI / Format Check (push) Failing after 5s
Documentation / Build API Documentation (push) Failing after 5s
CI / Build (ubuntu-latest) (push) Failing after 14s
CI / Clippy Check (push) Failing after 3m1s
Documentation / Build User Guide (push) Successful in 6s
Performance Benchmarks / Run Benchmarks (push) Failing after 11s
CI / Build CPU-Only (Explicit) (push) Failing after 2m33s
Co-Authored-By: Claude Fable 5.1 <[email protected]>
210 lines
7.1 KiB
Rust
210 lines
7.1 KiB
Rust
//! PERF-2 P3-iii (`docs/perf2_campaign.md` in omni-cortex): the
|
|
//! `MarchPlane` — K overset FSI marches in ONE process, one lane thread
|
|
//! each, the device V-cycles of every lane inside a CG gathered into one
|
|
//! batched launch set by the plane rendezvous in `rtx-cfd`'s Poisson
|
|
//! driver. Every lane runs the unchanged `run_march_overset`.
|
|
//!
|
|
//! Knobs (all the `RTX_FSI2O_*` knobs of `fsi2_on_the_overset` apply to
|
|
//! every lane; these add the plane):
|
|
//! - `RTX_FSI2O_PLANE_FILE`: the points, one per line, `u_mean e_s tag`
|
|
//! (the `p7_points.txt` format; `#` comments and blank lines skipped).
|
|
//! Without it the test returns.
|
|
//! - `RTX_FSI2O_PLANE_DIR`: each lane's CSV goes to `<dir>/<tag>.csv`.
|
|
//! - `RTX_FSI2O_PLANE_STACK_MB`: the lane threads' stack (default 64).
|
|
//! Requires `RTX_FSI2O_MG_RB=1 RTX_FSI2O_MG_DEVICE=1` on a
|
|
//! `--features rtx-cfd/cuda` build; the test fails loudly otherwise (a
|
|
//! CPU plane is K separate processes, not this driver).
|
|
//!
|
|
//! `RTX_FSI2O_PLANE_FILE=points.txt RTX_FSI2O_PLANE_DIR=$L RTX_FSI2O_MG_RB=1 RTX_FSI2O_MG_DEVICE=1 RTX_CUDA_ARCH=sm_120 <binary> fsi2_overset_plane --nocapture`
|
|
|
|
mod fsi2_harness;
|
|
|
|
use fsi2_harness::overset_march::{OversetMarchConfig, run_march_overset};
|
|
use fsi2_harness::{BenchmarkCase, FSI2, FSI3};
|
|
use rtx_cfd::solvers::incompressible::{plane_counters, set_plane_lane};
|
|
use std::time::Instant;
|
|
|
|
struct Point {
|
|
u_mean: f64,
|
|
e_s: f64,
|
|
tag: String,
|
|
}
|
|
|
|
fn read_points(path: &str) -> Vec<Point> {
|
|
let text = std::fs::read_to_string(path).unwrap_or_else(|e| panic!("{path}: {e}"));
|
|
text.lines()
|
|
.map(str::trim)
|
|
.filter(|l| !l.is_empty() && !l.starts_with('#'))
|
|
.map(|l| {
|
|
let f: Vec<&str> = l.split_whitespace().collect();
|
|
assert!(
|
|
f.len() >= 3,
|
|
"plane point line needs `u_mean e_s tag`: {l:?}"
|
|
);
|
|
Point {
|
|
u_mean: f[0].parse().expect("u_mean"),
|
|
e_s: f[1].parse().expect("e_s"),
|
|
tag: f[2].to_string(),
|
|
}
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
struct LaneSummary {
|
|
tag: String,
|
|
wall: f64,
|
|
steps: usize,
|
|
subit: f64,
|
|
retries: usize,
|
|
uy_amp_mm: f64,
|
|
uy_mid_mm: f64,
|
|
frequency: Option<f64>,
|
|
drag_mid: f64,
|
|
lift_amp: f64,
|
|
death: Option<(usize, f64, String)>,
|
|
}
|
|
|
|
#[test]
|
|
fn fsi2_overset_plane() {
|
|
let Ok(points_path) = std::env::var("RTX_FSI2O_PLANE_FILE") else {
|
|
return;
|
|
};
|
|
let dir = std::env::var("RTX_FSI2O_PLANE_DIR").unwrap_or_else(|_| ".".into());
|
|
let stack_mb: usize = std::env::var("RTX_FSI2O_PLANE_STACK_MB")
|
|
.ok()
|
|
.and_then(|v| v.parse().ok())
|
|
.unwrap_or(64);
|
|
let device_on = std::env::var("RTX_FSI2O_MG_DEVICE").is_ok_and(|v| v == "1")
|
|
&& std::env::var("RTX_FSI2O_MG_RB").is_ok_and(|v| v == "1");
|
|
assert!(
|
|
device_on,
|
|
"the plane needs RTX_FSI2O_MG_RB=1 RTX_FSI2O_MG_DEVICE=1 on a cuda build"
|
|
);
|
|
let points = read_points(&points_path);
|
|
let k = points.len();
|
|
assert!(k >= 1, "no plane points in {points_path}");
|
|
|
|
let config = OversetMarchConfig::from_env(
|
|
"FSI2O",
|
|
OversetMarchConfig {
|
|
ny: 41,
|
|
flag_nx: 35,
|
|
t_release: 6.0,
|
|
t_end: 7.0,
|
|
subcycle: 1,
|
|
tol_floor: 2e-4,
|
|
rtol: 1e-2,
|
|
stall_accept: 5.0,
|
|
max_subiterations: 12,
|
|
coupler: "aitken".into(),
|
|
reuse: 2,
|
|
initial_relaxation: 0.5,
|
|
c1_interface: false,
|
|
predictor: "structure".into(),
|
|
sweeps: 100,
|
|
max_rounds: 3,
|
|
csv_path: None,
|
|
trace_steps: 0,
|
|
},
|
|
);
|
|
let base = if std::env::var("RTX_FSI2O_CASE").as_deref() == Ok("FSI3") {
|
|
FSI3
|
|
} else {
|
|
FSI2
|
|
};
|
|
println!(
|
|
" MARCH PLANE: {k} lanes in one process ({} base, ny {}, t_end {}, coupler {}, s = {}); points from {points_path}, CSVs under {dir}",
|
|
base.name, config.ny, config.t_end, config.coupler, config.subcycle
|
|
);
|
|
for (i, p) in points.iter().enumerate() {
|
|
println!(
|
|
" lane {i}: {} u_mean {} (Re {:.0}) e_s {:.4e} (E/E0 {:.4})",
|
|
p.tag,
|
|
p.u_mean,
|
|
100.0 * p.u_mean,
|
|
p.e_s,
|
|
p.e_s / base.e_s
|
|
);
|
|
}
|
|
|
|
let t_plane = Instant::now();
|
|
let handles: Vec<_> = points
|
|
.into_iter()
|
|
.enumerate()
|
|
.map(|(i, p)| {
|
|
let mut cfg = config.clone();
|
|
cfg.csv_path = Some(format!("{dir}/{}.csv", p.tag));
|
|
let case = BenchmarkCase {
|
|
u_mean: p.u_mean,
|
|
e_s: p.e_s,
|
|
..base
|
|
};
|
|
std::thread::Builder::new()
|
|
.name(format!("lane-{i}-{}", p.tag))
|
|
.stack_size(stack_mb << 20)
|
|
.spawn(move || {
|
|
set_plane_lane(Some(i));
|
|
let t0 = Instant::now();
|
|
let r = run_march_overset(case, &cfg);
|
|
let m = &r.result;
|
|
let w = m.window(3.0);
|
|
LaneSummary {
|
|
tag: p.tag,
|
|
wall: t0.elapsed().as_secs_f64(),
|
|
steps: m.coupled_steps,
|
|
subit: m.mean_subiterations,
|
|
retries: m.retried_steps,
|
|
uy_amp_mm: w.uy_amp * 1e3,
|
|
uy_mid_mm: w.uy_mid * 1e3,
|
|
frequency: w.frequency,
|
|
drag_mid: w.drag_mid,
|
|
lift_amp: w.lift_amp,
|
|
death: r.death,
|
|
}
|
|
})
|
|
.expect("lane thread")
|
|
})
|
|
.collect();
|
|
let mut summaries = Vec::with_capacity(k);
|
|
let mut failures = Vec::new();
|
|
for (i, h) in handles.into_iter().enumerate() {
|
|
match h.join() {
|
|
Ok(s) => summaries.push(s),
|
|
Err(_) => failures.push(i),
|
|
}
|
|
}
|
|
let plane_wall = t_plane.elapsed().as_secs_f64();
|
|
let (batches, served) = plane_counters();
|
|
println!(
|
|
" MARCH PLANE done: {k} lanes in {plane_wall:.0} s wall; {batches} batched V-cycle launches served {served} lane calls ({:.2} lanes per batch)",
|
|
if batches > 0 {
|
|
served as f64 / batches as f64
|
|
} else {
|
|
0.0
|
|
}
|
|
);
|
|
for s in &summaries {
|
|
println!(
|
|
" lane {}: {:.0} s wall, {} coupled steps, {:.1} subit/step, {} retries; last 3 s: uy(A) = {:.3} ± {:.3} mm, f = {:?} Hz, drag mid {:.2}, lift amp {:.2}; death {:?}",
|
|
s.tag,
|
|
s.wall,
|
|
s.steps,
|
|
s.subit,
|
|
s.retries,
|
|
s.uy_mid_mm,
|
|
s.uy_amp_mm,
|
|
s.frequency,
|
|
s.drag_mid,
|
|
s.lift_amp,
|
|
s.death
|
|
);
|
|
}
|
|
assert!(failures.is_empty(), "lane threads panicked: {failures:?}");
|
|
let dead: Vec<&str> = summaries
|
|
.iter()
|
|
.filter(|s| s.death.is_some())
|
|
.map(|s| s.tag.as_str())
|
|
.collect();
|
|
assert!(dead.is_empty(), "lanes died: {dead:?}");
|
|
}
|