Repository navigation
Automatic test-tolerance measurement and tightening (RBF pilot) #1357
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5880c60
dbe4fe9
2f32a3b
db07312
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| { | ||
| "Cell": { | ||
| "exclaim_ape_R02B04": 3.1e-09, | ||
| "exclaim_ch_r04b09_dsl": 0.04, | ||
| "exclaim_gauss3d": 1e-14 | ||
| }, | ||
| "Edge": { | ||
| "exclaim_ape_R02B04": 8e-14, | ||
| "exclaim_ch_r04b09_dsl": 2e-09, | ||
| "exclaim_gauss3d": 0 | ||
| }, | ||
| "Vertex": { | ||
| "exclaim_ape_R02B04": 3e-10, | ||
| "exclaim_ch_r04b09_dsl": 0.003, | ||
| "exclaim_gauss3d": 1e-15 | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -33,5 +33,15 @@ def _default_download_cache() -> pathlib.Path: | |||||
| DALLCLOSE_PRINT_INSTEAD_OF_FAIL: bool = env.flag_to_bool( | ||||||
| "ICON4PY_DALLCLOSE_PRINT_INSTEAD_OF_FAIL", False | ||||||
| ) | ||||||
| # When set to a file path, 'assert_dallclose' records the measured max absolute/relative | ||||||
| # differences (instead of asserting) so that tolerances can be measured and tightened automatically. | ||||||
|
Comment on lines
+36
to
+37
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
You don't need this |
||||||
| RECORD_TOLERANCES_PATH: pathlib.Path | None = ( | ||||||
| env.path("ICON4PY_RECORD_TOLERANCES", pathlib.Path()) | ||||||
| if "ICON4PY_RECORD_TOLERANCES" in os.environ | ||||||
| else None | ||||||
| ) | ||||||
| # When enabled, 'assert_dallclose' emits a non-failing warning whenever a passed tolerance is much | ||||||
| # larger than the measured difference, flagging tolerances that have become too loose. | ||||||
|
Comment on lines
+43
to
+44
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Neither this |
||||||
| TOLERANCE_DRIFT_WARN: bool = env.flag_to_bool("ICON4PY_TOLERANCE_DRIFT_WARN", False) | ||||||
| DOWNLOAD_CACHE_PATH: pathlib.Path = env.path("ICON4PY_DOWNLOAD_CACHE", _default_download_cache()) | ||||||
| DRIVER_LOGGING_LEVEL: str = os.environ.get("ICON4PY_DRIVER_LOGGING_LEVEL", "debug") | ||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -13,7 +13,7 @@ | |||||
| import pytest | ||||||
|
|
||||||
| from icon4py.model.common import model_backends | ||||||
| from icon4py.model.testing import filters | ||||||
| from icon4py.model.testing import config as testing_config, filters, tolerances | ||||||
|
|
||||||
|
|
||||||
| __all__ = [ | ||||||
|
|
@@ -51,6 +51,10 @@ def pytest_configure(config): | |||||
| if config.getoption("--datatest-skip"): | ||||||
| config.option.markexpr = " and ".join(["not datatest", *m_option]) | ||||||
|
|
||||||
| # Activate tolerance recording/drift detection when the corresponding options are enabled. | ||||||
| if testing_config.RECORD_TOLERANCES_PATH is not None or testing_config.TOLERANCE_DRIFT_WARN: | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I think you can also just say this |
||||||
| tolerances.activate_recorder() | ||||||
|
|
||||||
| handle_mpi_options(config) | ||||||
|
|
||||||
|
|
||||||
|
|
@@ -159,10 +163,27 @@ def pytest_collection_modifyitems(config, items): | |||||
| ) | ||||||
|
|
||||||
|
|
||||||
| def _record_test_context(item: pytest.Item) -> None: | ||||||
| """Provide the current test id, backend and experiment to the active tolerance recorder.""" | ||||||
| recorder = tolerances.get_active_recorder() | ||||||
| if recorder is None: | ||||||
| return | ||||||
| params = getattr(item, "callspec", None) | ||||||
| params = params.params if params is not None else {} | ||||||
| experiment = params.get("experiment_description", params.get("experiment", "")) | ||||||
| recorder.set_context( | ||||||
| nodeid=item.nodeid, | ||||||
| backend=item.config.getoption("--backend"), | ||||||
| experiment=getattr(experiment, "name", str(experiment)), | ||||||
| ) | ||||||
|
|
||||||
|
|
||||||
| @pytest.hookimpl(trylast=True) | ||||||
| def pytest_runtest_setup(item: pytest.Item) -> None: | ||||||
| """Apply test item filters as the final test setup step.""" | ||||||
|
|
||||||
| _record_test_context(item) | ||||||
|
|
||||||
| item_marker_filters = filters.item_marker_filters | ||||||
| for marker_name in set(m.name for m in item.iter_markers()) & item_marker_filters.keys(): | ||||||
| item_filter = item_marker_filters[marker_name] | ||||||
|
|
@@ -236,10 +257,26 @@ def pytest_runtest_makereport(item, call): | |||||
| report.sections.append(("benchmark-extra", tuple([filtered_benchmark_name, info]))) | ||||||
|
|
||||||
|
|
||||||
| def _report_tolerance_drift(terminalreporter) -> None: | ||||||
| """Print a non-failing summary of tolerances that are much looser than the measured difference.""" | ||||||
| recorder = tolerances.get_active_recorder() | ||||||
| if recorder is None or not recorder.drift_warnings: | ||||||
| return | ||||||
| terminalreporter.ensure_newline() | ||||||
| terminalreporter.section("Tolerance drift (tolerances too loose)", sep="-", yellow=True) | ||||||
| for warning in recorder.drift_warnings: | ||||||
| terminalreporter.line( | ||||||
| f"{warning.nodeid} [{warning.field}]: atol={warning.atol:g} " | ||||||
| f"but measured max diff {warning.max_abs:g}" | ||||||
| ) | ||||||
|
|
||||||
|
|
||||||
| def pytest_terminal_summary(terminalreporter, exitstatus, config): | ||||||
| """ | ||||||
| Add a custom section to the terminal summary with GT4Py timer metrics from benchmarks. | ||||||
| """ | ||||||
| _report_tolerance_drift(terminalreporter) | ||||||
|
|
||||||
| # Gather gtx_metrics | ||||||
| benchmark_gtx_metrics = [] | ||||||
| for outcome in ("passed", "failed", "skipped"): | ||||||
|
|
@@ -398,3 +435,7 @@ def pytest_sessionfinish(session: pytest.Session, exitstatus: int) -> None: | |||||
| scheduler = getattr(session.config, "_mpi_scheduler", None) | ||||||
| if scheduler is not None: | ||||||
| scheduler.finalize() | ||||||
|
|
||||||
| recorder = tolerances.get_active_recorder() | ||||||
| if recorder is not None and testing_config.RECORD_TOLERANCES_PATH is not None: | ||||||
| recorder.dump(testing_config.RECORD_TOLERANCES_PATH) | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.