Spaces:
Running on Zero
Running on Zero
Download codestar/core/comparator.py from dongyanggg/codestar-backend: direct link, hf CLI and curl.
- Browser
- Download file 7.17 kB
-
https://huggingface.co/spaces/dongyanggg/codestar-backend/resolve/main/codestar/core/comparator.py
- Command line
-
hf download hf://spaces/dongyanggg/codestar-backend/codestar/core/comparator.py
-
curl -L -o comparator.py https://huggingface.co/spaces/dongyanggg/codestar-backend/resolve/main/codestar/core/comparator.py
7.17 kB
| from __future__ import annotations | |
| import math | |
| from typing import Any, Tuple, Optional | |
| try: | |
| import torch | |
| HAS_TORCH = True | |
| except ImportError: | |
| HAS_TORCH = False | |
| try: | |
| import numpy as np | |
| HAS_NUMPY = True | |
| except ImportError: | |
| HAS_NUMPY = False | |
| def summarize_obj(obj: Any, max_len: int = 60) -> str: | |
| """Generate a clean, informative one-line summary of a tensor, array, or object.""" | |
| if HAS_TORCH and isinstance(obj, torch.Tensor): | |
| return f"Tensor(shape={list(obj.shape)}, dtype={str(obj.dtype).replace('torch.', '')})" | |
| if HAS_NUMPY and isinstance(obj, np.ndarray): | |
| return f"ndarray(shape={list(obj.shape)}, dtype={obj.dtype})" | |
| if isinstance(obj, (list, tuple)): | |
| items_preview = ", ".join(summarize_obj(x, 20) for x in obj[:3]) | |
| if len(obj) > 3: | |
| items_preview += f", ... (+{len(obj)-3} more)" | |
| bracket = ("(", ")") if isinstance(obj, tuple) else ("[", "]") | |
| return f"{bracket[0]}{items_preview}{bracket[1]}" | |
| s = str(obj) | |
| if len(s) > max_len: | |
| return s[:max_len] + "..." | |
| return s | |
| def compare_outputs( | |
| actual: Any, | |
| expected: Any, | |
| atol: float = 1e-4, | |
| rtol: float = 1e-4, | |
| ) -> Tuple[bool, Optional[str], Optional[float]]: | |
| """ | |
| Compare actual output against expected output with numerical tolerance. | |
| Returns: | |
| (is_match, error_message, max_diff) | |
| """ | |
| # 1. Exact identity or both None | |
| if actual is expected: | |
| return True, None, 0.0 | |
| # 2. PyTorch Tensor comparison | |
| if HAS_TORCH and (isinstance(actual, torch.Tensor) or isinstance(expected, torch.Tensor)): | |
| if not isinstance(actual, torch.Tensor): | |
| return False, f"Type mismatch: expected torch.Tensor, got {type(actual).__name__}", None | |
| if not isinstance(expected, torch.Tensor): | |
| return False, f"Type mismatch: expected {type(expected).__name__}, got torch.Tensor", None | |
| # Check shape | |
| if actual.shape != expected.shape: | |
| return False, f"Shape mismatch: expected {list(expected.shape)}, got {list(actual.shape)}", None | |
| # Check dtype | |
| if actual.dtype != expected.dtype: | |
| return False, f"Dtype mismatch: expected {expected.dtype}, got {actual.dtype}", None | |
| act = actual.detach().cpu().float() | |
| exp = expected.detach().cpu().float() | |
| # Check for NaN / Inf | |
| if torch.isnan(act).any() and not torch.isnan(exp).any(): | |
| return False, "Output contains unexpected NaN values", None | |
| if torch.isinf(act).any() and not torch.isinf(exp).any(): | |
| return False, "Output contains unexpected Inf values", None | |
| # Filter out identical infinite values (e.g. -inf and -inf) to avoid (-inf) - (-inf) => NaN | |
| matching_neginf = torch.isneginf(act) & torch.isneginf(exp) | |
| matching_posinf = torch.isposinf(act) & torch.isposinf(exp) | |
| matching_inf = matching_neginf | matching_posinf | |
| matching_nan = torch.isnan(act) & torch.isnan(exp) | |
| ignore_mask = matching_inf | matching_nan | |
| if ignore_mask.all(): | |
| max_diff = 0.0 | |
| else: | |
| act_diff = torch.where(ignore_mask, torch.zeros_like(act), act) | |
| exp_diff = torch.where(ignore_mask, torch.zeros_like(exp), exp) | |
| diff = torch.abs(act_diff - exp_diff) | |
| finite_diff = diff[~torch.isnan(diff) & ~torch.isinf(diff)] | |
| max_diff = float(finite_diff.max().item()) if finite_diff.numel() > 0 else 0.0 | |
| if torch.allclose(act, exp, rtol=rtol, atol=atol, equal_nan=True): | |
| return True, None, max_diff | |
| else: | |
| return ( | |
| False, | |
| f"Numerical mismatch: max absolute diff {max_diff:.6e} > tolerance (atol={atol}, rtol={rtol})", | |
| max_diff, | |
| ) | |
| # 3. NumPy ndarray comparison | |
| if HAS_NUMPY and (isinstance(actual, np.ndarray) or isinstance(expected, np.ndarray)): | |
| if not isinstance(actual, np.ndarray): | |
| return False, f"Type mismatch: expected np.ndarray, got {type(actual).__name__}", None | |
| if not isinstance(expected, np.ndarray): | |
| return False, f"Type mismatch: expected {type(expected).__name__}, got np.ndarray", None | |
| if actual.shape != expected.shape: | |
| return False, f"Shape mismatch: expected {list(expected.shape)}, got {list(actual.shape)}", None | |
| if actual.dtype != expected.dtype: | |
| return False, f"Dtype mismatch: expected {expected.dtype}, got {actual.dtype}", None | |
| act = actual.astype(np.float64) | |
| exp = expected.astype(np.float64) | |
| if np.isnan(act).any() and not np.isnan(exp).any(): | |
| return False, "Output contains unexpected NaN values", None | |
| if np.isinf(act).any() and not np.isinf(exp).any(): | |
| return False, "Output contains unexpected Inf values", None | |
| matching_inf = np.isinf(act) & np.isinf(exp) & (np.sign(act) == np.sign(exp)) | |
| matching_nan = np.isnan(act) & np.isnan(exp) | |
| ignore_mask = matching_inf | matching_nan | |
| if ignore_mask.all(): | |
| max_diff = 0.0 | |
| else: | |
| act_diff = np.where(ignore_mask, 0.0, act) | |
| exp_diff = np.where(ignore_mask, 0.0, exp) | |
| diff = np.abs(act_diff - exp_diff) | |
| finite_diff = diff[~np.isnan(diff) & ~np.isinf(diff)] | |
| max_diff = float(np.max(finite_diff)) if finite_diff.size > 0 else 0.0 | |
| if np.allclose(act, exp, rtol=rtol, atol=atol, equal_nan=True): | |
| return True, None, max_diff | |
| else: | |
| return ( | |
| False, | |
| f"Numerical mismatch: max absolute diff {max_diff:.6e} > tolerance (atol={atol}, rtol={rtol})", | |
| max_diff, | |
| ) | |
| # 4. Sequences (tuple / list) | |
| if isinstance(expected, (tuple, list)): | |
| if not isinstance(actual, (tuple, list)): | |
| return False, f"Type mismatch: expected {type(expected).__name__}, got {type(actual).__name__}", None | |
| if len(actual) != len(expected): | |
| return False, f"Length mismatch: expected {len(expected)} elements, got {len(actual)}", None | |
| max_sub_diff = 0.0 | |
| for i, (act_elem, exp_elem) in enumerate(zip(actual, expected)): | |
| match, err, sub_diff = compare_outputs(act_elem, exp_elem, atol=atol, rtol=rtol) | |
| if sub_diff is not None: | |
| max_sub_diff = max(max_sub_diff, sub_diff) | |
| if not match: | |
| return False, f"Element [{i}] mismatch: {err}", max_sub_diff | |
| return True, None, max_sub_diff | |
| # 5. Scalar numbers (float, int) | |
| if isinstance(expected, (float, int)) and isinstance(actual, (float, int)): | |
| diff = abs(float(actual) - float(expected)) | |
| allowed = atol + rtol * abs(float(expected)) | |
| if diff <= allowed: | |
| return True, None, diff | |
| return False, f"Value mismatch: expected {expected}, got {actual} (diff {diff:.6e} > {allowed:.6e})", diff | |
| # 6. Fallback exact equality | |
| if actual == expected: | |
| return True, None, 0.0 | |
| return False, f"Expected {expected!r}, but got {actual!r}", None | |