liangsu9988 commited on
Commit
f921568
·
verified ·
1 Parent(s): 9a02ec5

Add Cosmos3-Edge BF16 production gates

Browse files
Files changed (1) hide show
  1. tests/test_diffusion_step_ops.py +437 -0
tests/test_diffusion_step_ops.py ADDED
@@ -0,0 +1,437 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Correctness tests for diffusion-step-ops."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import ctypes
8
+ import ctypes.util
9
+ import importlib
10
+ import os
11
+ import sys
12
+ from pathlib import Path
13
+
14
+ import torch
15
+
16
+
17
+ ROOT = Path(__file__).resolve().parents[2]
18
+ PACKAGE = ROOT / "diffusion-step-ops"
19
+ REGISTRATION_INCLUDE = (
20
+ ROOT.parent
21
+ / "kernels"
22
+ / "kernel-builder"
23
+ / "src"
24
+ / "pyproject"
25
+ / "templates"
26
+ / "torch"
27
+ )
28
+
29
+
30
+ class SourceOps:
31
+ def __init__(self, namespace: str) -> None:
32
+ self._ops = getattr(torch.ops, namespace)
33
+
34
+ def add_bf16(self, a, b):
35
+ out = torch.empty_like(a)
36
+ self._ops.add_bf16_out(a, b, out)
37
+ return out
38
+
39
+ def euler_step_bf16(self, latent, velocity, dt):
40
+ out = torch.empty_like(latent)
41
+ self._ops.euler_step_bf16_out(latent, velocity, float(dt), out)
42
+ return out
43
+
44
+ def cfg_combine_into_residual_bf16(self, residual, v_cond, v_uncond, beta):
45
+ self._ops.cfg_combine_into_residual_bf16(residual, v_cond, v_uncond, float(beta))
46
+ return residual
47
+
48
+ def cfg_combine_into_residual_fp16(self, residual, v_cond, v_uncond, beta):
49
+ self._ops.cfg_combine_into_residual_fp16(residual, v_cond, v_uncond, float(beta))
50
+ return residual
51
+
52
+ def teacher_force_first_frame_bf16(self, video_latent, cond_latent):
53
+ self._ops.teacher_force_first_frame_bf16(video_latent, cond_latent)
54
+ return video_latent
55
+
56
+ def motus_decode_postprocess_bf16_to_fp32(self, decoded):
57
+ out = torch.empty(
58
+ (decoded.shape[0], decoded.shape[1], decoded.shape[2] - 1, decoded.shape[3], decoded.shape[4]),
59
+ device=decoded.device,
60
+ dtype=torch.float32,
61
+ )
62
+ self._ops.motus_decode_postprocess_bf16_to_fp32(decoded, out)
63
+ return out
64
+
65
+ def cast_bf16_to_fp32(self, src):
66
+ dst = torch.empty_like(src, dtype=torch.float32)
67
+ self._ops.cast_bf16_to_fp32(src, dst)
68
+ return dst
69
+
70
+ def pack_tail_bf16(self, tail, flat_dim, out=None):
71
+ if out is None:
72
+ out = torch.empty((flat_dim,), device=tail.device, dtype=tail.dtype)
73
+ self._ops.pack_tail_bf16(tail, int(flat_dim), out)
74
+ return out
75
+
76
+ def add_bias_zero_tail_bf16(self, input, bias, valid_cols, out=None):
77
+ if out is None:
78
+ out = torch.empty_like(input)
79
+ self._ops.add_bias_zero_tail_bf16(input, bias, int(valid_cols), out)
80
+ return out
81
+
82
+ def extract_tail_f32_to_bf16(self, flat, tail_numel, out=None):
83
+ if out is None:
84
+ out = torch.empty((tail_numel,), device=flat.device, dtype=torch.bfloat16)
85
+ self._ops.extract_tail_f32_to_bf16(flat, int(tail_numel), out)
86
+ return out
87
+
88
+ def add_bias_pair_bf16(self, input, bias_a, bias_b):
89
+ out = torch.empty_like(input)
90
+ self._ops.add_bias_pair_bf16(input, bias_a, bias_b, out)
91
+ return out
92
+
93
+ def unipc_step_f32_bf16(
94
+ self,
95
+ sample,
96
+ velocity,
97
+ prev_m1,
98
+ prev_m2,
99
+ prev_last_sample,
100
+ sigma,
101
+ corrector_order,
102
+ predictor_order,
103
+ corrector_coefficients,
104
+ predictor_coefficients,
105
+ ):
106
+ outputs = [torch.empty_like(sample) for _ in range(3)]
107
+ self._ops.unipc_step_f32_bf16(
108
+ sample,
109
+ velocity,
110
+ prev_m1,
111
+ prev_m2,
112
+ prev_last_sample,
113
+ float(sigma),
114
+ int(corrector_order),
115
+ int(predictor_order),
116
+ *map(float, corrector_coefficients),
117
+ *map(float, predictor_coefficients),
118
+ *outputs,
119
+ )
120
+ return tuple(outputs)
121
+
122
+
123
+ def _preload_cublaslt() -> None:
124
+ for parent in Path(torch.__file__).resolve().parents:
125
+ candidate = parent / "nvidia" / "cublas" / "lib" / "libcublasLt.so.12"
126
+ if candidate.exists():
127
+ ctypes.CDLL(str(candidate), mode=ctypes.RTLD_GLOBAL)
128
+ return
129
+ library = ctypes.util.find_library("cublasLt")
130
+ if library:
131
+ ctypes.CDLL(library, mode=ctypes.RTLD_GLOBAL)
132
+
133
+
134
+ def _current_arch_list() -> str:
135
+ major, minor = torch.cuda.get_device_capability(0)
136
+ return f"{major}.{minor}"
137
+
138
+
139
+ def load_source_ops() -> SourceOps:
140
+ from torch.utils.cpp_extension import load
141
+
142
+ if not REGISTRATION_INCLUDE.is_dir():
143
+ raise RuntimeError(f"missing kernel-builder registration include: {REGISTRATION_INCLUDE}")
144
+ _preload_cublaslt()
145
+ os.environ.setdefault("TORCH_CUDA_ARCH_LIST", _current_arch_list())
146
+ namespace = "diffusion_step_ops_test"
147
+ load(
148
+ name=namespace,
149
+ sources=[
150
+ str(PACKAGE / "torch-ext" / "torch_binding.cpp"),
151
+ str(PACKAGE / "csrc" / "diffusion_step_ops.cu"),
152
+ ],
153
+ extra_include_paths=[str(PACKAGE / "csrc"), str(REGISTRATION_INCLUDE)],
154
+ extra_cflags=["-O3", "-DCUDA_KERNEL"],
155
+ extra_cuda_cflags=["-O3", "--expt-relaxed-constexpr", "-DCUDA_KERNEL"],
156
+ verbose=False,
157
+ )
158
+ return SourceOps(namespace)
159
+
160
+
161
+ def load_installed_ops(artifact: str | None):
162
+ if artifact:
163
+ sys.path.insert(0, artifact)
164
+ try:
165
+ return importlib.import_module("diffusion_step_ops")
166
+ finally:
167
+ if artifact:
168
+ sys.path.remove(artifact)
169
+
170
+
171
+ def assert_close(name: str, got: torch.Tensor, ref: torch.Tensor, atol: float) -> None:
172
+ diff = (got.float() - ref.float()).abs()
173
+ max_err = diff.max().item()
174
+ mean_err = diff.mean().item()
175
+ cos = torch.nn.functional.cosine_similarity(got.float().flatten(), ref.float().flatten(), dim=0).item()
176
+ if max_err > atol or cos < 0.9999:
177
+ raise AssertionError(f"{name}: max_err={max_err:.8f}, mean_err={mean_err:.8f}, cos={cos:.8f}")
178
+
179
+
180
+ def run_elementwise_tests(ops) -> int:
181
+ count = 0
182
+ for shape in [(1024,), (1025,), (4, 4096), (2, 16, 32, 64)]:
183
+ a = torch.randn(shape, device="cuda", dtype=torch.bfloat16)
184
+ b = torch.randn(shape, device="cuda", dtype=torch.bfloat16)
185
+ got = ops.add_bf16(a, b)
186
+ ref = (a.float() + b.float()).to(torch.bfloat16)
187
+ assert_close(f"add_bf16 shape={shape}", got, ref, 0.0)
188
+
189
+ dt = -0.125
190
+ got = ops.euler_step_bf16(a, b, dt)
191
+ ref = (a.float() + b.float() * dt).to(torch.bfloat16)
192
+ assert_close(f"euler_step_bf16 shape={shape}", got, ref, 0.0)
193
+
194
+ residual = torch.randn(shape, device="cuda", dtype=torch.bfloat16)
195
+ residual_ref = residual.clone()
196
+ beta = 4.5
197
+ got = ops.cfg_combine_into_residual_bf16(residual, a, b, beta)
198
+ ref = (residual_ref.float() + b.float() + beta * (a.float() - b.float())).to(torch.bfloat16)
199
+ assert_close(f"cfg_bf16 shape={shape}", got, ref, 0.0)
200
+
201
+ ah = a.to(torch.float16)
202
+ bh = b.to(torch.float16)
203
+ residual_h = residual_ref.to(torch.float16)
204
+ residual_h_ref = residual_h.clone()
205
+ got = ops.cfg_combine_into_residual_fp16(residual_h, ah, bh, beta)
206
+ ref = (residual_h_ref.float() + bh.float() + beta * (ah.float() - bh.float())).to(torch.float16)
207
+ assert_close(f"cfg_fp16 shape={shape}", got, ref, 0.0)
208
+
209
+ got = ops.cast_bf16_to_fp32(a)
210
+ ref = a.float()
211
+ assert_close(f"cast_bf16_to_fp32 shape={shape}", got, ref, 0.0)
212
+ count += 5
213
+ return count
214
+
215
+
216
+ def run_video_tests(ops) -> int:
217
+ count = 0
218
+ for shape in [(1, 4, 5, 16, 16), (2, 8, 9, 8, 8), (1, 16, 17, 16, 24)]:
219
+ video = torch.randn(shape, device="cuda", dtype=torch.bfloat16)
220
+ cond = torch.randn((shape[0], shape[1], shape[3], shape[4]), device="cuda", dtype=torch.bfloat16)
221
+ ref = video.clone()
222
+ ref[:, :, 0] = cond
223
+ got = ops.teacher_force_first_frame_bf16(video.clone(), cond)
224
+ assert_close(f"teacher_force shape={shape}", got, ref, 0.0)
225
+
226
+ decoded = torch.randn(shape, device="cuda", dtype=torch.bfloat16) * 3.0
227
+ got = ops.motus_decode_postprocess_bf16_to_fp32(decoded)
228
+ ref = ((decoded[:, :, 1:].float() + 1.0) * 0.5).clamp(0.0, 1.0).contiguous()
229
+ assert_close(f"motus_postprocess shape={shape}", got, ref, 0.0)
230
+ count += 2
231
+ return count
232
+
233
+
234
+ def run_tail_tests(ops) -> int:
235
+ count = 0
236
+ for flat_dim, tail_numel in [(32, 7), (257, 51), (4096, 1024)]:
237
+ tail = torch.randn((tail_numel,), device="cuda", dtype=torch.bfloat16)
238
+ got = ops.pack_tail_bf16(tail, flat_dim)
239
+ ref = torch.zeros((flat_dim,), device="cuda", dtype=torch.bfloat16)
240
+ ref[-tail_numel:] = tail
241
+ assert_close(f"pack_tail {flat_dim=} {tail_numel=}", got, ref, 0.0)
242
+
243
+ flat = torch.randn((flat_dim,), device="cuda", dtype=torch.float32)
244
+ got = ops.extract_tail_f32_to_bf16(flat, tail_numel)
245
+ ref = flat[-tail_numel:].to(torch.bfloat16)
246
+ assert_close(f"extract_tail {flat_dim=} {tail_numel=}", got, ref, 0.0)
247
+ count += 2
248
+
249
+ for rows, cols, valid_cols in [(1, 16, 7), (51, 64, 32), (105, 257, 256)]:
250
+ input = torch.randn((rows, cols), device="cuda", dtype=torch.bfloat16)
251
+ bias = torch.randn((cols,), device="cuda", dtype=torch.bfloat16)
252
+ got = ops.add_bias_zero_tail_bf16(input, bias, valid_cols)
253
+ ref = (input.float() + bias.float()).to(torch.bfloat16)
254
+ ref[:, valid_cols:] = 0
255
+ assert_close(
256
+ f"add_bias_zero_tail {rows=} {cols=} {valid_cols=}",
257
+ got,
258
+ ref,
259
+ 0.0,
260
+ )
261
+
262
+ bias_b = torch.randn((cols,), device="cuda", dtype=torch.bfloat16)
263
+ got = ops.add_bias_pair_bf16(input, bias, bias_b)
264
+ ref = (input.float() + bias.float()).to(torch.bfloat16)
265
+ ref = (ref.float() + bias_b.float()).to(torch.bfloat16)
266
+ assert_close(f"add_bias_pair {rows=} {cols=}", got, ref, 0.0)
267
+ count += 2
268
+
269
+ tail = torch.randn((51,), device="cuda", dtype=torch.bfloat16)
270
+ input = torch.randn((51, 64), device="cuda", dtype=torch.bfloat16)
271
+ bias_a = torch.randn((64,), device="cuda", dtype=torch.bfloat16)
272
+ bias_b = torch.randn((64,), device="cuda", dtype=torch.bfloat16)
273
+
274
+ def invoke(tail, input, bias_a, bias_b):
275
+ return (
276
+ ops.pack_tail_bf16(tail, 257),
277
+ ops.add_bias_pair_bf16(input, bias_a, bias_b),
278
+ )
279
+
280
+ eager = invoke(tail, input, bias_a, bias_b)
281
+ compiled = torch.compile(invoke, fullgraph=True)(tail, input, bias_a, bias_b)
282
+ for got, expected in zip(compiled, eager):
283
+ torch.testing.assert_close(got, expected, rtol=0.0, atol=0.0)
284
+ print("PASS action-tail torch.compile fullgraph")
285
+ return count + 1
286
+
287
+
288
+ def run_cosmos_edge_contract(ops) -> int:
289
+ flat_dim = 1_201_920
290
+ tail_numel = 60 * 64
291
+ rows, cols, valid_cols = 60, 64, 9
292
+
293
+ tail = torch.randn((tail_numel,), device="cuda", dtype=torch.bfloat16)
294
+ flat = torch.randn((flat_dim,), device="cuda", dtype=torch.float32)
295
+ matrix = torch.randn((rows, cols), device="cuda", dtype=torch.bfloat16)
296
+ bias = torch.randn((cols,), device="cuda", dtype=torch.bfloat16)
297
+ packed = torch.empty((flat_dim,), device="cuda", dtype=torch.bfloat16)
298
+ extracted = torch.empty((tail_numel,), device="cuda", dtype=torch.bfloat16)
299
+ biased = torch.empty_like(matrix)
300
+
301
+ ops.pack_tail_bf16(tail, flat_dim, out=packed)
302
+ ops.extract_tail_f32_to_bf16(flat, tail_numel, out=extracted)
303
+ ops.add_bias_zero_tail_bf16(matrix, bias, valid_cols, out=biased)
304
+ expected_packed = torch.zeros_like(packed)
305
+ expected_packed[-tail_numel:] = tail
306
+ expected_extracted = flat[-tail_numel:].to(torch.bfloat16)
307
+ expected_biased = (matrix.float() + bias.float()).to(torch.bfloat16)
308
+ expected_biased[:, valid_cols:] = 0
309
+ torch.testing.assert_close(packed, expected_packed, rtol=0.0, atol=0.0)
310
+ torch.testing.assert_close(extracted, expected_extracted, rtol=0.0, atol=0.0)
311
+ torch.testing.assert_close(biased, expected_biased, rtol=0.0, atol=0.0)
312
+
313
+ graph = torch.cuda.CUDAGraph()
314
+ torch.cuda.synchronize()
315
+ with torch.cuda.graph(graph):
316
+ ops.pack_tail_bf16(tail, flat_dim, out=packed)
317
+ ops.extract_tail_f32_to_bf16(flat, tail_numel, out=extracted)
318
+ ops.add_bias_zero_tail_bf16(matrix, bias, valid_cols, out=biased)
319
+ graph.replay()
320
+ torch.cuda.synchronize()
321
+ first = (packed.clone(), extracted.clone(), biased.clone())
322
+ graph.replay()
323
+ torch.cuda.synchronize()
324
+ second = (packed.clone(), extracted.clone(), biased.clone())
325
+ for got, expected in zip(second, first):
326
+ torch.testing.assert_close(got, expected, rtol=0.0, atol=0.0)
327
+ print("PASS Cosmos3-Edge action-tail contract and CUDA Graph replay")
328
+ return 4
329
+
330
+
331
+ def run_unipc_tests(ops) -> int:
332
+ count = 0
333
+ corrector = (0.75, 0.2, -0.1, 0.05, 0.4)
334
+ predictor = (0.8, 0.3, -0.07)
335
+ for shape in [(1,), (257,), (1, 16, 17, 8, 8)]:
336
+ sample = torch.randn(shape, device="cuda", dtype=torch.float32)
337
+ velocity = torch.randn(
338
+ shape, device="cuda", dtype=torch.bfloat16
339
+ )
340
+ prev_m1 = torch.randn_like(sample)
341
+ prev_m2 = torch.randn_like(sample)
342
+ prev_last = torch.randn_like(sample)
343
+ for corrector_order, predictor_order in [(0, 1), (1, 1), (1, 2), (2, 2)]:
344
+ got_next, got_m, got_last = ops.unipc_step_f32_bf16(
345
+ sample,
346
+ velocity,
347
+ prev_m1,
348
+ prev_m2,
349
+ prev_last,
350
+ 0.37,
351
+ corrector_order,
352
+ predictor_order,
353
+ corrector,
354
+ predictor,
355
+ )
356
+ sigma_velocity = (velocity.float() * 0.37).to(
357
+ torch.bfloat16
358
+ ).float()
359
+ expected_m = sample - sigma_velocity
360
+ expected_last = corrector[0] * sample + corrector[4] * expected_m
361
+ if corrector_order >= 1:
362
+ expected_last = (
363
+ expected_last
364
+ + corrector[1] * prev_last
365
+ + corrector[2] * prev_m1
366
+ )
367
+ if corrector_order >= 2:
368
+ expected_last = expected_last + corrector[3] * prev_m2
369
+ expected_next = (
370
+ predictor[0] * expected_last + predictor[1] * expected_m
371
+ )
372
+ if predictor_order >= 2:
373
+ expected_next = expected_next + predictor[2] * prev_m1
374
+ torch.testing.assert_close(
375
+ got_m, expected_m, rtol=1e-6, atol=1e-6
376
+ )
377
+ torch.testing.assert_close(
378
+ got_last, expected_last, rtol=2e-6, atol=2e-6
379
+ )
380
+ torch.testing.assert_close(
381
+ got_next, expected_next, rtol=2e-6, atol=2e-6
382
+ )
383
+ count += 1
384
+
385
+ sample = torch.randn((257,), device="cuda", dtype=torch.float32)
386
+ velocity = torch.randn(
387
+ (257,), device="cuda", dtype=torch.bfloat16
388
+ )
389
+ history = [torch.randn_like(sample) for _ in range(3)]
390
+
391
+ def invoke(sample, velocity, prev_m1, prev_m2, prev_last):
392
+ return ops.unipc_step_f32_bf16(
393
+ sample,
394
+ velocity,
395
+ prev_m1,
396
+ prev_m2,
397
+ prev_last,
398
+ 0.37,
399
+ 2,
400
+ 2,
401
+ corrector,
402
+ predictor,
403
+ )
404
+
405
+ eager = invoke(sample, velocity, *history)
406
+ compiled = torch.compile(invoke, fullgraph=True)(
407
+ sample, velocity, *history
408
+ )
409
+ for got, expected in zip(compiled, eager):
410
+ torch.testing.assert_close(got, expected, rtol=0.0, atol=0.0)
411
+ print("PASS unipc_step torch.compile fullgraph")
412
+ return count + 1
413
+
414
+
415
+ def main() -> int:
416
+ parser = argparse.ArgumentParser()
417
+ parser.add_argument("--backend", choices=["source", "installed"], default="source")
418
+ parser.add_argument("--artifact", default=None)
419
+ args = parser.parse_args()
420
+ if not torch.cuda.is_available():
421
+ raise RuntimeError("CUDA is required")
422
+ torch.manual_seed(0)
423
+ ops = load_source_ops() if args.backend == "source" else load_installed_ops(args.artifact)
424
+ total = (
425
+ run_elementwise_tests(ops)
426
+ + run_video_tests(ops)
427
+ + run_tail_tests(ops)
428
+ + run_cosmos_edge_contract(ops)
429
+ + run_unipc_tests(ops)
430
+ )
431
+ torch.cuda.synchronize()
432
+ print(f"diffusion-step-ops correctness passed: {total} checks")
433
+ return 0
434
+
435
+
436
+ if __name__ == "__main__":
437
+ raise SystemExit(main())