sparse-delta-memory 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sparse_delta_memory/__init__.py +13 -0
- sparse_delta_memory/backward.py +367 -0
- sparse_delta_memory/cache.py +70 -0
- sparse_delta_memory/cuda/__init__.py +3 -0
- sparse_delta_memory/cuda/_build.py +254 -0
- sparse_delta_memory/cuda/_device.py +158 -0
- sparse_delta_memory/cuda/_devmemo.py +64 -0
- sparse_delta_memory/cuda/_image.py +128 -0
- sparse_delta_memory/cuda/bwd_steps_cuda.py +111 -0
- sparse_delta_memory/cuda/gather_cuda.py +796 -0
- sparse_delta_memory/cuda/gather_ns_cuda.py +134 -0
- sparse_delta_memory/cuda/gx_gds_cuda.py +1297 -0
- sparse_delta_memory/cuda/gx_gemm_cuda.py +857 -0
- sparse_delta_memory/cuda/gx_pa2_cuda.py +1320 -0
- sparse_delta_memory/cuda/mover_da2_cuda.py +1802 -0
- sparse_delta_memory/cuda/mover_fx_cuda.py +2229 -0
- sparse_delta_memory/cuda/pa_dc_cuda.py +1500 -0
- sparse_delta_memory/cuda/phase1_bwd_cuda.py +852 -0
- sparse_delta_memory/cuda/phase1_common.cuh +181 -0
- sparse_delta_memory/cuda/phase1_cuda.py +3801 -0
- sparse_delta_memory/cuda/product_key_topk_anyf_cuda.py +668 -0
- sparse_delta_memory/cuda/product_key_topk_cuda.py +735 -0
- sparse_delta_memory/cuda/px_qrec_cuda.py +167 -0
- sparse_delta_memory/cuda/sdm_copy.cuh +90 -0
- sparse_delta_memory/cuda/sdm_plancap.cuh +150 -0
- sparse_delta_memory/cuda_graph.py +605 -0
- sparse_delta_memory/decode.py +140 -0
- sparse_delta_memory/forward.py +464 -0
- sparse_delta_memory/layer.py +663 -0
- sparse_delta_memory/memory_ops.py +1069 -0
- sparse_delta_memory/memory_stats.py +221 -0
- sparse_delta_memory/movers.py +198 -0
- sparse_delta_memory/phase2_kernels.py +267 -0
- sparse_delta_memory/product_key_topk.py +113 -0
- sparse_delta_memory/wy_solve.py +273 -0
- sparse_delta_memory-2.0.0.dist-info/METADATA +482 -0
- sparse_delta_memory-2.0.0.dist-info/RECORD +40 -0
- sparse_delta_memory-2.0.0.dist-info/WHEEL +5 -0
- sparse_delta_memory-2.0.0.dist-info/licenses/LICENSE +407 -0
- sparse_delta_memory-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,2229 @@
|
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
"""mover_fx_cuda: the deterministic forward's bank-direct DW kernels (Phase-2 decay and write, bf16 or f32 bank) and its
|
|
3
|
+
fused step loop.
|
|
4
|
+
|
|
5
|
+
Same arithmetic, bit for bit, as mover_da2_cuda's dense walk (da2_dw_kernel MODE 32, sf_full = 1), which was itself
|
|
6
|
+
bitwise 2.0.0's Triton DW (phase2_kernels):
|
|
7
|
+
|
|
8
|
+
per group (sorted order): acc = 0; acc = fma(sc_m, dv[tok_m], acc) over the members in sorted order;
|
|
9
|
+
bank[slot] = bf16_rn(fma(base, cpe_last, acc)) (a float32 bank: the fma result itself), base = the group's
|
|
10
|
+
pre-write bank row;
|
|
11
|
+
acc restarts after EVERY LAST (valid or not); only valid LASTs store.
|
|
12
|
+
|
|
13
|
+
float32 banks (fx_dw_kernel<float, ...>: one column slice per warp, 64-bit row offsets) compute mover_da2_cuda's float32
|
|
14
|
+
walk: the same per-group chain, base = the group's pre-step bank row, stored unrounded.
|
|
15
|
+
|
|
16
|
+
How it differs (speed only):
|
|
17
|
+
* Base rows are read from bank[slot] itself (bank-direct): within one launch every group owns its slot, so no other
|
|
18
|
+
emit writes it, and the read precedes the group's own store in the same thread.
|
|
19
|
+
* Token rows: one 1D bulk copy (one slice) or one 2D tensor-map box {SW, C} (column slices) instead of C per-row
|
|
20
|
+
copies.
|
|
21
|
+
* The walk: per 32-position batch one STS.128 per lane of {token offset, scale, row byte offset, cpe}; per
|
|
22
|
+
8-position sub-batch the records and token rows are loaded first, and a sub-batch without any LAST runs a
|
|
23
|
+
branch-free FMA path; the emit is one code path with 32-bit row offsets (64-bit for banks over 4 GiB).
|
|
24
|
+
* fx_dw2_kernel takes two column slices per warp (even slice counts), so records, masks and addressing serve 8
|
|
25
|
+
columns instead of 4.
|
|
26
|
+
* In the saving forward the kernel can also write the dense compact snapshot rows (raw copies of the base rows it
|
|
27
|
+
reads), so the gather need not write them.
|
|
28
|
+
Tiles, records (krec), the tile group prefix (segb) and the dense row layout are mover_da2_cuda's / Phase 1's; every
|
|
29
|
+
group is summed in sorted order by one warp, so the output does not depend on the geometry.
|
|
30
|
+
|
|
31
|
+
fx_fwd_steps runs the deterministic forward's step loop (gather, dual matmul, DW of every step) in one host call."""
|
|
32
|
+
|
|
33
|
+
import functools
|
|
34
|
+
|
|
35
|
+
import torch
|
|
36
|
+
|
|
37
|
+
from ._build import load_cuda_extension
|
|
38
|
+
from .gx_gds_cuda import QUANT_HELPERS, ROW_HELPERS
|
|
39
|
+
from ._device import RESERVED_SMEM, headers, num_sms, prev_pow2
|
|
40
|
+
from ._devmemo import (backend as _device_backend, pdl_ok as _device_pdl_ok, sm90 as _sm90, smem_cap as _device_smem_cap,
|
|
41
|
+
smem_per_sm as _device_smem_per_sm)
|
|
42
|
+
|
|
43
|
+
_CPP = r"""
|
|
44
|
+
at::Tensor fx_maps(at::Tensor snapc, at::Tensor dv, int64_t C, int64_t wide_rows);
|
|
45
|
+
at::Tensor fx_maps2(at::Tensor snapc, at::Tensor dv, int64_t C);
|
|
46
|
+
at::Tensor fx_dw_plan(at::Tensor bank, at::Tensor dv, int64_t dv_step_rows, at::Tensor krec, at::Tensor tb,
|
|
47
|
+
at::Tensor segb, at::Tensor snapc, int64_t sb_step, int64_t P, int64_t C, int64_t K, int64_t nw,
|
|
48
|
+
int64_t nu, at::Tensor maps, int64_t bankb, int64_t quant, at::Tensor payload, at::Tensor scale,
|
|
49
|
+
int64_t portable, int64_t smem_cap);
|
|
50
|
+
void fx_dw_step(at::Tensor plan, int64_t ti, int64_t pdl);
|
|
51
|
+
int64_t fx_q_capacity(int64_t quant, int64_t w2, int64_t bf, int64_t nw, int64_t t2d, int64_t allon, int64_t cluster,
|
|
52
|
+
int64_t smem);
|
|
53
|
+
int64_t fx_tri_desc_size();
|
|
54
|
+
void fx_fwd_steps(at::Tensor gplan, int64_t gather_addr, at::Tensor tdesc, at::Tensor dwplan, int64_t t0, int64_t t1,
|
|
55
|
+
int64_t SC, int64_t C, int64_t tokens, int64_t pdl_gather, int64_t pdl_dw);
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
_CUDA = r"""
|
|
59
|
+
#include <torch/extension.h>
|
|
60
|
+
#include <ATen/cuda/CUDAContext.h>
|
|
61
|
+
#include <c10/cuda/CUDAGuard.h>
|
|
62
|
+
#include <c10/cuda/CUDAException.h>
|
|
63
|
+
#include <cuda.h>
|
|
64
|
+
#include <cuda_bf16.h>
|
|
65
|
+
#include <cuda_fp8.h>
|
|
66
|
+
#include <dlfcn.h>
|
|
67
|
+
#include <algorithm>
|
|
68
|
+
#include <cstdint>
|
|
69
|
+
#include <cstring>
|
|
70
|
+
#include <limits>
|
|
71
|
+
#include <map>
|
|
72
|
+
#include <mutex>
|
|
73
|
+
#include <utility>
|
|
74
|
+
#include "sdm_copy.cuh"
|
|
75
|
+
|
|
76
|
+
namespace {
|
|
77
|
+
|
|
78
|
+
constexpr uint32_t kFirst = 0x80000000u;
|
|
79
|
+
constexpr uint32_t kLast = 0x40000000u;
|
|
80
|
+
constexpr uint32_t kInvalid = 0x20000u;
|
|
81
|
+
constexpr unsigned kFull = 0xFFFFFFFFu;
|
|
82
|
+
constexpr int kUnitRows = 8; // dense rows per unit (one TMA, one wait)
|
|
83
|
+
constexpr int kTileRows = 128; // valid groups per warp tile, every fx tile set (FIRST-snapped, <= 128 positions apart)
|
|
84
|
+
constexpr int64_t kMaxHeadDim = int64_t(1) << 20; // host bound: row bytes, TMA coordinates and slice offsets stay
|
|
85
|
+
// 32-bit safe (hd * 4 < 2^23); Python supported() gates it
|
|
86
|
+
|
|
87
|
+
// The portable side (SDM_PORTABLE_ARCH, sdm_copy.cuh: sm_80 / 86 / 89 / 120 ...): no barrier object (FX_SM90_OR_NONE:
|
|
88
|
+
// nothing), the token rows staged by every thread with sdmcp's cp.async copies and published by a CTA barrier, no
|
|
89
|
+
// griddepcontrol, no clusters; the bulk / tensor copies and transaction waits (FX_SM90) trap there (never reached).
|
|
90
|
+
#ifdef SDM_PORTABLE_ARCH
|
|
91
|
+
#define FX_PRE_SM90 1
|
|
92
|
+
#define FX_SM90(...) __trap()
|
|
93
|
+
#define FX_SM90_OR_NONE(...)
|
|
94
|
+
#else
|
|
95
|
+
#define FX_SM90(...) __VA_ARGS__
|
|
96
|
+
#define FX_SM90_OR_NONE(...) __VA_ARGS__
|
|
97
|
+
#endif
|
|
98
|
+
|
|
99
|
+
__device__ __forceinline__ uint32_t smem_u32(const void* p) {
|
|
100
|
+
return static_cast<uint32_t>(__cvta_generic_to_shared(p));
|
|
101
|
+
}
|
|
102
|
+
__device__ __forceinline__ void mbar_init(uint64_t* bar, uint32_t count) {
|
|
103
|
+
FX_SM90_OR_NONE(asm volatile("mbarrier.init.shared::cta.b64 [%0], %1;\n" ::"r"(smem_u32(bar)), "r"(count) : "memory"));
|
|
104
|
+
}
|
|
105
|
+
__device__ __forceinline__ void fence_mbar_init() {
|
|
106
|
+
FX_SM90_OR_NONE(asm volatile("fence.mbarrier_init.release.cluster;\n" ::: "memory"));
|
|
107
|
+
}
|
|
108
|
+
__device__ __forceinline__ void fence_proxy_async() {
|
|
109
|
+
FX_SM90_OR_NONE(asm volatile("fence.proxy.async.shared::cta;\n" ::: "memory"));
|
|
110
|
+
}
|
|
111
|
+
// Invalidates an mbarrier before its memory is initialized again (the all-slices walk re-runs the prologue per slice).
|
|
112
|
+
__device__ __forceinline__ void mbar_inval(uint64_t* bar) {
|
|
113
|
+
FX_SM90_OR_NONE(asm volatile("mbarrier.inval.shared::cta.b64 [%0];\n" ::"r"(smem_u32(bar)) : "memory"));
|
|
114
|
+
}
|
|
115
|
+
// One arrival that also announces `bytes` of transactions (count-1 barriers).
|
|
116
|
+
__device__ __forceinline__ void mbar_arrive_tx(uint64_t* bar, uint32_t bytes) {
|
|
117
|
+
FX_SM90(asm volatile("mbarrier.arrive.expect_tx.shared::cta.b64 _, [%0], %1;\n" ::"r"(smem_u32(bar)),
|
|
118
|
+
"r"(bytes) : "memory"));
|
|
119
|
+
}
|
|
120
|
+
// 1D TMA bulk copy global -> this CTA's shared memory (16-byte aligned, multiple of 16 bytes).
|
|
121
|
+
__device__ __forceinline__ void bulk_g2s(void* dst, const void* src, uint32_t bytes, uint64_t* bar) {
|
|
122
|
+
FX_SM90(asm volatile(
|
|
123
|
+
"cp.async.bulk.shared::cluster.global.mbarrier::complete_tx::bytes [%0], [%1], %2, [%3];\n"
|
|
124
|
+
::"r"(smem_u32(dst)), "l"(src), "r"(bytes), "r"(smem_u32(bar)) : "memory"));
|
|
125
|
+
}
|
|
126
|
+
// 16-byte cp.async global -> this thread's shared memory (L2 only, .cg); completion: cp_async_wait_all (PF path).
|
|
127
|
+
__device__ __forceinline__ void cp_async16(void* dst, const void* src) {
|
|
128
|
+
FX_SM90(asm volatile("cp.async.cg.shared.global [%0], [%1], 16;\n" ::"r"(smem_u32(dst)), "l"(src) : "memory"));
|
|
129
|
+
}
|
|
130
|
+
// Waits for every cp.async this thread issued (commit + wait_group 0); the caller's __syncwarp then makes the copied
|
|
131
|
+
// bytes visible to the other lanes.
|
|
132
|
+
__device__ __forceinline__ void cp_async_wait_all() {
|
|
133
|
+
FX_SM90(asm volatile("cp.async.wait_all;\n" ::: "memory"));
|
|
134
|
+
}
|
|
135
|
+
// 2D tensor-map TMA box -> shared memory (128-byte aligned destination); c0 = column, c1 = row.
|
|
136
|
+
__device__ __forceinline__ void tma2d_g2s(void* dst, const CUtensorMap* map, int c0, int c1, uint64_t* bar) {
|
|
137
|
+
FX_SM90(asm volatile(
|
|
138
|
+
"cp.async.bulk.tensor.2d.shared::cluster.global.mbarrier::complete_tx::bytes [%0], [%1, {%2, %3}], [%4];\n"
|
|
139
|
+
::"r"(smem_u32(dst)), "l"(reinterpret_cast<uint64_t>(map)), "r"(c0), "r"(c1), "r"(smem_u32(bar))
|
|
140
|
+
: "memory"));
|
|
141
|
+
}
|
|
142
|
+
// Spins until phase `parity` of `bar` completed; returns 0. No memory clobber: the caller adds the result to the
|
|
143
|
+
// address of every read that must follow the wait (acquire semantics of try_wait), as in mover_da2_cuda.
|
|
144
|
+
__device__ __forceinline__ uint32_t mbar_wait_dep(uint64_t* bar, uint32_t parity) {
|
|
145
|
+
#ifdef FX_PRE_SM90
|
|
146
|
+
__trap();
|
|
147
|
+
return 0u;
|
|
148
|
+
#else
|
|
149
|
+
uint32_t ok;
|
|
150
|
+
asm volatile(
|
|
151
|
+
"{\n"
|
|
152
|
+
" .reg .pred p;\n"
|
|
153
|
+
" FX_WAIT:\n"
|
|
154
|
+
" mbarrier.try_wait.parity.shared::cta.b64 p, [%1], %2;\n"
|
|
155
|
+
" @!p bra FX_WAIT;\n"
|
|
156
|
+
" mov.u32 %0, 0;\n"
|
|
157
|
+
"}\n"
|
|
158
|
+
: "=r"(ok) : "r"(smem_u32(bar)), "r"(parity));
|
|
159
|
+
return ok;
|
|
160
|
+
#endif
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Thread-block cluster (sm_90+) of a quantized multi-slice walk: the CTAs of every column slice of the same tiles
|
|
164
|
+
// (cluster rank = slice) exchange their per-row partial amax through distributed shared memory.
|
|
165
|
+
__device__ __forceinline__ void cluster_arrive() { // release
|
|
166
|
+
FX_SM90(asm volatile("barrier.cluster.arrive.aligned;\n" ::: "memory"));
|
|
167
|
+
}
|
|
168
|
+
__device__ __forceinline__ void cluster_wait() { // acquire
|
|
169
|
+
FX_SM90(asm volatile("barrier.cluster.wait.aligned;\n" ::: "memory"));
|
|
170
|
+
}
|
|
171
|
+
// The float at shared address p of CTA `rank` of this cluster.
|
|
172
|
+
__device__ __forceinline__ float ld_peer(const float* p, uint32_t rank) {
|
|
173
|
+
#ifdef FX_PRE_SM90
|
|
174
|
+
__trap();
|
|
175
|
+
return 0.f;
|
|
176
|
+
#else
|
|
177
|
+
uint32_t a;
|
|
178
|
+
float v;
|
|
179
|
+
asm volatile("mapa.shared::cluster.u32 %0, %1, %2;\n" : "=r"(a) : "r"(smem_u32(p)), "r"(rank));
|
|
180
|
+
asm volatile("ld.shared::cluster.f32 %0, [%1];\n" : "=f"(v) : "r"(a) : "memory");
|
|
181
|
+
return v;
|
|
182
|
+
#endif
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
@ROW_HELPERS@
|
|
186
|
+
__device__ __forceinline__ uint4 ld_rec(const uint4* __restrict__ kr, int pos, int e) {
|
|
187
|
+
if (pos < e) return __ldg(kr + pos);
|
|
188
|
+
return make_uint4(kFirst | kLast | kInvalid, 0u, 0u, 0x3F800000u); // padding: own (invalid) group
|
|
189
|
+
}
|
|
190
|
+
// The bank row of a record-batch entry. Banks <= 4 GiB keep the 32-bit BYTE offset in the batch; BIG banks (> 4 GiB,
|
|
191
|
+
// e.g. 4 heads x head dim 960 x 921,600 slots) keep the row INDEX and widen it to a 64-bit byte offset here.
|
|
192
|
+
template <bool BIG>
|
|
193
|
+
__device__ __forceinline__ unsigned char* fx_row(unsigned char* lane_base, uint32_t rec, uint32_t row_bytes) {
|
|
194
|
+
if constexpr (BIG) {
|
|
195
|
+
return lane_base + static_cast<uint64_t>(rec) * row_bytes;
|
|
196
|
+
} else {
|
|
197
|
+
return lane_base + rec;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
__device__ __forceinline__ void fma4(float4& acc, uint32_t scb, const float4& x) {
|
|
202
|
+
const float sc = __uint_as_float(scb);
|
|
203
|
+
acc.x = fmaf(sc, x.x, acc.x);
|
|
204
|
+
acc.y = fmaf(sc, x.y, acc.y);
|
|
205
|
+
acc.z = fmaf(sc, x.z, acc.z);
|
|
206
|
+
acc.w = fmaf(sc, x.w, acc.w);
|
|
207
|
+
}
|
|
208
|
+
// Tails, SCALAR (hd % 4 != 0, variant bit 32): bank and compact rows are only element-aligned, so a lane touches its
|
|
209
|
+
// first nv (<= 4) columns element by element: the same values and roundings as the 4-wide accesses.
|
|
210
|
+
template <typename E>
|
|
211
|
+
__device__ __forceinline__ typename Row4<E>::T ld_row_n(const unsigned char* p, int nv) {
|
|
212
|
+
if constexpr (std::is_same<E, __nv_bfloat16>::value) {
|
|
213
|
+
const unsigned short* q = reinterpret_cast<const unsigned short*>(p);
|
|
214
|
+
const uint32_t a = nv > 0 ? __ldcg(q) : 0u, b = nv > 1 ? __ldcg(q + 1) : 0u;
|
|
215
|
+
const uint32_t c = nv > 2 ? __ldcg(q + 2) : 0u, d = nv > 3 ? __ldcg(q + 3) : 0u;
|
|
216
|
+
return make_uint2(a | (b << 16), c | (d << 16));
|
|
217
|
+
} else {
|
|
218
|
+
const float* q = reinterpret_cast<const float*>(p);
|
|
219
|
+
return make_float4(nv > 0 ? __ldcg(q) : 0.f, nv > 1 ? __ldcg(q + 1) : 0.f, nv > 2 ? __ldcg(q + 2) : 0.f,
|
|
220
|
+
nv > 3 ? __ldcg(q + 3) : 0.f);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
__device__ __forceinline__ void st4_n(__nv_bfloat16* p, float4 v, int nv) {
|
|
224
|
+
const uint32_t lo = pack_bf2(v.x, v.y), hi = pack_bf2(v.z, v.w);
|
|
225
|
+
unsigned short* q = reinterpret_cast<unsigned short*>(p);
|
|
226
|
+
if (nv > 0) q[0] = static_cast<unsigned short>(lo);
|
|
227
|
+
if (nv > 1) q[1] = static_cast<unsigned short>(lo >> 16);
|
|
228
|
+
if (nv > 2) q[2] = static_cast<unsigned short>(hi);
|
|
229
|
+
if (nv > 3) q[3] = static_cast<unsigned short>(hi >> 16);
|
|
230
|
+
}
|
|
231
|
+
__device__ __forceinline__ void st4_n(float* p, float4 v, int nv) {
|
|
232
|
+
if (nv > 0) p[0] = v.x;
|
|
233
|
+
if (nv > 1) p[1] = v.y;
|
|
234
|
+
if (nv > 2) p[2] = v.z;
|
|
235
|
+
if (nv > 3) p[3] = v.w;
|
|
236
|
+
}
|
|
237
|
+
__device__ __forceinline__ void st_raw_n(__nv_bfloat16* p, uint2 v, int nv) {
|
|
238
|
+
unsigned short* q = reinterpret_cast<unsigned short*>(p);
|
|
239
|
+
if (nv > 0) q[0] = static_cast<unsigned short>(v.x);
|
|
240
|
+
if (nv > 1) q[1] = static_cast<unsigned short>(v.x >> 16);
|
|
241
|
+
if (nv > 2) q[2] = static_cast<unsigned short>(v.y);
|
|
242
|
+
if (nv > 3) q[3] = static_cast<unsigned short>(v.y >> 16);
|
|
243
|
+
}
|
|
244
|
+
__device__ __forceinline__ void st_raw_n(float* p, float4 v, int nv) { st4_n(p, v, nv); }
|
|
245
|
+
|
|
246
|
+
@QUANT_HELPERS@
|
|
247
|
+
// Quantized walks (QUANT 1 fp8 / 2 int4), before the walk: the valid LASTs of the warp's tile [s, e) in order, kQB at a
|
|
248
|
+
// time: f(sl, n, k0) with sl[j] (j < n) the slot of the tile's valid group k0 + j (sl[j >= n]: a repeat of sl[n - 1]).
|
|
249
|
+
// Each record batch's valid slots are compacted through scr (the warp's 32 record words, free before the walk), so every
|
|
250
|
+
// call carries kQB rows whatever the share of valid positions. Returns the tile's valid groups.
|
|
251
|
+
constexpr int kQB = 8;
|
|
252
|
+
template <typename F>
|
|
253
|
+
__device__ __forceinline__ int q_batches(const uint4* __restrict__ kr, int s, int e, int lane, uint32_t* scr, F f) {
|
|
254
|
+
int k = 0;
|
|
255
|
+
for (int pos = s; pos < e; pos += 32) {
|
|
256
|
+
const uint4 r = ld_rec(kr, pos + lane, e);
|
|
257
|
+
const bool v = (r.x & (kLast | kInvalid)) == kLast;
|
|
258
|
+
const uint32_t ml = __ballot_sync(kFull, v);
|
|
259
|
+
const int nv = __popc(ml);
|
|
260
|
+
if (v) scr[__popc(ml & ((1u << lane) - 1u))] = r.y;
|
|
261
|
+
__syncwarp();
|
|
262
|
+
#pragma unroll 1
|
|
263
|
+
for (int b0 = 0; b0 < nv; b0 += kQB) {
|
|
264
|
+
const int n = min(kQB, nv - b0);
|
|
265
|
+
uint32_t sl[kQB];
|
|
266
|
+
#pragma unroll
|
|
267
|
+
for (int j = 0; j < kQB; ++j) sl[j] = scr[b0 + min(j, n - 1)];
|
|
268
|
+
f(sl, n, k);
|
|
269
|
+
k += n;
|
|
270
|
+
}
|
|
271
|
+
__syncwarp(); // this batch's reads precede the next batch's writes
|
|
272
|
+
}
|
|
273
|
+
return k;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
struct FxGeo {
|
|
277
|
+
int P, C, n, hd, NSL, SW, K, TU;
|
|
278
|
+
int tok_bytes; // shared bytes of the token rows (128-aligned)
|
|
279
|
+
uint32_t row_bytes; // hd * sizeof(element): bytes of one bank row
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// A quantized walk's rows (QUANT_HELPERS), before the walk, over q_batches: load(sl, n, v) reads the lane's columns of
|
|
283
|
+
// the pre-write rows of slots sl[j < n] into v (a V); amax(v, a) gives each lane's |max| a[j] over them (0 for inactive
|
|
284
|
+
// columns) -> the warp's max in shared memory after tok_bar ({amax, scale} per valid group of the CTA's tiles, from the
|
|
285
|
+
// first tile's dense ordinal); CL (column slices, sm_90+: the CTAs of every slice of the same tiles form one cluster):
|
|
286
|
+
// barrier, row amax = max of the peers' partials (DSMEM; exact, any order); scale -> qw[k].y, the writer stores it at
|
|
287
|
+
// qsc_row0[k]; then store(v, n, k0, qw) writes the lane's quantized columns of the rows k0 + j (the first batch from
|
|
288
|
+
// registers, the others re-read). Bank reads, the quantized rows and their scales only (no other kernel of the step
|
|
289
|
+
// touches them). CL: the caller waits on the cluster before it exits.
|
|
290
|
+
template <int Q, int NW, bool CL, typename V, typename Ld, typename Am, typename St>
|
|
291
|
+
__device__ __forceinline__ void q_rows(unsigned char* smem, const FxGeo& g, const int32_t* __restrict__ segb, int64_t tix0,
|
|
292
|
+
int g0, const uint4* __restrict__ kr, int s, int e, int lane, uint32_t* scr,
|
|
293
|
+
bool writer, float* __restrict__ qsc_row0, Ld load, Am amax, St store) {
|
|
294
|
+
const int ord0 = __ldg(segb + tix0);
|
|
295
|
+
if (__ldg(segb + tix0 + NW) - ord0 > NW * kTileRows) __trap(); // the shared-memory bound of every fx tile set
|
|
296
|
+
float2* const qw = reinterpret_cast<float2*>(smem + g.tok_bytes + NW * 32 * sizeof(uint4) + 16) + (g0 - ord0);
|
|
297
|
+
V held; // the tile's first batch (n_held rows), kept for the stores
|
|
298
|
+
int n_held = 0;
|
|
299
|
+
const int k = q_batches(kr, s, e, lane, scr, [&](const uint32_t* sl, int n, int k0) {
|
|
300
|
+
V v;
|
|
301
|
+
load(sl, n, v);
|
|
302
|
+
float a[kQB];
|
|
303
|
+
amax(v, a);
|
|
304
|
+
#pragma unroll
|
|
305
|
+
for (int j = 0; j < kQB; ++j) {
|
|
306
|
+
if (j < n) {
|
|
307
|
+
const float m = q_warp_max(a[j]);
|
|
308
|
+
if (lane == 0) qw[k0 + j].x = m;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (k0 == 0) {
|
|
312
|
+
held = v;
|
|
313
|
+
n_held = n;
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
if constexpr (CL) {
|
|
317
|
+
cluster_arrive();
|
|
318
|
+
cluster_wait();
|
|
319
|
+
} else {
|
|
320
|
+
__syncwarp();
|
|
321
|
+
}
|
|
322
|
+
for (int i = lane; i < k; i += 32) {
|
|
323
|
+
float m = qw[i].x;
|
|
324
|
+
if constexpr (CL) {
|
|
325
|
+
float pv[16];
|
|
326
|
+
#pragma unroll
|
|
327
|
+
for (int r = 0; r < 16; ++r) pv[r] = r < g.NSL ? ld_peer(&qw[i].x, static_cast<uint32_t>(r)) : 0.f;
|
|
328
|
+
#pragma unroll
|
|
329
|
+
for (int r = 0; r < 16; ++r) m = fmaxf(m, pv[r]);
|
|
330
|
+
}
|
|
331
|
+
qw[i].y = q_scale<Q>(m);
|
|
332
|
+
if (writer) qsc_row0[i] = qw[i].y;
|
|
333
|
+
}
|
|
334
|
+
__syncwarp();
|
|
335
|
+
if constexpr (CL) cluster_arrive(); // this CTA's reads of its peers' rows are done
|
|
336
|
+
if (n_held > 0) store(held, n_held, 0, qw);
|
|
337
|
+
if (k > n_held) {
|
|
338
|
+
q_batches(kr, s, e, lane, scr, [&](const uint32_t* sl, int n, int k0) {
|
|
339
|
+
if (k0 > 0) {
|
|
340
|
+
V v;
|
|
341
|
+
load(sl, n, v);
|
|
342
|
+
store(v, n, k0, qw);
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// The all-slices quantized form (QUANT 5 fp8 / 6 int4, q = QUANT & 3; every backend: the portable side's only column-
|
|
349
|
+
// sliced quantized form (no clusters there), and the modern form for heads past the 16-CTA cluster): ONE CTA per tile set
|
|
350
|
+
// walks every slice of its tiles (grid P * K, the kernels' slice passes), so before any of its walks writes a bank row
|
|
351
|
+
// each warp quantizes its own tile's valid groups over ALL hd columns (q_batches order = the dense ordinals, as q_rows):
|
|
352
|
+
// per kQB-row batch the nsl slices of SW columns are streamed twice (pass 1: the lane's |max| over its 4 columns of every
|
|
353
|
+
// slice -> q_warp_max -> q_scale, lane 0 stores the scale once per row; pass 2: q_store4 per slice), O(1) registers per
|
|
354
|
+
// row, no shared memory beyond scr (the warp's record words, as q_batches), no barrier: the warp is the only writer of its
|
|
355
|
+
// tile's slots and walks them afterwards. Lanes past the slice width (4 * lane >= SW: the final tail) are off. The max is
|
|
356
|
+
// exact, so the scales and payload bytes are those of the cluster form (EXACT = !bf16, y = __frcp_rn(sc), as q_rows).
|
|
357
|
+
// Plain coherent loads (the rows the previous step's DW wrote). Bank reads, quantized rows and scales only.
|
|
358
|
+
template <int Q, bool EXACT, typename E>
|
|
359
|
+
__device__ __forceinline__ void q_rows_cta(const E* __restrict__ bank, const FxGeo& g, int nsl, const uint4* __restrict__ kr,
|
|
360
|
+
int s, int e, int lane, uint32_t* scr, uint8_t* __restrict__ qpay,
|
|
361
|
+
float* __restrict__ qsc) {
|
|
362
|
+
using RowT = typename Row4<E>::T;
|
|
363
|
+
const bool on = 4 * lane < g.SW;
|
|
364
|
+
const int c4 = on ? 4 * lane : 0;
|
|
365
|
+
const int64_t qrow = Q == 1 ? g.hd : g.hd / 2;
|
|
366
|
+
q_batches(kr, s, e, lane, scr, [&](const uint32_t* sl, int n, int k0) {
|
|
367
|
+
float a[kQB];
|
|
368
|
+
#pragma unroll
|
|
369
|
+
for (int j = 0; j < kQB; ++j) a[j] = 0.f;
|
|
370
|
+
#pragma unroll 1
|
|
371
|
+
for (int c = 0; c < nsl; ++c) {
|
|
372
|
+
const int64_t col = static_cast<int64_t>(c) * g.SW + c4;
|
|
373
|
+
#pragma unroll
|
|
374
|
+
for (int j = 0; j < kQB; ++j) {
|
|
375
|
+
if (j < n && on) {
|
|
376
|
+
a[j] = fmaxf(a[j], q_amax4(cvt4(__ldcg(reinterpret_cast<const RowT*>(
|
|
377
|
+
bank + static_cast<int64_t>(sl[j]) * g.hd + col)))));
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
float sc[kQB];
|
|
382
|
+
#pragma unroll
|
|
383
|
+
for (int j = 0; j < kQB; ++j) {
|
|
384
|
+
sc[j] = 1.f;
|
|
385
|
+
if (j < n) { // warp-uniform: every lane takes part in the warp max
|
|
386
|
+
sc[j] = q_scale<Q>(q_warp_max(a[j]));
|
|
387
|
+
if (lane == 0) qsc[k0 + j] = sc[j];
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
#pragma unroll 1
|
|
391
|
+
for (int c = 0; c < nsl; ++c) {
|
|
392
|
+
const int64_t col = static_cast<int64_t>(c) * g.SW + c4;
|
|
393
|
+
#pragma unroll
|
|
394
|
+
for (int j = 0; j < kQB; ++j) {
|
|
395
|
+
if (j < n && on) {
|
|
396
|
+
const RowT v = __ldcg(reinterpret_cast<const RowT*>(bank + static_cast<int64_t>(sl[j]) * g.hd + col));
|
|
397
|
+
q_store4<Q, EXACT>(qpay + static_cast<int64_t>(k0 + j) * qrow, col, cvt4(v), sc[j],
|
|
398
|
+
EXACT ? 0.f : __frcp_rn(sc[j]));
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Grid: P * NSL * K CTAs (CTA b: cr = b % K, slice = b / K % NSL, p = rest); warp w walks tile cr * NW + w of its chunk.
|
|
406
|
+
// Shared memory: token rows [C][SW] f32 | rings [NW][NU][8 rows][SW] bf16 | record batch [NW][32] uint4 |
|
|
407
|
+
// mbarriers [1 + NW * NU].
|
|
408
|
+
// BANKB (bank-direct, the only mode the launchers instantiate): no rings, no segb; the base row of each valid LAST is
|
|
409
|
+
// read from bank[slot] itself (the pre-step row: within one launch every group owns its slot, so no other emit writes
|
|
410
|
+
// it; the read precedes the group's own store in the same thread). Same values as the dense snapshot rows (raw copies
|
|
411
|
+
// of those bank rows), hence the same arithmetic and bits. Shared memory: token rows | record batch | tok_bar.
|
|
412
|
+
// Without BANKB the base rows stream from the dense snapshot through the TMA rings; PF (BANKB only) cp.async's a
|
|
413
|
+
// batch's base rows into per-warp buffers [NW][32 rows][RB] before its walk. Neither is instantiated.
|
|
414
|
+
// WR (BANKB only): the DW also writes the saving forward's dense compact snapshot (layout 2): the base row of each valid
|
|
415
|
+
// group (its pre-write bank row, which BANKB reads anyway) goes to snapc row (ti * sb_step + p) * n + g, g = the group's
|
|
416
|
+
// dense ordinal in the chunk = segb[tile] + valid LASTs before it in the tile. Raw copies: the same bytes the
|
|
417
|
+
// gather would write there, so the backward is unchanged; the gather then writes no compact rows.
|
|
418
|
+
// E: the bank / snapshot element type (ROW_HELPERS: Row4, cvt4, st4, st_raw). bf16: one RN rounding per stored element;
|
|
419
|
+
// float: the fmaf result is stored as it is (float32 rows, 16 bytes per lane), always with 64-bit row offsets (BIG).
|
|
420
|
+
// QUANT (1 fp8, 2 int4; BANKB, !PF, !WR, BIG): the saving forward's compact rows, quantized (QUANT_HELPERS): the row of
|
|
421
|
+
// each valid group (WR's row index and base-row registers) goes to qpay ([rows, hd] fp8 bytes or [rows, hd / 2] int4)
|
|
422
|
+
// with its per-row scale in qsc[row]; qpay / qsc travel in the first two words of map_rows (a bank-direct walk never
|
|
423
|
+
// reads that map), so every instantiation keeps one parameter list. One slice (!T2D): the warp holds the whole row and
|
|
424
|
+
// quantizes it at its valid LAST (warp max). Column slices: the CTAs of the slices of the same tiles form one cluster
|
|
425
|
+
// (launched slice-fastest) and q_rows writes the rows before the PDL wait (overlapping the dual matmul).
|
|
426
|
+
// SE: the compact snapshot's element type: E, or bf16 rows of a float32 bank's WR walk (snapshot_quant bf16: each group's
|
|
427
|
+
// pre-write row rounded to bf16, RN, at its dense row; the bank update is the same walk and arithmetic as with SE = E).
|
|
428
|
+
template <typename E, int NW, int NU, bool T2D, bool BANKB, bool PF, bool WR, bool BIG, int QUANT = 0, typename SE = E>
|
|
429
|
+
__global__ void __launch_bounds__(NW * 32, (NW <= 8 ? 2 : 1)) fx_dw_kernel(
|
|
430
|
+
E* __restrict__ bank, const float* __restrict__ dv, int64_t dv_step_rows,
|
|
431
|
+
const uint4* __restrict__ krec, const int32_t* __restrict__ tb, const int32_t* __restrict__ segb,
|
|
432
|
+
SE* __restrict__ snapc, int64_t sb_step, int ti, FxGeo g,
|
|
433
|
+
const __grid_constant__ CUtensorMap map_rows, const __grid_constant__ CUtensorMap map_tok) {
|
|
434
|
+
static_assert(std::is_same<SE, E>::value ||
|
|
435
|
+
(std::is_same<E, float>::value && std::is_same<SE, __nv_bfloat16>::value && BANKB && WR && !PF &&
|
|
436
|
+
(QUANT & 7) == 0),
|
|
437
|
+
"fx_dw_kernel: a compact snapshot type other than the bank's: bf16 rows of a float32 bank's WR walk");
|
|
438
|
+
static_assert(BANKB || !PF, "fx_dw_kernel: the batch prefetch (PF) is a bank-direct (BANKB) mode");
|
|
439
|
+
static_assert(BANKB || !WR, "fx_dw_kernel: the compact-snapshot write (WR) is a bank-direct (BANKB) mode");
|
|
440
|
+
static_assert(!(PF && WR), "fx_dw_kernel: WR is not combined with the batch prefetch");
|
|
441
|
+
static_assert((BANKB && !PF) || !BIG, "fx_dw_kernel: banks of > 4 GiB use the bank-direct walk without prefetch");
|
|
442
|
+
constexpr bool kBf = std::is_same<E, __nv_bfloat16>::value;
|
|
443
|
+
static_assert(kBf || (BANKB && !PF && BIG), "fx_dw_kernel: float32 banks: the bank-direct walk, 64-bit offsets");
|
|
444
|
+
#if defined(FX_PRE_SM90)
|
|
445
|
+
static_assert(BANKB && !PF, "fx_dw_kernel: the portable side runs the bank-direct walk without prefetch only");
|
|
446
|
+
#endif
|
|
447
|
+
using RowT = typename Row4<E>::T; // a lane's 4 columns of a bank row
|
|
448
|
+
// QUANT = variant bits: bits 0-2 the quant form (0 none; 1 / 2 the cluster form (column slices: the CTAs of every slice
|
|
449
|
+
// of the same tiles, sm_90+) / in-walk quantization (one slice); 5 / 6 the all-slices form (q_rows_cta: one CTA per
|
|
450
|
+
// tile set, grid P * K, the slices walked in turn); q = QF & 3), bit 4 UNEVEN (tails: 128-wide column slices, the
|
|
451
|
+
// last one short)
|
|
452
|
+
constexpr int QF = QUANT & 7;
|
|
453
|
+
constexpr bool kQC = QF == 1 || QF == 2; // the cluster form (column slices) / in-walk quantization (one slice)
|
|
454
|
+
constexpr bool kAllSl = QF >= 5; // the all-slices form
|
|
455
|
+
constexpr bool kUneven = (QUANT & 16) != 0;
|
|
456
|
+
constexpr bool kScalar = (QUANT & 32) != 0; // tails: hd % 4 != 0 (element accesses, CTA-copied token rows)
|
|
457
|
+
static_assert(QF == 0 || (BANKB && !PF && !WR && BIG), "fx_dw_kernel: quantized rows: bank-direct, 64-bit offsets");
|
|
458
|
+
static_assert(!kAllSl || T2D, "fx_dw_kernel: the all-slices quantized form is for column-sliced heads");
|
|
459
|
+
static_assert(!kUneven || (BANKB && !PF && T2D && QF == 0), "fx_dw_kernel: uneven slices: bank-direct, unquantized");
|
|
460
|
+
static_assert(!kScalar || (BANKB && !PF && QF == 0 && !kUneven), "fx_dw_kernel: SCALAR: bank-direct, unquantized");
|
|
461
|
+
extern __shared__ __align__(128) unsigned char smem[];
|
|
462
|
+
const int lane = threadIdx.x & 31, warp = threadIdx.x >> 5;
|
|
463
|
+
int b = blockIdx.x;
|
|
464
|
+
if constexpr (kQC && T2D) b = b / g.NSL % g.K + g.K * (b % g.NSL + g.NSL * (b / g.NSL / g.K)); // cluster order
|
|
465
|
+
if constexpr (kAllSl) b = b / g.K * g.NSL * g.K + b % g.K; // all slices: grid P * K, from slice 0
|
|
466
|
+
fx_slice: // kAllSl: the next slice of this CTA's tiles restarts here (the end of the kernel)
|
|
467
|
+
const int cr = b % g.K;
|
|
468
|
+
b /= g.K;
|
|
469
|
+
const int sl = b % g.NSL;
|
|
470
|
+
const int p = b / g.NSL;
|
|
471
|
+
const int64_t chunk = static_cast<int64_t>(ti) * g.P + p;
|
|
472
|
+
const int SW = g.SW, C = g.C;
|
|
473
|
+
const int RB = SW * static_cast<int>(sizeof(E)); // bytes of a row slice (multiple of 16)
|
|
474
|
+
const int UB = kUnitRows * RB; // bytes of a ring unit (multiple of 128)
|
|
475
|
+
float* tok_s = reinterpret_cast<float*>(smem);
|
|
476
|
+
constexpr int NUR = BANKB ? 0 : NU; // ring units per warp held in shared memory
|
|
477
|
+
const int PFB = PF ? 32 * RB : 0; // PF: bytes of a warp's prefetch buffer (32 rows of RB)
|
|
478
|
+
unsigned char* const rings = smem + g.tok_bytes;
|
|
479
|
+
unsigned char* const ring = rings + static_cast<size_t>(warp) * NUR * UB;
|
|
480
|
+
unsigned char* const pfb = rings + static_cast<size_t>(warp) * PFB; // PF: row j = position pos0 + j of the batch
|
|
481
|
+
uint4* const rec_s = reinterpret_cast<uint4*>(rings + static_cast<size_t>(NW) * NUR * UB +
|
|
482
|
+
static_cast<size_t>(NW) * PFB) + warp * 32;
|
|
483
|
+
uint64_t* const bars = reinterpret_cast<uint64_t*>(rings + static_cast<size_t>(NW) * NUR * UB +
|
|
484
|
+
static_cast<size_t>(NW) * PFB +
|
|
485
|
+
static_cast<size_t>(NW) * 32 * sizeof(uint4));
|
|
486
|
+
uint64_t* const tok_bar = bars;
|
|
487
|
+
uint64_t* const ubar = bars + 1 + warp * NUR;
|
|
488
|
+
if (threadIdx.x == 0) mbar_init(tok_bar, 1);
|
|
489
|
+
if (lane < NUR) mbar_init(ubar + lane, 1);
|
|
490
|
+
fence_mbar_init();
|
|
491
|
+
__syncthreads();
|
|
492
|
+
|
|
493
|
+
// Token rows of the slice, all on tok_bar: [C][SW] f32 (row stride SW floats).
|
|
494
|
+
const int64_t tok_row0 = static_cast<int64_t>(ti) * dv_step_rows + static_cast<int64_t>(p) * C;
|
|
495
|
+
// SCALAR: no TMA (the token rows' stride hd * 4 is not 16-byte aligned): the CTA copies [C][SW] with plain loads,
|
|
496
|
+
// zero past the slice's columns (SW = 128, or hd rounded up to 4 for one slice), then a CTA barrier.
|
|
497
|
+
auto tok_copy_scalar = [&]() {
|
|
498
|
+
const int swe_t = min(SW, g.hd - sl * SW);
|
|
499
|
+
const float* const src = dv + tok_row0 * g.hd + static_cast<int64_t>(sl) * SW;
|
|
500
|
+
for (int i = threadIdx.x; i < C * SW; i += NW * 32) {
|
|
501
|
+
const int r = i / SW, c = i - r * SW;
|
|
502
|
+
tok_s[i] = c < swe_t ? __ldcg(src + static_cast<int64_t>(r) * g.hd + c) : 0.f;
|
|
503
|
+
}
|
|
504
|
+
__syncthreads();
|
|
505
|
+
};
|
|
506
|
+
#if defined(FX_PRE_SM90)
|
|
507
|
+
// Portable: every thread copies its share of the C token rows (the slice's SW floats of each delta_v row; one slice:
|
|
508
|
+
// SW == hd; UNEVEN: the last slice's own columns, the walk reads no column past them) with cp.async and commits its
|
|
509
|
+
// group; the token wait below publishes them (no exit precedes it). SCALAR (hd % 4 != 0: rows are not 16-byte
|
|
510
|
+
// aligned): the CTA copies them with plain loads and a CTA barrier (tok_copy_scalar).
|
|
511
|
+
if constexpr (kScalar) {
|
|
512
|
+
tok_copy_scalar();
|
|
513
|
+
} else {
|
|
514
|
+
const int swt = kUneven ? min(SW, g.hd - sl * SW) : SW;
|
|
515
|
+
sdmcp::copy_rows16(tok_s, SW * 4, dv + tok_row0 * g.hd + static_cast<int64_t>(sl) * SW, static_cast<int64_t>(g.hd) * 4,
|
|
516
|
+
C, swt * 4, static_cast<int>(threadIdx.x), NW * 32);
|
|
517
|
+
sdmcp::commit();
|
|
518
|
+
}
|
|
519
|
+
#endif
|
|
520
|
+
|
|
521
|
+
const int64_t tix = chunk * (g.TU + 1) + cr * NW + warp;
|
|
522
|
+
const int s = __ldg(tb + tix), e = __ldg(tb + tix + 1);
|
|
523
|
+
const int g0 = (BANKB && !WR && QF == 0) ? 0 : __ldg(segb + tix); // WR / QUANT: the tile's first dense ordinal
|
|
524
|
+
const int nseg = BANKB ? 0 : __ldg(segb + tix + 1) - g0; // valid groups of the tile = emits = dense rows (ring)
|
|
525
|
+
const int nunits = (nseg + kUnitRows - 1) / kUnitRows;
|
|
526
|
+
const int64_t drow0 = (static_cast<int64_t>(ti) * sb_step + p) * g.n + g0; // dense row of the tile's group 0
|
|
527
|
+
// Unit u = dense rows [drow0 + 8u, drow0 + 8u + 8) clipped to the tile, into ring slot u % NU, completing on
|
|
528
|
+
// ubar[u % NU] (k-th use of a slot = phase k). Lane 0 only.
|
|
529
|
+
const CUtensorMap* const mrows = &map_rows; // generic address of the grid-constant parameter
|
|
530
|
+
auto issue_unit = [&](int u) {
|
|
531
|
+
uint64_t* bar = ubar + (u % NU);
|
|
532
|
+
unsigned char* dst = ring + (u % NU) * UB;
|
|
533
|
+
const int k = min(kUnitRows, nseg - u * kUnitRows);
|
|
534
|
+
const int64_t row = drow0 + static_cast<int64_t>(u) * kUnitRows;
|
|
535
|
+
mbar_arrive_tx(bar, static_cast<uint32_t>(k * RB));
|
|
536
|
+
if constexpr (T2D) {
|
|
537
|
+
if (k == kUnitRows) {
|
|
538
|
+
tma2d_g2s(dst, mrows, sl * SW, static_cast<int>(row), bar);
|
|
539
|
+
} else {
|
|
540
|
+
for (int i = 0; i < k; ++i) {
|
|
541
|
+
bulk_g2s(dst + i * RB, snapc + (row + i) * g.hd + static_cast<int64_t>(sl) * SW,
|
|
542
|
+
static_cast<uint32_t>(RB), bar);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
} else {
|
|
546
|
+
bulk_g2s(dst, snapc + row * g.hd, static_cast<uint32_t>(k * RB), bar); // one slice: contiguous rows
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
const uint4* const kr = krec + chunk * g.n;
|
|
551
|
+
const int c4 = 4 * lane;
|
|
552
|
+
const int swe = (kUneven || kScalar) ? min(SW, g.hd - sl * SW) : SW; // UNEVEN / SCALAR: the last slice is short
|
|
553
|
+
const bool on = c4 < swe;
|
|
554
|
+
const int nv = kScalar ? min(4, swe - c4) : 4; // SCALAR: the lane's columns in range (<= 0 when off)
|
|
555
|
+
const int coff = on ? c4 : 0; // safe column for token loads
|
|
556
|
+
const int ring_off = (on ? lane : 0) * static_cast<int>(sizeof(RowT)); // the lane's 4 columns in a ring row
|
|
557
|
+
// coff, not c4: an inactive lane (c4 >= SW) never forms a pointer past the bank; its stores are off.
|
|
558
|
+
unsigned char* const bank_lane = reinterpret_cast<unsigned char*>(bank + static_cast<int64_t>(sl) * SW + coff);
|
|
559
|
+
// PF: the slice's column 0 (16-byte aligned: bank, hd * 2 and SW * 2 are, host-checked); copy lane chunk c16.
|
|
560
|
+
const unsigned char* const bank_sl = reinterpret_cast<const unsigned char*>(bank + static_cast<int64_t>(sl) * SW);
|
|
561
|
+
const int c16 = (lane & 15) * 16;
|
|
562
|
+
// WR: this lane's 4 columns of the slice in a compact-snapshot row r: element r * hd + snap_col (written iff on).
|
|
563
|
+
const int64_t snap_col = static_cast<int64_t>(sl) * SW + coff;
|
|
564
|
+
|
|
565
|
+
uint4 r0 = ld_rec(kr, s + lane, e);
|
|
566
|
+
uint4 r1 = ld_rec(kr, s + 32 + lane, e);
|
|
567
|
+
uint4 r2 = ld_rec(kr, s + 64 + lane, e);
|
|
568
|
+
uint4 r3 = ld_rec(kr, s + 96 + lane, e);
|
|
569
|
+
if (lane == 0) {
|
|
570
|
+
for (int u = 0; u < NU && u < nunits; ++u) issue_unit(u);
|
|
571
|
+
}
|
|
572
|
+
// QUANT && T2D: the quantized rows before the PDL wait (they read the bank rows the previous step's DW wrote, three
|
|
573
|
+
// launches back, and write only the quantized rows and their scales), overlapping the dual matmul.
|
|
574
|
+
uint8_t* const qpay = QF != 0 ? reinterpret_cast<uint8_t*>(map_rows.opaque[0]) + drow0 * ((QF & 3) == 1 ? g.hd : g.hd / 2)
|
|
575
|
+
: nullptr;
|
|
576
|
+
float* const qsc = QF != 0 ? reinterpret_cast<float*>(map_rows.opaque[1]) + drow0 : nullptr;
|
|
577
|
+
if constexpr (kAllSl) { // every slice of these rows is written by this CTA: quantize them whole before slice 0's walk
|
|
578
|
+
if (sl == 0) q_rows_cta<QF & 3, !kBf>(bank, g, g.NSL, kr, s, e, lane, reinterpret_cast<uint32_t*>(rec_s), qpay, qsc);
|
|
579
|
+
}
|
|
580
|
+
if constexpr (kQC && T2D) {
|
|
581
|
+
struct QV { RowT v[kQB]; };
|
|
582
|
+
q_rows<QF, NW, true, QV>(smem, g, segb, chunk * (g.TU + 1) + cr * NW, g0, kr, s, e, lane,
|
|
583
|
+
reinterpret_cast<uint32_t*>(rec_s), sl == 0, qsc,
|
|
584
|
+
[&](const uint32_t* sla, int n, QV& q) {
|
|
585
|
+
#pragma unroll
|
|
586
|
+
for (int j = 0; j < kQB; ++j) {
|
|
587
|
+
q.v[j] = make_zero_row<RowT>();
|
|
588
|
+
if (j < n) q.v[j] = __ldcg(reinterpret_cast<const RowT*>(fx_row<BIG>(bank_lane, sla[j], g.row_bytes)));
|
|
589
|
+
}
|
|
590
|
+
}, [&](const QV& q, float* a) {
|
|
591
|
+
#pragma unroll
|
|
592
|
+
for (int j = 0; j < kQB; ++j) {
|
|
593
|
+
if constexpr (kBf) {
|
|
594
|
+
a[j] = on ? q_amax_bf4(q.v[j]) : 0.f;
|
|
595
|
+
} else {
|
|
596
|
+
a[j] = on ? q_amax4(cvt4(q.v[j])) : 0.f;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}, [&](const QV& q, int n, int k0, const float2* qw) {
|
|
600
|
+
#pragma unroll
|
|
601
|
+
for (int j = 0; j < kQB; ++j) {
|
|
602
|
+
if (j < n && on) {
|
|
603
|
+
const float sc = qw[k0 + j].y;
|
|
604
|
+
q_store4<QF, !kBf>(qpay + static_cast<int64_t>(k0 + j) * ((QF & 3) == 1 ? g.hd : g.hd / 2), snap_col,
|
|
605
|
+
cvt4(q.v[j]), sc, kBf ? __frcp_rn(sc) : 0.f);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
#if !defined(FX_PRE_SM90)
|
|
611
|
+
// PDL: everything above reads only data written two or more launches back: Phase 1's records, tile bounds and segb
|
|
612
|
+
// (built before the step loop), the ring units' dense snapshot rows (written by this step's gather, which completed
|
|
613
|
+
// before the dual matmul launched) and, QUANT, the bank rows (the previous step's DW). The token rows are the dual
|
|
614
|
+
// matmul's output (the launch right before): every thread waits for that grid's completion and memory first (no exit
|
|
615
|
+
// precedes this point; the only global writes before it are QUANT's rows and scales, which no other launch of the
|
|
616
|
+
// step reads or writes), then thread 0 copies them. Later reads of predecessor-written data are coherent: token rows by TMA, bank rows by
|
|
617
|
+
// __ldcg / cp.async.cg. Dependents (the next step's gather, which waits for this grid in turn) may launch from here.
|
|
618
|
+
asm volatile("griddepcontrol.wait;" ::: "memory");
|
|
619
|
+
if constexpr (kScalar) {
|
|
620
|
+
tok_copy_scalar();
|
|
621
|
+
} else if (threadIdx.x == 0) {
|
|
622
|
+
mbar_arrive_tx(tok_bar, static_cast<uint32_t>(C * SW * 4));
|
|
623
|
+
if constexpr (T2D) {
|
|
624
|
+
tma2d_g2s(tok_s, &map_tok, sl * SW, static_cast<int>(tok_row0), tok_bar);
|
|
625
|
+
} else {
|
|
626
|
+
bulk_g2s(tok_s, dv + tok_row0 * g.hd, static_cast<uint32_t>(C * SW * 4), tok_bar); // SW == hd
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
asm volatile("griddepcontrol.launch_dependents;" ::: "memory");
|
|
630
|
+
#endif
|
|
631
|
+
int issued_u = min(NU, nunits); // units issued so far (warp-uniform)
|
|
632
|
+
// PF: the token rows are waited for inside batch 0, after its base-row copies are issued (the two latencies overlap).
|
|
633
|
+
#if defined(FX_PRE_SM90)
|
|
634
|
+
if constexpr (!kScalar) sdmcp::publish_cta(); // every copier's token rows landed; the CTA barrier publishes them
|
|
635
|
+
const uint32_t ztok = 0u; // (SCALAR: tok_copy_scalar ended with its CTA barrier)
|
|
636
|
+
#else
|
|
637
|
+
const uint32_t ztok = (PF || kScalar) ? 0u : mbar_wait_dep(tok_bar, 0u); // SCALAR: copied above (barrier)
|
|
638
|
+
#endif
|
|
639
|
+
const float* tok = tok_s + ztok + coff;
|
|
640
|
+
uint32_t zu = 0u; // sum of the unit waits' tokens (0): ring reads depend on it
|
|
641
|
+
float4 acc = make_float4(0.f, 0.f, 0.f, 0.f);
|
|
642
|
+
int done = 0; // groups emitted so far (= dense row of the next emit)
|
|
643
|
+
int roff = 0; // ring byte offset of row `done` (set at each unit start)
|
|
644
|
+
const int nb = (e - s + 31) >> 5;
|
|
645
|
+
for (int bi = 0; bi < nb; ++bi) {
|
|
646
|
+
if (bi > 0) {
|
|
647
|
+
r0 = r1;
|
|
648
|
+
r1 = r2;
|
|
649
|
+
r2 = r3;
|
|
650
|
+
r3 = ld_rec(kr, s + 32 * (bi + 3) + lane, e);
|
|
651
|
+
}
|
|
652
|
+
const uint32_t x = r0.x;
|
|
653
|
+
const uint32_t ml = __ballot_sync(kFull, (x & (kLast | kInvalid)) == kLast); // valid LASTs: emit
|
|
654
|
+
const uint32_t me = __ballot_sync(kFull, (x & kLast) != 0u); // every LAST: restart
|
|
655
|
+
const uint32_t tokoff = static_cast<uint32_t>(min(static_cast<int>((x >> 9) & 0xFFu), C - 1) * SW);
|
|
656
|
+
// {token offset, scale, row bytes (BIG: row index), cpe}
|
|
657
|
+
rec_s[lane] = make_uint4(tokoff, r0.z, BIG ? r0.y : r0.y * g.row_bytes, r0.w);
|
|
658
|
+
if constexpr (PF) {
|
|
659
|
+
// Base rows of this batch's valid LASTs (ml), bank[slot] -> pfb row j (position pos0 + j): lanes 0-15 copy
|
|
660
|
+
// row 2i, lanes 16-31 row 2i + 1, 16 bytes per lane per row. The previous batch's reads of pfb precede
|
|
661
|
+
// these writes (its closing __syncwarp; the cp.async asm carries a memory clobber). The bytes are the ones
|
|
662
|
+
// BANKB's __ldcg reads later in the walk: no store of this launch writes a valid LAST's slot except that
|
|
663
|
+
// group's own emit, which follows this read (one group per slot per launch: the single-writer rule).
|
|
664
|
+
const uint32_t rbl = r0.y * g.row_bytes; // this lane's row bytes (= rec_s[lane].z)
|
|
665
|
+
#pragma unroll
|
|
666
|
+
for (int i = 0; i < 16; ++i) {
|
|
667
|
+
const int j = 2 * i + (lane >> 4);
|
|
668
|
+
const uint32_t rbj = __shfl_sync(kFull, rbl, j);
|
|
669
|
+
if (c16 < RB && ((ml >> j) & 1u)) cp_async16(pfb + j * RB + c16, bank_sl + rbj + c16);
|
|
670
|
+
}
|
|
671
|
+
if (bi == 0) tok = tok_s + mbar_wait_dep(tok_bar, 0u) + coff; // token rows (dependency token)
|
|
672
|
+
cp_async_wait_all(); // this lane's copies landed; the __syncwarp shares them
|
|
673
|
+
}
|
|
674
|
+
__syncwarp();
|
|
675
|
+
const int pos0 = s + 32 * bi;
|
|
676
|
+
const int kend = min(32, e - pos0);
|
|
677
|
+
#pragma unroll 1
|
|
678
|
+
for (int k8 = 0; k8 < kend; k8 += 8) {
|
|
679
|
+
uint2 rr[8];
|
|
680
|
+
float4 xx[8];
|
|
681
|
+
#pragma unroll
|
|
682
|
+
for (int j = 0; j < 8; ++j) rr[j] = *reinterpret_cast<const uint2*>(rec_s + k8 + j);
|
|
683
|
+
#pragma unroll
|
|
684
|
+
for (int j = 0; j < 8; ++j) xx[j] = *reinterpret_cast<const float4*>(tok + rr[j].x);
|
|
685
|
+
const uint32_t e8 = (me >> k8) & 0xFFu;
|
|
686
|
+
if (e8 == 0u) {
|
|
687
|
+
#pragma unroll
|
|
688
|
+
for (int j = 0; j < 8; ++j) fma4(acc, rr[j].y, xx[j]);
|
|
689
|
+
} else if (BANKB) {
|
|
690
|
+
// Bank-direct: the walk's ops in the walk's order (acc = fma(sc, x, acc) per position; at a valid LAST
|
|
691
|
+
// o = fma(base, cpe, acc), one bf16 RN rounding, store; +0 after EVERY LAST); base = bank[slot] read
|
|
692
|
+
// first for every valid LAST of the sub-batch (distinct slots: no store of this sub-batch aliases a
|
|
693
|
+
// load). Inactive lanes (c4 >= SW) read column 0 of the slice (in bounds) and never store.
|
|
694
|
+
const uint32_t l8 = (ml >> k8) & 0xFFu;
|
|
695
|
+
RowT bv[8];
|
|
696
|
+
uint2 rc[8];
|
|
697
|
+
#pragma unroll
|
|
698
|
+
for (int j = 0; j < 8; ++j) {
|
|
699
|
+
rc[j] = *reinterpret_cast<const uint2*>(
|
|
700
|
+
reinterpret_cast<const unsigned char*>(rec_s + k8 + j) + 8); // {row bytes, cpe}
|
|
701
|
+
bv[j] = make_zero_row<RowT>();
|
|
702
|
+
if (l8 & (1u << j)) {
|
|
703
|
+
if constexpr (PF) {
|
|
704
|
+
bv[j] = *reinterpret_cast<const RowT*>(pfb + (k8 + j) * RB + ring_off); // prefetched
|
|
705
|
+
} else if constexpr (kScalar) {
|
|
706
|
+
bv[j] = ld_row_n<E>(fx_row<BIG>(bank_lane, rc[j].x, g.row_bytes), nv);
|
|
707
|
+
} else {
|
|
708
|
+
bv[j] = __ldcg(reinterpret_cast<const RowT*>(fx_row<BIG>(bank_lane, rc[j].x, g.row_bytes)));
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
#pragma unroll
|
|
713
|
+
for (int j = 0; j < 8; ++j) {
|
|
714
|
+
fma4(acc, rr[j].y, xx[j]);
|
|
715
|
+
if (l8 & (1u << j)) {
|
|
716
|
+
const float cpe = __uint_as_float(rc[j].y);
|
|
717
|
+
const float4 base = cvt4(bv[j]);
|
|
718
|
+
float4 o;
|
|
719
|
+
o.x = fmaf(base.x, cpe, acc.x);
|
|
720
|
+
o.y = fmaf(base.y, cpe, acc.y);
|
|
721
|
+
o.z = fmaf(base.z, cpe, acc.z);
|
|
722
|
+
o.w = fmaf(base.w, cpe, acc.w);
|
|
723
|
+
if constexpr (kScalar) {
|
|
724
|
+
if (on) st4_n(reinterpret_cast<E*>(fx_row<BIG>(bank_lane, rc[j].x, g.row_bytes)), o, nv);
|
|
725
|
+
} else {
|
|
726
|
+
if (on) st4(reinterpret_cast<E*>(fx_row<BIG>(bank_lane, rc[j].x, g.row_bytes)), o);
|
|
727
|
+
}
|
|
728
|
+
if constexpr (WR) {
|
|
729
|
+
// the group's dense row: ordinal done + (valid LASTs of this sub-batch before position j)
|
|
730
|
+
const int64_t row = drow0 + done + __popc(l8 & ((1u << j) - 1u));
|
|
731
|
+
if constexpr (!std::is_same<SE, E>::value) { // bf16 rows of a float32 bank: RN
|
|
732
|
+
if constexpr (kScalar) {
|
|
733
|
+
if (on) st4_n(snapc + (row * g.hd + snap_col), base, nv);
|
|
734
|
+
} else {
|
|
735
|
+
if (on) st4(snapc + (row * g.hd + snap_col), base);
|
|
736
|
+
}
|
|
737
|
+
} else if constexpr (kScalar) {
|
|
738
|
+
if (on) st_raw_n(snapc + (row * g.hd + snap_col), bv[j], nv); // raw row
|
|
739
|
+
} else {
|
|
740
|
+
if (on) st_raw(snapc + (row * g.hd + snap_col), bv[j]); // raw row
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
if constexpr (kQC && !T2D) { // one slice: the row, quantized with the warp's amax
|
|
744
|
+
const int kq = done + __popc(l8 & ((1u << j) - 1u));
|
|
745
|
+
const float sc = q_scale<QF>(q_warp_max(on ? q_amax4(base) : 0.f));
|
|
746
|
+
if (lane == 0) qsc[kq] = sc;
|
|
747
|
+
if (on) q_store4<QF, !kBf>(qpay + static_cast<int64_t>(kq) * ((QF & 3) == 1 ? g.hd : g.hd / 2),
|
|
748
|
+
snap_col, base, sc, kBf ? __frcp_rn(sc) : 0.f);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
if (e8 & (1u << j)) acc = make_float4(0.f, 0.f, 0.f, 0.f);
|
|
752
|
+
}
|
|
753
|
+
if constexpr (WR || (kQC && !T2D)) done += __popc(l8);
|
|
754
|
+
} else {
|
|
755
|
+
const uint32_t l8 = (ml >> k8) & 0xFFu;
|
|
756
|
+
#pragma unroll
|
|
757
|
+
for (int j = 0; j < 8; ++j) {
|
|
758
|
+
fma4(acc, rr[j].y, xx[j]);
|
|
759
|
+
if (e8 & (1u << j)) {
|
|
760
|
+
if (l8 & (1u << j)) {
|
|
761
|
+
if ((done & (kUnitRows - 1)) == 0) { // first row of unit u: refill, then wait
|
|
762
|
+
const int u = done / kUnitRows;
|
|
763
|
+
if (u >= 1 && u - 1 + NU < nunits) {
|
|
764
|
+
// Slot of unit u - 1 is free: every lane's generic reads of it precede the
|
|
765
|
+
// async-proxy refill (fence, THEN the warp sync that hands the slot over).
|
|
766
|
+
fence_proxy_async();
|
|
767
|
+
__syncwarp();
|
|
768
|
+
if (lane == 0) issue_unit(u - 1 + NU);
|
|
769
|
+
++issued_u;
|
|
770
|
+
}
|
|
771
|
+
zu += mbar_wait_dep(ubar + (u % NU), static_cast<uint32_t>(u / NU) & 1u);
|
|
772
|
+
roff = (u % NU) * UB;
|
|
773
|
+
}
|
|
774
|
+
const RowT bv = *reinterpret_cast<const RowT*>(ring + roff + zu + ring_off);
|
|
775
|
+
roff += RB;
|
|
776
|
+
const uint2 rc = *reinterpret_cast<const uint2*>(
|
|
777
|
+
reinterpret_cast<const unsigned char*>(rec_s + k8 + j) + 8); // {row bytes, cpe}
|
|
778
|
+
const float cpe = __uint_as_float(rc.y);
|
|
779
|
+
const float4 base = cvt4(bv);
|
|
780
|
+
float4 o;
|
|
781
|
+
o.x = fmaf(base.x, cpe, acc.x);
|
|
782
|
+
o.y = fmaf(base.y, cpe, acc.y);
|
|
783
|
+
o.z = fmaf(base.z, cpe, acc.z);
|
|
784
|
+
o.w = fmaf(base.w, cpe, acc.w);
|
|
785
|
+
if (on) st4(reinterpret_cast<E*>(bank_lane + rc.x), o);
|
|
786
|
+
++done;
|
|
787
|
+
}
|
|
788
|
+
acc = make_float4(0.f, 0.f, 0.f, 0.f);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
__syncwarp(); // this batch's rec_s reads precede the next batch's STS
|
|
794
|
+
}
|
|
795
|
+
// Drain (defensive; normally empty: every issued unit holds rows the walk emitted, so it was waited for): no copy
|
|
796
|
+
// into this CTA's shared memory may still be in flight when the CTA exits.
|
|
797
|
+
for (int u = (done + kUnitRows - 1) / kUnitRows; u < issued_u; ++u) {
|
|
798
|
+
(void)mbar_wait_dep(ubar + (u % NU), static_cast<uint32_t>(u / NU) & 1u);
|
|
799
|
+
}
|
|
800
|
+
if constexpr (PF) {
|
|
801
|
+
if (nb == 0) (void)mbar_wait_dep(tok_bar, 0u); // empty tile: the token-row TMA lands before the CTA can exit
|
|
802
|
+
}
|
|
803
|
+
if constexpr (kQC && T2D) cluster_wait(); // no peer reads this CTA's rows any more
|
|
804
|
+
if constexpr (kAllSl) {
|
|
805
|
+
if (sl + 1 < g.NSL) { // CTA-uniform: the next slice of the same tiles
|
|
806
|
+
fence_proxy_async(); // this slice's generic reads of the token rows precede the next slice's async-proxy copy
|
|
807
|
+
__syncthreads(); // every warp is done with this slice (token rows, record batches, bank rows)
|
|
808
|
+
if (threadIdx.x == 0) mbar_inval(tok_bar); // the prologue initializes it again
|
|
809
|
+
b = (p * g.NSL + sl + 1) * g.K + cr;
|
|
810
|
+
goto fx_slice;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// fx_dw2_kernel: the bank-direct DW (fx_dw_kernel with BANKB, never PF) with TWO column slices per warp: lanes 0-15
|
|
816
|
+
// take slice 2 * sp, lanes 16-31 slice 2 * sp + 1 of slice pair sp, 8 columns per lane in two groups of 4: columns [4
|
|
817
|
+
// l', 4 l' + 4) and [64 + 4 l', 64 + 4 l' + 4) of the half's slice (l' = lane & 15; conflict-free LDS.128 of the token
|
|
818
|
+
// rows, 128-byte coalesced 8-byte global accesses per half). One warp walks each position for both slices: the same
|
|
819
|
+
// records, masks and branches for all 32 lanes, and per column the BANKB walk's ops in its order (acc = fma(sc, x, acc)
|
|
820
|
+
// per position; at a valid LAST o = fma(base, cpe, acc), one bf16 RN rounding, store; acc = +0 after EVERY LAST; base =
|
|
821
|
+
// bank[slot] read before the group's own store, the single-writer rule), hence the bits of fx_dw_kernel<NW, *, true,
|
|
822
|
+
// true, false, WR, BIG>. Records, masks and addressing are shared by 8 columns instead of 4 (about half the
|
|
823
|
+
// instructions per column; the walk is issue-bound). Token rows: one 2D box {2 SW, C} per CTA ([C][2 SW] f32, map from
|
|
824
|
+
// fx_maps2). Grid P * (NSL / 2) * K (g.NSL = slice pairs). Shared memory: token rows | record batch [NW][32] uint4 |
|
|
825
|
+
// tok_bar. ALLON: SW == 128 (every column group of every lane in range). QUANT (!WR, BIG): the quantized rows as in
|
|
826
|
+
// fx_dw_kernel (q_rows before the PDL wait; one cluster = the CTAs of every slice pair of the same tiles).
|
|
827
|
+
template <int NW, bool WR, bool BIG, bool ALLON, int QUANT = 0>
|
|
828
|
+
__global__ void __launch_bounds__(NW * 32, (NW <= 8 ? 2 : 1)) fx_dw2_kernel(
|
|
829
|
+
__nv_bfloat16* __restrict__ bank, const float* __restrict__ dv, int64_t dv_step_rows,
|
|
830
|
+
const uint4* __restrict__ krec, const int32_t* __restrict__ tb, const int32_t* __restrict__ segb,
|
|
831
|
+
__nv_bfloat16* __restrict__ snapc, int64_t sb_step, int ti, FxGeo g,
|
|
832
|
+
const __grid_constant__ CUtensorMap map_rows, const __grid_constant__ CUtensorMap map_tok) {
|
|
833
|
+
constexpr int QF = QUANT & 7; // variant bits as in fx_dw_kernel (no tail bits on this walk)
|
|
834
|
+
constexpr bool kQC = QF == 1 || QF == 2; // the cluster form
|
|
835
|
+
constexpr bool kAllSl = QF >= 5; // the all-slices form (as fx_dw_kernel's): slice pairs walked in turn
|
|
836
|
+
static_assert(QF == 0 || (!WR && BIG), "fx_dw2_kernel: quantized rows: 64-bit offsets, no raw rows");
|
|
837
|
+
static_assert((QUANT & 48) == 0, "fx_dw2_kernel: no tail variants (tails take fx_dw_kernel)");
|
|
838
|
+
extern __shared__ __align__(128) unsigned char smem[];
|
|
839
|
+
const int lane = threadIdx.x & 31, warp = threadIdx.x >> 5;
|
|
840
|
+
int b = blockIdx.x;
|
|
841
|
+
if constexpr (kQC) b = b / g.NSL % g.K + g.K * (b % g.NSL + g.NSL * (b / g.NSL / g.K)); // cluster order
|
|
842
|
+
if constexpr (kAllSl) b = b / g.K * g.NSL * g.K + b % g.K; // all slices: grid P * K, from slice pair 0
|
|
843
|
+
fx2_slice: // kAllSl: the next slice pair restarts here (the end of the kernel)
|
|
844
|
+
const int cr = b % g.K;
|
|
845
|
+
b /= g.K;
|
|
846
|
+
const int sp = b % g.NSL; // slice pair
|
|
847
|
+
const int p = b / g.NSL;
|
|
848
|
+
const int64_t chunk = static_cast<int64_t>(ti) * g.P + p;
|
|
849
|
+
const int SW = g.SW, C = g.C;
|
|
850
|
+
const int RW = 2 * SW; // floats of a token row in shared memory (both slices)
|
|
851
|
+
float* tok_s = reinterpret_cast<float*>(smem);
|
|
852
|
+
uint4* const rec_s = reinterpret_cast<uint4*>(smem + g.tok_bytes) + warp * 32;
|
|
853
|
+
uint64_t* const tok_bar =
|
|
854
|
+
reinterpret_cast<uint64_t*>(smem + g.tok_bytes + static_cast<size_t>(NW) * 32 * sizeof(uint4));
|
|
855
|
+
if (threadIdx.x == 0) mbar_init(tok_bar, 1);
|
|
856
|
+
fence_mbar_init();
|
|
857
|
+
__syncthreads();
|
|
858
|
+
|
|
859
|
+
// Token rows of the slice pair, all on tok_bar: [C][2 SW] f32.
|
|
860
|
+
const int64_t tok_row0 = static_cast<int64_t>(ti) * dv_step_rows + static_cast<int64_t>(p) * C;
|
|
861
|
+
#if defined(FX_PRE_SM90)
|
|
862
|
+
// Portable: the slice pair's 2 SW floats of each token row, copied by every thread (as fx_dw_kernel's).
|
|
863
|
+
sdmcp::copy_rows16(tok_s, RW * 4, dv + tok_row0 * g.hd + static_cast<int64_t>(sp) * RW, static_cast<int64_t>(g.hd) * 4,
|
|
864
|
+
C, RW * 4, static_cast<int>(threadIdx.x), NW * 32);
|
|
865
|
+
sdmcp::commit();
|
|
866
|
+
#endif
|
|
867
|
+
|
|
868
|
+
const int64_t tix = chunk * (g.TU + 1) + cr * NW + warp;
|
|
869
|
+
const int s = __ldg(tb + tix), e = __ldg(tb + tix + 1);
|
|
870
|
+
const int g0 = (WR || QF != 0) ? __ldg(segb + tix) : 0; // WR / QUANT: the tile's first dense ordinal
|
|
871
|
+
const int64_t drow0 = (static_cast<int64_t>(ti) * sb_step + p) * g.n + g0; // WR: dense row of the tile's group 0
|
|
872
|
+
const uint4* const kr = krec + chunk * g.n;
|
|
873
|
+
const int h = lane >> 4; // the lane's slice: 2 sp + h
|
|
874
|
+
const int lc = 4 * (lane & 15); // first column of the lane's group 0 (group 1: lc + 64)
|
|
875
|
+
const bool on0 = ALLON || lc < SW;
|
|
876
|
+
const bool on1 = ALLON || lc + 64 < SW;
|
|
877
|
+
// Safe columns: an inactive group reads column 0 of the slice (in bounds) and never stores.
|
|
878
|
+
const int c0 = on0 ? lc : 0, c1 = on1 ? lc + 64 : 0;
|
|
879
|
+
const int64_t col = static_cast<int64_t>(2 * sp + h) * SW; // element offset of the lane's slice in a row
|
|
880
|
+
unsigned char* const bank0 = reinterpret_cast<unsigned char*>(bank + col + c0);
|
|
881
|
+
unsigned char* const bank1 = reinterpret_cast<unsigned char*>(bank + col + c1);
|
|
882
|
+
const int64_t snap0 = col + c0, snap1 = col + c1; // WR: element offsets in a compact row
|
|
883
|
+
|
|
884
|
+
uint4 r0 = ld_rec(kr, s + lane, e);
|
|
885
|
+
uint4 r1 = ld_rec(kr, s + 32 + lane, e);
|
|
886
|
+
uint4 r2 = ld_rec(kr, s + 64 + lane, e);
|
|
887
|
+
uint4 r3 = ld_rec(kr, s + 96 + lane, e);
|
|
888
|
+
// QUANT: the quantized rows before the PDL wait (as fx_dw_kernel's), overlapping the dual matmul.
|
|
889
|
+
if constexpr (kAllSl) { // the whole rows (2 slices per pair), once, before slice pair 0's walk
|
|
890
|
+
if (sp == 0) {
|
|
891
|
+
q_rows_cta<QF & 3, false>(bank, g, 2 * g.NSL, kr, s, e, lane, reinterpret_cast<uint32_t*>(rec_s),
|
|
892
|
+
reinterpret_cast<uint8_t*>(map_rows.opaque[0]) + drow0 * ((QF & 3) == 1 ? g.hd : g.hd / 2),
|
|
893
|
+
reinterpret_cast<float*>(map_rows.opaque[1]) + drow0);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
if constexpr (kQC) {
|
|
897
|
+
uint8_t* const qpay = reinterpret_cast<uint8_t*>(map_rows.opaque[0]) + drow0 * ((QF & 3) == 1 ? g.hd : g.hd / 2);
|
|
898
|
+
struct QV { uint2 v0[kQB], v1[kQB]; };
|
|
899
|
+
q_rows<QF, NW, true, QV>(smem, g, segb, chunk * (g.TU + 1) + cr * NW, g0, kr, s, e, lane,
|
|
900
|
+
reinterpret_cast<uint32_t*>(rec_s), sp == 0,
|
|
901
|
+
reinterpret_cast<float*>(map_rows.opaque[1]) + drow0,
|
|
902
|
+
[&](const uint32_t* sla, int n, QV& q) {
|
|
903
|
+
#pragma unroll
|
|
904
|
+
for (int j = 0; j < kQB; ++j) {
|
|
905
|
+
q.v0[j] = make_uint2(0u, 0u);
|
|
906
|
+
q.v1[j] = make_uint2(0u, 0u);
|
|
907
|
+
if (j < n) {
|
|
908
|
+
q.v0[j] = __ldcg(reinterpret_cast<const uint2*>(fx_row<BIG>(bank0, sla[j], g.row_bytes)));
|
|
909
|
+
q.v1[j] = __ldcg(reinterpret_cast<const uint2*>(fx_row<BIG>(bank1, sla[j], g.row_bytes)));
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
}, [&](const QV& q, float* a) {
|
|
913
|
+
#pragma unroll
|
|
914
|
+
for (int j = 0; j < kQB; ++j) a[j] = fmaxf(on0 ? q_amax_bf4(q.v0[j]) : 0.f, on1 ? q_amax_bf4(q.v1[j]) : 0.f);
|
|
915
|
+
}, [&](const QV& q, int n, int k0, const float2* qw) {
|
|
916
|
+
#pragma unroll
|
|
917
|
+
for (int j = 0; j < kQB; ++j) {
|
|
918
|
+
if (j < n) {
|
|
919
|
+
const float sc = qw[k0 + j].y, y = __frcp_rn(sc);
|
|
920
|
+
uint8_t* const qrow = qpay + static_cast<int64_t>(k0 + j) * ((QF & 3) == 1 ? g.hd : g.hd / 2);
|
|
921
|
+
if (on0) q_store4<QF, false>(qrow, snap0, cvt4(q.v0[j]), sc, y);
|
|
922
|
+
if (on1) q_store4<QF, false>(qrow, snap1, cvt4(q.v1[j]), sc, y);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
#if !defined(FX_PRE_SM90)
|
|
928
|
+
// PDL (as in fx_dw_kernel): everything above reads data written two or more launches back (tile bounds, segb,
|
|
929
|
+
// records; QUANT: bank rows); the token rows are the dual matmul's output: every thread waits for that grid first (no
|
|
930
|
+
// exit precedes this point; before it only QUANT's rows and scales are written), then thread 0 copies them; the walk
|
|
931
|
+
// reads bank rows after this wait (__ldcg). Dependents (the
|
|
932
|
+
// next step's gather, which waits for this grid in turn) may launch from here.
|
|
933
|
+
asm volatile("griddepcontrol.wait;" ::: "memory");
|
|
934
|
+
if (threadIdx.x == 0) {
|
|
935
|
+
mbar_arrive_tx(tok_bar, static_cast<uint32_t>(C * RW * 4));
|
|
936
|
+
tma2d_g2s(tok_s, &map_tok, sp * RW, static_cast<int>(tok_row0), tok_bar);
|
|
937
|
+
}
|
|
938
|
+
asm volatile("griddepcontrol.launch_dependents;" ::: "memory");
|
|
939
|
+
#endif
|
|
940
|
+
#if defined(FX_PRE_SM90)
|
|
941
|
+
sdmcp::publish_cta(); // every copier's token rows landed; the CTA barrier publishes them
|
|
942
|
+
const uint32_t ztok = 0u;
|
|
943
|
+
#else
|
|
944
|
+
const uint32_t ztok = mbar_wait_dep(tok_bar, 0u);
|
|
945
|
+
#endif
|
|
946
|
+
const float* const tok0 = tok_s + ztok + h * SW + c0; // the lane's token columns (group 0 / group 1)
|
|
947
|
+
const float* const tok1 = tok_s + ztok + h * SW + c1;
|
|
948
|
+
float4 a0 = make_float4(0.f, 0.f, 0.f, 0.f); // running group sums: group 0 / group 1 columns
|
|
949
|
+
float4 a1 = make_float4(0.f, 0.f, 0.f, 0.f);
|
|
950
|
+
int done = 0; // WR: groups emitted so far (dense ordinal of the next emit)
|
|
951
|
+
const int nb = (e - s + 31) >> 5;
|
|
952
|
+
for (int bi = 0; bi < nb; ++bi) {
|
|
953
|
+
if (bi > 0) {
|
|
954
|
+
r0 = r1;
|
|
955
|
+
r1 = r2;
|
|
956
|
+
r2 = r3;
|
|
957
|
+
r3 = ld_rec(kr, s + 32 * (bi + 3) + lane, e);
|
|
958
|
+
}
|
|
959
|
+
const uint32_t x = r0.x;
|
|
960
|
+
const uint32_t ml = __ballot_sync(kFull, (x & (kLast | kInvalid)) == kLast); // valid LASTs: emit
|
|
961
|
+
const uint32_t me = __ballot_sync(kFull, (x & kLast) != 0u); // every LAST: restart
|
|
962
|
+
const uint32_t tokoff = static_cast<uint32_t>(min(static_cast<int>((x >> 9) & 0xFFu), C - 1) * RW);
|
|
963
|
+
// {token row offset (floats), scale, row bytes (BIG: row index), cpe}
|
|
964
|
+
rec_s[lane] = make_uint4(tokoff, r0.z, BIG ? r0.y : r0.y * g.row_bytes, r0.w);
|
|
965
|
+
__syncwarp();
|
|
966
|
+
const int pos0 = s + 32 * bi;
|
|
967
|
+
const int kend = min(32, e - pos0);
|
|
968
|
+
#pragma unroll 1
|
|
969
|
+
for (int k8 = 0; k8 < kend; k8 += 8) {
|
|
970
|
+
const uint32_t e8 = (me >> k8) & 0xFFu;
|
|
971
|
+
if (e8 == 0u) {
|
|
972
|
+
// No LAST in the sub-batch: the running sums only.
|
|
973
|
+
#pragma unroll
|
|
974
|
+
for (int j = 0; j < 8; ++j) {
|
|
975
|
+
const uint2 rt = *reinterpret_cast<const uint2*>(rec_s + k8 + j); // {token offset, scale}
|
|
976
|
+
fma4(a0, rt.y, *reinterpret_cast<const float4*>(tok0 + rt.x));
|
|
977
|
+
fma4(a1, rt.y, *reinterpret_cast<const float4*>(tok1 + rt.x));
|
|
978
|
+
}
|
|
979
|
+
continue;
|
|
980
|
+
}
|
|
981
|
+
// Base rows (bank[slot]) of the sub-batch's valid LASTs, read first (distinct slots: no store of this
|
|
982
|
+
// sub-batch aliases a load).
|
|
983
|
+
const uint32_t l8 = (ml >> k8) & 0xFFu;
|
|
984
|
+
uint2 bv0[8], bv1[8];
|
|
985
|
+
#pragma unroll
|
|
986
|
+
for (int j = 0; j < 8; ++j) {
|
|
987
|
+
bv0[j] = make_uint2(0u, 0u);
|
|
988
|
+
bv1[j] = make_uint2(0u, 0u);
|
|
989
|
+
if (l8 & (1u << j)) {
|
|
990
|
+
const uint32_t rref = rec_s[k8 + j].z;
|
|
991
|
+
bv0[j] = __ldcg(reinterpret_cast<const uint2*>(fx_row<BIG>(bank0, rref, g.row_bytes)));
|
|
992
|
+
bv1[j] = __ldcg(reinterpret_cast<const uint2*>(fx_row<BIG>(bank1, rref, g.row_bytes)));
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
#pragma unroll
|
|
996
|
+
for (int j = 0; j < 8; ++j) {
|
|
997
|
+
const uint4 rc = rec_s[k8 + j]; // {token offset, scale, row ref, cpe}
|
|
998
|
+
fma4(a0, rc.y, *reinterpret_cast<const float4*>(tok0 + rc.x));
|
|
999
|
+
fma4(a1, rc.y, *reinterpret_cast<const float4*>(tok1 + rc.x));
|
|
1000
|
+
if (l8 & (1u << j)) {
|
|
1001
|
+
const float cpe = __uint_as_float(rc.w);
|
|
1002
|
+
const float4 b0 = cvt4(bv0[j]), b1 = cvt4(bv1[j]);
|
|
1003
|
+
float4 o0, o1;
|
|
1004
|
+
o0.x = fmaf(b0.x, cpe, a0.x);
|
|
1005
|
+
o0.y = fmaf(b0.y, cpe, a0.y);
|
|
1006
|
+
o0.z = fmaf(b0.z, cpe, a0.z);
|
|
1007
|
+
o0.w = fmaf(b0.w, cpe, a0.w);
|
|
1008
|
+
o1.x = fmaf(b1.x, cpe, a1.x);
|
|
1009
|
+
o1.y = fmaf(b1.y, cpe, a1.y);
|
|
1010
|
+
o1.z = fmaf(b1.z, cpe, a1.z);
|
|
1011
|
+
o1.w = fmaf(b1.w, cpe, a1.w);
|
|
1012
|
+
if (on0) *reinterpret_cast<uint2*>(fx_row<BIG>(bank0, rc.z, g.row_bytes)) = make_uint2(pack_bf2(o0.x, o0.y), pack_bf2(o0.z, o0.w));
|
|
1013
|
+
if (on1) *reinterpret_cast<uint2*>(fx_row<BIG>(bank1, rc.z, g.row_bytes)) = make_uint2(pack_bf2(o1.x, o1.y), pack_bf2(o1.z, o1.w));
|
|
1014
|
+
if constexpr (WR) {
|
|
1015
|
+
// the group's dense row: ordinal done + (valid LASTs of this sub-batch before position j)
|
|
1016
|
+
const int64_t row = drow0 + done + __popc(l8 & ((1u << j) - 1u));
|
|
1017
|
+
if (on0) *reinterpret_cast<uint2*>(snapc + (row * g.hd + snap0)) = bv0[j]; // raw rows
|
|
1018
|
+
if (on1) *reinterpret_cast<uint2*>(snapc + (row * g.hd + snap1)) = bv1[j];
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
if (e8 & (1u << j)) {
|
|
1022
|
+
a0 = make_float4(0.f, 0.f, 0.f, 0.f);
|
|
1023
|
+
a1 = make_float4(0.f, 0.f, 0.f, 0.f);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
if constexpr (WR) done += __popc(l8);
|
|
1027
|
+
}
|
|
1028
|
+
__syncwarp(); // this batch's rec_s reads precede the next batch's STS
|
|
1029
|
+
}
|
|
1030
|
+
if constexpr (kQC) cluster_wait(); // no peer reads this CTA's rows any more
|
|
1031
|
+
if constexpr (kAllSl) {
|
|
1032
|
+
if (sp + 1 < g.NSL) { // CTA-uniform: the next slice pair of the same tiles
|
|
1033
|
+
fence_proxy_async(); // this pair's generic reads of the token rows precede the next pair's async-proxy copy
|
|
1034
|
+
__syncthreads(); // every warp is done with this slice pair
|
|
1035
|
+
if (threadIdx.x == 0) mbar_inval(tok_bar); // the prologue initializes it again
|
|
1036
|
+
b = (p * g.NSL + sp + 1) * g.K + cr;
|
|
1037
|
+
goto fx2_slice;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// cudaFuncSetAttribute is per device: key on (kernel, device).
|
|
1043
|
+
template <typename K>
|
|
1044
|
+
void set_smem(K kernel, size_t bytes) {
|
|
1045
|
+
static std::mutex mu;
|
|
1046
|
+
static std::map<std::pair<const void*, int>, size_t> done;
|
|
1047
|
+
int dev = 0;
|
|
1048
|
+
C10_CUDA_CHECK(cudaGetDevice(&dev));
|
|
1049
|
+
std::lock_guard<std::mutex> lk(mu);
|
|
1050
|
+
const auto key = std::make_pair(reinterpret_cast<const void*>(kernel), dev);
|
|
1051
|
+
auto it = done.find(key);
|
|
1052
|
+
if (it != done.end() && it->second >= bytes) return;
|
|
1053
|
+
C10_CUDA_CHECK(cudaFuncSetAttribute(kernel, cudaFuncAttributeMaxDynamicSharedMemorySize, static_cast<int>(bytes)));
|
|
1054
|
+
done[key] = bytes;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
int slices_of(int hd) { return (hd + 127) / 128; }
|
|
1058
|
+
// Tails: a head whose equal slices would not be 4-column groups takes 128-wide slices, the last one short (UNEVEN,
|
|
1059
|
+
// variant bit 16: the bank-direct one-slice-per-warp walk only).
|
|
1060
|
+
// SCALAR (variant bit 32): hd % 4 != 0: element accesses, token rows copied by the CTA; 128-wide slices (the last one
|
|
1061
|
+
// short), or one slice padded to hd rounded up to 4 (the token rows' shared-memory row stride).
|
|
1062
|
+
bool scalar_of(int hd) { return hd % 4 != 0; }
|
|
1063
|
+
bool uneven_of(int hd) {
|
|
1064
|
+
const int n = slices_of(hd);
|
|
1065
|
+
return !scalar_of(hd) && n > 1 && (hd % n != 0 || (hd / n) % 4 != 0);
|
|
1066
|
+
}
|
|
1067
|
+
int slice_width(int hd) {
|
|
1068
|
+
if (scalar_of(hd)) return slices_of(hd) > 1 ? 128 : (hd + 3) / 4 * 4;
|
|
1069
|
+
return uneven_of(hd) ? 128 : hd / slices_of(hd);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// cuTensorMapEncodeTiled from the driver the process already loaded (no -lcuda at build time).
|
|
1073
|
+
using EncodeTiled = CUresult (*)(CUtensorMap*, CUtensorMapDataType, cuuint32_t, void*, const cuuint64_t*,
|
|
1074
|
+
const cuuint64_t*, const cuuint32_t*, const cuuint32_t*, CUtensorMapInterleave,
|
|
1075
|
+
CUtensorMapSwizzle, CUtensorMapL2promotion, CUtensorMapFloatOOBfill);
|
|
1076
|
+
EncodeTiled encode_tiled() {
|
|
1077
|
+
static EncodeTiled fn = nullptr;
|
|
1078
|
+
static std::once_flag once;
|
|
1079
|
+
std::call_once(once, [] {
|
|
1080
|
+
void* h = dlopen("libcuda.so.1", RTLD_NOW | RTLD_NOLOAD);
|
|
1081
|
+
if (h == nullptr) h = dlopen("libcuda.so.1", RTLD_NOW);
|
|
1082
|
+
if (h != nullptr) fn = reinterpret_cast<EncodeTiled>(dlsym(h, "cuTensorMapEncodeTiled"));
|
|
1083
|
+
});
|
|
1084
|
+
return fn;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// A 2D map over a row-major [rows, cols] tensor of element size es, box {box_cols, box_rows}.
|
|
1088
|
+
void encode_2d(CUtensorMap* m, void* base, CUtensorMapDataType dt, int es, uint64_t cols, uint64_t rows,
|
|
1089
|
+
uint32_t box_cols, uint32_t box_rows) {
|
|
1090
|
+
EncodeTiled fn = encode_tiled();
|
|
1091
|
+
TORCH_CHECK(fn != nullptr, "fx_maps: cuTensorMapEncodeTiled not found in libcuda.so.1");
|
|
1092
|
+
std::memset(m, 0, sizeof(CUtensorMap));
|
|
1093
|
+
const cuuint64_t dims[2] = {cols, rows};
|
|
1094
|
+
const cuuint64_t strides[1] = {cols * static_cast<uint64_t>(es)};
|
|
1095
|
+
const cuuint32_t box[2] = {box_cols, box_rows};
|
|
1096
|
+
const cuuint32_t estr[2] = {1u, 1u};
|
|
1097
|
+
const CUresult r = fn(m, dt, 2, base, dims, strides, box, estr, CU_TENSOR_MAP_INTERLEAVE_NONE,
|
|
1098
|
+
CU_TENSOR_MAP_SWIZZLE_NONE, CU_TENSOR_MAP_L2_PROMOTION_L2_128B,
|
|
1099
|
+
CU_TENSOR_MAP_FLOAT_OOB_FILL_NONE);
|
|
1100
|
+
TORCH_CHECK(r == CUDA_SUCCESS, "fx_maps: cuTensorMapEncodeTiled failed (", static_cast<int>(r), ")");
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
} // namespace
|
|
1104
|
+
|
|
1105
|
+
// CPU uint8 [2, 128]: the 2D maps for column-sliced heads (hd > 128): dense snapc rows box {SW, 8} (the bank dtype: bf16
|
|
1106
|
+
// or f32) and the token rows box {SW, C} (f32 delta_v). Empty for one-slice heads (1D bulk copies, no maps).
|
|
1107
|
+
// wide_rows (<= 2^31): from this many snapc rows the snapc slot is left zeroed (its 32-bit row coordinates end at 2^31).
|
|
1108
|
+
// Only the ring walks read that map and the launchers instantiate the bank-direct walk only (BANKB: no ring units), so
|
|
1109
|
+
// the outputs do not change; below wide_rows the map is encoded as before. The caller passes 2^31 (tests: 0).
|
|
1110
|
+
at::Tensor fx_maps(at::Tensor snapc, at::Tensor dv, int64_t C, int64_t wide_rows) {
|
|
1111
|
+
TORCH_CHECK(wide_rows >= 0 && wide_rows <= (int64_t(1) << 31), "fx_maps: wide_rows must be in [0, 2^31]");
|
|
1112
|
+
TORCH_CHECK(snapc.dim() >= 1 && snapc.size(-1) >= 1 && snapc.size(-1) <= kMaxHeadDim,
|
|
1113
|
+
"fx_maps: head_dim must be in [1, ", kMaxHeadDim, "]"); // before any int arithmetic
|
|
1114
|
+
const int hd = static_cast<int>(snapc.size(-1));
|
|
1115
|
+
const int NSL = slices_of(hd);
|
|
1116
|
+
if (NSL == 1) return at::empty({0}, at::TensorOptions().dtype(at::kByte));
|
|
1117
|
+
if (scalar_of(hd)) { // SCALAR: the walk copies its token rows itself (no TMA maps)
|
|
1118
|
+
TORCH_CHECK(C >= 1 && C <= 256, "fx_maps: unsupported chunk");
|
|
1119
|
+
return at::zeros({2, 128}, at::TensorOptions().dtype(at::kByte));
|
|
1120
|
+
}
|
|
1121
|
+
const int SW = slice_width(hd); // UNEVEN heads: box 128; TMA zero-fills the columns past hd
|
|
1122
|
+
const bool bf = snapc.scalar_type() == at::kBFloat16;
|
|
1123
|
+
const int es = bf ? 2 : 4;
|
|
1124
|
+
// rows16: the snapc map (ring units) needs 16-byte slice rows and rows; the token map only hd % 4 == 0, SW % 4 == 0
|
|
1125
|
+
// (tails: the bank-direct walks, which never read the snapc map, get a zeroed one)
|
|
1126
|
+
const bool rows16 = !uneven_of(hd) && (SW * es) % 16 == 0 && (hd * es) % 16 == 0;
|
|
1127
|
+
TORCH_CHECK((hd % NSL == 0 || uneven_of(hd)) && SW % 4 == 0 && hd % 4 == 0 && C >= 1 && C <= 256,
|
|
1128
|
+
"fx_maps: unsupported head_dim / chunk");
|
|
1129
|
+
TORCH_CHECK(snapc.is_cuda() && (bf || snapc.scalar_type() == at::kFloat) && snapc.is_contiguous() &&
|
|
1130
|
+
reinterpret_cast<uintptr_t>(snapc.data_ptr()) % 16 == 0, "fx_maps: snapc bf16 / f32 contiguous aligned");
|
|
1131
|
+
TORCH_CHECK(dv.is_cuda() && dv.scalar_type() == at::kFloat && dv.is_contiguous() && dv.size(-1) == hd &&
|
|
1132
|
+
reinterpret_cast<uintptr_t>(dv.data_ptr()) % 16 == 0, "fx_maps: delta_v f32 contiguous aligned");
|
|
1133
|
+
const int64_t srows = snapc.numel() / hd, drows = dv.numel() / hd;
|
|
1134
|
+
// the token map's rows are token rows (< 2^31: memory_ops' token-row bound); the snapc map is encoded below wide_rows
|
|
1135
|
+
TORCH_CHECK(drows < (int64_t(1) << 31), "fx_maps: row coordinates must fit int32");
|
|
1136
|
+
at::Tensor out = at::zeros({2, 128}, at::TensorOptions().dtype(at::kByte));
|
|
1137
|
+
const c10::cuda::CUDAGuard guard(snapc.device());
|
|
1138
|
+
alignas(64) CUtensorMap m;
|
|
1139
|
+
if (rows16 && srows < wide_rows) {
|
|
1140
|
+
encode_2d(&m, snapc.data_ptr(), bf ? CU_TENSOR_MAP_DATA_TYPE_BFLOAT16 : CU_TENSOR_MAP_DATA_TYPE_FLOAT32, es, hd,
|
|
1141
|
+
srows, SW, kUnitRows);
|
|
1142
|
+
std::memcpy(out.data_ptr<uint8_t>(), &m, 128);
|
|
1143
|
+
}
|
|
1144
|
+
encode_2d(&m, dv.data_ptr(), CU_TENSOR_MAP_DATA_TYPE_FLOAT32, 4, hd, drows, SW, static_cast<uint32_t>(C));
|
|
1145
|
+
std::memcpy(out.data_ptr<uint8_t>() + 128, &m, 128);
|
|
1146
|
+
return out;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
// CPU uint8 [2, 128] for the two-slice walk (fx_dw2_kernel): the token rows of a slice PAIR, box {2 SW, C}
|
|
1150
|
+
// (f32 delta_v), in both slots (that kernel reads no compact rows; the first slot only keeps a valid descriptor).
|
|
1151
|
+
// Needs an even number of column slices (hd = 2 * pairs * SW, SW <= 128).
|
|
1152
|
+
at::Tensor fx_maps2(at::Tensor snapc, at::Tensor dv, int64_t C) {
|
|
1153
|
+
TORCH_CHECK(dv.dim() >= 1 && dv.size(-1) >= 1 && dv.size(-1) <= kMaxHeadDim,
|
|
1154
|
+
"fx_maps2: head_dim must be in [1, ", kMaxHeadDim, "]");
|
|
1155
|
+
const int hd = static_cast<int>(dv.size(-1));
|
|
1156
|
+
const int NSL = slices_of(hd);
|
|
1157
|
+
TORCH_CHECK(NSL >= 2 && NSL % 2 == 0, "fx_maps2: the two-slice walk needs an even number of column slices");
|
|
1158
|
+
const int SW = hd / NSL;
|
|
1159
|
+
TORCH_CHECK(hd % NSL == 0 && (SW * 2) % 16 == 0 && C >= 1 && C <= 256, "fx_maps2: unsupported head_dim / chunk");
|
|
1160
|
+
TORCH_CHECK(snapc.size(-1) == hd, "fx_maps2: snapc rows of head_dim");
|
|
1161
|
+
TORCH_CHECK(dv.is_cuda() && dv.scalar_type() == at::kFloat && dv.is_contiguous() &&
|
|
1162
|
+
reinterpret_cast<uintptr_t>(dv.data_ptr()) % 16 == 0, "fx_maps2: delta_v f32 contiguous aligned");
|
|
1163
|
+
const int64_t drows = dv.numel() / hd;
|
|
1164
|
+
TORCH_CHECK(drows < (int64_t(1) << 31), "fx_maps2: row coordinates must fit int32");
|
|
1165
|
+
at::Tensor out = at::empty({2, 128}, at::TensorOptions().dtype(at::kByte));
|
|
1166
|
+
const c10::cuda::CUDAGuard guard(dv.device());
|
|
1167
|
+
alignas(64) CUtensorMap m;
|
|
1168
|
+
encode_2d(&m, dv.data_ptr(), CU_TENSOR_MAP_DATA_TYPE_FLOAT32, 4, hd, drows, static_cast<uint32_t>(2 * SW),
|
|
1169
|
+
static_cast<uint32_t>(C));
|
|
1170
|
+
std::memcpy(out.data_ptr<uint8_t>(), &m, 128);
|
|
1171
|
+
std::memcpy(out.data_ptr<uint8_t>() + 128, &m, 128);
|
|
1172
|
+
return out;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// Everything the DW launch checks and derives, computed once (fx_plan_make); a launch then needs only the step index
|
|
1176
|
+
// (fx_launch_plan). fx_dw_plan / fx_dw_step expose that split to Python: the checks and the argument binding run once
|
|
1177
|
+
// per factory call instead of once per step.
|
|
1178
|
+
struct FxPlan {
|
|
1179
|
+
uint64_t magic; // kPlanMagic: a plan tensor made by fx_dw_plan of this module
|
|
1180
|
+
int64_t ti_max; // every step index in [0, ti_max] passes the step checks (< 0: none does)
|
|
1181
|
+
int32_t device;
|
|
1182
|
+
int32_t blocks; // 0: nothing to launch (no CTAs, or an empty bank)
|
|
1183
|
+
int32_t threads;
|
|
1184
|
+
int32_t cluster; // CTAs per thread-block cluster (quantized column-sliced walks); 0: no cluster
|
|
1185
|
+
int32_t portable; // 1: the portable side (A100 / RTX rows): no tensor maps, no PDL, no cluster
|
|
1186
|
+
uint64_t smem;
|
|
1187
|
+
const void* fn; // the fx_dw_kernel instantiation (its smem attribute set for `device`)
|
|
1188
|
+
void* bank; // bf16 or f32 rows (the kernel instantiation's element type)
|
|
1189
|
+
const float* dv;
|
|
1190
|
+
int64_t dv_step_rows;
|
|
1191
|
+
const uint4* krec;
|
|
1192
|
+
const int32_t* tb;
|
|
1193
|
+
const int32_t* segb;
|
|
1194
|
+
void* snapc; // the bank's element type
|
|
1195
|
+
int64_t sb_step;
|
|
1196
|
+
FxGeo g;
|
|
1197
|
+
CUtensorMap mrows; // 64-byte aligned by its type
|
|
1198
|
+
CUtensorMap mtok;
|
|
1199
|
+
};
|
|
1200
|
+
constexpr uint64_t kPlanMagic = 0x464750314e414c50ull;
|
|
1201
|
+
|
|
1202
|
+
// The quantized instantiation of a plan (QUANT 1 fp8 / 2 int4; bank-direct, 64-bit row offsets, no raw rows; nw 8 / 16).
|
|
1203
|
+
// quant 1 / 2: the cluster form; 5 / 6: the all-slices form (column-sliced heads only: t2d or the two-slice walk).
|
|
1204
|
+
const void* fx_q_fn(int64_t quant, bool w2, bool bf, int64_t nw, bool t2d, bool allon) {
|
|
1205
|
+
TORCH_CHECK((quant == 1 || quant == 2 || ((quant == 5 || quant == 6) && (t2d || w2))) && (nw == 8 || nw == 16) &&
|
|
1206
|
+
(bf || !w2), "fx_dw: no quantized instantiation");
|
|
1207
|
+
#define FX_QE(E_, NW_, Q_) (t2d ? reinterpret_cast<const void*>(fx_dw_kernel<E_, NW_, 2, true, true, false, false, true, Q_>) \
|
|
1208
|
+
: reinterpret_cast<const void*>(fx_dw_kernel<E_, NW_, 2, false, true, false, false, true, Q_>))
|
|
1209
|
+
#define FX_Q2(NW_, Q_) (allon ? reinterpret_cast<const void*>(fx_dw2_kernel<NW_, false, true, true, Q_>) \
|
|
1210
|
+
: reinterpret_cast<const void*>(fx_dw2_kernel<NW_, false, true, false, Q_>))
|
|
1211
|
+
#define FX_QN(Q_) (w2 ? (nw == 8 ? FX_Q2(8, Q_) : FX_Q2(16, Q_)) \
|
|
1212
|
+
: bf ? (nw == 8 ? FX_QE(__nv_bfloat16, 8, Q_) : FX_QE(__nv_bfloat16, 16, Q_)) \
|
|
1213
|
+
: (nw == 8 ? FX_QE(float, 8, Q_) : FX_QE(float, 16, Q_)))
|
|
1214
|
+
// the all-slices form exists for column-sliced heads only (T2D = true; one-slice heads quantize in the walk)
|
|
1215
|
+
#define FX_QA(E_, NW_, Q_) reinterpret_cast<const void*>(fx_dw_kernel<E_, NW_, 2, true, true, false, false, true, Q_>)
|
|
1216
|
+
#define FX_QAN(Q_) (w2 ? (nw == 8 ? FX_Q2(8, Q_) : FX_Q2(16, Q_)) \
|
|
1217
|
+
: bf ? (nw == 8 ? FX_QA(__nv_bfloat16, 8, Q_) : FX_QA(__nv_bfloat16, 16, Q_)) \
|
|
1218
|
+
: (nw == 8 ? FX_QA(float, 8, Q_) : FX_QA(float, 16, Q_)))
|
|
1219
|
+
switch (quant) {
|
|
1220
|
+
case 1: return FX_QN(1);
|
|
1221
|
+
case 2: return FX_QN(2);
|
|
1222
|
+
case 5: return FX_QAN(5);
|
|
1223
|
+
default: return FX_QAN(6);
|
|
1224
|
+
}
|
|
1225
|
+
#undef FX_QAN
|
|
1226
|
+
#undef FX_QA
|
|
1227
|
+
#undef FX_QN
|
|
1228
|
+
#undef FX_Q2
|
|
1229
|
+
#undef FX_QE
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
// Clusters of `cluster` CTAs of fn (smem bytes, `threads` each; > 8: the non-portable size) that can be co-resident on
|
|
1233
|
+
// the current device (cudaOccupancyMaxActiveClusters): 0 = they never fit.
|
|
1234
|
+
int64_t cluster_capacity(const void* fn, int64_t cluster, size_t smem, int threads) {
|
|
1235
|
+
set_smem(fn, smem);
|
|
1236
|
+
if (cluster > 8) C10_CUDA_CHECK(cudaFuncSetAttribute(fn, cudaFuncAttributeNonPortableClusterSizeAllowed, 1));
|
|
1237
|
+
cudaLaunchAttribute at[1];
|
|
1238
|
+
at[0].id = cudaLaunchAttributeClusterDimension;
|
|
1239
|
+
at[0].val.clusterDim.x = static_cast<unsigned>(cluster);
|
|
1240
|
+
at[0].val.clusterDim.y = at[0].val.clusterDim.z = 1;
|
|
1241
|
+
cudaLaunchConfig_t cfg = {};
|
|
1242
|
+
cfg.gridDim = dim3(static_cast<unsigned>(cluster));
|
|
1243
|
+
cfg.blockDim = dim3(static_cast<unsigned>(threads));
|
|
1244
|
+
cfg.dynamicSmemBytes = smem;
|
|
1245
|
+
cfg.attrs = at;
|
|
1246
|
+
cfg.numAttrs = 1;
|
|
1247
|
+
int n = 0;
|
|
1248
|
+
C10_CUDA_CHECK(cudaOccupancyMaxActiveClusters(&n, fn, &cfg));
|
|
1249
|
+
return n;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
// A portable plan's validation (A100 / RTX rows, decided in Python from _device.backend): the device runs the portable
|
|
1253
|
+
// image of fn (its PTX target is not sm_90 / 100 / 103), so the kernel takes the cp.async side the plan is made for (no
|
|
1254
|
+
// tensor maps, no PDL wait). Cached per (function, device); never called for modern plans.
|
|
1255
|
+
bool fx_portable_image(const void* fn) {
|
|
1256
|
+
static std::mutex mu;
|
|
1257
|
+
static std::map<std::pair<const void*, int>, bool> done;
|
|
1258
|
+
int dev = 0;
|
|
1259
|
+
C10_CUDA_CHECK(cudaGetDevice(&dev));
|
|
1260
|
+
std::lock_guard<std::mutex> lk(mu);
|
|
1261
|
+
const auto key = std::make_pair(fn, dev);
|
|
1262
|
+
auto it = done.find(key);
|
|
1263
|
+
if (it != done.end()) return it->second;
|
|
1264
|
+
cudaFuncAttributes a;
|
|
1265
|
+
C10_CUDA_CHECK(cudaFuncGetAttributes(&a, fn));
|
|
1266
|
+
const bool portable = a.ptxVersion != 90 && a.ptxVersion != 100 && a.ptxVersion != 103;
|
|
1267
|
+
done[key] = portable;
|
|
1268
|
+
return portable;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
// All the launch checks except the step's, the geometry, the shared memory size, the kernel instantiation (and its
|
|
1272
|
+
// smem attribute). The caller holds a CUDAGuard on bank's device. portable (Python: _device.backend() != "modern"):
|
|
1273
|
+
// no tensor maps (maps empty), no programmatic launch, no cluster; smem_cap: the class's opt-in bytes per block
|
|
1274
|
+
// (_device.smem_cap; modern 232,448).
|
|
1275
|
+
// bankb: 1 = bank-direct, 3 = bank-direct + compact-snapshot write, 4 / 5 = 1 / 3 with two column slices per warp
|
|
1276
|
+
// (fx_dw2_kernel); the checks refuse other values. quant 1 (fp8) / 2 (int4), bankb 1 / 4 only: the compact rows go
|
|
1277
|
+
// quantized to payload ([>= N * n, hd] or [>= N * n, hd / 2] bytes) and scale (f32 [>= N * n]); snapc only anchors the maps.
|
|
1278
|
+
FxPlan fx_plan_make(at::Tensor bank, at::Tensor dv, int64_t dv_step_rows, at::Tensor krec, at::Tensor tb,
|
|
1279
|
+
at::Tensor segb, at::Tensor snapc, int64_t sb_step, int64_t P, int64_t C, int64_t K, int64_t nw,
|
|
1280
|
+
int64_t nu, at::Tensor maps, int64_t bankb, int64_t quant, at::Tensor payload, at::Tensor scale,
|
|
1281
|
+
int64_t portable, int64_t smem_cap) {
|
|
1282
|
+
TORCH_CHECK((portable == 0 || portable == 1) && smem_cap > 0 && smem_cap <= 227 * 1024,
|
|
1283
|
+
"fx_dw: portable must be 0 or 1 and smem_cap in (0, 227 KiB]");
|
|
1284
|
+
TORCH_CHECK(bankb == 1 || bankb == 3 || bankb == 4 || bankb == 5, "fx_dw: bankb must be 1 (bank-direct), "
|
|
1285
|
+
"3 (bank-direct + compact-snapshot write), 4 (1, two slices per warp) or 5 (3, two slices per warp)");
|
|
1286
|
+
// bankb 3 / 5: the DW writes the dense compact rows of every step's chunks (the every-step snapshot only).
|
|
1287
|
+
const bool wr = bankb == 3 || bankb == 5;
|
|
1288
|
+
const bool w2 = bankb == 4 || bankb == 5; // fx_dw2_kernel (two column slices per warp)
|
|
1289
|
+
TORCH_CHECK(!wr || (sb_step == P && P >= 1), "fx_dw: the compact-snapshot write needs sb_step == P");
|
|
1290
|
+
TORCH_CHECK(quant == 0 || ((quant == 1 || quant == 2 || quant == 5 || quant == 6) && (bankb == 1 || bankb == 4) &&
|
|
1291
|
+
sb_step == P && P >= 1),
|
|
1292
|
+
"fx_dw: quantized compact rows (quant 1 / 2) need bankb 1 or 4 and sb_step == P");
|
|
1293
|
+
// bf16 banks, or f32 banks (the one-slice-per-warp bank-direct walk, nw 8 / 16, 64-bit row offsets).
|
|
1294
|
+
const bool bf = bank.scalar_type() == at::kBFloat16;
|
|
1295
|
+
TORCH_CHECK(bank.dim() == 2 && bank.is_contiguous() && (bf || bank.scalar_type() == at::kFloat),
|
|
1296
|
+
"fx_dw: bank [slots, hd] bf16 / f32 contiguous");
|
|
1297
|
+
TORCH_CHECK(bf || (!w2 && (nw == 8 || nw == 16)), "fx_dw: a float32 bank takes the one-slice walk with 8 or 16 warps");
|
|
1298
|
+
const int es = bf ? 2 : 4;
|
|
1299
|
+
// Banks of > 4 GiB run the bank-direct walk with 64-bit row offsets (BIG instantiations); the record's slot
|
|
1300
|
+
// field is 32-bit (rows < 2^31 checked below), the 32-bit byte-offset kernels serve every bf16 bank <= 4 GiB
|
|
1301
|
+
// unchanged. float32 banks always take the BIG instantiations.
|
|
1302
|
+
const bool big = !bf || static_cast<uint64_t>(bank.numel()) * 2 > (uint64_t(1) << 32);
|
|
1303
|
+
TORCH_CHECK(!big || (bankb != 0 && bankb != 2),
|
|
1304
|
+
"fx_dw: a bank of > 4 GiB needs the bank-direct walk without prefetch (bankb 1, 3, 4 or 5)");
|
|
1305
|
+
// Every value narrowed to int below, and every product, is bounded here first (64-bit arithmetic).
|
|
1306
|
+
constexpr int64_t kI32 = std::numeric_limits<int32_t>::max();
|
|
1307
|
+
TORCH_CHECK(bank.size(1) >= 1 && bank.size(1) <= kMaxHeadDim, "fx_dw: head_dim must be in [1, ", kMaxHeadDim, "]");
|
|
1308
|
+
TORCH_CHECK(bank.size(0) <= kI32, "fx_dw: bank rows must fit the records' 32-bit slot field");
|
|
1309
|
+
TORCH_CHECK(P >= 0 && P <= kI32 && K >= 0 && K <= kI32 && nw >= 1 && nw <= 32, "fx_dw: P, K, nw out of range");
|
|
1310
|
+
// n (positions per chunk) <= 2^30: the kernel's position arithmetic (s + 32 * (bi + 3) + lane <= n + 127, e - s + 31)
|
|
1311
|
+
// stays in int; Phase 1 emits n <= 65536. The tile table's column count is bounded before any cast, so
|
|
1312
|
+
// TU = cols - 1 and the kernel's TU + 1 fit int.
|
|
1313
|
+
TORCH_CHECK(krec.dim() == 3 && krec.size(1) <= (int64_t(1) << 30) && tb.dim() == 2 && tb.size(1) >= 1 &&
|
|
1314
|
+
tb.size(1) <= kI32, "fx_dw: record / tile table sizes out of range");
|
|
1315
|
+
TORCH_CHECK(dv_step_rows >= 0 && dv_step_rows <= dv.numel() && (sb_step == 0 || sb_step == P),
|
|
1316
|
+
"fx_dw: dv_step_rows / sb_step out of range (sb_step is 0 or P)");
|
|
1317
|
+
const int hd = static_cast<int>(bank.size(1));
|
|
1318
|
+
const int NSL = slices_of(hd);
|
|
1319
|
+
const bool uneven = uneven_of(hd); // tails: 128-wide slices, the last one short
|
|
1320
|
+
const bool scalar = scalar_of(hd); // tails: hd % 4 != 0
|
|
1321
|
+
TORCH_CHECK(hd % NSL == 0 || uneven || scalar, "fx_dw: head_dim ", hd, " does not split into ", NSL, " slices");
|
|
1322
|
+
const int SW = slice_width(hd);
|
|
1323
|
+
// 16-byte slice rows and rows (ring units, snapc maps, the two-slice walk); the bank-direct one-slice-per-warp walk
|
|
1324
|
+
// (bankb 1 / 3) needs only 8-byte (bf16) / 16-byte (f32) lane vectors: hd % 4 == 0 (tails)
|
|
1325
|
+
// rows16: 16-byte slice rows and rows (every regular plan); anything else is a tail (classes a / b / c)
|
|
1326
|
+
const bool rows16 = !uneven && !scalar && (SW * es) % 16 == 0 && (hd * es) % 16 == 0;
|
|
1327
|
+
TORCH_CHECK(rows16 || ((bankb == 1 || bankb == 3) && quant == 0 && (nw == 8 || nw == 16)),
|
|
1328
|
+
"fx_dw: tail shapes (rows that are not 16-byte multiples, uneven slices, hd % 4 != 0): the unquantized "
|
|
1329
|
+
"bank-direct one-slice-per-warp walk, 8 or 16 warps");
|
|
1330
|
+
TORCH_CHECK(SW % 4 == 0 && SW <= 128 && (hd % 4 == 0 || scalar) && (rows16 || bankb == 1 || bankb == 3),
|
|
1331
|
+
"fx_dw: slice rows must be 16-byte multiples, or hd % 4 == 0 on the bank-direct walk (hd ", hd, ")");
|
|
1332
|
+
TORCH_CHECK(reinterpret_cast<uintptr_t>(bank.data_ptr()) % 16 == 0 &&
|
|
1333
|
+
reinterpret_cast<uintptr_t>(dv.data_ptr()) % 16 == 0 &&
|
|
1334
|
+
reinterpret_cast<uintptr_t>(snapc.data_ptr()) % 16 == 0, "fx_dw: 16-byte alignment");
|
|
1335
|
+
TORCH_CHECK(dv.scalar_type() == at::kFloat && dv.is_contiguous() && dv.device() == bank.device(),
|
|
1336
|
+
"fx_dw: dv f32 contiguous");
|
|
1337
|
+
TORCH_CHECK(C >= 1 && C <= 256, "fx_dw: C <= 256");
|
|
1338
|
+
TORCH_CHECK(krec.dim() == 3 && krec.size(2) == 4 && krec.is_contiguous() && krec.scalar_type() == at::kInt &&
|
|
1339
|
+
reinterpret_cast<uintptr_t>(krec.data_ptr()) % 16 == 0 && krec.device() == bank.device(),
|
|
1340
|
+
"fx_dw: krec [N, n, 4] int32, 16-byte aligned");
|
|
1341
|
+
const int n = static_cast<int>(krec.size(1));
|
|
1342
|
+
const int TU = static_cast<int>(tb.size(1) - 1);
|
|
1343
|
+
TORCH_CHECK(tb.dim() == 2 && tb.size(0) == krec.size(0) && tb.scalar_type() == at::kInt && tb.is_contiguous(),
|
|
1344
|
+
"fx_dw: tb [N, TU + 1] int32");
|
|
1345
|
+
TORCH_CHECK(static_cast<int64_t>(TU) == K * nw, "fx_dw: tile table holds ", TU, " tiles, the grid needs ", K * nw);
|
|
1346
|
+
TORCH_CHECK(segb.scalar_type() == at::kInt && segb.is_contiguous() && segb.sizes() == tb.sizes(),
|
|
1347
|
+
"fx_dw: segb int32 shaped like tb");
|
|
1348
|
+
TORCH_CHECK(tb.device() == bank.device() && segb.device() == bank.device(), "fx_dw: tb / segb on the bank's device");
|
|
1349
|
+
if (wr) { // WR: the compact rows are written: they must not alias the bank or delta_v
|
|
1350
|
+
const auto lo = [](const at::Tensor& t) { return reinterpret_cast<uintptr_t>(t.data_ptr()); };
|
|
1351
|
+
const auto overlap = [&](const at::Tensor& a, const at::Tensor& b) {
|
|
1352
|
+
return lo(a) < lo(b) + b.nbytes() && lo(b) < lo(a) + a.nbytes();
|
|
1353
|
+
};
|
|
1354
|
+
TORCH_CHECK(!overlap(snapc, bank) && !overlap(snapc, dv),
|
|
1355
|
+
"fx_dw: the compact snapshot must not overlap the bank or delta_v");
|
|
1356
|
+
}
|
|
1357
|
+
// bf16 compact rows of a float32 bank (snapshot_quant bf16): written by the WR walk (bankb 3), rounded to bf16
|
|
1358
|
+
const bool s16 = wr && quant == 0 && bank.scalar_type() == at::kFloat && snapc.scalar_type() == at::kBFloat16;
|
|
1359
|
+
TORCH_CHECK((snapc.scalar_type() == bank.scalar_type() || s16) && snapc.is_contiguous() && snapc.device() == bank.device() &&
|
|
1360
|
+
snapc.size(-1) == hd &&
|
|
1361
|
+
(quant != 0 || n == 0 || (sb_step == 0 ? P : krec.size(0)) <= snapc.numel() / hd / n), // no overflow
|
|
1362
|
+
"fx_dw: snapc [rows, hd] in the bank dtype (or bf16 rows of a float32 bank's WR walk) holding the dense "
|
|
1363
|
+
"rows of every chunk read");
|
|
1364
|
+
if (quant != 0) { // per-entry-sized quantized rows, never aliasing the bank / delta_v
|
|
1365
|
+
const auto lo = [](const at::Tensor& t) { return reinterpret_cast<uintptr_t>(t.data_ptr()); };
|
|
1366
|
+
const auto apart = [&](const at::Tensor& a, const at::Tensor& b) {
|
|
1367
|
+
return lo(a) >= lo(b) + b.nbytes() || lo(b) >= lo(a) + a.nbytes();
|
|
1368
|
+
};
|
|
1369
|
+
TORCH_CHECK(payload.is_contiguous() && payload.element_size() == 1 && payload.device() == bank.device() &&
|
|
1370
|
+
payload.dim() == 2 && payload.size(1) == ((quant & 3) == 1 ? hd : hd / 2) &&
|
|
1371
|
+
payload.size(0) >= krec.size(0) * n && lo(payload) % 16 == 0 && scale.is_contiguous() &&
|
|
1372
|
+
scale.scalar_type() == at::kFloat && scale.device() == bank.device() &&
|
|
1373
|
+
scale.numel() >= krec.size(0) * n, "fx_dw: payload [>= N * n, hd or hd / 2] bytes, 16-byte aligned, "
|
|
1374
|
+
"and scale f32 [>= N * n], contiguous on the bank's device");
|
|
1375
|
+
TORCH_CHECK(apart(payload, bank) && apart(payload, dv) && apart(scale, bank) && apart(scale, dv) &&
|
|
1376
|
+
apart(payload, scale), "fx_dw: payload / scale must not overlap the bank, delta_v or each other");
|
|
1377
|
+
}
|
|
1378
|
+
// The step checks (0 <= ti <= INT32_MAX, (ti + 1) * P <= N, ti * dv_step_rows + P * C <= delta_v rows) as one range:
|
|
1379
|
+
// every ti in [0, ti_max] passes them (64-bit, division forms: no overflow); fx_launch_plan checks ti against it.
|
|
1380
|
+
const int64_t dv_rows_total = dv.numel() / hd;
|
|
1381
|
+
TORCH_CHECK(P * C <= dv_rows_total, "fx_dw: delta_v rows");
|
|
1382
|
+
int64_t ti_max = kI32;
|
|
1383
|
+
if (P > 0) ti_max = std::min(ti_max, krec.size(0) / P - 1);
|
|
1384
|
+
if (dv_step_rows > 0) ti_max = std::min(ti_max, (dv_rows_total - P * C) / dv_step_rows);
|
|
1385
|
+
const bool t2d = NSL > 1;
|
|
1386
|
+
TORCH_CHECK(!w2 || (NSL >= 2 && NSL % 2 == 0), "fx_dw: the two-slice walk needs an even number of column slices");
|
|
1387
|
+
alignas(64) CUtensorMap mrows, mtok;
|
|
1388
|
+
std::memset(&mrows, 0, sizeof(mrows));
|
|
1389
|
+
std::memset(&mtok, 0, sizeof(mtok));
|
|
1390
|
+
if (portable) { // the portable side addresses the token rows through dv itself: no tensor map is read
|
|
1391
|
+
TORCH_CHECK(maps.numel() == 0, "fx_dw: a portable plan takes no tensor maps");
|
|
1392
|
+
TORCH_CHECK(quant < 5 ? !(quant != 0 && t2d) : true, "fx_dw: the portable side's quantized column-sliced rows take "
|
|
1393
|
+
"the all-slices form (quant 5 / 6; no clusters)");
|
|
1394
|
+
} else if (t2d) {
|
|
1395
|
+
TORCH_CHECK(maps.device().is_cpu() && maps.scalar_type() == at::kByte && maps.numel() == 256 &&
|
|
1396
|
+
maps.is_contiguous(), "fx_dw: column-sliced heads need fx_maps(snapc, delta_v, C)");
|
|
1397
|
+
std::memcpy(&mrows, maps.data_ptr<uint8_t>(), 128);
|
|
1398
|
+
std::memcpy(&mtok, maps.data_ptr<uint8_t>() + 128, 128);
|
|
1399
|
+
}
|
|
1400
|
+
FxGeo g;
|
|
1401
|
+
g.P = static_cast<int>(P);
|
|
1402
|
+
g.C = static_cast<int>(C);
|
|
1403
|
+
g.n = n;
|
|
1404
|
+
g.hd = hd;
|
|
1405
|
+
g.NSL = w2 ? NSL / 2 : NSL; // two-slice walk: slice PAIRS
|
|
1406
|
+
g.SW = SW;
|
|
1407
|
+
g.K = static_cast<int>(K);
|
|
1408
|
+
g.TU = TU;
|
|
1409
|
+
g.tok_bytes = (static_cast<int>(C) * SW * (w2 ? 2 : 1) * 4 + 127) / 128 * 128;
|
|
1410
|
+
g.row_bytes = static_cast<uint32_t>(hd) * static_cast<uint32_t>(es);
|
|
1411
|
+
const size_t ub = static_cast<size_t>(kUnitRows) * SW * es;
|
|
1412
|
+
const size_t nur = bankb ? 0 : static_cast<size_t>(nu); // BANKB: no rings (must match the kernel's NUR)
|
|
1413
|
+
const size_t pfb = bankb == 2 ? static_cast<size_t>(32) * SW * es : 0; // PF buffer per warp (kernel's PFB)
|
|
1414
|
+
const size_t smem0 = static_cast<size_t>(g.tok_bytes) + static_cast<size_t>(nw) * nur * ub +
|
|
1415
|
+
static_cast<size_t>(nw) * pfb +
|
|
1416
|
+
static_cast<size_t>(nw) * 32 * 16 + 8 * (1 + static_cast<size_t>(nw) * nur);
|
|
1417
|
+
// quantized column-sliced walks: one cluster per tile range (every slice), {amax, scale} per valid group (q_rows)
|
|
1418
|
+
// quant 5 / 6 (all slices): one CTA per tile set walks every slice (grid P * K), no cluster, no {amax, scale} rows
|
|
1419
|
+
const bool all_sl = quant >= 5;
|
|
1420
|
+
TORCH_CHECK(!all_sl || t2d, "fx_dw: the all-slices quantized form needs a column-sliced head");
|
|
1421
|
+
const int cluster = (quant != 0 && !all_sl && t2d) ? g.NSL : 0;
|
|
1422
|
+
TORCH_CHECK(cluster <= 16, "fx_dw: quantized rows: ", cluster, " CTAs per cluster (at most 16)");
|
|
1423
|
+
const size_t smem = smem0 + (cluster != 0 ? 8 + static_cast<size_t>(nw) * kTileRows * 8 : 0);
|
|
1424
|
+
TORCH_CHECK(smem <= static_cast<size_t>(smem_cap), "fx_dw: ", smem, " bytes of shared memory per CTA (cap ", smem_cap, ")");
|
|
1425
|
+
TORCH_CHECK(P == 0 || K == 0 || P * g.NSL <= kI32 / K, "fx_dw: grid of P * NSL * K blocks exceeds int32");
|
|
1426
|
+
const int blocks = static_cast<int>(all_sl ? P * K : P * g.NSL * K);
|
|
1427
|
+
FxPlan pl;
|
|
1428
|
+
std::memset(&pl, 0, sizeof(pl));
|
|
1429
|
+
pl.magic = kPlanMagic;
|
|
1430
|
+
pl.ti_max = ti_max;
|
|
1431
|
+
pl.device = static_cast<int32_t>(bank.get_device());
|
|
1432
|
+
pl.smem = smem;
|
|
1433
|
+
pl.bank = bank.data_ptr();
|
|
1434
|
+
pl.dv = dv.data_ptr<float>();
|
|
1435
|
+
pl.dv_step_rows = dv_step_rows;
|
|
1436
|
+
pl.krec = reinterpret_cast<const uint4*>(krec.data_ptr<int32_t>());
|
|
1437
|
+
pl.tb = tb.data_ptr<int32_t>();
|
|
1438
|
+
pl.segb = segb.data_ptr<int32_t>();
|
|
1439
|
+
pl.snapc = snapc.data_ptr();
|
|
1440
|
+
pl.sb_step = sb_step;
|
|
1441
|
+
pl.g = g;
|
|
1442
|
+
std::memcpy(&pl.mrows, &mrows, sizeof(mrows));
|
|
1443
|
+
std::memcpy(&pl.mtok, &mtok, sizeof(mtok));
|
|
1444
|
+
if (quant != 0) { // the quantized walks' output pointers, in the rows map they never read (see fx_dw_kernel)
|
|
1445
|
+
std::memset(&pl.mrows, 0, sizeof(pl.mrows));
|
|
1446
|
+
pl.mrows.opaque[0] = reinterpret_cast<uint64_t>(payload.data_ptr());
|
|
1447
|
+
pl.mrows.opaque[1] = reinterpret_cast<uint64_t>(scale.data_ptr());
|
|
1448
|
+
}
|
|
1449
|
+
pl.cluster = cluster;
|
|
1450
|
+
pl.portable = static_cast<int32_t>(portable);
|
|
1451
|
+
// Nothing to write (no CTAs, or an empty bank: no row any record could name): no launch, so the kernel never forms
|
|
1452
|
+
// a bank pointer outside an empty array; no instantiation is chosen either.
|
|
1453
|
+
pl.blocks = (blocks == 0 || bank.numel() == 0) ? 0 : blocks;
|
|
1454
|
+
if (pl.blocks == 0) return pl;
|
|
1455
|
+
if (quant != 0) {
|
|
1456
|
+
pl.fn = fx_q_fn(quant, w2, bf, nw, t2d, SW == 128);
|
|
1457
|
+
pl.threads = static_cast<int32_t>(nw) * 32;
|
|
1458
|
+
set_smem(pl.fn, smem);
|
|
1459
|
+
TORCH_CHECK(cluster <= 1 || cluster_capacity(pl.fn, cluster, smem, pl.threads) > 0, "fx_dw: clusters of ", cluster,
|
|
1460
|
+
" CTAs with ", smem, " bytes of shared memory each do not fit this device");
|
|
1461
|
+
TORCH_CHECK(!portable || fx_portable_image(pl.fn), "fx_dw: a portable (A100 / RTX) plan, but this device runs the "
|
|
1462
|
+
"modern image of the kernel (build the extensions for the row's architecture)");
|
|
1463
|
+
return pl;
|
|
1464
|
+
}
|
|
1465
|
+
#define FX_DW(NW_, NU_, T2D_, BK_, PF_, WR_, BIG_) FX_DWE(__nv_bfloat16, NW_, NU_, T2D_, BK_, PF_, WR_, BIG_)
|
|
1466
|
+
#define FX_DWE(E_, NW_, NU_, T2D_, BK_, PF_, WR_, BIG_) \
|
|
1467
|
+
{ \
|
|
1468
|
+
auto kf = fx_dw_kernel<E_, NW_, NU_, T2D_, BK_, PF_, WR_, BIG_>; \
|
|
1469
|
+
set_smem(kf, smem); \
|
|
1470
|
+
pl.fn = reinterpret_cast<const void*>(kf); \
|
|
1471
|
+
pl.threads = NW_ * 32; \
|
|
1472
|
+
}
|
|
1473
|
+
// Tails: UNEVEN (variant 16) column-sliced bank-direct walks.
|
|
1474
|
+
#define FX_DWV(E_, NW_, WR_, BIG_) \
|
|
1475
|
+
{ \
|
|
1476
|
+
auto kf = fx_dw_kernel<E_, NW_, 2, true, true, false, WR_, BIG_, 16>; \
|
|
1477
|
+
set_smem(kf, smem); \
|
|
1478
|
+
pl.fn = reinterpret_cast<const void*>(kf); \
|
|
1479
|
+
pl.threads = NW_ * 32; \
|
|
1480
|
+
}
|
|
1481
|
+
#define FX_DW_UB(NW_) { if (wr) { if (big) FX_DWV(__nv_bfloat16, NW_, true, true) else FX_DWV(__nv_bfloat16, NW_, true, false) } \
|
|
1482
|
+
else { if (big) FX_DWV(__nv_bfloat16, NW_, false, true) else FX_DWV(__nv_bfloat16, NW_, false, false) } }
|
|
1483
|
+
#define FX_DW_UF(NW_) { if (wr) FX_DWV(float, NW_, true, true) else FX_DWV(float, NW_, false, true) }
|
|
1484
|
+
// Tails: SCALAR (variant 32) bank-direct walks, one slice (T2D false) or column slices.
|
|
1485
|
+
#define FX_DWS(E_, NW_, T2D_, WR_, BIG_) \
|
|
1486
|
+
{ \
|
|
1487
|
+
auto kf = fx_dw_kernel<E_, NW_, 2, T2D_, true, false, WR_, BIG_, 32>; \
|
|
1488
|
+
set_smem(kf, smem); \
|
|
1489
|
+
pl.fn = reinterpret_cast<const void*>(kf); \
|
|
1490
|
+
pl.threads = NW_ * 32; \
|
|
1491
|
+
}
|
|
1492
|
+
#define FX_DW_SB2(NW_, T2D_) { if (wr) { if (big) FX_DWS(__nv_bfloat16, NW_, T2D_, true, true) else FX_DWS(__nv_bfloat16, NW_, T2D_, true, false) } \
|
|
1493
|
+
else { if (big) FX_DWS(__nv_bfloat16, NW_, T2D_, false, true) else FX_DWS(__nv_bfloat16, NW_, T2D_, false, false) } }
|
|
1494
|
+
#define FX_DW_SB(NW_) { if (t2d) FX_DW_SB2(NW_, true) else FX_DW_SB2(NW_, false) }
|
|
1495
|
+
#define FX_DW_SF2(NW_, T2D_) { if (wr) FX_DWS(float, NW_, T2D_, true, true) else FX_DWS(float, NW_, T2D_, false, true) }
|
|
1496
|
+
#define FX_DW_SF(NW_) { if (t2d) FX_DW_SF2(NW_, true) else FX_DW_SF2(NW_, false) }
|
|
1497
|
+
#define FX_W2(NW_, WR_, BIG_, ALLON_) \
|
|
1498
|
+
{ \
|
|
1499
|
+
auto kf = fx_dw2_kernel<NW_, WR_, BIG_, ALLON_>; \
|
|
1500
|
+
set_smem(kf, smem); \
|
|
1501
|
+
pl.fn = reinterpret_cast<const void*>(kf); \
|
|
1502
|
+
pl.threads = NW_ * 32; \
|
|
1503
|
+
}
|
|
1504
|
+
#define FX_W2_A(NW_, WR_, BIG_) { if (SW == 128) FX_W2(NW_, WR_, BIG_, true) else FX_W2(NW_, WR_, BIG_, false) }
|
|
1505
|
+
#define FX_W2_N(NW_) { if (wr) { if (big) FX_W2_A(NW_, true, true) else FX_W2_A(NW_, true, false) } \
|
|
1506
|
+
else { if (big) FX_W2_A(NW_, false, true) else FX_W2_A(NW_, false, false) } }
|
|
1507
|
+
#define FX_DW_B(NW_) { if (big) { if (t2d) FX_DW(NW_, 2, true, true, false, false, true) else FX_DW(NW_, 2, false, true, false, false, true) } \
|
|
1508
|
+
else { if (t2d) FX_DW(NW_, 2, true, true, false, false, false) else FX_DW(NW_, 2, false, true, false, false, false) } }
|
|
1509
|
+
#define FX_DW_W(NW_) { if (big) { if (t2d) FX_DW(NW_, 2, true, true, false, true, true) else FX_DW(NW_, 2, false, true, false, true, true) } \
|
|
1510
|
+
else { if (t2d) FX_DW(NW_, 2, true, true, false, true, false) else FX_DW(NW_, 2, false, true, false, true, false) } }
|
|
1511
|
+
// float32 banks, bf16 compact rows (snapshot_quant bf16): the WR walks (one slice / column slices, UNEVEN 16, SCALAR 32)
|
|
1512
|
+
// with bf16 stores of the dense rows, 64-bit row offsets.
|
|
1513
|
+
#define FX_S16(NW_, T2D_, V_) \
|
|
1514
|
+
{ \
|
|
1515
|
+
auto kf = fx_dw_kernel<float, NW_, 2, T2D_, true, false, true, true, V_, __nv_bfloat16>; \
|
|
1516
|
+
set_smem(kf, smem); \
|
|
1517
|
+
pl.fn = reinterpret_cast<const void*>(kf); \
|
|
1518
|
+
pl.threads = NW_ * 32; \
|
|
1519
|
+
}
|
|
1520
|
+
#define FX_S16N(NW_) { if (scalar) { if (t2d) FX_S16(NW_, true, 32) else FX_S16(NW_, false, 32) } \
|
|
1521
|
+
else if (uneven) FX_S16(NW_, true, 16) \
|
|
1522
|
+
else { if (t2d) FX_S16(NW_, true, 0) else FX_S16(NW_, false, 0) } }
|
|
1523
|
+
// float32 banks: the bank-direct walk, 64-bit row offsets, with (WR) or without the compact-snapshot write.
|
|
1524
|
+
#define FX_DW_F(NW_) { if (wr) { if (t2d) FX_DWE(float, NW_, 2, true, true, false, true, true) else FX_DWE(float, NW_, 2, false, true, false, true, true) } \
|
|
1525
|
+
else { if (t2d) FX_DWE(float, NW_, 2, true, true, false, false, true) else FX_DWE(float, NW_, 2, false, true, false, false, true) } }
|
|
1526
|
+
if (s16) { // fx_dw_kernel<float, NW, 2, T2D, true, false, true, true, 0 / 16 / 32, bf16> (nw 8 / 16)
|
|
1527
|
+
TORCH_CHECK(nw == 8 || nw == 16, "fx_dw: bf16 compact rows of a float32 bank: 8 or 16 warps");
|
|
1528
|
+
if (nw == 8) FX_S16N(8) else FX_S16N(16)
|
|
1529
|
+
}
|
|
1530
|
+
else if (scalar) { // fx_dw_kernel<E, NW, 2, T2D, true, false, WR, BIG, 32> (nw 8 / 16; float32: BIG)
|
|
1531
|
+
if (bf) { if (nw == 8) FX_DW_SB(8) else FX_DW_SB(16) }
|
|
1532
|
+
else { if (nw == 8) FX_DW_SF(8) else FX_DW_SF(16) }
|
|
1533
|
+
}
|
|
1534
|
+
else if (uneven) { // fx_dw_kernel<E, NW, 2, true, true, false, WR, BIG, 16> (nw 8 / 16; float32: BIG)
|
|
1535
|
+
if (bf) { if (nw == 8) FX_DW_UB(8) else FX_DW_UB(16) }
|
|
1536
|
+
else { if (nw == 8) FX_DW_UF(8) else FX_DW_UF(16) }
|
|
1537
|
+
}
|
|
1538
|
+
else if (!bf) { // fx_dw_kernel<float, NW, 2, T2D, true, false, WR, true> (nw 8 / 16)
|
|
1539
|
+
if (nw == 8) FX_DW_F(8)
|
|
1540
|
+
else FX_DW_F(16)
|
|
1541
|
+
}
|
|
1542
|
+
else if (w2) { // fx_dw2_kernel<NW, WR, BIG, ALLON> (nw 8 / 16)
|
|
1543
|
+
if (nw == 8) FX_W2_N(8)
|
|
1544
|
+
else if (nw == 16) FX_W2_N(16)
|
|
1545
|
+
else TORCH_CHECK(false, "fx_dw: unsupported two-slice nw = ", nw);
|
|
1546
|
+
}
|
|
1547
|
+
else if (bankb == 3) {
|
|
1548
|
+
if (nw == 8) FX_DW_W(8)
|
|
1549
|
+
else if (nw == 16) FX_DW_W(16)
|
|
1550
|
+
else if (nw == 4) FX_DW_W(4)
|
|
1551
|
+
else TORCH_CHECK(false, "fx_dw: unsupported bank-direct compact-write nw = ", nw);
|
|
1552
|
+
}
|
|
1553
|
+
else {
|
|
1554
|
+
if (nw == 8) FX_DW_B(8)
|
|
1555
|
+
else if (nw == 16) FX_DW_B(16)
|
|
1556
|
+
else if (nw == 4) FX_DW_B(4)
|
|
1557
|
+
else TORCH_CHECK(false, "fx_dw: unsupported bank-direct nw = ", nw);
|
|
1558
|
+
}
|
|
1559
|
+
#undef FX_S16N
|
|
1560
|
+
#undef FX_S16
|
|
1561
|
+
#undef FX_DW_SF
|
|
1562
|
+
#undef FX_DW_SF2
|
|
1563
|
+
#undef FX_DW_SB
|
|
1564
|
+
#undef FX_DW_SB2
|
|
1565
|
+
#undef FX_DWS
|
|
1566
|
+
#undef FX_DW_UF
|
|
1567
|
+
#undef FX_DW_UB
|
|
1568
|
+
#undef FX_DWV
|
|
1569
|
+
#undef FX_W2_N
|
|
1570
|
+
#undef FX_W2_A
|
|
1571
|
+
#undef FX_W2
|
|
1572
|
+
#undef FX_DW_F
|
|
1573
|
+
#undef FX_DW_W
|
|
1574
|
+
#undef FX_DW_B
|
|
1575
|
+
#undef FX_DW
|
|
1576
|
+
#undef FX_DWE
|
|
1577
|
+
TORCH_CHECK(!portable || fx_portable_image(pl.fn), "fx_dw: a portable (A100 / RTX) plan, but this device runs the "
|
|
1578
|
+
"modern image of the kernel (build the extensions for the row's architecture)");
|
|
1579
|
+
return pl;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
// One step of a plan on the current stream of the plan's device (the caller holds a CUDAGuard on it): the step check,
|
|
1583
|
+
// then the launch with the plan's arguments (the kernel's parameter order) and ti. pdl = 1: programmatic stream
|
|
1584
|
+
// serialization, i.e. the grid may launch before the previous kernel of the stream completes; the kernel waits for it
|
|
1585
|
+
// (griddepcontrol.wait) before reading its output. The caller asserts that the kernel's pre-wait reads were written
|
|
1586
|
+
// two or more launches back (the deterministic forward: the dual matmul precedes every step's DW).
|
|
1587
|
+
void fx_launch_plan(const FxPlan& pl, int64_t ti, int64_t pdl) {
|
|
1588
|
+
TORCH_CHECK(ti >= 0 && ti <= pl.ti_max, "fx_dw: step ", ti, " out of range [0, ", pl.ti_max, "]");
|
|
1589
|
+
TORCH_CHECK(pdl == 0 || pdl == 1, "fx_dw: pdl must be 0 or 1");
|
|
1590
|
+
TORCH_CHECK(pdl == 0 || pl.portable == 0, "fx_dw: no programmatic launch on the portable side (A100 / RTX)");
|
|
1591
|
+
if (pl.blocks == 0) return;
|
|
1592
|
+
alignas(64) FxPlan a = pl; // the argument copies the launch reads
|
|
1593
|
+
int tii = static_cast<int>(ti); // ti <= ti_max <= INT32_MAX
|
|
1594
|
+
void* args[] = {(void*)&a.bank, (void*)&a.dv, (void*)&a.dv_step_rows, (void*)&a.krec, (void*)&a.tb,
|
|
1595
|
+
(void*)&a.segb, (void*)&a.snapc, (void*)&a.sb_step, (void*)&tii, (void*)&a.g, (void*)&a.mrows,
|
|
1596
|
+
(void*)&a.mtok};
|
|
1597
|
+
auto stream = at::cuda::getCurrentCUDAStream();
|
|
1598
|
+
if (pdl == 1 || a.cluster > 1) {
|
|
1599
|
+
cudaLaunchAttribute attr[2];
|
|
1600
|
+
unsigned na = 0;
|
|
1601
|
+
if (pdl == 1) {
|
|
1602
|
+
attr[na].id = cudaLaunchAttributeProgrammaticStreamSerialization;
|
|
1603
|
+
attr[na].val.programmaticStreamSerializationAllowed = 1;
|
|
1604
|
+
++na;
|
|
1605
|
+
}
|
|
1606
|
+
if (a.cluster > 1) { // quantized column-sliced walks: the CTAs of every slice of the same tiles (slice-fastest)
|
|
1607
|
+
attr[na].id = cudaLaunchAttributeClusterDimension;
|
|
1608
|
+
attr[na].val.clusterDim.x = static_cast<unsigned>(a.cluster);
|
|
1609
|
+
attr[na].val.clusterDim.y = 1;
|
|
1610
|
+
attr[na].val.clusterDim.z = 1;
|
|
1611
|
+
++na;
|
|
1612
|
+
}
|
|
1613
|
+
cudaLaunchConfig_t cfg = {};
|
|
1614
|
+
cfg.gridDim = dim3(static_cast<unsigned>(a.blocks));
|
|
1615
|
+
cfg.blockDim = dim3(static_cast<unsigned>(a.threads));
|
|
1616
|
+
cfg.dynamicSmemBytes = static_cast<size_t>(a.smem);
|
|
1617
|
+
cfg.stream = stream.stream();
|
|
1618
|
+
cfg.attrs = attr;
|
|
1619
|
+
cfg.numAttrs = na;
|
|
1620
|
+
C10_CUDA_CHECK(cudaLaunchKernelExC(&cfg, a.fn, args));
|
|
1621
|
+
C10_CUDA_KERNEL_LAUNCH_CHECK();
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
C10_CUDA_CHECK(cudaLaunchKernel(a.fn, dim3(static_cast<unsigned>(a.blocks)), dim3(static_cast<unsigned>(a.threads)),
|
|
1625
|
+
args, static_cast<size_t>(a.smem), stream.stream()));
|
|
1626
|
+
C10_CUDA_KERNEL_LAUNCH_CHECK();
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
// CPU uint8 [sizeof(FxPlan)]: the DW's checks and launch arguments for every step of one factory call. The plan holds raw
|
|
1630
|
+
// pointers into bank / dv / krec / tb / segb / snapc: the caller keeps those tensors alive and unresized while it is used.
|
|
1631
|
+
at::Tensor fx_dw_plan(at::Tensor bank, at::Tensor dv, int64_t dv_step_rows, at::Tensor krec, at::Tensor tb,
|
|
1632
|
+
at::Tensor segb, at::Tensor snapc, int64_t sb_step, int64_t P, int64_t C, int64_t K, int64_t nw,
|
|
1633
|
+
int64_t nu, at::Tensor maps, int64_t bankb, int64_t quant, at::Tensor payload, at::Tensor scale,
|
|
1634
|
+
int64_t portable, int64_t smem_cap) {
|
|
1635
|
+
const at::cuda::CUDAGuard guard(bank.device());
|
|
1636
|
+
const FxPlan pl = fx_plan_make(bank, dv, dv_step_rows, krec, tb, segb, snapc, sb_step, P, C, K, nw, nu, maps, bankb,
|
|
1637
|
+
quant, payload, scale, portable, smem_cap);
|
|
1638
|
+
at::Tensor out = at::empty({static_cast<int64_t>(sizeof(FxPlan))}, at::TensorOptions().dtype(at::kByte));
|
|
1639
|
+
std::memcpy(out.data_ptr<uint8_t>(), &pl, sizeof(FxPlan));
|
|
1640
|
+
return out;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
// Admission of a quantized plan on the current device: clusters that can be co-resident (> 0: launchable).
|
|
1644
|
+
int64_t fx_q_capacity(int64_t quant, int64_t w2, int64_t bf, int64_t nw, int64_t t2d, int64_t allon, int64_t cluster,
|
|
1645
|
+
int64_t smem) {
|
|
1646
|
+
TORCH_CHECK(cluster >= 1 && cluster <= 16 && smem > 0 && smem <= 227 * 1024, "fx_q_capacity: cluster / smem");
|
|
1647
|
+
return cluster_capacity(fx_q_fn(quant, w2 != 0, bf != 0, nw, t2d != 0, allon != 0), cluster, static_cast<size_t>(smem),
|
|
1648
|
+
static_cast<int>(nw) * 32);
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
void fx_dw_step(at::Tensor plan, int64_t ti, int64_t pdl) {
|
|
1652
|
+
TORCH_CHECK(plan.device().is_cpu() && plan.scalar_type() == at::kByte && plan.is_contiguous() &&
|
|
1653
|
+
plan.numel() == static_cast<int64_t>(sizeof(FxPlan)), "fx_dw_step: not an fx_dw_plan tensor");
|
|
1654
|
+
alignas(64) FxPlan pl;
|
|
1655
|
+
std::memcpy(&pl, plan.data_ptr<uint8_t>(), sizeof(FxPlan));
|
|
1656
|
+
TORCH_CHECK(pl.magic == kPlanMagic, "fx_dw_step: not an fx_dw_plan tensor (magic)");
|
|
1657
|
+
const c10::cuda::CUDAGuard guard(static_cast<c10::DeviceIndex>(pl.device));
|
|
1658
|
+
fx_launch_plan(pl, ti, pdl);
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
// ---- The deterministic forward's step loop in ONE call (eager host cost) ------------------------------------------
|
|
1662
|
+
// For ti in [t0, t1): (1) the gather plan step (sdm_token_gather_dc's C++ entry point, address passed in), (2) the Triton
|
|
1663
|
+
// dual matmul exactly as Triton 3.7's CudaLauncher issues it for this kernel (cuLaunchKernelEx; grid (grid_x, 1, 1) x
|
|
1664
|
+
// num_ctas 1, block 32 * num_warps, the kernel's dynamic shared memory, no launch attributes since the kernel has no
|
|
1665
|
+
// PDL / cooperative / cluster launch; parameters = its non-constexpr arguments in signature order + the null global and
|
|
1666
|
+
// profile scratch pointers; only ti changes per step), (3) this module's DW plan step. Same kernels, same arguments,
|
|
1667
|
+
// same order, same stream as the Python step loop: same launches, same bits. The descriptor is built and checked in
|
|
1668
|
+
// Python (mover_fx_cuda.tri_desc) from the CompiledKernel Triton's launcher would use.
|
|
1669
|
+
using LaunchKernelEx = CUresult (*)(const CUlaunchConfig*, CUfunction, void**, void**);
|
|
1670
|
+
LaunchKernelEx launch_kernel_ex() {
|
|
1671
|
+
static LaunchKernelEx fn = nullptr;
|
|
1672
|
+
static std::once_flag once;
|
|
1673
|
+
std::call_once(once, [] {
|
|
1674
|
+
void* h = dlopen("libcuda.so.1", RTLD_NOW | RTLD_NOLOAD);
|
|
1675
|
+
if (h == nullptr) h = dlopen("libcuda.so.1", RTLD_NOW);
|
|
1676
|
+
if (h != nullptr) fn = reinterpret_cast<LaunchKernelEx>(dlsym(h, "cuLaunchKernelEx"));
|
|
1677
|
+
});
|
|
1678
|
+
return fn;
|
|
1679
|
+
}
|
|
1680
|
+
constexpr int kTriMaxParams = 24;
|
|
1681
|
+
constexpr uint64_t kTriMagic = 0x3154584644585446ull;
|
|
1682
|
+
struct FxTriDesc { // Python packs it with struct "<QQiIIIii24i24Q" (checked against sizeof)
|
|
1683
|
+
uint64_t magic;
|
|
1684
|
+
uint64_t function; // CUfunction (CompiledKernel.function)
|
|
1685
|
+
int32_t device;
|
|
1686
|
+
uint32_t grid_x;
|
|
1687
|
+
uint32_t block_x; // 32 * num_warps
|
|
1688
|
+
uint32_t shared; // metadata.shared
|
|
1689
|
+
int32_t nparams; // kernel parameters incl. the two trailing scratch pointers
|
|
1690
|
+
int32_t ti_slot; // the 32-bit parameter that carries ti
|
|
1691
|
+
int32_t sizes[kTriMaxParams]; // 4 or 8 bytes
|
|
1692
|
+
uint64_t values[kTriMaxParams]; // little-endian values (pointers, ints; the scratch pointers 0)
|
|
1693
|
+
};
|
|
1694
|
+
using GatherLaunch = int64_t (*)(const void*, int64_t, int64_t, int64_t, int64_t, void*);
|
|
1695
|
+
|
|
1696
|
+
int64_t fx_tri_desc_size() { return static_cast<int64_t>(sizeof(FxTriDesc)); }
|
|
1697
|
+
|
|
1698
|
+
void fx_fwd_steps(at::Tensor gplan, int64_t gather_addr, at::Tensor tdesc, at::Tensor dwplan, int64_t t0, int64_t t1,
|
|
1699
|
+
int64_t SC, int64_t C, int64_t tokens, int64_t pdl_gather, int64_t pdl_dw) {
|
|
1700
|
+
TORCH_CHECK(gplan.device().is_cpu() && gplan.scalar_type() == at::kByte && gplan.is_contiguous() && gplan.numel() > 0,
|
|
1701
|
+
"fx_fwd_steps: gather plan");
|
|
1702
|
+
TORCH_CHECK(gather_addr != 0, "fx_fwd_steps: gather entry point");
|
|
1703
|
+
TORCH_CHECK(tdesc.device().is_cpu() && tdesc.scalar_type() == at::kByte && tdesc.is_contiguous() &&
|
|
1704
|
+
tdesc.numel() == static_cast<int64_t>(sizeof(FxTriDesc)), "fx_fwd_steps: not a dual descriptor");
|
|
1705
|
+
TORCH_CHECK(dwplan.device().is_cpu() && dwplan.scalar_type() == at::kByte && dwplan.is_contiguous() &&
|
|
1706
|
+
dwplan.numel() == static_cast<int64_t>(sizeof(FxPlan)), "fx_fwd_steps: not an fx_dw_plan tensor");
|
|
1707
|
+
alignas(64) FxPlan dw;
|
|
1708
|
+
std::memcpy(&dw, dwplan.data_ptr<uint8_t>(), sizeof(FxPlan));
|
|
1709
|
+
TORCH_CHECK(dw.magic == kPlanMagic, "fx_fwd_steps: not an fx_dw_plan tensor (magic)");
|
|
1710
|
+
FxTriDesc td;
|
|
1711
|
+
std::memcpy(&td, tdesc.data_ptr<uint8_t>(), sizeof(FxTriDesc));
|
|
1712
|
+
TORCH_CHECK(td.magic == kTriMagic && td.function != 0 && td.nparams >= 2 && td.nparams <= kTriMaxParams &&
|
|
1713
|
+
td.ti_slot >= 0 && td.ti_slot < td.nparams - 2 && td.sizes[td.ti_slot] == 4 && td.grid_x >= 1 &&
|
|
1714
|
+
td.block_x >= 32 && td.block_x <= 1024 && td.block_x % 32 == 0, "fx_fwd_steps: bad dual descriptor");
|
|
1715
|
+
for (int i = 0; i < td.nparams; ++i) TORCH_CHECK(td.sizes[i] == 4 || td.sizes[i] == 8, "fx_fwd_steps: param size");
|
|
1716
|
+
TORCH_CHECK(td.device == dw.device, "fx_fwd_steps: the dual matmul and the DW plan are on different devices");
|
|
1717
|
+
TORCH_CHECK(t0 >= 0 && t0 <= t1 && t1 - 1 <= dw.ti_max && t1 - 1 <= std::numeric_limits<int32_t>::max() &&
|
|
1718
|
+
C >= 1 && SC >= 0 && tokens >= 0 && (pdl_gather == 0 || pdl_gather == 1) && (pdl_dw == 0 || pdl_dw == 1),
|
|
1719
|
+
"fx_fwd_steps: step range / arguments");
|
|
1720
|
+
TORCH_CHECK(t1 <= t0 || tokens > (t1 - 1) * C, "fx_fwd_steps: every step must hold valid tokens");
|
|
1721
|
+
LaunchKernelEx lk = launch_kernel_ex();
|
|
1722
|
+
TORCH_CHECK(lk != nullptr, "fx_fwd_steps: cuLaunchKernelEx not found in libcuda.so.1");
|
|
1723
|
+
const c10::cuda::CUDAGuard guard(static_cast<c10::DeviceIndex>(dw.device));
|
|
1724
|
+
const cudaStream_t stream = at::cuda::getCurrentCUDAStream().stream();
|
|
1725
|
+
const GatherLaunch gather = reinterpret_cast<GatherLaunch>(gather_addr);
|
|
1726
|
+
const uint8_t* gp = gplan.data_ptr<uint8_t>();
|
|
1727
|
+
uint64_t vals[kTriMaxParams];
|
|
1728
|
+
void* params[kTriMaxParams];
|
|
1729
|
+
std::memcpy(vals, td.values, sizeof(vals));
|
|
1730
|
+
for (int i = 0; i < td.nparams; ++i) params[i] = &vals[i];
|
|
1731
|
+
CUlaunchConfig cfg;
|
|
1732
|
+
std::memset(&cfg, 0, sizeof(cfg));
|
|
1733
|
+
cfg.gridDimX = td.grid_x;
|
|
1734
|
+
cfg.gridDimY = 1;
|
|
1735
|
+
cfg.gridDimZ = 1;
|
|
1736
|
+
cfg.blockDimX = td.block_x;
|
|
1737
|
+
cfg.blockDimY = 1;
|
|
1738
|
+
cfg.blockDimZ = 1;
|
|
1739
|
+
cfg.sharedMemBytes = td.shared;
|
|
1740
|
+
cfg.hStream = reinterpret_cast<CUstream>(stream);
|
|
1741
|
+
cfg.attrs = nullptr;
|
|
1742
|
+
cfg.numAttrs = 0;
|
|
1743
|
+
for (int64_t ti = t0; ti < t1; ++ti) {
|
|
1744
|
+
const int64_t valid = std::min(C, tokens - ti * C);
|
|
1745
|
+
const int64_t rc = gather(gp, gplan.numel(), ti * SC, valid, (pdl_gather && ti > 0) ? 1 : 0,
|
|
1746
|
+
static_cast<void*>(stream));
|
|
1747
|
+
TORCH_CHECK(rc == 0, "fx_fwd_steps: gather step ", ti, " failed (", rc, ")");
|
|
1748
|
+
vals[td.ti_slot] = static_cast<uint64_t>(static_cast<uint32_t>(static_cast<int32_t>(ti)));
|
|
1749
|
+
const CUresult r = lk(&cfg, reinterpret_cast<CUfunction>(td.function), params, nullptr);
|
|
1750
|
+
TORCH_CHECK(r == CUDA_SUCCESS, "fx_fwd_steps: dual matmul step ", ti, " failed (CUresult ", static_cast<int>(r), ")");
|
|
1751
|
+
fx_launch_plan(dw, ti, pdl_dw);
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
""".replace("@ROW_HELPERS@\n", ROW_HELPERS.lstrip("\n")).replace("@QUANT_HELPERS@\n", QUANT_HELPERS.lstrip("\n"))
|
|
1755
|
+
|
|
1756
|
+
_SMEM_MAX = 227 * 1024
|
|
1757
|
+
_MAX_HEAD_DIM = 1 << 20 # = kMaxHeadDim (every row / element offset in the kernels is 64-bit)
|
|
1758
|
+
# fp8 / int4 rows: heads above the old 16384 bound have no quantized writer (QT class T0: refused up front) until the
|
|
1759
|
+
# quantized-tails bundle admits them with its own tests
|
|
1760
|
+
_QUANT_MAX_HEAD_DIM = 16384
|
|
1761
|
+
# Compact snapshot rows from which fx_maps leaves its snapc map zeroed (never read: the bank-direct walk only; its 32-bit
|
|
1762
|
+
# row coordinates end at 2**31): 2**31. Tests set 0 (the zeroed map at small sizes: the same bits).
|
|
1763
|
+
_WIDE_SNAP_ROWS = 2 ** 31
|
|
1764
|
+
|
|
1765
|
+
|
|
1766
|
+
def pdl_ok(device):
|
|
1767
|
+
"""PDL may be requested for launches on `device` (a torch.device) iff it is on the modern backend
|
|
1768
|
+
(_device.pdl_ok: sm_90 / 100 / 103): the portable device passes compile the griddepcontrol hooks away, so a
|
|
1769
|
+
programmatic launch is never requested elsewhere (RTX 50 included). Device only: never the bits."""
|
|
1770
|
+
return _device_pdl_ok(device)
|
|
1771
|
+
|
|
1772
|
+
|
|
1773
|
+
def fwd_pdl_ok(device):
|
|
1774
|
+
"""pdl_ok for the forward kernels (the DW here, token_gather_dc)."""
|
|
1775
|
+
return pdl_ok(device)
|
|
1776
|
+
|
|
1777
|
+
|
|
1778
|
+
def gwr_pdl_ok(device):
|
|
1779
|
+
"""pdl_ok for pa_dc_cuda's dc_gwr (its PDL build reads G through the coherent path after its wait)."""
|
|
1780
|
+
return pdl_ok(device)
|
|
1781
|
+
|
|
1782
|
+
|
|
1783
|
+
@functools.cache
|
|
1784
|
+
def _module():
|
|
1785
|
+
return load_cuda_extension(
|
|
1786
|
+
"sdm_mover_fx",
|
|
1787
|
+
cpp_sources=_CPP,
|
|
1788
|
+
cuda_sources=_CUDA,
|
|
1789
|
+
functions=("fx_maps", "fx_maps2", "fx_dw_plan", "fx_dw_step", "fx_q_capacity", "fx_tri_desc_size",
|
|
1790
|
+
"fx_fwd_steps"),
|
|
1791
|
+
headers=headers(),
|
|
1792
|
+
extra_cuda_cflags=("-O3", "-lineinfo"),
|
|
1793
|
+
extra_ldflags=("-ldl",),
|
|
1794
|
+
)
|
|
1795
|
+
|
|
1796
|
+
|
|
1797
|
+
def slice_width(head_dim) -> int:
|
|
1798
|
+
"""Columns per slice (= the C++ slice_width): hd / nsl when that is a multiple of 4, else 128 (tails)."""
|
|
1799
|
+
nsl = -(-head_dim // 128)
|
|
1800
|
+
if head_dim % 4: # SCALAR (hd % 4 != 0): 128-wide slices, or one slice padded to 4 columns
|
|
1801
|
+
return 128 if nsl > 1 else -(-head_dim // 4) * 4
|
|
1802
|
+
if nsl > 1 and (head_dim % nsl or (head_dim // nsl) % 4):
|
|
1803
|
+
return 128
|
|
1804
|
+
return head_dim // nsl
|
|
1805
|
+
|
|
1806
|
+
|
|
1807
|
+
def smem_bytes(chunk_size, head_dim, nw, nu, bankb=False, pf=False, w2=False, quant=0):
|
|
1808
|
+
nsl = -(-head_dim // 128)
|
|
1809
|
+
sw = slice_width(head_dim)
|
|
1810
|
+
tok = (chunk_size * sw * (2 if w2 else 1) * 4 + 127) // 128 * 128 # two-slice walk: token rows of a slice pair
|
|
1811
|
+
nur = 0 if bankb else nu # bank-direct: no rings
|
|
1812
|
+
pfb = 32 * sw * 2 if (bankb and pf) else 0 # prefetch buffer per warp (32 rows)
|
|
1813
|
+
qrows = 8 + nw * _TILE_ROWS * 8 if (quant in (1, 2) and nsl > 1) else 0 # the cluster form: {amax, scale} per group
|
|
1814
|
+
return tok + nw * nur * 8 * sw * 2 + nw * pfb + nw * 32 * 16 + 8 * (1 + nw * nur) + qrows
|
|
1815
|
+
|
|
1816
|
+
|
|
1817
|
+
_TILE_ROWS = 128 # valid groups per warp tile of every fx tile set (kTileRows)
|
|
1818
|
+
|
|
1819
|
+
|
|
1820
|
+
# Host cost: tensor maps are reused when both tensors' address, shape, strides and dtype, the chunk, the device and
|
|
1821
|
+
# the encoder match (a map encodes only those). The cache never iterates (cleared when full). Device lookups are not
|
|
1822
|
+
# cached.
|
|
1823
|
+
_MAPS: dict = {}
|
|
1824
|
+
|
|
1825
|
+
|
|
1826
|
+
def _smem_limit(device):
|
|
1827
|
+
"""The device class's opt-in shared memory per block (never the bits)."""
|
|
1828
|
+
return _device_smem_cap(device)
|
|
1829
|
+
|
|
1830
|
+
|
|
1831
|
+
def geometry(chunk_size, head_dim, nw, limit=_SMEM_MAX, bankb=False, quant=0):
|
|
1832
|
+
"""(nw, 2) or None: the bank-direct walk (bankb True) has no ring units, so nu is a placeholder (2); None when nw is
|
|
1833
|
+
not 4 / 8 / 16, when its shared memory does not fit `limit`, or without bankb (only the bank-direct walk is
|
|
1834
|
+
launched). Shapes and the device's smem limit only."""
|
|
1835
|
+
if not bankb:
|
|
1836
|
+
return None # only the bank-direct walk is launched
|
|
1837
|
+
if quant and nw not in (8, 16):
|
|
1838
|
+
return None
|
|
1839
|
+
return (nw, 2) if nw in (4, 8, 16) and smem_bytes(chunk_size, head_dim, nw, 2, True, quant=quant) <= limit else None
|
|
1840
|
+
|
|
1841
|
+
|
|
1842
|
+
def _portable(device) -> bool:
|
|
1843
|
+
"""`device` runs the portable side (a100 / rtx); False on the modern backend (raises outside the matrix)."""
|
|
1844
|
+
return _device_backend(device) != "modern"
|
|
1845
|
+
|
|
1846
|
+
|
|
1847
|
+
def quant_form(device, head_dim, quant):
|
|
1848
|
+
"""The kernels' QUANT for fp8 (1) / int4 (2) rows: quant (cluster form) or quant | 4 (all slices; always on a100 / rtx
|
|
1849
|
+
for column-sliced heads)."""
|
|
1850
|
+
if not quant:
|
|
1851
|
+
return 0
|
|
1852
|
+
if -(-head_dim // 128) > 1 and _portable(device):
|
|
1853
|
+
return quant | 4
|
|
1854
|
+
return quant
|
|
1855
|
+
def rows16(bank) -> bool:
|
|
1856
|
+
"""The head splits into equal 16-byte slice rows and 16-byte rows (tails do not)."""
|
|
1857
|
+
hd = bank.shape[-1]
|
|
1858
|
+
nsl = -(-hd // 128)
|
|
1859
|
+
es = bank.element_size()
|
|
1860
|
+
return hd % nsl == 0 and (hd // nsl * es) % 16 == 0 and (hd * es) % 16 == 0
|
|
1861
|
+
|
|
1862
|
+
|
|
1863
|
+
def tail_supported(bank, chunk_size, quant="none") -> bool:
|
|
1864
|
+
"""movers.tail_admits hook (tails): this DW runs the call's tail class with the bank-direct one-slice-per-warp walk,
|
|
1865
|
+
8 or 16 warps, on every backend. Unquantized snapshots; bf16 snapshots (float32 banks) are the same walk with bf16 stores."""
|
|
1866
|
+
if quant not in ("none", "bf16") or not bank.is_cuda or rows16(bank):
|
|
1867
|
+
return False
|
|
1868
|
+
if quant == "bf16" and bank.dtype != torch.float32:
|
|
1869
|
+
return False
|
|
1870
|
+
return any(supported(bank, chunk_size, nw, True) for nw in (8, 16))
|
|
1871
|
+
|
|
1872
|
+
|
|
1873
|
+
def s16_supported(bank, chunk_size) -> bool:
|
|
1874
|
+
"""snapshot_quant bf16 on a float32 bank: this DW's bank-direct WR walk (8 or 16 warps) writes the dense compact rows as
|
|
1875
|
+
bf16 (RN) at their dense ordinals."""
|
|
1876
|
+
return (bank.is_cuda and bank.dtype == torch.float32 and bank.dim() == 2
|
|
1877
|
+
and any(supported(bank, chunk_size, nw, True) for nw in (8, 16)))
|
|
1878
|
+
|
|
1879
|
+
|
|
1880
|
+
def supported(bank, chunk_size, nw, bankb=False):
|
|
1881
|
+
"""Shapes this DW runs (bf16 bank, or f32 bank on the bank-direct walk with 8 / 16 warps; every backend of
|
|
1882
|
+
_device.backend: modern, or the portable side on a100 / rtx; 16-byte slice rows, bank < 4 GiB unless bank-direct,
|
|
1883
|
+
smem fits the device's class); else mover_da2_cuda's kernel (modern) or phase2_kernels' (elsewhere)."""
|
|
1884
|
+
if not bank.is_cuda or bank.dtype not in (torch.bfloat16, torch.float32) or bank.dim() != 2:
|
|
1885
|
+
return False
|
|
1886
|
+
if bank.dtype == torch.float32 and not (bankb and nw in (8, 16)):
|
|
1887
|
+
return False
|
|
1888
|
+
try:
|
|
1889
|
+
_device_backend(bank.device)
|
|
1890
|
+
except RuntimeError:
|
|
1891
|
+
return False
|
|
1892
|
+
hd = bank.shape[-1]
|
|
1893
|
+
sw = slice_width(hd)
|
|
1894
|
+
es = bank.element_size()
|
|
1895
|
+
if sw % 4 or chunk_size > 256 or hd > _MAX_HEAD_DIM:
|
|
1896
|
+
return False
|
|
1897
|
+
if not rows16(bank) and not (bankb and nw in (8, 16)): # tails (incl. hd % 4 != 0): the bank-direct walk, 8 / 16 warps
|
|
1898
|
+
return False
|
|
1899
|
+
# Banks of > 4 GiB only through the bank-direct walk (64-bit row offsets; rows < 2^31: 32-bit record slots)
|
|
1900
|
+
if (bank.numel() * es > 2 ** 32 and not bankb) or bank.shape[0] >= 2 ** 31:
|
|
1901
|
+
return False
|
|
1902
|
+
return geometry(chunk_size, hd, nw, _smem_limit(bank.device), bankb) is not None
|
|
1903
|
+
|
|
1904
|
+
|
|
1905
|
+
def _warp_target(device=None) -> int:
|
|
1906
|
+
"""Warps a DW step aims for: the largest power of two within one wave of 16 resident warps per SM (GB300: 2048);
|
|
1907
|
+
the geometry never changes bits."""
|
|
1908
|
+
return prev_pow2(16 * num_sms(device))
|
|
1909
|
+
|
|
1910
|
+
|
|
1911
|
+
def tile_geometry(num_parallel, head_dim, n, chunk_size, nw_default, device=None, quant=0):
|
|
1912
|
+
"""(nw, ppw): warps per CTA and positions per warp tile of this DW for a step of num_parallel chunks of n sorted
|
|
1913
|
+
positions. ppw 128 (Phase 1's tile length), halved to 64 / 32 while the step would launch fewer than
|
|
1914
|
+
_warp_target(device) warps (small steps: shorter per-warp chains); nw 16 when the chunk's token rows take >= 96 KiB
|
|
1915
|
+
of shared memory (more warps share them), else nw_default. Shapes only, speed only (bits do not depend on the
|
|
1916
|
+
tiles: every tile is cut at FIRST boundaries and each group is summed by one warp in sorted order)."""
|
|
1917
|
+
nsl = -(-head_dim // 128)
|
|
1918
|
+
sw = head_dim // nsl
|
|
1919
|
+
ppw = 128
|
|
1920
|
+
target = _warp_target(device)
|
|
1921
|
+
# all-slices quant form: tiles down to 8 positions, 1 on H100; any FIRST-snapped tile set: same bits
|
|
1922
|
+
nsl_w, ppw_min = (1, 1 if _sm90(device) else 8) if quant_form(device, head_dim, quant) >= 5 else (nsl, 32)
|
|
1923
|
+
while ppw > ppw_min and num_parallel * nsl_w * -(-n // ppw) < target:
|
|
1924
|
+
ppw //= 2
|
|
1925
|
+
if _portable(device): # A100 / RTX: 16 warps when two 8-warp CTAs do not fit one SM (the same 16 warps per SM)
|
|
1926
|
+
nw = 16 if 2 * (smem_bytes(chunk_size, head_dim, 8, 2, True) + RESERVED_SMEM) > _device_smem_per_sm(device) \
|
|
1927
|
+
else nw_default
|
|
1928
|
+
else:
|
|
1929
|
+
nw = 16 if chunk_size * sw * 4 >= 96 * 1024 else nw_default
|
|
1930
|
+
return nw, ppw
|
|
1931
|
+
|
|
1932
|
+
|
|
1933
|
+
def admits(bank, chunk_size):
|
|
1934
|
+
"""Off the modern backend (a100 / rtx), the forward's DW admission (movers.dw_admits): the bank-direct walk with 8 warps
|
|
1935
|
+
takes calls on ``bank`` with chunks of ``chunk_size`` tokens (16-byte slice rows; tails: movers.tail_admits)."""
|
|
1936
|
+
return rows16(bank) and supported(bank, chunk_size, 8, True)
|
|
1937
|
+
|
|
1938
|
+
|
|
1939
|
+
_TRI_FMT = "<QQiIIIii24i24Q"
|
|
1940
|
+
_TRI_MAGIC = 0x3154584644585446
|
|
1941
|
+
|
|
1942
|
+
|
|
1943
|
+
def tri_desc(ck, arg_names, args, grid0, varying="ti"):
|
|
1944
|
+
"""The launch descriptor (CPU uint8, FxTriDesc) of a compiled Triton kernel `ck` for the argument tuple
|
|
1945
|
+
`args` (JITFunction.run's bound_args, in arg_names order) and grid (grid0, 1, 1), as Triton 3.7's CudaLauncher
|
|
1946
|
+
would launch it; `varying` names the one 32-bit int argument the fused loop sets per step. None when the launch is
|
|
1947
|
+
outside what fx_fwd_steps replicates (cluster / cooperative / PDL launch, scratch buffers, tensor descriptors,
|
|
1948
|
+
other argument types, too many parameters, a non-CUDA or unloaded kernel, a Triton other than 3.7.x)."""
|
|
1949
|
+
import struct
|
|
1950
|
+
import triton
|
|
1951
|
+
if not str(getattr(triton, "__version__", "")).startswith("3.7."):
|
|
1952
|
+
return None
|
|
1953
|
+
md = ck.metadata
|
|
1954
|
+
if (getattr(md, "num_ctas", 1) != 1 or getattr(md, "launch_cooperative_grid", False)
|
|
1955
|
+
or getattr(md, "launch_pdl", False) or getattr(md, "global_scratch_size", 0)
|
|
1956
|
+
or getattr(md, "profile_scratch_size", 0) or getattr(md, "tensordesc_meta", None)):
|
|
1957
|
+
return None
|
|
1958
|
+
sig = list(ck.src.signature.items())
|
|
1959
|
+
if len(sig) != len(arg_names) or len(args) != len(arg_names):
|
|
1960
|
+
return None
|
|
1961
|
+
sizes, values, slot = [], [], -1
|
|
1962
|
+
for (key, ty), name, val in zip(sig, arg_names, args):
|
|
1963
|
+
if isinstance(key, str) and key != name:
|
|
1964
|
+
return None
|
|
1965
|
+
if ty == "constexpr":
|
|
1966
|
+
continue
|
|
1967
|
+
if not isinstance(ty, str):
|
|
1968
|
+
return None
|
|
1969
|
+
if ty.startswith("*"):
|
|
1970
|
+
if not (isinstance(val, torch.Tensor) and val.is_cuda):
|
|
1971
|
+
return None
|
|
1972
|
+
sizes.append(8)
|
|
1973
|
+
values.append(val.data_ptr())
|
|
1974
|
+
elif ty in ("i32", "i64"):
|
|
1975
|
+
if not isinstance(val, int) or isinstance(val, bool):
|
|
1976
|
+
return None
|
|
1977
|
+
lim = 2 ** 31 if ty == "i32" else 2 ** 63
|
|
1978
|
+
if not -lim <= val < lim:
|
|
1979
|
+
return None
|
|
1980
|
+
sizes.append(4 if ty == "i32" else 8)
|
|
1981
|
+
values.append(val & (2 ** (32 if ty == "i32" else 64) - 1))
|
|
1982
|
+
if name == varying:
|
|
1983
|
+
if ty != "i32":
|
|
1984
|
+
return None
|
|
1985
|
+
slot = len(sizes) - 1
|
|
1986
|
+
else:
|
|
1987
|
+
return None
|
|
1988
|
+
sizes += [8, 8] # global / profile scratch pointers (both null: sizes are 0)
|
|
1989
|
+
values += [0, 0]
|
|
1990
|
+
if slot < 0 or len(sizes) > 24 or not ck.function:
|
|
1991
|
+
return None
|
|
1992
|
+
sizes += [0] * (24 - len(sizes))
|
|
1993
|
+
values += [0] * (24 - len(values))
|
|
1994
|
+
block = 32 * md.num_warps
|
|
1995
|
+
raw = struct.pack(_TRI_FMT, _TRI_MAGIC, ck.function, torch.cuda.current_device(), grid0, block, md.shared,
|
|
1996
|
+
len([z for z in sizes if z]), slot, *sizes, *values)
|
|
1997
|
+
if len(raw) != _module().fx_tri_desc_size():
|
|
1998
|
+
return None
|
|
1999
|
+
return torch.frombuffer(bytearray(raw), dtype=torch.uint8).clone()
|
|
2000
|
+
|
|
2001
|
+
|
|
2002
|
+
_W2_PPW = (16, 32, 64) # two-slice walk: positions per warp tile (half the one-slice walk's 32 / 64 / 128)
|
|
2003
|
+
STATS = {"fx_dw2_wave": 0} # two-slice DW factory calls whose grid _w1_ppw enlarged (counted only when K grows)
|
|
2004
|
+
|
|
2005
|
+
|
|
2006
|
+
def _dw2_wave_ctas(device=None) -> int:
|
|
2007
|
+
"""Resident 8-warp two-slice DW CTAs of one wave: two per SM (GB300: 304; speed only)."""
|
|
2008
|
+
return 2 * num_sms(device)
|
|
2009
|
+
|
|
2010
|
+
|
|
2011
|
+
def tile_geometry_w2(num_parallel, head_dim, n, chunk_size, limit=_SMEM_MAX, device=None, quant=0):
|
|
2012
|
+
"""Two column slices per warp (fx_dw2_kernel): (nw, ppw) or None when the shape does not take it (an odd number of
|
|
2013
|
+
slices, i.e. head_dim <= 128 or 3 / 5 / 7 slices; the token rows of a slice pair do not fit). nw 8 while two
|
|
2014
|
+
8-warp CTAs fit an SM's shared memory (<= 112 KiB each), else 16 (one CTA, the same 16 warps per SM). ppw 64,
|
|
2015
|
+
halved to 32 / 16 while the step would launch fewer than _warp_target(device) warps (the one-slice rule's warps at
|
|
2016
|
+
twice the columns per warp), then _w1_ppw (fill one resident wave). Shapes only, speed only: bits do not depend on
|
|
2017
|
+
the tiles or the column split."""
|
|
2018
|
+
nsl = -(-head_dim // 128)
|
|
2019
|
+
if nsl < 2 or nsl % 2 or head_dim % nsl:
|
|
2020
|
+
return None
|
|
2021
|
+
pairs = nsl // 2
|
|
2022
|
+
qf = quant_form(device, head_dim, quant) # 1 / 2: the cluster form; 5 / 6: all slices (no cluster, no smem rows)
|
|
2023
|
+
# two 8-warp CTAs per SM (modern: <= 112 KiB each; A100 / RTX: the residency rule of tile_geometry), else 16 warps
|
|
2024
|
+
two8 = (2 * (smem_bytes(chunk_size, head_dim, 8, 2, True, w2=True) + RESERVED_SMEM) <= _device_smem_per_sm(device)
|
|
2025
|
+
if _portable(device) else smem_bytes(chunk_size, head_dim, 8, 2, True, w2=True, quant=qf) <= 112 * 1024)
|
|
2026
|
+
if two8 and smem_bytes(chunk_size, head_dim, 8, 2, True, w2=True, quant=qf) <= limit: # within limit
|
|
2027
|
+
nw = 8
|
|
2028
|
+
elif smem_bytes(chunk_size, head_dim, 16, 2, True, w2=True, quant=qf) <= limit:
|
|
2029
|
+
nw = 16
|
|
2030
|
+
else:
|
|
2031
|
+
return None
|
|
2032
|
+
if qf in (1, 2) and not q_fits(device, quant, True, True, nw, True, head_dim // nsl == 128, pairs,
|
|
2033
|
+
smem_bytes(chunk_size, head_dim, nw, 2, True, w2=True, quant=qf)):
|
|
2034
|
+
return None
|
|
2035
|
+
# all-slices form: tiles down to 1 position on H100; any FIRST-snapped tile set: same bits
|
|
2036
|
+
units, ppw_min = (num_parallel, 1 if _sm90(device) else 8) if qf >= 5 else (num_parallel * pairs, _W2_PPW[0])
|
|
2037
|
+
ppw = _W2_PPW[-1]
|
|
2038
|
+
target = _warp_target(device)
|
|
2039
|
+
while ppw > ppw_min and units * -(-n // ppw) < target:
|
|
2040
|
+
ppw //= 2
|
|
2041
|
+
ppw = _w1_ppw(units, n, nw, ppw, device)
|
|
2042
|
+
return nw, ppw
|
|
2043
|
+
|
|
2044
|
+
|
|
2045
|
+
def _w1_ppw(units, n, nw, ppw, device=None):
|
|
2046
|
+
"""Wave fill: when the two-slice DW's grid (units = chunks x slice pairs, times K tiles per unit) fills at least half
|
|
2047
|
+
but not all of ONE resident wave of CTAs (_dw2_wave_ctas(device) for 8-warp CTAs, half for 16-warp CTAs), raise K to
|
|
2048
|
+
wave // units (positions per warp tile ceil(n / (nw K))): every resident slot gets a CTA and each warp walks a
|
|
2049
|
+
shorter tile, still one wave. Shapes only, speed only: any monotone FIRST-snapped tile set gives the same bits (tile
|
|
2050
|
+
bounds snap to group FIRSTs, so every group is summed whole by one warp in sorted order). The caller's K = ceil(n /
|
|
2051
|
+
(nw ppw)) <= wave // units, so the grid stays within one wave. Counted in STATS["fx_dw2_wave"] per factory call
|
|
2052
|
+
whose K this raised; when ceil rounding would leave K unchanged (e.g. n <= nw), the caller's ppw is returned and
|
|
2053
|
+
nothing is counted."""
|
|
2054
|
+
if n <= 0 or units <= 0:
|
|
2055
|
+
return ppw
|
|
2056
|
+
wave_ctas = _dw2_wave_ctas(device)
|
|
2057
|
+
wave = wave_ctas if nw <= 8 else wave_ctas // 2
|
|
2058
|
+
k_now = max(1, -(-n // (nw * ppw)))
|
|
2059
|
+
if not wave <= 2 * units * k_now: # below half a wave (tiny grids): unchanged tiles
|
|
2060
|
+
return ppw
|
|
2061
|
+
k_wave = wave // units
|
|
2062
|
+
if k_wave <= k_now:
|
|
2063
|
+
return ppw
|
|
2064
|
+
ppw_w = max(1, -(-n // (nw * k_wave)))
|
|
2065
|
+
if -(-n // (nw * ppw_w)) <= k_now: # rounding leaves K unchanged: unchanged tiles, not counted
|
|
2066
|
+
return ppw
|
|
2067
|
+
STATS["fx_dw2_wave"] = STATS.get("fx_dw2_wave", 0) + 1
|
|
2068
|
+
return ppw_w
|
|
2069
|
+
|
|
2070
|
+
|
|
2071
|
+
_QFITS: dict = {}
|
|
2072
|
+
|
|
2073
|
+
|
|
2074
|
+
def q_capacity(device, quant, w2, bf, nw, t2d, allon, cluster, smem):
|
|
2075
|
+
"""Co-resident clusters of a quantized plan on `device` (cudaOccupancyMaxActiveClusters; 0: none), cached."""
|
|
2076
|
+
idx = torch.device(device).index if device is not None else None
|
|
2077
|
+
idx = torch.cuda.current_device() if idx is None else idx # the device the query runs on
|
|
2078
|
+
key = (idx, quant, w2, bf, nw, t2d, allon, cluster, smem)
|
|
2079
|
+
n = _QFITS.get(key)
|
|
2080
|
+
if n is None:
|
|
2081
|
+
n = 0
|
|
2082
|
+
if 1 < cluster <= 16:
|
|
2083
|
+
try:
|
|
2084
|
+
with torch.cuda.device(idx):
|
|
2085
|
+
n = int(_module().fx_q_capacity(quant, int(w2), int(bf), nw, int(t2d), int(allon), cluster, smem))
|
|
2086
|
+
except RuntimeError:
|
|
2087
|
+
n = 0
|
|
2088
|
+
_QFITS[key] = n
|
|
2089
|
+
return n
|
|
2090
|
+
|
|
2091
|
+
|
|
2092
|
+
def q_fits(device, quant, w2, bf, nw, t2d, allon, cluster, smem):
|
|
2093
|
+
"""A quantized plan's clusters fit `device` (no cluster: always)."""
|
|
2094
|
+
return cluster <= 1 or q_capacity(device, quant, w2, bf, nw, t2d, allon, cluster, smem) > 0
|
|
2095
|
+
|
|
2096
|
+
|
|
2097
|
+
def quant_k(bank, chunk_size, quant, w2, nw, num_parallel, n, K):
|
|
2098
|
+
"""Tiles per chunk of a quantized cluster plan: K, lowered so that the P * K clusters fit one wave when they would
|
|
2099
|
+
overflow it by less than one (speed only: any FIRST-snapped tile set gives the same bits)."""
|
|
2100
|
+
hd = bank.shape[-1]
|
|
2101
|
+
nsl = -(-hd // 128)
|
|
2102
|
+
cluster = nsl // 2 if w2 else nsl
|
|
2103
|
+
if cluster <= 1 or quant_form(bank.device, hd, quant) >= 5: # (the all-slices form: no clusters)
|
|
2104
|
+
return K
|
|
2105
|
+
cap = q_capacity(bank.device, quant, w2, bank.dtype == torch.bfloat16, nw, True, hd // nsl == 128, cluster,
|
|
2106
|
+
smem_bytes(chunk_size, hd, nw, 2, True, w2=w2, quant=quant))
|
|
2107
|
+
k_min = max(1, -(-n // (nw * _TILE_ROWS)))
|
|
2108
|
+
if num_parallel * K <= cap or num_parallel * K > 2 * cap or cap // num_parallel < k_min:
|
|
2109
|
+
return K
|
|
2110
|
+
STATS["fx_dw_q_wave"] = STATS.get("fx_dw_q_wave", 0) + 1
|
|
2111
|
+
return cap // num_parallel
|
|
2112
|
+
|
|
2113
|
+
|
|
2114
|
+
def quant_geometry(bank, chunk_size, nw, quant):
|
|
2115
|
+
"""The one-slice-per-warp quantized walk with nw warps fits (shared memory; column slices: one cluster of <= 16)."""
|
|
2116
|
+
hd = bank.shape[-1]
|
|
2117
|
+
nsl = -(-hd // 128)
|
|
2118
|
+
qf = quant_form(bank.device, hd, quant)
|
|
2119
|
+
if qf >= 5: # the all-slices form: no cluster, no {amax, scale} rows, any number of slices
|
|
2120
|
+
return geometry(chunk_size, hd, nw, _smem_limit(bank.device), True, qf) is not None
|
|
2121
|
+
if nsl > 16 or geometry(chunk_size, hd, nw, _smem_limit(bank.device), True, quant) is None:
|
|
2122
|
+
return False
|
|
2123
|
+
return q_fits(bank.device, quant, False, bank.dtype == torch.bfloat16, nw, nsl > 1, hd // nsl == 128, nsl,
|
|
2124
|
+
smem_bytes(chunk_size, hd, nw, 2, True, quant=quant))
|
|
2125
|
+
|
|
2126
|
+
|
|
2127
|
+
def _quant_form_fits(bank, chunk_size, q):
|
|
2128
|
+
"""A quantized plan of form q (1 / 2 cluster, 5 / 6 all slices) fits, in mover_da2_cuda.make_dw's order: the two-slice
|
|
2129
|
+
walk, else the one-slice walk with 8 or 16 warps."""
|
|
2130
|
+
hd = bank.shape[-1]
|
|
2131
|
+
if (bank.dtype == torch.bfloat16 and supported(bank, chunk_size, 8, True)
|
|
2132
|
+
and tile_geometry_w2(1, hd, 1, chunk_size, _smem_limit(bank.device), bank.device, q) is not None):
|
|
2133
|
+
return True
|
|
2134
|
+
return any(supported(bank, chunk_size, nw, True) and quant_geometry(bank, chunk_size, nw, q) for nw in (8, 16))
|
|
2135
|
+
|
|
2136
|
+
|
|
2137
|
+
def quant_choice(bank, chunk_size, quant):
|
|
2138
|
+
"""The kernels' quantized form for fp8 (1) / int4 (2) rows, 0 when none fits (the op then refuses the call): the
|
|
2139
|
+
cluster form, else (modern) the all-slices form (quant | 4: same rows, bits and launch)."""
|
|
2140
|
+
if quant not in (1, 2) or not bank.is_cuda or not rows16(bank): # quantized tails: not taken
|
|
2141
|
+
return 0
|
|
2142
|
+
if bank.shape[-1] > _QUANT_MAX_HEAD_DIM: # (QT class T0: heads above the old bound, with its own tests)
|
|
2143
|
+
return 0
|
|
2144
|
+
if _quant_form_fits(bank, chunk_size, quant):
|
|
2145
|
+
return quant
|
|
2146
|
+
if -(-bank.shape[-1] // 128) > 1 and not _portable(bank.device) and _quant_form_fits(bank, chunk_size, quant | 4):
|
|
2147
|
+
return quant | 4
|
|
2148
|
+
return 0
|
|
2149
|
+
|
|
2150
|
+
|
|
2151
|
+
def quant_supported(bank, chunk_size, quant):
|
|
2152
|
+
"""fp8 (1) / int4 (2) compact rows from this DW (= mover_da2_cuda.make_dw's choice, quant_choice)."""
|
|
2153
|
+
return quant_choice(bank, chunk_size, quant) != 0
|
|
2154
|
+
|
|
2155
|
+
|
|
2156
|
+
_NOQ = torch.empty(0, dtype=torch.uint8) # placeholder payload / scale of unquantized plans (never read)
|
|
2157
|
+
|
|
2158
|
+
|
|
2159
|
+
def make_dw_fx(bank, delta_v, krec, tb, segb, snapc, *, chunk_size, num_parallel, dv_rows, sb_step, nw, K,
|
|
2160
|
+
bankb=False, snapw=False, w2=False, quant=0, payload=None, scale=None):
|
|
2161
|
+
"""Step launcher of this DW over mover_da2_cuda's records / tiles / segb and a dense snapc ([rows, hd] view): row
|
|
2162
|
+
(ti * sb_step + p) * n + g of chunk p's g-th valid group (sb_step = P: the saving forward's every-step snapshot;
|
|
2163
|
+
0: a one-step buffer, the no-grad forward). bankb (required): base rows from bank[slot]; snapc / segb are not read
|
|
2164
|
+
(still shape-checked; snapc also anchors the column-sliced token-row map). snapw (sb_step = P only): the DW writes
|
|
2165
|
+
those dense rows (each valid group's pre-write bank row, raw) at its segb ordinal, so the forward's gather need not;
|
|
2166
|
+
segb must then be the tiles' valid-group prefix. quant 1 (fp8) / 2 (int4), sb_step = P, not snapw: the DW writes those
|
|
2167
|
+
rows quantized instead, to payload ([N * n, hd] or [N * n, hd / 2] one-byte rows) and scale (f32 [N * n])."""
|
|
2168
|
+
hd = bank.shape[-1]
|
|
2169
|
+
limit = _smem_limit(bank.device)
|
|
2170
|
+
quant = quant_form(bank.device, hd, quant) if quant in (1, 2) else quant # the kernels' QUANT (5 / 6: all slices)
|
|
2171
|
+
nw, nu = (nw, 2) if w2 else geometry(chunk_size, hd, nw, limit, bankb, quant)
|
|
2172
|
+
# 64-bit row offsets (bank-direct only, no prefetch): bf16 banks of > 4 GiB, every float32 bank
|
|
2173
|
+
big = bank.dtype == torch.float32 or bank.numel() * 2 > 2 ** 32
|
|
2174
|
+
if bank.dtype == torch.float32:
|
|
2175
|
+
STATS["fx_dw_f32"] = STATS.get("fx_dw_f32", 0) + 1 # float32 DW factory calls (engagement counter)
|
|
2176
|
+
snap2 = snapc.view(-1, hd)
|
|
2177
|
+
mod = _module()
|
|
2178
|
+
dv2 = delta_v.view(-1, hd)
|
|
2179
|
+
# The portable side (a100 / rtx) reads the token rows through delta_v itself: no tensor maps are encoded there.
|
|
2180
|
+
portable = _portable(bank.device)
|
|
2181
|
+
if portable:
|
|
2182
|
+
maps = _NOQ
|
|
2183
|
+
else:
|
|
2184
|
+
encode = mod.fx_maps2 if w2 else mod.fx_maps # two-slice walk: the token-row box of a slice pair
|
|
2185
|
+
wide = 0 if w2 else min(max(int(_WIDE_SNAP_ROWS), 0), 2 ** 31) # fx_maps: the snapc map's row threshold
|
|
2186
|
+
mkey = (encode, snap2.data_ptr(), tuple(snap2.shape), tuple(snap2.stride()), snap2.dtype, dv2.data_ptr(),
|
|
2187
|
+
tuple(dv2.shape), tuple(dv2.stride()), dv2.dtype, chunk_size, snap2.get_device(), wide)
|
|
2188
|
+
maps = _MAPS.get(mkey)
|
|
2189
|
+
if maps is None:
|
|
2190
|
+
# empty for one-slice heads
|
|
2191
|
+
maps = encode(snap2, dv2, chunk_size) if w2 else encode(snap2, dv2, chunk_size, wide)
|
|
2192
|
+
if len(_MAPS) > 64:
|
|
2193
|
+
_MAPS.clear()
|
|
2194
|
+
_MAPS[mkey] = maps
|
|
2195
|
+
|
|
2196
|
+
# DW mode: 1 bank-direct, 3 bank-direct + compact-snapshot write; 4 / 5 = 1 / 3 with two column slices per warp
|
|
2197
|
+
# (fx_dw2_kernel)
|
|
2198
|
+
bk = 3 if snapw else 1
|
|
2199
|
+
if w2:
|
|
2200
|
+
bk = 5 if snapw else 4
|
|
2201
|
+
|
|
2202
|
+
# The launch checks run once per factory call; each step passes only the plan and ti (same kernel, same arguments,
|
|
2203
|
+
# same launch). The plan holds raw pointers: the launcher keeps every tensor it names alive.
|
|
2204
|
+
qpay = payload.view(-1, hd if (quant & 3) == 1 else hd // 2) if quant else _NOQ
|
|
2205
|
+
plan = mod.fx_dw_plan(bank, delta_v, dv_rows, krec, tb, segb, snap2, sb_step, num_parallel, chunk_size, K, nw,
|
|
2206
|
+
nu, maps, bk, quant, qpay, scale.view(-1) if quant else _NOQ, int(portable), limit)
|
|
2207
|
+
if quant:
|
|
2208
|
+
STATS["fx_dw_q"] = STATS.get("fx_dw_q", 0) + 1 # quantized-row DW factory calls (reachability counter)
|
|
2209
|
+
step = mod.fx_dw_step
|
|
2210
|
+
pdl = [0]
|
|
2211
|
+
|
|
2212
|
+
def launch(ti: int) -> None:
|
|
2213
|
+
step(plan, ti, pdl[0])
|
|
2214
|
+
|
|
2215
|
+
def set_pdl(on: bool) -> None:
|
|
2216
|
+
# Programmatic dependent launches for every step of this factory (sm_90+ only; else a no-op). The CALLER asserts
|
|
2217
|
+
# the rule the kernel's pre-wait code relies on: the launch right before each step's DW is that step's dual matmul,
|
|
2218
|
+
# launched after the step's gather (the deterministic forward).
|
|
2219
|
+
pdl[0] = 1 if (on and fwd_pdl_ok(bank.device)) else 0
|
|
2220
|
+
launch.set_pdl = set_pdl
|
|
2221
|
+
launch.fx_plan = plan # the fused deterministic step loop launches this plan (with fx_pdl[0])
|
|
2222
|
+
launch.fx_pdl = pdl
|
|
2223
|
+
launch._fx_keep = (bank, delta_v, krec, tb, segb, snap2, maps, plan, qpay, scale)
|
|
2224
|
+
|
|
2225
|
+
nsl = -(-hd // 128)
|
|
2226
|
+
launch.geom = dict(nw=nw, nu=0 if bankb else nu, K=K, fx=True, bankb=bankb, pf=False, snapw=snapw, big=big or bool(quant),
|
|
2227
|
+
w2=w2, quant=quant, cluster=(nsl // 2 if w2 else nsl) if quant in (1, 2) and nsl > 1 else 0,
|
|
2228
|
+
all_slices=quant >= 5)
|
|
2229
|
+
return launch
|