From d9a2752cb9f0a4a78ebd681d72857e71591efb6b Mon Sep 17 00:00:00 2001 From: Omar Sobh Date: Sat, 19 Sep 2026 18:35:04 -0500 Subject: [PATCH] =?UTF-8?q?embedded3=20PERF-3=20P1-2:=20the=20predictor's?= =?UTF-8?q?=20device=20tables=20derived=20from=20the=20projection's=20(sha?= =?UTF-8?q?red=20buffers=20move=20over;=20282=20->=2048=20ms),=20the=20ope?= =?UTF-8?q?rator=20key=20lazy=20after=20a=20refresh=20(240=20->=200=20ms),?= =?UTF-8?q?=20parallel=20active=20/=20owner=20maps=20=E2=80=94=20slab=20fl?= =?UTF-8?q?ag=20CSV=20byte-identical;=20RTX=5FE3=5FTABLES=5FREBUILD=3D1=20?= =?UTF-8?q?=3D=20the=20full-rebuild=20reference?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 --- .../embedded3/poisson/device_cg.rs | 12 ++-- .../embedded3/step/device/cut.rs | 68 ++++++++++++++++++- 2 files changed, 73 insertions(+), 7 deletions(-) diff --git a/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/poisson/device_cg.rs b/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/poisson/device_cg.rs index 414e761..a7e4ea2 100644 --- a/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/poisson/device_cg.rs +++ b/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/poisson/device_cg.rs @@ -55,7 +55,9 @@ fn kernels() -> &'static CgKernels { /// The prepared operator on the device and the CG's work vectors. pub struct DeviceCg { - key: OperatorKey, + /// `None` after a `refresh` (PERF-3 P1-2): the key copies and hashes the whole operator, and + /// the refresh path never consults it — a refreshed solver simply never matches the cache. + key: Option, n: usize, nx: i32, n_cells: usize, @@ -139,7 +141,7 @@ impl DeviceCg { link_ptr.push(link_idx.len() as u32); } Self { - key: OperatorKey::of(problem, params), + key: Some(OperatorKey::of(problem, params)), n, nx: problem.nx as i32, n_cells, @@ -178,7 +180,9 @@ impl DeviceCg { /// Whether this prepared operator is the one for `problem` + `params`. pub fn matches(&self, problem: &Problem, params: &MultigridParameters) -> bool { - self.key.matches(problem, params) + self.key + .as_ref() + .is_some_and(|k| k.matches(problem, params)) } /// Refresh the FINE operator (cells, coefficients, links, components) @@ -251,7 +255,7 @@ impl DeviceCg { self.singular = singular_count > 0; self.active_host = fine.active.clone(); let l_upload = lap.elapsed(); - self.key = OperatorKey::of(problem, params); + self.key = None; let l_key = lap.elapsed(); // The V-cycle's finest level follows the operator (its coarser // levels stay): the fine level alone, no hierarchy build. diff --git a/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/step/device/cut.rs b/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/step/device/cut.rs index fbc44d2..79d9fec 100644 --- a/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/step/device/cut.rs +++ b/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/step/device/cut.rs @@ -184,7 +184,9 @@ impl DeviceCut { mask.wall_flux_table(body, t).0 }; let nc = g.cells(); + use rayon::prelude::*; let active: Vec = (0..nc) + .into_par_iter() .map(|i| { i32::from(if projection { mask.cell_active(i) @@ -202,6 +204,7 @@ impl DeviceCut { let ap_v: &[f64] = step.map_or(&cut.a_v, |a| &a.1); let ap_w: &[f64] = step.map_or(&cut.a_w, |a| &a.2); let owner: Vec = (0..nc) + .into_par_iter() .map(|i| mask.master(i).unwrap_or(i) as u32) .collect(); // The slaves of every cell as a CSR by counting (no per-cell lists). @@ -246,6 +249,51 @@ impl DeviceCut { }) } + /// The PREDICTOR tables from the PROJECTION tables of the same mask and + /// time (PERF-3 P1-2): the wall distances, the centroid shifts, the + /// surface velocities, the wall flux and the merged-cell maps are the + /// same arrays — their device buffers move over; only the open flags, the + /// activity flags and the apertures (end-of-step instead of the step's) + /// are recomputed and uploaded. Identical to a fresh `build_with` in + /// every table. + pub(super) fn predictor_from(prev: Self, solver: &Solver, g: Grid) -> Option { + use rayon::prelude::*; + let mask = solver.mask()?; + let cut = mask.cut()?; + let rt = runtime(); + let up_f = |v: &[f64]| -> CudaSlice { + rt.stream + .memcpy_stod(if v.is_empty() { &[0.0f64][..] } else { v }) + .expect("upload") + }; + let up_i = |v: &[i32]| -> CudaSlice { rt.stream.memcpy_stod(v).expect("upload") }; + let (nx, ny, nz) = (g.nx, g.ny, g.nz); + let counts = [(nx + 1) * ny * nz, nx * (ny + 1) * nz, nx * ny * (nz + 1)]; + let open = |c: usize| -> Vec { + (0..counts[c]) + .into_par_iter() + .map(|idx| { + let kind = match c { + 0 => mask.u_kind(idx), + 1 => mask.v_kind(idx), + _ => mask.w_kind(idx), + }; + i32::from(kind == FaceKind::Fluid) + }) + .collect() + }; + let active: Vec = (0..g.cells()) + .into_par_iter() + .map(|i| i32::from(mask.is_fluid_cell(i))) + .collect(); + Some(Self { + a: [up_f(&cut.a_u), up_f(&cut.a_v), up_f(&cut.a_w)], + open: [up_i(&open(0)), up_i(&open(1)), up_i(&open(2))], + active: up_i(&active), + ..prev + }) + } + fn ptrs(&self) -> E3CutPtrs { let rt = runtime(); let s = &rt.stream; @@ -526,9 +574,23 @@ impl DeviceStep { // The prescribed faces take the surface velocity (the host's // end-of-step impose), and the next predictor's tables. if self.solver.is_moving() { - let shared = self.cut.take().map(|c| c.ub_host); - self.cut = - DeviceCut::build_with(&self.solver, g, Phase::Predictor, t_new, shared.as_ref()); + let lap_tables = Instant::now(); + self.cut = match self.cut.take() { + Some(prev) if std::env::var("RTX_E3_TABLES_REBUILD").is_err() => { + DeviceCut::predictor_from(prev, &self.solver, g) + } + // `RTX_E3_TABLES_REBUILD=1`: the full rebuild (the identity reference). + prev => { + let shared = prev.map(|c| c.ub_host); + DeviceCut::build_with(&self.solver, g, Phase::Predictor, t_new, shared.as_ref()) + } + }; + if std::env::var("RTX_E3_MOVING_PROFILE").is_ok() { + eprintln!( + " moving laps: predictor tables {:.0} ms", + lap_tables.elapsed().as_secs_f64() * 1e3 + ); + } let cptrs = self.cut.as_ref().expect("cut").ptrs(); for c in 0..3i32 { unsafe {