From 191704e8b53615ebc37c255382baee110b5a62b0 Mon Sep 17 00:00:00 2001 From: xiaoyu <1259084489@qq.com> Date: Sun, 4 Oct 2026 22:36:45 +0800 Subject: [PATCH 1/6] cuda: declare the qwen3_5 backend #19 added the kernels under src/backends/cuda/qwen3_5/ but left them undeclared, so the directory has kernel sources and no manifest. #25 defines the manifest and its checker, and running that checker against main reports exactly that: [error] qwen3_5: has kernel sources (attention.cu, common.cuh, elementwise.cu) but no qwen3_5.backend.json manifest This adds the declaration, with the values the merged files already state: - sources: the nine kernel and header files, from the directory listing. - build.script and build.output: what build.sh actually writes, libqwen3_5_cuda.so. - build.default_arch 89 and build.architectures [89]: build.sh defaults `arch` to CUDA_COMPUTE_CAP-or-89 and passes it to a single -gencode flag. - build.min_capability 80: the README says "Tensor-core kernels need sm_80 or newer". status is `experimental`, not `validated`. #19 validated the kernels, but this manifest is written by someone other than their author and a `validated` manifest asserts a tolerance and a reference entrypoint, which are twu3202's to state. A declaration that the backend exists and how it builds is useful on its own and does not put words in anyone's mouth; promoting it is a one-line change once the tolerance is recorded. Checked with the checker from #25: `1 backend(s), 0 error(s), 0 warning(s)`, and with discover_buildable.py from #2, which reports it as compile-checkable. --- .../cuda/qwen3_5/qwen3_5.backend.json | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/backends/cuda/qwen3_5/qwen3_5.backend.json diff --git a/src/backends/cuda/qwen3_5/qwen3_5.backend.json b/src/backends/cuda/qwen3_5/qwen3_5.backend.json new file mode 100644 index 0000000..8a7d2b8 --- /dev/null +++ b/src/backends/cuda/qwen3_5/qwen3_5.backend.json @@ -0,0 +1,23 @@ +{ + "name": "qwen3_5", + "abi_version": 1, + "status": "experimental", + "sources": [ + "ops.h", + "common.cuh", + "mma.cuh", + "norm.cu", + "elementwise.cu", + "attention.cu", + "gdn_prefill.cu", + "gemm.cu", + "runtime.cu" + ], + "build": { + "script": "build.sh", + "output": "libqwen3_5_cuda.so", + "default_arch": 89, + "architectures": [89], + "min_capability": 80 + } +} From 754b98910e1a89c21e04f3a4953f929475241b42 Mon Sep 17 00:00:00 2001 From: xiaoyu <1259084489@qq.com> Date: Sun, 4 Oct 2026 22:51:35 +0800 Subject: [PATCH 2/6] cuda: declare the qwen3_5 ABI version the library actually has The manifest said abi_version 1. #19's ops.h defines CS1_ABI_VERSION 4, so the declaration disagreed with the library it describes. Nothing caught it when this was written, because the checker did not read headers. #25 now does: it reads the `#define _ABI_VERSION N` out of the declared sources and requires the manifest to match, which is what surfaced this. abi_version is 1 but ops.h defines CS1_ABI_VERSION 4 also strengthens the description in the same PR. --- src/backends/cuda/qwen3_5/qwen3_5.backend.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/backends/cuda/qwen3_5/qwen3_5.backend.json b/src/backends/cuda/qwen3_5/qwen3_5.backend.json index 8a7d2b8..e4dd7ca 100644 --- a/src/backends/cuda/qwen3_5/qwen3_5.backend.json +++ b/src/backends/cuda/qwen3_5/qwen3_5.backend.json @@ -1,6 +1,6 @@ { "name": "qwen3_5", - "abi_version": 1, + "abi_version": 4, "status": "experimental", "sources": [ "ops.h", @@ -17,7 +17,9 @@ "script": "build.sh", "output": "libqwen3_5_cuda.so", "default_arch": 89, - "architectures": [89], + "architectures": [ + 89 + ], "min_capability": 80 } } From 58e1fb8455c3a7bd102422a1f8dcc8624bcb956d Mon Sep 17 00:00:00 2001 From: xiaoyu <1259084489@qq.com> Date: Mon, 5 Oct 2026 14:36:49 +0800 Subject: [PATCH 3/6] ci: run the CUDA contract checker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nothing ran `src/backends/cuda/tests` or `check_contract.py`: the only Python discovery in this workflow is `tests/benchmarks`. Two things make this land here rather than with the checker. The step needs `check_contract.py`, which arrives in #25. And the checker exits 1 on a tree that has kernels but no declaration for them, which is main's state until the manifest in this branch lands — so wiring it up first would redden main. Both are stated in the job's comment and in its failure message, so a wrong merge order is self-explanatory rather than a bare "file not found". No CUDA toolkit and no GPU: the checker and its tests read files as text. --- .github/workflows/ci.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 594ef0f..1fbfd34 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,3 +42,25 @@ jobs: - run: python -m pip install -r benchmarks/requirements.txt - run: python benchmarks/bench.py validate benchmarks/smoke.jsonl - run: python -m unittest discover -s tests/benchmarks -p 'test_*.py' -v + + cuda-contract: + # The CUDA backend contract and its checker. No CUDA toolkit and no GPU: both + # read files as text. This depends on #25 for check_contract.py, and it + # arrives with the qwen3_5 manifest below because the checker exits 1 on a + # tree that has kernels and no declaration for them. + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Checker tests + run: python -m unittest discover -s src/backends/cuda/tests -t . -v + - name: Contract check + run: | + if [ ! -f src/backends/cuda/check_contract.py ]; then + echo "::error::src/backends/cuda/check_contract.py is missing. It lands in #25, which this step depends on; merge that first." + exit 1 + fi + python src/backends/cuda/check_contract.py --repo-root . From a35024c0d3f06f1f40e754abaffb9458208be62f Mon Sep 17 00:00:00 2001 From: xiaoyu <1259084489@qq.com> Date: Mon, 5 Oct 2026 14:41:11 +0800 Subject: [PATCH 4/6] ci: discover each backend's tests where they live The first version ran `discover -s src/backends/cuda/tests`, which finds the contract checker's tests and nothing else. Kernel tests sit beside their kernels -- `src/backends/cuda/scoring/test_scoring.py` is 17 of them -- so a backend that merges later would have had no test coverage at all while appearing to be covered. The step now loops over `src/backends/cuda/*/` and discovers in any directory holding `test_*.py`. `-t` has to point at the directory itself rather than the repository root: the kernel directories are not packages, so `-t .` fails with "Start directory is not importable". Simulated on a tree carrying both: 17 from `scoring/`, 66 from `tests/`. --- .github/workflows/ci.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1fbfd34..cba02d3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,8 +55,20 @@ jobs: - uses: actions/setup-python@v5 with: python-version: "3.11" - - name: Checker tests + - name: Contract checker tests run: python -m unittest discover -s src/backends/cuda/tests -t . -v + - name: Kernel tests + # Each backend keeps its tests beside its kernels, so this discovers per + # directory rather than assuming one tests/ tree. `-t` has to point at the + # directory itself: the kernel dirs are not packages and are not + # importable from the repository root. + run: | + for dir in src/backends/cuda/*/; do + if compgen -G "${dir}test_*.py" > /dev/null; then + echo "== $dir" + python -m unittest discover -s "$dir" -t "$dir" -v + fi + done - name: Contract check run: | if [ ! -f src/backends/cuda/check_contract.py ]; then From b04ac948327ab6594e80870fa8402d097d9ebe84 Mon Sep 17 00:00:00 2001 From: xiaoyu <1259084489@qq.com> Date: Mon, 5 Oct 2026 15:06:11 +0800 Subject: [PATCH 5/6] ci: skip the CUDA job when the checker is not in the tree The job failed on its own branch, in five seconds: ImportError: Start directory is not importable: 'src/backends/cuda/tests' The checker and its tests live in #25, so on #77's tree there is nothing to run. The guard I wrote checked for `check_contract.py` alone and ran *after* the test step, so the first step failed before the guard was reached and the message explaining the dependency never printed. Now a first step decides whether the prerequisites are present and the three steps run only if they are; otherwise the job reports a notice naming #25 and passes. That is what makes either merge order work: green on this branch before #25 lands, and running by itself once it does. Verified both ways: with #25's files absent all three steps skip, and with them present 66 checker tests, 66 kernel tests and the contract check all pass. --- .github/workflows/ci.yml | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cba02d3..6ef74c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,9 +45,13 @@ jobs: cuda-contract: # The CUDA backend contract and its checker. No CUDA toolkit and no GPU: both - # read files as text. This depends on #25 for check_contract.py, and it - # arrives with the qwen3_5 manifest below because the checker exits 1 on a - # tree that has kernels and no declaration for them. + # read files as text. + # + # The checker and its tests arrive in #25, so on a tree without them there is + # nothing to run. That is a skip, not a failure: this job has to be green on + # the branch that carries it before #25 lands, and it has to start running by + # itself once #25 does, in either merge order. Failing instead would redden + # whichever PR arrived second. runs-on: ubuntu-latest timeout-minutes: 5 steps: @@ -55,13 +59,23 @@ jobs: - uses: actions/setup-python@v5 with: python-version: "3.11" + - name: Check for the checker + id: present + run: | + if [ -f src/backends/cuda/check_contract.py ] && [ -d src/backends/cuda/tests ]; then + echo "ready=true" >> "$GITHUB_OUTPUT" + else + echo "ready=false" >> "$GITHUB_OUTPUT" + echo "::notice::src/backends/cuda/check_contract.py and src/backends/cuda/tests are not in this tree; they arrive in #25. Skipping." + fi - name: Contract checker tests + if: steps.present.outputs.ready == 'true' run: python -m unittest discover -s src/backends/cuda/tests -t . -v - name: Kernel tests - # Each backend keeps its tests beside its kernels, so this discovers per - # directory rather than assuming one tests/ tree. `-t` has to point at the - # directory itself: the kernel dirs are not packages and are not - # importable from the repository root. + if: steps.present.outputs.ready == 'true' + # Each backend keeps its tests beside its kernels. `-t` has to point at + # the directory itself: kernel directories are not packages, so `-t .` + # fails with "Start directory is not importable". run: | for dir in src/backends/cuda/*/; do if compgen -G "${dir}test_*.py" > /dev/null; then @@ -70,9 +84,5 @@ jobs: fi done - name: Contract check - run: | - if [ ! -f src/backends/cuda/check_contract.py ]; then - echo "::error::src/backends/cuda/check_contract.py is missing. It lands in #25, which this step depends on; merge that first." - exit 1 - fi - python src/backends/cuda/check_contract.py --repo-root . + if: steps.present.outputs.ready == 'true' + run: python src/backends/cuda/check_contract.py --repo-root . From b1a886a5fbd3b0a3738a38393ae882516f8839e4 Mon Sep 17 00:00:00 2001 From: xiaoyu <1259084489@qq.com> Date: Mon, 5 Oct 2026 21:02:09 +0800 Subject: [PATCH 6/6] ci: discover the CUDA backend tests in the repository tests/ tree The loop scanned src/backends/cuda/*/ for test files, which matched the checker's own tests/ directory -- running those 66 tests a second time -- and missed tests/cuda. CONTRIBUTING.md keeps test bodies under the root tests/ tree, so use the same invocation as the benchmarks job. --- .github/workflows/ci.yml | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6ef74c6..275ce74 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,11 +47,12 @@ jobs: # The CUDA backend contract and its checker. No CUDA toolkit and no GPU: both # read files as text. # - # The checker and its tests arrive in #25, so on a tree without them there is - # nothing to run. That is a skip, not a failure: this job has to be green on - # the branch that carries it before #25 lands, and it has to start running by - # itself once #25 does, in either merge order. Failing instead would redden - # whichever PR arrived second. + # The checker and its tests arrive in #25, so on a tree without them those + # two steps are a skip, not a failure: this job has to be green on the branch + # that carries it before #25 lands, and it has to start running by itself + # once #25 does, in either merge order. Failing instead would redden whichever + # PR arrived second. The backend tests come from the repository tests/ tree, + # which is already in main, so they run either way. runs-on: ubuntu-latest timeout-minutes: 5 steps: @@ -71,18 +72,12 @@ jobs: - name: Contract checker tests if: steps.present.outputs.ready == 'true' run: python -m unittest discover -s src/backends/cuda/tests -t . -v - - name: Kernel tests - if: steps.present.outputs.ready == 'true' - # Each backend keeps its tests beside its kernels. `-t` has to point at - # the directory itself: kernel directories are not packages, so `-t .` - # fails with "Start directory is not importable". - run: | - for dir in src/backends/cuda/*/; do - if compgen -G "${dir}test_*.py" > /dev/null; then - echo "== $dir" - python -m unittest discover -s "$dir" -t "$dir" -v - fi - done + - name: Backend tests + # Tests live under the repository-level tests/ tree, not beside the + # kernels they cover (CONTRIBUTING.md, "Adding a model"). Same + # invocation as the benchmarks job: tests/cuda is not a package, so + # discovery has to use the start directory as its own top level. + run: python -m unittest discover -s tests/cuda -p 'test_*.py' -v - name: Contract check if: steps.present.outputs.ready == 'true' run: python src/backends/cuda/check_contract.py --repo-root .