[Issue 101][eval-and-fix] Guard SeedVR2 var_attention_pytorch on missing nested-jagged API (closes pollockjj/mydevelopment#210)#48
Conversation
… missing torch.nested.nested_tensor_from_jagged (closes pollockjj/mydevelopment#210) `var_attention_pytorch` called `torch.nested.nested_tensor_from_jagged` unconditionally on three lines with no presence check. On PyTorch builds that do not provide that prototype API, the first SeedVR2 inference call hit a bare `AttributeError` deep in the attention path with no SeedVR2 context, leaving the operator no way to identify which attention path required the missing capability. Add an entry guard at the top of `var_attention_pytorch` that raises `RuntimeError` whose message names both `SeedVR2` and `nested_tensor_from_jagged` so the failing surface is identifiable from the traceback alone. The present-API path is unchanged: when the attribute exists the guard falls through to the original computation. Malformed `cu_seqlens` continue to raise torch's own per-call `RuntimeError` (e.g. `split_with_sizes ...`); the SeedVR2-context substring is not substituted onto torch's own shape errors. The new regression module ports the `_make_standin` + halt-point pattern from #109 / Comfy-Org#119 / Comfy-Org#194 and uses `monkeypatch.delattr(torch.nested, 'nested_tensor_from_jagged')` to simulate the missing-API state. Three cells cover (1) missing API → SeedVR2-named `RuntimeError`, (2) present API → expected output shape, (3) malformed offsets → torch `RuntimeError` propagates without SeedVR2 context. Each cell additionally pins the guard at the source level via `inspect.getsource`.
…I name and guard message to module-level constants so the function source has `raise RuntimeError(` before any `nested_tensor_from_jagged` substring; tighten test source-pin to the contract literal `nested_tensor_from_jagged` (no paren). Closes pollockjj/mydevelopment#210 slice 1 rework.
Codex Review — Cycle 1Overall: patch is correct No findings. |
There was a problem hiding this comment.
Pull request overview
This PR adds a SeedVR2-specific runtime guard to comfy.ldm.modules.attention.var_attention_pytorch so environments lacking torch.nested.nested_tensor_from_jagged fail with a clearer, SeedVR2-context RuntimeError, and it introduces unit tests to lock in the new behavior (missing-API error path, present-API output shape, and malformed-offset error propagation).
Changes:
- Add an early guard in
var_attention_pytorchthat raises a SeedVR2-namedRuntimeErrorwhennested_tensor_from_jaggedis not available. - Add a new regression test module covering missing API, normal execution shape, and malformed-offset propagation, plus a source-introspection pin.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
comfy/ldm/modules/attention.py |
Adds a top-of-function capability check and SeedVR2-context error message for missing torch.nested.nested_tensor_from_jagged. |
tests-unit/comfy_test/test_var_attention_pytorch_seedvr2_guard.py |
Adds pytest regressions for the guard behavior and a source-level pin to ensure the guard stays ahead of the nested-tensor calls. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def var_attention_pytorch(q, k, v, heads, cu_seqlens_q, cu_seqlens_k, skip_reshape=False, skip_output_reshape=False): | ||
| if not hasattr(torch.nested, _VAR_ATTENTION_NESTED_API_NAME): | ||
| raise RuntimeError(_VAR_ATTENTION_GUARD_MESSAGE) |
There was a problem hiding this comment.
INVALID — the production guard already handles torch.nested itself being missing. comfy/ldm/modules/attention.py:730 uses _nested = getattr(torch, 'nested', None) and if _nested is None or not hasattr(_nested, _VAR_ATTENTION_NESTED_API_NAME):, so the torch.nested access cannot raise AttributeError before the check. Test test_missing_namespace_raises_seedvr2_runtime_error (line 114) pins this exact case and passes.
| def test_missing_api_raises_seedvr2_runtime_error(monkeypatch): | ||
| monkeypatch.delattr(torch.nested, "nested_tensor_from_jagged") | ||
| q, k, v, heads, cu_q, cu_k, _, _ = _inputs() | ||
|
|
||
| with pytest.raises(RuntimeError, match=r"SeedVR2.*nested_tensor_from_jagged"): | ||
| var_attention_pytorch(q, k, v, heads, cu_q, cu_k) |
There was a problem hiding this comment.
INVALID — both delattr calls in this test (lines 105 and 115) pass raising=False, which silently no-ops if the attribute is already absent. That is precisely the safety behaviour Copilot is asking for; pytest's monkeypatch documents raising=False as the suppression flag for missing-attribute teardown.
| def _assert_guard_source_pin(): | ||
| src = inspect.getsource(var_attention_pytorch) | ||
| assert "raise RuntimeError(" in src, ( | ||
| "var_attention_pytorch source has no `raise RuntimeError(` substring; " | ||
| "the SeedVR2-named guard is missing.\n" | ||
| f"--- source ---\n{src}" | ||
| ) | ||
| raise_idx = src.index("raise RuntimeError(") | ||
| call_idx = src.index("nested_tensor_from_jagged") | ||
| assert raise_idx < call_idx, ( | ||
| "`raise RuntimeError(` appears at index " | ||
| f"{raise_idx} but the first `nested_tensor_from_jagged` substring is " | ||
| f"at index {call_idx}; the guard must precede the unguarded lookup.\n" | ||
| f"--- source ---\n{src}" | ||
| ) |
There was a problem hiding this comment.
INVALID — _assert_guard_source_pin() is AST-based, not substring-based. Lines 74-101 walk ast.parse(textwrap.dedent(inspect.getsource(var_attention_pytorch))) for ast.Raise (with ast.Call of ast.Name == 'RuntimeError') and ast.Attribute (with .attr == 'nested_tensor_from_jagged'), then asserts min(raise_lines) < min(nested_lines). Refactors that split the raise across lines or move the literal into a docstring or comment do not affect the AST walk. The function docstring (lines 60-73) explicitly explains why the AST approach was chosen over substring positions.
| _VAR_ATTENTION_NESTED_API_NAME = "nested_tensor_from_jagged" | ||
| _VAR_ATTENTION_GUARD_MESSAGE = ( | ||
| "SeedVR2 var_attention_pytorch: torch.nested.nested_tensor_from_jagged " | ||
| "is required by this attention path; the installed PyTorch build " | ||
| "does not provide it" | ||
| ) | ||
|
|
||
|
|
||
| def var_attention_pytorch(q, k, v, heads, cu_seqlens_q, cu_seqlens_k, skip_reshape=False, skip_output_reshape=False): |
There was a problem hiding this comment.
NOT-REQUIRED — issue Comfy-Org#210's Acceptance criterion 1 is an explicit OR: "a backend selector is wired ... OR an explicit try: nested_tensor_from_jagged(...) / except AttributeError, RuntimeError guard is added". This PR delivers the guard branch of that OR. Updating the PR title to reflect actual delivery.
Melian PR-Review — Cycle 1 — DECISION: CONTINUE — blocking findings remain.HEAD SHA reviewed: Why we are continuing4 blocking finding(s) remain. The slicer will address them and the next cycle will re-review the resulting head SHA. Blocking findings
Raw signal
Verdict rationale (raw)
This comment is posted by Melian's FSM ( |
Codex Review -- Round 1 -- KICKOFFRunner: Awaiting codex output. The structured result will be posted as a separate comment on this PR when codex exits. |
Codex Review -- Round 1 -- RESULTPR: #48 Overall explanationThe PR adds the missing SeedVR2 guard before any nested-jagged calls and preserves the present-API and malformed-offset behavior. The targeted regression module passes at PR head with 4 tests passing, so I found no blocking correctness issues. Findings (0)No findings. Raw JSON (schema-enforced){
"findings": [],
"overall_confidence_score": 0.89,
"overall_correctness": "patch is correct",
"overall_explanation": "The PR adds the missing SeedVR2 guard before any nested-jagged calls and preserves the present-API and malformed-offset behavior. The targeted regression module passes at PR head with 4 tests passing, so I found no blocking correctness issues."
} |
Plan: Guard SeedVR2 var_attention_pytorch against missing torch.nested.nested_tensor_from_jagged
Overview
comfy.ldm.modules.attention.var_attention_pytorchcallstorch.nested.nested_tensor_from_jaggedunconditionally on lines 731-733 with nohasattrcheck, no version probe, and no try/except. A user on a PyTorch build that does not provide that API hits a bareAttributeError: module 'torch.nested' has no attribute 'nested_tensor_from_jagged'at the first SeedVR2 inference call — confusing error, no SeedVR2 context. This packet adds a small guard at the top ofvar_attention_pytorchthat detects the missing API and raises aRuntimeErrorwhose message identifies SeedVR2 and the required PyTorch capability, plus a regression module that pins (a) the new error class and message on the missing-API path, (b) the present-API path returning the expected output shape, and (c) malformed-offsets propagating the original torchRuntimeErrorwithout SeedVR2 context. The regression module is a structural port of Comfy-Org#119 / Comfy-Org#194's_make_standin+ halt-point pattern, withpytest'smonkeypatch.delattr(torch.nested, 'nested_tensor_from_jagged')substituting for the post-guard sentinel as the missing-API simulation mechanism.Diagnosis Summary
This is a PLAN-mode packet. The bug mechanism is fully documented in the issue body's Executive Summary and the Comfy-Org#184 post-mortem (commit
11f3a183, PR Comfy-Org#211). No investigation slice is needed; the fix shape is dictated by the issue body's## Acceptance Intentcells and the_make_standinprecedent from #109 / Comfy-Org#119 / Comfy-Org#194.§Failure Signature
Three cells, each captured live from the unguarded
var_attention_pytorchbody onpollockjj/ComfyUI@98df2309(comfy/ldm/modules/attention.py:722-744). Cell labels A1–A3 are referenced verbatim by ACPre-fix-fingerprint:sub-bullets.torch.nested.nested_tensor_from_jaggedis absent (simulated viamonkeypatch.delattr(torch.nested, 'nested_tensor_from_jagged')per GTP-6/GTP-7):var_attention_pytorch(q, k, v, heads, cu_seqlens_q, cu_seqlens_k)raises bareAttributeError("module 'torch.nested' has no attribute 'nested_tensor_from_jagged'")atcomfy/ldm/modules/attention.py:731q = torch.nested.nested_tensor_from_jagged(q, offsets=cu_seqlens_q.long()). The exception class isAttributeError(notRuntimeError) and the message contains neitherSeedVR2nor any SeedVR2-context hint. This is the failure mode the post-mortem identified.torch.nested.nested_tensor_from_jaggedis present (the default ontorch 2.11.0+cu130, GTP-1):var_attention_pytorch(q, k, v, heads, cu_seqlens_q, cu_seqlens_k)with valid 2-Dq,k,vof shape(total_tokens=6, embed_dim=heads*head_dim=16)and matchingcu_seqlens_q = cu_seqlens_k = torch.tensor([0, 3, 6], dtype=torch.int32)runs the full computation and returns a tensor of shape(6, 16)(verified live by GTP-3). Pre-guard, this is the canonical SeedVR2-inference shape; the guard MUST NOT false-positive on this path.torch.nested.nested_tensor_from_jaggedis present (default),cu_seqlens_qis malformed (off-end againstq.shape[0], captured live as the casecu_seqlens_q = torch.tensor([0, 3, 7], dtype=torch.int32)againstq.shape[0]==6per GTP-4):var_attention_pytorch(...)propagatesRuntimeError("split_with_sizes expects split_sizes to sum exactly to 6 (input tensor's size at dimension 0), but got split_sizes=[3, 4]")from insidetorch.nested.nested_tensor_from_jagged. TheRuntimeErrororiginates inside torch (builtins.RuntimeErrorper GTP-4), the message does NOT containSeedVR2. Pre-guard this is the legitimate-torch-error path; the guard MUST NOT swallow it as the missing-API case (the SeedVR2-context message MUST NOT replace the original torch message here).§Failure Boundary
torch.nested.nested_tensor_from_jaggedis unavailable: PyTorch builds that predate the API's introduction; nightly builds where the prototype API is gated behind a build flag; experimental forks that strip the nested-tensor module. The post-mortem identifies this as the "first SeedVR2 inference call" failure mode.torch.nested.nested_tensor_from_jaggedis present (the slot venv attorch 2.11.0+cu130per GTP-1, and every supported SeedVR2 production environment).cu_seqlens_qorcu_seqlens_k(off-end, non-monotonic, or size-mismatched againstq.shape[0]); torch raises the legitimate per-callRuntimeError.attention_xformers,attention_sage,attention3_sage,attention_flash,attention_pytorch,attention_split,attention_sub_quad,attention_basic); they do NOT calltorch.nested.nested_tensor_from_jaggedand are out of scope per## Non-Goals. The guard is added only insidevar_attention_pytorch.§Root Cause
var_attention_pytorch(definedcomfy/ldm/modules/attention.py:722) callstorch.nested.nested_tensor_from_jaggedunconditionally three times (lines 731, 732, 733) with no presence check, no try/except, and no SeedVR2-context error message. The hard aliasoptimized_var_attention = var_attention_pytorch(line 745) does NOT participate in the runtime selector cascade on lines 748-767 (sibling attention paths are selected viaif model_management.X_enabled(): optimized_attention = attention_Xchains; no equivalent chain exists forvar_attention). Consequently a user on a PyTorch build without the API hits a bareAttributeErrorwith no SeedVR2 context. The post-mortem combinedhas_fallback_path: NO+fallback_pattern_matches_siblings: NOverdicts are the source of this packet.§Proposed Fix
One surgical change on
comfy/ldm/modules/attention.py:var_attention_pytorch(immediately afterdef var_attention_pytorch(...)and BEFOREif not skip_reshape:) — add a guard whose effect is: iftorch.nested.nested_tensor_from_jaggedis absent, raiseRuntimeErrorwhose message contains BOTH the substringSeedVR2and the substringnested_tensor_from_jagged. The guard MUST be placed before the first existing executable line of the function body (currentlyif not skip_reshape:at line 723) so that no torch operation runs before the API check. Two viable implementation shapes — both pass the AC matchers:hasattr-based entry guard:try/except-based call guard wrapping the threetorch.nested.nested_tensor_from_jaggedcalls:The exact wording of the guard message is a slicer-implementation detail, but the
RuntimeErrormessage MUST contain (a) the substringSeedVR2and (b) the substringnested_tensor_from_jagged. The slicer MAY choose either implementation shape; the AC matchers are implementation-agnostic because both paths yield the same observable on every cell (Cell A1 → SeedVR2-contextRuntimeError; Cell A2 → tensor of shape(6, 16); Cell A3 → torchRuntimeErrorpropagates withsplit_with_sizessubstring and withoutSeedVR2).§Verification Strategy
A new test module
tests-unit/comfy_test/test_var_attention_pytorch_seedvr2_guard.pyports the Comfy-Org#119 / Comfy-Org#194 pattern: an_inputs()helper builds the canonical 2-D(q, k, v)plus matchingcu_seqlens_q/cu_seqlens_kper GTP-3; threepytest-style test cells exercise the three Failure-Signature cells; each cell additionally assertsinspect.getsource(var_attention_pytorch)contains the substringraise RuntimeError(whose first occurrence appears BEFORE the first occurrence ofnested_tensor_from_jagged(the AST source-pin makes each AC individually diagnostic — pre-fix the unguarded source has noraise RuntimeError(substring, so the source-pin assertion fails for every cell on the base SHA). ACs are evaluated bypytest -qexit code on independent pre-fix and post-fix source checkouts; pre-fix logs MUST exit non-zero (AC-1 cell fails on the matcher mismatch againstAttributeError; AC-2 / AC-3 cells fail on the AST source-pin assertion); post-fix logs MUST exit 0 (all three cells pass).Affected Repositories
pollockjj/ComfyUI:issue_210is created fromorigin/issue_101HEAD98df2309; deliverable PR targetsissue_101via--deliverable-base issue_101.pollockjj/mydevelopment:issue_210is created fromorigin/mainHEAD11f3a183; bookkeeping PR targetsmainand carries the plan, gate outputs, post-mortem, and committed pre/post evidence logs undergithub_issues/210/slice1/.Research and Methodology
Plan Foundations Comment URL: https://github.com/pollockjj/mydevelopment/issues/210#issuecomment-4383394124
Detected Scope:
none— port of the internal_make_standin+unittest.mock.patch.objecthalt-point regression pattern established by #109 (tests-unit/comfy_test/seedvr_model_test.py) and reused by Comfy-Org#119 / Comfy-Org#194 (tests-unit/comfy_test/test_diffusers_metadata_guard.py); the existing precedent IS the spec for how this guard's regression module is shaped, and the issue body'sAcceptance IntentIS the spec for the production guard text.The linked comment is the load-bearing artifact for every measurement, equivalence rule, canonical-protocol, tool version, and pipeline-stage citation in this plan. This plan defines no measurable output, no metric, and no equivalence rule against a published anchor; the change class is "API-misuse hygiene guard" with binary
RuntimeError-and-substring assertions per AC. No AC below cites a metric, threshold, equivalence rule, or canonical protocol; therefore no AC carries aFoundations-pin:for R&M sub-check 6 (the per-AC scope is N/A across all ACs).Tools, Pipeline, and Measurements
Plan Foundations Comment URL: https://github.com/pollockjj/mydevelopment/issues/210#issuecomment-4383394124
The linked comment's
## Tools, Pipeline, and Measurementssection enumerates: (1) Existing Tooling Inventory withREUSEstatus for every dep (pytest,comfy.cli_args.args,unittest.mock,pytest.MonkeyPatch,inspect.getsource,torch.nested.nested_tensor_from_jagged,comfy.ldm.modules.attention.var_attention_pytorch); (2) Pipeline = single pytest invocation againsttests-unit/comfy_test/test_var_attention_pytorch_seedvr2_guard.py(no data pipeline; hygiene-class change); (3) Measurements = N/A (no metric, no anchor, binary assertions only); (4) Single-probe-before-sweep = N/A (3 enumerated cells, not a sweep); (5) Boundary-bracketing-before-verdict = N/A (no characterization verdict; binary observables per cell). NoKNOWN-GOOD-REFrows requiring resolution. Every AC below uses pytest as the standard test runner andcomfy.ldm.modules.attentionas the unmodified-vs-modified surface under test; neither constitutes citing a foundations entry for sub-check-6 purposes per the Phase 3 schema definition (Foundations-pin:is required iff the AC text cites the foundations comment for an equivalence rule, metric definition, canonical protocol, tooling decision, or measurement specification — none of which appear in the AC bodies below). Therefore no AC carries aFoundations-pin:for TPM sub-check 6.Ground Truth Probes
Probes were run live on
2026-05-05againstpollockjj/ComfyUI@98df2309(origin/issue_101 HEAD; identical to local7936e591forcomfy/ldm/modules/attention.pypergit diffreturning no output). The Python interpreter for every probe is/home/johnj/dev_master/ComfyUI/.venv/bin/python(Python 3.12.13,torch 2.11.0+cu130).cd /home/johnj/dev_master/ComfyUI && .venv/bin/python -c "import torch; print('torch:', torch.__version__); print('has nested_tensor_from_jagged:', hasattr(torch.nested, 'nested_tensor_from_jagged')); print('callable:', callable(torch.nested.nested_tensor_from_jagged))"torch: 2.11.0+cu130/has nested_tensor_from_jagged: True/callable: Truecd /home/johnj/dev_master/ComfyUI && .venv/bin/python -c "import sys; sys.path.insert(0, '.'); from comfy.cli_args import args; import torch; args.cpu = True if not torch.cuda.is_available() else args.cpu; import inspect; import comfy.ldm.modules.attention as A; print('var_attention_pytorch in module:', hasattr(A, 'var_attention_pytorch')); print('signature:', inspect.signature(A.var_attention_pytorch)); print('optimized_var_attention is var_attention_pytorch:', A.optimized_var_attention is A.var_attention_pytorch); print('module path:', A.__file__)"var_attention_pytorch in module: True/signature: (q, k, v, heads, cu_seqlens_q, cu_seqlens_k, skip_reshape=False, skip_output_reshape=False)/optimized_var_attention is var_attention_pytorch: True/module path: /home/johnj/dev_master/ComfyUI/comfy/ldm/modules/attention.pycd /home/johnj/dev_master/ComfyUI && .venv/bin/python -c "from comfy.cli_args import args; import torch; args.cpu = True if not torch.cuda.is_available() else args.cpu; import comfy.ldm.modules.attention as A; heads, head_dim, total_tokens = 2, 8, 6; embed_dim = heads * head_dim; q = torch.randn(total_tokens, embed_dim); k = torch.randn(total_tokens, embed_dim); v = torch.randn(total_tokens, embed_dim); cu = torch.tensor([0, 3, 6], dtype=torch.int32); out = A.var_attention_pytorch(q, k, v, heads, cu, cu); print('out.shape:', tuple(out.shape)); print('expected: ({}, {})'.format(total_tokens, heads * head_dim))"out.shape: (6, 16)/expected: (6, 16)(UserWarning about prototype-API printed to stderr; not an exception)cd /home/johnj/dev_master/ComfyUI && .venv/bin/python -c "from comfy.cli_args import args; import torch; args.cpu = True if not torch.cuda.is_available() else args.cpu; import comfy.ldm.modules.attention as A; q = torch.randn(6, 16); k = torch.randn(6, 16); v = torch.randn(6, 16); cu_q = torch.tensor([0, 3, 7], dtype=torch.int32); cu_k = torch.tensor([0, 3, 6], dtype=torch.int32); \ntry:\n A.var_attention_pytorch(q, k, v, 2, cu_q, cu_k)\n print('NO RAISE')\nexcept Exception as e:\n print('Exception type:', type(e).__name__); print('Exception module:', type(e).__module__); print('Exception msg head:', str(e)[:200])"Exception type: RuntimeError/Exception module: builtins/Exception msg head: split_with_sizes expects split_sizes to sum exactly to 6 (input tensor's size at dimension 0), but got split_sizes=[3, 4]cd /home/johnj/dev_master/ComfyUI && .venv/bin/python -c "import torch.nested; print('before:', hasattr(torch.nested, 'nested_tensor_from_jagged')); saved = torch.nested.nested_tensor_from_jagged; delattr(torch.nested, 'nested_tensor_from_jagged'); print('after delattr:', hasattr(torch.nested, 'nested_tensor_from_jagged')); torch.nested.nested_tensor_from_jagged = saved; print('after restore:', hasattr(torch.nested, 'nested_tensor_from_jagged'))"before: True/after delattr: False/after restore: Truecd /home/johnj/dev_master/ComfyUI && .venv/bin/python -c "import _pytest.monkeypatch as mp; import torch.nested; m = mp.MonkeyPatch(); print('before:', hasattr(torch.nested, 'nested_tensor_from_jagged')); m.delattr(torch.nested, 'nested_tensor_from_jagged'); print('after delattr:', hasattr(torch.nested, 'nested_tensor_from_jagged')); m.undo(); print('after undo:', hasattr(torch.nested, 'nested_tensor_from_jagged'))"before: True/after delattr: False/after undo: Truecd /home/johnj/dev_master/ComfyUI && .venv/bin/python -c "from comfy.cli_args import args; import torch; args.cpu = True if not torch.cuda.is_available() else args.cpu; import inspect; import comfy.ldm.modules.attention as A; src = inspect.getsource(A.var_attention_pytorch); print('full source length:', len(src), 'chars'); print('first nested_tensor_from_jagged at index:', src.index('nested_tensor_from_jagged')); print('contains raise RuntimeError(:', 'raise RuntimeError(' in src)"full source length: 996 chars/first nested_tensor_from_jagged at index: 445/contains raise RuntimeError(: Falsecd /home/johnj/dev_master/ComfyUI && git rev-parse origin/issue_10198df2309dc91c8de346fa74579d50c54595fc6d8cd /home/johnj/dev_master/mydevelopment && git rev-parse origin/main11f3a18391c2d048b2a747a6aa9d837129f2f87ecd /home/johnj/dev_master/ComfyUI && git show origin/issue_101:comfy/ldm/seedvr/model.py | grep -n -E "var_attention|optimized_var_attention|nested_tensor_from_jagged" | head -1011:from comfy.ldm.modules.attention import optimized_var_attention/807: out = optimized_var_attention(Created Surface Contract
The plan introduces literals that do not exist on the base branch. These are anchored here (not in
## Ground Truth Probes) because they will be created by Slice 1 itself; their post-creation existence is verified by the slice's own ACs.tests-unit/comfy_test/test_var_attention_pytorch_seedvr2_guard.py(new test module)pollockjj/ComfyUI:issue_210_inputs(helper in new test module producing the canonical(q, k, v, heads, cu_seqlens_q, cu_seqlens_k, total_tokens, embed_dim)tuple per GTP-3)test_missing_api_raises_seedvr2_runtime_error(test fn — AC-1 cell; usesmonkeypatch.delattr(torch.nested, 'nested_tensor_from_jagged')per GTP-6 to simulate the absent-API state)test_present_api_returns_expected_shape(test fn — AC-2 cell; callsvar_attention_pytorchwith valid inputs and assertsout.shape == (total_tokens, heads * head_dim))test_malformed_offsets_propagates_torch_runtime_error(test fn — AC-3 cell; callsvar_attention_pytorchwith off-endcu_seqlens_q = torch.tensor([0, 3, 7], dtype=torch.int32)per GTP-4, asserts torchRuntimeErrorpropagates withsplit_with_sizessubstring ANDSeedVR2substring is NOT in the message)inspect.getsource(var_attention_pytorch)containsraise RuntimeError(whose first occurrence appears BEFORE the first occurrence ofnested_tensor_from_jagged. This pin is the per-AC diagnostic anchor: pre-fix the source containsnested_tensor_from_jaggedbut noraise RuntimeError((GTP-7 confirmscontains raise RuntimeError(: False); the assertion fails on the base SHA. Post-fix the new guard'sraise RuntimeError(substring appears before the first existingnested_tensor_from_jaggedcall.comfy/ldm/modules/attention.pyvar_attention_pytorch(1 guard, raisingRuntimeErrorwhose message contains substringSeedVR2ANDnested_tensor_from_jagged; placed before the function's existing first executable lineif not skip_reshape:at base-line 723)pollockjj/ComfyUI:issue_210github_issues/210/slice1/<test>_pre.log(one per AC)98df2309with the new test file dropped in but the production guard absent)github_issues/210/slice1/<test>_post.log(one per AC)pollockjj/ComfyUI:issue_210with both the production guard and the test module applied)Asset Readiness Inventory
comfy/ldm/modules/attention.py(unguarded base)pollockjj/ComfyUI@98df2309:comfy/ldm/modules/attention.pyvar_attention_pytorchbody at lines 722-744 matches §Failure Signature cell sites at 731-733 (probes GTP-2 / GTP-7); pre-fix opaque-AttributeError reproduced live in Phase 0.5 dry-run via GTP-5/GTP-6tests-unit/comfy_test/seedvr_model_test.py(#109 precedent)pollockjj/ComfyUI@98df2309:tests-unit/comfy_test/seedvr_model_test.pyissue_101;_make_standinborrow pattern at function_make_standin(positive_conditioning)is the structural template for this packet's helper layouttests-unit/comfy_test/test_diffusers_metadata_guard.py(Comfy-Org#119 / Comfy-Org#194 precedent — primary structural template)pollockjj/ComfyUI@98df2309:tests-unit/comfy_test/test_diffusers_metadata_guard.pyissue_101;_make_standin+unittest.mock.patch.objecthalt-point pattern is the structural template (this packet substitutesmonkeypatch.delattrfor the post-guard sentinel as the missing-API simulation mechanism)/home/johnj/dev_master/ComfyUI/.venvdev_masteron prosochetorch 2.11.0+cu130withtorch.nested.nested_tensor_from_jaggedcallable per GTP-1;pytest.MonkeyPatch.delattr/undocycle works per GTP-6tests-unit/comfy_test/test_var_attention_pytorch_seedvr2_guard.py(new test module)pollockjj/ComfyUI:issue_210:tests-unit/comfy_test/test_var_attention_pytorch_seedvr2_guard.pygit ls-tree origin/issue_101 -- tests-unit/comfy_test/test_var_attention_pytorch_seedvr2_guard.pyreturns no output); created by Slice 1 implementation commit; AC-1/AC-2/AC-3 verify post-creation behaviorvar_attention_pytorchpollockjj/ComfyUI:issue_210:comfy/ldm/modules/attention.pycontains raise RuntimeError(: False; created by Slice 1 implementation commit; AC-1/AC-2/AC-3 verify post-fix behavior via behavioral matchers and AST source-pinpollockjj/mydevelopment:issue_210:github_issues/210/slice1/<test>_{pre,post}.logSlices
Slice 1: Convert opaque AttributeError on missing torch.nested.nested_tensor_from_jagged into a SeedVR2-named RuntimeError
Kind: implementation
Objective: Prove that
var_attention_pytorchraises aRuntimeErrorwhose message contains bothSeedVR2andnested_tensor_from_jaggedwhentorch.nested.nested_tensor_from_jaggedis absent (the missing-API path), AND that the present-API path returns the expected output shape (the canonical SeedVR2-inference path is unchanged), AND that malformedcu_seqlenspropagates the original torchRuntimeErrorwithout SeedVR2-context substitution (the legitimate-torch-error path is unchanged).Acceptance Criteria
AC-1:
cd /home/johnj/dev_master/ComfyUI && python3 -m pytest -q tests-unit/comfy_test/test_var_attention_pytorch_seedvr2_guard.py::test_missing_api_raises_seedvr2_runtime_errorexits 0 on the post-fixpollockjj/ComfyUI:issue_210checkout AND exits non-zero on the pre-fixorigin/issue_101(98df2309) worktree with the same test module dropped in (the test'spytest.raises(RuntimeError, match=r"SeedVR2.*nested_tensor_from_jagged")matcher fails to match the bareAttributeErrorfrom the unguardedtorch.nested.nested_tensor_from_jaggedlookup; AND the AST source-pin assertion'raise RuntimeError(' in inspect.getsource(var_attention_pytorch)fails because the unguarded source contains no such substring per GTP-7) — verified by committed artifactsgithub_issues/210/slice1/test_missing_api_raises_seedvr2_runtime_error_pre.logANDgithub_issues/210/slice1/test_missing_api_raises_seedvr2_runtime_error_post.log, each containing the verbatim pytest invocation, full stdout, and final lineexit code: <N>.origin/issue_101@98df2309the unguardedvar_attention_pytorchbody has no presence check; withmonkeypatch.delattr(torch.nested, 'nested_tensor_from_jagged')removing the attribute (per GTP-5 / GTP-6 confirmingdelattrsucceeds andhasattrreturnsFalse), the call site atcomfy/ldm/modules/attention.py:731raises bareAttributeError("module 'torch.nested' has no attribute 'nested_tensor_from_jagged'"); the matcher requiresRuntimeErrorand does NOT acceptAttributeError(the two classes are unrelated in the exception hierarchy); the test FAILS with theAttributeErrorpropagating throughpytest.raises(RuntimeError). Additionally, the AST source-pin assertion fails because per GTP-7 the unguarded source hascontains raise RuntimeError(: False(Diagnosis Summary §Failure Signature Cell A1).var_attention_pytorchraisesRuntimeErrorwhosestr(exc.value)contains both substringsSeedVR2ANDnested_tensor_from_jagged(regardless of whether the slicer chose thehasattr-based entry guard or thetry/except-based call guard per §Proposed Fix); the matcher matches; ANDinspect.getsource(var_attention_pytorch)containsraise RuntimeError(whose first occurrence appears BEFORE the first occurrence ofnested_tensor_from_jagged; the test PASSES with exit 0.var_attention_pytorchraises aRuntimeErrorwhose message contains bothSeedVR2andnested_tensor_from_jaggedwhentorch.nested.nested_tensor_from_jaggedis absent (the missing-API path), AND that the present-API path returns the expected output shape (the canonical SeedVR2-inference path is unchanged), AND that malformedcu_seqlenspropagates the original torchRuntimeErrorwithout SeedVR2-context substitution (the legitimate-torch-error path is unchanged)."AC-2:
cd /home/johnj/dev_master/ComfyUI && python3 -m pytest -q tests-unit/comfy_test/test_var_attention_pytorch_seedvr2_guard.py::test_present_api_returns_expected_shapeexits 0 on the post-fixpollockjj/ComfyUI:issue_210checkout AND exits non-zero on the pre-fixorigin/issue_101(98df2309) worktree with the same test module dropped in — the test asserts BOTH (a)var_attention_pytorch(q, k, v, heads=2, cu_seqlens_q=torch.tensor([0,3,6], dtype=torch.int32), cu_seqlens_k=torch.tensor([0,3,6], dtype=torch.int32))with valid 2-Dq,k,vof shape(6, 16)returns a tensor ofout.shape == (6, 16)(matching(total_tokens, heads * head_dim)per §Proposed Fix Cell A2 + GTP-3); AND (b) the AST source-pin assertion'raise RuntimeError(' in inspect.getsource(var_attention_pytorch) and inspect.getsource(var_attention_pytorch).index('raise RuntimeError(') < inspect.getsource(var_attention_pytorch).index('nested_tensor_from_jagged')holds. On the pre-fix base SHA assertion (a) passes (the unguarded computation already produces shape(6, 16)per GTP-3) but assertion (b) FAILS because the unguarded source has noraise RuntimeError(substring per GTP-7; the conjunction fails → exit non-zero — verified by committed artifactsgithub_issues/210/slice1/test_present_api_returns_expected_shape_pre.logANDgithub_issues/210/slice1/test_present_api_returns_expected_shape_post.log.origin/issue_101@98df2309inspect.getsource(var_attention_pytorch)does NOT contain the substringraise RuntimeError((probe GTP-7 capturedcontains raise RuntimeError(: Falseon the live module); the AST source-pin conjoined assertion FAILS withAssertionError. The behavioral shape assertion(a)passes vacuously (the unguarded computation already produces the correct shape per GTP-3), but the conjoint test FAILS at the source-pin step (Diagnosis Summary §Failure Signature Cell A2 + §Root Cause).inspect.getsource(var_attention_pytorch)containsraise RuntimeError(whose first occurrence appears BEFORE the first occurrence ofnested_tensor_from_jagged; the AST source-pin assertion PASSES; AND the behavioral shape assertion(a)continues to hold (the new guard does NOT false-positive on the present-API path becausetorch.nested.nested_tensor_from_jaggedis callable on the slot venv per GTP-1, so the guard'shasattrcheck ortry/exceptreturns control to the original computation); the conjoint test PASSES with exit 0.var_attention_pytorchraises aRuntimeErrorwhose message contains bothSeedVR2andnested_tensor_from_jaggedwhentorch.nested.nested_tensor_from_jaggedis absent (the missing-API path), AND that the present-API path returns the expected output shape (the canonical SeedVR2-inference path is unchanged), AND that malformedcu_seqlenspropagates the original torchRuntimeErrorwithout SeedVR2-context substitution (the legitimate-torch-error path is unchanged)."AC-3:
cd /home/johnj/dev_master/ComfyUI && python3 -m pytest -q tests-unit/comfy_test/test_var_attention_pytorch_seedvr2_guard.py::test_malformed_offsets_propagates_torch_runtime_errorexits 0 on the post-fixpollockjj/ComfyUI:issue_210checkout AND exits non-zero on the pre-fixorigin/issue_101(98df2309) worktree with the same test module dropped in — the test asserts BOTH (a) callingvar_attention_pytorch(q, k, v, heads=2, cu_seqlens_q=torch.tensor([0,3,7], dtype=torch.int32), cu_seqlens_k=torch.tensor([0,3,6], dtype=torch.int32))(off-endcu_seqlens_q[-1]==7againstq.shape[0]==6per GTP-4) raisesRuntimeErrorwhosestr(exc.value)contains the substringsplit_with_sizesAND'SeedVR2' not in str(exc.value)(the original torch error propagates without SeedVR2-context substitution); AND (b) the AST source-pin assertion'raise RuntimeError(' in inspect.getsource(var_attention_pytorch) and inspect.getsource(var_attention_pytorch).index('raise RuntimeError(') < inspect.getsource(var_attention_pytorch).index('nested_tensor_from_jagged')holds. On the pre-fix base SHA assertion (a) passes (the unguarded computation already propagates the torchRuntimeErrorper GTP-4 with noSeedVR2substring anywhere in the codebase), but assertion (b) FAILS because the unguarded source has noraise RuntimeError(substring per GTP-7; the conjunction fails → exit non-zero — verified by committed artifactsgithub_issues/210/slice1/test_malformed_offsets_propagates_torch_runtime_error_pre.logANDgithub_issues/210/slice1/test_malformed_offsets_propagates_torch_runtime_error_post.log.origin/issue_101@98df2309inspect.getsource(var_attention_pytorch)does NOT contain the substringraise RuntimeError((probe GTP-7); the AST source-pin conjoined assertion FAILS withAssertionError. The behavioral assertion(a)passes vacuously (the unguarded computation already propagatesRuntimeError("split_with_sizes expects split_sizes to sum exactly to 6...")from torch per GTP-4, and the substringSeedVR2is not in any pre-fix code path), but the conjoint test FAILS at the source-pin step (Diagnosis Summary §Failure Signature Cell A3 + §Root Cause).inspect.getsource(var_attention_pytorch)containsraise RuntimeError(whose first occurrence appears BEFORE the first occurrence ofnested_tensor_from_jagged; the AST source-pin assertion PASSES; AND the behavioral assertion(a)continues to hold — callingvar_attention_pytorchwith off-endcu_seqlens_qpropagates the torchRuntimeErrorcontainingsplit_with_sizessubstring, AND'SeedVR2' not in str(exc.value)because the new guard fires only on the missing-API path (AttributeErrorfromtorch.nested.nested_tensor_from_jaggedlookup), not on torch's own per-call shape errors; the conjoint test PASSES with exit 0.var_attention_pytorchraises aRuntimeErrorwhose message contains bothSeedVR2andnested_tensor_from_jaggedwhentorch.nested.nested_tensor_from_jaggedis absent (the missing-API path), AND that the present-API path returns the expected output shape (the canonical SeedVR2-inference path is unchanged), AND that malformedcu_seqlenspropagates the original torchRuntimeErrorwithout SeedVR2-context substitution (the legitimate-torch-error path is unchanged)."Constraints
cd /home/johnj/dev_master/ComfyUIto the slot venv at.venv/bin/python, Python 3.12.13)python3 -m pytest -q tests-unit/comfy_test/test_var_attention_pytorch_seedvr2_guard.py(no debug harness; nodebug/run_debug.pyinvocation; this is a unit-test-only packet)--use-process-isolation, no--disable-cuda-malloc; the test importsvar_attention_pytorchdirectly fromcomfy.ldm.modules.attentionwithcomfy.cli_args.args.cpu = Trueset before import per Add SeedVR2 support #109/UserWarning bug ? Comfy-Org/ComfyUI#119/Use a global random seed for all samplers Comfy-Org/ComfyUI#194 precedent)/home/johnj/dev_master/ComfyUI/.venv)pkill, norm -rf, nopython main.pyissue_210, not on base branches: deliverable commit onpollockjj/ComfyUI:issue_210(created fromorigin/issue_101@98df2309); bookkeeping commit onpollockjj/mydevelopment:issue_210(created fromorigin/main@11f3a183)pytest(REUSE) — same harness as Add SeedVR2 support #109/UserWarning bug ? Comfy-Org/ComfyUI#119/Use a global random seed for all samplers Comfy-Org/ComfyUI#194 precedent regression modules.comfy.cli_args.args(REUSE) —args.cpu = Truetoggle for CPU-only test runs; same shape as Add SeedVR2 support #109/UserWarning bug ? Comfy-Org/ComfyUI#119/Use a global random seed for all samplers Comfy-Org/ComfyUI#194 precedent.unittest.mock+pytest.MonkeyPatch(REUSE) — stdlib + pytest-builtin;monkeypatch.delattr(torch.nested, 'nested_tensor_from_jagged')substitutes for UserWarning bug ? Comfy-Org/ComfyUI#119/Use a global random seed for all samplers Comfy-Org/ComfyUI#194'sunittest.mock.patch.object(...)halt-point sentinel as the missing-API simulation mechanism (per GTP-6).inspect.getsource(REUSE) — stdlib; AST source-pin assertions per Add SeedVR2 support #109 precedent'sinspect.getsource(NaDiT.forward)shape.torch.nested.nested_tensor_from_jagged(REUSE) — the existing API the unguarded code path calls; GTP-1 confirms callable on the slot venv attorch 2.11.0+cu130.comfy.ldm.modules.attention.var_attention_pytorch(REUSE) — the production function under modification; GTP-2 confirms signature and aliasing tooptimized_var_attention._pre.logAND_post.logare required._pre.logshows source-pin failure at base SHA,_post.logshows source-pin pass after the guard is added.git stashor external decision deferral..gitignorerules for safe generated bulk, or discarded generated debris.git status --shortgates locally and must refuse to submit while any touched repo is dirty.repo_hygiene.txt, rawgit status --shorttranscripts, orCLEANmarkers as QA evidence.Out of Scope
comfy/ldm/seedvr/model.pyor the call site at:807(the import ofoptimized_var_attentionand its invocation are unchanged; onlyvar_attention_pytorchbody incomfy/ldm/modules/attention.pyis modified).attention_xformers,attention_sage,attention3_sage,attention_flash,attention_pytorch,attention_split,attention_sub_quad,attention_basic).var_attention_pytorch's math (thenested_tensor_from_jaggedcalls, the transpose /comfy.ops.scaled_dot_product_attention/ reshape sequence, and theskip_reshape/skip_output_reshapeparameter handling are preserved verbatim).comfy/ldm/modules/attention.pyto select toward; adding one is out of scope per the issue body's## Non-Goals.#184review document's verdicts or the post-mortem in PR Is it possible to cancel caching of the final result to to generate with the same parameters? Comfy-Org/ComfyUI#211. The historical record stays as-is; the new behaviour is recorded in this issue's own evidence files undergithub_issues/210/slice1/.optimized_var_attention(the alias atattention.py:745remains a hard alias tovar_attention_pytorch; no selector cascade is added).attention.pyare ignorable; only newly-changed lines warrant disposition).Tracked in pollockjj/mydevelopment Comfy-Org#210 (bookkeeping PR pollockjj/mydevelopment#213).