| |
| from __future__ import annotations |
|
|
| import argparse |
| import sys |
| from pathlib import Path |
| from typing import Any |
|
|
| PROJECT_ROOT = Path(__file__).resolve().parents[1] |
| if str(PROJECT_ROOT) not in sys.path: |
| sys.path.insert(0, str(PROJECT_ROOT)) |
|
|
| from dovla_cil.data.datasets import CILDataset |
| from dovla_cil.data.schema import ActionChunk, CILRecord |
| from dovla_cil.models.action_encoder import devectorize_toy_action |
| from dovla_cil.models.dovla import DoVLAConfig, vectorize_toy_observation |
| from dovla_cil.utils.io import write_json |
|
|
| try: |
| import torch |
| except ImportError: |
| torch = None |
|
|
|
|
| def main(argv: list[str] | None = None) -> int: |
| parser = argparse.ArgumentParser( |
| description="Run lightweight toy DoVLA inference or a documented no-torch fallback." |
| ) |
| parser.add_argument("--dataset", type=Path, required=True, help="CIL dataset directory.") |
| parser.add_argument("--checkpoint", type=Path, default=None, help="DoVLA checkpoint path.") |
| parser.add_argument("--out", type=Path, required=True, help="JSON output path.") |
| parser.add_argument("--group-id", default=None, help="Optional CIL group id to query.") |
| parser.add_argument("--instruction", default=None, help="Override instruction text.") |
| parser.add_argument("--device", default="auto") |
| args = parser.parse_args(argv) |
|
|
| dataset = CILDataset(args.dataset) |
| group_id = args.group_id or (dataset.group_ids[0] if dataset.group_ids else None) |
| if group_id is None: |
| raise ValueError(f"No groups found in dataset {args.dataset}") |
| group = dataset.get_group(group_id) |
| query = group[0] |
| instruction = args.instruction or query.instruction |
|
|
| prediction = _model_prediction(args.checkpoint, query, instruction, args.device) |
| if prediction is None: |
| best = _best_record(group) |
| prediction = { |
| "mode": "dataset_best_action_fallback", |
| "note": ( |
| "Torch/model inference was unavailable; selected the highest-reward action " |
| "within the queried CIL group. This is a transparent toy inference baseline, " |
| "not a learned-model result." |
| ), |
| "selected_record_id": best.record_id, |
| "action_chunk": best.action_chunk.to_dict(), |
| "reference_reward": best.reward.to_dict(), |
| "candidate_type": best.candidate_type, |
| } |
| else: |
| prediction["action_chunk"] = _bind_prediction_to_group( |
| ActionChunk.from_dict(prediction["action_chunk"]), group |
| ).to_dict() |
|
|
| output = { |
| "dataset": str(args.dataset), |
| "checkpoint": str(args.checkpoint) if args.checkpoint else None, |
| "group_id": group_id, |
| "task_id": query.task_id, |
| "instruction": instruction, |
| "query_record_id": query.record_id, |
| "prediction": prediction, |
| } |
| write_json(output, args.out) |
| print(f"wrote inference output to {args.out}") |
| print(f"mode={prediction['mode']} action_id={prediction['action_chunk'].get('action_id')}") |
| return 0 |
|
|
|
|
| def _model_prediction( |
| checkpoint_path: Path | None, query: CILRecord, instruction: str, device: str |
| ) -> dict[str, Any] | None: |
| if torch is None or checkpoint_path is None or not checkpoint_path.exists(): |
| return None |
| try: |
| checkpoint = torch.load( |
| checkpoint_path, |
| map_location="cuda" if device == "auto" and torch.cuda.is_available() else "cpu", |
| ) |
| except Exception: |
| return None |
| if not isinstance(checkpoint, dict) or "model_state_dict" not in checkpoint: |
| return None |
|
|
| from dovla_cil.models.dovla import DoVLAModel |
|
|
| model_config = DoVLAConfig(**dict(checkpoint.get("model_config", {}))) |
| resolved_device = "cuda" if device == "auto" and torch.cuda.is_available() else device |
| if resolved_device == "auto": |
| resolved_device = "cpu" |
| model = DoVLAModel(model_config).to(resolved_device) |
| model.load_state_dict(checkpoint["model_state_dict"]) |
| model.eval() |
| with torch.no_grad(): |
| obs = torch.tensor( |
| [ |
| vectorize_toy_observation( |
| query.observation_inline or {}, obs_dim=model_config.obs_dim |
| ) |
| ], |
| dtype=torch.float32, |
| device=resolved_device, |
| ) |
| action_values = model.forward_policy(obs, [instruction])[0].detach().cpu().tolist() |
| action = devectorize_toy_action(action_values) |
| return { |
| "mode": "model_policy", |
| "action_chunk": action.to_dict(), |
| "model_config": checkpoint.get("model_config", {}), |
| } |
|
|
|
|
| def _best_record(records: list[CILRecord]) -> CILRecord: |
| return max(records, key=lambda record: (record.reward.score, -float(record.rank_within_group or 0))) |
|
|
|
|
| def _bind_prediction_to_group(action: ActionChunk, records: list[CILRecord]) -> ActionChunk: |
| target = _first_metadata_value(records, "intended_target") |
| reference = _first_metadata_value(records, "intended_reference") or _first_metadata_value( |
| records, "intended_relation_reference" |
| ) |
| if not target or not isinstance(action.values, list): |
| return action |
| if not all(isinstance(item, dict) for item in action.values): |
| return action |
| values: list[dict[str, Any]] = [] |
| changed = False |
| for raw_command in action.values: |
| command = dict(raw_command) |
| command_name = str(command.get("command") or command.get("type") or "") |
| if command_name in {"move_to", "grasp", "push", "place_at", "open", "close"}: |
| if command.get("object") in {None, "", "predicted_target"}: |
| command["object"] = target |
| changed = True |
| if command.get("target") in {None, "", "predicted_target"}: |
| command["target"] = target |
| changed = True |
| for key in ("reference", "container"): |
| if reference and command.get(key) in {None, "", "predicted_reference"}: |
| command[key] = reference |
| changed = True |
| values.append(command) |
| if not changed: |
| return action |
| metadata = dict(action.metadata) |
| metadata.update({"task_bound": True, "bound_target": target}) |
| if reference: |
| metadata["bound_reference"] = reference |
| return ActionChunk( |
| action_id=f"{action.action_id}-task-bound", |
| representation=action.representation, |
| horizon=action.horizon, |
| values=values, |
| skill_type=action.skill_type, |
| metadata=metadata, |
| ) |
|
|
|
|
| def _first_metadata_value(records: list[CILRecord], key: str) -> str: |
| for record in records: |
| value = record.action_chunk.metadata.get(key) |
| if value: |
| return str(value) |
| return "" |
|
|
|
|
| if __name__ == "__main__": |
| raise SystemExit(main()) |
|
|