Port Qwen row-map SwiGLU expert gathers

This commit is contained in:
Georg Bauer committed 2026-10-04 16:24:33 +02:00
1 parent 8965e77ea1
commit 7b894fbfee
42 files changed
+11489 -65

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright © 2023 Apple Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+199
View File
@@ -0,0 +1,199 @@
// MTPLX 9882703, MIT; see MTPLX-LICENSE.txt.
constexpr int BM = 64;
constexpr int BN = 64;
constexpr int BK = 64;
constexpr int WN = 2;
constexpr short SM = 32;
constexpr short SN = 32;
constexpr short SK = 32;
constexpr short TM = SM / 16;
constexpr short TN = SN / 16;
constexpr short TK = SK / 16;
constexpr int PACK = get_pack_factor<BITS, 8>();
constexpr int PACK_BYTES = get_bytes_per_pack<BITS>();
constexpr int BK_PAD = BK + 16 / sizeof(T);
constexpr int ROW_BYTES = KD * PACK_BYTES / PACK;
constexpr int ROW_GROUPS = KD / GS;
constexpr int OUT_COLS = OUTC;
// SWIGLU: the tile's first 32 weight rows are gate rows, the last 32 the
// matching up rows, each half loaded by 64 threads from its own row range.
using WeightLoader = metal::conditional_t<
SWIGLU,
QuantizedBlockLoader<T, BN / 2, BK, BK_PAD, 1, 64, GS, BITS>,
QuantizedBlockLoader<T, BN, BK, BK_PAD, 1, 128, GS, BITS>>;
threadgroup T w_tile[BN * BK_PAD];
// The tile this threadgroup owns, and the one expert whose rows it holds:
// the last expert whose first tile is at or before it.
const int tile = int(threadgroup_position_in_grid.y);
if (tile >= tile_start[ED]) {
return;
}
int lo = 0;
int hi = ED - 1;
while (lo < hi) {
const int mid = (lo + hi + 1) >> 1;
if (tile_start[mid] <= tile) {
lo = mid;
} else {
hi = mid - 1;
}
}
const int expert = lo;
const int row0 = row_start[expert] + (tile - tile_start[expert]) * BM;
const int tile_rows = min(BM, row_start[expert + 1] - row0);
const int col0 = int(threadgroup_position_in_grid.x) * BN;
const ushort sg = ushort(simdgroup_index_in_threadgroup);
const ushort lane = ushort(thread_index_in_simdgroup);
const short tm = SM * short(sg / WN);
const short tn = SN * short(sg % WN);
// Rows of this tile that fall in this simdgroup's 32-row half (0 to 32).
const short rows = short(clamp(tile_rows - int(tm), 0, int(SM)));
ushort load_sg = sg;
const device uint8_t* w_src = (const device uint8_t*)w;
const device T* s_src = scales;
const device T* b_src = biases;
size_t w_row = size_t(expert) * ND + size_t(col0);
threadgroup T* w_dst = w_tile;
if (SWIGLU) {
// Output columns [col0 / 2, col0 / 2 + 32): the matching gate rows
// of w and up rows of wu (the same array at an offset when the
// weight is a fused [gate | up]).
const int up_half = int(load_sg / 2);
const size_t c = size_t(col0 / 2);
if (up_half) {
w_src = (const device uint8_t*)wu;
s_src = scales_u;
b_src = biases_u;
w_row = size_t(expert) * UD + size_t(UOFF) + c;
} else {
w_row = size_t(expert) * ND + c;
}
w_dst = w_tile + up_half * (BN / 2) * BK_PAD;
load_sg = load_sg % 2;
}
thread WeightLoader loader(
w_src + w_row * ROW_BYTES,
s_src + w_row * ROW_GROUPS,
b_src + w_row * ROW_GROUPS,
KD,
w_dst,
load_sg,
lane);
NAXTile<float, TM, TN> acc;
acc.clear();
// A lane holds rows (y, y + 8) and four consecutive columns from x of every
// 16x16 fragment (MLX's fragment coordinates). Its four token rows are
// fixed for the whole K loop; rows past the tile read as zero, as MLX's
// bounded load does.
const short2 coord = BaseNAXFrag::get_coord();
const device T* lane_rows[TM][2];
bool lane_live[TM][2];
for (short i = 0; i < TM; i++) {
for (short r = 0; r < 2; r++) {
const short local = i * 16 + coord.y + r * 8;
lane_live[i][r] = local < rows;
const int token = lane_live[i][r] ? int(row_map[row0 + tm + local]) : 0;
lane_rows[i][r] = x + size_t(token) * KD + coord.x;
}
}
// The K loop, compiled twice: for a simdgroup whose 32 rows are all live
// (no bounds) and for a partial or empty half.
dispatch_bool(rows == SM, [&](auto full) {
for (int k = 0; k < KD / BK; k++) {
threadgroup_barrier(mem_flags::mem_threadgroup);
loader.load_unsafe();
threadgroup_barrier(mem_flags::mem_threadgroup);
STEEL_PRAGMA_NO_UNROLL
for (short kk = 0; kk < BK; kk += SK) {
if (decltype(full)::value || rows > 0) {
NAXTile<T, TM, TK> a;
NAXTile<T, TN, TK> b;
volatile int keep_order;
const int col = k * BK + kk;
STEEL_PRAGMA_UNROLL
for (short i = 0; i < TM; i++) {
STEEL_PRAGMA_UNROLL
for (short j = 0; j < TK; j++) {
thread auto& frag = a.frag_at(i, j);
STEEL_PRAGMA_UNROLL
for (short r = 0; r < 2; r++) {
const device T* src = lane_rows[i][r] + col + j * 16;
STEEL_PRAGMA_UNROLL
for (short c = 0; c < 4; c++) {
if constexpr (decltype(full)::value) {
frag[r * 4 + c] = src[c];
} else {
frag[r * 4 + c] = lane_live[i][r] ? src[c] : T(0);
}
}
}
}
}
b.template load<T, BK_PAD, 1>(w_tile + tn * BK_PAD + kk);
tile_matmad_nax(
acc,
a,
metal::bool_constant<false>{},
b,
metal::bool_constant<true>{});
(void)keep_order;
}
}
loader.next();
}
});
if constexpr (SWIGLU) {
// The up simdgroup of each 32-row half hands its values, rounded to T
// as the stock gather's output is, to the gate simdgroup of the same
// rows through the (now free) weight tile, lane to lane. The gate
// simdgroup writes silu(gate) * up with MLX's own functors, rounding to
// T after each op as the stock chain does.
constexpr short VALS = TM * TN * 8;
static_assert(2 * 32 * VALS <= BN * BK_PAD, "the hand-off must fit the weight tile");
threadgroup T* handoff = w_tile + (sg / 2) * (32 * VALS) + lane * VALS;
const thread float* vals = acc.elems();
threadgroup_barrier(mem_flags::mem_threadgroup);
if (tn != 0) {
for (short e = 0; e < VALS; e++) {
handoff[e] = static_cast<T>(vals[e]);
}
}
threadgroup_barrier(mem_flags::mem_threadgroup);
if (tn == 0 && rows > 0) {
device T* out = y + size_t(row0 + tm) * OUT_COLS + size_t(col0 / 2);
for (short i = 0; i < TM; i++) {
for (short j = 0; j < TN; j++) {
for (short r = 0; r < 2; r++) {
const short row = i * 16 + coord.y + r * 8;
if (row >= rows) {
continue;
}
for (short c = 0; c < 4; c++) {
const short e = (i * TN + j) * 8 + r * 4 + c;
const T gate = static_cast<T>(vals[e]);
const T act = Multiply()(gate, Sigmoid()(gate));
out[size_t(row) * OUT_COLS + j * 16 + coord.x + c] =
Multiply()(act, handoff[e]);
}
}
}
}
}
} else if (rows > 0) {
device T* out = y + size_t(row0 + tm) * ND + size_t(col0 + tn);
if (rows == SM) {
acc.store(out, ND);
} else {
acc.store_safe(out, ND, short2(SN, rows));
}
}
File diff suppressed because it is too large. Load diff