Skip to main content

cache-dit-operator-migration

Use when doing operator migration or kernel migration for cache-dit; porting kernels from nunchaku, deepcompressor, or other repos; designing CUDA operator registration and public wrappers; wiring build and packaging for optional CUDA extensions; or reviewing an operator migration plan. cache-dit maintains CUDA operators only, so this skill intentionally excludes Triton and CuTe DSL work. Do not use for blind copy-paste ports.

Informations de source

Dépôt
vipshop/cache-dit
Dernière activité de la source
30 octobre 2026 à 08:55
Langue détectée de SKILL.md
anglais
Étoiles
1 298
Forks
93

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
cache-dit-operator-migration
description
Use when doing operator migration or kernel migration for cache-dit; porting kernels from nunchaku, deepcompressor, or other repos; designing CUDA operator registration and public wrappers; wiring build and packaging for optional CUDA extensions; or reviewing an operator migration plan. cache-dit maintains CUDA operators only, so this skill intentionally excludes Triton and CuTe DSL work. Do not use for blind copy-paste ports.
argument-hint
Describe the operator family, source repo, target public API, required dtypes or architectures, and current migration status.
user-invocable
true
# Operator Migration for cache-dit ## Goal Migrate one operator or kernel family into cache-dit in a way that is: - semantically correct - aligned with cache-dit repository conventions - safe to import when optional native extensions are absent - validated at multiple layers instead of by one smoke test This skill is for migration work that touches native CUDA code, Python wrappers, operator registration, build packaging, or quantized module integration. ## Backend Policy: CUDA Only This is a hard, long-term constraint, not a per-task preference. cache-dit maintains **CUDA operators exclusively**. Every operator that lives long term in the repository is a CUDA kernel under `csrc/kernels/`, reachable through a `torch.library` schema in the `cache_dit_cuda_ops` namespace. Therefore: 1. Do not add a Triton or CuTe DSL operator to cache-dit. Not as a fast path, not as a portable fallback, not "just for A100", not temporarily. 2. Do not recreate `src/cache_dit/kernels/triton/`, `src/cache_dit/kernels/cutedsl/`, or any per-backend subpackage. Those trees were removed and are covered by regression guards, see `tests/kernels/test_ops_dispatch.py`. 3. Do not reintroduce a backend selector. `kernels/backend.py`, `kernels/ops.py`, the `KernelBackend` enum, and the `_resolve_backend` / `_select_*_backend` dispatch helpers are gone; `test_ops_dispatch.py` asserts they stay gone. A single CUDA path with a clear error message replaces the dispatch table. 4. If a source repository only has a Triton implementation, read it for the algorithm and rewrite the kernel in CUDA. Treat Triton as pseudocode, never as a target language. 5. If someone asks for Triton or CuTe DSL support, say the maintenance policy excludes it, and offer the CUDA route instead. The reason is maintenance cost: three parallel backends with three registration layers, three fake-registration paths, and three build stories cost more than the wins they deliver, and the CUDA path is the only one that ships in the release wheel. Use `triton-kernel` or `cute-dsl-kernel` for standalone kernel work outside cache-dit; do not let those workflows leak into this repository. ## When to Use Use this skill when you need to: - migrate a CUDA operator from another repo into cache-dit - port a nunchaku operator or kernel family into cache-dit - add a new CUDA operator domain (a new native directory plus its Python module) - decide what native files are actually required for a migration - design cache-dit public wrappers for a newly migrated operator - register low-level ops through `torch.library` in the `cache_dit_cuda_ops` namespace - add optional-extension build logic, submodule checks, or packaging guards - design layered validation for a migrated operator - review whether an operator migration plan is thoughtful or mechanical Do not use this skill for: - generic model integration with no operator or kernel work - pure Python feature work unrelated to kernels or extensions - Triton or CuTe DSL operator work, which this repository does not maintain - blind "copy upstream into csrc" execution ## Core Rule Do not mechanically replay upstream structure. Treat the source repository as the reference for semantics, not as the required layout. Before writing code, answer these questions: 1. What behavior is essential to preserve? 2. What is the smallest native and Python closure needed to preserve that behavior? 3. Which names should remain source-compatible, and which should be renamed to match cache-dit conventions? 4. What must be public, and what should remain private implementation detail? 5. Which tests prove the migration works, instead of merely compiling? If those questions are not answered yet, do not start copying files. ## Reference Style Rule Use portable references only. - For cache-dit files, use repo-relative paths such as `src/cache_dit/kernels/_comm.py` or `tests/kernels/test_cuda_fp8_comm_ops.py`. - For sibling or external repos, use repository-relative or GitHub-searchable paths such as `nunchaku/nunchaku/models/linear.py` or `deepcompressor/deepcompressor/backend/nunchaku/utils.py`. - Do not write machine-local absolute paths such as `/abs/path/to/workspace/...` into the skill, its supporting documentation, or any error message you add to cache-dit. ## Current Layout The repository is CUDA-only after the operator refactor, so there is exactly one shape to follow. ### Native tree ``` csrc/kernels/ attn/attn.{h,cpp} # pybind TU for cache_dit._C_attn attn/merge_states.cu # fused attention-state merge comm/comm_ops.{h,cpp} # pybind TU for cache_dit._C_comm comm/fp8_comm.cu # FP8 per-token / QKV-permute comm kernels svdq/ # W4A4 GEMM family, split into core/ int4/ fp4/ pybind.cpp torch.{h,cpp} ops.h zgemm.h dispatch_utils.h Tensor.h common.h csrc/third_party/spdlog/ # git submodule, SVDQuant logging only ``` Two naming facts that trip people up: - A native directory is **not** an extension module, but the operator family decides which extension hosts its files: `attn/` is compiled into `cache_dit._C_attn`, `comm/` into `cache_dit._C_comm`. A kernel never gets registered in another family's pybind TU. - C++ namespaces follow the extension, not the directory: attention-state merge lives in `namespace cache_dit::attn` and talks to Python through `csrc/kernels/attn/attn.cpp`. ### Python tree ``` src/cache_dit/kernels/ __init__.py # flat public surface + __all__, re-exported from domain modules _attn.py # attention-state merge: loader for cache_dit._C_attn + registration _comm.py # FP8 comm ops: loader for cache_dit._C_comm + registration _svdquant.py # SVDQ W4A4 ops, loader for cache_dit._C_svdquant (+ utils submodule) ``` There is no `ops.py`, no `cuda/`, no `_ops_registery.py`, no `backend.py`, and no per-backend package. Each domain module owns its own registrations and its own extension loader. ### Extensions and build flags | Extension | Built by | Contents | |---|---|---| | `cache_dit._C_svdquant` | `CACHE_DIT_BUILD_SVDQUANT=1` | SVDQ W4A4 GEMM + quantizers; exposes `ops` and `utils` submodules | | `cache_dit._C_comm` | `CACHE_DIT_BUILD_COMM=1`, or any `CACHE_DIT_BUILD_SVDQUANT=1` build | FP8 comm ops; exposes an `ops` submodule | | `cache_dit._C_attn` | `CACHE_DIT_BUILD_ATTN=1`, `CACHE_DIT_BUILD_COMM=1`, or any `CACHE_DIT_BUILD_SVDQUANT=1` build | Attention-state merge; exposes an `ops` submodule | `CACHE_DIT_BUILD_SVDQUANT=1` enables all three extensions, because the release wheel must ship them. The optional operator extensions (`_C_comm`, `_C_attn`) are gated to SM89+; the SVDQuant extension filters out sm90 targets its INT4 kernels cannot use. Default arch targets are `("89", "90a", "100", "120a")`, override with `CACHE_DIT_CUDA_ARCH_LIST` or `TORCH_CUDA_ARCH_LIST`. An explicit `CACHE_DIT_BUILD_ATTN=1` raises when no SM target resolves, so a requested extension is never dropped silently. ## Phase 0: Gather Before Coding Collect the migration inputs first. ### Required inputs 1. Source operator and source repo Example: `nunchaku/nunchaku/ops/gemm.py` plus the native files it depends on. 2. Target cache-dit user-facing surface Example: a low-level op wrapper, a quantized module, or both. 3. Required dtypes, architectures, and scope boundaries Example: "INT4 CUDA is required now; FP4 implementation may be retained but not gate current validation." 4. Build and packaging requirements Example: which extension hosts the op, which flag enables it, whether a submodule is needed. 5. Validation target Example: import safety, low-level parity, module parity, end-to-end inference, or shape rejection. ### Gather checklist - Identify the source operator entry points. - Identify the native files that implement them. - Identify helper files that are truly required by those implementations. - Identify which existing cache-dit domain module should host the migrated behavior. - Identify the minimum feature slice that must work first. - Identify what will explicitly not be validated in the current milestone. ## Phase 1: Survey the Existing Design Inspect both sides before making edits. ### Survey the source implementation Look for: - the true call chain from public API to kernel launch - required helper headers, interop layers, dispatch utilities, and packers - runtime assumptions such as shape, rank, alignment, architecture, or dtype restrictions - dependency assumptions such as vendored headers, submodules, or environment variables - test coverage that already encodes behavior worth preserving - whether the source is CUDA already; if not, note that the kernel must be rewritten ### Survey cache-dit integration points Anchor files: - `src/cache_dit/kernels/__init__.py` - the public surface - `src/cache_dit/kernels/_<domain>.py` - the module that will host the op - `csrc/kernels/<domain>/` - the native side - `setup.py` - `_required_<ext>_sources()`, `_missing_<ext>_sources()`, `_get_<ext>_extension()` - `tests/kernels/` - operator tests - `tools/inspect_release_wheel.py` - release wheel content gates Ask these questions while surveying: 1. Which existing domain module is the right host, or is a new domain justified? 2. Does the op belong in `cache_dit._C_comm`, `cache_dit._C_attn`, or a new/dedicated extension module? 3. How should extension loading fail when the op is unavailable? 4. Is there already a naming convention in `cache_dit_cuda_ops` for this operator family? 5. Does `test_ops_dispatch.py` already list the public names this migration will extend? ## Phase 2: Decide the Migration Shape Make the design decisions before editing files. ### 1. Freeze the public surface first Define the cache-dit-facing API early. Examples of questions to settle: - Which operator names should be exposed publicly? - Should the public API be low-level only, module-level only, or both? - Should internal backend toggles be hidden from users? - Should wrapper functions be explicit rather than `partial(...)` so editors and type tools can see the real signature? Default rule: keep tuning and launch details private unless there is a strong user-facing reason to expose them. ### 2. Migrate the minimal viable closure Do not import an entire subsystem if only one slice is needed. Usually migrate: - the kernel implementation files that are actually on the call path - the minimum helper headers or Python utilities they require - the binding TU entry, the loader, the registration, and the wrappers Usually do not migrate yet: - unrelated kernels in the same source repo directory - optimization branches that are not needed for the current milestone - extra tooling, benchmark harnesses, or framework abstractions with no direct execution path impact ### 3. Preserve semantics before cleanup During the first migration pass: - preserve behavior first - preserve shape and dtype rules first - preserve dataflow first Do not mix the migration with optional cleanups such as naming polish, API reshaping, or algorithmic changes unless they are necessary for repository consistency or import safety. ## Phase 3: Implement the Migration Apply changes from lowest level to highest level. ### A. Native code and dependency boundary 1. Move only the required native closure into `csrc/kernels/<domain>/`. 2. Put new code in `namespace cache_dit::<domain>` and keep upstream helper names internal with an anonymous namespace. The migrated SVDQ tree keeps its upstream `svdq::` namespace because it is upstream-derived; do not rename it as part of unrelated work. 3. Keep dispatch structure if it is functionally necessary (SVDQ needs its inheritance-based launch dispatch); do not rewrite it just because it looks unfamiliar. 4. Decide dependency strategy explicitly: - vendored in-tree - git submodule (`csrc/third_party/spdlog` is the only one today) - preinstalled system dependency 5. Add or extend the binding TU. `csrc/kernels/attn/attn.cpp`, `csrc/kernels/comm/comm_ops.cpp` and `csrc/kernels/svdq/pybind.cpp` are the only `PYBIND11_MODULE` entry points; new ops in that extension go through the existing entry point, not a new one. A new operator family gets its own `<domain>/{<domain>.h,<domain>.cpp}` pair instead of borrowing another family's TU. ### B. Domain Python module Put everything for one domain in a single private module, `src/cache_dit/kernels/_<domain>.py`. The sections and their order are consistent across `_attn.py`, `_comm.py`, and `_svdquant.py`, and a migration should follow the same shape: ```python _EXTENSION_MODULE_NAME = "cache_dit._C_<ext>" _cached_extension_module: ModuleType | None = None _cached_load_error: Exception | None = None def _load_extension_module() -> ModuleType | None: # import once, cache both the module and the failure def <domain>_extension_is_available() -> bool: ... def <domain>_get_load_error() -> Exception | None: ... def _get_required_ops_module() -> ModuleType: # raise RuntimeError with the exact build command when unavailable def _call_<op>(...) -> None: # thin bridge: shape/pack differences only, no policy torch.library.define("cache_dit_cuda_ops::<op>", "(Tensor x) -> Tensor") @torch.library.impl("cache_dit_cuda_ops::<op>", "CUDA") def _<op>(x: torch.Tensor) -> torch.Tensor: ... @torch.library.register_fake("cache_dit_cuda_ops::<op>") def _fake_<op>(x: torch.Tensor) -> torch.Tensor: ... def <op>(x: torch.Tensor) -> torch.Tensor: """Public wrapper, Sphinx docstring, calls torch.ops.cache_dit_cuda_ops.<op>.""" ``` Rules that the existing modules enforce: - the `torch.library` namespace is always `cache_dit_cuda_ops` - implementations register on the `"CUDA"` dispatch key - fake registrations are named `_fake_<operator_name>`; no anonymous `def _(...)` - allocation and shape math live in the `impl`, the `_call_` bridge only adapts the ABI - dtype and shape validation raises `ValueError` with the actual value, before allocating - the public wrapper calls `torch.ops.cache_dit_cuda_ops.<op>`, never the bridge directly, so `torch.compile` and fake tensors see one path - module `__all__` lists public wrappers; private helpers use a leading underscore and are listed only when tests or sibling modules need them - the module holds no mutable global policy state; its only module-level state is the loader cache and static lookup tables such as dtype-id maps Keeping the loader in the same module as its ops means a domain's failure mode, registration, and public API cannot drift apart. Each domain loads its own extension (`_attn.py` → `cache_dit._C_attn`, `_comm.py` → `cache_dit._C_comm`, `_svdquant.py` → `cache_dit._C_svdquant`); borrow a sibling loader only when the ops really ship in that sibling's extension. ### C. Public surface Expose the operator from `src/cache_dit/kernels/__init__.py` with an explicit re-export and an `__all__` entry: ```python from ._comm import fp8_comm_per_token_quant ``` Default conventions: - one flat namespace, mirroring the domain modules; no subpackage re-export layers - explicit functions rather than `partial(...)` aliases when signature discoverability matters - `__all__` must equal the non-underscore public names, which `test_ops_dispatch.py::test_public_api_matches_all` enforces - each name must be the same object as the one in its domain module, which `test_public_api_is_reexported_from_domain_module` enforces Do not create a `kernels/ops.py` or a registry module to "centralize" this. The centralization was removed on purpose; adding it back is the exact regression the policy forbids. ### D. Higher-level modules and state adaptation If the migration also adds a module abstraction such as a quantized `nn.Module`: 1. keep the module's expected state keys stable 2. adapt upstream raw export keys into cache-dit module keys explicitly 3. do not leak source-repo naming into the public API if cache-dit already has a better convention 4. route the module through the public wrapper, not through `_call_*` or `torch.ops` directly ## Phase 4: Validate in Layers Do not rely on one test. Validation should usually proceed in this order: 1. **Import safety** - importing cache-dit without the optional extension should not crash 2. **API surface** - public names re-exported identically, `__all__` consistent 3. **Low-level smoke** - low-level op runs with expected dtype, device, and shape 4. **Low-level correctness** - compare operator output against a dense or reference implementation 5. **Module correctness** - verify the higher-level module uses the migrated operator path correctly 6. **Fake / compile path** - at least one `register_fake` shape-and-dtype assertion, since every op ships a fake 7. **Round-trip or end-to-end validation** - if serialization, quantization, or pipeline integration exists, test that explicitly 8. **Boundary tests** - unsupported geometry, rank, alignment, or build conditions should fail clearly Matching test files: - `tests/kernels/test_ops_dispatch.py` - re-export identity, `__all__`, and the removed-dispatch guards - `tests/kernels/test_cuda_fp8_comm_ops.py` - FP8 comm correctness and fake paths - `tests/kernels/test_cuda_merge_attn_states.py` - attention-state merge correctness - `tests/kernels/test_svdquant_runtime.py` - SVDQ runtime behavior - `tests/quantization/` - module-level and quantizer behavior When scope is intentionally limited, say so explicitly. Example: - "INT4 CUDA path is the validation gate." - "FP4 code is retained but not currently gated by runtime correctness tests."
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub