Repository navigation
Conversation
Brings #603's commits after a9ea2b8 (acc1c8f, df3fa3c, 8cc8177, bf44317) onto the stack head 965e772 (#630). Conflicts: - backward_induction.py: _internal_reservations_by_cell keeps the stack's native-schedule and host-buffer bookkeeping and #603's invariant-assembly reservation; marked noqa C901 since the two together exceed the limit. - ci-workloads.json: stack side plus #603's measured weights for test_capture_padding.py and test_invariant_assembly_reservation.py; shards regenerated. - Certificate: stack pins, _build_zero_V_arr pin renamed to _build_V_arr_template as in acc1c8f, corridors re-pinned for backward_induction.py, period_replay.py and v_topology.py, seals fixed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ExecutionConfig.value_storage="host" copies each period's values to pinned host memory right after the period's kernels, so the copy runs while the next period computes. Before the next period is dispatched the solve waits for the copy started one period earlier, so the device holds at most the period being solved, the period it reads and one whose copy is finishing. Reads hand out device copies: the value store wraps a host value in a lazy entry, and simulation and the NaN breakdown read through a view that keeps two periods on the device. Values, reads and panels equal device storage bit for bit. Host storage is refused with the block-major schedule, which has its own route to the host. Policies, artifacts and dissolution flags, the planner's resident charge and the materialize refusal follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under value_storage="host" a collective regime's dissolution flags are copied to pinned host memory with the period's values and are part of the wait that bounds the device. Simulation reads them back through the device view; the public replay store holds them on the CPU device, where policies already are. The read-through view now keeps the three periods read most recently: replaying a same-period exit at period p reads p, p+1 and p+2, and dropping a period still in use broke the simulation's check that a source stays the same array. Policies and persistable artifacts keep their copy to the CPU device. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under value_storage="host" the residency walk no longer treats solved values as kept until the solve returns. A value of period q counts as read by every dispatch of periods q and q-1, so admission charges it while its peers run and while the next period reads it and its copy to the host finishes, which is when the solve frees it. Only the residency walk reads this ledger: release and donation keep the device ledger, so no value whose copy is in flight is handed to another owner. Under device storage a cell's residency refusal now names ExecutionConfig(value_storage='host'). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…by storage A host-stored value entry carries the solve's per-device budget, and the value store admits a full materialization against it: placing every value on the devices at once is refused before anything loads, naming value_storage and the remedies, when the summed bytes on a device exceed the budget. Tuning records key on value_storage, since storage decides what the planner charges. A record written before the field existed decodes as device storage, the only mode there was. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each period's copy to the host is dispatched after the period's kernels and before the next period's, which is what lets it run beside the next period. A blocked period-major solve and a solve sharded over two CPU devices publish the device-storage values and panels bit for bit; sharded values keep their sharding in host memory and are read back onto it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ExecutionConfig.value_storage takes an existing directory besides "device" and "host"; value_storage_kind names it "disk", which the tuning key records in place of the path. A directory is refused with the block-major schedule, as "host" is. StreamedSolutionArchive writes a solve's values one at a time, with their checksums computed as they are written, into <name>.h5.partial. Completing it adds the result's artifacts and manifest through the code SolutionResult.save uses and renames it to <name>.h5; abandoning it writes a record in place of the manifest and renames it to <name>.incomplete, which load_solution refuses. DiskValueEntry reads a written value back, verified, onto its sharding. The solve does not write to the directory yet: a directory behaves like "host" until the next change wires the archive in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With a directory as ExecutionConfig.value_storage, each solve writes its values to solution-<id>.h5 in it. A background thread writes each period's host copies and the solve swaps them for entries that read the value back from the file; the solve waits while two periods are still unwritten, which bounds host memory. The returned result reads its values from the archive, and its file is deleted once nothing reads them; SolutionResult.save copies it. A failed solve leaves solution-<id>.incomplete with the last written period, the dtype, the model and parameter fingerprints and the pylcm version. The structural blueprint key now takes the storage kind, not the directory: before this change a directory could not be solved at all, contrary to the previous commit's message. Model._solve_from_flat_params owns the archive and delegates to _solve_into. Certificate declarations edited by hand: the compiled_solve_owners_omitted anchor includes its preceding line, and Model._solve_into is pinned. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The per-chunk period loop becomes a generator, _subject_chunk_periods, that pauses after each period; _simulate_subject_chunk runs it to its end, so the chunk-outer path is unchanged. With values read through the host read-through view and several ungrouped, unbudgeted chunks, _simulate_windows_period_by_period runs every chunk through a period before any starts the next: the first chunk's read copies a value onto the device and later chunks reuse it, so a period's values cross over once, as with a single chunk. Each chunk's results of a period go to the host when the period is done; dissolution records keep chunk order. Budgeted runs still use the chunk-outer loop until the budget accounts for every chunk's carry. No eager prefetch: prefetching whole periods copied values no subject reads. SubjectChunkKwargs and SubjectChunkResults move from tests/conftest.py into simulate.py. Certificate declarations edited by hand: the generator and the driver are pinned wherever _simulate_subject_chunk is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d values Budgeted simulation refused every host- or disk-stored solution: the walk that charges what a solution keeps on the device did not know their entries. Host and disk entries now pin nothing, and a read-through view is charged only the device copies it keeps. Budgeted calls with values kept off the device now run their chunks period by period too. The planner admits the chunk width beside every subject's carry, which grows with the population rather than the width, and refuses up front, naming fewer subjects per call or a larger budget, when it does not fit. While a chunk runs, the waiting chunks' carries are a memory owner of their own, so every dispatch admission counts them; each chunk is admitted against its profile before its first period. Planning no longer copies stored values onto the device: footprints count a view's cached copies, the chunk profiler reads shape stand-ins the view carries, and boundaries that only pass the values along get runtime-coarse types, since a runtime check samples one entry and sampling a view copies a value. A budgeted call now makes the copies an unbudgeted one makes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under the block-major schedule, value_storage="host" solves each code with host storage, so the device holds the period window of one code, and the retention keeps the pinned copies that solve made instead of copying them to NumPy. Deleting a code's blocks frees device memory only. A combined solve and simulate retains each host-stored code at once and hands its subjects an upload. "device" keeps block-major as it was. A directory does not combine with block-major yet, and component jobs refuse any storage but "device". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With a directory as value storage, a block-major solve now writes each code's blocks into one archive per solve and keeps none in memory. Each code is solved with host storage, so the device holds one code's period window. The archive holds one full dataset per value, and each code's block is written into its slice along the blocked axis; checksums are computed at completion by reading each value back once, so the file is an ordinary saved solution that load_solution reads and SolutionResult.save copies. A failed solve leaves solution-<id>.incomplete with the codes already written. An archive never finished nor abandoned, as when a combined solve and simulate fails, is closed and kept as incomplete once nothing refers to it. The two archive kinds share one base for describing, abandoning and completing. Component jobs still take device storage only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings the 77 commits of the stack top onto the value-storage work. Conflicts: chunk_profiles keeps the projected-forward-input branch with the widened read-source type; entry_allocations keeps the runtime-coarse resolved inputs with the six-element tuple; imports and the block-major schedule keep both sides; the invariant-assembly check takes the stack's equivalent form. Without textual conflicts: the host-storage ledger call drops retain_all_artifacts, which the stack removed, and generation and validation_materializer, added to _solve_from_flat_params, now reach _solve_into, where the code reading them lives. Certificate: the stack's declarations, with Model._solve_into and the period-by-period simulation helpers registered again; corridors re-pinned for the sources this branch changes. verify.py still reports eager_stages.py missing from the seal map, as it does on the stack top itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Failed copies and writes raise ValueStorageError naming the period, regime (and code) and the target; the disk writer stops after its first failure so the incomplete record's last completed period is right. The record now carries the pylcm commit. - EGM-family simulation policies take the value path: copied to pinned host memory without blocking, written to the archive with the values under a directory (reused as the saved artifact at completion), and read through the caching device view, so all chunks of a period share one copy. - validate_V runs at each period's barrier while the values are on the device; the post-solve pass reads only per-row flags. - The period-by-period simulation starts copying the next period's declared reads before a period's chunks run; the planner reserves that read set. - Membership checks and beartype sampling on read-through views no longer copy values onto the device. - Tests: new-process and sharded disk round trips, a stochastic test-1 model, test 9 against the issue's references within blocking's 2 ULP, transfer start/end ordering, lookup_policy and snapshot reads; tests that rely on several chunks or on unbudgeted NB-EGM/blocked runs ask for no budget explicitly. Docs and CHANGES updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Documentation build overview
38 files changed ·
|
The `_Prefetch` protocol in its signature was defined below it and was not runtime-checkable, so beartype skipped the function with a warning and it ran without its runtime type checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The host-side policy check reads each disk-stored policy only when it looks it up, instead of loading every period's policies into host memory at once before simulating. - A failure record takes the pylcm commit from git only when pylcm's own checkout is the repository root, not from a project repository an installed pylcm happens to sit in. - A read-through view's registry entry goes with the view itself, so a new mapping that reuses a dead view's id is never taken for it while one of the view's periods is still alive. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Each period's barrier moves every regime's feasibility counts, read counts and NaN/Inf flags to the host in one transfer instead of one per regime. - Dissolution flags kept in host memory go into the result's replay store as they are; the store hands out device copies when read, as for policies. Removes to_cpu_device and _on_cpu_device. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Values loaded early from disk are read by a background thread, so the file reads overlap the period instead of delaying its chunks. - The early copies are charged in the runtime ledger as parked beside the waiting chunks' carries, so the per-dispatch budget check sees them. - A relative value_storage directory is resolved when the config is built. - One host-copy function covers values and policies. - One set of shared runtime-coarse aliases (StoredValues, DeclaredValues, StoredPolicies) replaces five private copies. - The NaN check walks back only through the current period's rows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The base removed the solve-time dissolution flag and its artifact key, the current-IID replay inputs and the projected-forward inputs of the chunk planner, and added participation surfaces to the result and its archive. Value storage follows: no flag is copied to the host, published, read early or reserved; the read declarations and the early load cover values and policies; the period-by-period simulation passes the chunks the base's new participation surfaces and node-axis inputs; and the streamed archive writes participation surfaces at completion, as a save does. Certificate pins and seals rebuilt on the base; CI manifest regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # tests/candidate_certificate/direct_flow.py # tests/candidate_certificate/sources.json # tests/ci/ci-workloads.json
# Conflicts: # tests/candidate_certificate/direct_flow.py # tests/candidate_certificate/sources.json # tests/ci/ci-workloads.json
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # tests/candidate_certificate/sources.json
# Conflicts: # tests/candidate_certificate/direct_flow.py # tests/candidate_certificate/sources.json
…t/591-value-storage # Conflicts: # tests/candidate_certificate/direct_flow.py # tests/candidate_certificate/sources.json # tests/ci/ci-workloads.json
…20261103 # Conflicts: # tests/candidate_certificate/direct_flow.py # tests/candidate_certificate/sources.json # tests/ci/ci-workloads.json
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## codex/570-singleton-initial #633 +/- ##
===============================================================
+ Coverage 94.12% 94.15% +0.02%
===============================================================
Files 660 662 +2
Lines 111076 111997 +921
===============================================================
+ Hits 104554 105449 +895
- Misses 6522 6548 +26
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Benchmark comparison (main → HEAD)Comparing
|
Host and disk value storage hand the chunk planner a `jax.ShapeDtypeStruct` per policy leaf in place of the stored array. The finite NNBEGM planning step only abstracts each leaf to its shape, dtype, weak type and placement, so it needs no values; its concrete-only checks predate stored policies. Accept a descriptor wherever a leaf is only described: the payload annotation, the planner guard, the abstraction callback and the replay read enumerator, which must name a descriptor leaf as one the replay acquires. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LAZ63AjbZFLweWuJyPUs6S
Under a device-memory budget the chunk planner receives descriptors of stored policies. Each case simulates from a host- or disk-stored solve under a budget and compares the panel to the device-stored one exactly: the finite NNBEGM route, and the DC-EGM policy-first read with its grid fallback. Both routes rejected the descriptors before they were admitted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LAZ63AjbZFLweWuJyPUs6S
hmgaudecker
left a comment
There was a problem hiding this comment.
Review summary
Thank you, the host and disk storage design reads well. The read views hand out device arrays, and the writer thread, the failure latch and the per-solve archive names are easy to follow.
One thing I fixed on the branch, with a test in 53cbc91 and an inline comment on 1a2d304 explaining why. One thing I would like changed before merging: the disk archive's failure handling. It is built on a try/except in Model.solve that this PR introduces, and the three ways it misses its own contract all come from that choice. The archive can record its progress as it goes and then needs no handler. Details below. Line numbers refer to 1a2d304. Where I say something ran, it ran in the branch's locked tests-cpu environment on CPU with x64.
Disk archive: record progress as it happens, drop the handler
Model.solve wraps the solve in try/except BaseException and calls abandon with a record built in the handler (src/lcm/model.py:2778-2820). This PR introduces that handler. I would rather it did not exist, and I think it does not have to.
Every field of the record is known before the handler runs. dtype, params_fingerprint, pylcm_version, pylcm_commit and the model_fingerprint that describe collects at model.py:2860 are known before the first period. last_completed_period and completed_codes are known the moment each period or code finishes, and the archive already tracks them in memory. If the archive writes the static fields into the .h5.partial file when it is created and updates the progress fields as each period lands, then the documented record is on disk at every moment of the solve. A failure anywhere leaves the file with the record already in it. There is nothing to do on the way out, so there is no handler. Completion writes the manifest, flushes, fsyncs and renames, as _close does now. The finalizer's rename of a leftover .partial to .incomplete at solution.py:3729-3740 then completes the picture instead of producing a file with no record.
The three gaps I found are consequences of the handler and go away with it:
Model.simulate(solution=None)on the block-major route creates its ownBlockedSolutionArchiveatmodel.py:4916-4924and completes it atmodel.py:5151-5152, but never abandons it. A NaN value function leaves a.partialthat the finalizer renames with no record inside. With the archive owning its record, this route gets the same record assolvefor free.abandonwrites through the same HDF5 handle that just failed (solution.py:3250-3264), and the call is unprotected. On a full volume the cleanup error replaces theValueStorageErrorthat names the period and regime. A record written before the failure cannot fail after it._closecloses the handle beforefsyncand the rename (solution.py:3308-3316). If either fails,abandoncallscreate_dataseton a closed handle; with h5py 3.16.0 that raisesValueError: Unable to synchronously create dataset (invalid identifier type to function), which replaces the original error. The file at that point holds a complete manifest and payloads, and the loader checks contents (solution.py:606-609), soload_solutionwould accept it even after the finalizer renames it. I derived this from the code and did not run it. Without a handler there is no second failure, and the question becomes only whether a file with a complete manifest that was never renamed should load. I would say it may, since the payload checksums are verified, but please decide and write it down.
For tests: a block-major simulate(solution=None) with a NaN in the first and in a later code, checked before the exception is released; a solve where the period write fails on a full volume, which must raise the ValueStorageError and leave the record with the last completed period; a solve where fsync fails in _close, which must raise that error and leave a file whose fate is the one you decided above.
Lesser notes
- The archive is created before the solve starts (
model.py:2765-2777), and a read-only directory passes_fail_if_value_storage_invalid(execution.py:522-530).solvethen raises h5py'sPermissionErrorfor the.h5.partialpath rather than aValueStorageError. I ran this; the directory is left empty, but the error does not name the storage setting. - The planning descriptors drop
weak_type(host_values.py:549-553and the twodevice_stand_inmethods insolution.pynear lines 3090 and 3183), while the device path keeps it in_placed_abstract(forward_program_profiles.py:870-876). A weakly typed stored leaf would be planned with a different signature than it is dispatched with. I did not check whether any stored leaf is weakly typed; 1a2d304 does not change this. DiskValueEntry.materializeandDiskPolicyEntry.materializeuse a plainjax.device_put(solution.py:3111-3116,3149-3166), while the saved-archive loader refuses implicit x64 narrowing in_to_jax_without_narrowing(solution.py:2237-2248). A change of the x64 flag between solve and read narrows silently on the live path. Please consider the same guard.PeriodValueWriter.submitqueues the new period before it waits (solution.py:3640-3655), so a slow writer can hold three unwritten periods in host memory, not two. The wording atexecution.py:354-356is correct but easy to misread; please say three.submitwaits on the oldest write with no timeout (solution.py:3650-3653). A write that never returns, on a stalled network volume say, holds the solve forever. If that is intended, please say so in the docstring.- The per-period NaN and Inf flags now go to the host in
_materialize_period_feasibility_counts(diagnostics.py:216-253). The merge base already blocked on the running flags with.item()each period, so I see no new synchronisation, only more flags per transfer. A warm timing against the merge base would settle it.
| """ | ||
|
|
||
| arrays: tuple[jax.Array, ...] | ||
| arrays: tuple[jax.Array | jax.ShapeDtypeStruct, ...] |
There was a problem hiding this comment.
I pushed this commit, so here is why. A budgeted simulation of host- or disk-stored finite NNBEGM policies failed at 628c38b with a BeartypeCallHintParamViolation in ReplayPayload.from_policy. The chunk planner receives planning_stand_in descriptors instead of the stored policies (chunk_admission.py:249, host_values.py:541-568), and the finite NNBEGM planning step still insisted on concrete arrays: this annotation, the guard at forward_program_profiles.py:661-666, and the leaf annotation of _abstract_policy_leaf at forward_program_profiles.py:709. Those are type assumptions from #440, from when every stored policy was a device array.
Nothing in planning needs the values. _profile_finite_decision maps every leaf through _abstract_policy_leaf, which keeps shape, dtype, weak type and placement, and hands that to _prepare_program. So the commit admits jax.ShapeDtypeStruct at the three sites, plus a fourth: replay_payload_reads at replay_inputs.py:64 enumerates the leaves a replay acquires, and without counting a descriptor there _policy_read_sources at chunk_profiles.py:1508-1509 zips four stand-in leaves against zero reads. The corridor pins in the candidate certificate are re-anchored in the same commit.
The route is public: a same-instance solve with value_storage="host" or a directory, plus a positive device_memory_bytes, on CPU. The budget suite runs it with device storage only, and the NNBEGM storage tests remove the budget (tests/solution/test_value_storage.py:463-477), so neither suite simulates finite NNBEGM policies from storage under a budget. The DC-EGM policy-first read at forward_program_profiles.py:466-476 goes through the same _abstract_policy_leaf and failed the same way.
53cbc91 adds tests/simulation/test_budgeted_stored_policies.py, which covers both routes: host- and disk-stored panels must equal the device panel exactly. All four cases fail on the sources of 628c38b and pass on 1a2d304. With the fix, tests/simulation/test_finite_policy_budget.py and tests/solution/test_value_storage.py pass as well, 94 cases.
A disk-storage archive now writes its record of the solve into the `.h5.partial` file when it is created and updates it as each period or code lands, so `Model.solve` needs no handler and `abandon` is gone. Completion replaces the record with the manifest. This closes three gaps the review found: block-major `simulate(solution=None)` left no record, a cleanup write after a failed write could replace the `ValueStorageError`, and a failed fsync or rename was replaced by an h5py error. A finished file whose flush or rename fails is kept as `.incomplete` and loads. Also from the review: a directory the archive cannot be created in raises `ValueStorageError`; planning descriptors and disk reads keep weak types; disk reads refuse implicit x64 narrowing; the writer docs say three pending periods and that its wait has no timeout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y test Block-major reads and uploads of stored blocks now go through the guard the period-major disk reads use, so every off-device read raises `IncompatibleSolutionError` instead of narrowing float64 values when x64 was turned off after the solve. The guard is shared as `place_stored_leaf`. The operand-placement test still expected `ReplayPayload` to refuse `jax.ShapeDtypeStruct` leaves, which 1a2d304 admits for planning from stored policies; it now checks that descriptors are accepted and other leaves are still refused. Also fix comments that described the removed `abandon` step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…633-20261105 # Conflicts: # tests/candidate_certificate/sources.json
tests/simulation/test_budgeted_stored_policies.py, added in 53cbc91, was in no CI invocation, so the manifest test failed and no lane ran it. The generator registers it and rebalances the shards around it. It has no measured weight yet; the shards run it at their median weight, and the next commit's ci-workload-weights hook records this run's measurement. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@mj023 We hit this refusal while running the EKL 2019 model (lcm-zoo) at production scale. The message itself is good, but it arrives about 4.5 hours too late. Setup. pylcm 7d0ec63, the #639 stack, which already carries this refusal text. Source5 grid, float64, one A100, What happened. Every kernel compiled. Then
The refusal at Suggestions for this PR:
A rerun of the same solve with |
|
Follow-up on the rerun I mentioned above. Same pin (7d0ec63), same EKL source5 solve on one A100, width 64, with Outcome: refused at the same cell after a similar compile time.
The resident split the engine logs explains why host storage has no effect here. At
My reading of the code at 7d0ec63 follows. I have not instrumented it.
Questions:
The message-side requests are collected in #643. |
A core that declares no reads pins every next-period value and continuation leaf it can reach. Under host value storage the residency walk kept those pins for the rest of the solve, so the planner charged every later period's values as resident although the solve frees them with the window; in EKL 2019 this was the whole 37 GB refusal. The host ledger now counts such a read as a read by its own dispatch, ending with that period as declared reads do. The device ledger, which release and donation use, keeps its pins. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A refusal over stored values arrived only after every compilation wave and narrowing; EKL 2019 spent 4.5 hours before it. Three checks end it sooner: - A cell whose floor, the resident bytes no narrower workspace lowers, reaches the budget is refused before anything is compiled. - Under device storage a refused cell is placed in the host-storage plan too. When its compiled candidates fit there but not on the device, the solve is refused after that pass, naming the host total and saying to switch storage; otherwise the refusal says host storage does not help. - After a narrowing, the part of the reservation that does not shrink with the chunk is estimated; when it does not fit beside the floor, the cell is refused instead of narrowed again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Implements #591:
ExecutionConfig(value_storage=...)keeps solved values in pinned host memory or on disk, so the device holds only the resident window instead of every solved period.Stacked on #630 (
codex/570-singleton-initial). The diff also carries the #590 commits that the stack does not have yet.Summary
Solve, period-major, per period
t:"device", a refusal caused by stored values namesvalue_storage="host".ValueStorageError, naming the period, the regime and the target. Under a directory the file stays assolution-<id>.incomplete, recording fingerprints, the pylcm version and commit, the dtype and the last completed period.Evidence
Every outline test in #591 is in
tests/solution/test_value_storage.pyandtests/solution/test_value_storage_sharded.py:Bitwise equality: device, host and directory storage give bitwise-equal values, EGM policies and panels. Checked on the stochastic 4-period model, with one chunk and with several, and on NB-EGM.
Resident window:
Round trips: a disk archive is solved in one process and loaded and simulated in another: one device to one, two sharded to one, one to two sharded.
Failures: an injected write failure names the period, the regime and the
.h5.partialpath. Before this change the rawOSErrorcame through, and the record said period 0 instead of 2, because the writer kept writing after the failure.Fewer copies:
"host", and 8 vs 4 disk reads.Blocking references: blocked host storage matches the unblocked solve, and block-major in every mode matches period-major, within 2 ULP. That is the gap blocking itself causes; nothing else shrank.
Local runs (after merging Honor finite floors in paired maximum reductions #630 at
1ed7559d2):test_distributed_simulation_values.py, the 16 skipped above)CI
CI ran the full suite on
2ca4c2c05: 41 jobs pass, 23 fail. Every failure comes from the base #630:1ed7559d2; which shard they land in differs.test_fgp_model,test_economic_validation,test_egm_passive_asset_row,test_process_simulation_laws,test_unbudgeted_subject_width[9000].simulate_draws=→simulated_draws=rename:test_density_derivative_accuracystill passes the old name.test_regime_mixture_reduction,test_invariant_assembly_reservation;test_current_iid_grid_search,test_policy_lookup_devices[8].FAILED_PRECONDITIONerror on the GPU:test_public_exit_queriesin the bounded GPU job.test_participation_pricing_depth::test_a_saved_solution_…andtest_collective_household_example. The first passes locally in 290 s.test_collective_extended_real,test_participation_read_alias_process_code[solve],test_outer_candidate_drop_report,test_block_major_lifetime, and the regression output forprecautionary_savings_health.These are left to #630 and should clear here once it is fixed and merged in.
Device memory, measured
Setup:
peak_bytes_in_useduring the solve andbytes_in_useafter it."device""host""device""host""device"it grows by every period's values: −474 MB at 60 periods and −973 MB at 120, about all values but the resident window. What remains is the period's workspace.Merge Danger
Door: two-way
"device"stays the default, and with it nothing changes except one thing every mode shares: the NaN check now runs at each period's barrier instead of after the loop. The disk archive is in the existingSolutionResult.saveformat.Blast Radius: moderate
"device"only.verify.pypasses.🤖 Generated with Claude Code