Skip to content

feat(models): H-Net and diffusion model zoo - #554

Open
ElmoPA wants to merge 19 commits into
graphite-base/554from
bf/3-models
Open

feat(models): H-Net and diffusion model zoo#554
ElmoPA wants to merge 19 commits into
graphite-base/554from
bf/3-models

Conversation

@ElmoPA

@ElmoPA ElmoPA commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

New subsystems with no counterpart on main: models/hnet (stages, blocks, the
scan and register chunk interfaces, routing) and models/diffusion (DiT3D and
spatial backbones, sampling, image VAE), plus cores and stems.

Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com

ElmoPA commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

ElmoPA and others added 2 commits August 8, 2026 15:31
Deletions -- each has zero importers on main, verified by module-path search,
per-symbol search, hydra _target_ strings, `python -m` invocations, file-path
references in shell/CI, and dynamic-import patterns:

  egomimic/utils/memory_utils.py            229
  egomimic/utils/real_utils.py              142
  egomimic/pl_utils/test_model_wrapper.py   135
  egomimic/rldb/compression_utils.py        130
  egomimic/rldb/data_utils.py               103
  egomimic/rldb/scripts/utils.py             86
  egomimic/utils/obs_utils.py                10

Two worth calling out:

  * rldb/scripts/utils.py is a dead DUPLICATE -- all five of its functions
    (nds_pq, is_key, is_listy, nds, str2bool) also exist in
    egomimic/utils/egomimicUtils.py, and every caller imports them from there.

  * compression_utils.py is the superseded video codec (encode_video /
    decode_video on PyAV). Its replacement is rldb/zarr/video_codec.py, which
    arrives with the h264 PR. Nothing on main imports compression_utils. The
    only importer anywhere is rldb/zarr/verify_zarr.py, which exists solely on
    six branches last touched in February 2026 and on neither main nor the
    batchflow stack; those branches carry their own copy of that file and would
    need a rebase regardless.

Rename:

  egomimic/rldb/zarr/zarr_write_test.py -> egomimic/rldb/zarr/hdf5_to_zarr.py

The name said "test", but it is not one: it is an HDF5 -> Zarr v3 conversion
utility with an argparse CLI (--hdf5-path / --arm / --extrinsics-key), forward
kinematics and camera-frame transforms. The new name matches the existing
aria_to_zarr / eva_to_zarr / mecka_to_zarr convention. Nothing referenced the
old name.

Deliberately KEPT, having been checked rather than assumed:

  * benchmark_forward_pass.py -- a working dataloader-throughput benchmark with
    an argparse CLI and no hardcoded paths.
  * test_zarr.py -- a genuine pytest module (13 tests over MultiDataset /
    ZarrDataset / action-chunk transforms). It is currently uncollected because
    of five hardcoded absolute paths and an import of
    build_human_bimanual_transform_list, which exists on no branch. That makes
    it a broken test to repair, not dead code to delete.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Deletions -- each has zero importers on main, verified by module-path search,
per-symbol search, hydra _target_ strings, `python -m` invocations, file-path
references in shell/CI, and dynamic-import patterns:

  egomimic/utils/memory_utils.py            229
  egomimic/utils/real_utils.py              142
  egomimic/pl_utils/test_model_wrapper.py   135
  egomimic/rldb/compression_utils.py        130
  egomimic/rldb/data_utils.py               103
  egomimic/rldb/scripts/utils.py             86
  egomimic/utils/obs_utils.py                10

Two worth calling out:

  * rldb/scripts/utils.py is a dead DUPLICATE -- all five of its functions
    (nds_pq, is_key, is_listy, nds, str2bool) also exist in
    egomimic/utils/egomimicUtils.py, and every caller imports them from there.

  * compression_utils.py is the superseded video codec (encode_video /
    decode_video on PyAV). Its replacement is rldb/zarr/video_codec.py, which
    arrives with the h264 PR. Nothing on main imports compression_utils. The
    only importer anywhere is rldb/zarr/verify_zarr.py, which exists solely on
    six branches last touched in February 2026 and on neither main nor the
    batchflow stack; those branches carry their own copy of that file and would
    need a rebase regardless.

Rename:

  egomimic/rldb/zarr/zarr_write_test.py -> egomimic/rldb/zarr/hdf5_to_zarr.py

The name said "test", but it is not one: it is an HDF5 -> Zarr v3 conversion
utility with an argparse CLI (--hdf5-path / --arm / --extrinsics-key), forward
kinematics and camera-frame transforms. The new name matches the existing
aria_to_zarr / eva_to_zarr / mecka_to_zarr convention. Nothing referenced the
old name.

Deliberately KEPT, having been checked rather than assumed:

  * benchmark_forward_pass.py -- a working dataloader-throughput benchmark with
    an argparse CLI and no hardcoded paths.
  * test_zarr.py -- a genuine pytest module (13 tests over MultiDataset /
    ZarrDataset / action-chunk transforms). It is currently uncollected because
    of five hardcoded absolute paths and an import of
    build_human_bimanual_transform_list, which exists on no branch. That makes
    it a broken test to repair, not dead code to delete.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ElmoPA
ElmoPA force-pushed the bf/3-models branch 2 times, most recently from e477a60 to 5be23a7 Compare August 8, 2026 22:47
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown

Claude Code Review

Review

Summary

Massive relocation + new-subsystem PR that vendors H-Net (stages, routing, blocks, scan), a diffusion model zoo (DiT3D, spatial backbones, VAE, samplers), and reorganizes cores/stems. Roughly 15k+ lines added across new files, plus a heavily rewritten AGENTS.md documenting packed dataloading, CUDA kernel installation, and training recipes.

Key concerns

1. PR scope is dangerously large

This bundles at least four independent efforts:

  • Cores relocation (bc_rnn_netscores/)
  • H-Net vendored stack + packed dataloading pipeline
  • Diffusion model zoo (DiT3D, VAE, samplers)
  • HPT/ACT primitive moves

Any of these could break existing training configs. Splitting into ≥3 PRs would make review tractable and let bisect actually work. Strongly recommend splitting before merge.

2. AGENTS.md documents behavior that contradicts codebase conventions

  • Test data paths (/coc/cedarp-dxu345-0/Tsim_datasets/...) are hardcoded — smoke tests will only run for one user. Should be parameterized or fixtured.
  • The doc references pushshapes_sim embodiment, but I can't verify this matches the canonical embodiment enum from the diff. Please confirm against egomimic/rldb/embodiment/.
  • No mention of Cloudflare R2 upload path for any generated artifacts.

3. Packed dataloading — subtle correctness risks

  • Normalization ordering: The doc claims norm_stats.normalize is "shape-agnostic per-feature broadcasting, so it works equally on padded (B, T, D) and packed (T_total, D)." This is true only if every feature's stats have shape (D,) or scalar. If any stat is shape (1, T, D) or (T, D) (as sometimes happens with per-timestep normalizers), packed mode will silently miscompute. Please add an assertion in process_batch_for_training on packed path, or a test.
  • _iter_leaves descent into ZarrEpisodePackedDataset.datasets: fixed once, but infer_norm_from_dataset was also patched separately. Any other MultiDataset traversal (e.g., in eval, in checkpoint restore) that iterates datasets directly rather than via _iter_leaves will still be broken. Grep for .datasets accesses.
  • chunk_offset semantics under resampling: if __getitem__ resamples on failure, does the returned chunk_offset correspond to the originally requested index or the resampled one? This affects reproducibility. Should be the resampled one — please verify.
  • min_seq_len=64 with chunking="none": what happens to episodes shorter than min_seq_len? Are they dropped or included? The tsimulation config has chunking="none" + min_seq_len=64, but circle/ has episodes as short as 245 frames — safe here, but the interaction is undocumented.

4. HNetCore — several fragile assumptions

  • Kernel-availability side channel: correctness (train==rollout parity, causality) depends on has_flash_attn() == False and has_mamba_scan() == False. If someone installs those kernels for perf, the bf16 hard-cast in DeChunkLayer._ema_kernel silently activates even under fp32. The docstring warns about this but there's no runtime guard. Please add:
    if self.training and (has_flash_attn() or has_mamba_scan()):
        warnings.warn(...)  # or assert if precision mismatch detected
  • d_model % 32 == 0 guard is good, but the error message says "DeChunkLayer EMA headdim" without pointing at the kernel; a link/reference would help future debuggers.
  • Buffer replay in step: the docstring correctly notes that KV-cached step was skipped because parity proof is hard. Fine, but the O(T²) replay cost at rollout should be flagged in the eval config or logged as a metric — otherwise someone will hit it in a longer-horizon eval and blame the wrong thing.
  • Ratio loss is "inert": the docstring says ratio loss weight is a knob but never collected by BC-RNN algo. This is a footgun — users will tune it expecting effect. Either wire it up or hard-code to 0 with a comment.

5. HNet.process_batch_for_training typo fix

The changelog notes: "the original if key is not None was always true; should have been if key_name is not None". This means previous H-Net training runs bucketed unrelated keys under None. Any prior norm stats, checkpoints, or published numbers from H-Net runs pre-fix may be tainted. Please:

  • Confirm no active experiments depend on the buggy behavior.
  • Add a regression test.

6. Test coverage

  • 86 tests total, ~20s — reasonable for the new hnet_nets code.
  • But: no tests for the diffusion zoo (DiT3D, VAE, samplers) are visible in the diff summary. Given this is new code, at minimum shape + one-step sampling smoke tests are needed.
  • No test that padded ↔ packed give the same result for a small H-Net. This is the single most important correctness check for packed mode. smoke_packed_training_e2e.py runs the path but doesn't compare to a padded reference on the same data.

7. Coordinate frames / embodiment conventions

Diff summary doesn't show any pose/coordinate-frame code, so probably fine. But the pushshapes embodiment obs is state_agent_obj (5-d, no pose) — please confirm this doesn't need SLAM→head frame re-expression (it's 2D sim, so likely not, but worth stating).

8. Zarr writing

Diff summary do


Reviewed by Claude · Review workflow

ElmoPA and others added 7 commits August 8, 2026 19:44
…odules

egomimicUtils.py was a 1,120-line grab-bag. Rather than inventing new modules,
the 20 symbols that still have callers move into the modules that already
exist and already own that concern:

  pose_utils.py    +9  pose/transform/camera-frame + the two array interpolators
  viz_utils.py     +3  draw_actions, draw_dot_on_frame, get_gaze_endpoint
  tensor_utils.py  +2  get_sinusoid_encoding_table, EinOpsRearrange
  utils.py         +4  str2bool, fmt, download_from_huggingface, STD_SCALE
  metrics.py       +2  frechet_gaussian_over_time, reverse_kl_from_samples (NEW --
                       the only case with no existing home; both are eval metrics
                       consumed solely by eval/eval_hpt.py)

viz_utils already imported draw_actions/draw_dot_on_frame/get_gaze_endpoint from
egomimicUtils, so moving them there removes an import rather than adding one.

Code is moved verbatim (exact AST source spans). egomimicUtils.py re-exports the
moved symbols, so all existing importers keep working unchanged.

Imports are resolved, not copied: each destination gets exactly the third-party
imports its moved code references, cross-module references point at the real new
home, and any destination that previously imported a now-local symbol from
egomimicUtils has that import removed -- otherwise egomimicUtils re-exporting from
viz_utils while viz_utils imports from egomimicUtils is a circular import.

The 24 symbols with no caller anywhere are left in egomimicUtils untouched;
removing them is a separate decision and a separate commit.

Verified: ruff F821/F811 clean on egomimic/utils; all 20 moved symbols resolve
through the old path; zero unresolvable global references in any moved function
body (checked by walking each bodys AST against its new module globals -- the
check that a plain import test does not catch); 13/14 importer modules import,
the 14th being egomimic.robot.rollout which fails on a pre-existing unrelated
bare robot_utils import.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
rollout.py and collect_demo.py did:

    from robot_utils import RateLoop

but there is no top-level robot_utils package -- the file is
egomimic/robot/robot_utils.py. That bare form only resolves when the script is
run from inside egomimic/robot/ (that directory on sys.path), so importing
either module as part of the package raised ModuleNotFoundError. It is also why
rollout.py looked dead to reference sweeps: it is the sole caller of
egomimicUtils.interpolate_arr / interpolate_arr_euler, and an import-based check
never got far enough to see it.

stream_aria.py and stream_d405.py already use the package-qualified form; this
makes all four consistent.

Verified: egomimic.robot.rollout now imports (previously ModuleNotFoundError).
collect_demo still fails on ppadb, an unrelated optional hardware dependency for
the Oculus reader that is simply absent from the cluster venv.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
rollout.py and collect_demo.py did:

    from robot_utils import RateLoop

but there is no top-level robot_utils package -- the file is
egomimic/robot/robot_utils.py. That bare form only resolves when the script is
run from inside egomimic/robot/ (that directory on sys.path), so importing
either module as part of the package raised ModuleNotFoundError. It is also why
rollout.py looked dead to reference sweeps: it is the sole caller of
egomimicUtils.interpolate_arr / interpolate_arr_euler, and an import-based check
never got far enough to see it.

stream_aria.py and stream_d405.py already use the package-qualified form; this
makes all four consistent.

Verified: egomimic.robot.rollout now imports (previously ModuleNotFoundError).
collect_demo still fails on ppadb, an unrelated optional hardware dependency for
the Oculus reader that is simply absent from the cluster venv.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Completes the breakup: the compatibility shim is gone and the module no longer
exists.

The 9 files that imported from it now import from the module that actually owns
each symbol -- pose_utils, viz_utils, tensor_utils, utils, metrics. Each import
statement was rewritten by mapping every imported name through the shim's own
re-export table, so aliases are preserved and nothing is guessed:

  algo/hpt.py                                        models/hpt_nets.py
  eval/eval_act.py                                   robot/rollout.py
  eval/eval_hpt.py                                   scripts/data_visualization.py
  scripts/aria_process/aria_to_zarr.py               scripts/eva_process/eva_to_zarr.py
  scripts/data_visualization/inspector_lib/dataset_view.py

The 24 symbols that had no home and no caller are removed with the file. Each
was checked individually for any reference in .py, .yaml or .ipynb outside
egomimicUtils itself -- all 24 came back with zero. This is the check that
previously caught get_vector_from_yaw_pitch, which had no external references
but was called by get_gaze_endpoint, and so was moved rather than dropped.

Verified: no code reference to egomimicUtils remains anywhere (only three
explanatory comments naming the old module); ruff F821/F811 clean across
egomimic/*.py; 13/14 affected modules import. The 14th, egomimic.robot.rollout,
fails on the unrelated bare robot_utils import fixed separately in #563.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous commit parked str2bool / fmt / STD_SCALE / download_from_huggingface
in utils/utils.py. That was wrong: utils.py is the hydra + lightning experiment
lifecycle module (extras, task_wrapper, get_metric_value), so this just traded one
grab-bag for another.

  str2bool                  -> type_utils.py  (a str->bool coercion, next to the
                               existing _to_numpy coercion)
  download_from_huggingface -> hf_utils.py    (new; keeps the Hub dependency
                               visible and optional, mirroring the aws/ subpackage)
  STD_SCALE                 -> algo/hpt.py    (not a shared utility at all -- it is
                               the HPT action-token init scale with exactly one
                               consumer, 20 lines below where it now lives)
  fmt                       -> deleted

fmt turned out to be dead. It was classified as live off a reference count that
word-matched unrelated  keyword arguments in plot_cartesian.py and in
vendored lerobot. Nothing has ever imported it -- not on main, not now.

utils.py is back to its three lifecycle helpers.

Verified: ruff F401/F821/F811 clean on egomimic/utils and algo/hpt.py; 13/14
affected modules import (the 14th is robot/rollout, pending #563).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
chore: remove seven unused modules, rename a mis-named converter

Deletions -- each has zero importers on main, verified by module-path search,
per-symbol search, hydra _target_ strings, `python -m` invocations, file-path
references in shell/CI, and dynamic-import patterns:

  egomimic/utils/memory_utils.py            229
  egomimic/utils/real_utils.py              142
  egomimic/pl_utils/test_model_wrapper.py   135
  egomimic/rldb/compression_utils.py        130
  egomimic/rldb/data_utils.py               103
  egomimic/rldb/scripts/utils.py             86
  egomimic/utils/obs_utils.py                10

Two worth calling out:

  * rldb/scripts/utils.py is a dead DUPLICATE -- all five of its functions
    (nds_pq, is_key, is_listy, nds, str2bool) also exist in
    egomimic/utils/egomimicUtils.py, and every caller imports them from there.

  * compression_utils.py is the superseded video codec (encode_video /
    decode_video on PyAV). Its replacement is rldb/zarr/video_codec.py, which
    arrives with the h264 PR. Nothing on main imports compression_utils. The
    only importer anywhere is rldb/zarr/verify_zarr.py, which exists solely on
    six branches last touched in February 2026 and on neither main nor the
    batchflow stack; those branches carry their own copy of that file and would
    need a rebase regardless.

Rename:

  egomimic/rldb/zarr/zarr_write_test.py -> egomimic/rldb/zarr/hdf5_to_zarr.py

The name said "test", but it is not one: it is an HDF5 -> Zarr v3 conversion
utility with an argparse CLI (--hdf5-path / --arm / --extrinsics-key), forward
kinematics and camera-frame transforms. The new name matches the existing
aria_to_zarr / eva_to_zarr / mecka_to_zarr convention. Nothing referenced the
old name.

Deliberately KEPT, having been checked rather than assumed:

  * benchmark_forward_pass.py -- a working dataloader-throughput benchmark with
    an argparse CLI and no hardcoded paths.
  * test_zarr.py -- a genuine pytest module (13 tests over MultiDataset /
    ZarrDataset / action-chunk transforms). It is currently uncollected because
    of five hardcoded absolute paths and an import of
    build_human_bimanual_transform_list, which exists on no branch. That makes
    it a broken test to repair, not dead code to delete.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

chore: remove seven unused modules, rename a mis-named converter (#561)

Deletions -- each has zero importers on main, verified by module-path search,
per-symbol search, hydra _target_ strings, `python -m` invocations, file-path
references in shell/CI, and dynamic-import patterns:

  egomimic/utils/memory_utils.py            229
  egomimic/utils/real_utils.py              142
  egomimic/pl_utils/test_model_wrapper.py   135
  egomimic/rldb/compression_utils.py        130
  egomimic/rldb/data_utils.py               103
  egomimic/rldb/scripts/utils.py             86
  egomimic/utils/obs_utils.py                10

Two worth calling out:

  * rldb/scripts/utils.py is a dead DUPLICATE -- all five of its functions
    (nds_pq, is_key, is_listy, nds, str2bool) also exist in
    egomimic/utils/egomimicUtils.py, and every caller imports them from there.

  * compression_utils.py is the superseded video codec (encode_video /
    decode_video on PyAV). Its replacement is rldb/zarr/video_codec.py, which
    arrives with the h264 PR. Nothing on main imports compression_utils. The
    only importer anywhere is rldb/zarr/verify_zarr.py, which exists solely on
    six branches last touched in February 2026 and on neither main nor the
    batchflow stack; those branches carry their own copy of that file and would
    need a rebase regardless.

Rename:

  egomimic/rldb/zarr/zarr_write_test.py -> egomimic/rldb/zarr/hdf5_to_zarr.py

The name said "test", but it is not one: it is an HDF5 -> Zarr v3 conversion
utility with an argparse CLI (--hdf5-path / --arm / --extrinsics-key), forward
kinematics and camera-frame transforms. The new name matches the existing
aria_to_zarr / eva_to_zarr / mecka_to_zarr convention. Nothing referenced the
old name.

Deliberately KEPT, having been checked rather than assumed:

  * benchmark_forward_pass.py -- a working dataloader-throughput benchmark with
    an argparse CLI and no hardcoded paths.
  * test_zarr.py -- a genuine pytest module (13 tests over MultiDataset /
    ZarrDataset / action-chunk transforms). It is currently uncollected because
    of five hardcoded absolute paths and an import of
    build_human_bimanual_transform_list, which exists on no branch. That makes
    it a broken test to repair, not dead code to delete.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

refactor(utils): break egomimicUtils apart into role-based modules

egomimicUtils.py had grown to 1,120 lines holding 44 unrelated symbols --
camera geometry, frame drawing, interpolation, distribution metrics, torch
building blocks and assorted helpers all in one file. It is the kind of module
everything imports and nobody can safely change.

Its contents now live in six modules next to it:

  utils/geometry.py       18 symbols, 397 lines  pose/transform/camera-frame
  utils/viz_draw.py        8 symbols, 273 lines  frame annotation + drawing
  utils/interpolation.py   3 symbols, 100 lines  array / euler interpolation
  utils/metrics.py         2 symbols, 100 lines  Frechet, reverse KL
  utils/nn_utils.py        2 symbols,  28 lines  sinusoid table, EinOpsRearrange
  utils/misc.py           11 symbols, 108 lines  str2bool, fmt, download, ...

Code is moved verbatim -- each symbol's exact source span was extracted by AST,
so nothing was reformatted or rewritten. egomimicUtils.py becomes a 109-line
re-export shim, so all ten existing importers keep working with no change and
`from egomimic.utils.egomimicUtils import X` still resolves for every X. New
code should import from the specific module.

NOTHING IS DELETED here. Twenty-five of the 44 symbols have no reference
anywhere outside the file (~533 lines) -- draw_rotation_text 89, ypr_to_matrix
53, draw_annotation_text 38, convert_to_cam_frame 35, miniviewer 32, nds 29,
nds_pq 25, AlohaFK 24, apply_vignette 14, and others. Removing them is a
separate judgement call and belongs in its own commit: several are plausible
hand-run tooling rather than dead weight, and "no importer" has already proved
a poor proxy for "unwanted" in this repo.

Verified: all 44 symbols re-export from the old path; every symbol is the SAME
object as in its new home (no accidental copies); all six modules import; all
ten importing files still parse.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The reconciliation layer. Every file where main and the batchflow lineage
genuinely disagreed lands here, so everything above it is bulk-new code.

embodiment.py takes main's collapsed enum (HUMAN_* 1-3, EVA_* 4-6) and re-adds
PUSHSHAPES_SIM 15 / _STICK 16 / _SMALL_CIRCLE 17 -- pinned because trained
checkpoints and collected datasets encode those IDs.

zarr_dataset_multi.py is a 3-way merge against the fork point: main's
SafeS3EpisodeResolver, EvenStrideDataset, _evenly_spaced_indices and intrinsics
property, plus batchflow's _read_span, _annotations_for_span and
LocalEpisodeResolverWithEmbodimentOverride. action_chunk_transforms.py keeps
batchflow's DeltaAction alongside main's PadGripperZeros.

The batchflow repo notes land in AGENTS.md rather than a second CLAUDE.md, so
the repo keeps one conventions file. DESIGN.md is not carried over: it was a
2026-06-06 restructure proposal written against EgoVerse-pact-2, still marked
"awaiting approval", describing a move that has since happened here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The JPEG-decodability probe at _probe_image_key calls
simplejpeg.decode_jpeg(...) but the module was never imported in this file.

The call sits inside a try/except Exception, so instead of crashing it made
the probe report EVERY image as undecodable -- a silent false negative rather
than an error. _common.py in the same package already imports it the same way.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
AGENTS.md still documented egomimic/models/hnet_nets/, egomimic/algo/hnet.py and
egomimic/eval/eval_hnet.py. None of those paths exist: the packages are
models/hnet/, algo/hnet/ and eval/core/eval_hnet.py. Anyone -- human or agent --
following the doc went looking for files that are not there, and AGENTS.md is the
first thing an agent reads.

Section headings renamed hnet_nets -> hnet to match. test_hnet_nets.py is left
alone: that file genuinely still has that name.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ElmoPA and others added 3 commits August 9, 2026 02:07
…oader

This PR introduces the packed subsystem -- ZarrEpisodePackedDataset and
pack_collate -- but MultiDataModuleWrapper, which is inherited unchanged from
main, hardcoded annotation_collate for every dataset. annotation_collate ends in
default_collate, which tries to torch.stack ragged packed samples, so every
packed_episode config died on its first batch with

    RuntimeError: Trying to resize storage that is not resizable

pack_collate needs two call sites. The other one -- MultiDataset's norm-stat
inference in zarr_dataset_multi.py -- already had it (collate_fn = pack_collate
if is_packed else None) and is covered by test_packed_pipeline. The training
dataloader had neither the wiring nor a test, and main has no packed configs at
all, so nothing exercised it.

_collate_fn_for is ported from EgoVerse-gmm-dualstream / EgoVerse2, where this
dispatch already backs the live H-Net runs, rather than written fresh.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two standalone simulator packages, each with its own pushshapes/, collect/,
examples/ and tests/. There is no v3: what was labelled v3 is the socket fix
that ships AS v2 -- the intermediate all-faces-grip build was a bug.

__init__.py aliases the active version's submodules to the top level so existing
'from Tsimulation.pushshapes import X' call sites keep working; TSIM_VERSION
selects the version per process.

Placed before eval because eval calls into it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…hing on them

Adding the u_socket was done by editing the environment. Its latch, friction and
penetration guards -- 12 methods, ~470 lines -- went into env.py as
`if pusher_shape == "u_socket"` branches, and its 3-DOF action became a hardcoded
`expected_shape = (3,) if self.pusher_shape == "u_socket" else (2,)`. That is the
single largest reason the two sims diverged: sim_v1's env.py has ZERO socket
references, sim_v2's had 115, and env.py's step loop called nine socket-specific
guards in sequence.

An Agent now owns the three things the environment should not know about:

  * ACTION SPACE  -- action_dim (2 for a free-moving pusher, 3 when the agent
    also controls orientation) and target_pose() to decode a raw action;
  * BODY          -- build() in the pymunk space;
  * CONTACT MODEL -- pre_substep()/post_substep() hooks around each physics
    substep, plus on_reset() for per-episode state.

env.step is agent-agnostic:

    captured = self.agent.pre_substep(self)
    self._drive_pusher_toward(tx, ty, dt_sub, target_angle)
    self._space.step(dt_sub)
    self._clamp_pusher_to_static()
    self.agent.post_substep(self, captured)

Agent (circle, circle_small, stick, L) implements the hooks as no-ops.
USocketAgent owns all the latch/guard logic and the socket geometry constants,
and its solid_pusher / socket_inside_friction_only flags become constructor
arguments rather than environment state. A new agent with an unusual action
space is a new class plus one line in make_agent(), not another branch in the
simulator.

env.py 1292 -> 795 lines.

sim_v1 IS DELIBERATELY UNTOUCHED. It is frozen so pre-rewrite data replays
exactly; refactoring it would put that at risk for no benefit, since it has no
socket to abstract in the first place.

VERIFIED BY REPLAY EQUIVALENCE, not by inspection. Baselined the unmodified sim
with the identical harness first, then compared:

    u_socket_3000_v2         100.0% -> 100.0%   (p50 0.0033 -> 0.0033)
    circle_3000_plus_gen_v2   89.7% ->  89.7%
    circle_v2_obstonly        17.1% ->  25.7%

The gate caught two real bugs that inspection did not: the moved
_socket_contact_is_on_inner_face call site lost its env argument, and
socket_latched -- a property DERIVED from `_socket_constraints is not None` --
had become a plain attribute nothing updated, initialised to [] so it would have
read as permanently latched. Both fixed; the socket went 0% -> 100%.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012V58H37tmcvgDthELMd5Xk
ElmoPA and others added 5 commits August 9, 2026 07:09
New subsystems with no counterpart on main: models/hnet (stages, blocks, the
scan and register chunk interfaces, routing) and models/diffusion (DiT3D and
spatial backbones, sampling, image VAE), plus cores and stems.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
widths=[512] in hpt_heads/hpt_stems and down_dims=[256,512,1024] in
denoising_nets are evaluated once at import, so every caller that omits the
argument shares one list object. None of the three currently mutates it, so
nothing is broken today -- this removes the footgun before something does.

Each becomes None with the original value restored inside the function, so the
behaviour for an omitted argument is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
obs_encoder.py and cond_encoders.py each carried a private _mlp() of the same
shape -- but ObsEncoder's used ReLU and CondEncoderModule's used GELU. Both are
live: ObsEncoder backs nine bc_rnn model configs (base/tx/tx_chunk8/hnet/
tx_cotrain_*), CondEncoderModule backs the H-Net firstend/cotrain/bf_rh configs.

They collapse into one build_mlp() whose 'act' argument is keyword-only with no
default, so every call site states its activation. That is the point of the
change: activations hold no parameters, so a checkpoint trained under one
activation loads into the other with no error and merely produces different
numbers -- exactly the kind of silent divergence two near-identical private
helpers invite.

Behaviour is unchanged: ReLU stays at both ObsEncoder call sites and GELU at both
CondEncoderModule ones. Verified by building both encoders from the old and new
trees -- identical activation lists, identical parameter keys, identical forward
output hashes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant