Retrospective: M7 (the sssp operators engine and mosp)#
Status: complete on the branch m7-mosp (from main at 8842acf, the merge of pull
request #4 with M5; main at 0.1.0rc1 merged in by the review and fix step), reviewed and fixed
(“Review and fix” below; ADR 0029 awaits the author). M7 is 0.2 work: it merges into main only after the 0.1.0
release. Steps 1-4 each have a section below; the milestone’s acceptance table, the verification
from a fresh clone, the re-estimate and the open items close the record.
Step 1: the sssp operators engine (sssp-operators)#
What was built#
Commit |
What |
|---|---|
|
the device building blocks shared by the two CUDA engines (packed words, stamp claims, the warp-aggregated append, the warp reductions, |
|
the CUDA operators engine ( |
|
both engines in every CUDA sssp suite, a larger engine-equality test, the operators engine’s input checks and budget; the kit’s C4 over presets, sizes, mixes and three batches |
|
|
|
ADR 0026 (accepted under delegation), the algorithm page, the backend how-to, the framework and conformance guides, the CHANGELOG |
(this record) |
|
Results#
Conformance check C4 runs for
ssspon CUDA (it was skipped everywhere in 0.1) and passes for the three graph types; the kit’s C4 now compares aftercompute()and three batches for every preset, the small and medium sizes and every batch mix.The golden corpus: 495/495 byte-identical with the operators engine, with 32- and 64-bit edge offsets (
parity/results/M7.mdsection 1).The randomized suites in the CUDA executable compare the operators engine with the fused engine and with the host backends on every chain (trees,
invalidated,affected,packed_parents); the MOSP fixtures, the packing boundary, the distance-only fallback, the input checks (an out-of-range distance, a parent cycle), the steady-state allocations and the strict budget run on both engines.Speed against the fused engine (
parity/results/M7.mdsection 2; informational, no gate): 1.03-1.18x on the 50K batches of the four gate graphs, 1.10-1.62x on the local 10K batches, whose many short near-far rounds each cost a host round trip of about 8-9 us.
Deviations and decisions (with reasons)#
MOSP_ESCHER’s structure, MOSP-CUDA’s semantics. PLAN 6.4.2 names MP1 as the structure to follow. MP1 is an earlier state of the algorithm and differs from MOSP-CUDA@e220ee2 in details that change outputs or error reports (no parent-cycle detection, candidates above the bound packed, no distance-only fallback, no
affected). The engine keeps MP1’s shape and takes every detail from the fused kernel, so the bytes are the same (ADR 0026 items 1 and 3).Fewer host round trips than MP1. MP1 synchronizes after Step 1, after the pull, twice per threshold raise and once per push iteration. Here the pointer-jumping rounds end on the device (a round after a converged one returns at once), the frontier’s length and the threshold stay on the device, so an update synchronizes 3 + iterations + epochs times. That is the engine’s I9 budget, stated after the phase.
Grid-stride kernels with at most as many blocks as the device holds at once (MP1 launches one thread per item). The pointer-jumping rounds that have nothing left to do then cost one empty launch of a few hundred blocks instead of n / 256 blocks.
Rule of two. Nothing enters
cpp/src/operators:cycle_count’s CUDA code shares none of these operators (it uses work counters and CUB scans), and the planned second user ofinvalidate_subtreeand the pull ishyper_sssp(0.3). The shared device blocks of the two sssp engines live in the sssp folder (kernels.cuh). PLAN 4.5.3’sschedule,sync_ruleandtie_breakpolicies were not needed (one schedule; the tie rule is the packed minimum).CHANGELOG placement. The M7 entries are a block at the end of
[Unreleased](before[0.0.1]), so the release-prep pull request onmain, which rewrites the head of[Unreleased], does not touch the same lines; after 0.1.0 the block stays under[Unreleased].
Open items#
The operators engine’s speed on updates with many short near-far rounds (1.4-1.6x the fused engine on the local batches). It is the fallback, not the default, and has no gate; if it ever matters, several rounds per synchronization (or a CUDA graph of the rounds) would remove most of the round trips. The risk
docs/developer/plan.mdlists for 0.2.0 (“the operators engine within 1.05x of the fused one”) materialised on the local batches (1.4-1.6x); no gate applies (criterion 1 makes the comparison informational).
Notes for the next steps#
mospcomposes Kssspproblems: the engine choice issssp::options::cuda_engineof each objective (the sketch’smosp::options::enginepasses it through). The operators engine shares the pooledsssp_cuda_workspace(its control block is sized for either engine).dyng-compat-mospalready sums whichever engine’s stages ran for the per-objective SOSP region, so amospdriver can reuse that rule.
Step 2: mosp (mosp-core)#
What was built#
Commit |
What |
|---|---|
|
sssp: |
|
|
|
|
|
|
|
ADR 0027 (accepted under delegation), the CHANGELOG |
|
|
(this record) |
the compat tools need mosp in a subset build ( |
Results#
The golden corpus with the whole MOSP update (
--mosp): 495/495 byte-identical on sequential, OpenMP with 1, 4 and 16 threads, and on cuda with the fused and the operators engine with 32- and 64-bit edge offsets, for the K updated trees, the updated CSR, theinvalidatedcounters and, new,combined/{distancesCsr,SSSPTreeCsr,mospCosts}.txt(K from 1 to 5 and 32 in the corpus). RecordsM7-mosp-goldens-host.json,M7-mosp-goldens-cuda.json.Preference vectors: the 8 committed cases (the thesis example with Pref {4, 1, 4} and {4, 4, 1}, K = 2..4 with preferences,
-k 2on a 4-column graph) byte-identical on every backend; both originals agreed on them first. The thesis’ costs (15, 3, 20) and (15, 24, 7) are asserted by the hand tests on every backend. At scale: roadNet-CA (K = 3, Pref {4, 1, 4}, the 50K and the local 10K batches) byte-identical to MOSP-OpenMP’smospon the OpenMP backend, and to MOSP-CUDA’s on cuda (section 3).The conformance kit: C1-C12 for
mospon every backend and graph type (C4 compares the fused and the operators engine through the K updates and the combined solve; C8’s budget on cuda is K + 3 synchronizations; C10 pairs mosp with sssp and cycle_count); sssp’s and cycle_count’s suites still pass with mosp as a C10 partner on 3-column graphs.ctest -L cpu(dev): 694/694;ctest -L gpu(dev-cuda, GPU 1): 321/321; compute-sanitizer memcheck, synccheck and racecheck on the mosp CUDA suites: 0 errors.Portability: the Clang 18 syntax pass (171 files, 0 failures) and the fortified cpu-only Release build are clean;
ci/docs.shpasses (670 declarations in the API baseline);ci/scaffold_check.shpasses (the kit’s changes keep the scaffold green).
Deviations and decisions (with reasons)#
mosp_problem is a participant, not a framework problem. PLAN 4.5.2 calls mosp “plain C++ composition”; the framework has no composite problem type and mosp is its first user (rule of two).
detail::mosp_problemimplementsupdate_participantand owns Kproblem_participant<sssp_problem>: one commit for the K objectives, each objective through its own update enactor (stages nested inmosp.objective), then the finalize step. It composes with any result indyng::update()(C10).The combined solve on a view. The combined CSR is pooled scratch, not a
dyng::graph(sketch question 3), so sssp gotsssp_solve_view(): the static enactor with the backend’s engine on a raw view, into caller-owned arrays. The sequential engine’s distance-only parent recovery reads in-edges (the OpenMP and CUDA engines read out-edges there), so the sequential combine builds the combined graph’s in-edge CSR when the packing does not fit; tested with L = 2^20 on 2^21 vertices.API additions to the sketch (ADR 0027):
options::num_objectives(MOSP’s-k);deltaapplies to the K updates only (the originals solve the combined graph with its own automatic width); the path costs cover the K objectives and are host memory on every backend (the compat driver writes the original’s every-column file with-k);affectedcounts the vertices whose combined distance or MOSP parent changed (a second pair of combined arrays, swapped);max_objectivesis 64 (the originals stop at 32: one byte per insertion and objective in the commit’s classification).sssp accepts batches without insertions of any weight count (as
graph::apply()): the kit’s empty batches on 3-column graphs and mosp’s objectives need it. A behaviour relaxation of a frozen header’s function, recorded in ADR 0027 and the CHANGELOG.The kit’s weight columns.
test_traits::num_weights(default 1) gives the kit’s graphs K columns; columns 1..K-1 are a fixed function of the edge and column 0, so the kit’s generators, inverses (C5) and re-weightings stay unchanged; C7 checks K + 1 columns, C10 uses the larger count of the pair.The compat driver’s default stays SOSP-only. The sssp gates’ timed regions (
parity/timed_regions/sssp.toml,perf_ab.py) measure the SOSP part and subtract the original’s combined step;--mospis the clone of the wholemospdriver. With--no-outputthe clone does not compute the path costs, asmospcomputes them only to writemospCosts.txt(the gate protocol runs--no-outputon both sides).Not done in this step (the next steps of M7): the Python binding
dyng.mosp, the CLI (dyng mosp compute|update), the performance gates and the timed-region map of mosp. Until the binding exists,python/tests/test_api_surface.py::test_uniform_contract[mosp]fails (the registry lists mosp), soci/python.shis red between the steps.
Open items#
The path costs are a sequential host pass (MOSP’s
mospPathCosts): about 0.5-1.4 s on roadNet-CA under the machine’s load at the time (random accesses over 2M vertices). The originals compute them only while writingmospCosts.txt, concurrently with the other files; in dynG they are part ofupdate()unlesscompute_path_costsis false. A parallel or device version (sketch question 1, “device later”) would matter for end-to-end runs that write the costs.The timings seen while checking parity at scale were taken under the shared lock on a loaded machine (another user’s 26-core job): not measurements. The gate step measures under the exclusive lock.
Notes for the next steps#
Python:
dyng.mosp.compute(g, source=0, preferences=[...]),Result.path_costsas an (n, K) host array,Result.from_arraysover lists of arrays (sketch, “Python”); the CLI’sdyng mosp updatecan reuseio::write_path_costs()and thecombinedGraph/layout of the compat driver.Gates: “(a) compute” = the K
mosp.objectiveregions (sssp’s engine stages inside) +mosp.combine+mosp.combined_sssp+mosp.finalizeagainst the originals’gpu_compute_ms/compute_ms(dyng-compat-mosp --mospprints both in itsRESULT compute_ms=line; the original’s combined step is itscombined_graph_gpu/combined_graph_computestage); “(b) end to end” with--no-outputon both sides.
Step 3: parity and performance of mosp (mosp-parity-perf)#
What was built#
Commit |
What |
|---|---|
|
the path costs on the handle’s host threads on openmp and cuda (a level-synchronous traversal of the MOSP tree; the sequential backend keeps the port); the tree’s download timed in |
|
|
|
|
|
the paper-scale goldens |
|
|
|
|
|
|
|
the combined solve counts |
|
the records: paper-scale parity, the K sweep, the mosp gates on both backends, the device memory before and after the fix, the sssp gate re-check; |
Results#
Parity at paper scale: the 20
mosp_scalecases (the four gate graphs x three batches, Pref {4, 1, 4} on roadNet-CA, the K sweep) byte-identical (SHA-256 of every output file, theinvalidatedcounters) on sequential, OpenMP (28 threads) and cuda with both engines; the golden corpus 495/495 on eight configurations on the final code (M7.md section 4).The K sweep: roadNet-CA widened to 4 objectives (
mospPrep widen), K = 1..4 and two preference vectors, byte-identical on every backend; timings reported (M7.md section 5; cuda K = 2..4, OpenMP K = 4 only).The mosp gates (M7.md section 6): “(a) compute” 0.992-1.005 on cuda and 0.669-0.924 on OpenMP (gate 1.05), “(b) end to end” 0.704-0.907 and 0.754-0.919 (gate 1.10), on roadNet-PA, roadNet-CA, rgg_n_2_20_s0 and road_usa with the three bench batches each. All met.
Device memory (PLAN 8.6, <= 1.05x): missed at first (1.068-1.075x on the road graphs), fixed (
5e20e0f): 1.015-1.019x, rgg 0.931x (M7.md section 8).The sssp gates re-checked on roadNet-CA and road_usa, both backends: all met (M7.md section 9).
The combined step (“watch”): 0.55-0.72x on OpenMP, 1.00-1.04x on cuda with kernels that are not slower than MOSP-CUDA’s (M7.md section 7).
Tests on the final code:
ctest -L gpu(dev-cuda, GPU 1) 322/322,ctest -L cpu(dev) 702/702,pytest parity/tests49 passed; compute-sanitizer memcheck, racecheck and synccheck on the mosp CUDA cases that run the in-place count (the thesis example, the stats, vertex growth, the distance-only mode with both engines, clone) and memcheck on the sssp engine tests: 0 errors; the Clang 18 syntax pass (172 files, 0 failures) and the fortified cpu-only build are clean; pre-commit andci/docs.shpass (the API snapshot unchanged, 670 declarations).
Deviations and decisions (with reasons)#
“(b) end to end” writes every output file on both sides, not
--no-outputas step 2’s notes proposed. The published end-to-end figures (MOSP-CUDA’sresults/README.md: “from reading the text inputs … to writing all outputs”) include the writes, and the originals compute the path costs only while writingmospCosts.txt; with--no-outputneither side would compute them, and dynG’smosp::update()cost of them would go unmeasured.perf_ab.py mosp --no-outputremains as an option.The device-memory gate was measured and fixed in this step. Milestone criterion 4 lists the timing gates; PLAN 8.6’s table also gates device memory (<= 1.05x), and M3 measured it for sssp. mosp missed it by keeping a second MOSP tree on the device for
affected. The fix changes the fused kernel’s unpack condition (a parameter instead of!from_scratch; the update path is unchanged in behaviour and registers), so the cuda corpus, the paper-scale goldens, the cuda mosp gates and the cuda sssp gate re-check were all run again on the final code; the earlier cuda records are kept under-before-names. The host backends keep their second pair of arrays: host memory has no gate, and their engines write the output arrays during the search, so the counted unpack would not apply without restructuring them.The paper-scale goldens store hashes, as PLAN 8.3 prescribes for paper-scale cases (the command lines and the SHA-256 of each output file), plus the SHA-256 of every input file (the inputs come from
perf_ab.py prepare, notdyng prep, which does not exist yet) and theinvalidatedcounters; the export was cross-checked against MOSP-CUDA.The K sweep is parity plus reported timings. PLAN 6.4.4 lists it under parity; its timing has no gate. With
-kbelow the graph’s columns, both sides write MOSP’s every-columnmospCosts.txt, so the “(b)” of K = 2 and 3 is not the library’s figure.The lock of the harness.
perf_ab.pypolled the perf lock withLOCK_NBonce a second and waited for more than half an hour behind another agent’s back-to-back shared holders; it now blocks inflock(2)after the first try (with the timeout as an interval timer), and the step’s runs were wrapped in a blockingflock(1)(perf_ab.py runs under its ancestor’s hold). No measurement rule changed.The combined step on cuda is 1.00-1.04x and not optimized further. It is a reported region inside the gated “(a)” (0.996-1.005x); Nsight Systems shows dynG’s combine and solve kernels at or below MOSP-CUDA’s times, so the remaining difference is host time around the same kernels at the locked clocks, not the MP2-style atomic regression PLAN 6.4.4 warns about.
Open items#
The ci scripts were not re-run from a fresh clone in this step (
ci/check.sh --parity,ci/gpu_local.sh,ci/docs.sh,ci/python.sh: the last step’s verification).ci/python.shstays red until the Python bindingdyng.mospexists (step 2, open item 1).ci/gpu_local.sh’s parity step replays--configs cudaonly.The OpenMP K sweep timings of K = 2 and 3 were not taken (informational; M7.md section 5).
The combined step’s 0-4 % on cuda (section 6 above, M7.md section 7) could be looked at with a locked-clock Nsight Systems run if it ever matters; it does not reach any gate.
From step 1: the operators engine’s speed on many short near-far rounds (the risk listed for 0.2.0 materialised on the local batches, 1.4-1.6x; no gate applies).
Notes for the next steps#
The gate commands:
parity/perf_ab.py mosp --backend {cuda,openmp} --graph <g> [--gpu 0] --runs 21(road_usa--runs 11),parity/perf_ab.py memory --mosp --gpu 0 --graph <g>,parity/compare.py mosp_scale --exe <dyng-compat-mosp> --configs ...; on a shared machine wrap each inflock $DYNG_SCRATCH/perf.lock(blocking).The Python binding can expose
mosp::stats::affectedwith the same meaning on every backend (the cuda count now comes from the solve’s unpack; the values are those of the host count).
Step 4: Python, command line, documentation and close-out (python-docs-finish)#
What was built#
Commit |
What |
|---|---|
|
|
|
`dyng mosp compute |
|
|
|
|
|
ADR 0028 (accepted under delegation, approvals-log row), the mosp page (Python, command line, the performance table), the sssp page (the engine table, the engine choice from Python and the command line), the Python API page, CHANGELOG, README, history |
|
what the first fresh-clone run of |
(this record) |
|
Results#
The Python suite: 437 passed and 4 skipped (torch, cupy, pandas not installed) in the working tree, including 24 mosp tests (the thesis example with Pref {4, 1, 4} and {4, 4, 1} on both host backends; the Hypothesis property with 60 examples: random graphs with 1-4 objectives, random preferences and batches with vertex growth, against a pure-Python oracle) and 13 mosp CLI and example tests.
dyng mosp updateis byte-identical to the committed files of both originals on the 8 preference cases, todyng-compat-mosp --mospon all 42 committed cases (34 SOSP cases, 8 preference cases; every output file,obj<k>/andcombinedGraph/), and to MOSP-OpenMP’s ownmospbinary ($DYNG_SCRATCH/ref/MOSP-OpenMP@c352151/unpatched/bin/mosp) on the 8 preference cases.scripts/regen.py --stubs --check, mypy (strict, 31 files) and griffe (no breaking change of the Python API againstmain) pass; the C++ API baseline is unchanged (670 declarations).
Deviations and decisions (with reasons)#
Per-objective accessors are methods (
Result.distances(k)), not list properties: the C++ API’s shape, and a list would read K arrays on every access (ADR 0028, alternatives).dyng.Arrayis no longer always 1-D. PLAN 5.5 sketchespath_costs.to_numpy()with shape (n, K); the C++ array is vertex-major, so the 2-D view is zero-copy.ndim,shapeandlen()follow the view (unchanged for the 1-D arrays); griffe sees no signature change.No
--pref/-kaliases ondyng mosp: PLAN 5.6 maps flags one to one to option fields; the original spellings stay indyng-compat-mosp --mosp. List-valued fields became a new kind of generated flag (comma-separated, MOSP’s--prefsyntax).Every column’s costs with
--num-objectivesbelow the columns, as the original writesmospCosts.txt; the extra columns come fromdyng.testing.mosp_path_costs, as the compat driver uses the C++ reference. The library’s ownpath_costskeep covering the K objectives (ADR 0027).dyng mosp computewrites the combined files of the static solve next tomospPrep init’s trees (no original command writes them for a static solve; the formats are the driver’s).Examples added (PLAN 9.6: one per algorithm) although the step did not list them.
ci/gpu_local.shchanged (both engines in the parity step, synccheck on mosp): open items of steps 1-3, needed for the milestone’s C4-on-the-corpus criterion.README and
docs/developer/plan.md: shared with the release-prep pull request onmain. Only the status rows of M7 changed (one row of the README’s status table split into the M7 row and the remaining 0.2 row; the M7 row of the plan’s milestone table); the orchestrator should check these two rows when it merges after 0.1.0.
Notes#
The first fresh-clone run of
ci/check.sh --parityfailed intidy,harness,scaffoldanddocs(the three defects above;scaffoldanddocsboth build the Sphinx site). Steps 2 and 3 had run the pieces (ci/docs.shbefore the Python module existed, the Clang syntax pass, the unit tests) but notci/check.sh’s clang-tidy step; the naming rule only shows there. The second run, on32e132f, passed every step.
Milestone acceptance#
# |
Criterion |
Evidence |
Status |
|---|---|---|---|
1 |
sssp |
step 1 (ADR 0026: rule of two, nothing in |
met |
2 |
|
update`, docs page |
steps 2-3 (ADR 0027); step 4 (ADR 0028; |
3 |
Byte parity with MOSP-CUDA e220ee2 and MOSP-OpenMP c352151 ( |
steps 2-3 ( |
met |
4 |
Gates (PLAN 8.6): “(a) compute” <= 1.05x, “(b) end to end” <= 1.10x on the four graphs and both backends; the sssp gates re-checked |
step 3 ( |
met |
5 |
A fresh clone passes |
“Verification” below; the C++ baseline gained |
met |
Verification (fresh clone of m7-mosp at 32e132f)#
All runs in $DYNG_SCRATCH/m7-verify/clone (a clone of the local repository, git checkout m7-mosp), with the shared perf lock for every heavy step:
Check |
Result |
|---|---|
|
all checks passed in 22 min: clang-format; |
|
all steps passed in 50 min (RTX A5000, driver 590.48.01, CUDA 13.1): the build; |
|
inside |
Portability pre-checks |
the Clang 18 syntax pass over a |
Re-estimate of the roadmap#
Measured. M7 was estimated within the 0.2 row of docs/developer/plan.md (M7-M9: 1.5-2
weeks focused). It took about one long day of wall-clock time for its four steps (2026-09-30
afternoon to 2026-10-01 afternoon by the commit times), of which roughly a third was measurement:
the corpus on eight configurations, the paper-scale goldens, the gates on two backends and four
graphs at locked clocks, the device memory, and the re-measurement after the device-memory fix,
with long waits for the exclusive perf lock behind other agents’ campaigns. The independent review
of M7 is still to come; earlier reviews found real defects every time (about half a day of fixes,
more when they reopen measurements).
Milestone |
Plan (working weeks) |
After M3 (focused) |
Now: focused effort, incl. review and fix |
Calendar incl. author gates |
Main risk |
|---|---|---|---|---|---|
M7 sssp operators engine + mosp |
(in 0.2’s 8-12) |
(in 0.2’s 1.5-2 weeks) |
done (about 1 day), plus its review and fix (0.5-1 day) |
the pull request after 0.1.0 |
the review reopening gate measurements |
M8 ESCHER store + hypergraph |
(in 0.2) |
3-5 days |
1-2 weeks |
the staged CBST merge (both originals’ traces after every step); the first hypergraph container and its headers |
|
M9 |
(in 0.2) |
2-4 days |
1-2 weeks |
the paper suites’ sizes; memory <= 1.05x; content-addressed deletions until M8’s keys |
|
0.2.0 |
8-12 |
1.5-2 weeks |
about 1.5-2 weeks from now (unchanged) |
4-6 weeks |
the release after 0.1.0’s; consent records are not needed (Appendix E) |
The 0.2 risk “the operators engine within 1.05x of the fused one” (docs/developer/plan.md,
section 4) is not met on the local batches (1.4-1.6x) and was never a gate (milestone criterion 1
makes the comparison informational); it moves to the open items as a possible optimization.
Review and fix#
The independent review of M7 (at d2e0bdb) confirmed 16 findings, two of them (7 and 12) the
same defect seen from two sides. Each is fixed or recorded below, in small commits on m7-mosp.
# |
Finding (severity) |
What was done |
Commit |
|---|---|---|---|
1 |
The host backends and cuda follow different originals’ packing rules, so non-canonical imported trees inside the window (n - 1) * W <= max_distance < n * W give different trees, against the docs’ cross-backend claim (high) |
a parity rule, not delegated: ADR 0029 proposed (option A, each backend as its original, recommended; option B, one rule everywhere), GOVERNANCE.md open decisions; code unchanged; the docs state the exception; |
|
2 |
mosp.md: “every tree is canonical” (low) |
reworded: the MOSP tree always, the K trees after |
|
3 |
The gate write-up drops PLAN 8.6’s noisy flags (medium) |
M7.md section 6: the spreads of both sides and the flags in the table, a paragraph on the flagged rows (all 24 end-to-end, 7 of 12 OpenMP compute); mosp.md section 5 |
|
4 |
OpenMP combined step: the original allocates its workspace inside the timed stage, dynG’s pool was warm (low) |
stated in M7.md section 7 with an estimate of the first-touch cost; the cold-workspace reading is an open item (below) |
|
5 |
mosp.toml: mosp.finalize described as a kernel and a sync that 5e20e0f removed (low) |
both notes corrected (cuda: reads the solve’s count; host: count_changed() and the swap) |
|
6 |
“The plan’s 0.2 estimate” is the plan’s listed 0.2.0 risk (low) |
reworded in this retrospective and in M7.md section 2; the confirmation request dropped; the plan’s risk cell gains the outcome |
|
7, 12 |
A CUDA mosp update that adds vertices goes one sync over its I9 budget (the release of the pinned tree copy; strict budgets throw and poison) (medium) |
the reserving run’s synchronizations are measured and counted; the mosp executables (hand, randomized, conformance) run every test with strict budgets; |
|
8 |
sssp.hpp’s |
both |
|
9 |
PLAN 11.3’s M7 deliverable |
moved to M8, recorded (deviation 3 below; plan.md M7/M8 rows, python_gaps.md) |
|
10 |
framework.md’s translate row and mosp’s template card describe a hook mosp does not have (low) |
the translate row (mosp needed none; hyper_sssp first), a row for mosp’s hand-written participant (no composite problem type), the card |
|
11 |
mosp.hpp’s |
“not one preference per objective (or none)”; only insertions with another weight count (also the Python docstring) |
|
13 |
The combined solve’s budget on the operators engine is the update formula, one too many (low) |
|
|
14 |
Merging into the current |
|
|
15 |
C8 hard-codes the fused budget: fails without cooperative launch, never checks the operators engine (medium) |
C8 runs with |
|
16 |
|
test_traits.hpp and conformance.md list it (and the new budget hook) |
|
Deviations and decisions (with reasons)#
The packing window waits for the author (finding 1). It is a parity rule (GOVERNANCE.md: not delegated), so nothing in the code changed: ADR 0029 is Proposed with option A, the behaviour since M1b, which keeps byte parity with both originals on every input and qualifies C3 by one documented exception. The window needs at least 65537 vertices with int32 weights (or, on roadNet-CA, a largest weight of 4462121 or 4462122) and non-canonical imported trees; no golden, paper-scale or randomized case lies in it. Option B would change one line per engine and the test’s expectation.
The merge of
maincame first. Every later fix was made on the merged tree, so the docs that both sides edit carry 0.1.0’s maturity and M7’s additions together. Mergingmainbrought one more conflict-free change to fix:parity/certify.py(new in 0.1.0rc1) readnum_filesandmanifest_sha256of every golden set, whichmosp_scale(SHA-256 per case) does not have, and foundcpp/tests/data/mosp_combinedmissing fromfixtures.toml(be943d7)..dgbmoves to M8 (finding 9). PLAN 11.3 lists.dgbbinary batches for M7, while PLAN 6.2 ships them with the ESCHER store (order 7, M8), and no M7 deliverable reads them (the MOSP parity usesinsert.txt/delete.txt, the tests and examples.dgt); the format (magicDYNGBAT1, a versioned header, CRC32, the streamingbatch_readerwith pinned staging) is better designed with the hypergraph batches it must also hold. Still 0.2.Strict budgets for the mosp executables, not for every test. The defect of findings 7 and 12 printed hundreds of warnings that ctest hides. The mosp executables now arm strict budgets for every test (
support/strict_budgets_environment.hpp); the whole CPU and GPU suites were also run once withDYNG_STRICT_BUDGETS=1and pass, but making that the default for every executable is a policy change for all algorithms (the OpenMP engines’ reservations differ with the thread count of the runner), left to a later milestone.mosp’s C8 budget with the operators engine is mosp’s own (finding 15): the combined solve’s epochs are not in
mosp::stats(adding a field would change the frozen header), so the traits returnrun_dependent_budgetthere and C8 checks the bounded budget mosp declares; the hand test pins that budget exactly (used == limit in the steady state).No new gate measurement. The fixes change no output and add, in a gated region, two reads of the thread’s budget counters (zero without
DYNG_DEBUG_BUDGETS, which the parity presets do not set) and one integer expression, so the section 6 gate records stand (parity/results/M7.mdsection 12).
Results#
ctest -L gpu(dev-cuda, GPU 1): 325/325, no budget warning in the log; withDYNG_STRICT_BUDGETS=1all pass too (the review saw 28 failures there).ctest -L cpu(dev): 707/707, also withDYNG_STRICT_BUDGETS=1.The packing window against both originals (n = 65537 at both ends, the review’s n = 65538 case, a canonical control): each backend byte-identical to its own original in every file; the canonical case equal everywhere (
parity/results/M7.mdsection 11).pytest parity/tests ci/tests: 130 passed (the certificate tests include the mosp set).Portability: the Clang 18 syntax pass over the
devdatabase (173 files) and adev+DYNG_BUILD_PYTHON=ONdatabase (187 files), 0 failures; the fortified (-D_FORTIFY_SOURCE=3)cpu-onlybuild with the Python module: clean, no warnings.Fresh clone of
m7-mospat939433c($DYNG_SCRATCH/m7-verify/clone, shared perf lock):
Check |
Result |
|---|---|
|
all checks passed in 24 min: clang-format; |
|
all steps passed in 72 min: |
Open items#
The ADR 0029 decision (open item 5 below).
A cold-workspace reading of dynG’s OpenMP combined step (finding 4): needs a mode of
dyng-compat-mospthat runs the update on a fresh pool; ungated and informational, so it is left for a later measurement campaign.After the final 0.1.0 release (
release-0.1.0-final) merges intomain, mergingmainintom7-mospagain conflicts only inCHANGELOG.md(git merge-tree, at the fix’s head): keep the M7 blocks under[Unreleased]above## [0.1.0].
Open items for the lead maintainer#
The operators engine’s speed on updates with many short near-far rounds (1.4-1.6x the fused engine on the local 10K batches, 1.03-1.18x on the 50K batches): the risk listed for 0.2.0 materialised on the local batches; no gate applies. Several rounds per synchronization or a CUDA graph of the rounds would close most of the gap if it is ever wanted.
The merge after 0.1.0.
mainat 0.1.0rc1 is merged intom7-mosp(review and fix); the CHANGELOG’s M7 blocks are under[Unreleased]. Againstrelease-0.1.0-finalonlyCHANGELOG.mdconflicts (the release renames the heading right below the M7 blocks): keep the blocks under[Unreleased]above## [0.1.0].Informational gaps of step 3: the OpenMP K-sweep timings of K = 2 and 3 (parity is complete), and a locked-clock Nsight look at the CUDA combined step’s 0-4 % (inside a gated region that is met).
The Python CUDA path of
dyng.mospis tested through the C++ suites only: the CPU wheel has no CUDA backend until the plugin wheels of 0.1.x (M6); the binding is the same code for both.ADR 0029, the packing window (a parity rule; GOVERNANCE.md, open decisions): for non-canonical imported trees inside (n - 1) * W <= max_distance < n * W the host backends follow MOSP-OpenMP’s packing rule and cuda MOSP-CUDA’s, so their tie parents differ, each byte-identical to its own original. Option A (recommended, the current behaviour): keep it and document the exception to C3. Option B: one rule everywhere, giving up byte parity with one original on those inputs.