Skip to content

Move validator business logic into every_eval_ever and update to de-duplication logic#194

Open
nelaturuharsha wants to merge 2 commits into
mainfrom
validator_changes
Open

Move validator business logic into every_eval_ever and update to de-duplication logic#194
nelaturuharsha wants to merge 2 commits into
mainfrom
validator_changes

Conversation

@nelaturuharsha

Copy link
Copy Markdown
Collaborator
  • Summary: Single source of truth for validation rules added to cli, and dedup operations now are part of the cli. PR to merge is up. README is officially out of date.
  • Changes:
    • Warning when score_type, max_score and min_score are not mentioned.
    • score should be between [min_score, max_score]
    • If HF model is provided, it should exist - wellness check for both model and dataset.
    • Will now check to ensure mandatory metadata under additional details for model_info i.e.
    • deployment_type and model_availability are present. Adapters need to be updated to fix this.
    • new commands/functionality added to cli
    • Users will have to submit deployment_type and model_availability in additional_details which the bot will enforce.
    • Three options: api, local, unknown
      • if api: model_availability can be -> closed_source, open_weights_deployment, other
      • if local: model_availability can be. -> hf, unavailable, other
      • if hf -> wellness check will ensure it exists

Ruff/Ty were run which formatted some adapters.

@Erotemic Erotemic left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly just a python-level review, I didn't consider anything holistically.

Comment thread every_eval_ever/dedup.py
file_fingerprints: dict[str, str] = {}
warnings: list[str] = []
for file_path in sorted(file_paths):
if not file_path.endswith('.json'):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a footgun. If you are expecting this to be called with only json paths, and you ever hit this case, you are silently dropping information about some bad pre-filtering done when constructing the content passed to this function. I would either remove this continue or raise an error.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had this in place because we don't have a good recipe to reliably de-duplicate jsonls at the moment and we have only jsons or jsonls in the target datastore.

Comment thread every_eval_ever/dedup.py
Comment thread every_eval_ever/dedup.py Outdated
Comment thread every_eval_ever/dedup.py
def compute_fingerprint(content: bytes) -> str:
"""Compute the semantic duplicate fingerprint for aggregate JSON bytes."""
try:
data = json.loads(content)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might end up being a bottleneck. It may be worth it to abstract the specific json reader / writer so the stdlib json is always an option, but you can opt-in to something like orjson or ujson. (Note: orjson is a bit opinionated. It's fast, but it also means you can't serialize generic floats like nans or infs, which technically isn't json, but the stdlib and ujson aren't so strict about it).

I've handled this in a few ways in the past: simple wrappers: https://gitlab.kitware.com/computer-vision/kwutil/-/blob/main/kwutil/util_json.py?blame=1&ref_type=heads#L391

And module level globals: https://gitlab.kitware.com/computer-vision/kwcoco/-/blob/main/kwcoco/coco_dataset.py?blame=1&ref_type=heads#L117

Comment thread every_eval_ever/dedup.py
return value


def _canon(obj: Any) -> Any:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The recursive implementation could probably be much faster using an iterator and maintaining your own stack: https://gitlab.kitware.com/computer-vision/kwutil/-/blob/main/kwutil/util_json.py?ref_type=heads#L29

A utility I wrote makes this fairly easy to express without too much extra complication: https://github.com/Erotemic/ubelt/blob/main/ubelt/util_indexable.py#L52

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think we're just going fingerprints that we created and not the JSON, would this still be a big performance gap you think?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd have to profile it to know, but recursion can get pretty costly. I just tend to avoid it when writing new code. Might be fine the way it is.

FWIW: a quick pass with sample data on both variants with line-profiler will tell you a lot.

Comment thread every_eval_ever/dedup.py Outdated
Comment thread every_eval_ever/dedup.py Outdated
Comment thread utils/multi_swe_bench/adapter.py
Comment thread every_eval_ever/dedup.py Outdated
Comment thread .gitignore
.sass-cache/
.jekyll-cache/
.jekyll-metadata No newline at end of file
.jekyll-metadataUSAGE_*

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a typo?

Comment on lines +86 to 90
local_paths = {file_path: file_path for file_path in file_paths}
dedup_report = check_duplicates(file_paths, local_paths, {'files': {}})
duplicate_results = [
result for result in dedup_report.results if result.duplicate_of
]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it only does local same-batch duplicate detection now. Since the manifest is always {'files': {}}, this path can’t catch “this candidate already exists in the accepted datastore,” even though the docs say the PR bot compares candidates against accepted records. Could we either load/pass the real manifest here, or make this command/docs explicit that this is local-only? Also, since file_paths are local CLI paths, we probably want to normalize them through repo_path_from_path; otherwise ./data/... or absolute paths won’t scope correctly by data/.

Comment thread every_eval_ever/dedup.py
Comment on lines +60 to +71
def _source_identity(source_data: Any) -> dict[str, Any]:
if not isinstance(source_data, dict):
return {}
identity = {
'type': _norm_str(source_data.get('source_type')),
'name': _norm_str(source_data.get('dataset_name')),
}
if source_data.get('source_type') == 'hf_dataset':
repo = _norm_str(source_data.get('hf_repo'))
if repo:
identity['hf'] = repo
return identity

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems too loose for non-HF sources. For url or leaderboard-style sources, two different datasets with the same dataset_name would collapse to the same source identity because the URL/source artifact/version is ignored. That could create false duplicate hits across different leaderboards or dataset releases. Could we include a normalized URL / source artifact / version field when present, not just dataset_name?

Comment on lines +185 to +206
def check_companion_exists(
repo_path: str,
aggregate_data: dict[str, Any],
available_files: Container[str],
) -> list[str]:
"""Warn when aggregate detailed results point to a missing JSONL companion."""
detail = aggregate_data.get('detailed_evaluation_results')
if not isinstance(detail, dict) or not detail.get('file_path'):
return []

folder = Path(repo_path).parent
uuid = Path(repo_path).stem
expected = {
str(folder / f'{uuid}.jsonl'),
str(folder / f'{uuid}_samples.jsonl'),
}
if not any(path in available_files for path in expected):
return [
f"Companion .jsonl for '{Path(repo_path).name}' not found "
'in the dataset or this PR'
]
return []

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ignores the actual detailed_evaluation_results.file_path and just infers the companion path from the aggregate UUID. That can pass when the referenced file is wrong but a same-UUID JSONL exists, and fail when the aggregate intentionally points to a different relative path. Could we validate the referenced path directly, maybe with same-folder UUID aliases as a fallback?

Comment on lines +216 to +246
for index, result in enumerate(results):
if not isinstance(result, dict):
continue
metric = result.get('metric_config')
if not isinstance(metric, dict):
continue
for key in ('score_type', 'min_score', 'max_score'):
if key not in metric:
warnings.append(
f"evaluation_results[{index}].metric_config: missing '{key}'"
)

score_details = result.get('score_details')
if not isinstance(score_details, dict):
continue
score = score_details.get('score')
lo = metric.get('min_score')
hi = metric.get('max_score')
if (
isinstance(score, (int, float))
and not isinstance(score, bool)
and isinstance(lo, (int, float))
and not isinstance(lo, bool)
and isinstance(hi, (int, float))
and not isinstance(hi, bool)
and (score < lo or score > hi)
):
warnings.append(
f'evaluation_results[{index}]: score {score} is outside '
f'[min_score={lo}, max_score={hi}]'
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only warns when the key is absent, but fails silently when they are present but null or NaN, and the bound checks for lo and hi are also silently skipped. If these fields are now expected, can we treat None/blank the same as missing?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants