mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 03:49:26 +00:00
fix(scan): a portion sizer that never extrapolates from a sample with no work in it
Measured on the plugin, first run of the adaptive sizer. A 600-visit portion answered in 184 s and the budget was correctly cut to 262. The next portion, 199 visits, answered in 73 s -- three seconds of work above the 70 s fixed start -- and 3 s over 199 visits read as 0.015 s each, so the budget grew thirteen-fold to 3,404 visits. The ceiling killed that portion. The harvest kept the 13 answers it had streamed, which is the mechanism working; the sizing was not. Two rules. A sample with less than ten seconds of work above the fixed start is not extrapolated from, because it carries no information about cost per visit. And a budget may double per portion, never more: the sizing may still be wrong, but by at most one factor of two, which a kill undoes. 1,423 tests pass, nineteen skipped; the sizer control asserts both rules. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
e3edf11cf8
commit
1aa7f175e9
@@ -922,6 +922,8 @@ def _batched(
|
||||
# a mis-estimate is bounded and a kill costs little.
|
||||
PORTION_TARGET_SECONDS = 120.0
|
||||
PORTION_FIXED_SECONDS = 70.0 # measured start-and-load on a 4 MB graph
|
||||
PORTION_MIN_INFORMATIVE_SECONDS = 10.0 # less work than this above the fixed start says nothing about cost per visit
|
||||
PORTION_MAX_GROWTH = 2.0 # a budget may double per portion, never more
|
||||
|
||||
|
||||
class _PortionSizer:
|
||||
@@ -965,9 +967,15 @@ class _PortionSizer:
|
||||
self.budget = max(1.0, self.budget / 2)
|
||||
logger.info("cpg portion of %d sink visit(s) did not answer in %.0fs; budget halved to %d", visits, seconds, int(self.budget))
|
||||
return
|
||||
per_visit = max(0.0, seconds - PORTION_FIXED_SECONDS) / visits
|
||||
if per_visit > 0:
|
||||
self.budget = max(1.0, (PORTION_TARGET_SECONDS - PORTION_FIXED_SECONDS) / per_visit)
|
||||
work = seconds - PORTION_FIXED_SECONDS
|
||||
# A portion that finished inside the fixed start carries no information about cost per visit -- 3 s
|
||||
# of work over 199 visits read as 0.015 s each and the next budget grew thirteen-fold to 3,404, which
|
||||
# the ceiling then killed. Measured on the plugin. So a sample below the floor is not extrapolated
|
||||
# from, and growth is capped at doubling per step: the sizing may be wrong, but never by more than
|
||||
# one factor of two per portion, which a kill can then undo.
|
||||
if work >= PORTION_MIN_INFORMATIVE_SECONDS:
|
||||
fitted = (PORTION_TARGET_SECONDS - PORTION_FIXED_SECONDS) / (work / visits)
|
||||
self.budget = max(1.0, min(fitted, self.budget * PORTION_MAX_GROWTH))
|
||||
logger.info("cpg portion of %d sink visit(s) answered in %.0fs; next budget %d visit(s)", visits, seconds, int(self.budget))
|
||||
|
||||
|
||||
|
||||
@@ -338,3 +338,10 @@ def test_a_portion_sizer_shrinks_to_budget_and_learns_from_what_answered() -> No
|
||||
assert sizer.budget == 300.0
|
||||
sizer.observe({"y": {}, "z": {}}, scan.PORTION_FIXED_SECONDS + 20.0, answered=True) # 0.1 s per visit
|
||||
assert 450 < sizer.budget < 550, sizer.budget
|
||||
# A portion that finished inside the fixed start says nothing: the budget must not move on it.
|
||||
before = sizer.budget
|
||||
sizer.observe({"y": {}}, scan.PORTION_FIXED_SECONDS + 1.0, answered=True)
|
||||
assert sizer.budget == before
|
||||
# And a genuinely cheap portion may double the budget, never more: 0.01 s per visit would fit 5,000.
|
||||
sizer.observe({"w": {}}, scan.PORTION_FIXED_SECONDS + 5.0 + 5.0, answered=True)
|
||||
assert sizer.budget == before * scan.PORTION_MAX_GROWTH, sizer.budget
|
||||
|
||||
Reference in New Issue
Block a user