Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions strategy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,12 @@ from strategy import Transform, register_transform, subspace_matmul, Config

class MyTransform(Transform):
name = "mine"
def basis(self, n, m, backend, dtype, A=None, B=None):
def basis(self, n, m, backend, dtype, A=None, B=None, frac=None):
Q = ... # (n, m), ORTHONORMAL columns, on backend.xp
return Q
return Q # frac = the run's --vram-fraction; forward it
# to any stream_gemm_* helper you call. Declare
# it even if unused: multiply_subspace only
# passes the budget to a basis() that does.

register_transform("mine", MyTransform)
C = subspace_matmul(A, B, config=Config(transform="mine", rank_m=256))
Expand Down
53 changes: 34 additions & 19 deletions tests/test_contributor_docs_basis_signature.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@
in the legacy branch and silently streams its basis at the default fraction
instead of Config.vram_fraction (the bug #211 fixed for rsvd).

The template was brought in line already; these tests cover the two remaining
copies contributors actually read:
The template was brought in line already; these tests cover every other copy a
contributor actually reads:

* CONTRIBUTING.md's "What you actually change" snippet -- the canonical
"that is enough to be scored" example.
* strategy/README.md's "Register your own (the updatable hook)" -- the copy a
contributor meets first, and the one this file used to leave uncovered, which
is how it stayed on the old signature (#274).
* strategy/examples/run_example.py's FirstAxes -- the worked custom transform.

Both are parsed as text/AST rather than imported: run_example.py runs a real
All are parsed as text/AST rather than imported: run_example.py runs a real
subspace_matmul at module scope, which needs a GPU. Pure parsing; no GPU needed.

Run: python tests/test_contributor_docs_basis_signature.py
Expand All @@ -24,14 +27,21 @@
import re
import sys

import pytest

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from strategy.transforms import Transform

_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
_CONTRIBUTING = os.path.join(_ROOT, "CONTRIBUTING.md")
_README = os.path.join(_ROOT, "strategy", "README.md")
_RUN_EXAMPLE = os.path.join(_ROOT, "strategy", "examples", "run_example.py")

# Every prose copy of the hook. Driven through one table so a new doc cannot be
# added -- or an existing one silently rolled back -- without being checked.
_MARKDOWN_SNIPPETS = ((_CONTRIBUTING, "CONTRIBUTING.md"), (_README, "strategy/README.md"))


def _read(path: str) -> str:
with open(path, encoding="utf-8") as fh:
Expand All @@ -52,12 +62,27 @@ def _basis_defs(source: str) -> list:
return out


def test_contributing_snippet_declares_frac():
"""CONTRIBUTING.md's transform snippet is the contract contributors copy."""
snippets = re.findall(r"def basis\(([^)]*)\)", _read(_CONTRIBUTING))
assert snippets, "no `def basis(...)` snippet found in CONTRIBUTING.md"
@pytest.mark.parametrize("path,label", _MARKDOWN_SNIPPETS, ids=[s[1] for s in _MARKDOWN_SNIPPETS])
def test_markdown_snippet_declares_frac(path, label):
"""Every prose basis() a contributor copies must show the real signature."""
snippets = re.findall(r"def basis\(([^)]*)\)", _read(path))
assert snippets, f"no `def basis(...)` snippet found in {label}"
for sig in snippets:
assert "frac" in sig, f"CONTRIBUTING.md basis() snippet omits frac: def basis({sig})"
assert "frac" in sig, f"{label} basis() snippet omits frac: def basis({sig})"


@pytest.mark.parametrize("path,label", _MARKDOWN_SNIPPETS, ids=[s[1] for s in _MARKDOWN_SNIPPETS])
def test_markdown_snippet_matches_the_base_class_contract(path, label):
"""Not just frac: the whole parameter list must match Transform.basis, so a
snippet cannot drift on any other argument either. Defaults are stripped --
the snippets legitimately show `A=None`, the contract is the parameter names
and their order."""
for sig in re.findall(r"def basis\(([^)]*)\)", _read(path)):
got = [p.strip().split("=")[0].strip() for p in sig.split(",")]
assert got == _base_params(), (
f"{label}: def basis({', '.join(got)}) does not match "
f"Transform.basis({', '.join(_base_params())})"
)


def test_run_example_custom_transform_declares_frac():
Expand All @@ -74,14 +99,4 @@ def test_run_example_basis_matches_the_base_class_contract():


if __name__ == "__main__":
fns = [v for k, v in sorted(globals().items()) if k.startswith("test_")]
failed = 0
for fn in fns:
try:
fn()
print(f"PASS {fn.__name__}")
except AssertionError as e:
failed += 1
print(f"FAIL {fn.__name__}: {e}")
print(f"\n{len(fns) - failed}/{len(fns)} passed")
sys.exit(1 if failed else 0)
sys.exit(pytest.main([__file__, "-q"]))
Loading