|
| 1 | +"""Execution counters over every path under claim. |
| 2 | +
|
| 3 | +Three times now a path has looked tested and never executed: a corpus that never |
| 4 | +reached a second attention split, a chunk-size row that never chunked, and an |
| 5 | +admission check that made the eviction path unreachable. Each was caught by |
| 6 | +noticing afterwards. This makes it a mechanism. |
| 7 | +
|
| 8 | +Every relation asserts not only its outcome but that the paths it exists to |
| 9 | +exercise had nonzero counts. A relation that passes without firing its own |
| 10 | +mechanism is a failure, not a pass. |
| 11 | +
|
| 12 | +The counters are also the fuzzer's coverage substrate, so the coverage report and |
| 13 | +the vacuity guards read the same numbers. A coverage denominator derived from |
| 14 | +somewhere else than the thing being counted is how a coverage number ends up |
| 15 | +describing a denominator someone chose. |
| 16 | +
|
| 17 | +Counters are observation only. They are incremented after a decision is taken, |
| 18 | +never read by one, and nothing in a kernel config path can see them: a counter |
| 19 | +that fed back into a decision would be a batch-derived quantity by another name. |
| 20 | +""" |
| 21 | + |
| 22 | +from __future__ import annotations |
| 23 | + |
| 24 | +from collections import Counter as _Counter |
| 25 | +from dataclasses import dataclass, field |
| 26 | + |
| 27 | +# Every path under claim, with the one-line statement of what firing it means. |
| 28 | +# The fuzzer's coverage report enumerates this dict rather than a hand-written |
| 29 | +# list, so a path added here is covered by both without a second edit. |
| 30 | +PATHS: dict[str, str] = { |
| 31 | + # admission and lifecycle |
| 32 | + "admit": "a waiting request was admitted", |
| 33 | + "admit_refused": "the policy declined to admit a waiting request", |
| 34 | + "finish": "a request reached its limit or an EOS and released its blocks", |
| 35 | + # prefill and chunking |
| 36 | + "prefill_chunk": "a partial prefill chunk was computed", |
| 37 | + "prefill_complete": "a prefill finished and the request began decoding", |
| 38 | + "decode_step": "a single-token decode step was computed", |
| 39 | + "chunk_boundary_mid_block": "a chunk ended part-way through a KV block", |
| 40 | + "chunk_boundary_mid_split": "a chunk ended part-way through an attention split", |
| 41 | + "chunk_boundary_on_block": "a chunk ended exactly on a block boundary", |
| 42 | + # attention shape |
| 43 | + "attention_multi_split": "attention ran with two or more splits", |
| 44 | + "attention_single_split": "attention ran within one split", |
| 45 | + # preemption |
| 46 | + "preempt_fired": "a running request was preempted for recompute", |
| 47 | + "preempt_depth_2": "a request was preempted for the second time", |
| 48 | + "preempt_depth_3_plus": "a request was preempted a third time or beyond", |
| 49 | + "resume": "a preempted request was re-admitted and recomputed", |
| 50 | + # prefix cache |
| 51 | + "cache_hit": "a prefix hit was found and honoured", |
| 52 | + "cache_hit_refused": "a prefix hit was found and the policy declined it", |
| 53 | + "cache_miss": "a lookup found no usable prefix", |
| 54 | + "cache_insert": "whole blocks were indexed after a request finished", |
| 55 | + "cache_full_prompt_trimmed": "a whole-prompt hit gave its last block back", |
| 56 | + # allocator |
| 57 | + "eviction_taken": "a cached block was reclaimed under pressure", |
| 58 | + "eviction_pass": "one pass of the reserve-with-eviction loop", |
| 59 | + "cow_performed": "a shared block was copied on write", |
| 60 | + "block_reclaimed_at_zero": "a block's last reference went away and it was freed", |
| 61 | + "out_of_blocks": "the pool could not satisfy a reservation", |
| 62 | +} |
| 63 | + |
| 64 | + |
| 65 | +@dataclass |
| 66 | +class Counters: |
| 67 | + """Per-run execution counts. Created by the Scheduler, shared with the pool. |
| 68 | +
|
| 69 | + Not a module global: a global would make one test's counts visible to the |
| 70 | + next and would not survive the cross-process replay, where two interpreters |
| 71 | + must produce identical trajectories. |
| 72 | + """ |
| 73 | + |
| 74 | + counts: _Counter = field(default_factory=_Counter) |
| 75 | + |
| 76 | + def hit(self, path: str, amount: int = 1) -> None: |
| 77 | + if path not in PATHS: |
| 78 | + raise KeyError( |
| 79 | + f"{path!r} is not a declared path. Add it to PATHS with a " |
| 80 | + "one-line statement of what firing it means, so the coverage " |
| 81 | + "report enumerates it too." |
| 82 | + ) |
| 83 | + self.counts[path] += amount |
| 84 | + |
| 85 | + def __getitem__(self, path: str) -> int: |
| 86 | + return self.counts.get(path, 0) |
| 87 | + |
| 88 | + def fired(self, *paths: str) -> bool: |
| 89 | + return all(self.counts.get(path, 0) > 0 for path in paths) |
| 90 | + |
| 91 | + def missing(self, *paths: str) -> list[str]: |
| 92 | + """Which of `paths` never fired. What a vacuity guard reports.""" |
| 93 | + return [path for path in paths if self.counts.get(path, 0) == 0] |
| 94 | + |
| 95 | + def merge(self, other: "Counters") -> None: |
| 96 | + self.counts.update(other.counts) |
| 97 | + |
| 98 | + def as_dict(self) -> dict[str, int]: |
| 99 | + return {path: self.counts.get(path, 0) for path in PATHS if self.counts.get(path, 0)} |
| 100 | + |
| 101 | + def summary(self) -> str: |
| 102 | + live = self.as_dict() |
| 103 | + if not live: |
| 104 | + return "no paths fired" |
| 105 | + width = max(len(path) for path in live) |
| 106 | + return "\n".join(f" {path:<{width}} {count}" for path, count in sorted(live.items())) |
| 107 | + |
| 108 | + |
| 109 | +class VacuousRun(AssertionError): |
| 110 | + """A relation passed without firing the paths it exists to exercise.""" |
| 111 | + |
| 112 | + |
| 113 | +def require_fired(counters: Counters, *paths: str, what: str = "this relation") -> None: |
| 114 | + """Assert the mechanism actually ran. The generalization of the hand checks. |
| 115 | +
|
| 116 | + MR3 reported "7 of 10 actually preempted" and MR4 reported "12 of 12 |
| 117 | + registering real hits" by hand. This is that, for every relation. |
| 118 | + """ |
| 119 | + missing = counters.missing(*paths) |
| 120 | + if missing: |
| 121 | + raise VacuousRun( |
| 122 | + f"{what} passed without firing: {', '.join(missing)}. " |
| 123 | + "A relation that never executes its own mechanism is not evidence. " |
| 124 | + f"Paths that did fire: {sorted(counters.as_dict())}" |
| 125 | + ) |
0 commit comments