diff --git a/e2e_challenge/README.md b/e2e_challenge/README.md index 6407a79c..edfa4967 100644 --- a/e2e_challenge/README.md +++ b/e2e_challenge/README.md @@ -7,6 +7,7 @@ and wait for approval. 1. Build a Docker image that serves the AlpaSim driver gRPC API. Start with the [starter kit](starter_kit/README.md) for a minimal example or the [VAVAM sample submission](sample_submission_vavam/README.md) for a richer model-backed example, then customize and test locally. 1. Push the image to your team's ECR repository and use the challenge CLI to submit the image URI for evaluation. 1. Wait for evaluation and check status or leaderboard results. +1. For questions or concerns, please submit [github issues](https://github.com/NVlabs/alpasim/issues) ## Competition Resources diff --git a/plugins/mtgs/server/engine/mtgs.py b/plugins/mtgs/server/engine/mtgs.py index 1c49397a..74a169ba 100644 --- a/plugins/mtgs/server/engine/mtgs.py +++ b/plugins/mtgs/server/engine/mtgs.py @@ -559,7 +559,6 @@ def local2global_translation_xy(self) -> Optional[np.ndarray]: class MTGSAssetManager: - def __init__(self, asset_folder_path: Path, device: torch.device): self.asset_folder_path = asset_folder_path self.current_asset_id = None @@ -594,10 +593,23 @@ def load_asset(self): ) road_height_map_path = self.asset_dir / "road_height_map" - self.road_height_map = dict( - map=np.load(road_height_map_path / "road_height_map.npy"), - sim2=Sim2.from_json(road_height_map_path / "sim2.json"), - ) + road_height_map_file = road_height_map_path / "road_height_map.npy" + road_height_sim2_file = road_height_map_path / "sim2.json" + if road_height_map_file.exists() and road_height_sim2_file.exists(): + self.road_height_map = dict( + map=np.load(road_height_map_file), + sim2=Sim2.from_json(road_height_sim2_file), + ) + else: + # Older MTGS challenge bundles contain only the background checkpoint + # and video metadata. The current renderer does not consume + # ``road_height_map`` after loading, so preserve compatibility with + # those bundles while keeping the richer artifact format supported. + self.road_height_map = None + logger.warning( + "Asset %s has no road_height_map; continuing without it", + self.current_asset_id, + ) video_scene_dict_path = self.asset_dir / "video_scene_dict.pkl" self.video_scene_dict = None diff --git a/plugins/mtgs/server/main.py b/plugins/mtgs/server/main.py index 195fcc46..747a9bee 100644 --- a/plugins/mtgs/server/main.py +++ b/plugins/mtgs/server/main.py @@ -10,6 +10,7 @@ """ import argparse +import functools import logging from concurrent import futures from pathlib import Path @@ -45,6 +46,29 @@ } +def _asset_folder_map_from_extra_params(extra_params) -> dict[str, str]: + """Return a validated scene-to-MTGS-asset mapping from dataset config.""" + raw_mapping = extra_params.get("asset_folder_map") + if raw_mapping is None: + return {} + if not hasattr(raw_mapping, "items"): + raise ValueError( + "scene_provider.trajdata.dataset.extra_params.asset_folder_map " + "must be a mapping of scene IDs to asset folder IDs." + ) + + mapping: dict[str, str] = {} + for scene_id, asset_folder in raw_mapping.items(): + if not isinstance(scene_id, str) or not scene_id: + raise ValueError("asset_folder_map scene IDs must be non-empty strings.") + if not isinstance(asset_folder, str) or not asset_folder: + raise ValueError( + f"asset_folder_map[{scene_id!r}] must be a non-empty string." + ) + mapping[scene_id] = asset_folder + return mapping + + def parse_args(arg_list: list[str] | None = None) -> argparse.Namespace: parser = argparse.ArgumentParser( description="MTGS Sensorsim Service Server", @@ -59,7 +83,7 @@ def parse_args(arg_list: list[str] | None = None) -> argparse.Namespace: "--cache-size", type=int, default=2, - help="LRU cache size for renderer instances", + help="LRU cache size for renderer instances and scene data sources", ) parser.add_argument("--device", type=str, default="cuda", choices=["cuda", "cpu"]) parser.add_argument( @@ -73,6 +97,7 @@ def parse_args(arg_list: list[str] | None = None) -> argparse.Namespace: def create_get_scene_function( user_config: UserSimulatorConfig, + cache_size: int = 2, ) -> tuple[Callable, Callable]: if not TRAJDATA_AVAILABLE: raise ImportError("trajdata is required for MTGS sensorsim server.") @@ -97,6 +122,12 @@ def create_get_scene_function( mapped_name = DATASET_NAME_MAPPING.get(dataset_config.name, dataset_config.name) mtgs_asset_base_path = str(Path(asset_base_path_config) / mapped_name / "assets") logger.info(f"MTGS asset path: {mtgs_asset_base_path}") + asset_folder_map = _asset_folder_map_from_extra_params(extra_params) + if asset_folder_map: + logger.info( + "Loaded %d explicit scene-to-asset-folder mappings", + len(asset_folder_map), + ) params = trajdata_provider_config_to_params(trajdata_config) logger.info("Creating UnifiedDataset from config") @@ -106,12 +137,8 @@ def create_get_scene_function( scene_id_to_idx = scene_name_to_index_from_dataset(dataset) logger.info(f"Built scene_id mapping for {len(scene_id_to_idx)} scenes") - scene_cache = {} - + @functools.lru_cache(maxsize=cache_size) def get_scene(scene_id: str) -> TrajdataDataSource: - if scene_id in scene_cache: - return scene_cache[scene_id] - scene_idx = scene_id_to_idx.get(scene_id) if scene_idx is None: raise KeyError(f"Scene {scene_id} not found in dataset") @@ -127,9 +154,17 @@ def get_scene(scene_id: str) -> TrajdataDataSource: vector_map_params=dataset.vector_map_params, smooth_trajectories=user_config.smooth_trajectories, asset_base_path=mtgs_asset_base_path, + asset_folder_resolver=( + ( + lambda resolved_scene: asset_folder_map.get( + resolved_scene.name, resolved_scene.name + ) + ) + if asset_folder_map + else None + ), ) - scene_cache[scene_id] = data_source logger.info(f"Loaded scene {scene_id}, asset_path={data_source.asset_path}") return data_source @@ -167,7 +202,9 @@ def main(arg_list: list[str] | None = None) -> None: return try: - get_scene, get_available_scene_ids = create_get_scene_function(user_config) + get_scene, get_available_scene_ids = create_get_scene_function( + user_config, cache_size=args.cache_size + ) except Exception as e: logger.error(f"Failed to create get_scene function: {e}") return diff --git a/plugins/mtgs/tests/test_mtgs_plugin.py b/plugins/mtgs/tests/test_mtgs_plugin.py index ae378cd5..c9d18873 100644 --- a/plugins/mtgs/tests/test_mtgs_plugin.py +++ b/plugins/mtgs/tests/test_mtgs_plugin.py @@ -96,6 +96,22 @@ def test_mtgs_engine_importable(): pytest.skip(f"Server dependencies not installed: {e}") +def test_mtgs_asset_manager_allows_missing_road_height_map(tmp_path, monkeypatch): + import torch + from alpasim_mtgs.server.engine.mtgs import MTGSAssetManager + + asset_id = "legacy-asset" + background_dir = tmp_path / asset_id / "background" + background_dir.mkdir(parents=True) + (background_dir / f"{asset_id}.ckpt").touch() + monkeypatch.setattr(torch, "load", lambda *args, **kwargs: {}) + + manager = MTGSAssetManager(tmp_path, torch.device("cpu")) + manager.reset(asset_id) + + assert manager.road_height_map is None + + def _mtgs_user_config( *, extra_params: dict | None = None, @@ -176,7 +192,13 @@ def get_scene(self, idx): def fake_trajdata_data_source(**kwargs): captured["data_source_kwargs"] = kwargs - return SimpleNamespace(asset_path="/tmp/mtgs-assets/navtest/assets/scene-a") + resolver = kwargs.get("asset_folder_resolver") + asset_folder = ( + resolver(kwargs["scene"]) if resolver is not None else kwargs["scene"].name + ) + return SimpleNamespace( + asset_path=f"/tmp/mtgs-assets/navtest/assets/{asset_folder}" + ) monkeypatch.setattr(mtgs_main, "TRAJDATA_AVAILABLE", True) monkeypatch.setattr(mtgs_main, "UnifiedDataset", FakeUnifiedDataset) @@ -184,7 +206,10 @@ def fake_trajdata_data_source(**kwargs): get_scene, get_available_scene_ids = mtgs_main.create_get_scene_function( _mtgs_user_config( - extra_params={"asset_base_path": "/tmp/mtgs-assets"}, + extra_params={ + "asset_base_path": "/tmp/mtgs-assets", + "asset_folder_map": {"scene-a": "asset-b"}, + }, vector_map_params={"incl_road_lanes": True, "incl_road_edges": True}, ) ) @@ -192,10 +217,13 @@ def fake_trajdata_data_source(**kwargs): assert get_available_scene_ids() == ["scene-a"] data_source = get_scene("scene-a") - assert data_source.asset_path == "/tmp/mtgs-assets/navtest/assets/scene-a" + assert data_source.asset_path == "/tmp/mtgs-assets/navtest/assets/asset-b" assert captured["dataset_params"]["desired_data"] == ["nuplan_test"] assert captured["dataset_params"]["dataset_kwargs"] == { - "nuplan_test": {"asset_base_path": "/tmp/mtgs-assets"} + "nuplan_test": { + "asset_base_path": "/tmp/mtgs-assets", + "asset_folder_map": {"scene-a": "asset-b"}, + } } assert captured["cache_scene"].name == "scene-a" @@ -208,3 +236,69 @@ def fake_trajdata_data_source(**kwargs): "incl_road_edges": True, } assert kwargs["asset_base_path"] == "/tmp/mtgs-assets/navtest/assets" + assert kwargs["asset_folder_resolver"](kwargs["scene"]) == "asset-b" + + get_unmapped_scene, _ = mtgs_main.create_get_scene_function( + _mtgs_user_config(extra_params={"asset_base_path": "/tmp/mtgs-assets"}) + ) + assert ( + get_unmapped_scene("scene-a").asset_path + == "/tmp/mtgs-assets/navtest/assets/scene-a" + ) + + +def test_mtgs_scene_loader_cache_is_bounded(monkeypatch): + from alpasim_mtgs.server import main as mtgs_main + + created_scene_ids = [] + + class FakeScene: + env_name = "nuplan_test" + + def __init__(self, name): + self.name = name + + class FakeUnifiedDataset: + def __init__(self, **params): + self.vector_map_params = params.get("vector_map_params", {}) + self._scenes = [FakeScene(f"scene-{idx}") for idx in range(3)] + + @property + def scene_name_to_index(self): + return {scene.name: idx for idx, scene in enumerate(self._scenes)} + + @property + def map_api(self): + return None + + def get_scene_cache(self, scene): + return object() + + def num_scenes(self): + return len(self._scenes) + + def get_scene(self, idx): + return self._scenes[idx] + + def fake_trajdata_data_source(**kwargs): + scene_id = kwargs["scene"].name + created_scene_ids.append(scene_id) + return SimpleNamespace(asset_path=f"/tmp/mtgs-assets/navtest/assets/{scene_id}") + + monkeypatch.setattr(mtgs_main, "TRAJDATA_AVAILABLE", True) + monkeypatch.setattr(mtgs_main, "UnifiedDataset", FakeUnifiedDataset) + monkeypatch.setattr(mtgs_main, "TrajdataDataSource", fake_trajdata_data_source) + + get_scene, _ = mtgs_main.create_get_scene_function( + _mtgs_user_config(extra_params={"asset_base_path": "/tmp/mtgs-assets"}), + cache_size=2, + ) + + first_scene = get_scene("scene-0") + assert get_scene("scene-0") is first_scene + get_scene("scene-1") + get_scene("scene-2") + + assert get_scene.cache_info().currsize == 2 + assert get_scene("scene-0") is not first_scene + assert created_scene_ids == ["scene-0", "scene-1", "scene-2", "scene-0"] diff --git a/src/controller/alpasim_controller/mpc_impl/linear_mpc.py b/src/controller/alpasim_controller/mpc_impl/linear_mpc.py index c00f460c..67850813 100644 --- a/src/controller/alpasim_controller/mpc_impl/linear_mpc.py +++ b/src/controller/alpasim_controller/mpc_impl/linear_mpc.py @@ -219,7 +219,6 @@ def _linearize_dynamics(self, x_op: np.ndarray) -> tuple[np.ndarray, np.ndarray] v_cg_x = x_op[self.IVX] v_cg_y = x_op[self.IVY] yaw_rate = x_op[self.IYAW_RATE] - steering = x_op[self.ISTEERING] # Use kinematic model at low speeds use_kinematic = v_cg_x < params.kinematic_threshold_speed @@ -232,7 +231,7 @@ def _linearize_dynamics(self, x_op: np.ndarray) -> tuple[np.ndarray, np.ndarray] sin_yaw = math.sin(yaw) if use_kinematic: - # Kinematic model linearization + # Kinematic model linearization. A[self.IX, self.IYAW] = -v_cg_x * sin_yaw A[self.IX, self.IVX] = cos_yaw @@ -242,14 +241,14 @@ def _linearize_dynamics(self, x_op: np.ndarray) -> tuple[np.ndarray, np.ndarray] A[self.IYAW, self.IYAW_RATE] = 1.0 A[self.IVX, self.IACCEL] = 1.0 + # Freeze v_cg_x as a scheduling parameter. Adding its product derivatives + # without an affine residual would double the operating-point terms. GAIN = 10.0 l_r = params.l_rig_to_cg L = params.wheelbase - A[self.IVY, self.IVX] = GAIN * steering * l_r / L A[self.IVY, self.IVY] = -GAIN A[self.IVY, self.ISTEERING] = GAIN * v_cg_x * l_r / L - A[self.IYAW_RATE, self.IVX] = GAIN * steering / L A[self.IYAW_RATE, self.ISTEERING] = GAIN * v_cg_x / L A[self.IYAW_RATE, self.IYAW_RATE] = -GAIN diff --git a/src/controller/tests/mpc_impl/test_linear_mpc.py b/src/controller/tests/mpc_impl/test_linear_mpc.py index 9a89cabf..76b4ef13 100644 --- a/src/controller/tests/mpc_impl/test_linear_mpc.py +++ b/src/controller/tests/mpc_impl/test_linear_mpc.py @@ -78,6 +78,32 @@ def test_compute_control_with_lateral_error(self): # Should command negative steering to correct positive y error assert output.control[0] < 0 + def test_control_is_continuous_at_kinematic_model_threshold(self): + """Crossing the speed threshold must not double the steering contribution.""" + controller = LinearMPC() + trajectory = _create_turning_trajectory() + state = np.array([0.0, 0.0, 0.0, 4.99, 0.666, 0.437, 0.251, 1.313]) + + below = controller.compute_control( + ControllerInput( + state=state, + reference_trajectory=trajectory, + timestamp_us=0, + ) + ) + state[3] = controller._vehicle_params.kinematic_threshold_speed + at_threshold = controller.compute_control( + ControllerInput( + state=state, + reference_trajectory=trajectory, + timestamp_us=0, + ) + ) + + assert below.status in ("solved", "solved_inaccurate") + assert at_threshold.status in ("solved", "solved_inaccurate") + assert abs(below.control[0] - at_threshold.control[0]) < 0.01 + class TestLinearMPCLinearization: """Tests for LinearMPC dynamics linearization.""" @@ -108,6 +134,28 @@ def test_linearize_dynamics_kinematic_model(self): assert not np.isnan(A_d).any() assert not np.isnan(B_d).any() + def test_kinematic_model_preserves_steady_turning_manifold(self): + """Steady low-speed lateral velocity and yaw rate should be preserved.""" + controller = LinearMPC() + params = controller._vehicle_params + velocity = params.kinematic_threshold_speed - 0.01 + steering = 0.25 + + state = np.zeros(controller.NX) + state[controller.IVX] = velocity + state[controller.IVY] = ( + velocity * steering * params.l_rig_to_cg / params.wheelbase + ) + state[controller.IYAW_RATE] = velocity * steering / params.wheelbase + state[controller.ISTEERING] = steering + command = np.array([steering, 0.0]) + + A_d, B_d = controller._linearize_dynamics(state) + next_state = A_d @ state + B_d @ command + + indices = [controller.IVY, controller.IYAW_RATE] + np.testing.assert_allclose(next_state[indices], state[indices], atol=1e-12) + def test_linearize_dynamics_dynamic_model(self): """At higher speeds, should use dynamic model.""" controller = LinearMPC() @@ -143,3 +191,18 @@ def _create_simple_trajectory( timestamps = np.array([i * dt_us for i in range(num_points)], dtype=np.uint64) return Trajectory(timestamps, positions, quaternions) + + +def _create_turning_trajectory() -> Trajectory: + """Create a gently curving trajectory that exercises lateral control.""" + timestamps = np.arange(0, 2_100_000, 100_000, dtype=np.uint64) + x = timestamps.astype(np.float64) * 5.0 / 1e6 + positions = np.stack( + [x, 0.08 * x**2, np.zeros_like(x)], + axis=1, + ).astype(np.float32) + quaternions = np.tile( + np.array([0.0, 0.0, 0.0, 1.0], dtype=np.float32), + (len(timestamps), 1), + ) + return Trajectory(timestamps, positions, quaternions) diff --git a/src/utils/alpasim_utils/trajdata_data_source.py b/src/utils/alpasim_utils/trajdata_data_source.py index 9a8320df..b403f3b9 100644 --- a/src/utils/alpasim_utils/trajdata_data_source.py +++ b/src/utils/alpasim_utils/trajdata_data_source.py @@ -33,7 +33,7 @@ import csaps import numpy as np from alpasim_utils.artifact import Metadata -from alpasim_utils.geometry import Pose, Trajectory +from alpasim_utils.geometry import Trajectory from alpasim_utils.scenario import ( AABB, CameraId, @@ -53,6 +53,15 @@ logger = logging.getLogger(__name__) +def _localize_positions( + positions_world: np.ndarray, position_origin_world: np.ndarray +) -> np.ndarray: + """Translate world positions in float64 before storing local float32 values.""" + positions_world_f64 = np.asarray(positions_world, dtype=np.float64) + position_origin_world_f64 = np.asarray(position_origin_world, dtype=np.float64) + return (positions_world_f64 - position_origin_world_f64).astype(np.float32) + + @dataclass class TrajdataDataSource(SceneDataSource): """ @@ -157,10 +166,11 @@ def _ensure_rig_loaded(self) -> None: def _extract_agent_trajectory( self, agent: AgentMetadata, - ) -> tuple[Optional[Trajectory], Optional[VehicleConfig]]: - """Extract the complete world-frame trajectory for an agent.""" + position_origin_world: np.ndarray | None = None, + ) -> tuple[Optional[Trajectory], Optional[VehicleConfig], Optional[np.ndarray]]: + """Extract an agent trajectory relative to a shared world-space origin.""" if self.scene is None: - return None, None + return None, None, None scene_cache = self.scene_cache dt = self.scene.dt @@ -173,11 +183,13 @@ def _extract_agent_trajectory( history_sec=(None, None), ) if len(states) == 0: - return None, None + return None, None, None - positions_agent_world = np.asarray( - states.position3d, - dtype=np.float32, + positions_agent_world = np.asarray(states.position3d, dtype=np.float64) + if position_origin_world is None: + position_origin_world = positions_agent_world[0].copy() + positions_agent_local = _localize_positions( + positions_agent_world, position_origin_world ) headings = np.asarray(states.heading, dtype=np.float64).reshape(-1, 1) quaternions_agent_world = R.from_euler("z", headings).as_quat() @@ -188,7 +200,7 @@ def _extract_agent_trajectory( trajectory = Trajectory( timestamps=timestamps_us, - positions=positions_agent_world, + positions=positions_agent_local, quaternions=quaternions_agent_world.astype(np.float32), ) @@ -201,11 +213,11 @@ def _extract_agent_trajectory( aabb_z_offset_m=-agent.extent.height / 2, ) - return trajectory, vehicle_config + return trajectory, vehicle_config, position_origin_world except Exception as e: logger.error(f"Failed to extract trajectory for agent {agent.name}: {e}") - return None, None + return None, None, None @property def rig(self) -> Rig: @@ -230,9 +242,11 @@ def rig(self) -> Rig: raise ValueError("No ego agent found in scene") # Extract ego trajectory - ego_trajectory, ego_vehicle_config = self._extract_agent_trajectory(ego_agent) + ego_trajectory, ego_vehicle_config, position_origin_world = ( + self._extract_agent_trajectory(ego_agent) + ) - if ego_trajectory is None: + if ego_trajectory is None or position_origin_world is None: logger.error( f"Failed to extract ego trajectory for agent {ego_agent.name}. " f"Check if scene_cache is properly initialized and agent data is available." @@ -240,24 +254,16 @@ def rig(self) -> Rig: raise ValueError("Cannot extract ego trajectory") # Calculate world_to_nre transformation matrix (use first trajectory point as origin) - world_to_nre = np.eye(4) - if len(ego_trajectory) > 0: - position_ego_first_world = ego_trajectory.positions[0] - world_to_nre[:3, 3] = -position_ego_first_world - logger.info( - f"Setting world_to_nre origin at first pose: {position_ego_first_world}, " - f"translation: {world_to_nre[:3, 3]}" - ) + world_to_nre = np.eye(4, dtype=np.float64) + world_to_nre[:3, 3] = -position_origin_world + logger.info( + f"Setting world_to_nre origin at first pose: {position_origin_world}, " + f"translation: {world_to_nre[:3, 3]}" + ) - # Convert ego trajectory to local coordinates (NRE). - # Use Pose.from_se3 so any rotation later added to world_to_nre flows - # through automatically; current callers populate translation only, but - # the SE3 path keeps quaternions correctly composed in both cases. + # Positions were translated in float64 before Trajectory construction so + # UTM-scale coordinates do not lose their sub-meter deltas in float32. if len(ego_trajectory) > 0: - # Pose.from_se3 expects a float32 4x4 SE3 matrix. - ego_trajectory = ego_trajectory.transform( - Pose.from_se3(world_to_nre.astype(np.float32, copy=False)) - ) local_positions = ego_trajectory.positions # Validate transform @@ -450,12 +456,9 @@ def traffic_objects(self) -> TrafficObjects: if ego_agent is None and len(all_agents) > 0: ego_agent = all_agents[0] - # world_to_nre is a per-scene constant; build the Pose once. - # Pose.from_se3 expects a float32 4x4 SE3 matrix. + # Reuse the ego's float64 world origin for every traffic-object trajectory. self._ensure_rig_loaded() - world_to_nre_pose = Pose.from_se3( - self._rig.world_to_nre.astype(np.float32, copy=False) - ) + position_origin_world = -self._rig.world_to_nre[:3, 3] traffic_dict = {} for agent in all_agents: @@ -464,15 +467,14 @@ def traffic_objects(self) -> TrafficObjects: continue # Extract trajectory - trajectory, _ = self._extract_agent_trajectory(agent) + trajectory, _, _ = self._extract_agent_trajectory( + agent, position_origin_world=position_origin_world + ) # Filter out empty trajectories or trajectories with only 1 data point if trajectory is None or len(trajectory) < 2: continue - # Convert trajectory to local coordinates (NRE). - trajectory = trajectory.transform(world_to_nre_pose) - # Smooth if needed if self.smooth_trajectories: try: diff --git a/src/wizard/configs/base_config.yaml b/src/wizard/configs/base_config.yaml index b291cc5d..7aff2789 100644 --- a/src/wizard/configs/base_config.yaml +++ b/src/wizard/configs/base_config.yaml @@ -31,8 +31,7 @@ defines: sensordata: "${defines.filesystem}/nre-artifacts" trafficsim_map_cache: "${defines.filesystem}/trafficsim/unified_data_cache" - renderer_entrypoint: "/app/internal/scripts/pycena/runtime/pycena_run" - sensorsim_entrypoint: "/app/internal/scripts/pycena/runtime/pycena_run" + renderer_entrypoint: "/app/internal/scripts/pycena/runtime/pycena_nrm_full" nre_max_workers: 4 helper: scripts vscode: sources/remote-vscode-server @@ -119,7 +118,7 @@ scenes: services: # \/ renderer defaults to NRE. It can be overridden with another renderer implementation. renderer: - image: nvcr.io/nvidia/nre/nre-ga:26.02 + image: nvcr.io/nvidia/nre/nre-ga:26.04 external_image: true # \/ volumes lets us mount host (ORD/local) containers to the running container volumes: @@ -139,6 +138,7 @@ services: - "--artifact-glob=/mnt/nre-data/{sceneset}/**/*.usdz" - "--egocar-hood-dir=/mnt/ego-hoods" - "--no-enable-nrend" + - "--enable-harmonizer" - "--download-cache-dir /tmp/nre-cache-dir" # unused - "--cache-size=${defines.nre_cache_size}" # as a rule of thumb n_concurrent_rollouts + 1 allows to avoid premature evictions - "--max-workers=${defines.nre_max_workers}" diff --git a/src/wizard/configs/e2e_challenge_nuplan/ec2.yaml b/src/wizard/configs/e2e_challenge_nuplan/ec2.yaml index 80046c4f..0496ae87 100644 --- a/src/wizard/configs/e2e_challenge_nuplan/ec2.yaml +++ b/src/wizard/configs/e2e_challenge_nuplan/ec2.yaml @@ -28,6 +28,9 @@ scenes: limit_to_first_n: 0 runtime: + # The shared NuPlan defaults are intentionally conservative; restore the + # production topology's worker count for concurrent competition rollouts. + nr_workers: 4 endpoints: renderer: n_concurrent_rollouts: 8 diff --git a/src/wizard/configs/e2e_challenge_nuplan_common/base.yaml b/src/wizard/configs/e2e_challenge_nuplan_common/base.yaml index ed194f65..7d40e9c9 100644 --- a/src/wizard/configs/e2e_challenge_nuplan_common/base.yaml +++ b/src/wizard/configs/e2e_challenge_nuplan_common/base.yaml @@ -105,12 +105,20 @@ runtime: simulation_config: n_sim_steps: 200 n_rollouts: 1 + # MTGS renders all same-timestamp cameras in one pass. Keep this in the + # shared NuPlan config so local validation and competition use the same path. + render_bundling: RENDER_AGGREGATED control_timestep_us: 500_000 force_gt_duration_us: 500_000 planner_delay_us: 0 min_traffic_duration_us: 0 physics_update_mode: NONE - route_generator_type: RECORDED + # Request up to 80 m of route lookahead by extending the recorded trajectory + # along connected map lanes. All public and private NuPlan challenge presets + # inherit this shared setting. + route_generator_type: MAP + # Submit only the portion of the route approximately 40-80 m ahead of ego. + route_start_offset_m: 40.0 assert_zero_decision_delay: false send_recording_ground_truth: false cameras: