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
135
136
137
//! GPU (Metal) backend for the FLUX.2 text-encoder (Qwen3-4B) f32 matmuls.
//!
//! This module 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 Metal GEMM kernel (`MetalGraph::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::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 = "metal", target_os =
//! "macos"))` — the same gate under which `oxibonsai-kernels` re-exports
//! `MetalGraph` — so a non-Metal / non-macOS 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 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 `TeGpuMatmulError`; 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!`).
//!
//! ## Weight residency (no-copy investigation)
//!
//! 36 layers of Qwen3-4B in f32 are ~16 GB once cached. The weights are ALREADY
//! f32 in host RAM (the `TeWeights` `.npy` buffers), so a zero-copy GPU alias
//! would be ideal. The `metal` crate (0.33) *does* expose
//! `Device::new_buffer_with_bytes_no_copy` (`newBufferWithBytesNoCopy:`), but
//! macOS requires the wrapped pointer to be **page-aligned** (`getpagesize()` =
//! 16 KiB on Apple Silicon) with a page-multiple length, or the call returns
//! `nil`; and the buffer would alias the host allocation, which must then
//! outlive every GPU use. The `TeWeights` weights are ordinary `Vec<f32>`
//! allocations from `.npy` parsing — not page-aligned — so a no-copy wrap would
//! be unsound/unreliable for them. We therefore use the existing
//! **upload-once-cache** path (`MetalGraph::get_or_upload_f32_weight`): each
//! weight is blitted to a `StorageModeShared` buffer the first time it is seen
//! and cached by its slice pointer for all subsequent forwards (the 36-layer
//! weight set uploads exactly once, ~16 GB resident on Apple unified memory).
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 Metal 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 **once** and cached by its (stable,
/// per-weight-unique) slice pointer key, so subsequent layers/forwards reuse the
/// resident buffer and only the activations cross the bus.
///
/// # Errors
/// Returns [`TeGpuMatmulError`] if the Metal 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.