ajh-code commited on
Commit
bef3c07
·
verified ·
1 Parent(s): 4ed4b92

Add runtime/torch_ops_native.py

Browse files
Files changed (1) hide show
  1. runtime/torch_ops_native.py +233 -0
runtime/torch_ops_native.py ADDED
@@ -0,0 +1,233 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Loader and fake/meta shim for the compiled resident NVFP4 torch op.
2
+
3
+ The compiled bridge lives in ``native/torch_op`` and, when built, moves CUDA
4
+ execution out of Python+ctypes and into a C++ dispatcher implementation that
5
+ calls the existing resident C ABI directly.
6
+
7
+ This shim keeps CPU/meta tests lightweight:
8
+
9
+ - if the compiled bridge exists, load it first;
10
+ - otherwise define a temporary Python schema fallback for shape-only tests;
11
+ - always register fake/meta implementations in Python.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import atexit
17
+ from pathlib import Path
18
+
19
+ import torch
20
+
21
+ from packed_nvfp4_linear import PackedNvfp4Linear, scale_layout
22
+
23
+
24
+ RELEASE_ROOT = Path(__file__).resolve().parents[1]
25
+ DEFAULT_EXTENSION_PATH = RELEASE_ROOT / "runtime" / "libmage_nvfp4_torch_op.so"
26
+
27
+ OP_NAMESPACE = "mage_nvfp4"
28
+ OP_NAME = "sm120_linear_native"
29
+ OP_QUALNAME = f"{OP_NAMESPACE}::{OP_NAME}"
30
+
31
+ _SCHEMA_FALLBACK_LIB: torch.library.Library | None = None
32
+ _META_LIB: torch.library.Library | None = None
33
+ _EXTENSION_LOADED = False
34
+ _FAKE_REGISTERED = False
35
+ _META_REGISTERED = False
36
+
37
+
38
+ def _expected_weight_bytes(out_features: int, in_features: int) -> int:
39
+ if out_features <= 0 or out_features % 8:
40
+ raise ValueError("resident NVFP4 requires out_features divisible by 8")
41
+ if in_features <= 0 or in_features % 32:
42
+ raise ValueError("resident NVFP4 requires in_features divisible by 32")
43
+ return (out_features * in_features) // 2
44
+
45
+
46
+ def _check_common_shapes(
47
+ input: torch.Tensor,
48
+ packed_weight: torch.Tensor,
49
+ weight_scales: torch.Tensor,
50
+ weight_scale: torch.Tensor,
51
+ bias: torch.Tensor | None,
52
+ in_features: int,
53
+ out_features: int,
54
+ ) -> None:
55
+ if input.ndim < 1:
56
+ raise ValueError("resident NVFP4 input must have at least one dimension")
57
+ if input.dtype != torch.bfloat16:
58
+ raise TypeError("resident NVFP4 input must be bfloat16")
59
+ if input.requires_grad:
60
+ raise RuntimeError("resident NVFP4 torch op is inference-only")
61
+ if int(input.shape[-1]) != int(in_features):
62
+ raise ValueError(
63
+ f"expected input last dimension {in_features}, got {tuple(input.shape)}"
64
+ )
65
+ if packed_weight.dtype != torch.uint8 or packed_weight.ndim != 1:
66
+ raise ValueError("packed_weight must be a 1D uint8 tensor")
67
+ if weight_scales.dtype != torch.uint8 or weight_scales.ndim != 1:
68
+ raise ValueError("weight_scales must be a 1D uint8 tensor")
69
+ if weight_scale.dtype != torch.float32 or weight_scale.numel() != 1:
70
+ raise ValueError("weight_scale must be a scalar or length-1 float32 tensor")
71
+ if bias is not None and (
72
+ bias.dtype != torch.bfloat16 or bias.ndim != 1 or int(bias.numel()) != int(out_features)
73
+ ):
74
+ raise ValueError("bias must be a 1D bfloat16 tensor with length out_features")
75
+ for tensor in (packed_weight, weight_scales, weight_scale, bias):
76
+ if tensor is not None and tensor.device != input.device:
77
+ raise ValueError("input and resident buffers must be on the same device")
78
+ if tensor is not None and not tensor.is_contiguous():
79
+ raise ValueError("resident packed buffers must be contiguous")
80
+
81
+ expected_weight_bytes = _expected_weight_bytes(int(out_features), int(in_features))
82
+ expected_scale_bytes = scale_layout(int(in_features), int(out_features)).num_bytes
83
+ if int(packed_weight.numel()) != expected_weight_bytes:
84
+ raise ValueError(
85
+ f"packed_weight size mismatch: expected {expected_weight_bytes}, got {packed_weight.numel()}"
86
+ )
87
+ if int(weight_scales.numel()) != expected_scale_bytes:
88
+ raise ValueError(
89
+ f"weight_scales size mismatch: expected {expected_scale_bytes}, got {weight_scales.numel()}"
90
+ )
91
+
92
+
93
+ def _logical_output(input: torch.Tensor, out_features: int) -> torch.Tensor:
94
+ return input.new_empty((*input.shape[:-1], int(out_features)), dtype=torch.bfloat16)
95
+
96
+
97
+ def ensure_native_sm120_schema(
98
+ *,
99
+ extension_path: str | Path = DEFAULT_EXTENSION_PATH,
100
+ allow_python_schema_fallback: bool = True,
101
+ ) -> bool:
102
+ global _EXTENSION_LOADED, _SCHEMA_FALLBACK_LIB
103
+ extension_path = Path(extension_path)
104
+ if not _EXTENSION_LOADED and extension_path.is_file():
105
+ torch.ops.load_library(str(extension_path.resolve()))
106
+ _EXTENSION_LOADED = True
107
+ return True
108
+ if _EXTENSION_LOADED:
109
+ return True
110
+ if not allow_python_schema_fallback:
111
+ return False
112
+ if _SCHEMA_FALLBACK_LIB is None:
113
+ lib = torch.library.Library(OP_NAMESPACE, "FRAGMENT")
114
+ lib.define(
115
+ "sm120_linear_native(Tensor input, Tensor packed_weight, Tensor weight_scales, Tensor weight_scale, Tensor? bias, int in_features, int out_features) -> Tensor"
116
+ )
117
+ lib.define("clear_native_contexts() -> ()")
118
+ _SCHEMA_FALLBACK_LIB = lib
119
+ return False
120
+
121
+
122
+ def _register_meta_impl() -> None:
123
+ global _META_LIB, _META_REGISTERED
124
+ if _META_REGISTERED:
125
+ return
126
+ _META_LIB = torch.library.Library(OP_NAMESPACE, "IMPL", "Meta")
127
+ _META_LIB.impl(
128
+ OP_NAME,
129
+ lambda input, packed_weight, weight_scales, weight_scale, bias, in_features, out_features: (
130
+ _check_common_shapes(
131
+ input,
132
+ packed_weight,
133
+ weight_scales,
134
+ weight_scale,
135
+ bias,
136
+ in_features,
137
+ out_features,
138
+ ),
139
+ _logical_output(input, int(out_features)),
140
+ )[1],
141
+ )
142
+ _META_REGISTERED = True
143
+
144
+
145
+ def _register_fake_impl() -> None:
146
+ global _FAKE_REGISTERED
147
+ if _FAKE_REGISTERED:
148
+ return
149
+
150
+ @torch.library.register_fake(OP_QUALNAME)
151
+ def _fake(
152
+ input: torch.Tensor,
153
+ packed_weight: torch.Tensor,
154
+ weight_scales: torch.Tensor,
155
+ weight_scale: torch.Tensor,
156
+ bias: torch.Tensor | None,
157
+ in_features: int,
158
+ out_features: int,
159
+ ) -> torch.Tensor:
160
+ _check_common_shapes(
161
+ input,
162
+ packed_weight,
163
+ weight_scales,
164
+ weight_scale,
165
+ bias,
166
+ in_features,
167
+ out_features,
168
+ )
169
+ return _logical_output(input, int(out_features))
170
+
171
+ _FAKE_REGISTERED = True
172
+
173
+
174
+ def initialize_native_sm120_op(
175
+ *,
176
+ extension_path: str | Path = DEFAULT_EXTENSION_PATH,
177
+ allow_python_schema_fallback: bool = True,
178
+ ) -> bool:
179
+ loaded = ensure_native_sm120_schema(
180
+ extension_path=extension_path,
181
+ allow_python_schema_fallback=allow_python_schema_fallback,
182
+ )
183
+ _register_meta_impl()
184
+ _register_fake_impl()
185
+ return loaded
186
+
187
+
188
+ def close_native_contexts() -> None:
189
+ """Synchronize and release native contexts when the compiled bridge is loaded."""
190
+ if _EXTENSION_LOADED:
191
+ torch.ops.mage_nvfp4.clear_native_contexts()
192
+
193
+
194
+ def _quiet_atexit_close() -> None:
195
+ try:
196
+ close_native_contexts()
197
+ except Exception:
198
+ # CUDA may already be shutting down. Explicit close_native_contexts()
199
+ # is the auditable path; atexit is only a best-effort fallback.
200
+ pass
201
+
202
+
203
+ class PackedNvfp4LinearNativeOp(PackedNvfp4Linear):
204
+ """Thin wrapper over resident packed buffers using the native torch op."""
205
+
206
+ def forward(self, input: torch.Tensor) -> torch.Tensor:
207
+ if input.device.type not in {"cuda", "meta"}:
208
+ raise ValueError("PackedNvfp4LinearNativeOp requires a CUDA or meta input")
209
+ return torch.ops.mage_nvfp4.sm120_linear_native(
210
+ input,
211
+ self.packed_weight,
212
+ self.weight_scales,
213
+ self.weight_scale,
214
+ self.bias,
215
+ self.in_features,
216
+ self.out_features,
217
+ )
218
+
219
+
220
+ initialize_native_sm120_op()
221
+ atexit.register(_quiet_atexit_close)
222
+
223
+
224
+ __all__ = [
225
+ "DEFAULT_EXTENSION_PATH",
226
+ "OP_NAME",
227
+ "OP_NAMESPACE",
228
+ "OP_QUALNAME",
229
+ "PackedNvfp4LinearNativeOp",
230
+ "close_native_contexts",
231
+ "ensure_native_sm120_schema",
232
+ "initialize_native_sm120_op",
233
+ ]