feat(graph): 🔍 separate the graph's flat-world slot cost from its traffic - #253
feat(graph): 🔍 separate the graph's flat-world slot cost from its traffic#253diagonal-hamiltonian wants to merge 2 commits into
Conversation
|
Docs preview: https://pr-253.monoprop-docs.pages.dev |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #253 +/- ##
=======================================
Coverage 97.70% 97.70%
=======================================
Files 14 14
Lines 742 742
Branches 98 98
=======================================
Hits 725 725
Misses 12 12
Partials 5 5
Flags with carried forward coverage won't be shown. Click here to find out more. |
f5d7702 to
82fb11b
Compare
…ffic `graph_memory_breakdown()` reported one `cross_rank_bytes` total, which mixes two costs that scale with different things: one record per slot of the FLAT world (ranks x partitions), and the endpoints actually crossing. On a partitioned run the first grows with a P the rank count never shows, so a breakdown that adds them cannot say which one a graph is spending on. Five diagnostics now split them, deliberately OUTSIDE `total_bytes()` because each is a count or a slice of a field already summed there: `slot_record_bytes`, `layer_cores`, `slot_records`, `occupied_slots` and `cross_rank_endpoints`. `slot_records / layer_cores` recovers P, and `occupied_slots / slot_records` is the occupancy. Three helpers in `MPGraphEncoding.cpp` derive them; `cross_rank_storage_bytes` now calls the first rather than repeating its expression, so the slice cannot drift from the total. Covered both sides of the binding: five Boost cases on the helpers and the `+=` merge, and `tests/test_memory_breakdown.py` on the dict keys, which are built inside nanobind lambdas with no C++ entry point returning the map -- so which field each key names is observable only from Python. It pins `monoprop_PARTITIONS` rather than letting P resolve to the host's core count. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
266152b to
29ab66d
Compare
|
…NOT FOR MERGE. One squashed commit on origin/main 48cadcb. A measurement tool, cherry-picked onto a branch under test and never merged. WHY THE GRAPH COUNTERS ARE IN HERE RATHER THAN IN MAIN. They were briefly a separate PR (#253) on the theory that reporting the graph's own slot cost was independently useful. It is not, yet: the memory results that actually decide the stack are kernel peak RSS from /usr/bin/time -v, measured outside the code under test, and the only consumers of these five counters are the private harness collators (sparse_collate.py, graph_world_report.py, graph_layout_collate.py, nocache_collate.py, sparse_stack_collate.py). Diagnostics whose only readers are measurement tools belong with the measurement tools. If a shipping PR ever needs them to make its own case, it should carry them. CONTENTS the instrument Profile.h, EnvConfig.h, the monoprop_ENABLE_PROFILE CMake option and its propagation to the INTERFACE target, every monoprop_PROF* annotation, comm_profile_tests.cpp, env_config_tests.cpp, the CI matrix cell, CpuTopology.{h,cpp} + cpu_topology_tests.cpp, PartitionGroup.h, AGENTS.md, parallelism.mdx, building.mdx, justfile, bindings.cpp.in graph counters 5 fields on GraphMemoryBreakdown, OUTSIDE total_bytes(): slot_record_bytes, layer_cores, slot_records, occupied_slots, cross_rank_endpoints, plus their += merge, the three cross_rank_* helpers, the binder keys, and both test suites (graph_encoding_tests.cpp, tests/test_memory_breakdown.py) PROVENANCE. This tree is byte-identical to perf/profile-gate f408176 and to the two-commit form 29ab66d + aa77ed5, all three at tree 321e870. HOW TO USE IT. Cherry-pick this, then the PR under test -- the PR goes LAST, because hunks authored against an instrumented tree will not apply otherwise. Build ENABLE_PROFILE=ON, and use ONE build tree and ONE venv per value of the define: [tool.uv] cache-keys covers neither SKBUILD_* nor config-settings, so flipping it inside a tree that already has a venv serves the other configuration's wheel. Arm identity is the installed _core.so md5 and nothing else. Pass TAG WITHOUT a -prof suffix to ctest-worktree.sh; PROFILE=ON appends it. Set monoprop_PROFILE to arm the timers; unset, an ON build differs from a default build in codegen only. Output goes to stderr from static destructors, so pytest needs -s. ONE OBLIGATION FOR A CHERRY-PICKED PR. tests/test_memory_breakdown.py asserts the bound breakdown key sets EXACTLY. A PR that adds a key must classify it there -- byte field if it is inside total_bytes(), d_-prefixed diagnostic if not -- or all four MPI layouts fail. That is the test working, not the test being brittle. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
🤖 AI text below 🤖 Closing this — the five counters have moved to the profiling-tool branch rather than to What actually decides the memory results is kernel peak RSS from Where the work went: branch If a PR later needs these fields to make its own case, it should carry them. One that already would: Two review threads this closes out:
For the record, the diff was green throughout: |



🤖 AI text below 🤖
Summary
graph_memory_breakdown()reported the graph's cross-rank storage as one number,cross_rank_bytes, which mixes two costs that scale with different things:ranks × partitions), whether or not that slot carriesany traffic, and
On a partitioned run the first grows with a
Pthe rank count never shows, so a breakdown that addsthem cannot say which of the two a graph is spending on. This splits them.
What changed
Five fields on the existing
GraphMemoryBreakdown, deliberately outsidetotal_bytes(),because each is a count or a slice of a field already summed there:
slot_record_bytescross_rank_byteslayer_coresLayerCores;slot_records / layer_coresrecoversPslot_recordsPper coreoccupied_slots/ slot_recordsis the occupancycross_rank_endpointsP-independent ceiling onoccupied_slotsThree helpers in
MPGraphEncoding.cppderive them.cross_rank_storage_bytesnow callscross_rank_slot_record_bytesrather than repeating its expression, so the slice cannot drift fromthe total it is a slice of.
Surfaced to Python as five
d_-prefixed keys ongraph_memory_breakdown(), matching thed_convention
operator_memory_breakdown()already uses for diagnostics excluded from the sum.Tests
Both sides of the binding, because they fail differently.
cpp/tests/graph_encoding_tests.cpp— five cases.occupied_slotsagainst zeros at thefront, the interior and the back (the three places a scan loses count);
slot_record_bytesagainst a pair of storages with identical traffic and different world sizes, which is the
whole point of the field;
total_bytes()unchanged by any diagnostic; and+=summing all five,since a partitioned propagator merges per-partition breakdowns.
tests/test_memory_breakdown.py— the dicts are built inside nanobind lambdas with no C++entry point returning the map, so which struct field each string key names is observable only
from Python. It also pins
monoprop_PARTITIONS; left alone,Presolves to the host's physicalcore count and every slot-record expectation becomes machine-dependent.
d_slot_record_bytes / d_slot_recordsis asserted to be exactly 32 or 40 bytes —CrossRankPartnerRangeis twosize_tand threeTermIndex, the latter 4 B or 8 B undermonoprop_WIDE_TERM_INDEX, and nothing in between.Scope
This PR was much larger and is now much smaller. It previously also carried the
monoprop_PROFILEinstrument. That instrument is held back: whether to ship an internal profiler atall is a separate decision from whether the graph can report its own slot cost, and the two were
only ever in one PR because they were written in one sitting. The instrument is kept as a
cherry-pickable commit for measurement work and is not part of this diff. Consequences:
grep -r 'monoprop_PROF\|profile::'over this diff returns nothing.fprintfcomment no longer applies to this PR — that code is not here. It wassix call sites, not eight (two of the grep hits were the word inside a comment), and they
became one
noexcepthelper overstd::formaton the branch that still has them.PR's result. For the record it resolved 0 of 24 timing tests against
48cadcb, with one memorycell at 1.01x and 15 flat — but that was the instrument plus this, not this.
Reviewers who remember closed #232
pr/layer-profile: that was the instrument's ancestor, not this.Verification
ctest -L unit220/220,-L serial219/219,-L mpi, and the Python MPI suite at 1x1, 1x16, 2x8and 8x16 — 600 passed at every layout, all suites green. Extension
3abe91dcace4eef73c43526d04e95a6e.clang-format --output-replacements-xmlreports 0 replacements on every changed C++ file.