Skip to content

Conversation

lukamac
Copy link
Collaborator

@lukamac lukamac commented Oct 17, 2025

Current aliasing is basically a bug. Instead of actually recording for each buffer who it's aliases are, it records all the aliases to all the buffers due to this line:

    def __init__(self, name: str = '', shape = [1], alias_of: Optional[List[str]] = []):

Did you spot the bug?

_Solution_

Ding! Ding! Ding! it's the alias_of initialization:

    def __init__(self, name: str = '', shape = [1], alias_of: Optional[List[str]] = []):
                                                                                    ^^

Every buffer got the same instance of a list, which meant every time any buffer added something to it's alias_of it got added to all of them.

This PR gives a quick, not-super-optimal, but working, solution to the problem by implementing the aliasing information basically as a doubly-linked list. On a reshape, both input and output record each others name, and to check whether an alias is alive, we just traverse the aliasing double-linked list in all the directions with a bfs.

Imo for our current cases this is good enough performance-wise since there usually aren't so many aliasing chains happening, so I don't want to invest more time in the performance aspect.

As a bonus, I sprinkled a few refactors in there, the removal of bitrotten fromVariableBuffers, refactor of hoistConstant to not use fromVariableBuffer (be careful with those shapes!), and refactor of TransientBuffer's __init__ function to use its super's one (reuse and all that).

Changed

  • Removed fromVariableBuffer
  • Refactored hoistConstant
  • Refactored TransientBuffer's __init__

Fixed

  • Fixed aliasing

PR Merge Checklist

  1. The PR is rebased on the latest devel commit and pointing to devel.
  2. Your PR reviewed and approved.
  3. All checks are passing.
  4. The CHANGELOG.md file has been updated.
  5. If the docker was modified, change back its link after review.

Copy link
Contributor

coderabbitai bot commented Oct 17, 2025

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Resolved aliasing inconsistencies affecting memory management.
  • Improvements

    • Enhanced memory deallocation safety through improved dependency tracking.
    • Optimized alias and buffer management for better performance.
    • Streamlined internal processing logic for increased reliability.

Walkthrough

Refactors buffer alias tracking and related APIs: renames and reimplements ancestor-liveliness checks to alias-based traversal, converts VariableBuffer alias storage to a set, updates hoistConstant to accept gs.Constant, and simplifies reshape parser/template alias wiring and allocation deallocation gating. (49 words)

Changes

Cohort / File(s) Summary
Memory Allocation Logic
Deeploy/CommonExtensions/CodeTransformationPasses/MemoryAllocation.py
Replaced deallocation guard call from buffer.has_live_ancestors(ctxt = ctxt) to buffer.has_live_aliases(ctxt), updating the check to use the new alias-based method name and positional argument.
Core Buffer & Types
Deeploy/DeeployTypes.py
Large refactor: VariableBuffer.__init__ signature changed (alias_of: Optional[List[str]] = []aliases: Optional[List[str]] = None), internal alias storage changed from List[str] to Set[str], has_live_ancestors renamed/reimplemented as has_live_aliases (BFS over aliases), removed legacy alias helpers, and NetworkContext.hoistConstant signature/behavior changed to accept gs.Constant and create/register ConstantBuffer directly.
Parser Simplification
Deeploy/Targets/Generic/Parsers.py
ReshapeParser.__init__ removed; parseNodeCtxt simplified to map inputs→data_in and outputs→data_out in a compact loop and return (ctxt, True), removing earlier alias/size bookkeeping.
Template Alias Wiring
Deeploy/Targets/Generic/Templates/ReshapeTemplate.py
Added VariableBuffer import; replaced inBuffer/outBuffer use with bufferIn/bufferOut and runtime VariableBuffer assertions; replaced a single-direction alias assignment with mutual alias updates (bufferIn.aliases and bufferOut.aliases).
Changelog
CHANGELOG.md
Added entries noting removed fromVariableBuffer, refactored hoistConstant, refactored TransientBuffer.__init__, and an aliasing fix.

Sequence Diagram(s)

sequenceDiagram
    participant MMG as MemoryManagementGeneration
    participant VB as VariableBuffer
    participant CTX as NetworkContext

    rect #E8F6FF
    MMG->>VB: request deallocation check (buffer)
    VB->>VB: has_live_aliases(ctxt)  -- BFS over aliases
    alt aliases live
        VB-->>MMG: True (do not deallocate)
    else no live aliases
        VB-->>MMG: False (safe to deallocate)
    end
    end
Loading
sequenceDiagram
    participant Parser as ReshapeParser
    participant Template as ReshapeTemplate
    participant VB as VariableBuffer

    Parser->>Template: parseNodeCtxt -> operatorRepresentation (inputs→data_in, outputs→data_out)
    Template->>VB: assert input/output are VariableBuffer
    Template->>VB: bufferIn.aliases += bufferOut.name
    Template->>VB: bufferOut.aliases += bufferIn.name
    Note right of VB: bidirectional alias linkage established
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The PR title "Fix aliasing" is directly and specifically related to the main objective of the changeset. The raw summary confirms this is the primary change—fixing a critical bug where all buffers shared the same alias_of list instance due to a mutable default argument in VariableBuffer's __init__. The title is concise, clear, and specific enough that a team member scanning the history would immediately understand the primary change without ambiguity.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
Description Check ✅ Passed The PR description is clearly related to the changeset and provides meaningful context about the changes. The author explains the root cause of the aliasing bug (mutable default argument [] in the alias_of parameter causing all buffers to share the same list instance), describes the implemented solution (doubly-linked list approach with BFS traversal), and lists the specific refactorings performed. All items mentioned in the description are reflected in the raw_summary: the parameter change from alias_of: Optional[List[str]] = [] to aliases: Optional[List[str]] = None, the renaming of has_live_ancestors to has_live_aliases with BFS logic, the removal of fromVariableBuffer, the refactoring of hoistConstant, the refactoring of TransientBuffer.__init__, and the changelog updates. The description is neither vague nor generic, providing sufficient detail about both the problem and solution.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
Deeploy/DeeployTypes.py (2)

327-349: Bug: visited initialized as set(self.name) (set of characters), not a set of names.

This can degrade traversal correctness and performance for alias cycles. Initialize with the buffer name itself.

-        visited = set(self.name)
+        visited = {self.name}
-        while len(queue) > 0:
-            next = queue.pop()
-            buffNext = ctxt.lookup(next)
+        while len(queue) > 0:
+            next_name = queue.pop()
+            buffNext = ctxt.lookup(next_name)
             assert isinstance(buffNext, VariableBuffer)
             live |= buffNext._live
-            visited.add(next)
-            queue |= buffNext.aliases - visited
+            visited.add(next_name)
+            queue |= (buffNext.aliases - visited)

946-979: Make hoistConstant robust to np.ndarray-valued constants.

The method currently assumes a gs.Constant object, but line 2462 in DeeployTypes.py passes x.attrs['value'] directly, which may be a numpy array. This will crash when accessing .outputs and .name. Wrap numpy arrays into gs.Constant before processing, and update the signature to accept Union[gs.Constant, np.ndarray].

-    def hoistConstant(self,
-                      constant: gs.Constant,
-                      name: Optional[str] = None,
-                      _type: Optional[Type[Pointer]] = None) -> str:
+    def hoistConstant(self,
+                      constant: Union[gs.Constant, np.ndarray],
+                      name: Optional[str] = None,
+                      _type: Optional[Type[Pointer]] = None) -> str:
         """Register a ConstantBuffer extracted directly from a graphsurgeon Constant

         Parameters
         ----------
-        constant : gs.Constant
-            graphsurgeon.Node containing a single constant output
+        constant : Union[gs.Constant, np.ndarray]
+            graphsurgeon.Node containing a single constant output, or a numpy array
         name : str
             Name of the ConstantBuffer to be registered
         _type : Optional[Type[Pointer]]
             Optional type assignment of the registered ConstantBuffer

         Returns
         -------
         str
             Returns the name of the newly registed ConstantBuffer

         """
+        # Normalize to a gs.Constant (GraphSurgeon tensor)
+        if isinstance(constant, np.ndarray):
+            constant = gs.Constant(name if name is not None else "CONST", constant)
         assert len(constant.outputs) <= 1, f"Constant {constant.name} has more than one output"

         name = name if name is not None else constant.name
🧹 Nitpick comments (2)
Deeploy/Targets/Generic/Parsers.py (1)

1023-1027: Use zip(..., strict=True) to enforce arity assumptions.

parseNode already constrains IO counts; strict makes this explicit and prevents silent truncation if assumptions change.

If Python >= 3.10 in your runtime, apply:

-        for tensor, symName in zip(node.inputs, ['data_in', 'shape']):
+        for tensor, symName in zip(node.inputs, ['data_in', 'shape'], strict=True):
             self.operatorRepresentation[symName] = ctxt.lookup(tensor.name).name
-        for tensor, symName in zip(node.outputs, ['data_out']):
+        for tensor, symName in zip(node.outputs, ['data_out'], strict=True):
             self.operatorRepresentation[symName] = ctxt.lookup(tensor.name).name
Deeploy/Targets/Generic/Templates/ReshapeTemplate.py (1)

28-36: Good: explicit, bidirectional alias linkage. Add self-guard.

Avoid self-edges if data_in == data_out.

-        # Link aliases to each buffer
-        bufferIn.aliases.add(bufferOut.name)
-        bufferOut.aliases.add(bufferIn.name)
+        # Link aliases to each buffer (skip self-aliasing)
+        if bufferIn.name != bufferOut.name:
+            bufferIn.aliases.add(bufferOut.name)
+            bufferOut.aliases.add(bufferIn.name)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 15c4a23 and 31ab818.

📒 Files selected for processing (4)
  • Deeploy/CommonExtensions/CodeTransformationPasses/MemoryAllocation.py (1 hunks)
  • Deeploy/DeeployTypes.py (7 hunks)
  • Deeploy/Targets/Generic/Parsers.py (1 hunks)
  • Deeploy/Targets/Generic/Templates/ReshapeTemplate.py (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
Deeploy/Targets/Generic/Templates/ReshapeTemplate.py (1)
Deeploy/DeeployTypes.py (5)
  • NetworkContext (508-1020)
  • NodeTemplate (87-229)
  • VariableBuffer (232-360)
  • lookup (720-752)
  • add (681-718)
Deeploy/CommonExtensions/CodeTransformationPasses/MemoryAllocation.py (1)
Deeploy/DeeployTypes.py (1)
  • has_live_aliases (327-349)
Deeploy/Targets/Generic/Parsers.py (1)
Deeploy/DeeployTypes.py (3)
  • inputs (2503-2520)
  • lookup (720-752)
  • outputs (2522-2539)
Deeploy/DeeployTypes.py (2)
DeeployTest/testUtils/dmaUtils.py (1)
  • size (72-73)
Deeploy/AbstractDataTypes.py (3)
  • Pointer (303-388)
  • PointerClass (536-559)
  • VoidType (121-127)
🪛 Ruff (0.14.0)
Deeploy/Targets/Generic/Parsers.py

1023-1023: zip() without an explicit strict= parameter

Add explicit value for parameter strict=

(B905)


1025-1025: zip() without an explicit strict= parameter

Add explicit value for parameter strict=

(B905)

Deeploy/DeeployTypes.py

241-241: Do not use mutable data structures for argument defaults

Replace with None; initialize within function

(B006)

🔇 Additional comments (2)
Deeploy/DeeployTypes.py (1)

241-261: Fix for mutable default argument looks good.

Switching to aliases: Optional[List[str]] = None and materializing a set resolves the shared-list bug and improves semantics.

Deeploy/CommonExtensions/CodeTransformationPasses/MemoryAllocation.py (1)

144-151: Deallocation guard updated to has_live_aliases — good alignment with new alias model.

Ensure alias links are symmetric for all aliasing ops (as done in ReshapeTemplate) so this guard remains sound.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
CHANGELOG.md (1)

7-7: LGTM! Entries are accurate and appropriately categorized.

The changelog entries align well with the PR objectives (removal of fromVariableBuffer, hoistConstant refactor, TransientBuffer.__init__ refactor, and aliasing bug fix). The terse style is consistent with other entries in the file.

Consider optionally expanding line 90 ("Fixed aliasing") with a brief note about the root cause (e.g., "mutable default argument causing shared state") for clarity to future readers, though the current entry is acceptable as-is.

Also applies to: 77-79, 90-90

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 31ab818 and 2aa6bd7.

📒 Files selected for processing (1)
  • CHANGELOG.md (3 hunks)
🧰 Additional context used
🪛 LanguageTool
CHANGELOG.md

[grammar] ~77-~77: There might be a mistake here.
Context: ...d of generateFunction(...). - Removed fromVariableBuffer - Refactored hoistConstant - Refactored ...

(QB_NEW_EN)


[grammar] ~78-~78: There might be a mistake here.
Context: ...moved fromVariableBuffer - Refactored hoistConstant - Refactored TransientBuffer's __init__ ...

(QB_NEW_EN)

🔇 Additional comments (1)
CHANGELOG.md (1)

77-78: Grammar warnings are false positives.

The static analysis hints flag lines 77–78 as potential grammar issues, but the Markdown formatting (backticks around identifiers, bullet-point dashes) is correct and consistent with the rest of the file. These warnings appear to be tool parsing artifacts.

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