重构body api,性能分析,项目整理
This commit is contained in:
1 parent
4758eb3215
commit
2e052480c2
64 files changed
+5196
-1693
No files matched your search
@@ -0,0 +1,269 @@
|
||||
"""Performance baseline for minimal host-interference LBM stepping.
|
||||
|
||||
This script builds a temporary config, runs warmup + measured batches, and
|
||||
reports MLUPS under a FluidX3D-like benchmark mindset:
|
||||
|
||||
- keep the main loop on GPU (`stepper.step`)
|
||||
- avoid host downloads by default
|
||||
- selectively enable host-touch paths to quantify overhead
|
||||
- sweep `inlet.scheme` to check stability-sensitive combinations
|
||||
|
||||
Usage::
|
||||
python tests/run_perf_baseline.py
|
||||
python tests/run_perf_baseline.py --lattice-model D2Q9 --nx 384 --ny 192 --steps 30000
|
||||
python tests/run_perf_baseline.py --macro-every 500 --ddf-every 1000
|
||||
python tests/run_perf_baseline.py --with-cylinder --obs-every 50
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
import time
|
||||
from typing import Any, Dict, List
|
||||
|
||||
import pycuda.driver as cuda
|
||||
|
||||
_REPO = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
_DEFAULT_LBM = os.path.join(_REPO, "src", "CelerisLab", "configs", "config_lbm.json")
|
||||
|
||||
|
||||
def _load_json(path: str) -> dict:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def _write_json(path: str, payload: dict) -> None:
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
json.dump(payload, f, indent=2)
|
||||
|
||||
|
||||
def _build_lbm_cfg(base: dict, args: argparse.Namespace) -> dict:
|
||||
cfg = json.loads(json.dumps(base))
|
||||
cfg["grid"]["lattice_model"] = args.lattice_model
|
||||
cfg["grid"]["nx"] = int(args.nx)
|
||||
cfg["grid"]["ny"] = int(args.ny)
|
||||
cfg["grid"]["nz"] = int(args.nz)
|
||||
|
||||
cfg["physics"]["viscosity"] = float(args.viscosity)
|
||||
cfg["physics"]["velocity"] = float(args.velocity)
|
||||
cfg["physics"]["rho"] = float(args.rho)
|
||||
|
||||
cfg["method"]["collision"] = str(args.collision).upper()
|
||||
cfg["method"]["streaming"] = str(args.streaming)
|
||||
cfg["method"]["store_precision"] = str(args.store_precision).upper()
|
||||
cfg["method"]["ddf_shifting"] = bool(args.ddf_shifting)
|
||||
cfg["method"]["les"]["enabled"] = bool(args.enable_les)
|
||||
cfg["method"]["outlet"]["mode"] = str(args.outlet_mode)
|
||||
cfg["method"]["inlet"]["profile"] = str(args.inlet_profile)
|
||||
# Expose inlet scheme as a benchmark axis; useful when stability depends
|
||||
# on collision/inlet coupling.
|
||||
cfg["method"]["inlet"]["scheme"] = str(args.inlet_scheme)
|
||||
cfg["method"]["y_wall_bc"] = str(args.y_wall_bc)
|
||||
|
||||
cfg["cuda"]["threads_per_block"] = int(args.threads_per_block)
|
||||
cfg["cuda"]["compute_capability"] = str(args.compute_capability)
|
||||
return cfg
|
||||
|
||||
|
||||
def _build_body_cfg(args: argparse.Namespace) -> dict:
|
||||
if not args.with_cylinder:
|
||||
return {"objects": []}
|
||||
cx = 0.5 * float(args.nx)
|
||||
cy = 0.5 * float(args.ny)
|
||||
radius = max(2.0, min(float(args.nx), float(args.ny)) * 0.08)
|
||||
return {
|
||||
"objects": [
|
||||
{
|
||||
"type": "cylinder",
|
||||
"center": [cx, cy],
|
||||
"radius": radius,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def _maybe_probe_macroscopic(sim: Any, step: int, every: int, repeat: int) -> None:
|
||||
if every > 0 and step % every == 0:
|
||||
for _ in range(max(1, int(repeat))):
|
||||
_ = sim.get_macroscopic()
|
||||
|
||||
|
||||
def _maybe_probe_ddf(sim: Any, step: int, every: int, repeat: int) -> None:
|
||||
if every > 0 and step % every == 0:
|
||||
for _ in range(max(1, int(repeat))):
|
||||
_ = sim.get_ddf()
|
||||
|
||||
|
||||
def _maybe_checkpoint(sim: Any, step: int, every: int, out_dir: str) -> None:
|
||||
if every > 0 and step % every == 0:
|
||||
path = os.path.join(out_dir, f"checkpoint_step_{step:09d}.h5")
|
||||
sim.save_checkpoint(path)
|
||||
|
||||
|
||||
def _maybe_probe_obs(sim: Any, stream: cuda.Stream, step: int, every: int) -> None:
|
||||
if every <= 0 or step % every != 0:
|
||||
return
|
||||
if sim.bodies.count <= 0:
|
||||
return
|
||||
sim.bodies.download_obs_full_async(stream)
|
||||
stream.synchronize()
|
||||
_ = sim.bodies.read_force(0)
|
||||
|
||||
|
||||
def run(args: argparse.Namespace) -> Dict[str, Any]:
|
||||
if not os.path.isfile(_DEFAULT_LBM):
|
||||
raise FileNotFoundError(f"Base config missing: {_DEFAULT_LBM}")
|
||||
|
||||
base = _load_json(_DEFAULT_LBM)
|
||||
lbm_cfg = _build_lbm_cfg(base, args)
|
||||
body_cfg = _build_body_cfg(args)
|
||||
|
||||
tmpd = tempfile.mkdtemp(prefix="celeris_perf_baseline_")
|
||||
lbm_path = os.path.join(tmpd, "config_lbm.json")
|
||||
body_path = os.path.join(tmpd, "config_body.json")
|
||||
ckpt_dir = os.path.join(tmpd, "checkpoints")
|
||||
os.makedirs(ckpt_dir, exist_ok=True)
|
||||
_write_json(lbm_path, lbm_cfg)
|
||||
_write_json(body_path, body_cfg)
|
||||
|
||||
from CelerisLab import Simulation # noqa: WPS433
|
||||
|
||||
sim = Simulation(lbm_config_path=lbm_path, body_config_path=body_path, device_id=args.device_id)
|
||||
sim.initialize()
|
||||
|
||||
stream = cuda.Stream()
|
||||
total_cells = int(args.nx) * int(args.ny) * int(args.nz)
|
||||
|
||||
# Warmup before measurement window.
|
||||
warmup_done = 0
|
||||
while warmup_done < int(args.warmup_steps):
|
||||
chunk = min(int(args.batch_steps), int(args.warmup_steps) - warmup_done)
|
||||
sim.stepper.step(chunk, action_gpu=sim.bodies.action_gpu, obs_gpu=sim.bodies.obs_gpu, stream=stream)
|
||||
warmup_done += chunk
|
||||
stream.synchronize()
|
||||
|
||||
measured_batch_s: List[float] = []
|
||||
measured_steps = int(args.steps)
|
||||
done = 0
|
||||
t0 = time.perf_counter()
|
||||
while done < measured_steps:
|
||||
chunk = min(int(args.batch_steps), measured_steps - done)
|
||||
step_start = time.perf_counter()
|
||||
sim.stepper.step(chunk, action_gpu=sim.bodies.action_gpu, obs_gpu=sim.bodies.obs_gpu, stream=stream)
|
||||
stream.synchronize()
|
||||
step_end = time.perf_counter()
|
||||
measured_batch_s.append(step_end - step_start)
|
||||
|
||||
done += chunk
|
||||
global_step = sim.stepper.step_count
|
||||
_maybe_probe_macroscopic(sim, global_step, int(args.macro_every), int(args.macro_repeat))
|
||||
_maybe_probe_ddf(sim, global_step, int(args.ddf_every), int(args.ddf_repeat))
|
||||
_maybe_checkpoint(sim, global_step, int(args.checkpoint_every), ckpt_dir)
|
||||
_maybe_probe_obs(sim, stream, global_step, int(args.obs_every))
|
||||
|
||||
stream.synchronize()
|
||||
elapsed_s = time.perf_counter() - t0
|
||||
|
||||
# Optional final sanity readback (outside core timing path by default).
|
||||
if args.final_macro_snapshot:
|
||||
_ = sim.get_macroscopic()
|
||||
|
||||
sim.close()
|
||||
|
||||
mlups = (total_cells * measured_steps) / max(elapsed_s, 1e-12) / 1.0e6
|
||||
batch_ms = [1000.0 * x for x in measured_batch_s]
|
||||
batch_ms_sorted = sorted(batch_ms)
|
||||
p50 = batch_ms_sorted[len(batch_ms_sorted) // 2] if batch_ms_sorted else 0.0
|
||||
p90 = batch_ms_sorted[min(len(batch_ms_sorted) - 1, int(0.9 * (len(batch_ms_sorted) - 1)))] if batch_ms_sorted else 0.0
|
||||
|
||||
return {
|
||||
"benchmark": "celerislab_stepper_baseline",
|
||||
"device_id": int(args.device_id),
|
||||
"lattice_model": args.lattice_model,
|
||||
"grid": {"nx": int(args.nx), "ny": int(args.ny), "nz": int(args.nz)},
|
||||
"collision": str(args.collision).upper(),
|
||||
"inlet_scheme": str(args.inlet_scheme),
|
||||
"streaming": str(args.streaming),
|
||||
"store_precision": str(args.store_precision).upper(),
|
||||
"steps": measured_steps,
|
||||
"warmup_steps": int(args.warmup_steps),
|
||||
"batch_steps": int(args.batch_steps),
|
||||
"mlups": float(mlups),
|
||||
"elapsed_s": float(elapsed_s),
|
||||
"batch_ms_p50": float(p50),
|
||||
"batch_ms_p90": float(p90),
|
||||
"overhead_switches": {
|
||||
"macro_every": int(args.macro_every),
|
||||
"macro_repeat": int(args.macro_repeat),
|
||||
"ddf_every": int(args.ddf_every),
|
||||
"ddf_repeat": int(args.ddf_repeat),
|
||||
"checkpoint_every": int(args.checkpoint_every),
|
||||
"obs_every": int(args.obs_every),
|
||||
"with_cylinder": bool(args.with_cylinder),
|
||||
"final_macro_snapshot": bool(args.final_macro_snapshot),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
ap = argparse.ArgumentParser(description="CelerisLab pure-step performance baseline")
|
||||
ap.add_argument("--device-id", type=int, default=0)
|
||||
ap.add_argument("--lattice-model", choices=("D2Q9", "D3Q19"), default="D3Q19")
|
||||
ap.add_argument("--nx", type=int, default=256)
|
||||
ap.add_argument("--ny", type=int, default=256)
|
||||
ap.add_argument("--nz", type=int, default=256)
|
||||
ap.add_argument("--steps", type=int, default=3000)
|
||||
ap.add_argument("--warmup-steps", type=int, default=400)
|
||||
ap.add_argument("--batch-steps", type=int, default=100)
|
||||
|
||||
ap.add_argument("--collision", choices=("SRT", "TRT", "MRT"), default="SRT")
|
||||
ap.add_argument("--streaming", choices=("double_buffer", "esopull"), default="double_buffer")
|
||||
ap.add_argument("--store-precision", choices=("FP32", "FP16S"), default="FP32")
|
||||
ap.add_argument("--ddf-shifting", action="store_true")
|
||||
ap.add_argument("--enable-les", action="store_true")
|
||||
ap.add_argument("--outlet-mode", choices=("neq_extrap", "zero_gradient", "blended"), default="neq_extrap")
|
||||
ap.add_argument("--inlet-profile", choices=("uniform", "parabolic"), default="uniform")
|
||||
ap.add_argument(
|
||||
"--inlet-scheme",
|
||||
choices=("zou_he_local", "channel_stabilized", "equilibrium", "regularized"),
|
||||
default="zou_he_local",
|
||||
)
|
||||
ap.add_argument("--y-wall-bc", choices=("bounce_back", "free_slip"), default="bounce_back")
|
||||
|
||||
ap.add_argument("--viscosity", type=float, default=0.0035)
|
||||
ap.add_argument("--velocity", type=float, default=0.03)
|
||||
ap.add_argument("--rho", type=float, default=1.0)
|
||||
ap.add_argument("--threads-per-block", type=int, default=256)
|
||||
ap.add_argument("--compute-capability", type=str, default="auto")
|
||||
|
||||
# Overhead attribution toggles.
|
||||
ap.add_argument("--macro-every", type=int, default=0, help="Call get_macroscopic() every N steps (0=off)")
|
||||
ap.add_argument("--macro-repeat", type=int, default=1, help="Repeat get_macroscopic() calls per probe step")
|
||||
ap.add_argument("--ddf-every", type=int, default=0, help="Call get_ddf() every N steps (0=off)")
|
||||
ap.add_argument("--ddf-repeat", type=int, default=1, help="Repeat get_ddf() calls per probe step")
|
||||
ap.add_argument("--checkpoint-every", type=int, default=0, help="Save checkpoint every N steps (0=off)")
|
||||
ap.add_argument("--obs-every", type=int, default=0, help="Download object obs every N steps (0=off)")
|
||||
ap.add_argument("--with-cylinder", action="store_true", help="Inject one cylinder object (needed for obs probes)")
|
||||
ap.add_argument("--final-macro-snapshot", action="store_true")
|
||||
|
||||
ap.add_argument("--json-out", type=str, default="", help="Optional path to save metrics JSON")
|
||||
return ap.parse_args()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
result = run(args)
|
||||
print(json.dumps(result, indent=2))
|
||||
if args.json_out.strip():
|
||||
out = os.path.abspath(args.json_out)
|
||||
os.makedirs(os.path.dirname(out), exist_ok=True)
|
||||
_write_json(out, result)
|
||||
print(f"Wrote: {out}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in new issue
Block a user