anhtld commited on
Commit
3e34c1e
·
verified ·
1 Parent(s): d7dac0b

Auto-sync: 2026-06-29 17:58:49

Browse files
dovla_cil/eval/maniskill_policy_rollout.py CHANGED
@@ -103,6 +103,7 @@ def evaluate_maniskill_policy_rollout(
103
  lattice_exclude_types: tuple[str, ...] = (),
104
  candidate_type_bonuses: dict[str, float] | None = None,
105
  candidate_type_bonus_components: bool = False,
 
106
  ) -> dict[str, Any]:
107
  """Execute a checkpoint policy from restored ManiSkill CIL states.
108
 
@@ -187,6 +188,8 @@ def evaluate_maniskill_policy_rollout(
187
  raise ValueError("field_optim_l2_penalty must be non-negative")
188
  if selection_margin < 0:
189
  raise ValueError("selection_margin must be non-negative")
 
 
190
  if retrieval_neighbors <= 0:
191
  raise ValueError("retrieval_neighbors must be positive")
192
  if retrieval_metric not in _RETRIEVAL_METRICS:
@@ -352,6 +355,7 @@ def evaluate_maniskill_policy_rollout(
352
  lattice_exclude_types=lattice_exclude_types,
353
  candidate_type_bonuses=candidate_type_bonuses,
354
  candidate_type_bonus_components=candidate_type_bonus_components,
 
355
  )
356
  rows.extend(task_rows)
357
  task_summaries[task_id] = _summarize_rows(task_rows)
@@ -465,6 +469,7 @@ def evaluate_maniskill_policy_rollout(
465
  "lattice_exclude_types": list(lattice_exclude_types),
466
  "candidate_type_bonuses": candidate_type_bonuses,
467
  "candidate_type_bonus_components": bool(candidate_type_bonus_components),
 
468
  "policy_rollout_success_rate": _mean([row["success"] for row in rows]),
469
  "policy_rollout_progress": _mean([row["progress"] for row in rows]),
470
  "oracle_success_rate": _mean([row["oracle_success"] for row in rows]),
@@ -490,6 +495,11 @@ def evaluate_maniskill_policy_rollout(
490
  ),
491
  "rows": rows,
492
  }
 
 
 
 
 
493
  if output_path is not None:
494
  write_json(result, output_path)
495
  return result
@@ -1141,6 +1151,7 @@ def _evaluate_task_cases(
1141
  lattice_exclude_types: tuple[str, ...] = (),
1142
  candidate_type_bonuses: dict[str, float] | None = None,
1143
  candidate_type_bonus_components: bool = False,
 
1144
  ) -> list[dict[str, Any]]:
1145
  rows: list[dict[str, Any]] = []
1146
  for start in range(0, len(cases), group_batch_size):
@@ -1272,6 +1283,38 @@ def _evaluate_task_cases(
1272
  device=env_device,
1273
  )
1274
  del after_state
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1275
  for index, case in enumerate(batch):
1276
  progress = float(max(0.0, min(1.0, rewards[index, 0])))
1277
  success = bool(successes[index, 0])
@@ -1333,11 +1376,267 @@ def _evaluate_task_cases(
1333
  "candidate_source_group_id": case.candidate_source_group_id,
1334
  }
1335
  )
 
 
1336
  finally:
1337
  env.close()
1338
  return rows
1339
 
1340
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1341
  def _select_action_chunk(
1342
  model: DoVLAModel,
1343
  observations: Any,
@@ -1691,6 +1990,49 @@ def _select_lattice_action_chunk(
1691
  device=candidate_type_bonus.device,
1692
  )
1693
  candidate_type_bonus = torch.cat([baseline_bonus, candidate_type_bonus], dim=1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1694
  batch_size, candidate_count = action_candidates.shape[:2]
1695
  candidates = _clamp_action_tensor(
1696
  action_candidates,
@@ -1729,18 +2071,10 @@ def _select_lattice_action_chunk(
1729
  penalty = delta.reshape(batch_size, candidate_count, -1).pow(2).mean(dim=2)
1730
  potentials = potentials - float(action_l2_penalty) * penalty
1731
  if candidate_mask is not None:
 
 
1732
  potentials = potentials.masked_fill(~candidate_mask, float("-inf"))
1733
- best_index = torch.argmax(potentials, dim=1)
1734
- if selection_margin > 0.0 and candidate_count > 0:
1735
- baseline = potentials[:, 0]
1736
- best_potential = potentials.gather(1, best_index.reshape(batch_size, 1)).reshape(
1737
- batch_size
1738
- )
1739
- keep_baseline = best_potential <= baseline + float(selection_margin)
1740
- best_index = torch.where(keep_baseline, torch.zeros_like(best_index), best_index)
1741
- batch_index = torch.arange(batch_size, device=candidates.device)
1742
- best_actions = candidates[batch_index, best_index]
1743
- return best_actions, best_index.detach().cpu().numpy()
1744
 
1745
 
1746
  def _select_residual_lattice_action_chunk(
@@ -1792,6 +2126,57 @@ def _select_residual_lattice_action_chunk(
1792
  selection_margin=selection_margin,
1793
  action_l2_penalty=action_l2_penalty,
1794
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1795
  candidate_blocks = [
1796
  policy_mean.unsqueeze(1) + scale * residuals
1797
  for scale in scales
@@ -1833,28 +2218,32 @@ def _select_residual_lattice_action_chunk(
1833
  device=candidate_type_bonus.device,
1834
  )
1835
  candidate_type_bonus = torch.cat([candidate_type_bonus, extra_bonus], dim=1)
1836
- return _select_lattice_action_chunk(
1837
- model,
1838
- observations,
1839
- instructions,
1840
- candidates,
1841
- torch=torch,
1842
- action_low=(
1843
- action_low.unsqueeze(1)
1844
- if action_low is not None and action_low.ndim == 3
1845
- else action_low
1846
- ),
1847
- action_high=(
1848
- action_high.unsqueeze(1)
1849
- if action_high is not None and action_high.ndim == 3
1850
- else action_high
1851
- ),
1852
- candidate_mask=candidate_mask,
1853
- candidate_type_bonus=candidate_type_bonus,
1854
- selection_margin=selection_margin,
1855
- action_l2_penalty=action_l2_penalty,
1856
- action_l2_penalty_base=policy_mean,
1857
  )
 
 
 
 
 
 
1858
 
1859
 
1860
  def _select_field_softmax_residual_action_chunk(
@@ -2316,7 +2705,7 @@ def _clip_to_action_space(actions: np.ndarray, env: Any) -> np.ndarray:
2316
 
2317
 
2318
  def _summarize_rows(rows: list[dict[str, Any]]) -> dict[str, Any]:
2319
- return {
2320
  "num_groups": len(rows),
2321
  "policy_rollout_success_rate": _mean([row["success"] for row in rows]),
2322
  "policy_rollout_progress": _mean([row["progress"] for row in rows]),
@@ -2331,6 +2720,48 @@ def _summarize_rows(rows: list[dict[str, Any]]) -> dict[str, Any]:
2331
  "action_mse_to_best": _mean([row["action_mse_to_best"] for row in rows]),
2332
  "restore_max_error": max([row["restore_error"] for row in rows], default=0.0),
2333
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2334
 
2335
 
2336
  def _mean(values: list[Any]) -> float:
 
103
  lattice_exclude_types: tuple[str, ...] = (),
104
  candidate_type_bonuses: dict[str, float] | None = None,
105
  candidate_type_bonus_components: bool = False,
106
+ candidate_oracle_rollouts: int = 0,
107
  ) -> dict[str, Any]:
108
  """Execute a checkpoint policy from restored ManiSkill CIL states.
109
 
 
188
  raise ValueError("field_optim_l2_penalty must be non-negative")
189
  if selection_margin < 0:
190
  raise ValueError("selection_margin must be non-negative")
191
+ if candidate_oracle_rollouts < 0:
192
+ raise ValueError("candidate_oracle_rollouts must be non-negative")
193
  if retrieval_neighbors <= 0:
194
  raise ValueError("retrieval_neighbors must be positive")
195
  if retrieval_metric not in _RETRIEVAL_METRICS:
 
355
  lattice_exclude_types=lattice_exclude_types,
356
  candidate_type_bonuses=candidate_type_bonuses,
357
  candidate_type_bonus_components=candidate_type_bonus_components,
358
+ candidate_oracle_rollouts=candidate_oracle_rollouts,
359
  )
360
  rows.extend(task_rows)
361
  task_summaries[task_id] = _summarize_rows(task_rows)
 
469
  "lattice_exclude_types": list(lattice_exclude_types),
470
  "candidate_type_bonuses": candidate_type_bonuses,
471
  "candidate_type_bonus_components": bool(candidate_type_bonus_components),
472
+ "candidate_oracle_rollouts": int(candidate_oracle_rollouts),
473
  "policy_rollout_success_rate": _mean([row["success"] for row in rows]),
474
  "policy_rollout_progress": _mean([row["progress"] for row in rows]),
475
  "oracle_success_rate": _mean([row["oracle_success"] for row in rows]),
 
495
  ),
496
  "rows": rows,
497
  }
498
+ candidate_oracle_rows = [
499
+ row for row in rows if row.get("candidate_oracle_success") is not None
500
+ ]
501
+ if candidate_oracle_rows:
502
+ result.update(_candidate_oracle_summary(candidate_oracle_rows))
503
  if output_path is not None:
504
  write_json(result, output_path)
505
  return result
 
1151
  lattice_exclude_types: tuple[str, ...] = (),
1152
  candidate_type_bonuses: dict[str, float] | None = None,
1153
  candidate_type_bonus_components: bool = False,
1154
+ candidate_oracle_rollouts: int = 0,
1155
  ) -> list[dict[str, Any]]:
1156
  rows: list[dict[str, Any]] = []
1157
  for start in range(0, len(cases), group_batch_size):
 
1283
  device=env_device,
1284
  )
1285
  del after_state
1286
+ candidate_oracle = None
1287
+ if candidate_oracle_rollouts > 0:
1288
+ candidate_oracle = _execute_candidate_oracle_prefix(
1289
+ task_id,
1290
+ batch,
1291
+ model=model,
1292
+ model_config=model_config,
1293
+ gym=gym,
1294
+ env=env,
1295
+ torch=torch,
1296
+ device=device,
1297
+ env_device=env_device,
1298
+ env_kwargs=env_kwargs,
1299
+ env_action_dim=env_dim,
1300
+ observations=observations,
1301
+ instructions=[case.instruction for case in batch],
1302
+ selected_indices=candidate_index,
1303
+ selection_mode=selection_mode,
1304
+ candidate_oracle_rollouts=candidate_oracle_rollouts,
1305
+ num_candidates=num_candidates,
1306
+ candidate_sigma=candidate_sigma,
1307
+ selection_seed=selection_seed + start,
1308
+ retrieval_residual_scale=retrieval_residual_scale,
1309
+ retrieval_residual_scales=retrieval_residual_scales,
1310
+ retrieval_residual_reduce=retrieval_residual_reduce,
1311
+ retrieval_residual_action_l2_penalty=retrieval_residual_action_l2_penalty,
1312
+ action_low=action_low,
1313
+ action_high=action_high,
1314
+ lattice_exclude_types=lattice_exclude_types,
1315
+ candidate_type_bonuses=candidate_type_bonuses or {},
1316
+ candidate_type_bonus_components=candidate_type_bonus_components,
1317
+ )
1318
  for index, case in enumerate(batch):
1319
  progress = float(max(0.0, min(1.0, rewards[index, 0])))
1320
  success = bool(successes[index, 0])
 
1376
  "candidate_source_group_id": case.candidate_source_group_id,
1377
  }
1378
  )
1379
+ if candidate_oracle is not None:
1380
+ rows[-1].update(candidate_oracle[index])
1381
  finally:
1382
  env.close()
1383
  return rows
1384
 
1385
 
1386
+ def _execute_candidate_oracle_prefix(
1387
+ task_id: str,
1388
+ batch: list[_RolloutCase],
1389
+ *,
1390
+ model: DoVLAModel,
1391
+ model_config: DoVLAConfig,
1392
+ gym: Any,
1393
+ env: Any,
1394
+ torch: Any,
1395
+ device: str,
1396
+ env_device: Any,
1397
+ env_kwargs: dict[str, Any],
1398
+ env_action_dim: int,
1399
+ observations: Any,
1400
+ instructions: list[str],
1401
+ selected_indices: np.ndarray,
1402
+ selection_mode: str,
1403
+ candidate_oracle_rollouts: int,
1404
+ num_candidates: int,
1405
+ candidate_sigma: float,
1406
+ selection_seed: int,
1407
+ retrieval_residual_scale: float,
1408
+ retrieval_residual_scales: tuple[float, ...],
1409
+ retrieval_residual_reduce: str,
1410
+ retrieval_residual_action_l2_penalty: float,
1411
+ action_low: Any | None,
1412
+ action_high: Any | None,
1413
+ lattice_exclude_types: tuple[str, ...],
1414
+ candidate_type_bonuses: dict[str, float],
1415
+ candidate_type_bonus_components: bool,
1416
+ ) -> list[dict[str, Any]]:
1417
+ del model_config, env_device
1418
+ if selection_mode != "retrieval_residual":
1419
+ raise ValueError("candidate_oracle_rollouts currently supports retrieval_residual")
1420
+ if retrieval_residual_reduce in _FIELD_CONDITIONED_RESIDUAL_REDUCERS:
1421
+ raise ValueError(
1422
+ "candidate_oracle_rollouts requires an explicit residual lattice, not field_softmax"
1423
+ )
1424
+ if candidate_oracle_rollouts <= 0:
1425
+ return [{} for _ in batch]
1426
+
1427
+ action_residuals = torch.tensor(
1428
+ [case.candidate_action_values for case in batch],
1429
+ dtype=torch.float32,
1430
+ device=device,
1431
+ )
1432
+ policy_mean = model.forward_policy(observations, instructions)
1433
+ candidate_mask = (
1434
+ _lattice_candidate_mask(
1435
+ batch,
1436
+ torch=torch,
1437
+ device=device,
1438
+ exclude_types=lattice_exclude_types,
1439
+ )
1440
+ if lattice_exclude_types
1441
+ else None
1442
+ )
1443
+ candidate_type_bonus = (
1444
+ _lattice_candidate_type_bonus(
1445
+ batch,
1446
+ torch=torch,
1447
+ device=device,
1448
+ candidate_type_bonuses=candidate_type_bonuses,
1449
+ use_components=candidate_type_bonus_components,
1450
+ )
1451
+ if candidate_type_bonuses or any(case.candidate_score_bonuses for case in batch)
1452
+ else None
1453
+ )
1454
+ candidates, candidate_mask, candidate_type_bonus = _build_residual_lattice_candidates(
1455
+ policy_mean,
1456
+ action_residuals.to(device=policy_mean.device, dtype=policy_mean.dtype),
1457
+ torch=torch,
1458
+ residual_scale=retrieval_residual_scale,
1459
+ residual_scales=retrieval_residual_scales,
1460
+ candidate_mask=candidate_mask,
1461
+ candidate_type_bonus=candidate_type_bonus,
1462
+ num_gaussian_candidates=num_candidates,
1463
+ candidate_sigma=candidate_sigma,
1464
+ selection_seed=selection_seed,
1465
+ )
1466
+ scored_candidates, potentials = _score_lattice_action_chunks(
1467
+ model,
1468
+ observations,
1469
+ instructions,
1470
+ candidates,
1471
+ torch=torch,
1472
+ action_low=(
1473
+ action_low.unsqueeze(1)
1474
+ if action_low is not None and action_low.ndim == 3
1475
+ else action_low
1476
+ ),
1477
+ action_high=(
1478
+ action_high.unsqueeze(1)
1479
+ if action_high is not None and action_high.ndim == 3
1480
+ else action_high
1481
+ ),
1482
+ candidate_mask=candidate_mask,
1483
+ candidate_type_bonus=candidate_type_bonus,
1484
+ action_l2_penalty=retrieval_residual_action_l2_penalty,
1485
+ action_l2_penalty_base=policy_mean,
1486
+ )
1487
+ candidate_indices = _diagnostic_candidate_indices(
1488
+ potentials,
1489
+ selected_indices,
1490
+ candidate_oracle_rollouts=candidate_oracle_rollouts,
1491
+ torch=torch,
1492
+ )
1493
+ batch_index = torch.arange(len(batch), device=scored_candidates.device).reshape(-1, 1)
1494
+ diagnostic_candidates = scored_candidates[batch_index, candidate_indices]
1495
+ diagnostic_np = diagnostic_candidates.detach().cpu().numpy().astype(np.float32)
1496
+ branch_count = int(diagnostic_np.shape[1])
1497
+ flat = diagnostic_np.reshape(
1498
+ len(batch) * branch_count,
1499
+ diagnostic_np.shape[-2],
1500
+ diagnostic_np.shape[-1],
1501
+ )
1502
+ flat = _adapt_action_dim(flat, env_action_dim)
1503
+ flat = _clip_to_action_space(flat, env)
1504
+ adapted = flat.reshape(len(batch), branch_count, flat.shape[-2], flat.shape[-1])
1505
+
1506
+ diag_env_kwargs = dict(env_kwargs)
1507
+ diag_env_kwargs["num_envs"] = len(batch) * branch_count
1508
+ diag_env = gym.make(task_id, **diag_env_kwargs)
1509
+ diag_base_env = diag_env.unwrapped
1510
+ try:
1511
+ diag_env_device = getattr(diag_base_env, "device", torch.device(device))
1512
+ after_state, rewards, successes, restore_error = execute_grouped_action_lattice_batch(
1513
+ diag_base_env,
1514
+ [case.state for case in batch],
1515
+ adapted,
1516
+ torch=torch,
1517
+ device=diag_env_device,
1518
+ )
1519
+ del after_state
1520
+ finally:
1521
+ diag_env.close()
1522
+
1523
+ clipped_rewards = np.clip(rewards.astype(np.float32), 0.0, 1.0)
1524
+ scores = clipped_rewards + successes.astype(np.float32)
1525
+ best_branch = np.argmax(scores, axis=1)
1526
+ indices_np = candidate_indices.detach().cpu().numpy()
1527
+ potentials_np = potentials.gather(1, candidate_indices).detach().cpu().numpy()
1528
+ rows: list[dict[str, Any]] = []
1529
+ for row_index, case in enumerate(batch):
1530
+ branch = int(best_branch[row_index])
1531
+ candidate_index = int(indices_np[row_index, branch])
1532
+ candidate_type = _selected_candidate_type(
1533
+ case,
1534
+ selected_index=candidate_index,
1535
+ selection_mode=selection_mode,
1536
+ residual_scale_count=_residual_scale_count(retrieval_residual_scales),
1537
+ retrieval_residual_reduce=retrieval_residual_reduce,
1538
+ )
1539
+ rows.append(
1540
+ {
1541
+ "candidate_oracle_rollout_count": branch_count,
1542
+ "candidate_oracle_success": bool(successes[row_index, branch]),
1543
+ "candidate_oracle_progress": float(clipped_rewards[row_index, branch]),
1544
+ "candidate_oracle_score": float(scores[row_index, branch]),
1545
+ "candidate_oracle_regret": max(
1546
+ 0.0,
1547
+ case.oracle_score - float(scores[row_index, branch]),
1548
+ ),
1549
+ "candidate_oracle_score_gain_over_selected": float(
1550
+ scores[row_index, branch] - scores[row_index, 0]
1551
+ ),
1552
+ "candidate_oracle_improves_selected": bool(
1553
+ scores[row_index, branch] > scores[row_index, 0] + 1.0e-9
1554
+ ),
1555
+ "candidate_oracle_candidate_index": candidate_index,
1556
+ "candidate_oracle_candidate_type": candidate_type,
1557
+ "candidate_oracle_residual_scale": _selected_residual_scale(
1558
+ case,
1559
+ selected_index=candidate_index,
1560
+ selection_mode=selection_mode,
1561
+ residual_scale=retrieval_residual_scale,
1562
+ residual_scales=retrieval_residual_scales,
1563
+ retrieval_residual_reduce=retrieval_residual_reduce,
1564
+ ),
1565
+ "candidate_oracle_field_potential": float(
1566
+ potentials_np[row_index, branch]
1567
+ ),
1568
+ "candidate_oracle_restore_error": float(restore_error),
1569
+ "candidate_oracle_selected_branch_success": bool(successes[row_index, 0]),
1570
+ "candidate_oracle_selected_branch_progress": float(
1571
+ clipped_rewards[row_index, 0]
1572
+ ),
1573
+ "candidate_oracle_selected_branch_score": float(scores[row_index, 0]),
1574
+ "candidate_oracle_indices": [
1575
+ int(value) for value in indices_np[row_index].tolist()
1576
+ ],
1577
+ "candidate_oracle_types": [
1578
+ _selected_candidate_type(
1579
+ case,
1580
+ selected_index=int(value),
1581
+ selection_mode=selection_mode,
1582
+ residual_scale_count=_residual_scale_count(
1583
+ retrieval_residual_scales
1584
+ ),
1585
+ retrieval_residual_reduce=retrieval_residual_reduce,
1586
+ )
1587
+ for value in indices_np[row_index].tolist()
1588
+ ],
1589
+ }
1590
+ )
1591
+ return rows
1592
+
1593
+
1594
+ def _diagnostic_candidate_indices(
1595
+ potentials: Any,
1596
+ selected_indices: np.ndarray,
1597
+ *,
1598
+ candidate_oracle_rollouts: int,
1599
+ torch: Any,
1600
+ ) -> Any:
1601
+ if potentials.ndim != 2:
1602
+ raise ValueError("potentials must have shape [B,K]")
1603
+ batch_size, candidate_count = potentials.shape
1604
+ if candidate_oracle_rollouts <= 0:
1605
+ return torch.empty(
1606
+ batch_size,
1607
+ 0,
1608
+ dtype=torch.long,
1609
+ device=potentials.device,
1610
+ )
1611
+ selected = torch.as_tensor(
1612
+ selected_indices,
1613
+ dtype=torch.long,
1614
+ device=potentials.device,
1615
+ ).reshape(-1)
1616
+ if selected.shape[0] != batch_size:
1617
+ raise ValueError("selected_indices must have shape [B]")
1618
+ rows: list[list[int]] = []
1619
+ for row_index in range(batch_size):
1620
+ selected_index = int(selected[row_index].item())
1621
+ if not 0 <= selected_index < candidate_count:
1622
+ selected_index = 0
1623
+ chosen = [selected_index]
1624
+ ordered = torch.argsort(potentials[row_index], descending=True)
1625
+ for value in ordered:
1626
+ candidate_index = int(value.item())
1627
+ if candidate_index in chosen:
1628
+ continue
1629
+ if not bool(torch.isfinite(potentials[row_index, candidate_index])):
1630
+ continue
1631
+ chosen.append(candidate_index)
1632
+ if len(chosen) >= candidate_oracle_rollouts:
1633
+ break
1634
+ while len(chosen) < candidate_oracle_rollouts:
1635
+ chosen.append(selected_index)
1636
+ rows.append(chosen)
1637
+ return torch.tensor(rows, dtype=torch.long, device=potentials.device)
1638
+
1639
+
1640
  def _select_action_chunk(
1641
  model: DoVLAModel,
1642
  observations: Any,
 
1990
  device=candidate_type_bonus.device,
1991
  )
1992
  candidate_type_bonus = torch.cat([baseline_bonus, candidate_type_bonus], dim=1)
1993
+ candidates, potentials = _score_lattice_action_chunks(
1994
+ model,
1995
+ observations,
1996
+ instructions,
1997
+ action_candidates,
1998
+ torch=torch,
1999
+ action_low=action_low,
2000
+ action_high=action_high,
2001
+ candidate_mask=candidate_mask,
2002
+ candidate_type_bonus=candidate_type_bonus,
2003
+ action_l2_penalty=action_l2_penalty,
2004
+ action_l2_penalty_base=action_l2_penalty_base,
2005
+ )
2006
+ batch_size, candidate_count = potentials.shape
2007
+ best_index = torch.argmax(potentials, dim=1)
2008
+ if selection_margin > 0.0 and candidate_count > 0:
2009
+ baseline = potentials[:, 0]
2010
+ best_potential = potentials.gather(1, best_index.reshape(batch_size, 1)).reshape(
2011
+ batch_size
2012
+ )
2013
+ keep_baseline = best_potential <= baseline + float(selection_margin)
2014
+ best_index = torch.where(keep_baseline, torch.zeros_like(best_index), best_index)
2015
+ batch_index = torch.arange(batch_size, device=candidates.device)
2016
+ best_actions = candidates[batch_index, best_index]
2017
+ return best_actions, best_index.detach().cpu().numpy()
2018
+
2019
+
2020
+ def _score_lattice_action_chunks(
2021
+ model: DoVLAModel,
2022
+ observations: Any,
2023
+ instructions: list[str],
2024
+ action_candidates: Any,
2025
+ *,
2026
+ torch: Any,
2027
+ action_low: Any | None,
2028
+ action_high: Any | None,
2029
+ candidate_mask: Any | None,
2030
+ candidate_type_bonus: Any | None = None,
2031
+ action_l2_penalty: float = 0.0,
2032
+ action_l2_penalty_base: Any | None = None,
2033
+ ) -> tuple[Any, Any]:
2034
+ if action_candidates.ndim != 4:
2035
+ raise ValueError("action_candidates must have shape [B,K,H,D]")
2036
  batch_size, candidate_count = action_candidates.shape[:2]
2037
  candidates = _clamp_action_tensor(
2038
  action_candidates,
 
2071
  penalty = delta.reshape(batch_size, candidate_count, -1).pow(2).mean(dim=2)
2072
  potentials = potentials - float(action_l2_penalty) * penalty
2073
  if candidate_mask is not None:
2074
+ if candidate_mask.shape != potentials.shape:
2075
+ raise ValueError("candidate_mask must have shape [B,K]")
2076
  potentials = potentials.masked_fill(~candidate_mask, float("-inf"))
2077
+ return candidates, potentials
 
 
 
 
 
 
 
 
 
 
2078
 
2079
 
2080
  def _select_residual_lattice_action_chunk(
 
2126
  selection_margin=selection_margin,
2127
  action_l2_penalty=action_l2_penalty,
2128
  )
2129
+ candidates, candidate_mask, candidate_type_bonus = _build_residual_lattice_candidates(
2130
+ policy_mean,
2131
+ residuals,
2132
+ torch=torch,
2133
+ residual_scale=residual_scale,
2134
+ residual_scales=residual_scales,
2135
+ candidate_mask=candidate_mask,
2136
+ candidate_type_bonus=candidate_type_bonus,
2137
+ num_gaussian_candidates=num_gaussian_candidates,
2138
+ candidate_sigma=candidate_sigma,
2139
+ selection_seed=selection_seed,
2140
+ )
2141
+ return _select_lattice_action_chunk(
2142
+ model,
2143
+ observations,
2144
+ instructions,
2145
+ candidates,
2146
+ torch=torch,
2147
+ action_low=(
2148
+ action_low.unsqueeze(1)
2149
+ if action_low is not None and action_low.ndim == 3
2150
+ else action_low
2151
+ ),
2152
+ action_high=(
2153
+ action_high.unsqueeze(1)
2154
+ if action_high is not None and action_high.ndim == 3
2155
+ else action_high
2156
+ ),
2157
+ candidate_mask=candidate_mask,
2158
+ candidate_type_bonus=candidate_type_bonus,
2159
+ selection_margin=selection_margin,
2160
+ action_l2_penalty=action_l2_penalty,
2161
+ action_l2_penalty_base=policy_mean,
2162
+ )
2163
+
2164
+
2165
+ def _build_residual_lattice_candidates(
2166
+ policy_mean: Any,
2167
+ residuals: Any,
2168
+ *,
2169
+ torch: Any,
2170
+ residual_scale: float,
2171
+ residual_scales: tuple[float, ...],
2172
+ candidate_mask: Any | None,
2173
+ candidate_type_bonus: Any | None,
2174
+ num_gaussian_candidates: int,
2175
+ candidate_sigma: float,
2176
+ selection_seed: int,
2177
+ ) -> tuple[Any, Any | None, Any | None]:
2178
+ residuals = _align_residual_horizon_to_policy(policy_mean, residuals, torch=torch)
2179
+ scales = tuple(float(scale) for scale in residual_scales) or (float(residual_scale),)
2180
  candidate_blocks = [
2181
  policy_mean.unsqueeze(1) + scale * residuals
2182
  for scale in scales
 
2218
  device=candidate_type_bonus.device,
2219
  )
2220
  candidate_type_bonus = torch.cat([candidate_type_bonus, extra_bonus], dim=1)
2221
+ return candidates, candidate_mask, candidate_type_bonus
2222
+
2223
+
2224
+ def _align_residual_horizon_to_policy(policy_mean: Any, residuals: Any, *, torch: Any) -> Any:
2225
+ if residuals.ndim != 4:
2226
+ raise ValueError("residuals must have shape [B,K,H,D]")
2227
+ if policy_mean.ndim != 3:
2228
+ raise ValueError("policy_mean must have shape [B,H,D]")
2229
+ policy_horizon = int(policy_mean.shape[1])
2230
+ residual_horizon = int(residuals.shape[2])
2231
+ if residual_horizon == policy_horizon:
2232
+ return residuals
2233
+ if residual_horizon > policy_horizon:
2234
+ return residuals[:, :, :policy_horizon, :]
2235
+ pad_shape = (
2236
+ residuals.shape[0],
2237
+ residuals.shape[1],
2238
+ policy_horizon - residual_horizon,
2239
+ residuals.shape[3],
 
 
2240
  )
2241
+ padding = torch.zeros(
2242
+ pad_shape,
2243
+ dtype=residuals.dtype,
2244
+ device=residuals.device,
2245
+ )
2246
+ return torch.cat([residuals, padding], dim=2)
2247
 
2248
 
2249
  def _select_field_softmax_residual_action_chunk(
 
2705
 
2706
 
2707
  def _summarize_rows(rows: list[dict[str, Any]]) -> dict[str, Any]:
2708
+ summary = {
2709
  "num_groups": len(rows),
2710
  "policy_rollout_success_rate": _mean([row["success"] for row in rows]),
2711
  "policy_rollout_progress": _mean([row["progress"] for row in rows]),
 
2720
  "action_mse_to_best": _mean([row["action_mse_to_best"] for row in rows]),
2721
  "restore_max_error": max([row["restore_error"] for row in rows], default=0.0),
2722
  }
2723
+ candidate_oracle_rows = [
2724
+ row for row in rows if row.get("candidate_oracle_success") is not None
2725
+ ]
2726
+ if candidate_oracle_rows:
2727
+ summary.update(_candidate_oracle_summary(candidate_oracle_rows))
2728
+ return summary
2729
+
2730
+
2731
+ def _candidate_oracle_summary(rows: list[dict[str, Any]]) -> dict[str, Any]:
2732
+ return {
2733
+ "candidate_oracle_success_rate": _mean(
2734
+ [row["candidate_oracle_success"] for row in rows]
2735
+ ),
2736
+ "candidate_oracle_progress": _mean(
2737
+ [row["candidate_oracle_progress"] for row in rows]
2738
+ ),
2739
+ "candidate_oracle_score": _mean(
2740
+ [row["candidate_oracle_score"] for row in rows]
2741
+ ),
2742
+ "candidate_oracle_regret": _mean(
2743
+ [row["candidate_oracle_regret"] for row in rows]
2744
+ ),
2745
+ "candidate_oracle_score_gain_over_selected": _mean(
2746
+ [row["candidate_oracle_score_gain_over_selected"] for row in rows]
2747
+ ),
2748
+ "candidate_oracle_improvement_rate": _mean(
2749
+ [row["candidate_oracle_improves_selected"] for row in rows]
2750
+ ),
2751
+ "candidate_oracle_selected_branch_success_rate": _mean(
2752
+ [row["candidate_oracle_selected_branch_success"] for row in rows]
2753
+ ),
2754
+ "candidate_oracle_selected_branch_progress": _mean(
2755
+ [row["candidate_oracle_selected_branch_progress"] for row in rows]
2756
+ ),
2757
+ "candidate_oracle_restore_max_error": max(
2758
+ [row["candidate_oracle_restore_error"] for row in rows],
2759
+ default=0.0,
2760
+ ),
2761
+ "candidate_oracle_type_counts": dict(
2762
+ Counter(row["candidate_oracle_candidate_type"] for row in rows)
2763
+ ),
2764
+ }
2765
 
2766
 
2767
  def _mean(values: list[Any]) -> float: