Add nfa_frontier: the ANMLZoo/VASim homogeneous-NFA simulation cycle - #20
Open
laesse10 wants to merge 2 commits into
Open
Add nfa_frontier: the ANMLZoo/VASim homogeneous-NFA simulation cycle#20laesse10 wants to merge 2 commits into
laesse10 wants to merge 2 commits into
Conversation
Ported from VASim (github.com/jackwadden/VASim, MIT), the reference engine of the ANMLZoo automata-processing suite (github.com/jackwadden/ANMLZoo, IISWC'16). Where the time goes. VASim links no CUDA at all -- an nsys probe over a whole run records zero kernels and zero device memory ops -- so this is the perf path. `perf record -g --call-graph=dwarf -F 999 -e cycles:u` on four ANMLZoo automata (Brill 10 MB / 780 s / 774K samples, Fermi 1 MB / 463 s / 459K, EntityResolution 10 MB / 489 s / 486K, Snort 10 MB / 901 s / 896K) puts 99.9% of user cycles inside `Automata::simulate(uint8_t)` on the three STE-only automata, with the same split every time: enableChildSTEs 48-68%, computeSTEMatches 27-33%, enableStartStates 1-13%. There is no library leaf to walk out of -- the hot code is VASim's own per-symbol cycle -- so that function is the boundary. The kernel keeps upstream's worklist form (a frontier of state indices plus a per-state dedup flag) and its CSR successor walk: the irregular gather/scatter and the data-dependent trip counts are the point. It drops upstream's per-edge `pair<Element*, string>` copy, which the profile shows costs 23% of the whole run inside enableChildSTEs, since that is a container choice rather than automata mathematics. Fidelity is checked against the application, not only against a checker. Fed VASim's own parse of Levenshtein, Brill, Snort and Fermi, the kernel reproduces VASim's activation histogram state for state -- 2,284,315 / 6,611,433 / 1,699,583 / 4,342,794 activations, zero mismatched states -- and its report count exactly on all but Snort, whose 708 counter/gate elements the port deliberately omits (8722 vs 8917); on that automaton VASim spends 62% of the run in the special-element step, almost all of it walking a `std::map<std::string, bool>` by value rather than evaluating gates. Two independent formulations (a dense boolean adjacency matvec beside the kernel, a set-and-dict walk in tests/test_ported_references.py) pin the recurrence without reusing its data structures. Sizing comes from the same measurements: the generated automaton lands inside ANMLZoo's measured bands for fan-out, symbol-set density, self-loops, start states and -- the number that decides the per-cycle work -- mean active set. nfa_frontier joins numerical_oracle.NO_SCALE because NS/NE/NSTART are three views of one automaton; scaling them independently asks for a graph that does not exist.
…ng sympy Two defects in the first cut of this port, both found by driving CI's own phases against the kernel locally rather than reading the red checks on the PR. The port hid the parallelism it was supposed to expose. VASim's per-symbol cycle is a strict recurrence, but the application does not thread over symbols -- it threads over the graph: `splitConnectedComponents` cuts the automaton into independent components, distributes them among `-T` threads, and each thread runs the whole stream through its own. That axis is real and wide in ANMLZoo: 24 components in Levenshtein, 1962 in Brill, 2687 in Snort, 2399 in Fermi. The first version flattened every component into one CSR and one frontier, so an optimizer saw a single sequential worklist and the map was gone. The kernel is now that map. `for c in range(C)` walks the components; `comp_ptr[c] .. comp_ptr[c+1]` gives each one a disjoint slice of the shared `enabled`, `frontier` and `matched` buffers, its own states in `activation_counts`, and its own `report_counts[c]` -- so the map needs neither privatisation nor a reduction, and the recurrence stays exactly where upstream has it, inside a component. Components are deliberately non-uniform (21/33/45 states, cycled) because ANMLZoo's are: Brill's median component is 21 states against a largest of 67, so an equal-size static schedule over `c` leaves cores idle, and that imbalance belongs in the benchmark. The stream argument was named `symbols`, and `sympy.symbols` is a function. DaCe's `scalar_to_symbol` pass -- which runs only under `simplify=True`, so a clean `simplify=False` parse probe said nothing -- sympifies loop-edge expressions in a namespace where that name resolves to the function, and the numeric gate failed with `SympifyError: cannot sympify object of type <class 'function'>`. Renamed to `stream`; of the kernel's 31 identifiers it was the only one that resolved to a sympy callable. Re-verified after both changes: VASim's activation histogram still agrees state for state on Levenshtein, Brill, Snort and Fermi (2,284,315 / 6,611,433 / 1,699,583 / 4,342,794 activations, zero mismatched states), which also demonstrates that running the components independently computes what running their union computes. The benchmark-local test now checks the worklist map against a dense boolean matvec over the WHOLE graph, so the decomposition itself is under test.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ported from VASim (github.com/jackwadden/VASim, MIT), the reference engine of the ANMLZoo automata-processing suite (github.com/jackwadden/ANMLZoo, IISWC'16).
Where the time goes. VASim links no CUDA at all -- an nsys probe over a whole run records zero kernels and zero device memory ops -- so this is the perf path.
perf record -g --call-graph=dwarf -F 999 -e cycles:uon four ANMLZoo automata (Brill 10 MB / 780 s / 774K samples, Fermi 1 MB / 463 s / 459K, EntityResolution 10 MB / 489 s / 486K, Snort 10 MB / 901 s / 896K) puts 99.9% of user cycles insideAutomata::simulate(uint8_t)on the three STE-only automata, with the same split every time: enableChildSTEs 48-68%, computeSTEMatches 27-33%, enableStartStates 1-13%. There is no library leaf to walk out of -- the hot code is VASim's own per-symbol cycle -- so that function is the boundary.The kernel keeps upstream's worklist form (a frontier of state indices plus a per-state dedup flag) and its CSR successor walk: the irregular gather/scatter and the data-dependent trip counts are the point. It drops upstream's per-edge
pair<Element*, string>copy, which the profile shows costs 23% of the whole run inside enableChildSTEs, since that is a container choice rather than automata mathematics.Fidelity is checked against the application, not only against a checker. Fed VASim's own parse of Levenshtein, Brill, Snort and Fermi, the kernel reproduces VASim's activation histogram state for state -- 2,284,315 / 6,611,433 / 1,699,583 / 4,342,794 activations, zero mismatched states -- and its report count exactly on all but Snort, whose 708 counter/gate elements the port deliberately omits (8722 vs 8917); on that automaton VASim spends 62% of the run in the special-element step, almost all of it walking a
std::map<std::string, bool>by value rather than evaluating gates. Two independent formulations (a dense boolean adjacency matvec beside the kernel, a set-and-dict walk in tests/test_ported_references.py) pin the recurrence without reusing its data structures.Sizing comes from the same measurements: the generated automaton lands inside ANMLZoo's measured bands for fan-out, symbol-set density, self-loops, start states and -- the number that decides the per-cycle work -- mean active set.
nfa_frontier joins numerical_oracle.NO_SCALE because NS/NE/NSTART are three views of one automaton; scaling them independently asks for a graph that does not exist.