diff --git a/bench/README.md b/bench/README.md index 1719f552..0f562c62 100644 --- a/bench/README.md +++ b/bench/README.md @@ -1,24 +1,24 @@ -# Environment benchmarks +# Benchmarks -Activate the project environment before running: +All benchmarks live in the `mxlab` CLI. -```bash -source .venv/bin/activate +## Environment `env.step()` benchmark +```bash # Whole-machine throughput: 10 seconds, one worker per usable NUMA node -python bench/bench_env.py --env g1-wbt-dance +mxlab bench --mode env --env g1-wbt-dance # Batch sweep with JSONL output -python bench/bench_env.py --env g1-wbt-dance --duration 10 --num-envs 2048 8192 --json +mxlab bench --mode env --env g1-wbt-dance --duration 10 --num-envs 2048 8192 --json # Compare two environments -python bench/bench_env.py --env cartpole --compare-env cartpole --duration 5 +mxlab bench --mode env --env cartpole --compare-env cartpole --duration 5 # Single process, preserving the caller's CPU/memory placement -python bench/bench_env.py --duration 5 --numa single +mxlab bench --mode env --duration 5 --numa single # Single-process step latency and CPU utilization -python bench/bench_env.py --steps 1000 --num-envs 2048 +mxlab bench --mode env --steps 1000 --num-envs 2048 ``` `--num-envs` is the total batch, split across usable NUMA nodes. Workers respect @@ -31,4 +31,24 @@ finish after the deadline are excluded. This measures `env.step()`, not policy inference or training data transfer. JSONL includes per-worker counts and placement. `--steps` and `--duration` are mutually exclusive. `--breakdown` requires step-count -mode and cannot be combined with `--json`. See `--help` for all options. +mode and cannot be combined with `--json`. + +## MotrixSim physics-only benchmark + +```bash +# In-process measurement of one robot +mxlab bench --mode physics --robot g1-29dof --num-envs 2048 + +# One pinned worker per NUMA node via numactl +mxlab bench --mode physics --numa shard +``` + +Times `model.step_n(data, nstep)` on a robot-on-flat-ground scene with state reset +before every timed call; no task env, no observation/reward/termination. `--numa` +selects `single`, `node` (pinned to node 0), or `shard` (one numactl-pinned worker +per NUMA node; requires numactl on PATH). + +See `mxlab bench --help` for all options. + +The remaining script in this directory, `bench_live_metrics.py`, is a standalone +simulator-side tool that is not part of the CLI. diff --git a/bench/env_benchmark/__init__.py b/motrixlab/src/motrixlab/benchmark/__init__.py similarity index 100% rename from bench/env_benchmark/__init__.py rename to motrixlab/src/motrixlab/benchmark/__init__.py diff --git a/bench/bench_physics.py b/motrixlab/src/motrixlab/benchmark/physics.py similarity index 74% rename from bench/bench_physics.py rename to motrixlab/src/motrixlab/benchmark/physics.py index 7bbe0189..d927af55 100644 --- a/bench/bench_physics.py +++ b/motrixlab/src/motrixlab/benchmark/physics.py @@ -18,10 +18,13 @@ shard spawn one worker per NUMA node, each pinned via numactl, each running ``--num-envs / num_nodes`` envs concurrently; aggregate throughput is the sum across workers. Reproduces the per-node process sharding - validated for per-node process sharding on a many-core host. + validated on a many-core host. Reports per-call latency (median + p10/p90), batch steps/s (step_n calls per second over all parallel envs in the process), and total env steps/s. + +This module imports the simulation runtime at import time and is therefore +loaded lazily by ``motrixlab.commands.bench`` only for ``--mode physics``. """ from __future__ import annotations @@ -38,12 +41,25 @@ import motrixsim as mtx import numpy as np -from report import render_table from motrix_env_core import registry from motrix_env_core.base import SimCfg from motrix_env_motrixsim.compiler import MotrixSimSceneCompiler from motrix_envs.config.scene import StandardSceneCfg, StandardSceneObjsCfg +from motrixlab.benchmark.report import render_table + +RESULT_HEADERS = [ + "mode", + "robot", + "num_envs", + "median ms", + "p10 ms", + "p90 ms", + "batch steps/s", + "total env steps/s", +] + +_WARMUP_DEFAULT = 20 def _detect_num_nodes() -> int: @@ -76,12 +92,11 @@ def _current_binding() -> str: def _device_context() -> dict: - ctx = { + return { "num_cpus": os.cpu_count(), "num_numa_nodes": _detect_num_nodes(), "binding": _current_binding(), } - return ctx def measure( @@ -151,18 +166,6 @@ def reset_state() -> None: } -_RESULT_HEADERS = [ - "mode", - "robot", - "num_envs", - "median ms", - "p10 ms", - "p90 ms", - "batch steps/s", - "total env steps/s", -] - - def _result_row(label: str, r: dict) -> list[str]: return [ label, @@ -176,22 +179,13 @@ def _result_row(label: str, r: dict) -> list[str]: ] -def run_single(args) -> tuple[list[list[str]], list[str]]: - r = measure( - robot=args.robot, - num_envs=args.num_envs, - dt=args.dt, - solver_iterations=args.solver_iterations, - nstep=args.nstep, - warmup=args.warmup, - iters=args.iters, - ) +def run_single(args: argparse.Namespace) -> tuple[list[list[str]], list[str]]: + r = _measure_from_args(args) return ([_result_row(f"single-process ({args.numa})", r)], []) -def _worker_entry(args) -> None: - """Internal: run one measurement and write JSON to --out-json. Used by node/shard drivers.""" - r = measure( +def _measure_from_args(args: argparse.Namespace) -> dict: + return measure( robot=args.robot, num_envs=args.num_envs, dt=args.dt, @@ -200,23 +194,31 @@ def _worker_entry(args) -> None: warmup=args.warmup, iters=args.iters, ) + + +def _worker_entry(args: argparse.Namespace) -> None: + """Internal: run one measurement and write JSON to --out-json. Used by node/shard drivers.""" + r = _measure_from_args(args) r["node"] = args.node Path(args.out_json).write_text(json.dumps(r), encoding="utf-8") -def _numactl_wrap(node: int, py_args: list[str]) -> list[str]: +def _worker_command(args: argparse.Namespace, node: int, num_envs: int, out_json: str) -> list[str]: + """Build a numactl-wrapped ``python -m`` command for one pinned worker.""" numactl = shutil.which("numactl") if numactl is None: raise RuntimeError("numactl not found on PATH; install numactl for --numa node/shard") - return [numactl, "--cpunodebind", str(node), "--membind", str(node)] + py_args - - -def _measure_via_subprocess(args, node: int, num_envs: int, out_json: str) -> dict: - """Spawn a pinned worker that runs `measure` and writes JSON; block until done.""" - script = str(Path(sys.argv[0]).resolve()) - py_args = [ + return [ + numactl, + "--cpunodebind", + str(node), + "--membind", + str(node), sys.executable, - script, + "-m", + "motrixlab.commands.bench", + "--mode", + "physics", "--numa", "_worker", "--node", @@ -234,17 +236,13 @@ def _measure_via_subprocess(args, node: int, num_envs: int, out_json: str) -> di "--warmup", str(args.warmup), "--iters", - args.iters if isinstance(args.iters, str) else str(args.iters), + str(args.iters), "--out-json", out_json, ] - cmd = _numactl_wrap(node, py_args) - env = os.environ.copy() - subprocess.run(cmd, check=True, env=env) - return json.loads(Path(out_json).read_text()) -def run_node(args) -> tuple[list[list[str]], list[str]]: +def run_node(args: argparse.Namespace) -> tuple[list[list[str]], list[str]]: """Re-exec the measurement pinned to NUMA node 0.""" notes: list[str] = [] num_nodes = _detect_num_nodes() @@ -253,13 +251,15 @@ def run_node(args) -> tuple[list[list[str]], list[str]]: with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as f: out_json = f.name try: - r = _measure_via_subprocess(args, node=0, num_envs=args.num_envs, out_json=out_json) + cmd = _worker_command(args, node=0, num_envs=args.num_envs, out_json=out_json) + subprocess.run(cmd, check=True, env=os.environ.copy()) + r = json.loads(Path(out_json).read_text()) return ([_result_row("node-pinned (cpunodebind=0, membind=0)", r)], notes) finally: Path(out_json).unlink(missing_ok=True) -def run_shard(args) -> tuple[list[list[str]], list[str]]: +def run_shard(args: argparse.Namespace) -> tuple[list[list[str]], list[str]]: """Spawn one pinned worker per NUMA node, each running num_envs/num_nodes envs.""" num_nodes = _detect_num_nodes() if num_nodes < 2: @@ -273,32 +273,7 @@ def run_shard(args) -> tuple[list[list[str]], list[str]]: fd, tmp_path = tempfile.mkstemp(suffix=f"_node{node}.json") os.close(fd) tmp_paths.append(tmp_path) - script = str(Path(sys.argv[0]).resolve()) - py_args = [ - sys.executable, - script, - "--numa", - "_worker", - "--node", - str(node), - "--num-envs", - str(per_node), - "--robot", - args.robot, - "--dt", - str(args.dt), - "--solver-iterations", - str(args.solver_iterations), - "--nstep", - str(args.nstep), - "--warmup", - str(args.warmup), - "--iters", - args.iters if isinstance(args.iters, str) else str(args.iters), - "--out-json", - tmp_path, - ] - cmd = _numactl_wrap(node, py_args) + cmd = _worker_command(args, node=node, num_envs=per_node, out_json=tmp_path) procs.append((node, subprocess.Popen(cmd, env=os.environ.copy()))) for node, p in procs: rc = p.wait() @@ -323,46 +298,24 @@ def run_shard(args) -> tuple[list[list[str]], list[str]]: return rows, notes -def main() -> None: - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--robot", default="g1-29dof") - parser.add_argument( - "--num-envs", - type=int, - nargs="+", - default=[2048], - help="Batch sizes to benchmark. Multiple values run a sweep.", - ) - parser.add_argument("--dt", type=float, default=0.005) - parser.add_argument("--solver-iterations", type=int, default=3) - parser.add_argument("--nstep", type=int, default=1, help="Native substeps per step_n call.") - parser.add_argument("--warmup", type=int, default=20) - parser.add_argument("--iters", type=int, default=200) - parser.add_argument( - "--numa", - choices=["single", "node", "shard", "_worker"], - default="single", - help="single=in-process; node=re-exec pinned to node 0; shard=one worker per NUMA node.", - ) - parser.add_argument("--node", type=int, default=0, help="(internal worker) which node.") - parser.add_argument("--out-json", type=str, default="", help="(internal worker) JSON output path.") - args = parser.parse_args() +def main(parser: argparse.ArgumentParser, args: argparse.Namespace) -> None: if any(num_envs < 1 for num_envs in args.num_envs): parser.error("--num-envs values must be positive") + if args.warmup is None: + args.warmup = _WARMUP_DEFAULT if args.numa == "_worker": if not args.out_json: - raise SystemExit("--numa _worker requires --out-json") + parser.error("--numa _worker requires --out-json") if len(args.num_envs) != 1: parser.error("internal worker mode requires exactly one --num-envs value") args.num_envs = args.num_envs[0] _worker_entry(args) return - num_envs_values = args.num_envs rows: list[list[str]] = [] notes: list[str] = [] - for num_envs in num_envs_values: + for num_envs in args.num_envs: args.num_envs = num_envs if args.numa == "single": batch_rows, batch_notes = run_single(args) @@ -376,7 +329,7 @@ def main() -> None: notes.append(note) context = _device_context() - print(render_table(_RESULT_HEADERS, rows)) + print(render_table(RESULT_HEADERS, rows)) print( f"sim: robot={args.robot} dt={args.dt}s solver_iterations={args.solver_iterations} " f"nstep={args.nstep} warmup={args.warmup} iters={args.iters}" @@ -384,7 +337,3 @@ def main() -> None: print(f"context: cpus={context['num_cpus']} numa_nodes={context['num_numa_nodes']} binding=[{context['binding']}]") for note in notes: print(note) - - -if __name__ == "__main__": - main() diff --git a/bench/report.py b/motrixlab/src/motrixlab/benchmark/report.py similarity index 100% rename from bench/report.py rename to motrixlab/src/motrixlab/benchmark/report.py diff --git a/bench/env_benchmark/runner.py b/motrixlab/src/motrixlab/benchmark/runner.py similarity index 99% rename from bench/env_benchmark/runner.py rename to motrixlab/src/motrixlab/benchmark/runner.py index e943d0ce..b74de4c5 100644 --- a/bench/env_benchmark/runner.py +++ b/motrixlab/src/motrixlab/benchmark/runner.py @@ -87,7 +87,7 @@ def run_steps( results.append( { "env": name, - "mode": args.mode, + "mode": args.env_mode, "num_envs": num_envs, "steps": args.steps, "warmup": args.warmup, @@ -222,7 +222,7 @@ def run_duration(args: argparse.Namespace, num_envs: int) -> list[dict[str, obje results.append( { "env": name, - "mode": args.mode, + "mode": args.env_mode, "num_envs": num_envs, "num_envs_scope": "total", "duration_seconds": args.duration, diff --git a/bench/env_benchmark/workload.py b/motrixlab/src/motrixlab/benchmark/workload.py similarity index 90% rename from bench/env_benchmark/workload.py rename to motrixlab/src/motrixlab/benchmark/workload.py index 20a8c713..e8a25648 100644 --- a/bench/env_benchmark/workload.py +++ b/motrixlab/src/motrixlab/benchmark/workload.py @@ -18,9 +18,10 @@ def load_runtime(): import numpy as np from motrix_env_core import registry + from motrix_env_core.direct.env import DirectEnv from motrix_env_core.manager import ManagerEnv - return np, registry, ManagerEnv + return np, registry, (DirectEnv, ManagerEnv) def prepare_env( @@ -32,7 +33,7 @@ def prepare_env( ): """Create an environment and compatible fixed actions, checking comparisons.""" np, registry, manager_env_type = load_runtime() - kwargs = {"mode": args.mode, "num_envs": num_envs} + kwargs = {"mode": args.env_mode, "num_envs": num_envs} if args.sim_backend is not None: kwargs["sim_backend"] = args.sim_backend env = registry.make(name, **kwargs) @@ -61,8 +62,8 @@ def prepare_env( # Compare runs pass the previous env's actions through; canonicalize # back to numpy so every env receives identically valued actions. actions = np.asarray(actions) - # Manager environments consume tensor actions; hand each env a - # zero-copy tensor view of the same canonical numpy values. + # Direct and Manager environments both consume tensor actions; hand each + # env a zero-copy tensor view of the same canonical numpy values. if isinstance(env, manager_env_type): import torch diff --git a/motrixlab/src/motrixlab/cli.py b/motrixlab/src/motrixlab/cli.py index 0393c348..b715a9ea 100644 --- a/motrixlab/src/motrixlab/cli.py +++ b/motrixlab/src/motrixlab/cli.py @@ -15,6 +15,7 @@ "export-onnx": "motrix_rl.commands.export_onnx", "export-deploy": "motrix_rl.commands.export_deploy", "deploy": "motrix_deploy.cli", + "bench": "motrixlab.commands.bench", } diff --git a/bench/bench_env.py b/motrixlab/src/motrixlab/commands/bench.py similarity index 63% rename from bench/bench_env.py rename to motrixlab/src/motrixlab/commands/bench.py index 144de0ff..4d6fce07 100644 --- a/bench/bench_env.py +++ b/motrixlab/src/motrixlab/commands/bench.py @@ -1,11 +1,13 @@ # Copyright Motphys Technology Co., Ltd. 2025, 2026 # SPDX-License-Identifier: Apache-2.0 -"""Benchmark the public ``env.step()`` contract for one or two environments. +"""``mxlab bench``: performance measurement entry point. -The reported CPU utilization is process CPU time divided by wall-clock time -over the timed ``env.step()`` calls. It is an aggregate across the process's -threads and can therefore exceed 100% when multiple CPU cores are busy. +``bench env`` benchmarks the public ``env.step()`` contract for one or two +environments. The reported CPU utilization is process CPU time divided by +wall-clock time over the timed ``env.step()`` calls. It is an aggregate across +the process's threads and can therefore exceed 100% when multiple CPU cores +are busy. """ from __future__ import annotations @@ -15,12 +17,8 @@ import math from typing import TYPE_CHECKING -if __package__: - from .env_benchmark import runner - from .report import render_table -else: - from env_benchmark import runner - from report import render_table +from motrixlab.benchmark import runner +from motrixlab.benchmark.report import render_table if TYPE_CHECKING: from motrix_env_core.perf import PerfNode @@ -69,31 +67,57 @@ def _result_row(result: dict[str, object], speedup: float | None) -> list[str]: ] -def main() -> None: - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--env", default="cartpole", help="Reference registered environment name.") - parser.add_argument("--compare-env", help="Optional registered environment to compare with --env.") - parser.add_argument("--mode", default="train", help="Environment mode (train/play).") +def _build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(prog="mxlab bench", description=__doc__) + parser.add_argument("--mode", choices=("env", "physics"), required=True, help="Benchmark target.") + parser.add_argument("--env", default="cartpole", help="(env) Reference registered environment name.") + parser.add_argument("--compare-env", help="(env) Optional registered environment to compare with --env.") + parser.add_argument("--env-mode", default="train", help="(env) Environment mode (train/play).", dest="env_mode") + parser.add_argument("--robot", default="g1-29dof", help="(physics) Registered robot name.") parser.add_argument( - "--num-envs", type=int, nargs="+", default=[2048], help="Total environment batch sizes to benchmark." + "--num-envs", + type=int, + nargs="+", + default=[2048], + help="Total environment batch sizes to benchmark.", ) measurement = parser.add_mutually_exclusive_group() - measurement.add_argument("--steps", type=int, help="Timed calls; selects legacy single-process mode.") - measurement.add_argument("--duration", type=float, help="Positive finite measurement seconds (default: 10).") + measurement.add_argument("--steps", type=int, help="(env) Timed calls; selects legacy single-process mode.") + measurement.add_argument("--duration", type=float, help="(env) Positive finite measurement seconds (default: 10).") + parser.add_argument( + "--numa", + choices=("single", "node", "shard", "_worker"), + help="Placement (env: single/shard, default shard for duration and single for steps; " + "physics: single/node/shard, default single).", + ) parser.add_argument( - "--numa", choices=("single", "shard"), help="Placement (default: shard for duration, single for steps)." + "--warmup", + type=int, + help="Untimed calls per measurement (default: 10 for env, 20 for physics).", ) - parser.add_argument("--warmup", type=int, default=10, help="Untimed env.step() calls per measurement.") parser.add_argument("--seed", type=int, default=20260808) parser.add_argument( "--zero-actions", action="store_true", - help="Use zero actions instead of seeded random actions.", + help="(env) Use zero actions instead of seeded random actions.", ) - parser.add_argument("--sim-backend", help="Optional simulation backend override.") - parser.add_argument("--breakdown", action="store_true", help="Print the environment Perf scope tree.") - parser.add_argument("--json", action="store_true", help="Print one JSON object per result.") - args = parser.parse_args() + parser.add_argument("--sim-backend", help="(env) Optional simulation backend override.") + parser.add_argument("--breakdown", action="store_true", help="(env) Print the environment Perf scope tree.") + parser.add_argument("--json", action="store_true", help="(env) Print one JSON object per result.") + parser.add_argument("--dt", type=float, default=0.005, help="(physics) Simulation timestep in seconds.") + parser.add_argument("--solver-iterations", type=int, default=3, help="(physics) Constraint solver iterations.") + parser.add_argument("--nstep", type=int, default=1, help="(physics) Native substeps per step_n call.") + parser.add_argument("--iters", type=int, default=200, help="(physics) Timed step_n calls per measurement.") + parser.add_argument("--node", type=int, default=0, help="(physics, internal worker) which NUMA node.") + parser.add_argument("--out-json", default="", help="(physics, internal worker) JSON output path.") + return parser + + +def _run_env(parser: argparse.ArgumentParser, args: argparse.Namespace) -> None: + if args.numa not in (None, "single", "shard"): + parser.error("--numa node/_worker only apply to --mode physics") + if args.warmup is None: + args.warmup = 10 if args.steps is None and args.duration is None: args.duration = 10.0 if args.numa is None: @@ -160,7 +184,7 @@ def main() -> None: return rows: list[list[str]] = [] - perf_sections: list[tuple[str, int, runner.ProfileResult]] = [] + perf_sections: list[tuple[str, int, object]] = [] for num_envs in args.num_envs: results, profiles = runner.run_steps(args, num_envs) reference = results[0] @@ -187,5 +211,20 @@ def main() -> None: _print_perf_tree(profile.root) +def main() -> None: + parser = _build_parser() + args = parser.parse_args() + if args.mode == "env": + _run_env(parser, args) + else: + # Imported lazily so the env mode (and the parent process of shard + # workers) never loads the simulation runtime. + from motrixlab.benchmark import physics + + if args.numa is None: + args.numa = "single" + physics.main(parser, args) + + if __name__ == "__main__": main() diff --git a/motrixlab/tests/test_mxlab_cli.py b/motrixlab/tests/test_mxlab_cli.py index 079d246e..5c68efcf 100644 --- a/motrixlab/tests/test_mxlab_cli.py +++ b/motrixlab/tests/test_mxlab_cli.py @@ -20,6 +20,7 @@ ("export-onnx", "motrix_rl.commands.export_onnx"), ("export-deploy", "motrix_rl.commands.export_deploy"), ("deploy", "motrix_deploy.cli"), + ("bench", "motrixlab.commands.bench"), ], ) def test_dispatch_forwards_arguments_and_exit_status(monkeypatch, command, module): diff --git a/tests/test_bench_duration.py b/tests/test_bench_duration.py index ec72c9f7..a19a503a 100644 --- a/tests/test_bench_duration.py +++ b/tests/test_bench_duration.py @@ -5,8 +5,8 @@ import multiprocessing as mp import pytest +from motrixlab.benchmark import runner as duration -from bench.env_benchmark import runner as duration from motrix_rl.numa import NodePlacement @@ -91,7 +91,13 @@ def test_startup_timeout_reaps_spawn_workers(monkeypatch): def _args(): return argparse.Namespace( - numa="shard", duration=0.05, env="reference", compare_env="compared", mode="train", warmup=0, zero_actions=True + numa="shard", + duration=0.05, + env="reference", + compare_env="compared", + env_mode="train", + warmup=0, + zero_actions=True, ) diff --git a/tests/test_bench_env_cli.py b/tests/test_bench_env_cli.py index 1b10a766..db2707c6 100644 --- a/tests/test_bench_env_cli.py +++ b/tests/test_bench_env_cli.py @@ -4,19 +4,18 @@ import sys import pytest - -from bench import bench_env -from bench.env_benchmark import runner, workload +from motrixlab.benchmark import runner, workload +from motrixlab.commands import bench @pytest.fixture def benchmark(): - return bench_env + return bench def test_default_is_whole_machine_duration_without_parent_runtime_import(benchmark, monkeypatch): calls = [] - monkeypatch.setattr(sys, "argv", ["bench_env.py", "--json"]) + monkeypatch.setattr(sys, "argv", ["mxlab bench", "--mode", "env", "--json"]) monkeypatch.setattr(workload, "load_runtime", lambda: pytest.fail("parent must stay lightweight")) def run(args, total): @@ -45,7 +44,7 @@ def run(args, total): ], ) def test_invalid_measurement_options_fail_before_runtime(benchmark, monkeypatch, arguments): - monkeypatch.setattr(sys, "argv", ["bench_env.py", *arguments]) + monkeypatch.setattr(sys, "argv", ["mxlab bench", "--mode", "env", *arguments]) monkeypatch.setattr(workload, "load_runtime", lambda: pytest.fail("must not load runtime")) with pytest.raises(SystemExit) as error: benchmark.main() diff --git a/tests/test_bench_numa.py b/tests/test_bench_numa.py index c3fc6f79..fdfe3e7a 100644 --- a/tests/test_bench_numa.py +++ b/tests/test_bench_numa.py @@ -2,8 +2,8 @@ # SPDX-License-Identifier: Apache-2.0 import pytest +from motrixlab.benchmark.runner import split_num_envs -from bench.env_benchmark.runner import split_num_envs from motrix_rl import numa diff --git a/tests/test_bench_workload.py b/tests/test_bench_workload.py index a3371104..2f0c3792 100644 --- a/tests/test_bench_workload.py +++ b/tests/test_bench_workload.py @@ -8,8 +8,7 @@ import numpy as np import pytest - -from bench.env_benchmark import workload +from motrixlab.benchmark import workload def test_benchmark_modules_import_without_runtime(): @@ -17,8 +16,8 @@ def test_benchmark_modules_import_without_runtime(): [ sys.executable, "-c", - "import sys; import bench.bench_env; import bench.env_benchmark.runner; " - "import bench.env_benchmark.workload; " + "import sys; import motrixlab.commands.bench; import motrixlab.benchmark.runner; " + "import motrixlab.benchmark.workload; " "assert not {'numpy', 'torch', 'motrixsim'} & sys.modules.keys()", ], check=True, @@ -39,7 +38,7 @@ def test_comparison_reuses_actions_and_closes_incompatible_environment(monkeypat environments = iter([reference, compared, incompatible]) registry = SimpleNamespace(make=lambda *args, **kwargs: next(environments)) monkeypatch.setattr(workload, "load_runtime", lambda: (np, registry, dict)) - args = SimpleNamespace(mode="train", sim_backend=None, zero_actions=False, seed=123) + args = SimpleNamespace(env_mode="train", sim_backend=None, zero_actions=False, seed=123) env, actions, spec = workload.prepare_env("reference", args, 3) assert env is reference assert actions.shape == (3, 2)