Skip to content

feat(algo): H-Net, diffusion and BC algorithms - #555

Open
ElmoPA wants to merge 6 commits into
bf/3-modelsfrom
bf/4-algo
Open

feat(algo): H-Net, diffusion and BC algorithms#555
ElmoPA wants to merge 6 commits into
bf/3-modelsfrom
bf/4-algo

Conversation

@ElmoPA

@ElmoPA ElmoPA commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

algo/hnet, algo/diffusion (with its outer-stage variants) and algo/bc. Builds on
the model zoo in the previous commit.

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

ElmoPA commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

@ElmoPA
ElmoPA force-pushed the bf/4-algo branch 2 times, most recently from 4debbbe to 39d9533 Compare August 9, 2026 01:55
@ElmoPA
ElmoPA force-pushed the bf/3-models branch 2 times, most recently from dc37329 to 6cddc2e Compare August 9, 2026 04:09
@ElmoPA
ElmoPA force-pushed the bf/4-algo branch 2 times, most recently from 53e3f8e to 02933b4 Compare August 9, 2026 06:24
ElmoPA and others added 6 commits August 9, 2026 07:17
…thms

algo/hnet, algo/diffusion (with its outer-stage variants) and algo/bc. Builds on
the model zoo in the previous commit.

Also carries egomimic/pipeline/ -- the batchflow stage framework, its runner
PipelineAlgo and the stage implementations -- plus the three H-Net lightning
callbacks (random_attn_dropout, chunker_residual_scheduler, ratio_loss_scheduler)
and BATCHFLOW.md.

These sit here rather than in the infra commit because they import
egomimic.models.hnet and egomimic.models.diffusion from the model zoo below,
and pipeline/algo.py additionally imports egomimic.algo.hnet.episode_transforms
from this commit. Carrying them lower left the infra commit unable to import
six of its own modules when checked out on its own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
HNetPolicy.step referenced embodiment_id at two places in its body but never
declared it -- not a parameter, local, or attribute. The method failed two ways:

  * PackedAlgoBase.inference_step already calls
    policy.step(..., embodiment_id=self.domain_by_id.get(emb_id)), which raised
    TypeError: unexpected keyword argument;
  * called without it, the body raised NameError at the action_out lookup.

Either way the AR single-step path used for closed-loop sim rollout could not
run. The sibling policy step() in this same file already declares
embodiment_id: Optional[str] = None; this matches it, so the existing callers
work unchanged and single-embodiment models keep the None default.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
auxiliary_ac_keys: dict = {} and aux_ac_keys=[] are evaluated once at import and
shared by every caller that omits them. Neither is mutated today (the dict is
copied on assignment, the list is only iterated), so this is prevention rather
than a live bug fix.

Both become None and are materialised inside the function, leaving the
omitted-argument behaviour identical. Adds the missing typing.Optional import.

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

DualStreamChunkedOuterStage and MultiStreamOuterStage both already subclass
DualStreamOuterStage, and all three opened encode() with byte-identical copies of
the same ~27 lines: the packed-only guard, unpacking actions/__obs, deriving
T_total/device/dtype, casting cu_seqlens, accumulating the SPECIFIC stream over
input_modules, and computing the AGNOSTIC stream.

That block is packed-sequence boundary handling. A cu_seqlens device or dtype fix
applied to one copy silently missed the other two -- three places to get the same
thing wrong, with no test that would notice.

Three helpers on the shared base replace it: _packed_inputs (guard + unpack,
taking the caller name so each class keeps its own NotImplementedError message),
_specific_stream and _agnostic_stream. Control flow in every encode() is
otherwise untouched; only the derivation moved.

Net -37 lines.

Verified: ruff F401/F821/F841 clean on both files; all three classes import and
inherit the helpers; the packed-only guard still fires per class with its own
message.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
egomimic/algo/diffusion/ (the DFoT algo and its nine outer stages) moves to its
own PR stacked on top of this one. Nothing on main depends on it, and nothing
remaining in this stack imports it -- models/diffusion stays, because the
batchflow pipeline imports SinusoidalPosEmb from it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
HNetPolicy.init_step_state and HNetOuterStage.init_step_state were the same 27
lines -- same signature, same T_max coercion, same seven-key state dict --
differing only in which submodule owns the KV cache (self.hnet vs
self.inner_stage). Neither class has a base to hang it on, so it becomes a
module-level _init_ar_state(module, cache_owner, ...).

Worth more than its line count because both copies carried

    dtype = dtype or next(self.parameters()).dtype

Allocating this state at a fixed dtype instead of the model's own is what once
produced a bf16 rollout state under fp32 weights, under-measuring H-Net
closed-loop coverage ~2-2.6x and reading as the policy failing closed-loop. One
copy of that derivation is the right number.

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

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

Claude Code Review

Review of PR #555: H-Net, diffusion and BC algorithms

Summary

Large refactor introducing algo/hnet (with PackedAlgoBase spine), algo/bc (WindowedBC as robomimic BC-RNN-GMM replica), and algo/diffusion (truncated). Also hoists shared embodiment-key resolution / obs-building / default reducer into Algo base class. Adds BATCHFLOW.md describing a stage-based convention (though this PR doesn't fully implement it — it's aspirational).

Key concerns

1. Algo.__init__ breakage of existing subclasses

The new Algo base introduces default implementations of compute_losses and log_info, but the default compute_losses reads predictions[f"{emb_id}_action_loss"] and iterates batch.keys() as if it's {emb_id: sub_batch}. This works for the new packed algos, but if any existing subclass (ACT, HPT, PI) doesn't override and expects the old NotImplementedError contract, silent behavior change or KeyError is possible. The docstring claims "subclasses… still override with their own superset reducer" — please confirm every existing subclass in the repo overrides both, or the default is a footgun.

Also, self.device is referenced in the default compute_losses but is not set by Algo.__init__ — relies on subclasses setting it. This is fine for the new algos but should be documented on the base.

2. _resolve_embodiment_keys — silent missing action key

for key in norm_stats.keys_of_type("action_keys", emb_id):
    if norm_stats.is_key_with_embodiment(key, emb_id) and key == self.ac_keys[emb]:
        self.resolved_ac_keys[emb_id] = key

If self.ac_keys[emb] doesn't match any key returned by norm_stats, resolved_ac_keys[emb_id] is silently missing, and the failure surfaces as a KeyError deep in _unpack_obs_actions. Please raise explicitly with the mismatch shown (embodiment enum + expected vs available keys) — mismatch between config embodiment strings and norm_stats topology is exactly the bug class that eats hours.

3. WindowedBC monkey-patches train_obs_transforms / episode_level_transforms

self.train_obs_transforms: list = []
self.episode_level_transforms: list = []

The comment admits this is a "pre-existing latent bug from the H-Net restructure." That's fine as a fix, but the real issue is that PackedAlgoBase.__init__ isn't being called and the base contract is being papered over. Either:

  • Call PackedAlgoBase.__init__ properly and let it initialize these, or
  • Move these to PackedAlgoBase class attributes (defaults) rather than instance attributes so subclasses inherit safely.

The current pattern will break again the next time PackedAlgoBase.process_batch_for_training gains a new attribute reference.

4. _cut_windows O(B·T_max) Python loop

pairs = []
for b in range(B):
    L = int(seq_lens[b].item())
    for s in range(L):
        pairs.append((b, s))

For B=32, T_max=500 that's 16k pairs enumerated in Python every training step, plus torch.randperm on a possibly-large list. Not fatal but shows up in profiler. Consider vectorizing (torch.cartesian_prod or masked index math). Low priority.

5. WindowedBCPolicy.steplast_action fallback

if state["queue"]:
    a = state["queue"].pop(0)
else:
    a = state["last_action"]

If state["last_action"] is None (first env step is somehow a non-obs step, e.g. t=0 with obs_stride>1? — actually 0 % anything == 0 so obs step, OK), this crashes with AttributeError on .unsqueeze. Add an explicit assertion that t=0 is always an obs step, or initialize last_action in init_step_state.

6. HNetPolicy._remap_legacy_input_keys contradicts PR description

The PR description references BATCHFLOW.md which says: "Backward ckpt compatibility explicitly NOT required (user 2026-07-10)." Yet the code carries a legacy-key remapping hook. Either delete the remap hook (align with declared policy) or delete the sentence in BATCHFLOW.md. Dead compatibility code compounds.

7. Diff truncated

The diff is cut off mid-HNetPolicy.step. I can't review:

  • HNetOuterStage (stage-based H-Net trunk)
  • algo/diffusion/ in full
  • The PackedAlgoBase class body

Please split this PR. ~1600 lines for hnet/algo.py alone, plus BC, plus diffusion, plus the Algo base refactor — this cannot be reviewed atomically. Suggest:

  • PR A: hoist shared helpers into Algo (small, mechanical, testable)
  • PR B: algo/hnet package
  • PR C: algo/bc package
  • PR D: algo/diffusion package
  • PR E: BATCHFLOW.md and any actual pipeline stages implementing it

8. BATCHFLOW.md is aspirational documentation of a design not implemented here

The doc claims stage(batch: dict) -> dict is the "one interface" and describes PipelineAlgo / stages_io / stages_hnet / stages_flow. Nothing in this diff shows these modules. Either land the doc with the pipeline it describes, or mark it as a design proposal — otherwise future readers will assume the codebase follows this convention when the algos in this same PR use HNetContext (which the doc explicitly


Reviewed by Claude · Review workflow

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