From aa67c900bb81fbb2d8c5d94b9518744366a09c62 Mon Sep 17 00:00:00 2001 From: luciferlive112116 <291889058+luciferlive112116@users.noreply.github.com> Date: Thu, 16 Jul 2026 13:27:49 +0800 Subject: [PATCH] fix(strategy): declare frac in every contributor-facing basis() snippet Re-lands #253, which was rolled back with the whole 9058d49..f35482a merge window (1dc8fce..e42c525 reverted all seven of that batch's PRs, LIFO, across five authors), and extends it to the copy #253 missed. multiply_subspace forwards the VRAM budget only to a basis() that declares frac: if "frac" in inspect.signature(transform.basis).parameters: Q = transform.basis(n, m, backend, cdt, A=A, B=B, frac=frac) else: Q = transform.basis(n, m, backend, cdt, A=A, B=B) That else-branch is a compatibility shim for legacy transforms, so a contributor copying a snippet without frac lands there and silently streams the basis at the default fraction rather than Config.vram_fraction -- the bug #211 fixed for rsvd, re-introduced once per contribution. Three copies a contributor reads were on the pre-#211 signature; strategy/README's 'Register your own' hook was never covered by #253 at all: * CONTRIBUTING.md 'What you actually change' * strategy/README.md 'Register your own (the updatable hook)' <- new * strategy/examples/run_example.py FirstAxes (fixed basis; frac unused but kept, since the forwarding gate is signature-based) The base class, transforms.py's module docstring, rsvd and transform_template (#251, still on main) already declare it. Tests parse the markdown snippets and AST-parse the example -- run_example.py runs a real subspace_matmul at module scope and cannot be imported without a GPU. --- CONTRIBUTING.md | 7 +- strategy/README.md | 6 +- strategy/examples/run_example.py | 5 +- .../test_contributor_docs_basis_signature.py | 94 +++++++++++++++++++ 4 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 tests/test_contributor_docs_basis_signature.py diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 596e506..a6f08db 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -70,8 +70,11 @@ from strategy.transforms import Transform, register_transform class MyTransform(Transform): name = "mine" - def basis(self, n, m, backend, dtype, A=None, B=None): - # return an (n, m) array on backend.xp with ORTHONORMAL columns + def basis(self, n, m, backend, dtype, A=None, B=None, frac=None): + # return an (n, m) array on backend.xp with ORTHONORMAL columns. + # frac = the run's vram_fraction; forward it to any stream_gemm_* + # helpers you call so your basis honours --vram-fraction (a basis() + # that omits frac is called without it and silently uses the default). Q = ... return Q diff --git a/strategy/README.md b/strategy/README.md index d48a448..c119c48 100644 --- a/strategy/README.md +++ b/strategy/README.md @@ -51,9 +51,11 @@ 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 # forward frac to any stream_gemm_* helpers: + # multiply_subspace only passes the run's + # vram_fraction to a basis() that declares it register_transform("mine", MyTransform) C = subspace_matmul(A, B, config=Config(transform="mine", rank_m=256)) diff --git a/strategy/examples/run_example.py b/strategy/examples/run_example.py index 18b07c2..e8c870a 100644 --- a/strategy/examples/run_example.py +++ b/strategy/examples/run_example.py @@ -42,7 +42,10 @@ def rel(a, b): # 3) Plug in your own transform (the updatable core tech). class FirstAxes(Transform): name = "firstaxes" - def basis(self, n, m, backend, dtype, A=None, B=None): + def basis(self, n, m, backend, dtype, A=None, B=None, frac=None): + # frac (the run's vram_fraction) is unused here -- this basis is fixed + # and streams nothing -- but keep it in the signature: multiply_subspace + # only forwards the budget to a basis() that declares it. Q = np.zeros((n, m), dtype=dtype) Q[np.arange(m), np.arange(m)] = 1.0 return backend.to_device(Q) diff --git a/tests/test_contributor_docs_basis_signature.py b/tests/test_contributor_docs_basis_signature.py new file mode 100644 index 0000000..fc41b0d --- /dev/null +++ b/tests/test_contributor_docs_basis_signature.py @@ -0,0 +1,94 @@ +"""Keep every contributor-facing basis() snippet on the real interface. + +`multiply_subspace` forwards the VRAM budget only to a basis() that declares +`frac` (an inspect.signature gate whose `else` is a legacy-compat shim), so any +snippet a contributor copies must declare it -- otherwise their transform lands +in the legacy branch and silently streams its basis at the default fraction +instead of Config.vram_fraction (the bug #211 fixed for rsvd). + +There are three copies a contributor actually reads, and they must all agree +with `Transform.basis`: + + * CONTRIBUTING.md -- "What you actually change", the canonical + "that is enough to be scored" snippet. + * strategy/README.md -- "Register your own (the updatable hook)". + * strategy/examples/run_example.py -- the worked FirstAxes transform. + +(strategy/examples/transform_template.py is covered by its own test.) + +The markdown snippets are regex-checked and the example is AST-parsed 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 +""" +import ast +import inspect +import os +import re +import sys + +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__))) +_MARKDOWN = { + "CONTRIBUTING.md": os.path.join(_ROOT, "CONTRIBUTING.md"), + "strategy/README.md": os.path.join(_ROOT, "strategy", "README.md"), +} +_RUN_EXAMPLE = os.path.join(_ROOT, "strategy", "examples", "run_example.py") + + +def _read(path: str) -> str: + with open(path, encoding="utf-8") as fh: + return fh.read() + + +def _base_params() -> list: + return list(inspect.signature(Transform.basis).parameters) + + +def _basis_defs(source: str) -> list: + """Every `def basis(...)` parameter list found by parsing the module.""" + out = [] + for node in ast.walk(ast.parse(source)): + if isinstance(node, ast.FunctionDef) and node.name == "basis": + args = [a.arg for a in node.args.args] + [a.arg for a in node.args.kwonlyargs] + out.append(args) + return out + + +def test_markdown_snippets_declare_frac(): + """The snippets contributors copy out of the docs are the contract.""" + for label, path in _MARKDOWN.items(): + sigs = re.findall(r"def basis\(([^)]*)\)", _read(path)) + assert sigs, f"no `def basis(...)` snippet found in {label}" + for sig in sigs: + assert "frac" in sig, f"{label} basis() snippet omits frac: def basis({sig})" + + +def test_run_example_custom_transform_declares_frac(): + defs = _basis_defs(_read(_RUN_EXAMPLE)) + assert defs, "no basis() definition found in strategy/examples/run_example.py" + for params in defs: + assert "frac" in params, f"run_example basis() omits frac: {params}" + + +def test_run_example_basis_matches_the_base_class_contract(): + for params in _basis_defs(_read(_RUN_EXAMPLE)): + assert params == _base_params() + + +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)