From afec45e6b29d84b7678e789994636e38417d0c24 Mon Sep 17 00:00:00 2001 From: xiaoyu <1259084489@qq.com> Date: Sun, 4 Oct 2026 22:36:45 +0800 Subject: [PATCH 01/10] 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 00000000..8a7d2b86 --- /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 b23ccb41ed878d23545cdcc531dda9cc31f9c465 Mon Sep 17 00:00:00 2001 From: xiaoyu <1259084489@qq.com> Date: Sun, 4 Oct 2026 22:51:35 +0800 Subject: [PATCH 02/10] 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 8a7d2b86..e4dd7cae 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 fe8f608b2c4f913cf2d6d9af0de44ebd4fab584d Mon Sep 17 00:00:00 2001 From: xiaoyu <1259084489@qq.com> Date: Mon, 5 Oct 2026 14:36:49 +0800 Subject: [PATCH 03/10] 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 594ef0f0..1fbfd349 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 c48681af46a274b3cf4ab2139b7aaa599dbb041b Mon Sep 17 00:00:00 2001 From: xiaoyu <1259084489@qq.com> Date: Mon, 5 Oct 2026 14:41:11 +0800 Subject: [PATCH 04/10] 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 1fbfd349..cba02d37 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 997dfc1871745a1a563a0f43ecc088b5714282f8 Mon Sep 17 00:00:00 2001 From: xiaoyu <1259084489@qq.com> Date: Mon, 5 Oct 2026 15:06:11 +0800 Subject: [PATCH 05/10] 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 cba02d37..6ef74c6e 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 507a8e02d38921e4b729177149bc904958d294f5 Mon Sep 17 00:00:00 2001 From: xiaoyu <1259084489@qq.com> Date: Mon, 5 Oct 2026 21:02:09 +0800 Subject: [PATCH 06/10] 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 6ef74c6e..275ce744 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 . From 4c4a8dd4d555ce06952f893a3387bd3739c964f2 Mon Sep 17 00:00:00 2001 From: xiaoyu <1259084489@qq.com> Date: Wed, 7 Oct 2026 20:12:36 +0800 Subject: [PATCH 07/10] cuda: follow CS1_ABI_VERSION to 5 `ops.h` was bumped to 5 by the Cua-S1 vision work merged after this branch was opened, so the manifest no longer matched the library it describes. The contract checker caught it the first time the CI step ran against current main, which is what the check exists for: [error] qwen3_5: abi_version is 4 but ops.h defines CS1_ABI_VERSION 5 --- src/backends/cuda/qwen3_5/qwen3_5.backend.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backends/cuda/qwen3_5/qwen3_5.backend.json b/src/backends/cuda/qwen3_5/qwen3_5.backend.json index e4dd7cae..df59cd9b 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": 4, + "abi_version": 5, "status": "experimental", "sources": [ "ops.h", From d7599cd5de7bedf74ec3fc3b5dd80136ad829dd1 Mon Sep 17 00:00:00 2001 From: xiaoyu <1259084489@qq.com> Date: Thu, 8 Oct 2026 21:34:12 +0800 Subject: [PATCH 08/10] cuda: list the consumers in the qwen3_5 manifest `models` names the model executors that consume a backend, which is what makes reuse visible instead of a private arrangement between crates. `src/models/cua_s1/native/` and `src/models/open_jev/native/` both depend on `omni-qwen3-5-native`, the executor that loads `libqwen3_5_cuda.so`, and both exist on main. contract.md's example manifest is this backend and lists them. --- src/backends/cuda/qwen3_5/qwen3_5.backend.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/backends/cuda/qwen3_5/qwen3_5.backend.json b/src/backends/cuda/qwen3_5/qwen3_5.backend.json index df59cd9b..eb2e693a 100644 --- a/src/backends/cuda/qwen3_5/qwen3_5.backend.json +++ b/src/backends/cuda/qwen3_5/qwen3_5.backend.json @@ -13,6 +13,10 @@ "gemm.cu", "runtime.cu" ], + "models": [ + "src/models/cua_s1/native/", + "src/models/open_jev/native/" + ], "build": { "script": "build.sh", "output": "libqwen3_5_cuda.so", From c75379177d8b517b50bbb0056ab3047c68c9a996 Mon Sep 17 00:00:00 2001 From: xiaoyu <1259084489@qq.com> Date: Sat, 10 Oct 2026 09:59:34 +0800 Subject: [PATCH 09/10] cuda: declare vision.cu in the qwen3_5 manifest `build.sh` compiles `"$here"/*.cu`, so `vision.cu` has been built into `libqwen3_5_cuda.so` since the vision work (#47/#64) landed -- the same work that took `CS1_ABI_VERSION` to 5. The manifest described the pre-vision library: nine sources out of the ten in the directory, and none of the `cs1_vision_*` family that file adds to the ABI surface. The checker reads the ABI macro out of the declared sources and does not compare a directory against `sources`, so nothing failed. This PR compared them by hand, against a tree from before the rebase; the rebase is what made that check stale. --- src/backends/cuda/qwen3_5/qwen3_5.backend.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/backends/cuda/qwen3_5/qwen3_5.backend.json b/src/backends/cuda/qwen3_5/qwen3_5.backend.json index eb2e693a..7416777b 100644 --- a/src/backends/cuda/qwen3_5/qwen3_5.backend.json +++ b/src/backends/cuda/qwen3_5/qwen3_5.backend.json @@ -11,7 +11,8 @@ "attention.cu", "gdn_prefill.cu", "gemm.cu", - "runtime.cu" + "runtime.cu", + "vision.cu" ], "models": [ "src/models/cua_s1/native/", From 633c9533007c77dd596e2a0b0a6bf4b3f77cd00e Mon Sep 17 00:00:00 2001 From: levius <2114377220@qq.com> Date: Sat, 10 Oct 2026 22:29:49 +0800 Subject: [PATCH 10/10] cuda: align Qwen backend metadata with current main --- src/backends/cuda/qwen3_5/README.md | 7 ++++++- src/backends/cuda/qwen3_5/qwen3_5.backend.json | 5 +++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/backends/cuda/qwen3_5/README.md b/src/backends/cuda/qwen3_5/README.md index 63825533..9056ca23 100644 --- a/src/backends/cuda/qwen3_5/README.md +++ b/src/backends/cuda/qwen3_5/README.md @@ -1,6 +1,11 @@ # Qwen3.5/3.8 prefill operations -CUDA kernels for a prefill-only Qwen3.5/3.8 forward pass, built into `libqwen3_5_cuda.so` with a C interface ([`ops.h`](ops.h)), so that a Rust model engine loads it at run time and builds without a CUDA toolkit. The Cua-S1 and Open-Jev native workers share the layer loop and buffers in [`src/models/qwen3_5/native/`](../../../models/qwen3_5/native/). +CUDA kernels for a prefill-only Qwen3.5/3.8 forward pass, built into `libqwen3_5_cuda.so` with a C interface ([`ops.h`](ops.h)), so that a Rust model engine loads it at run time and builds without a CUDA toolkit. The Cua-S1, Open-Jev and experimental JEV-VL native workers share the layer loop and buffers in [`src/models/qwen3_5/native/`](../../../models/qwen3_5/native/). + +The backend manifest remains `experimental`. Maintained CI checks source/build +metadata and CPU tests; it does not compile this backend with NVCC or establish +GPU numerical parity. Current-head CUDA compilation and GPU regressions remain +separate release checks. ```sh src/backends/cuda/qwen3_5/build.sh [compute capability, default 89] diff --git a/src/backends/cuda/qwen3_5/qwen3_5.backend.json b/src/backends/cuda/qwen3_5/qwen3_5.backend.json index 7416777b..37484cef 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": 5, + "abi_version": 6, "status": "experimental", "sources": [ "ops.h", @@ -16,7 +16,8 @@ ], "models": [ "src/models/cua_s1/native/", - "src/models/open_jev/native/" + "src/models/open_jev/native/", + "src/models/jev_vl/native/" ], "build": { "script": "build.sh",