优化雅可比矩阵计算;端口转发情况下仿真结果传输方式优化
This commit is contained in:
1 parent
3bc4be3c06
commit
aa4951b14e
28 files changed
+8038
-23
No files matched your search
@@ -13,6 +13,9 @@ unchanged. Full output/state/counter equality is checked outside run timing.
|
||||
Inclusive durations are nested: only exclusiveSeconds may be added. Clock and
|
||||
bookkeeping overhead remain in measured totals; compare against the control.
|
||||
No property/pipe/libc allocation is inferred from this outer-only diagnostic.
|
||||
The production automatic Jacobian is used by default. --verify-jacobian enables
|
||||
full-matrix checking; those diagnostic timings are not ordinary production cost.
|
||||
Recorded --jacobian auto/verify options are translated; dense replay is rejected.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -32,7 +35,7 @@ ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
from app.simulation.native_codegen.build import LIBRARIES, toolchain
|
||||
|
||||
CATEGORIES = ["integration", "cvode_step", "rhs", "poll", "accept", "append", "dense_output", "dense_setup", "dense_solve"]
|
||||
CATEGORIES = ["integration", "cvode_step", "rhs", "poll", "accept", "append", "dense_output", "dense_setup", "dense_solve", "jacobian"]
|
||||
COUNTERS = ["rhs", "linear_rhs", "nonlinear_iterations", "nonlinear_failures"]
|
||||
PROFILE_HEADER = r'''
|
||||
#ifndef NATIVE_COMPUTE_PROFILE_H
|
||||
@@ -145,8 +148,8 @@ def instrument(native: Path) -> None:
|
||||
(native / "include/compute_profile.h").write_text(PROFILE_HEADER.replace("@CATEGORIES@", ", ".join("PROFILE_" + c.upper() for c in CATEGORIES)))
|
||||
(native / "runtime/compute_profile.c").write_text(PROFILE_SOURCE.replace("@NAMES@", ",".join(json.dumps(c) for c in CATEGORIES)))
|
||||
for filename, functions in {
|
||||
"common.c": {"native_rhs": "rhs", "native_poll": "poll", "native_accept": "accept", "native_append": "append"},
|
||||
"cvode_solver.c": {"native_bdf": "integration", "cv_dense": "dense_output"},
|
||||
"common.c": {"native_rhs": "rhs", "native_jacobian_rhs": "rhs", "native_poll": "poll", "native_accept": "accept", "native_append": "append"},
|
||||
"cvode_solver.c": {"native_bdf": "integration", "cv_dense": "dense_output", "cv_jacobian": "jacobian"},
|
||||
"rk45.c": {"native_rk45": "integration"},
|
||||
}.items():
|
||||
path = native / "runtime" / filename
|
||||
@@ -168,7 +171,13 @@ def instrument(native: Path) -> None:
|
||||
path.write_text(text)
|
||||
|
||||
|
||||
def runtime_arguments(stages: Path | None) -> list[str]:
|
||||
def runtime_arguments(stages: Path | None, verify_jacobian: bool = False) -> list[str]:
|
||||
"""Replay numerical options using the production Jacobian only.
|
||||
|
||||
Historical auto becomes the default and verify becomes --verify-jacobian.
|
||||
A historical dense request must run with its frozen historical tool/runtime;
|
||||
silently replaying it with today's automatic algorithm would fake a baseline.
|
||||
"""
|
||||
if stages:
|
||||
original = json.loads(stages.read_text())["process"]["command"]
|
||||
args = original[1:]
|
||||
@@ -178,18 +187,30 @@ def runtime_arguments(stages: Path | None) -> list[str]:
|
||||
value_options = {"--method", "--start", "--stop", "--sample-step", "--max-step", "--rtol", "--timeout"}
|
||||
while index < len(args):
|
||||
key = args[index]
|
||||
if key == "--verify-jacobian":
|
||||
verify_jacobian = True; index += 1; continue
|
||||
if key == "--solve-only":
|
||||
safe.append(key); index += 1; continue
|
||||
if key not in value_options | {"--output", "--result-index", "--cancel-file"} or index + 1 >= len(args):
|
||||
if key not in value_options | {"--jacobian", "--output", "--result-index", "--cancel-file"} or index + 1 >= len(args):
|
||||
raise RuntimeError(f"Unsupported replay argument: {key}")
|
||||
if key in value_options:
|
||||
if key == "--jacobian":
|
||||
recorded = args[index + 1]
|
||||
if recorded == "dense":
|
||||
raise RuntimeError("Historical --jacobian dense requires the frozen historical executable and tool; current production has no legacy strategy selector")
|
||||
if recorded not in {"auto", "verify"}:
|
||||
raise RuntimeError(f"Unsupported historical Jacobian mode: {recorded}")
|
||||
verify_jacobian = verify_jacobian or recorded == "verify"
|
||||
elif key in value_options:
|
||||
safe.extend(args[index:index + 2])
|
||||
index += 2
|
||||
if verify_jacobian:
|
||||
safe.append("--verify-jacobian")
|
||||
return safe
|
||||
|
||||
|
||||
def prepare(args: argparse.Namespace) -> dict:
|
||||
cache, output = args.cache_dir.resolve(), args.output_dir.resolve()
|
||||
numerical_arguments = runtime_arguments(args.request_stages, args.verify_jacobian)
|
||||
if not output.is_relative_to(ROOT / "test"):
|
||||
raise RuntimeError("Diagnostic output must be in the repository's ignored test/ directory")
|
||||
manifest = json.loads((cache / "manifest.json").read_text())
|
||||
@@ -223,7 +244,7 @@ def prepare(args: argparse.Namespace) -> dict:
|
||||
shutil.copy2(cache / name, output / name)
|
||||
instrument(native)
|
||||
command = [compiler, *manifest["compilerFlags"], "-I", str(output), "-I", str(native / "include"), "-I", str(sundials / "include"), str(output / "model.c"), *map(str, sorted(native.rglob("*.c"))), "-Wl,--start-group", *map(str, libraries), "-Wl,--end-group", "-lm", "-o", str(output / "profiled-model")]
|
||||
prepared = {"controlExecutable": str(cache / "model"), "profiledExecutable": str(output / "profiled-model"), "buildCommand": command, "runtimeArguments": runtime_arguments(args.request_stages), "cacheDir": str(cache), "cachedMainDifference": differences, "stateCount": len(manifest["stateKeys"]), "modelSha256": digest(output / "model.c"), "compiler": compiler_version, "warmups": args.warmups, "repeats": args.repeats, "scope": "Inclusive times overlap. Exclusive scope times partition integration including instrumentation overhead. No component or libc sub-cost inference.", "sourceHashes": {str(p.relative_to(output)): digest(p) for p in sorted(native.rglob("*")) if p.is_file()}}
|
||||
prepared = {"controlExecutable": str(cache / "model"), "profiledExecutable": str(output / "profiled-model"), "buildCommand": command, "runtimeArguments": numerical_arguments, "verifyJacobian": "--verify-jacobian" in numerical_arguments, "algorithm": "production-automatic", "cacheDir": str(cache), "cachedMainDifference": differences, "stateCount": len(manifest["stateKeys"]), "modelSha256": digest(output / "model.c"), "compiler": compiler_version, "warmups": args.warmups, "repeats": args.repeats, "scope": "Inclusive times overlap. Exclusive scope times partition integration including instrumentation overhead. No component or libc sub-cost inference.", "sourceHashes": {str(p.relative_to(output)): digest(p) for p in sorted(native.rglob("*")) if p.is_file()}}
|
||||
write_json(output / "prepared.json", prepared)
|
||||
return prepared
|
||||
|
||||
@@ -274,10 +295,11 @@ def execute(args: argparse.Namespace, prepared: dict) -> None:
|
||||
write_json(run / "parity-failure.json", mismatches)
|
||||
raise RuntimeError(f"Numerical/counter parity failed: {mismatches}")
|
||||
row = {"variant": variant, "run": label, "warmup": index < 0, "processWallSeconds": wall, "solveSeconds": result["solveSeconds"], "solveCpuSeconds": result["solveCpuSeconds"], "fullParity": True, "resultBytes": result_path.stat().st_size, "nfev": result["nfev"], "njev": result["njev"], "nlu": result["nlu"], "acceptedSteps": result["acceptedSteps"], "solverStarts": result["solverStarts"]}
|
||||
row.update({key: result[key] for key in ("jacobianMode", "jacobianRhsCalls", "jacobianColoredEvals", "jacobianFallbacks", "jacobianChecks", "jacobianMismatches", "cvodeRhsCalls", "cvodeLinearRhsCalls") if key in result})
|
||||
if variant == "profiled":
|
||||
profile = json.loads(profile_path.read_text())
|
||||
counters = profile["cvodeCounters"]
|
||||
checks = {"counterReadsSucceeded": profile["counterErrors"] == 0, "rhsCountMatches": counters["rhs"] + counters["linear_rhs"] == result["nfev"], "linearRhsEqualsJacobianCountTimesStates": counters["linear_rhs"] == result["njev"] * prepared["stateCount"], "rhsClockCountMatches": profile["scopes"]["integration"]["rhs"]["calls"] == result["nfev"]}
|
||||
checks = {"counterReadsSucceeded": profile["counterErrors"] == 0, "rhsCountMatches": counters["rhs"] + counters["linear_rhs"] + result.get("jacobianRhsCalls", 0) == result["nfev"], "defaultLinearRhsEqualsJacobianCountTimesStates": (counters["linear_rhs"] == result["njev"] * prepared["stateCount"] if result.get("jacobianMode") == "dense-difference" and not result.get("jacobianRhsCalls", 0) else None), "rhsClockCountMatches": profile["scopes"]["integration"]["rhs"]["calls"] == result["nfev"]}
|
||||
row["profile"] = profile
|
||||
row["counterChecks"] = checks
|
||||
if not checks["counterReadsSucceeded"] or not checks["rhsClockCountMatches"] or (result["method"] == "BDF" and not checks["rhsCountMatches"]):
|
||||
@@ -288,7 +310,7 @@ def execute(args: argparse.Namespace, prepared: dict) -> None:
|
||||
print(f"{variant}/{label}: solve={row['solveSeconds']:.6f}s wall={wall:.6f}s parity=true", flush=True)
|
||||
medians = {variant: {key: statistics.median(row[key] for row in rows if row["variant"] == variant and not row["warmup"]) for key in ("processWallSeconds", "solveSeconds", "solveCpuSeconds")} for variant in ("control", "profiled")}
|
||||
overhead = {key: medians["profiled"][key] / medians["control"][key] - 1 for key in medians["control"]}
|
||||
write_json(output / "summary.json", {"prepared": prepared, "runs": rows, "medians": medians, "instrumentationOverheadFraction": overhead, "allFullParity": True, "interpretation": "CVODE linear_rhs counts finite-difference RHS calls independently of model nfev. Its multiplication by stateCount is checked, not assumed. RHS time includes all model work; no Jacobian-specific RHS time is inferred. cvode_step.exclusiveSeconds excludes nested RHS, polling and Dense ops; integration.exclusiveSeconds is outer setup/loop/cleanup work. Dense setup covers the original factorization operation; dense_solve covers the original triangular solve operation. Clock/bookkeeping costs remain in all measured totals. Production control may retain its pre-existing sparse main clocks; cachedMainDifference records this."})
|
||||
write_json(output / "summary.json", {"prepared": prepared, "runs": rows, "medians": medians, "instrumentationOverheadFraction": overhead, "allFullParity": True, "interpretation": "CVODE linear_rhs counts only its built-in finite-difference calls. Custom jacobianRhsCalls are counted separately and included in nfev reconciliation. State-count multiplication applies only to the default callback. The custom jacobian scope includes its canonical base/probe RHS work; these inclusive durations overlap RHS totals. cvode_step.exclusiveSeconds excludes nested RHS, polling and Dense ops; integration.exclusiveSeconds is outer setup/loop/cleanup work. Dense setup covers the original factorization operation; dense_solve covers the original triangular solve operation. Clock/bookkeeping costs remain in all measured totals. Production control may retain its pre-existing sparse main clocks; cachedMainDifference records this."})
|
||||
print(f"Summary: {output / 'summary.json'}", flush=True)
|
||||
|
||||
|
||||
@@ -297,10 +319,13 @@ def main() -> None:
|
||||
parser.add_argument("--cache-dir", required=True, type=Path)
|
||||
parser.add_argument("--request-stages", type=Path)
|
||||
parser.add_argument("--output-dir", required=True, type=Path)
|
||||
parser.add_argument("--verify-jacobian", action="store_true", help="Enable full-matrix diagnostic checks in both control/profiled runs; default uses production automatic Jacobian")
|
||||
parser.add_argument("--run", action="store_true", help="Build and run serial warmups/repeats; default only prepares")
|
||||
parser.add_argument("--warmups", type=int, default=1)
|
||||
parser.add_argument("--repeats", type=int, default=3)
|
||||
parser.add_argument("--process-timeout", type=float, default=360)
|
||||
if any(arg == "--jacobian" or arg.startswith("--jacobian=") for arg in sys.argv[1:]):
|
||||
parser.error("--jacobian strategy selection was removed; use the production default or --verify-jacobian. Dense requests require a frozen historical version (benchmark only: --baseline-executable).")
|
||||
args = parser.parse_args()
|
||||
if args.warmups < 0 or args.repeats < 1:
|
||||
parser.error("warmups must be nonnegative and repeats positive")
|
||||
|
||||
Reference in new issue
Block a user