1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
//! GPU (CUDA) backend for the FLUX.2 text-encoder (Qwen3-4B) f32 matmuls.
//!
//! CUDA sibling of [`crate::te::gpu`] (the Metal backend), authored as a
//! line-for-line mirror. It routes the dominant per-layer Linears of the
//! Qwen3-4B text encoder (Q/K/V/o_proj + gate/up/down across 36 layers) onto the
//! project's f32-exact CUDA GEMM kernel ([`CudaGraph::encode_gemm_f32`] in
//! `oxibonsai-kernels`), keeping each weight's row-major f32 bytes resident on
//! the GPU and crossing the bus only with the (small) f32 activations per
//! matmul.
//!
//! Unlike the DiT path ([`crate::cuda_gpu`]), the TE weights are **pure f32**
//! (the 4-bit MLX weights are dequantized to f32 offline by `TeWeights`), so the
//! op is a plain `out[m,n] = Σ_k input[m,k] · weight[n,k]` with **no
//! quantization** — the GPU only reassociates the sum, which keeps it cos ≈ 1.0
//! vs the CPU `gemm_abt` reference. Parity is therefore trivially safe (the
//! `te_parity` gate stays cos ≥ 0.999).
//!
//! The whole module is gated on `cfg(all(feature = "native-cuda", any(target_os
//! = "linux", target_os = "windows")))` — the same gate under which
//! `oxibonsai-kernels` re-exports [`CudaGraph`] — and is `target_os`-DISJOINT
//! from the Metal gate (macOS), so a non-CUDA build never references it and the
//! default Pure-Rust CPU path is entirely unaffected.
//!
//! Default OFF: unlike the DiT (`OXI_DIT_GPU`, default ON), the TE GPU path is
//! opt-in via `OXI_TE_GPU=1` (the same env var as the Metal path; Metal and CUDA
//! are mutually exclusive at build by `target_os`). The CPU TE already tracks the
//! goldens; the GPU path is a speed optimization, enabled explicitly for A/B and
//! production use.
//!
//! On *any* error this module returns a [`CudaTeGpuMatmulError`]; the caller (the
//! `matmul` helper in [`crate::te::forward`]) swallows it and falls back to the
//! CPU [`crate::gemm::gemm_abt`], so a GPU failure can never break a forward
//! pass (no `unwrap`/`expect`/`panic!`).
use ;
use OnceLock;
use ;
/// An error from the GPU f32-matmul path. The caller converts this into a
/// silent CPU fallback, so it never propagates out of a forward pass.
/// One-time confirmation that the TE GPU path actually executed at least once
/// (used by the parity example to PROVE the GPU ran, not a silent CPU fallback).
static TE_GPU_USED: AtomicBool = new;
/// Returns `true` once any [`te_matmul_gpu`] call has succeeded.
///
/// Lock-free and cheap; intended for diagnostics / parity assertions.
/// Cached runtime toggle for the GPU TE path. Default **OFF**; set env
/// `OXI_TE_GPU=1` to route the TE matmuls through the CUDA f32 GEMM.
static TE_GPU_ENABLED: = new;
/// Whether the text encoder should use the GPU f32 path.
///
/// `false` unless the environment variable `OXI_TE_GPU` is set to `1`. The env
/// read is cached in a [`OnceLock`] on first call.
/// Compute `out[m, n] = Σ_k input[m, k] · weight[n, k]` (`x · Wᵀ`) on the GPU.
///
/// - `weight`: row-major f32 `[n, k]` (the dequantized TE Linear weight,
/// borrowed from the long-lived [`crate::te::weights::TeWeights`] registry).
/// - `input`: row-major `[m, k]`.
/// - `out`: row-major `[m, n]` (written in full).
///
/// The weight is uploaded to the GPU for this call and **evicted immediately
/// afterwards**: the 4-bit MLX `TeWeights` dequantises into a fresh transient
/// buffer per Linear, so its pointer is recycled across calls and cannot serve
/// as a long-lived cache key (see the body comment). Each matmul therefore
/// re-uploads its weight; only one TE weight is GPU-resident at a time.
///
/// # Errors
/// Returns [`CudaTeGpuMatmulError`] if the CUDA graph is unavailable or the
/// kernel upload/encode fails (e.g. a length mismatch). The caller falls back to
/// the CPU path on any error.