forked from 1b-agent/fieldcraft-upstream
solve.fluid: backend "cpu" (the default) or "gpu"
The CPU stays the default, so every existing result and fixture is bit-identical; "gpu" steps on the device FIELDCRAFT_GPU picks through the registered backend, and is an error when the build has none. The desktop app and the CLI (hence the MCP server) register fieldcraft-lbm-gpu at start; the summary names what stepped. Hostile values are errors. Part of #529.
This commit is contained in:
1 parent
b123f31d47
commit
48cc80bef5
8 files changed
+26
-2
No files matched your search
Generated
+2
@@ -1271,6 +1271,7 @@ dependencies = [
|
|||||||
"embed-resource",
|
"embed-resource",
|
||||||
"fieldcraft-engine",
|
"fieldcraft-engine",
|
||||||
"fieldcraft-io",
|
"fieldcraft-io",
|
||||||
|
"fieldcraft-lbm-gpu",
|
||||||
"fieldcraft-ui-egui",
|
"fieldcraft-ui-egui",
|
||||||
"fieldcraft-update",
|
"fieldcraft-update",
|
||||||
"getrandom 0.4.3",
|
"getrandom 0.4.3",
|
||||||
@@ -1290,6 +1291,7 @@ dependencies = [
|
|||||||
"fieldcraft-engine",
|
"fieldcraft-engine",
|
||||||
"fieldcraft-gpu",
|
"fieldcraft-gpu",
|
||||||
"fieldcraft-io",
|
"fieldcraft-io",
|
||||||
|
"fieldcraft-lbm-gpu",
|
||||||
"fieldcraft-mcp",
|
"fieldcraft-mcp",
|
||||||
"fieldcraft-render",
|
"fieldcraft-render",
|
||||||
"fieldcraft-update",
|
"fieldcraft-update",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
fieldcraft-engine = { workspace = true }
|
fieldcraft-engine = { workspace = true }
|
||||||
|
fieldcraft-lbm-gpu = { workspace = true }
|
||||||
fieldcraft-gpu = { workspace = true }
|
fieldcraft-gpu = { workspace = true }
|
||||||
fieldcraft-render = { workspace = true }
|
fieldcraft-render = { workspace = true }
|
||||||
fieldcraft-io = { workspace = true }
|
fieldcraft-io = { workspace = true }
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ usage:
|
|||||||
const MAX_SCRIPT_BYTES: u64 = 256 * 1024 * 1024;
|
const MAX_SCRIPT_BYTES: u64 = 256 * 1024 * 1024;
|
||||||
|
|
||||||
fn main() -> ExitCode {
|
fn main() -> ExitCode {
|
||||||
|
// The lattice Boltzmann GPU backend, for `solve.fluid` with `backend: "gpu"` (scripts and MCP).
|
||||||
|
fieldcraft_lbm_gpu::register();
|
||||||
let args: Vec<String> = std::env::args().skip(1).collect();
|
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||||
let result = match args.first().map(String::as_str) {
|
let result = match args.first().map(String::as_str) {
|
||||||
Some("commands") if args.get(1).map(String::as_str) == Some("--json") => {
|
Some("commands") if args.get(1).map(String::as_str) == Some("--json") => {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ neg_cmp_op_on_partial_ord = "allow"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
fieldcraft-engine = { workspace = true }
|
fieldcraft-engine = { workspace = true }
|
||||||
|
fieldcraft-lbm-gpu = { workspace = true }
|
||||||
fieldcraft-ui-egui = { workspace = true }
|
fieldcraft-ui-egui = { workspace = true }
|
||||||
fieldcraft-io = { workspace = true }
|
fieldcraft-io = { workspace = true }
|
||||||
fieldcraft-update = { workspace = true }
|
fieldcraft-update = { workspace = true }
|
||||||
|
|||||||
@@ -224,6 +224,8 @@ fn parse_args(args: impl IntoIterator<Item = String>, env_port: Option<String>)
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> eframe::Result {
|
fn main() -> eframe::Result {
|
||||||
|
// The lattice Boltzmann GPU backend, for `solve.fluid` with `backend: "gpu"`.
|
||||||
|
fieldcraft_lbm_gpu::register();
|
||||||
let args = match parse_args(std::env::args().skip(1), std::env::var("FIELDCRAFT_CONTROL_PORT").ok()) {
|
let args = match parse_args(std::env::args().skip(1), std::env::var("FIELDCRAFT_CONTROL_PORT").ok()) {
|
||||||
Ok(Some(a)) => a,
|
Ok(Some(a)) => a,
|
||||||
Ok(None) => return Ok(()),
|
Ok(None) => return Ok(()),
|
||||||
|
|||||||
@@ -1953,6 +1953,9 @@ pub const SOLVE_EXPLICIT: &[Field] = &[
|
|||||||
pub const SOLVE_HISTORY: &[Field] = &[SOLVED.req(), int("every", "Keep one in", 1, 1_000_000_000).help("Keep one history sample in this many.")];
|
pub const SOLVE_HISTORY: &[Field] = &[SOLVED.req(), int("every", "Keep one in", 1, 1_000_000_000).help("Keep one history sample in this many.")];
|
||||||
pub const SOLVE_FLUID: &[Field] = &[
|
pub const SOLVE_FLUID: &[Field] = &[
|
||||||
STEP,
|
STEP,
|
||||||
|
choice("backend", "Backend", &[("cpu", "CPU"), ("gpu", "GPU")])
|
||||||
|
.help("The CPU (double precision, the same result on every machine) or the GPU the build and FIELDCRAFT_GPU pick (single precision).")
|
||||||
|
.def("\"cpu\""),
|
||||||
int("max_cells", "Max cells", 1, 16_000_000).help("Cap on the lattice cells."),
|
int("max_cells", "Max cells", 1, 16_000_000).help("Cap on the lattice cells."),
|
||||||
int("max_steps", "Max steps", 1, 10_000_000).help("Cap on the lattice time steps."),
|
int("max_steps", "Max steps", 1, 10_000_000).help("Cap on the lattice time steps."),
|
||||||
positive("max_cell_updates", "Max cell updates", D::None).help("Cap on cells × steps."),
|
positive("max_cell_updates", "Max cell updates", D::None).help("Cap on cells × steps."),
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ pub const HISTORY: CommandSpec = CommandSpec {
|
|||||||
pub const FLUID: CommandSpec = CommandSpec {
|
pub const FLUID: CommandSpec = CommandSpec {
|
||||||
id: "solve.fluid",
|
id: "solve.fluid",
|
||||||
label: "Solve Fluid Step",
|
label: "Solve Fluid Step",
|
||||||
params: r#"{"step": "Flow", "max_cells": 16000000, "max_steps": 10000000, "max_cell_updates": 1e12, "max_frame_bytes": 2e9, "threads": 8} — runs a fluid step with the lattice Boltzmann solver (the only fluid step when `step` is left out): obstacles are triangulated from the open design or taken from imported surfaces; returns the time step, relaxation time, Mach number, mass and the final force on every wall; the caps may only be lowered; `threads` as for solve.static"#,
|
params: r#"{"step": "Flow", "backend": "cpu", "max_cells": 16000000, "max_steps": 10000000, "max_cell_updates": 1e12, "max_frame_bytes": 2e9, "threads": 8} — runs a fluid step with the lattice Boltzmann solver (the only fluid step when `step` is left out): obstacles are triangulated from the open design or taken from imported surfaces; returns the time step, relaxation time, Mach number, mass and the final force on every wall; `backend` is `cpu` (the default: double precision, the same result on every machine) or `gpu` (the step on the GPU `FIELDCRAFT_GPU` picks, single precision, within the backend's stated tolerance of the CPU; an error when the build has none); the caps may only be lowered; `threads` as for solve.static"#,
|
||||||
form: Some(super::forms::SOLVE_FLUID),
|
form: Some(super::forms::SOLVE_FLUID),
|
||||||
kind: CommandKind::Model,
|
kind: CommandKind::Model,
|
||||||
requires: &[Requirement::FluidDomain, Requirement::Step],
|
requires: &[Requirement::FluidDomain, Requirement::Step],
|
||||||
@@ -487,10 +487,15 @@ fn run_fluid(s: &mut Session, p: &Value) -> Result<Value> {
|
|||||||
max_frame_bytes: cap(p, "max_frame_bytes", defaults.max_frame_bytes)?,
|
max_frame_bytes: cap(p, "max_frame_bytes", defaults.max_frame_bytes)?,
|
||||||
};
|
};
|
||||||
let step = opt::<String>(p, "step")?;
|
let step = opt::<String>(p, "step")?;
|
||||||
|
let backend = match opt::<String>(p, "backend")? {
|
||||||
|
None => fieldcraft_lbm::Choice::Cpu,
|
||||||
|
Some(b) => fieldcraft_lbm::Choice::parse(&b)
|
||||||
|
.ok_or_else(|| EngineError::Params(format!("`backend` must be `cpu` or `gpu`, not `{}`", b.chars().take(40).collect::<String>())))?,
|
||||||
|
};
|
||||||
let (_, domain) = fieldcraft_lbm::fluid_step(&s.model, step.as_deref())?;
|
let (_, domain) = fieldcraft_lbm::fluid_step(&s.model, step.as_deref())?;
|
||||||
let obstacles = obstacles(s, domain)?;
|
let obstacles = obstacles(s, domain)?;
|
||||||
let model = &s.model;
|
let model = &s.model;
|
||||||
let r = with_threads(threads(s, p)?, || fieldcraft_lbm::solve_fluid(model, step.as_deref(), &obstacles, &limits))?;
|
let r = with_threads(threads(s, p)?, || fieldcraft_lbm::solve_fluid_on(model, step.as_deref(), &obstacles, &limits, backend))?;
|
||||||
let summary = fluid_summary(&r);
|
let summary = fluid_summary(&r);
|
||||||
s.store_result(StepResult::Fluid(r));
|
s.store_result(StepResult::Fluid(r));
|
||||||
Ok(summary)
|
Ok(summary)
|
||||||
@@ -517,6 +522,7 @@ fn fluid_summary(r: &FluidResult) -> Value {
|
|||||||
let (m0, m1) = (r.frames.first().map_or(0.0, |f| f.mass), last.map_or(0.0, |f| f.mass));
|
let (m0, m1) = (r.frames.first().map_or(0.0, |f| f.mass), last.map_or(0.0, |f| f.mass));
|
||||||
json!({
|
json!({
|
||||||
"step": r.step,
|
"step": r.step,
|
||||||
|
"backend": r.stats.backend,
|
||||||
"cells": r.stats.cells,
|
"cells": r.stats.cells,
|
||||||
"fluid_cells": r.stats.fluid_cells,
|
"fluid_cells": r.stats.fluid_cells,
|
||||||
"steps": r.stats.steps,
|
"steps": r.stats.steps,
|
||||||
@@ -1175,6 +1181,10 @@ pub(crate) mod tests {
|
|||||||
json!({"step": "Shear", "threads": 0}),
|
json!({"step": "Shear", "threads": 0}),
|
||||||
json!({"step": "Shear", "threads": 100000}),
|
json!({"step": "Shear", "threads": 100000}),
|
||||||
json!({"step": "Shear", "threads": null}),
|
json!({"step": "Shear", "threads": null}),
|
||||||
|
json!({"step": "Shear", "backend": "tpu"}),
|
||||||
|
json!({"step": "Shear", "backend": 7}),
|
||||||
|
// no device backend is registered in this test binary: an error, not a panic
|
||||||
|
json!({"step": "Shear", "backend": "gpu"}),
|
||||||
];
|
];
|
||||||
for id in ["solve.fluid", "solve.probe", "solve.static", "solve.frequency"] {
|
for id in ["solve.fluid", "solve.probe", "solve.static", "solve.frequency"] {
|
||||||
for p in &hostile {
|
for p in &hostile {
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ pub struct FluidStats {
|
|||||||
pub mach: f64,
|
pub mach: f64,
|
||||||
/// Fluid cells times steps.
|
/// Fluid cells times steps.
|
||||||
pub cell_updates: u64,
|
pub cell_updates: u64,
|
||||||
|
/// What stepped the flow: `cpu`, or `gpu: <adapter>`.
|
||||||
|
pub backend: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The result of a fluid step.
|
/// The result of a fluid step.
|
||||||
@@ -209,6 +211,7 @@ pub fn run(setup: &Setup, flow: &mut dyn Backend, limits: &Limits) -> Result<Flu
|
|||||||
lattice_velocity: setup.lattice_velocity,
|
lattice_velocity: setup.lattice_velocity,
|
||||||
mach: setup.mach,
|
mach: setup.mach,
|
||||||
cell_updates: (flow.fluid_cells() as u64).saturating_mul(total),
|
cell_updates: (flow.fluid_cells() as u64).saturating_mul(total),
|
||||||
|
backend: flow.name(),
|
||||||
},
|
},
|
||||||
warnings,
|
warnings,
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in new issue
Block a user