-
Notifications
You must be signed in to change notification settings - Fork 23
add unified contraction algebra interface with tropical and bf16 applications #125
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
DeanTMaxim
wants to merge
16
commits into
tensorcircuit:master
Choose a base branch
from
DeanTMaxim:feat/contraction-algebra-tropical
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 5 commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
539ff5d
add unified contraction algebra interface with tropical and bf16 appl…
DeanTMaxim a1f8c1b
bf16: genuine-bf16 einsum kernel
DeanTMaxim b3d3d34
fix: reduce cognitive complexity in _pair_einsum and fix implicit str…
DeanTMaxim 5de41f8
refactor: merge 6 tropical test files into single test_tropical.py
DeanTMaxim b218efd
refactor: remove algebra parameter from set_contractor
DeanTMaxim 3ad432c
refactor: replace prefer_einsum attr with get_contractor_kwargs() method
DeanTMaxim df2776b
refactor: flatten contraction_algebra subpackage into single file
DeanTMaxim 3c78e81
fix: strict mypy Dict[str, Any] annotation + black formatting
DeanTMaxim 2b16100
refactor: move contraction_algebra import to top of cons.py
DeanTMaxim 986588b
fix: address PR review minor issues (code quality + docs)
DeanTMaxim c8e8aea
refactor: default _contraction_algebra to None instead of StandardAlg…
DeanTMaxim d69d7f7
refactor: default _contraction_algebra to None, clean up guard checks
DeanTMaxim dcb9490
refactor: simplify _algebraic_base_contraction — ns = alg is not None
DeanTMaxim a345215
test: update assertions for _contraction_algebra default None
DeanTMaxim 0184776
fix: inline alg is not None for mypy type narrowing
DeanTMaxim bc7671c
introduce pairtensor wrapper and apply contraction algebra review fee…
DeanTMaxim File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,194 @@ | ||
| """complex<bfloat16> pair-algebra — a reference APPLICATION of ContractionAlgebra. | ||
|
|
||
| Pair repr: complex tensor = stack([re, im], axis=-1) of bf16. Contraction = 4 real | ||
| bf16 matmuls (4M). Activated via ``cons.set_contraction_algebra(ComplexPairAlgebra())`` or | ||
| the ``bcomplex32()`` CM. encode/decode at the ``_algebraic_base_contraction`` boundary | ||
| keep the pair axis off tn.Node (dodges the axis==edge wall). | ||
| """ | ||
|
|
||
| from typing import Any, Dict, Iterator, List, Tuple | ||
| import contextlib | ||
|
|
||
| import numpy as np | ||
|
|
||
| import tensorcircuit.cons as cons | ||
| from tensorcircuit.contraction_algebra import ContractionAlgebra, Representation | ||
|
|
||
| Tensor = Any | ||
| Backend = Any | ||
|
|
||
|
|
||
| def _bf16_dtype() -> Any: | ||
| import ml_dtypes | ||
|
|
||
| return ml_dtypes.bfloat16 | ||
|
|
||
|
|
||
| def _complex_to_pair(be: Backend, t: Tensor) -> Tensor: | ||
| """complex tensor -> stack([re, im], axis=-1) of bf16.""" | ||
| bf = _bf16_dtype() | ||
| re = be.cast(be.real(t), bf) | ||
| im = be.cast(be.imag(t), bf) | ||
| return be.stack([re, im], axis=-1) | ||
|
|
||
|
|
||
| def _pair_to_complex(be: Backend, pair: Tensor) -> Tensor: | ||
| """pair of bf16 -> complex64 tensor (recombine; no copy risk via cast).""" | ||
| re = be.cast(pair[..., 0], "float32") | ||
| im = be.cast(pair[..., 1], "float32") | ||
| return be.cast(re + 1j * im, "complex64") | ||
|
|
||
|
|
||
| def _pair_tensordot(be: Backend, a: Tensor, b: Tensor, axes: Any) -> Tensor: | ||
| """Complex tensordot = 4 real bf16 tensordots (4M). Uses be.tensordot (never patched).""" | ||
| ar, ai = a[..., 0], a[..., 1] | ||
| br, bi = b[..., 0], b[..., 1] | ||
| cr = be.tensordot(ar, br, axes) - be.tensordot(ai, bi, axes) | ||
| ci = be.tensordot(ar, bi, axes) + be.tensordot(ai, br, axes) | ||
| return be.stack([cr, ci], axis=-1) | ||
|
DeanTMaxim marked this conversation as resolved.
Outdated
|
||
|
|
||
|
|
||
| def _einsum_single_operand_half( | ||
| be: Backend, x: Tensor, lhs: str, out_subs: str | ||
| ) -> Tensor: | ||
| """Apply a 1-operand einsum to one bf16 half (decomposed into diagonal + sum | ||
| + transpose), staying in bf16 end-to-end. Handles reductions, transposes, | ||
| diagonals, and traces without float32 upcast. | ||
| """ | ||
| x_subs = list(lhs) # mutable subscript list we update in place | ||
|
|
||
| # Step 1 — diagonalise every repeated index. | ||
| while True: | ||
| dup = next((c for c in set(x_subs) if x_subs.count(c) > 1), None) | ||
| if dup is None: | ||
| break | ||
| pos = [i for i, c in enumerate(x_subs) if c == dup] | ||
| x = np.diagonal(x, axis1=pos[0], axis2=pos[-1]) | ||
| x_subs = [c for i, c in enumerate(x_subs) if i != pos[-1]] + [dup] | ||
|
|
||
| # Step 2 — sum over indices NOT wanted in the output. | ||
| out_set = set(out_subs) | ||
| sum_indices = [c for c in x_subs if c not in out_set] | ||
| if sum_indices: | ||
| x = be.sum(x, axis=tuple(x_subs.index(c) for c in sum_indices)) | ||
| x_subs = [c for c in x_subs if c in out_set] | ||
|
|
||
| # Step 3 — transpose remaining indices into the requested output order. | ||
| if x_subs != list(out_subs): | ||
| perm = tuple(x_subs.index(c) for c in out_subs) | ||
| x = be.transpose(x, perm) | ||
|
|
||
| return x | ||
|
|
||
|
|
||
| def _pair_einsum(be: Backend, eq: str, *operands: Tensor) -> Tensor: | ||
| """Complex einsum = 4 real bf16 einsums (4M for 2 operands, 2 for 1). | ||
|
|
||
| **Two-operand (genuine bf16):** manually decomposed into ``be.tensordot`` + | ||
| ``be.transpose``, because numpy's C ``einsum`` rejects ``ml_dtypes.bfloat16`` | ||
| (it is a standalone C routine with a hardcoded dtype allowlist, not a | ||
| ufunc). ``np.tensordot`` accepts bf16 because it dispatches through ufunc | ||
| loops, so the compute is genuine bf16 end-to-end — no float32 upcast. | ||
| The decomposition parses the einsum subscript equation to find contracted | ||
| axes, then uses ``tensordot`` for the contraction and ``transpose`` to | ||
| match the output subscript order. | ||
|
|
||
| **Single-operand (genuine bf16):** decomposed into ``np.diagonal`` + | ||
| ``be.sum`` + ``be.transpose``, all bf16-safe. Handles reductions, | ||
| transposes, diagonals, and traces — the full einsum single-operand | ||
| semantics without any float32 upcast. | ||
|
|
||
| ``_pair_tensordot`` needs no such routing because ``np.tensordot`` already | ||
| preserves bf16 (verified: it accumulates in bf16, not float32). | ||
| cotengra feeds only 1-2-operand equations here (it decomposes hyperedges | ||
| itself), all of which are pairwise contractions that map cleanly to | ||
| tensordot. | ||
| """ | ||
| if len(operands) == 1: | ||
| # ── Single-operand: pure bf16 (decompose into sum + transpose + diagonal) ── | ||
| a = operands[0] | ||
|
|
||
| # Implicit mode (no ``->``) is an identity / no-op at the einsum level. | ||
| if "->" not in eq: | ||
| return a | ||
|
|
||
| lhs, out_subs = eq.split("->") | ||
| return be.stack( | ||
| [ | ||
| _einsum_single_operand_half(be, a[..., 0], lhs, out_subs), | ||
| _einsum_single_operand_half(be, a[..., 1], lhs, out_subs), | ||
| ], | ||
| axis=-1, | ||
| ) | ||
|
|
||
| # ── 2-operand: bf16-safe tensordot decomposition ────────────────── | ||
| a, b = operands | ||
| ar, ai = a[..., 0], a[..., 1] | ||
| br, bi = b[..., 0], b[..., 1] | ||
|
|
||
| # Parse the einsum equation once | ||
| if "->" not in eq: | ||
| raise ValueError( | ||
| f"implicit-mode einsum {eq!r} not supported for bf16; use explicit '->'" | ||
| ) | ||
| lhs, out_subs = eq.split("->") | ||
| a_subs, b_subs = lhs.split(",") | ||
|
|
||
| a_set: set[str] = set(a_subs) | ||
| b_set: set[str] = set(b_subs) | ||
| contracted = [c for c in a_subs if c in b_set] | ||
|
|
||
| a_free = [c for c in a_subs if c not in b_set] | ||
| b_free = [c for c in b_subs if c not in a_set] | ||
| out_order = list(out_subs) | ||
|
|
||
| def _contract(x: Tensor, y: Tensor) -> Tensor: | ||
| """Pairwise bf16-safe einsum → tensordot + optional transpose.""" | ||
| if contracted: | ||
| a_axes = [a_subs.index(c) for c in contracted] | ||
| b_axes = [b_subs.index(c) for c in contracted] | ||
| result = be.tensordot(x, y, axes=(a_axes, b_axes)) | ||
| else: | ||
| result = be.tensordot(x, y, axes=0) | ||
|
|
||
| free_order = a_free + b_free | ||
| if free_order != out_order: | ||
| perm = [free_order.index(c) for c in out_order] | ||
| result = be.transpose(result, perm) | ||
| return result | ||
|
|
||
| cr = _contract(ar, br) - _contract(ai, bi) | ||
| ci = _contract(ar, bi) + _contract(ai, br) | ||
| return be.stack([cr, ci], axis=-1) | ||
|
|
||
|
|
||
| class PairBf16Representation(Representation): | ||
| name = "pair_bf16" | ||
|
|
||
| def encode(self, be: Backend, tensors: List[Tensor]) -> List[Tensor]: | ||
| return [_complex_to_pair(be, t) for t in tensors] | ||
|
|
||
| def decode(self, be: Backend, tensor: Tensor) -> Tuple[Tensor, Dict[str, Tensor]]: | ||
| return _pair_to_complex(be, tensor), {} | ||
|
|
||
|
|
||
| class ComplexPairAlgebra(ContractionAlgebra): | ||
| name = "bcomplex32_pair" | ||
| representation = PairBf16Representation() | ||
| prefer_einsum = True # pair operands carry a trailing storage axis | ||
|
|
||
| def tensordot(self, be: Backend, a: Tensor, b: Tensor, axes: Any) -> Tensor: | ||
| return _pair_tensordot(be, a, b, axes) | ||
|
|
||
| def einsum(self, be: Backend, eq: str, *operands: Tensor) -> Tensor: | ||
| return _pair_einsum(be, eq, *operands) | ||
|
|
||
|
|
||
| @contextlib.contextmanager | ||
| def bcomplex32() -> Iterator[None]: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. delete this |
||
| prev = cons.get_contraction_algebra() | ||
| cons.set_contraction_algebra(ComplexPairAlgebra()) | ||
| try: | ||
| yield | ||
| finally: | ||
| cons.set_contraction_algebra(prev) | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.