Source tree is identical to MetaX-MACA/TileOPs-Metax dev at f02d3d8; this commit carries only the summer-camp documentation and PR templates. Content verified by running everything on a real MetaX C500 (MACA 3.7.1.5, torch 2.8.0+metax3.7.1.3, tilelang 0.1.10+cuda.gitf549117c, sGPU slice 16000 MiB). Installation (high severity). The documented `make install`, `pip install tileops`, and bare `python3 -m venv .venv` steps destroy a working MACA environment. The container's TileLang is an in-place source build imported via PYTHONPATH, so pip reports it as absent and resolves the official CUDA wheel over it; a venv without --system-site-packages cuts off the MetaX PyTorch build and the ABI-coupled apache-tvm-ffi. Replace those steps with the PYTHONPATH setup, document that tileops needs no install at all, and note that --no-deps is the only safe install form (as scripts/ci/install_tileops.sh already does). Flag -c constraints.txt as CUDA-CI-only for the same ABI reason. Add TileLang provenance and backend checks to the verification list, which previously covered mx-smi, torch and einops but not the component most likely to be wrong. Quick start. GemmOp(M, N, K, dtype=...) does not match the implementation -- GemmOp is input-inferred and takes only trans_a/trans_b. Fix the signature and document the trans_b default, keeping the original M,N,K of 1024,1024,512, which passes on C500 via the MACA dispatch path. New sections 1.2 and 1.3 in the migration guide. Document the is_maca() dispatch to *_maca.py kernels, and that a gated kernel does not imply an unusable Op: GemmKernel declares [89, 90] and is gated on C500, yet GemmOp works because it dispatches to gemm_maca.py ([80, 86, 89, 90]). Availability must be judged from what the Op layer dispatches to, not from one kernel's supported_archs. List the 20 declarations that exclude 80 as unsuitable migration targets, and note that adding a *_maca.py kernel plus dispatch is a good target instead. Record that get_sm_version() reuses NVIDIA's encoding, so C500 reports 80 while sharing nothing with Ampere, and that the raw "architecture 80" message names no device. Document that a usable Op still has shape limits: SoftmaxFwdOp fails above a 1024-wide reduction dimension (mcErrorInvalidValue), independent of row count. Document that a parent process which has imported tilelang will see any subprocess that imports it again SIGKILLed with no output, which aborts tests/test_validate_manifest.py at exit 137, and give the deselect workaround. Roofline. Record the sGPU slice quota and state whether peaks are whole-card or slice-scaled; dividing a slice measurement by a whole-card peak yields an unexplainable efficiency. Verified on C500 against this tree: validate_manifest.py exit 0; 29 passed across test_ops_manifest.py, test_kernel_map_install.py and benchmarks/tests; GemmOp passes at 1024x1024x512, 1024^3 and 4096^3; the documented quick-start snippet and every self-check command run as written. pre-commit and ruff are unavailable in this container (installing them would invoke pip dependency resolution), so formatting was checked via git diff --check and end-of-file newlines instead. Squashed documentation commits by Beckylu <648245013@qq.com> and FrRay <1077376663@qq.com> covering the summer-camp guide, PR templates, and README translations. Co-Authored-By: Beckylu <648245013@qq.com> Co-Authored-By: FrRay <1077376663@qq.com> |
||
|---|---|---|
| .. | ||
| Dockerfile | ||
| Dockerfile.metax | ||
| README.md | ||
| entrypoint.sh | ||
README.md
CI runner image
Multi-stage image for the self-hosted GPU runner. It bakes a tilelang wheel (compiled once from a pinned commit, or installed from a PyPI release) plus the test/benchmark stack onto a public CUDA base, so CI never recompiles tilelang per PR.
Built manually on a GPU host (needs nvcc), then pushed to ghcr.io. It is not
built in CI.
Prerequisites
- An NVIDIA GPU host with a CUDA 12.9-capable driver and
nvcc. - Docker with BuildKit enabled (
DOCKER_BUILDKIT=1). - Run from the repository root — the build context must contain
constraints.txt,scripts/ci/verify_runtime_stack.py, and.github/runner/entrypoint.sh(the Dockerfile copies all three).
Build
Provide tilelang one of two ways — pass exactly one of these build-args:
- main commit:
--build-arg TILELANG_GIT_SHA=<commit>— shallow-fetches and compiles that commit. The Dockerfile carries no commit literal; the commit you pass is the single source of truth, and the image tag records it. - release:
--build-arg TILELANG_VERSION=<version>—pip install tilelang==<version>.
# from the repository root (main-commit mode)
DOCKER_BUILDKIT=1 docker build \
-f .github/runner/Dockerfile \
--target final \
--build-arg TILELANG_GIT_SHA=65dbc9837beedf6882a40a08e18ea571d92fd6a5 \
-t ghcr.io/tile-ai/tileops-runner:65dbc98 \
.
Tag with the tilelang commit's short SHA (:65dbc98). If you rebuild the same commit,
add a numeric suffix (:65dbc98-2).
Roll out an updated runner image
Changes to this Dockerfile are not picked up by CI automatically. After a Dockerfile change lands, rebuild and tag the image from a GPU host using the build command above, then push it:
docker push ghcr.io/tile-ai/tileops-runner:<new-tag>
Then redeploy the self-hosted runner launcher to use the new tag (maintainer task, done outside this repository). Merging the TileOPs PR only changes the image recipe; the live self-hosted runners keep using their existing image until that manual rollout is done.
Build args
| Arg | Default | Purpose |
|---|---|---|
TILELANG_GIT_SHA |
(none) | tilelang commit to shallow-clone and compile (main mode). |
TILELANG_VERSION |
(none) | tilelang PyPI version to pip install (release mode). |
BASE_IMAGE |
nvidia/cuda:12.9.1-devel-ubuntu22.04 |
Public CUDA devel base (Python 3.12 via deadsnakes). |
MAX_JOBS |
64 |
Parallelism for the tilelang / FA2 / FA3 source builds. |
NVCC_THREADS |
4 |
Per-nvcc threads. |
DEEPGEMM_GIT_SHA |
c9f8b34dcdacc20aa746b786f983492c51072870 |
DeepGEMM commit for the grouped-GEMM benchmark baseline (v2.1.1.post3). |
RUNNER_VERSION |
2.334.0 |
GitHub Actions runner version baked into final. |
Set exactly one of TILELANG_GIT_SHA / TILELANG_VERSION; the build fails fast if neither is set.
Stages (--target)
| Stage | Contents |
|---|---|
runtime |
Python 3.12 + torch / torchvision / torchaudio 2.10.0 / 0.25.0 / 2.10.0 +cu129 + triton 3.6.0 + tilelang build/runtime deps (incl. apache-tvm-ffi 0.1.11). No tilelang itself. |
post-fa3 |
runtime + pytest / pytest-xdist / ruff + FlashAttention-3 (built from the hopper/ source). |
fa2 |
post-fa3 + FlashAttention-2 (flash-attn 2.8.3, source-built in its own layer so changes to the bench loop never recompile it). |
fullstack |
fa2 + flash-linear-attention 0.4.2 + vLLM 0.19.1 + mamba-ssm 2.3.1 + DeepGEMM 2.1.1.post3, then flashinfer-python/-cubin upgraded to 0.6.11.post2 (--no-deps, so torch stays +cu129). sgl-kernel is not installed. |
tilelang |
fullstack + the tilelang wheel (--no-deps), then the build-time guard. Built last so a SHA bump rebuilds only this layer. |
final |
tilelang + the GitHub Actions runner (no TileOPs source baked). |
Build an earlier stage for debugging with --target runtime (etc.).
The tilelang stage ends by running scripts/ci/verify_runtime_stack.py (GPU-free): it fails
the build unless tilelang imports, the installed apache-tvm-ffi sits inside the tilelang
wheel's declared range, and torch is still the cu129 build.
Verify the built image
docker run --rm --gpus all ghcr.io/tile-ai/tileops-runner:65dbc98 python - <<'PY'
import torch
print("torch", torch.__version__, "cuda", torch.version.cuda) # expect 2.10.0+cu129, cuda 12.9
import tilelang; print("tilelang", tilelang.__version__)
import flashinfer, flashinfer_cubin
print("flashinfer", flashinfer.__version__)
from pathlib import Path
cubin_dir = Path(flashinfer_cubin.get_cubin_dir())
probe_dir = cubin_dir / "flashinfer" / "trtllm" / "gemm" / "_tileops_write_probe"
probe_dir.mkdir(parents=True, exist_ok=True)
(probe_dir / "probe.txt").write_text("ok")
print("flashinfer cubin write OK")
import selective_scan_cuda
import mamba_ssm
from mamba_ssm.ops.triton.ssd_combined import mamba_chunk_scan_combined
assert mamba_chunk_scan_combined is not None
print("mamba", mamba_ssm.__version__)
import deep_gemm
print("deep_gemm", deep_gemm.__version__)
# cuBLAS probe: matmul / bmm / einsum on the GPU
a = torch.randn(512, 512, device="cuda", dtype=torch.float16)
b = torch.randn(512, 512, device="cuda", dtype=torch.float16)
assert torch.matmul(a, b).isfinite().all()
ab = torch.randn(8, 128, 128, device="cuda", dtype=torch.float16)
assert torch.bmm(ab, ab).isfinite().all()
assert torch.einsum("bik,bkj->bij", ab, ab).isfinite().all()
print("cuBLAS probe OK")
PY
Then run the smoke tests against a checkout of this repo:
docker run --rm --gpus all -v "$PWD:/src" -w /src \
ghcr.io/tile-ai/tileops-runner:65dbc98 \
bash -c 'scripts/ci/install_tileops.sh && pytest -m smoke'
install_tileops.sh installs TileOPs --no-deps against the baked stack; it fails fast if
tilelang is missing (the image provides it).
Run as a self-hosted runner
entrypoint.sh registers an ephemeral runner (one job per container), then deregisters on
exit. Provide a registration token and the target URL; bind-mount the host cache. The
entrypoint removes RUNNER_TOKEN from the environment before the runner starts, so jobs
cannot read the registration token.
docker run -d --gpus all \
-e RUNNER_URL=https://github.com/tile-ai/TileOPs \
-e RUNNER_TOKEN=<registration-token> \
-e RUNNER_LABELS=self-hosted,tile-ops,venv \
-v <host-cache-dir>:/ci-cache \
ghcr.io/tile-ai/tileops-runner:65dbc98
The image sets cache env vars (TILELANG_CACHE_DIR, TRITON_CACHE_DIR, PIP_CACHE_DIR, …)
under /ci-cache; the directories are pre-created so the container also works unmounted.
Bumping the tilelang commit
A commit (or release) bump always rebuilds, but never edits the Dockerfile: rebuild with a
new --build-arg TILELANG_GIT_SHA=<commit> (or TILELANG_VERSION=<version>) and a new
:<short-sha> tag, push to ghcr.io, then point the runner at the new tag. Because tilelang
is the last stage, only its layer recompiles — the bench layers (FA2 / FA3 / vLLM / …) stay
cached. Switching between a release and a main commit is the same — only the build-arg and tag
change.