loopyt cbb28a8d65 feat(h20): make the pure-torch kernel references usable without TileLang
The bring-up plan for a card that cannot build TileLang is to run the model on
`tile_kernels.torch` -- pure-torch implementations of nearly every kernel the forward pass
calls -- and replace the hot ones with Triton afterwards. That plan was blocked at the
import: `import tile_kernels.torch` dragged in the whole compiled stack.

Three eager imports were responsible, and each is now lazy or optional:

* the top-level `__init__` re-exported every compiled kernel; it resolves them on access
* `quant/__init__` did the same for 13 modules, and `import quant.common` runs it first
* `quant/common.py` mixed host-side config dataclasses with `@T.macro` fragments; the
  dataclasses now import without TileLang, the macros are behind a guard, and its three
  dtype-handle members resolve TileLang when called rather than when imported

`config.py` also only needed TileLang for three constants, now literals.

One thing that looks like it should work and does not: a module-wide
`from __future__ import annotations`. It also stringifies the annotations inside `@T.macro`
bodies, which TileLang's macro parser evaluates itself, and it fails there. Only the four
host-side annotations are quoted, individually.

The patches are idempotent and reversible, and `verify_tile_kernels_patch.py` proves the
surface is unchanged where TileLang is installed -- the kernel modules' wildcard imports of
`quant.common` still see the macros, `from pkg import sub` still works, config knobs still
take effect.

The other half of the question, whether the references are numerically trustworthy, is
answered by `compare_with_cuda.py`: on the recipe the engine actually uses on Hopper,
per_token_cast, cast_back, swiglu_forward_and_per_token_cast and engram_hash are bitwise
identical, and apply_rotary differs by one bf16 ULP on one element in 65536. The quantisation
chain being bitwise equal is what makes a torch-first bring-up safe rather than approximate;
a cast that were merely close would change tokens downstream.

Not yet done, and recorded in the doc: MHC and the MoE gate still need their own comparison
before anyone assumes they match, and the routing layer that points deepinfer's 25 call sites
at these references does not exist yet.
2026-10-22 16:08:46 +08:00

54 lines
2.2 KiB
Python

"""Make TileLang unimportable, the way it is on a card where it was never installed.
Two independent switches, because the two scenarios need different things:
* ``DEEPINFER_SIMULATE_NO_TILELANG=1`` -- ``tilelang`` and ``tvm_ffi`` are gone. This is the
target state of the torch-reference bring-up: `tile_kernels` itself is installed (it is a
plain Python package), but none of its compiled kernels can be built or loaded.
* ``DEEPINFER_SIMULATE_NO_TILE_KERNELS=1`` -- additionally removes `tile_kernels`. Stricter
than any real deployment needs to be, useful only to prove that no interface module reaches
for it at import time.
Blocking by meta path rather than by uninstalling matters because the image ships its own
tilelang in system site-packages, and a venv that inherits it silently gets the package back.
It also must live in `sitecustomize`: the engine spawns one process per data-parallel rank,
and an import in the parent would not follow them.
"""
import os
_blocked = []
if os.environ.get("DEEPINFER_SIMULATE_NO_TILELANG"):
_blocked += ["tilelang", "tvm_ffi"]
if os.environ.get("DEEPINFER_SIMULATE_NO_TILE_KERNELS"):
_blocked += ["tile_kernels"]
if _blocked:
import sys
from importlib.abc import MetaPathFinder
_BLOCKED = tuple(_blocked)
class _Blocked(MetaPathFinder):
def find_spec(
self,
fullname: str,
path: object = None,
target: object = None,
) -> None:
if fullname.split(".")[0] in _BLOCKED:
# Raise rather than return None: a finder returning None only says "not mine",
# and the remaining finders would happily locate the installed package. Raising
# is the only way a meta path entry can actually make something unimportable.
# `importlib.util.find_spec` will raise too, so probes must expect that.
raise ModuleNotFoundError(
f"No module named {fullname!r} (blocked: simulating a host without it)",
name=fullname,
)
return None
sys.meta_path.insert(0, _Blocked())
for _name in list(sys.modules):
if _name.split(".")[0] in _BLOCKED:
del sys.modules[_name]