mirror of
https://github.com/Niko1221/Strata.git
synced 2026-11-07 18:35:04 -10:00
perf(cuda,decode): zero-doorbell verify graph, sub-warp expert packing, shared-mem staging, and batched PLE/MTP
This commit is contained in:
1 parent
586367cb00
commit
a36be1db29
21 files changed
+1197
-322
No files matched your search
@@ -82,6 +82,7 @@ public:
|
||||
/// Coupled mode with penalties: the history the next draft() chain starts from - what the session holds after the
|
||||
/// window's commit (`tail`) and the window's pick at its last accepted row (`next`, the next window's row 0).
|
||||
void set_draft_history(const int32_t* tail, int64_t n_tail, int32_t next);
|
||||
void set_ple_session(SessionState* s) { ple_ss_ = s; }
|
||||
bool coupled() const { return coupled_active_; }
|
||||
|
||||
double ms_draft = 0, ms_prefill = 0;
|
||||
@@ -124,6 +125,7 @@ private:
|
||||
|
||||
const ModelGeometry* g_ = nullptr;
|
||||
SessionState* ss_ = nullptr;
|
||||
SessionState* ple_ss_ = nullptr;
|
||||
const WeightTable* wt_ = nullptr;
|
||||
const NativeHead* head_ = nullptr;
|
||||
const float* window_R_ = nullptr;
|
||||
|
||||
@@ -45,6 +45,7 @@ using PoolMultiFn = void (*)(void* user, const float* x_f, const int32_t* ids, i
|
||||
|
||||
struct VerifyHits {
|
||||
const int32_t* d_res = nullptr; ///< device [n_layers * n_expert] slot or -1
|
||||
const int32_t* h_res = nullptr; ///< host [n_layers * n_expert] slot or -1 (when all >= 0 in stage: zero-doorbell)
|
||||
const uint8_t* cache_base = nullptr; ///< slot 0 of the VRAM expert arena
|
||||
const uint64_t* slot_off = nullptr; ///< E-6: host per-slot offsets when slots differ in size (null: slot * blob)
|
||||
int64_t n_slots = 0; ///< E-6: how many (for the device copy)
|
||||
@@ -176,6 +177,7 @@ private:
|
||||
bool head_sampling_ = true; ///< set_head_sampling
|
||||
int device_ = -1; ///< the device `init` ran on: run/commit switch to it (layer split)
|
||||
std::atomic<bool> released_{false}; ///< #267: release_gpu_waits ran (maybe on the watchdog thread): no more windows
|
||||
bool all_resident_ = false; ///< 100% of experts in [lb_, le_) resident in VRAM: zero-doorbell graph
|
||||
bool device_plan_ = false; ///< E-6: resident-only layers planned on the device (STRATA_VERIFY_DEVICE_PLAN)
|
||||
uint32_t* skip_ = nullptr; ///< E-6: per group, the ring whose plan the device built (0: the host's)
|
||||
unsigned long long* slot_off_d_ = nullptr; ///< E-6: the slot offsets on the device
|
||||
@@ -185,6 +187,9 @@ private:
|
||||
Verifier* next_ = nullptr;
|
||||
void* next_user_ = nullptr;
|
||||
bool ple_stage() const { return lb_ <= 1 && 1 < le_; } ///< holds layer 1, where the PLE block runs
|
||||
void stage_inputs(int T, const int32_t* tokens, int64_t pos0);
|
||||
bool staged_ = false;
|
||||
bool copy_used_ = false;
|
||||
bool capture_commit(std::string& err);
|
||||
bool record_window(int T, cudaStream_t cs, std::string& err);
|
||||
// #649: STRATA_VERIFY_TRACE=1 - a host event ring (trace_ev) and GPU breadcrumbs: the profiler's stamp points,
|
||||
@@ -212,6 +217,8 @@ private:
|
||||
int32_t last_tokens_[8] = {};
|
||||
int64_t n_vocab_ = 0;
|
||||
cudaStream_t cs_ = nullptr;
|
||||
cudaStream_t sh_cs_ = nullptr;
|
||||
cudaEvent_t ev_fork_ = nullptr, ev_join_ = nullptr;
|
||||
cudaGraphExec_t exec_[9] = {};
|
||||
cudaGraphExec_t commit_exec_ = nullptr;
|
||||
|
||||
@@ -252,6 +259,7 @@ private:
|
||||
float *ple_ = nullptr, *emb_ = nullptr, *R_ = nullptr, *mixed_ = nullptr, *bo_ = nullptr;
|
||||
float *inj_ = nullptr, *inj2_ = nullptr, *lo_ = nullptr, *rs_ = nullptr, *xn_ = nullptr;
|
||||
uint8_t* xq_ = nullptr; // T columns of q8_1
|
||||
uint8_t* sh_xq_ = nullptr; // T columns of q8_1 for shared expert branch
|
||||
float *qkv_L_ = nullptr, *h_L_ = nullptr, *gate_L_ = nullptr, *beta_L_ = nullptr; // per GDN layer
|
||||
float *z_ = nullptr, *y_ = nullptr, *y_dummy_ = nullptr;
|
||||
float *qfull_ = nullptr, *qcur_ = nullptr, *kcur_ = nullptr, *vcur_ = nullptr, *idx_raw_L_ = nullptr;
|
||||
|
||||
@@ -44,6 +44,8 @@ struct ExpertLayout {
|
||||
/// Plan v0.3 P6: whether this CPU (and its OS) runs the AVX-512 kernels (F, BW, VL, VNNI, VBMI). Probed in a
|
||||
/// file compiled without AVX-512, so asking is safe everywhere; STRATA_FORCE_AVX2=1 answers no (for tests).
|
||||
bool cpu_avx512_ok();
|
||||
/// Whether this CPU runs AVX-512F + AVX-512BW + AVX-512VL + AVX-512DQ (e.g. Skylake-X i9-9920X) for iq_avx512.cpp.
|
||||
bool cpu_avx512bw_ok();
|
||||
/// Whether this CPU (and its OS) runs the AVX2 kernels (AVX, AVX2, FMA, F16C): the floor of every expert kernel
|
||||
/// (q2_avx2.cpp, iq_avx2.cpp, and ggml-cpu in the portable build). STRATA_FORCE_AVX2 does not change it.
|
||||
bool cpu_avx2_ok();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// include/strata/kernels/elementwise.hpp - the small ops a LAYER needs between its GEMVs, P2.S5.
|
||||
// include/strata/kernels/elementwise.hpp - the small ops a LAYER needs between its GEMVs, P2.S5.
|
||||
//
|
||||
// These are not interesting kernels and that is why they are in one file with one parity test: they are the
|
||||
// glue in the per-layer graph, and glue that is written inline at each call site is glue with several
|
||||
@@ -118,6 +118,8 @@ void doorbell_publish(const float* x, const int32_t* ids, const float* weights,
|
||||
void doorbell_publish_value(const float* x, const int32_t* ids, const float* weights, int64_t n, int64_t k,
|
||||
float* x_out, int32_t* ids_out, float* weights_out, uint32_t* d_seq, uint32_t value,
|
||||
void* stream);
|
||||
void doorbell_publish_res(const float* x, const int32_t* ids, const int32_t* d_res, int n_expert, int64_t n, int64_t k,
|
||||
float* x_out, int32_t* ids_out, uint32_t* d_seq, void* stream);
|
||||
|
||||
/// Plan v0.3 P3: copy `n` int32 from mapped pinned host memory into device memory with a kernel (the QSA
|
||||
/// per-token step and positions), instead of a host-to-device memcpy node in the middle of a layer.
|
||||
|
||||
@@ -143,6 +143,9 @@ public:
|
||||
/// needs the rows. `gather` is `issue` followed by `collect`. In Mmap mode `issue` only prefetches.
|
||||
bool issue(const uint32_t* rows16);
|
||||
bool collect(float* out2560, std::string& err);
|
||||
/// Non-blocking prefetch of 16 rows for one token into the reader's row cache while the GPU is busy.
|
||||
void prefetch_rows(const uint32_t* rows16);
|
||||
void wait_prefetches();
|
||||
/// Plan v0.3 P5: the rows of `n_tokens` tokens (16 each, `rows` token-major) into `out` (2560 floats per token),
|
||||
/// as ONE reader request - page dedupe and sort across the whole batch, the reader's full queue depth. Not
|
||||
/// while a single-token `issue` is pending. The mapped mode gathers row by row.
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace strata::kernels {
|
||||
/// Opt in to pinned CUDA BF16/F32 MMVF for the PLE value projection only (default false).
|
||||
/// Configure before session capture; captured graphs retain their selected projection kernels.
|
||||
void ple_set_native_bf16(bool enabled);
|
||||
bool ple_native_bf16_enabled();
|
||||
|
||||
/// Opt in to pinned CUDA postprojection norms, gate, convolution and residual
|
||||
/// arithmetic (default false). Set before capture; existing graphs keep their
|
||||
@@ -107,6 +108,9 @@ uint64_t ple_block_scratch_bytes();
|
||||
void ple_block(const float* emb, const float* hidden, const float* hist_rows, const PleWeights& w,
|
||||
PleOut& out, void* scratch, void* stream);
|
||||
|
||||
void ple_block_projected(const float* projected_key, const float* projected_value, const float* hidden,
|
||||
const float* hist_rows, const PleWeights& w, PleOut& out, void* scratch, void* stream);
|
||||
|
||||
/// Advance the row-fastest normalized history by one token: hist[r,c] = old_hist[r+1,c], then append
|
||||
/// normalized[c] at row NG_HIST-1. One thread owns each channel, making the in-place shift well-defined.
|
||||
/// hist and normalized must be nonnull, disjoint device regions of NG_HIST*NG_HC_DIM and NG_HC_DIM floats.
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace strata::kernels {
|
||||
/// Configure before session capture; captured graphs retain their selected kernels.
|
||||
/// In this mode shared_expert requires its optional unrounded x_f32 input.
|
||||
void shared_expert_set_native_bf16(bool enabled);
|
||||
bool shared_expert_native_bf16_enabled();
|
||||
|
||||
/// Optional native GGUF projections. Each supported type with nonnull data
|
||||
/// replaces only that canonical projection; absent or unsupported entries fall
|
||||
@@ -37,6 +38,7 @@ struct NativeSharedWeights {
|
||||
const void* up_data = nullptr;
|
||||
const void* down_data = nullptr;
|
||||
void* q8_1 = nullptr;
|
||||
const void* x_q8_1 = nullptr; // optional pre-quantized Q8_1 input for gate/up in shared_expert_multi
|
||||
};
|
||||
|
||||
/// Bytes of caller-owned scratch `shared_expert` needs. **THE KERNEL USED TO `cudaMalloc` FOUR BUFFERS ON
|
||||
|
||||
+23
-12
@@ -2099,16 +2099,23 @@ void expert_pool_dispatch_multi(ExpertDispatch& d, const float* x_f, const int32
|
||||
for (int64_t i = 0; i < n; ++i) if (d.remote[r]->owns(i)) kind[i] = 2;
|
||||
}
|
||||
}
|
||||
static const bool dec_batch = [] { const char* v = std::getenv("STRATA_DEC_BATCH"); return v == nullptr || std::atoi(v) != 0; }();
|
||||
const bool gpu_zeroes_hits = (d.plan != nullptr && n <= kMaxWindowEntries && n <= d.plan->cap && dec_batch);
|
||||
bool any_cpu = false;
|
||||
for (int64_t i = 0; i < n; ++i)
|
||||
if (kind[i] < 0) { any_cpu = true; break; }
|
||||
const auto c1 = std::chrono::steady_clock::now();
|
||||
if (native && lay.fmt[(size_t) d.layers].gu_type == 42) // a native Q2_0 pack: the Q2_0 kernels' activations
|
||||
for (int64_t t = 0; t < n_tok; ++t) act_quant_any(x_f + (size_t) t * H, H, d.act_multi[(size_t) t]);
|
||||
else if (native)
|
||||
for (int64_t t = 0; t < n_tok; ++t)
|
||||
native_quant_act(lay.fmt[(size_t) d.layers], x_f + (size_t) t * H, d.nact_multi.data() + (size_t) t * kNativeActBytes);
|
||||
else
|
||||
for (int64_t t = 0; t < n_tok; ++t) act_quant_q8_1(x_f + (size_t) t * H, H, d.act_multi[(size_t) t]);
|
||||
if (any_cpu) {
|
||||
if (native && lay.fmt[(size_t) d.layers].gu_type == 42) // a native Q2_0 pack: the Q2_0 kernels' activations
|
||||
for (int64_t t = 0; t < n_tok; ++t) act_quant_any(x_f + (size_t) t * H, H, d.act_multi[(size_t) t]);
|
||||
else if (native)
|
||||
for (int64_t t = 0; t < n_tok; ++t)
|
||||
native_quant_act(lay.fmt[(size_t) d.layers], x_f + (size_t) t * H, d.nact_multi.data() + (size_t) t * kNativeActBytes);
|
||||
else
|
||||
for (int64_t t = 0; t < n_tok; ++t) act_quant_q8_1(x_f + (size_t) t * H, H, d.act_multi[(size_t) t]);
|
||||
}
|
||||
const auto c2 = std::chrono::steady_clock::now();
|
||||
{ // CS-T: the experts the CPU computes, fetched together (the GGUF in place reads them on several threads)
|
||||
if (any_cpu) { // CS-T: the experts the CPU computes, fetched together (the GGUF in place reads them on several threads)
|
||||
static thread_local std::vector<int64_t> miss;
|
||||
miss.clear();
|
||||
for (int64_t i = 0; i < n_tok * k; ++i)
|
||||
@@ -2134,8 +2141,10 @@ void expert_pool_dispatch_multi(ExpertDispatch& d, const float* x_f, const int32
|
||||
if (kind[i] == 0) ++d.cache_hits;
|
||||
else ++d.offload_entries; // #588: PCIe or another GPU
|
||||
if (kind[i] == 2 && d.peer != nullptr) ++d.peer_entries;
|
||||
// multi-GPU: a direct peer launch is writing this row right now - zeroing it would race it
|
||||
if (!(kind[i] == 2 && d.peer != nullptr && d.peer->launched_direct()))
|
||||
// multi-GPU: a direct peer launch is writing this row right now - zeroing it would race it;
|
||||
// dec_batch: copy_rows_from_mapped_kernel already zeroes kind 0/1 rows on the GPU
|
||||
if (!((kind[i] == 2 && d.peer != nullptr && d.peer->launched_direct()) ||
|
||||
(gpu_zeroes_hits && (kind[i] == 0 || kind[i] == 1))))
|
||||
std::memset(row, 0, (size_t) H * sizeof(float));
|
||||
continue;
|
||||
}
|
||||
@@ -2165,8 +2174,10 @@ void expert_pool_dispatch_multi(ExpertDispatch& d, const float* x_f, const int32
|
||||
}
|
||||
const auto c3 = std::chrono::steady_clock::now();
|
||||
pt("run", njobs);
|
||||
if (native) d.pool->run_split_multi_native(lay.fmt[(size_t) d.layers], d.jobs_multi.data(), njobs);
|
||||
else d.pool->run_split_multi(d.jobs_multi.data(), njobs);
|
||||
if (njobs > 0) {
|
||||
if (native) d.pool->run_split_multi_native(lay.fmt[(size_t) d.layers], d.jobs_multi.data(), njobs);
|
||||
else d.pool->run_split_multi(d.jobs_multi.data(), njobs);
|
||||
}
|
||||
if (d.remote_count > 0) {
|
||||
static thread_local std::string remote_error;
|
||||
for (int r = 0; r < d.remote_count; ++r)
|
||||
|
||||
+113
-28
@@ -26,6 +26,7 @@
|
||||
#include "strata/kernels/sampler.hpp"
|
||||
#include "strata/kernels/shared_expert.hpp"
|
||||
#include "strata/kernels/verify_kernels.hpp"
|
||||
#include "strata/kernels/ngram.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
@@ -37,6 +38,11 @@
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#if defined(_WIN32)
|
||||
#include <intrin.h>
|
||||
#elif defined(__x86_64__)
|
||||
#include <immintrin.h>
|
||||
#endif
|
||||
|
||||
namespace strata::core {
|
||||
namespace {
|
||||
@@ -146,6 +152,7 @@ bool MtpDrafter::load(const std::string& rt_dir, const ModelGeometry& g, Session
|
||||
cudaGetDevice(&device_); // a layer split's last stage on another GPU: the drafter lives there
|
||||
g_ = &g;
|
||||
ss_ = &ss;
|
||||
if (ple_ss_ == nullptr) ple_ss_ = &ss;
|
||||
max_t_ = max_t;
|
||||
rt_dir_ = rt_dir;
|
||||
if (max_t < 1 || max_t > strata::kernels::kVerifyMaxT) { err = "mtp: max_t out of range"; return false; }
|
||||
@@ -589,14 +596,22 @@ bool MtpDrafter::record_forward(int T, int step_row0, cudaStream_t cs, std::stri
|
||||
native_moe_combine(parts_ + (size_t) t * K * N, w_ + t * K, shared_ + t * N, y_ + t * N, N, K, cs);
|
||||
else
|
||||
moe_combine(parts_ + (size_t) t * K * N, w_ + t * K, shared_ + t * N, y_ + t * N, N, K, cs);
|
||||
gr_write(R_ + (size_t) t * HC * N, y_ + t * N, inj2_ + t * HC, gs, R_ + (size_t) t * HC * N, cs);
|
||||
}
|
||||
// ---- the final mixer and the main model's head
|
||||
for (int t = 0; t < T; ++t)
|
||||
gr_read(R_ + (size_t) t * HC * N, f32("hyper_connection_mixer.hc_norm.weight"),
|
||||
bf16("hyper_connection_mixer.input_mix_weight_down.weight"),
|
||||
bf16("hyper_connection_mixer.input_mix_weight_up.weight"), nullptr, EPS, gs, ss.block.gr,
|
||||
sample_ + t * N, dummy_inj_, cs);
|
||||
// ---- the final mixer (with the MLP write folded in) and the main model's head
|
||||
{
|
||||
FusedGrArgs fa[kFusedGrMaxT];
|
||||
for (int t = 0; t < T; ++t) {
|
||||
fa[t].R = R_ + (size_t) t * HC * N; fa[t].R_out = R_ + (size_t) t * HC * N; fa[t].apply = true;
|
||||
fa[t].bo_prev = y_ + t * N; fa[t].inj_prev = inj2_ + t * HC;
|
||||
fa[t].w_norm = f32("hyper_connection_mixer.hc_norm.weight");
|
||||
fa[t].w_down = bf16("hyper_connection_mixer.input_mix_weight_down.weight");
|
||||
fa[t].w_up = bf16("hyper_connection_mixer.input_mix_weight_up.weight");
|
||||
fa[t].w_inject = nullptr;
|
||||
fa[t].eps = EPS; fa[t].lo = lo_ + t * g.hc_lr; fa[t].rs = rs_ + t * HC;
|
||||
fa[t].inject_out = dummy_inj_; fa[t].mixed = sample_ + t * N;
|
||||
}
|
||||
fused_gr_read_multi(fa, T, xn_, cs);
|
||||
}
|
||||
native_quantize_q8_1(sample_, xq_, (int) N, T, cs);
|
||||
const bool sub = dhead_ != nullptr;
|
||||
const int64_t nv = sub ? n_dvocab_ : n_vocab_;
|
||||
@@ -829,6 +844,9 @@ bool MtpDrafter::draft(int T, const int32_t* tokens, int64_t p, int a, int32_t*
|
||||
if (T < 1 || T > max_t_ || a < 0 || a >= T) { err = "mtp: draft arguments out of range"; return false; }
|
||||
const bool cp = coupled_active_; // coupled draft sampling for this request: its own graphs
|
||||
if (!capture_round(T, cp, err)) return false;
|
||||
const int max_steps = std::min(max_t_ - 1, max_drafts_);
|
||||
for (int j = 1; j < max_steps; ++j)
|
||||
if (!capture_step(j, cp, err)) return false;
|
||||
const Clock::time_point t0 = Clock::now();
|
||||
const int64_t NH = g_->n_head;
|
||||
auto put = [&](int row, int64_t cell) {
|
||||
@@ -843,32 +861,99 @@ bool MtpDrafter::draft(int T, const int32_t* tokens, int64_t p, int a, int32_t*
|
||||
put(t, p + t);
|
||||
}
|
||||
put(2 * max_t_ - 1, coupled_draft_cell(p, a, 0)); // p + a: draft 0's cell
|
||||
for (int j = 1; j < max_steps; ++j)
|
||||
put(max_t_ + j - 1, coupled_draft_cell(p, a, j));
|
||||
for (int j = 0; j < max_steps; ++j) ((volatile int32_t*) h_out_)[j] = -1;
|
||||
h_row_[0] = a;
|
||||
h_row_[1] = 0;
|
||||
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||
if (cudaGraphLaunch(cp ? round_exec_c_[T] : round_exec_[T], cs_) != cudaSuccess ||
|
||||
cudaStreamSynchronize(cs_) != cudaSuccess) {
|
||||
err = std::string("mtp draft: ") + cudaGetErrorString(cudaGetLastError());
|
||||
return false;
|
||||
}
|
||||
drafts[0] = ((volatile int32_t*) h_out_)[0];
|
||||
float pj = ((volatile float*) h_prob_)[0];
|
||||
if (probs) probs[0] = pj;
|
||||
int n = 1;
|
||||
// the chain continues while the last draft is likely enough to be verified
|
||||
for (int j = 1; j < std::min(max_t_ - 1, max_drafts_) && pj >= min_p; ++j) {
|
||||
if (!capture_step(j, cp, err)) return false;
|
||||
put(max_t_ + j - 1, coupled_draft_cell(p, a, j)); // p + a + j; coupled: drawn at counter cell + 1
|
||||
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||
if (cudaGraphLaunch(cp ? step_exec_c_[j] : step_exec_[j], cs_) != cudaSuccess ||
|
||||
cudaStreamSynchronize(cs_) != cudaSuccess) {
|
||||
err = std::string("mtp draft step: ") + cudaGetErrorString(cudaGetLastError());
|
||||
|
||||
SessionState* const pss = ple_ss_ ? ple_ss_ : ss_;
|
||||
const bool do_ple = pss != nullptr && pss->ple.ready() && pss->ple.table != nullptr;
|
||||
int32_t ple_prev[2] = {do_ple ? pss->ple_prev[0] : 0, do_ple ? pss->ple_prev[1] : 0};
|
||||
auto prefetch_ple = [&](int32_t tok) {
|
||||
if (!do_ple || tok < 0) return;
|
||||
uint32_t rows16[strata::kernels::PLE_N_HEADS];
|
||||
strata::kernels::ngram_rows(&tok, ple_prev, 1, pss->ple.consts, rows16);
|
||||
ple_prev[0] = ple_prev[1];
|
||||
ple_prev[1] = tok;
|
||||
pss->ple.table->prefetch_rows(rows16);
|
||||
};
|
||||
|
||||
int n = 0;
|
||||
if (min_p <= 0.0f && max_steps > 0) {
|
||||
if (cudaGraphLaunch(cp ? round_exec_c_[T] : round_exec_[T], cs_) != cudaSuccess) {
|
||||
err = std::string("mtp draft: ") + cudaGetErrorString(cudaGetLastError());
|
||||
return false;
|
||||
}
|
||||
for (int j = 1; j < max_steps; ++j) {
|
||||
if (cudaGraphLaunch(cp ? step_exec_c_[j] : step_exec_[j], cs_) != cudaSuccess) {
|
||||
err = std::string("mtp draft step: ") + cudaGetErrorString(cudaGetLastError());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
(void) cudaStreamQuery(cs_);
|
||||
prefetch_ple(tokens[a]);
|
||||
for (int j = 0; j + 1 < max_steps; ++j) {
|
||||
uint32_t spins = 0;
|
||||
while (((volatile int32_t*) h_out_)[j] < 0) {
|
||||
#if defined(_WIN32) || defined(__x86_64__)
|
||||
_mm_pause();
|
||||
#endif
|
||||
if ((++spins & 1023u) == 0 && cudaStreamQuery(cs_) != cudaErrorNotReady) break;
|
||||
}
|
||||
drafts[j] = ((volatile int32_t*) h_out_)[j];
|
||||
if (probs) probs[j] = ((volatile float*) h_prob_)[j];
|
||||
prefetch_ple(drafts[j]);
|
||||
}
|
||||
if (cudaStreamSynchronize(cs_) != cudaSuccess) {
|
||||
err = std::string("mtp draft: ") + cudaGetErrorString(cudaGetLastError());
|
||||
return false;
|
||||
}
|
||||
const int last = max_steps - 1;
|
||||
drafts[last] = ((volatile int32_t*) h_out_)[last];
|
||||
if (probs) probs[last] = ((volatile float*) h_prob_)[last];
|
||||
prefetch_ple(drafts[last]);
|
||||
n = max_steps;
|
||||
} else if (max_steps > 0) {
|
||||
if (cudaGraphLaunch(cp ? round_exec_c_[T] : round_exec_[T], cs_) != cudaSuccess) {
|
||||
err = std::string("mtp draft: ") + cudaGetErrorString(cudaGetLastError());
|
||||
return false;
|
||||
}
|
||||
(void) cudaStreamQuery(cs_);
|
||||
prefetch_ple(tokens[a]);
|
||||
auto wait_step = [&](int j) {
|
||||
uint32_t spins = 0;
|
||||
while (((volatile int32_t*) h_out_)[j] < 0) {
|
||||
#if defined(_WIN32) || defined(__x86_64__)
|
||||
_mm_pause();
|
||||
#endif
|
||||
if ((++spins & 1023u) == 0 && cudaStreamQuery(cs_) != cudaErrorNotReady) break;
|
||||
}
|
||||
};
|
||||
wait_step(0);
|
||||
drafts[0] = ((volatile int32_t*) h_out_)[0];
|
||||
float pj = ((volatile float*) h_prob_)[0];
|
||||
if (probs) probs[0] = pj;
|
||||
n = 1;
|
||||
for (int j = 1; j < max_steps && pj >= min_p; ++j) {
|
||||
if (cudaGraphLaunch(cp ? step_exec_c_[j] : step_exec_[j], cs_) != cudaSuccess) {
|
||||
err = std::string("mtp draft step: ") + cudaGetErrorString(cudaGetLastError());
|
||||
return false;
|
||||
}
|
||||
(void) cudaStreamQuery(cs_);
|
||||
prefetch_ple(drafts[j - 1]);
|
||||
wait_step(j);
|
||||
drafts[j] = ((volatile int32_t*) h_out_)[j];
|
||||
pj = ((volatile float*) h_prob_)[j];
|
||||
if (probs) probs[j] = pj;
|
||||
++n;
|
||||
}
|
||||
prefetch_ple(drafts[n - 1]);
|
||||
if (cudaStreamSynchronize(cs_) != cudaSuccess) {
|
||||
err = std::string("mtp draft: ") + cudaGetErrorString(cudaGetLastError());
|
||||
return false;
|
||||
}
|
||||
drafts[j] = ((volatile int32_t*) h_out_)[j];
|
||||
pj = ((volatile float*) h_prob_)[j];
|
||||
if (probs) probs[j] = pj;
|
||||
++n;
|
||||
}
|
||||
for (int j = n; j < max_t_ - 1; ++j) { drafts[j] = 0; if (probs) probs[j] = 0.0f; }
|
||||
if (n_drafts) *n_drafts = n;
|
||||
|
||||
+245
-105
@@ -274,12 +274,16 @@ Verifier::~Verifier() {
|
||||
slot.compare_exchange_strong(me, nullptr);
|
||||
}
|
||||
if (cs_) cudaStreamSynchronize(cs_);
|
||||
if (sh_cs_) cudaStreamSynchronize(sh_cs_);
|
||||
for (auto& e : exec_)
|
||||
if (e) cudaGraphExecDestroy(e);
|
||||
if (commit_exec_) cudaGraphExecDestroy(commit_exec_);
|
||||
if (cs_) cudaStreamDestroy(cs_);
|
||||
if (sh_cs_) cudaStreamDestroy(sh_cs_);
|
||||
if (copy_) { cudaStreamSynchronize(copy_); cudaStreamDestroy(copy_); }
|
||||
if (commit_done_) cudaEventDestroy(commit_done_);
|
||||
if (ev_fork_) cudaEventDestroy(ev_fork_);
|
||||
if (ev_join_) cudaEventDestroy(ev_join_);
|
||||
if (arena_) cudaFree(arena_);
|
||||
void* hosts[] = {h_tok_, h_step_, h_pos_, h_commit_, h_ple_, h_out_, h_x_, h_ids_, h_w_, h_seq_, h_flag_, h_ymiss_,
|
||||
h_flagA_, h_plan_, h_flagB_};
|
||||
@@ -399,6 +403,7 @@ bool Verifier::init(const WeightTable& wt, const ModelGeometry& g, SessionState&
|
||||
inj_ = b.take<float>(T * HC); inj2_ = b.take<float>(T * HC);
|
||||
lo_ = b.take<float>(T * (uint64_t) g.hc_lr); rs_ = b.take<float>(T * HC); xn_ = b.take<float>(T * HC * N);
|
||||
xq_ = b.take<uint8_t>(strata::kernels::native_q8_1_bytes(max_in, (int) T));
|
||||
sh_xq_ = b.take<uint8_t>(strata::kernels::native_q8_1_bytes(max_in, (int) T));
|
||||
qkv_L_ = b.take<float>(nG * T * C); h_L_ = b.take<float>(nG * T * C);
|
||||
gate_L_ = b.take<float>(nG * T * HV); beta_L_ = b.take<float>(nG * T * HV);
|
||||
z_ = b.take<float>(T * ZV); y_ = b.take<float>(T * ZV); y_dummy_ = b.take<float>(T * ZV);
|
||||
@@ -465,31 +470,56 @@ bool Verifier::init(const WeightTable& wt, const ModelGeometry& g, SessionState&
|
||||
err = "verify: copy stream create failed";
|
||||
return false;
|
||||
}
|
||||
if (cudaStreamCreateWithFlags(&cs_, cudaStreamNonBlocking) != cudaSuccess) {
|
||||
if (cudaStreamCreateWithFlags(&cs_, cudaStreamNonBlocking) != cudaSuccess ||
|
||||
cudaStreamCreateWithFlags(&sh_cs_, cudaStreamNonBlocking) != cudaSuccess) {
|
||||
err = "verify: stream create failed";
|
||||
return false;
|
||||
}
|
||||
if (cudaEventCreateWithFlags(&commit_done_, cudaEventDisableTiming) != cudaSuccess) {
|
||||
if (cudaEventCreateWithFlags(&commit_done_, cudaEventDisableTiming) != cudaSuccess ||
|
||||
cudaEventCreateWithFlags(&ev_fork_, cudaEventDisableTiming) != cudaSuccess ||
|
||||
cudaEventCreateWithFlags(&ev_join_, cudaEventDisableTiming) != cudaSuccess) {
|
||||
err = "verify: event create failed";
|
||||
return false;
|
||||
}
|
||||
// Check whether 100% of experts across [lb_, le_) are resident in this stage's VRAM cache.
|
||||
// When true, every layer plans on device and writes directly into parts_ without any CPU doorbells,
|
||||
// wait_flag_ge spins, PCIe empty launches, or moe_hit_add copies.
|
||||
all_resident_ = false;
|
||||
if (hits.h_res != nullptr && hits.d_res != nullptr && hits.cache_base != nullptr) {
|
||||
const char* v_ar = std::getenv("STRATA_VERIFY_ALL_RESIDENT");
|
||||
if (v_ar == nullptr || std::atoi(v_ar) != 0) {
|
||||
bool all_ok = true;
|
||||
for (int64_t l = lb_; l < le_ && all_ok; ++l) {
|
||||
for (int64_t e = 0; e < g.n_expert; ++e) {
|
||||
if (hits.h_res[l * g.n_expert + e] < 0) {
|
||||
all_ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
all_resident_ = all_ok;
|
||||
}
|
||||
}
|
||||
// E-6: a layer whose routed experts are all resident is planned on the device (STRATA_VERIFY_DEVICE_PLAN=1: on;
|
||||
// exact, but neutral on RIBPC 1-2 GPUs: off by default)
|
||||
{
|
||||
const char* v = std::getenv("STRATA_VERIFY_DEVICE_PLAN");
|
||||
device_plan_ = v != nullptr && std::atoi(v) != 0;
|
||||
device_plan_ = !all_resident_ && (v != nullptr && std::atoi(v) != 0);
|
||||
}
|
||||
if (device_plan_) {
|
||||
bool ok2 = cudaMalloc((void**) &skip_, 64) == cudaSuccess && cudaMemset(skip_, 0, 64) == cudaSuccess;
|
||||
if (all_resident_ || device_plan_) {
|
||||
bool ok2 = true;
|
||||
if (device_plan_)
|
||||
ok2 = cudaMalloc((void**) &skip_, 64) == cudaSuccess && cudaMemset(skip_, 0, 64) == cudaSuccess;
|
||||
if (ok2 && hits.slot_off != nullptr && hits.n_slots > 0) {
|
||||
ok2 = cudaMalloc((void**) &slot_off_d_, (size_t) hits.n_slots * sizeof(unsigned long long)) == cudaSuccess &&
|
||||
cudaMemcpy(slot_off_d_, hits.slot_off, (size_t) hits.n_slots * sizeof(unsigned long long),
|
||||
cudaMemcpyHostToDevice) == cudaSuccess;
|
||||
}
|
||||
if (!ok2) { cudaGetLastError(); device_plan_ = false; }
|
||||
if (!ok2) { cudaGetLastError(); all_resident_ = false; device_plan_ = false; }
|
||||
}
|
||||
std::fprintf(stderr, "strata verify: window up to %d tokens, %.1f MiB of device buffers\n", max_t,
|
||||
(double) count.used / 1048576.0);
|
||||
std::fprintf(stderr, "strata verify: window up to %d tokens, %.1f MiB of device buffers%s\n", max_t,
|
||||
(double) count.used / 1048576.0,
|
||||
all_resident_ ? " (100% VRAM resident: zero-doorbell graph)" : "");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -537,17 +567,14 @@ bool Verifier::record_window(int T, cudaStream_t cs, std::string& err) {
|
||||
// per-ROW positions of the K rows [t][NKV] and the indexer query rows [t][IQ] (for batched RoPE)
|
||||
const int32_t* pos_k = pos_ + MT * NH;
|
||||
const int32_t* pos_i = pos_ + MT * (NH + NKV);
|
||||
if (ple_on) copy_from_mapped(ple_, m_ple_, (int64_t) T * N, cs);
|
||||
|
||||
// ---- the embeddings, broadcast to the hc streams - or, in a later stage of a layer split, the previous stage's
|
||||
// residual, pending write and inject (see set_stage)
|
||||
const int64_t HB = Verifier::handoff_floats(g);
|
||||
if (lb_ > 0) {
|
||||
for (int t = 0; t < T; ++t) {
|
||||
copy_from_mapped(Rt(t), hand_in_ + (size_t) t * HB, HC * N, cs);
|
||||
copy_from_mapped(bo_ + (size_t) t * N, hand_in_ + (size_t) t * HB + HC * N, N, cs);
|
||||
copy_from_mapped(inj2_ + (size_t) t * HC, hand_in_ + (size_t) t * HB + HC * N + N, HC, cs);
|
||||
}
|
||||
copy_from_mapped(R_, hand_in_, (int64_t) T * HC * N, cs);
|
||||
copy_from_mapped(bo_, hand_in_ + (size_t) T * HC * N, (int64_t) T * N, cs);
|
||||
copy_from_mapped(inj2_, hand_in_ + (size_t) T * (HC + 1) * N, (int64_t) T * HC, cs);
|
||||
} else if (const NativeEmbed* ne = native_embed()) { // plan v0.3 P6: the GGUF-form table
|
||||
ne->gather_dev(tok_, T, emb_, cs);
|
||||
broadcast_streams(emb_, R_, N, (int) HC, T, cs);
|
||||
@@ -595,14 +622,37 @@ bool Verifier::record_window(int T, cudaStream_t cs, std::string& err) {
|
||||
// already applied it)
|
||||
bool pending = l > 0 && !cvec().covers(l - 1);
|
||||
if (l == 1 && ple_on) {
|
||||
if (grp == 0) {
|
||||
if (all_resident_) wait_flag_ge(m_flag_, 1, cs);
|
||||
copy_from_mapped(ple_, m_ple_, (int64_t) T * N, cs);
|
||||
}
|
||||
float* normalized = (float*) ((uint8_t*) ss.ple.scratch + ple_block_scratch_bytes());
|
||||
const bool ple_batch_kv = dec_batch && n > 1 && ss.ple.w.key_bf16 != nullptr &&
|
||||
ss.ple.w.value_bf16 != nullptr && ple_native_bf16_enabled() &&
|
||||
ple_native_postops_enabled();
|
||||
if (ple_batch_kv) {
|
||||
try {
|
||||
bf16_gemv_fp32_mmvf_multi(ple_ + (size_t) tb * N, N, ss.ple.w.key_bf16,
|
||||
xn_ + (size_t) tb * HC * N, HC * N, N, HC * N, n, cs);
|
||||
bf16_gemv_fp32_mmvf_multi(ple_ + (size_t) tb * N, N, ss.ple.w.value_bf16,
|
||||
z_ + (size_t) tb * N, N, N, N, n, cs);
|
||||
} catch (const std::exception& e) {
|
||||
err = std::string("verify PLE multi: ") + e.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (int t = tb; t < te; ++t) {
|
||||
gr_write(Rt(t), bo_ + t * N, inj2_ + t * HC, gs, Rt(t), cs);
|
||||
PleOut po;
|
||||
po.normalized = normalized;
|
||||
po.result = Rt(t);
|
||||
try {
|
||||
ple_block(ple_ + t * N, Rt(t), ss.ple.hist, ss.ple.w, po, ss.ple.scratch, cs);
|
||||
if (ple_batch_kv) {
|
||||
ple_block_projected(xn_ + (size_t) t * HC * N, z_ + (size_t) t * N, Rt(t), ss.ple.hist,
|
||||
ss.ple.w, po, ss.ple.scratch, cs);
|
||||
} else {
|
||||
ple_block(ple_ + t * N, Rt(t), ss.ple.hist, ss.ple.w, po, ss.ple.scratch, cs);
|
||||
}
|
||||
ple_history_advance(ss.ple.hist, normalized, cs);
|
||||
} catch (const std::exception& e) {
|
||||
err = std::string("verify PLE: ") + e.what();
|
||||
@@ -792,6 +842,12 @@ bool Verifier::record_window(int T, cudaStream_t cs, std::string& err) {
|
||||
}
|
||||
stamp(l, 16, grp);
|
||||
gr_read_group(1, true, inj_, inj2_);
|
||||
const bool sh_fork = !prof_on_ && sh_cs_ != nullptr && ev_fork_ != nullptr && ev_join_ != nullptr;
|
||||
cudaStream_t sh_stream = sh_fork ? sh_cs_ : cs;
|
||||
if (sh_fork) {
|
||||
cudaEventRecord(ev_fork_, cs);
|
||||
cudaStreamWaitEvent(sh_cs_, ev_fork_, 0);
|
||||
}
|
||||
// the window's rows routed in 2 launches (one router GEMV reading the weight once, one
|
||||
// top-10) instead of 2 per token; every row's arithmetic is the single-token call's (STRATA_DEC_BATCH=0: old)
|
||||
const WeightRef* w_router = v.get("ffn_gate_inp.weight");
|
||||
@@ -807,19 +863,28 @@ bool Verifier::record_window(int T, cudaStream_t cs, std::string& err) {
|
||||
mb.logits = logits_ + t * NE; mb.ids = ids_ + t * K; mb.weights = w_ + t * K;
|
||||
if (!moe_route(wt, g, l, K, mb, mixed_ + t * N, cs, err, nullptr)) return false;
|
||||
}
|
||||
if (device_plan_) // E-6: every routed expert resident: this group's plan without the host
|
||||
if (all_resident_) {
|
||||
resident_plan(ids_ + tb * K, n * (int) K, (int) K, hits_.d_res + l * g.n_expert, (int) g.n_expert,
|
||||
hits_.cache_base, slot_off_d_, (long long) hits_.blob,
|
||||
plan_ + (size_t) grp * (size_t) (plan_i32_ + 16), (long long) max_t_ * K, skip_ + grp,
|
||||
(uint32_t) ((l - lb_) * G + grp + 1), cs);
|
||||
plan_ + (size_t) grp * (size_t) (plan_i32_ + 16), (long long) max_t_ * K, nullptr, 0, cs);
|
||||
} else {
|
||||
if (device_plan_) // E-6: every routed expert resident: this group's plan without the host
|
||||
resident_plan(ids_ + tb * K, n * (int) K, (int) K, hits_.d_res + l * g.n_expert, (int) g.n_expert,
|
||||
hits_.cache_base, slot_off_d_, (long long) hits_.blob,
|
||||
plan_ + (size_t) grp * (size_t) (plan_i32_ + 16), (long long) max_t_ * K, skip_ + grp,
|
||||
(uint32_t) ((l - lb_) * G + grp + 1), cs);
|
||||
#if defined(STRATA_USE_HIP)
|
||||
if (g_doorbell_store) // #649 A/B: the step's ring stored, not incremented over PCIe
|
||||
doorbell_publish_value(xm, ids_ + tb * K, w_ + tb * K, (int64_t) n * N, (int64_t) n * K, m_x_ + tb * N,
|
||||
m_ids_ + tb * K, m_w_ + tb * K, m_seq_, (uint32_t) ((l - lb_) * G + grp + 1), cs);
|
||||
else
|
||||
if (g_doorbell_store) // #649 A/B: the step's ring stored, not incremented over PCIe
|
||||
doorbell_publish_value(xm, ids_ + tb * K, w_ + tb * K, (int64_t) n * N, (int64_t) n * K, m_x_ + tb * N,
|
||||
m_ids_ + tb * K, m_w_ + tb * K, m_seq_, (uint32_t) ((l - lb_) * G + grp + 1), cs);
|
||||
else
|
||||
#endif
|
||||
doorbell_publish(xm, ids_ + tb * K, w_ + tb * K, (int64_t) n * N, (int64_t) n * K, m_x_ + tb * N,
|
||||
m_ids_ + tb * K, m_w_ + tb * K, m_seq_, cs);
|
||||
{
|
||||
const int32_t* layer_res = hits_.d_res != nullptr ? (hits_.d_res + l * g.n_expert) : nullptr;
|
||||
doorbell_publish_res(xm, ids_ + tb * K, layer_res, (int) g.n_expert, (int64_t) n * N, (int64_t) n * K,
|
||||
m_x_ + tb * N, m_ids_ + tb * K, m_seq_, cs);
|
||||
}
|
||||
}
|
||||
stamp(l, 17, grp);
|
||||
{
|
||||
const WeightRef *wgi = need(v, "ffn_gate_inp_shexp.weight", err), *wsg = need(v, "ffn_gate_shexp.weight", err),
|
||||
@@ -832,16 +897,19 @@ bool Verifier::record_window(int T, cudaStream_t cs, std::string& err) {
|
||||
nsw.gate_type = wsg->native_type; nsw.gate_data = wsg->native_data;
|
||||
nsw.up_type = wsu->native_type; nsw.up_data = wsu->native_data;
|
||||
nsw.down_type = wsd->native_type; nsw.down_data = wsd->native_data;
|
||||
nsw.q8_1 = xq_;
|
||||
if (dec_batch) f32_to_bf16_bulk(mixed_ + tb * N, sh_bf16_ + tb * N, (int64_t) n * N, cs); // contiguous rows
|
||||
else for (int t = tb; t < te; ++t) f32_to_bf16_bulk(mixed_ + t * N, sh_bf16_ + t * N, N, cs);
|
||||
nsw.q8_1 = sh_fork ? sh_xq_ : xq_;
|
||||
if (!shared_expert_native_bf16_enabled()) {
|
||||
if (dec_batch) f32_to_bf16_bulk(mixed_ + tb * N, sh_bf16_ + tb * N, (int64_t) n * N, sh_stream); // contiguous rows
|
||||
else for (int t = tb; t < te; ++t) f32_to_bf16_bulk(mixed_ + t * N, sh_bf16_ + t * N, N, sh_stream);
|
||||
}
|
||||
try {
|
||||
shared_expert_multi(n, xm, sh_bf16_ + tb * N, nsw, (const uint16_t*) wgi->data, sh_gate_ + (size_t) tb * g.n_ff,
|
||||
sh_up_ + (size_t) tb * g.n_ff, sh_g_ + tb, shared_ + tb * N, N, g.n_ff, cs);
|
||||
sh_up_ + (size_t) tb * g.n_ff, sh_g_ + tb, shared_ + tb * N, N, g.n_ff, sh_stream);
|
||||
} catch (const std::exception& e) {
|
||||
err = std::string("verify shared expert: ") + e.what();
|
||||
return false;
|
||||
}
|
||||
if (sh_fork) cudaEventRecord(ev_join_, sh_cs_);
|
||||
}
|
||||
if (strata::kernels::cpu::expert_layout().native)
|
||||
quantize_q8_1_rows(xm, n, N, nat_xq_ + (size_t) tb * (N / 32) * 36, cs);
|
||||
@@ -852,19 +920,13 @@ bool Verifier::record_window(int T, cudaStream_t cs, std::string& err) {
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------- post(l, group): experts, combine
|
||||
const bool fuse_head_gr = (le_ == g.n_layers) && (head_ != nullptr && head_->loaded()) &&
|
||||
!cvec().covers(g.n_layers - 1);
|
||||
auto post = [&](int64_t l, int grp) -> bool {
|
||||
const int tb = tb_[grp], te = te_[grp], n = te - tb;
|
||||
const uint32_t ring = (uint32_t) ((l - lb_) * G + grp + 1);
|
||||
const int64_t cap = (int64_t) n * K, capx = (int64_t) max_t_ * K;
|
||||
int32_t* pl = plan_ + (size_t) grp * (size_t) (plan_i32_ + 16);
|
||||
if (device_plan_) { // E-6: skipped when the device planned this group (all its experts resident)
|
||||
wait_flag_ge_or(m_flagA_, ring, skip_ + grp, cs);
|
||||
copy_i32_from_mapped_unless(pl, m_plan_ + (size_t) grp * (size_t) plan_i32_, plan_i32_, skip_ + grp, ring, cs);
|
||||
} else {
|
||||
wait_flag_ge(m_flagA_, ring, cs); // the pool published this group's GPU plan
|
||||
copy_i32_from_mapped(pl, m_plan_ + (size_t) grp * (size_t) plan_i32_, plan_i32_, cs);
|
||||
}
|
||||
stamp(l, 19, grp);
|
||||
const int32_t* p_counts = pl;
|
||||
const int32_t* p_start = pl + 4;
|
||||
const int32_t* p_dst = p_start + capx + 1;
|
||||
@@ -874,50 +936,68 @@ bool Verifier::record_window(int T, cudaStream_t cs, std::string& err) {
|
||||
const unsigned long long* p_ptr2 = p_ptr + capx;
|
||||
const int32_t* p_start2 = pl + ptr_off + 4 * capx;
|
||||
float* hit_out = hit_out_ + (size_t) tb * K * N;
|
||||
float* parts_out = parts_ + (size_t) tb * K * N;
|
||||
const auto& lay = strata::kernels::cpu::expert_layout();
|
||||
// plan v0.3 P6: the VRAM groups now; the PCIe groups once the copy engine has landed them in staging.
|
||||
// `gy`: the native launch's groups side by side (0: cap, one block row per possible group).
|
||||
auto grouped = [&](const unsigned long long* gp, const int32_t* gs, const int32_t* gn, int64_t gy) {
|
||||
auto grouped = [&](const unsigned long long* gp, const int32_t* gs, const int32_t* gn, int64_t gy, float* dst_buf) {
|
||||
if (lay.native) {
|
||||
// the layer's GGUF formats (i-quant gate/up, Q2_0 / IQ4_NL down)
|
||||
const auto& f = lay.fmt[(size_t) l];
|
||||
const NativeExpertLayout L = native_expert_layout(f.gu_type, f.d_type, f.n_embd, f.n_ff);
|
||||
native_expert_grouped(L, gp, gs, gn, p_dst, p_tok, cap, cap,
|
||||
nat_xq_ + (size_t) tb * (N / 32) * 36, hit_scratch_, hit_out, cs, gy);
|
||||
nat_xq_ + (size_t) tb * (N / 32) * 36, hit_scratch_, dst_buf, cs, gy);
|
||||
} else {
|
||||
moe_grouped_s2(gp, gs, gn, p_dst, p_tok, cap, cap, hit_xq_ + (size_t) tb * (N / 32) * 34,
|
||||
hit_xs_ + (size_t) tb * (N / 32), hit_scratch_, hit_out, cs);
|
||||
hit_xs_ + (size_t) tb * (N / 32), hit_scratch_, dst_buf, cs);
|
||||
}
|
||||
};
|
||||
grouped(p_ptr, p_start, p_counts, 0);
|
||||
stamp(l, 20, grp);
|
||||
if (device_plan_) wait_flag_ge_or(m_flagB_, ring, skip_ + grp, cs);
|
||||
else wait_flag_ge(m_flagB_, ring, cs); // the PCIe share is in staging (DMA) or mapped
|
||||
if (sink_.pcie_mode == 2) { // stage it with a copy kernel, then point at staging
|
||||
const int64_t per = G == 2 ? kStagingBlobs / 2 : kStagingBlobs;
|
||||
uint8_t* stage = staging_ + (size_t) (grp * per) * lay.max_blob;
|
||||
fetch_blobs(p_ptr2, p_counts + 2, stage, (int64_t) lay.blob_bytes(l), (int) per, cs);
|
||||
rebase_ptrs((unsigned long long*) p_ptr2, p_counts + 2, stage, (int64_t) lay.blob_bytes(l), cs);
|
||||
}
|
||||
stamp(l, 21, grp);
|
||||
// the PCIe share is pcie_frac of the misses: a few groups when the cache is cold, usually none (always none at
|
||||
// pcie_frac 0), so its launch is kPcieGroupRows block rows striding over the groups, not cap of them
|
||||
grouped(p_ptr2, p_start2, p_counts + 2, kPcieGroupRows);
|
||||
stamp(l, 22, grp);
|
||||
if (device_plan_) { // no CPU share when the device planned the group: its rows are zeros
|
||||
wait_flag_ge_or(m_flag_, ring, skip_ + grp, cs);
|
||||
copy_or_zero_from_mapped(parts_ + (size_t) tb * K * N, m_ymiss_ + (size_t) tb * K * N, (long long) n * K * N,
|
||||
skip_ + grp, ring, cs);
|
||||
if (all_resident_) {
|
||||
stamp(l, 19, grp);
|
||||
grouped(p_ptr, p_start, p_counts, 0, parts_out);
|
||||
stamp(l, 20, grp);
|
||||
} else {
|
||||
wait_flag_ge(m_flag_, ring, cs); // the CPU's share is in the mapped rows
|
||||
stamp(l, 23, grp);
|
||||
if (dec_batch) // only the CPU rows cross PCIe (p_dst[0, counts[1]) = the GPU's own rows)
|
||||
copy_rows_from_mapped(parts_ + (size_t) tb * K * N, m_ymiss_ + (size_t) tb * K * N, (int64_t) n * K, N,
|
||||
p_dst, p_counts + 1, cs);
|
||||
else
|
||||
copy_from_mapped(parts_ + (size_t) tb * K * N, m_ymiss_ + (size_t) tb * K * N, (int64_t) n * K * N, cs);
|
||||
if (device_plan_) { // E-6: skipped when the device planned this group (all its experts resident)
|
||||
wait_flag_ge_or(m_flagA_, ring, skip_ + grp, cs);
|
||||
copy_i32_from_mapped_unless(pl, m_plan_ + (size_t) grp * (size_t) plan_i32_, plan_i32_, skip_ + grp, ring, cs);
|
||||
} else {
|
||||
wait_flag_ge(m_flagA_, ring, cs); // the pool published this group's GPU plan
|
||||
copy_i32_from_mapped(pl, m_plan_ + (size_t) grp * (size_t) plan_i32_, plan_i32_, cs);
|
||||
}
|
||||
stamp(l, 19, grp);
|
||||
grouped(p_ptr, p_start, p_counts, 0, hit_out);
|
||||
stamp(l, 20, grp);
|
||||
if (device_plan_) wait_flag_ge_or(m_flagB_, ring, skip_ + grp, cs);
|
||||
else wait_flag_ge(m_flagB_, ring, cs); // the PCIe share is in staging (DMA) or mapped
|
||||
if (sink_.pcie_mode == 2) { // stage it with a copy kernel, then point at staging
|
||||
const int64_t per = G == 2 ? kStagingBlobs / 2 : kStagingBlobs;
|
||||
uint8_t* stage = staging_ + (size_t) (grp * per) * lay.max_blob;
|
||||
fetch_blobs(p_ptr2, p_counts + 2, stage, (int64_t) lay.blob_bytes(l), (int) per, cs);
|
||||
rebase_ptrs((unsigned long long*) p_ptr2, p_counts + 2, stage, (int64_t) lay.blob_bytes(l), cs);
|
||||
}
|
||||
stamp(l, 21, grp);
|
||||
// the PCIe share is pcie_frac of the misses: a few groups when the cache is cold, usually none (always none at
|
||||
// pcie_frac 0), so its launch is kPcieGroupRows block rows striding over the groups, not cap of them
|
||||
grouped(p_ptr2, p_start2, p_counts + 2, kPcieGroupRows, hit_out);
|
||||
stamp(l, 22, grp);
|
||||
if (device_plan_) { // no CPU share when the device planned the group: its rows are zeros
|
||||
wait_flag_ge_or(m_flag_, ring, skip_ + grp, cs);
|
||||
copy_or_zero_from_mapped(parts_out, m_ymiss_ + (size_t) tb * K * N, (long long) n * K * N,
|
||||
skip_ + grp, ring, cs);
|
||||
} else {
|
||||
wait_flag_ge(m_flag_, ring, cs); // the CPU's share is in the mapped rows
|
||||
stamp(l, 23, grp);
|
||||
if (dec_batch) // only the CPU rows cross PCIe (p_dst[0, counts[1]) = the GPU's own rows)
|
||||
copy_rows_from_mapped(parts_out, m_ymiss_ + (size_t) tb * K * N, (int64_t) n * K, N,
|
||||
p_dst, p_counts + 1, cs);
|
||||
else
|
||||
copy_from_mapped(parts_out, m_ymiss_ + (size_t) tb * K * N, (int64_t) n * K * N, cs);
|
||||
}
|
||||
moe_hit_add(parts_out, hit_out, p_dst, p_counts + 1, cap, N, cs);
|
||||
}
|
||||
if (!prof_on_ && sh_cs_ != nullptr && ev_fork_ != nullptr && ev_join_ != nullptr) {
|
||||
cudaStreamWaitEvent(cs, ev_join_, 0);
|
||||
}
|
||||
moe_hit_add(parts_ + (size_t) tb * K * N, hit_out, p_dst, p_counts + 1, cap, N, cs);
|
||||
if (dec_batch && n > 1 && native_moe_combine_enabled()) { // one launch for the window's rows
|
||||
try {
|
||||
native_moe_combine_multi(parts_ + (size_t) tb * K * N, w_ + tb * K, shared_ + tb * N, bo_ + tb * N, N, K, n, cs);
|
||||
@@ -930,8 +1010,10 @@ bool Verifier::record_window(int T, cudaStream_t cs, std::string& err) {
|
||||
}
|
||||
stamp(l, 24, grp);
|
||||
if (l == g.n_layers - 1) {
|
||||
for (int t = tb; t < te; ++t) gr_write(Rt(t), bo_ + t * N, inj2_ + t * HC, gs, Rt(t), cs);
|
||||
if (cvec().covers(l)) cvec_apply(Rt(tb), l, n, HC * N, nullptr, 0, nullptr, 0, false, cs);
|
||||
if (!fuse_head_gr) {
|
||||
for (int t = tb; t < te; ++t) gr_write(Rt(t), bo_ + t * N, inj2_ + t * HC, gs, Rt(t), cs);
|
||||
if (cvec().covers(l)) cvec_apply(Rt(tb), l, n, HC * N, nullptr, 0, nullptr, 0, false, cs);
|
||||
}
|
||||
} else if (cvec().covers(l)) {
|
||||
cvec_apply(Rt(tb), l, n, HC * N, bo_ + tb * N, N, inj2_ + tb * HC, HC, true, cs);
|
||||
}
|
||||
@@ -946,11 +1028,9 @@ bool Verifier::record_window(int T, cudaStream_t cs, std::string& err) {
|
||||
if (l + 1 < le_ && !pre(l + 1, grp)) return false;
|
||||
}
|
||||
if (le_ < g.n_layers) { // a layer split's earlier stage: hand the residual on, no head
|
||||
for (int t = 0; t < T; ++t) {
|
||||
copy_from_mapped(hand_out_ + (size_t) t * HB, Rt(t), HC * N, cs);
|
||||
copy_from_mapped(hand_out_ + (size_t) t * HB + HC * N, bo_ + (size_t) t * N, N, cs);
|
||||
copy_from_mapped(hand_out_ + (size_t) t * HB + HC * N + N, inj2_ + (size_t) t * HC, HC, cs);
|
||||
}
|
||||
copy_from_mapped(hand_out_, R_, (int64_t) T * HC * N, cs);
|
||||
copy_from_mapped(hand_out_ + (size_t) T * HC * N, bo_, (int64_t) T * N, cs);
|
||||
copy_from_mapped(hand_out_ + (size_t) T * (HC + 1) * N, inj2_, (int64_t) T * HC, cs);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -960,14 +1040,33 @@ bool Verifier::record_window(int T, cudaStream_t cs, std::string& err) {
|
||||
const WeightRef *hn = wt.find("output_hc_norm.weight"), *hd = wt.find("output_hc_down.weight"),
|
||||
*hu = wt.find("output_hc_up.weight");
|
||||
if (!hn || !hd || !hu) { err = "verify: an output_hc_* weight is missing"; return false; }
|
||||
for (int t = 0; t < T; ++t) {
|
||||
BlockBuffers bb = ss.block;
|
||||
bb.R = Rt(t);
|
||||
bb.mixed = head_mixed_ + t * N;
|
||||
if (head_ != nullptr && head_->loaded()) {
|
||||
if (!lm_head_mix(wt, g, bb, cs, err)) return false;
|
||||
} else if (!lm_head(wt, g, bb, head_logits_ + (size_t) t * n_vocab_, cs, err)) {
|
||||
return false;
|
||||
if (fuse_head_gr) {
|
||||
FusedGrArgs fa[kFusedGrMaxT];
|
||||
for (int t = 0; t < T; ++t) {
|
||||
FusedGrArgs& a = fa[t];
|
||||
a.R = Rt(t); a.R_out = Rt(t); a.apply = true;
|
||||
a.bo_prev = bo_ + t * N; a.inj_prev = inj2_ + t * HC;
|
||||
a.w_norm = (const float*) hn->data;
|
||||
a.w_down = (const uint16_t*) hd->data;
|
||||
a.w_up = (const uint16_t*) hu->data;
|
||||
a.w_inject = nullptr;
|
||||
a.eps = EPS;
|
||||
a.lo = lo_ + t * g.hc_lr;
|
||||
a.rs = rs_ + t * HC;
|
||||
a.inject_out = head_inj_;
|
||||
a.mixed = head_mixed_ + t * N;
|
||||
}
|
||||
fused_gr_read_multi(fa, T, xn_, cs);
|
||||
} else {
|
||||
for (int t = 0; t < T; ++t) {
|
||||
BlockBuffers bb = ss.block;
|
||||
bb.R = Rt(t);
|
||||
bb.mixed = head_mixed_ + t * N;
|
||||
if (head_ != nullptr && head_->loaded()) {
|
||||
if (!lm_head_mix(wt, g, bb, cs, err)) return false;
|
||||
} else if (!lm_head(wt, g, bb, head_logits_ + (size_t) t * n_vocab_, cs, err)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (head_ != nullptr && head_->loaded()) {
|
||||
@@ -1111,7 +1210,7 @@ bool Verifier::capture_commit(std::string& err) {
|
||||
gdn_conv_commit(conv, qkv, (int) C, commit_, cs_);
|
||||
gdn_step_norm_multi(state, h_L_ + (size_t) gdn_index * MT * C, (int) C, gate_L_ + (size_t) gdn_index * MT * HV,
|
||||
beta_L_ + (size_t) gdn_index * MT * HV, z_, (const float*) wnm->data, EPS, y_dummy_,
|
||||
(int) g.ssm_k_heads, (int) HV, (int) MT, commit_, cs_);
|
||||
(int) g.ssm_k_heads, (int) HV, (int) MT, commit_, cs_, (int) MT);
|
||||
++gdn_index;
|
||||
} else {
|
||||
const QsaState& st = ss.qsa_states[qsa_index];
|
||||
@@ -1146,6 +1245,31 @@ bool Verifier::capture_commit(std::string& err) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void Verifier::stage_inputs(int T, const int32_t* tokens, int64_t pos0) {
|
||||
using namespace strata::kernels;
|
||||
const ModelGeometry& g = *g_;
|
||||
const QsaShapes s = shapes_of(g);
|
||||
int32_t* const pk = h_pos_ + (size_t) max_t_ * g.n_head;
|
||||
int32_t* const pi = pk + (size_t) max_t_ * g.n_head_kv;
|
||||
for (int t = 0; t < T; ++t) {
|
||||
h_tok_[t] = tokens[t];
|
||||
qsa_step_fill(h_step_ + t * kStepCount, pos0 + t, s);
|
||||
const int32_t pos_t = (int32_t) (pos0 + t);
|
||||
for (int64_t h = 0; h < g.n_head; ++h) h_pos_[t * g.n_head + h] = pos_t;
|
||||
for (int64_t h = 0; h < g.n_head_kv; ++h) pk[t * g.n_head_kv + h] = pos_t;
|
||||
for (int64_t h = 0; h < g.idx_q_heads; ++h) pi[t * g.idx_q_heads + h] = pos_t;
|
||||
}
|
||||
*(volatile uint32_t*) h_seq_ = 0;
|
||||
*(volatile uint32_t*) h_flag_ = 0;
|
||||
*(volatile uint32_t*) h_flagA_ = 0;
|
||||
*(volatile uint32_t*) h_flagB_ = 0;
|
||||
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||
last_t_ = T;
|
||||
last_pos0_ = pos0;
|
||||
for (int t = 0; t < T; ++t) last_tokens_[t] = tokens[t];
|
||||
staged_ = true;
|
||||
}
|
||||
|
||||
bool Verifier::run(int T, const int32_t* tokens, int64_t pos0, PoolMultiFn pool, void* user, int32_t* out,
|
||||
std::string& err) {
|
||||
using namespace strata::kernels;
|
||||
@@ -1158,36 +1282,22 @@ bool Verifier::run(int T, const int32_t* tokens, int64_t pos0, PoolMultiFn pool,
|
||||
if (!capture(T, err) || !capture_commit(err)) return false;
|
||||
VDBG("captured; staging\n");
|
||||
const Clock::time_point t0 = Clock::now();
|
||||
const QsaShapes s = shapes_of(g);
|
||||
for (int t = 0; t < T; ++t) {
|
||||
h_tok_[t] = tokens[t];
|
||||
qsa_step_fill(h_step_ + t * kStepCount, pos0 + t, s);
|
||||
for (int64_t h = 0; h < g.n_head; ++h) h_pos_[t * g.n_head + h] = (int32_t) (pos0 + t);
|
||||
int32_t* pk = h_pos_ + (size_t) max_t_ * g.n_head;
|
||||
int32_t* pi = pk + (size_t) max_t_ * g.n_head_kv;
|
||||
for (int64_t h = 0; h < g.n_head_kv; ++h) pk[t * g.n_head_kv + h] = (int32_t) (pos0 + t);
|
||||
for (int64_t h = 0; h < g.idx_q_heads; ++h) pi[t * g.idx_q_heads + h] = (int32_t) (pos0 + t);
|
||||
}
|
||||
if (ss.ple.ready() && ple_stage()) {
|
||||
uint32_t rows[kVerifyMaxT * PLE_N_HEADS];
|
||||
if (!staged_) stage_inputs(T, tokens, pos0);
|
||||
staged_ = false;
|
||||
const bool do_ple = ss.ple.ready() && ple_stage();
|
||||
uint32_t ple_rows[kVerifyMaxT * PLE_N_HEADS];
|
||||
if (do_ple) {
|
||||
int32_t prev[2] = {ss.ple_prev[0], ss.ple_prev[1]};
|
||||
for (int t = 0; t < T; ++t) {
|
||||
ngram_rows(&tokens[t], prev, 1, ss.ple.consts, rows + t * PLE_N_HEADS);
|
||||
ngram_rows(&tokens[t], prev, 1, ss.ple.consts, ple_rows + t * PLE_N_HEADS);
|
||||
prev[0] = prev[1];
|
||||
prev[1] = tokens[t];
|
||||
ss.ple.table->prefetch_rows(ple_rows + t * PLE_N_HEADS);
|
||||
}
|
||||
if (!ss.ple.table->gather_batch(rows, (size_t) T, h_ple_, err)) return false;
|
||||
}
|
||||
*(volatile uint32_t*) h_seq_ = 0;
|
||||
*(volatile uint32_t*) h_flag_ = 0;
|
||||
*(volatile uint32_t*) h_flagA_ = 0;
|
||||
*(volatile uint32_t*) h_flagB_ = 0;
|
||||
if (trace_h_ != nullptr) std::memset(trace_h_, 0, trace_n_ * 8); // #649: this window's breadcrumbs only
|
||||
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||
trace_ev("WINDOW", -1, -1, pos0 * 16 + T);
|
||||
last_t_ = T;
|
||||
last_pos0_ = pos0;
|
||||
for (int t = 0; t < T; ++t) last_tokens_[t] = tokens[t];
|
||||
ms_host += ms_since(t0);
|
||||
VDBG("staged; launching\n");
|
||||
const cudaError_t le = cudaGraphLaunch(exec_[T], cs_);
|
||||
@@ -1201,6 +1311,18 @@ bool Verifier::run(int T, const int32_t* tokens, int64_t pos0, PoolMultiFn pool,
|
||||
const int gtb[2] = {0, (T + 1) / 2}, gte[2] = {G == 2 ? (T + 1) / 2 : T, T};
|
||||
const int64_t steps = (le_ - lb_) * G;
|
||||
const bool test_stall = g_test_stall > 0 && windows + 1 == g_test_stall; // #267 test hook (off: false)
|
||||
if (all_resident_ && !test_stall) {
|
||||
if (do_ple) {
|
||||
const Clock::time_point tp = Clock::now();
|
||||
if (!ss.ple.table->gather_batch(ple_rows, (size_t) T, h_ple_, err)) return false;
|
||||
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||
_mm_sfence();
|
||||
*flag = 1;
|
||||
ms_host += ms_since(tp);
|
||||
} else {
|
||||
*flag = 1;
|
||||
}
|
||||
} else
|
||||
for (int64_t k = 0; k < steps; ++k) {
|
||||
const int64_t l = lb_ + k / G;
|
||||
const int grp = (int) (k % G);
|
||||
@@ -1264,10 +1386,22 @@ bool Verifier::run(int T, const int32_t* tokens, int64_t pos0, PoolMultiFn pool,
|
||||
*(volatile uint32_t*) h_flagA_ = want;
|
||||
raise_flag(h_flagB_, want);
|
||||
}
|
||||
// Layer 1's pre(1, 0) copies h_ple_ -> ple_ after Layer 0's wait_flag_ge(m_flag_, 1).
|
||||
// By collecting PLE here at k == 0 (after publishing flagA/flagB for Layer 0 so the GPU can run
|
||||
// Layer 0's VRAM experts, and before raising *flag = 1), the NVMe PLE reads overlap with both
|
||||
// MtpDrafter::draft and Layer 0's attention + router + expert execution!
|
||||
if (k == 0 && do_ple) {
|
||||
const Clock::time_point tp = Clock::now();
|
||||
if (!ss.ple.table->gather_batch(ple_rows, (size_t) T, h_ple_, err)) return false;
|
||||
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||
_mm_sfence();
|
||||
ms_host += ms_since(tp);
|
||||
}
|
||||
if (!(test_stall && k + 1 == steps)) *flag = want;
|
||||
ms_wait += std::chrono::duration<double, std::milli>(b - a).count();
|
||||
ms_pool += ms_since(b);
|
||||
}
|
||||
if (next_ != nullptr) next_->stage_inputs(T, tokens, pos0);
|
||||
progress_at("verify window: waiting for the GPU to finish the window (flags A/B/M raised)", (int64_t) T);
|
||||
// #267: a window the GPU never finishes (a spin kernel that never sees its flag) holds the host here; the stall
|
||||
// watchdog then releases every verifier's GPU waits (release_live_verifiers) before it ends the engine, so no
|
||||
@@ -1278,8 +1412,12 @@ bool Verifier::run(int T, const int32_t* tokens, int64_t pos0, PoolMultiFn pool,
|
||||
const cudaError_t se = cudaStreamSynchronize(cs_);
|
||||
trace_ev("SYNCED", -1, -1, (int64_t) se);
|
||||
if (se != cudaSuccess) { err = std::string("verify: ") + cudaGetErrorString(se); return false; }
|
||||
commit_pending_ = false;
|
||||
progress_at("verify window: waiting for the expert copies", (int64_t) T);
|
||||
cudaStreamSynchronize(copy_); // no host function of this window may raise flag B in the next one
|
||||
if (copy_used_) {
|
||||
cudaStreamSynchronize(copy_); // no host function of this window may raise flag B in the next one
|
||||
copy_used_ = false;
|
||||
}
|
||||
if (prof_on_ && G == 1) { // the window's GPU stage stamps
|
||||
cudaMemcpy(prof_h_.data(), prof_, prof_h_.size() * 8, cudaMemcpyDeviceToHost);
|
||||
const int64_t L = g.n_layers;
|
||||
@@ -1395,6 +1533,7 @@ void Verifier::fetch_dma(void* ctx, const uint8_t* const* src, int n, size_t byt
|
||||
Verifier* v = (Verifier*) ctx;
|
||||
const uint32_t want = v->cur_layer_ + 1;
|
||||
if (n <= 0) { raise_flag(v->h_flagB_, want); return; }
|
||||
v->copy_used_ = true;
|
||||
uint8_t* stage = (uint8_t*) v->sink_.staging; // this group's half in a split window
|
||||
for (int i = 0; i < n; ++i) cudaMemcpyAsync(stage + (size_t) i * bytes, src[i], bytes, cudaMemcpyHostToDevice, v->copy_);
|
||||
FlagSet& fs = v->flag_sets_[v->cur_layer_ % (sizeof v->flag_sets_ / sizeof v->flag_sets_[0])];
|
||||
@@ -1481,12 +1620,13 @@ bool Verifier::commit(int n_keep, std::string& err) {
|
||||
// set_commit_async: no wait here - the next window runs on the same stream after it, and the drafter (its own
|
||||
// stream) reads only this window's final rows and its own K/V. h_commit_ is next written after the next window's
|
||||
// results are read, i.e. after this graph has run. Everything else waits on commit_done_ (wait_commit).
|
||||
if (!g_commit_async || next_ != nullptr) {
|
||||
if (!g_commit_async) {
|
||||
const cudaError_t se = cudaStreamSynchronize(cs_);
|
||||
if (se != cudaSuccess) { err = std::string("verify: commit: ") + cudaGetErrorString(se); return false; }
|
||||
} else {
|
||||
const cudaError_t re = cudaEventRecord(commit_done_, cs_);
|
||||
if (re != cudaSuccess) { err = std::string("verify: commit event: ") + cudaGetErrorString(re); return false; }
|
||||
(void) cudaStreamQuery(cs_);
|
||||
commit_pending_ = true;
|
||||
}
|
||||
if (ple_stage()) // stages that share one session must advance it once
|
||||
|
||||
@@ -53,6 +53,38 @@ bool cpu_avx512_ok() {
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool cpu_avx512bw_ok() {
|
||||
static const bool ok = [] {
|
||||
if (const char* f = std::getenv("STRATA_FORCE_AVX2"); f != nullptr && f[0] == '1') return false;
|
||||
unsigned r[4] = {0, 0, 0, 0};
|
||||
auto cpuid = [&](unsigned leaf, unsigned sub) {
|
||||
#if defined(_MSC_VER)
|
||||
int x[4];
|
||||
__cpuidex(x, (int) leaf, (int) sub);
|
||||
for (int i = 0; i < 4; ++i) r[i] = (unsigned) x[i];
|
||||
#else
|
||||
__cpuid_count(leaf, sub, r[0], r[1], r[2], r[3]);
|
||||
#endif
|
||||
};
|
||||
cpuid(0, 0);
|
||||
if (r[0] < 7) return false;
|
||||
cpuid(1, 0);
|
||||
if (!((r[2] >> 27) & 1u)) return false; // OSXSAVE
|
||||
#if defined(_MSC_VER)
|
||||
const unsigned long long xcr0 = _xgetbv(0);
|
||||
#else
|
||||
unsigned lo = 0, hi = 0;
|
||||
__asm__ volatile("xgetbv" : "=a"(lo), "=d"(hi) : "c"(0));
|
||||
const unsigned long long xcr0 = ((unsigned long long) hi << 32) | lo;
|
||||
#endif
|
||||
if ((xcr0 & 0xE6) != 0xE6) return false; // the OS saves the AVX-512 state
|
||||
cpuid(7, 0);
|
||||
const unsigned ebx = r[1];
|
||||
return ((ebx >> 16) & 1u) && ((ebx >> 17) & 1u) && ((ebx >> 30) & 1u) && ((ebx >> 31) & 1u);
|
||||
}();
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool cpu_avx2_ok() {
|
||||
static const bool ok = [] {
|
||||
unsigned r[4] = {0, 0, 0, 0};
|
||||
|
||||
@@ -83,7 +83,7 @@ void native_gu_rows(const NativeFmt& f, const uint8_t* blob, const void* const*
|
||||
// first, then the AVX-2 one (Zen 2/3, Intel 12th-14th gen). STRATA_NO_IQ512 drops an AVX-512 CPU to the
|
||||
// AVX-2 kernel, STRATA_NO_IQ256 drops the AVX-2 kernel; ggml-cpu's single-token vec_dot is reached only with
|
||||
// both set (and on a CPU without AVX-512, STRATA_NO_IQ512 changes nothing).
|
||||
static const bool avx512 = cpu_avx512_ok() && std::getenv("STRATA_NO_IQ512") == nullptr;
|
||||
static const bool avx512 = cpu_avx512bw_ok() && std::getenv("STRATA_NO_IQ512") == nullptr;
|
||||
static const bool avx2 = std::getenv("STRATA_NO_IQ256") == nullptr;
|
||||
// #152: from how many tokens the multi-token kernels run (ggml's vec_dot below that). The default 2 is the
|
||||
// measured-fastest rule, but a token's expert rows then round differently alone than in a group, so greedy output
|
||||
@@ -101,7 +101,7 @@ void native_gu_rows(const NativeFmt& f, const uint8_t* blob, const void* const*
|
||||
// A format with only an AVX-2 kernel (IQ4_XS, #415) takes it on AVX-2 CPUs only: an AVX-512 CPU keeps ggml-cpu for
|
||||
// it, as before (its rows would round differently). Each kernel only for the formats it implements: falling
|
||||
// through an empty switch would leave ff unwritten instead of falling back to ggml-cpu.
|
||||
static const bool cpu512 = cpu_avx512_ok();
|
||||
static const bool cpu512 = cpu_avx512bw_ok();
|
||||
if (nt >= mt_min && (iq512_supported(f.gu_type) || (!cpu512 && iq256_supported(f.gu_type)))) {
|
||||
if (avx512 && iq512_supported(f.gu_type)) {
|
||||
iq512_gu_rows(f.gu_type, blob, f.gu_row, f.up_off, (int) f.n_embd, act, nt, ff, r0, r1);
|
||||
|
||||
@@ -294,11 +294,34 @@ __global__ void doorbell_publish_kernel(const float* __restrict__ x, const int32
|
||||
const float* __restrict__ w, int n, int k, float* x_out, int32_t* ids_out,
|
||||
float* w_out, uint32_t* seq) {
|
||||
for (int i = threadIdx.x; i < n; i += blockDim.x) x_out[i] = x[i];
|
||||
if ((int) threadIdx.x < k) { ids_out[threadIdx.x] = ids[threadIdx.x]; w_out[threadIdx.x] = w[threadIdx.x]; }
|
||||
if ((int) threadIdx.x < k) {
|
||||
ids_out[threadIdx.x] = ids[threadIdx.x];
|
||||
if (w_out != nullptr) w_out[threadIdx.x] = w[threadIdx.x];
|
||||
}
|
||||
__threadfence_system();
|
||||
__syncthreads();
|
||||
if (threadIdx.x == 0) {
|
||||
*(volatile uint32_t*) seq = *(volatile uint32_t*) seq + 1u;
|
||||
}
|
||||
}
|
||||
|
||||
__global__ void doorbell_publish_res_kernel(const float* __restrict__ x, const int32_t* __restrict__ ids,
|
||||
const int32_t* __restrict__ d_res, int n_expert, int n, int k,
|
||||
float* x_out, int32_t* ids_out, uint32_t* seq) {
|
||||
int any_miss = 0;
|
||||
if ((int) threadIdx.x < k) {
|
||||
const int32_t id = ids[threadIdx.x];
|
||||
ids_out[threadIdx.x] = id;
|
||||
if (d_res == nullptr || id < 0 || id >= n_expert || d_res[id] < 0) any_miss = 1;
|
||||
}
|
||||
if (__syncthreads_or(any_miss)) {
|
||||
for (int i = threadIdx.x; i < n; i += blockDim.x) x_out[i] = x[i];
|
||||
__threadfence_system();
|
||||
} else if ((int) threadIdx.x < k) {
|
||||
__threadfence_system();
|
||||
}
|
||||
__syncthreads();
|
||||
if (threadIdx.x == 0) {
|
||||
*(volatile uint32_t*) seq = *(volatile uint32_t*) seq + 1u;
|
||||
}
|
||||
}
|
||||
@@ -335,6 +358,14 @@ void doorbell_publish(const float* x, const int32_t* ids, const float* weights,
|
||||
check_launch("doorbell_publish");
|
||||
}
|
||||
|
||||
void doorbell_publish_res(const float* x, const int32_t* ids, const int32_t* d_res, int n_expert, int64_t n, int64_t k,
|
||||
float* x_out, int32_t* ids_out, uint32_t* d_seq, void* stream) {
|
||||
if (k > 1024) { std::fprintf(stderr, "doorbell_publish_res: k too large\n"); std::exit(1); }
|
||||
doorbell_publish_res_kernel<<<1, 1024, 0, (cudaStream_t) stream>>>(x, ids, d_res, n_expert, (int) n, (int) k,
|
||||
x_out, ids_out, d_seq);
|
||||
check_launch("doorbell_publish_res");
|
||||
}
|
||||
|
||||
__global__ void copy_i32_from_mapped_kernel(int32_t* __restrict__ dst, const volatile int32_t* src, int n) {
|
||||
for (int i = threadIdx.x; i < n; i += blockDim.x) dst[i] = src[i];
|
||||
}
|
||||
|
||||
@@ -320,13 +320,13 @@ __global__ void __launch_bounds__(THREADS) gr_up_multi_kernel(GrMulti m) {
|
||||
constexpr int PR = LR + HC; // partial rows per (token, stream): 320 down + 4 inject
|
||||
constexpr int TQ3 = N / 8 / 32; // uint4 weight chunks per lane in one stream's slice (10)
|
||||
|
||||
template <int S>
|
||||
template <int S, int MAX_T = kFusedGrMaxT, bool EXACT_T = false>
|
||||
__global__ void __launch_bounds__(THREADS) gr_down_v3_kernel(GrMulti m, float* __restrict__ part, float* __restrict__ ssg) {
|
||||
extern __shared__ __align__(16) float xs[]; // [T][N / S]
|
||||
constexpr int R2 = 1; // down rows per warp
|
||||
__shared__ float red[WARPS][kFusedGrMaxT];
|
||||
__shared__ float red[WARPS][MAX_T];
|
||||
const int t = threadIdx.x, lane = t & 31, warp = t >> 5;
|
||||
const int T = m.T;
|
||||
const int T = EXACT_T ? MAX_T : m.T;
|
||||
// S = 2: each stream's 2560 columns in two halves (blockIdx.y = stream * S + half): twice the blocks
|
||||
constexpr int SL = N / S, TQS = SL / 8 / 32;
|
||||
const int rg = blockIdx.x, c = blockIdx.y / S, h = blockIdx.y - (blockIdx.y / S) * S;
|
||||
@@ -337,19 +337,11 @@ __global__ void __launch_bounds__(THREADS) gr_down_v3_kernel(GrMulti m, float* _
|
||||
const int nrows = inject_block ? ((m.a[0].w_inject != nullptr && warp < HC) ? 1 : 0) : R2;
|
||||
const bool active = nrows > 0;
|
||||
const uint16_t* wbase = inject_block ? m.a[0].w_inject : m.a[0].w_down;
|
||||
uint4 wv[R2][TQS];
|
||||
float ssp[MAX_T];
|
||||
#pragma unroll
|
||||
for (int r = 0; r < R2; ++r) {
|
||||
if (r >= nrows) break;
|
||||
const uint4* w4 = reinterpret_cast<const uint4*>(wbase + (size_t) (row0 + r) * D + (size_t) c * N + (size_t) h * SL);
|
||||
#pragma unroll
|
||||
for (int q = 0; q < TQS; ++q) wv[r][q] = __ldg(w4 + lane + 32 * q);
|
||||
}
|
||||
float ssp[kFusedGrMaxT];
|
||||
#pragma unroll
|
||||
for (int k = 0; k < kFusedGrMaxT; ++k) {
|
||||
for (int k = 0; k < MAX_T; ++k) {
|
||||
ssp[k] = 0.0f;
|
||||
if (k >= T) continue;
|
||||
if (!EXACT_T && k >= T) continue;
|
||||
const FusedGrArgs& a = m.a[k];
|
||||
const float gw = a.apply ? 2.0f * sigmoidf_(a.inj_prev[c] / (float) HC) : 0.0f;
|
||||
const float4* R4 = reinterpret_cast<const float4*>(a.R + (size_t) c * N + (size_t) h * SL);
|
||||
@@ -369,8 +361,8 @@ __global__ void __launch_bounds__(THREADS) gr_down_v3_kernel(GrMulti m, float* _
|
||||
}
|
||||
}
|
||||
#pragma unroll
|
||||
for (int k = 0; k < kFusedGrMaxT; ++k) {
|
||||
if (k >= T) break;
|
||||
for (int k = 0; k < MAX_T; ++k) {
|
||||
if (!EXACT_T && k >= T) break;
|
||||
const float v = warp_sum(ssp[k]);
|
||||
if (lane == 0) red[warp][k] = v;
|
||||
}
|
||||
@@ -384,34 +376,36 @@ __global__ void __launch_bounds__(THREADS) gr_down_v3_kernel(GrMulti m, float* _
|
||||
#pragma unroll
|
||||
for (int r = 0; r < R2; ++r) {
|
||||
if (r >= nrows) break;
|
||||
float acc[kFusedGrMaxT];
|
||||
const uint4* w4 = reinterpret_cast<const uint4*>(wbase + (size_t) (row0 + r) * D + (size_t) c * N + (size_t) h * SL);
|
||||
float acc[MAX_T];
|
||||
#pragma unroll
|
||||
for (int k = 0; k < kFusedGrMaxT; ++k) acc[k] = 0.0f;
|
||||
for (int k = 0; k < MAX_T; ++k) acc[k] = 0.0f;
|
||||
#pragma unroll
|
||||
for (int q = 0; q < TQS; ++q) {
|
||||
const int j = lane + 32 * q;
|
||||
const uint4 wvq = __ldg(w4 + j);
|
||||
#pragma unroll
|
||||
for (int k = 0; k < kFusedGrMaxT; ++k)
|
||||
if (k < T) acc[k] += dot8(wv[r][q], xs + (size_t) k * SL + j * 8);
|
||||
for (int k = 0; k < MAX_T; ++k)
|
||||
if (EXACT_T || k < T) acc[k] += dot8(wvq, xs + (size_t) k * SL + j * 8);
|
||||
}
|
||||
const int prow = inject_block ? LR + warp : row0 + r;
|
||||
#pragma unroll
|
||||
for (int k = 0; k < kFusedGrMaxT; ++k) {
|
||||
if (k >= T) break;
|
||||
for (int k = 0; k < MAX_T; ++k) {
|
||||
if (!EXACT_T && k >= T) break;
|
||||
const float v = warp_sum(acc[k]);
|
||||
if (lane == 0) part[(((size_t) k * HC + c) * S + h) * PR + prow] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <int S>
|
||||
template <int S, int MAX_T = kFusedGrMaxT, bool EXACT_T = false>
|
||||
__global__ void __launch_bounds__(THREADS) gr_up_v3_kernel(GrMulti m, const float* __restrict__ part,
|
||||
const float* __restrict__ ssg) {
|
||||
__shared__ __align__(16) float lo[kFusedGrMaxT][LR];
|
||||
__shared__ float rsS[kFusedGrMaxT][HC];
|
||||
__shared__ float g[kFusedGrMaxT][HC][UPM_COLS];
|
||||
__shared__ __align__(16) float lo[MAX_T][LR];
|
||||
__shared__ float rsS[MAX_T][HC];
|
||||
__shared__ float g[MAX_T][HC][UPM_COLS];
|
||||
const int t = threadIdx.x, lane = t & 31, warp = t >> 5;
|
||||
const int T = m.T;
|
||||
const int T = EXACT_T ? MAX_T : m.T;
|
||||
const int d0 = blockIdx.x * UPM_COLS;
|
||||
constexpr int RPW = HC * UPM_COLS / WARPS; // 8 rows per warp
|
||||
if (t < T * HC) {
|
||||
@@ -470,8 +464,8 @@ __global__ void __launch_bounds__(THREADS) gr_up_v3_kernel(GrMulti m, const floa
|
||||
}
|
||||
float mine = 0.0f;
|
||||
#pragma unroll
|
||||
for (int k = 0; k < kFusedGrMaxT; ++k) {
|
||||
if (k >= T) break;
|
||||
for (int k = 0; k < MAX_T; ++k) {
|
||||
if (!EXACT_T && k >= T) break;
|
||||
float acc = dot8(wa, lo[k] + lane * 8);
|
||||
if (lane < LR / 8 - 32) acc += dot8(wb, lo[k] + (32 + lane) * 8);
|
||||
acc = warp_sum(acc);
|
||||
@@ -843,12 +837,16 @@ void fused_gr_read_multi(const FusedGrArgs* a, int n_tok, float* xn_scratch, voi
|
||||
int optin = 0;
|
||||
cudaDeviceGetAttribute(&optin, cudaDevAttrMaxSharedMemoryPerBlockOptin, dev3);
|
||||
const int limit = optin > 0 ? optin : 48 * 1024;
|
||||
const int need1 = (int) (kFusedGrMaxT * N * sizeof(float)), need2 = need1 / 2;
|
||||
const int need1 = (int) (kFusedGrMaxT * N * sizeof(float)), need6 = (int) (6 * N * sizeof(float)), need2 = need1 / 2;
|
||||
int split = -1;
|
||||
if (need1 <= limit &&
|
||||
cudaFuncSetAttribute(gr_down_v3_kernel<1>, cudaFuncAttributeMaxDynamicSharedMemorySize, need1) == cudaSuccess)
|
||||
cudaFuncSetAttribute(gr_down_v3_kernel<1, kFusedGrMaxT, false>, cudaFuncAttributeMaxDynamicSharedMemorySize, need1) == cudaSuccess &&
|
||||
cudaFuncSetAttribute(gr_down_v3_kernel<1, 6, true>, cudaFuncAttributeMaxDynamicSharedMemorySize, need6) == cudaSuccess) {
|
||||
cudaFuncSetAttribute(gr_down_v3_kernel<1, 4, true>, cudaFuncAttributePreferredSharedMemoryCarveout, 100);
|
||||
cudaFuncSetAttribute(gr_down_v3_kernel<1, 4, false>, cudaFuncAttributePreferredSharedMemoryCarveout, 100);
|
||||
cudaFuncSetAttribute(gr_down_v3_kernel<1, 2, true>, cudaFuncAttributePreferredSharedMemoryCarveout, 100);
|
||||
split = 1;
|
||||
else if (need2 <= limit)
|
||||
} else if (need2 <= limit)
|
||||
// #375 (kenh0u): the S = 2 split (a 64 KB opt-in card: Turing) disagrees with itself in gr_parity
|
||||
// (graph replay vs direct call) - such a card keeps the default read until that split is fixed
|
||||
std::fprintf(stderr, "strata: STRATA_GR_V3=1 needs the two-half split on this card, which fails its "
|
||||
@@ -864,10 +862,18 @@ void fused_gr_read_multi(const FusedGrArgs* a, int n_tok, float* xn_scratch, voi
|
||||
const size_t sm = (size_t) n_tok * (N / split) * sizeof(float);
|
||||
if (stamp_buf) gpu_stamp(stamp_buf, stamp_i0, stream);
|
||||
if (split == 2) gr_down_v3_kernel<2><<<dim3(LR / WARPS + 1, HC * 2), THREADS, sm, st>>>(m, part, ssg);
|
||||
else gr_down_v3_kernel<1><<<dim3(LR / WARPS + 1, HC), THREADS, sm, st>>>(m, part, ssg);
|
||||
else if (n_tok == 2) gr_down_v3_kernel<1, 2, true><<<dim3(LR / WARPS + 1, HC), THREADS, sm, st>>>(m, part, ssg);
|
||||
else if (n_tok == 4) gr_down_v3_kernel<1, 4, true><<<dim3(LR / WARPS + 1, HC), THREADS, sm, st>>>(m, part, ssg);
|
||||
else if (n_tok == 6) gr_down_v3_kernel<1, 6, true><<<dim3(LR / WARPS + 1, HC), THREADS, sm, st>>>(m, part, ssg);
|
||||
else if (n_tok < 4) gr_down_v3_kernel<1, 4, false><<<dim3(LR / WARPS + 1, HC), THREADS, sm, st>>>(m, part, ssg);
|
||||
else gr_down_v3_kernel<1, kFusedGrMaxT, false><<<dim3(LR / WARPS + 1, HC), THREADS, sm, st>>>(m, part, ssg);
|
||||
if (stamp_buf) gpu_stamp(stamp_buf, stamp_i0 + 1, stream);
|
||||
if (split == 2) gr_up_v3_kernel<2><<<UPM_BLOCKS, THREADS, 0, st>>>(m, part, ssg);
|
||||
else gr_up_v3_kernel<1><<<UPM_BLOCKS, THREADS, 0, st>>>(m, part, ssg);
|
||||
else if (n_tok == 2) gr_up_v3_kernel<1, 2, true><<<UPM_BLOCKS, THREADS, 0, st>>>(m, part, ssg);
|
||||
else if (n_tok == 4) gr_up_v3_kernel<1, 4, true><<<UPM_BLOCKS, THREADS, 0, st>>>(m, part, ssg);
|
||||
else if (n_tok == 6) gr_up_v3_kernel<1, 6, true><<<UPM_BLOCKS, THREADS, 0, st>>>(m, part, ssg);
|
||||
else if (n_tok < 4) gr_up_v3_kernel<1, 4, false><<<UPM_BLOCKS, THREADS, 0, st>>>(m, part, ssg);
|
||||
else gr_up_v3_kernel<1, kFusedGrMaxT, false><<<UPM_BLOCKS, THREADS, 0, st>>>(m, part, ssg);
|
||||
const cudaError_t e3 = cudaGetLastError();
|
||||
if (e3 != cudaSuccess) {
|
||||
std::fprintf(stderr, "fused_gr_read_multi v3: %s\n", cudaGetErrorString(e3));
|
||||
|
||||
+328
-42
@@ -561,15 +561,16 @@ template<int TY> inline constexpr bool kSplit = false;
|
||||
template<> inline constexpr bool kSplit<16> = true;
|
||||
template<> struct Split<16> { // IQ2_XXS
|
||||
struct W { int g[8]; int ls; float dw; };
|
||||
__device__ static W load(const void* __restrict__ vbq, int kbx, int iqs) {
|
||||
__device__ static W load(const void* __restrict__ vbq, int kbx, int iqs, const void* __restrict__ s_grid = nullptr) {
|
||||
const block_iq2_xxs* bq2 = (const block_iq2_xxs*) vbq + kbx;
|
||||
const int q2 = get_int_b2(bq2->qs, iqs);
|
||||
const uint8_t* aux8 = (const uint8_t*) &q2;
|
||||
const uint32_t aux32 = get_int_b2(bq2->qs, iqs + 1);
|
||||
const uint2* grid_lut = s_grid ? (const uint2*) s_grid : (const uint2*) iq2xxs_grid;
|
||||
W r;
|
||||
#pragma unroll
|
||||
for (int k0 = 0; k0 < 8; k0 += 2) {
|
||||
const uint2 grid_pos = ((const uint2*) iq2xxs_grid)[aux8[k0 / 2]];
|
||||
const uint2 grid_pos = grid_lut[aux8[k0 / 2]];
|
||||
const uint32_t signs = unpack_ksigns(aux32 >> (7 * k0 / 2));
|
||||
const int signs0 = __vcmpne4(signs & 0x08040201, 0);
|
||||
r.g[k0 + 0] = __vsub4(grid_pos.x ^ signs0, signs0);
|
||||
@@ -605,16 +606,17 @@ struct SplitLs2 {
|
||||
};
|
||||
template<> inline constexpr bool kSplit<17> = true;
|
||||
template<> struct Split<17> : SplitLs2 { // IQ2_XS
|
||||
__device__ static W load(const void* __restrict__ vbq, int kbx, int iqs) {
|
||||
__device__ static W load(const void* __restrict__ vbq, int kbx, int iqs, const void* __restrict__ s_grid = nullptr) {
|
||||
const block_iq2_xs* bq2 = (const block_iq2_xs*) vbq + kbx;
|
||||
const int2 q2_packed = make_int2(get_int_b2(bq2->qs, iqs + 0), get_int_b2(bq2->qs, iqs + 1));
|
||||
const uint16_t* q2 = (const uint16_t*) &q2_packed;
|
||||
const uint2* grid_lut = s_grid ? (const uint2*) s_grid : (const uint2*) iq2xs_grid;
|
||||
W r;
|
||||
r.ls0 = bq2->scales[iqs / 2] & 0x0F;
|
||||
r.ls1 = bq2->scales[iqs / 2] >> 4;
|
||||
#pragma unroll
|
||||
for (int l0 = 0; l0 < 8; l0 += 2) {
|
||||
const uint2 grid_pos = ((const uint2*) iq2xs_grid)[q2[l0 / 2] & 0x1FF];
|
||||
const uint2 grid_pos = grid_lut[q2[l0 / 2] & 0x1FF];
|
||||
const uint32_t signs = unpack_ksigns(q2[l0 / 2] >> 9);
|
||||
const int signs0 = __vcmpne4(signs & 0x08040201, 0);
|
||||
r.g[l0 + 0] = __vsub4(grid_pos.x ^ signs0, signs0);
|
||||
@@ -627,19 +629,20 @@ template<> struct Split<17> : SplitLs2 { // IQ2_XS
|
||||
};
|
||||
template<> inline constexpr bool kSplit<22> = true;
|
||||
template<> struct Split<22> : SplitLs2 { // IQ2_S
|
||||
__device__ static W load(const void* __restrict__ vbq, int kbx, int iqs) {
|
||||
__device__ static W load(const void* __restrict__ vbq, int kbx, int iqs, const void* __restrict__ s_grid = nullptr) {
|
||||
const block_iq2_s* bq2 = (const block_iq2_s*) vbq + kbx;
|
||||
const int qs_packed = get_int_b2(bq2->qs, iqs / 2);
|
||||
const uint8_t* qs = (const uint8_t*) &qs_packed;
|
||||
const int qh = bq2->qh[iqs / 2];
|
||||
const int signs_packed_32 = get_int_b2(bq2->qs, QK_K / 32 + iqs / 2);
|
||||
const uint8_t* signs_packed_8 = (const uint8_t*) &signs_packed_32;
|
||||
const uint64_t* grid_lut = s_grid ? (const uint64_t*) s_grid : iq2s_grid;
|
||||
W r;
|
||||
r.ls0 = bq2->scales[iqs / 2] & 0x0F;
|
||||
r.ls1 = bq2->scales[iqs / 2] >> 4;
|
||||
#pragma unroll
|
||||
for (int l0 = 0; l0 < 8; l0 += 2) {
|
||||
const int* grid_pos = (const int*) (iq2s_grid + (qs[l0 / 2] | ((qh << (8 - l0)) & 0x300)));
|
||||
const int* grid_pos = (const int*) (grid_lut + (qs[l0 / 2] | ((qh << (8 - l0)) & 0x300)));
|
||||
const int signs0 = __vcmpne4(((signs_packed_8[l0 / 2] & 0x03) << 7) | ((signs_packed_8[l0 / 2] & 0x0C) << 21), 0x00000000);
|
||||
const int signs1 = __vcmpne4(((signs_packed_8[l0 / 2] & 0x30) << 3) | ((signs_packed_8[l0 / 2] & 0xC0) << 17), 0x00000000);
|
||||
r.g[l0 + 0] = __vsub4(grid_pos[0] ^ signs0, signs0);
|
||||
@@ -652,15 +655,16 @@ template<> struct Split<22> : SplitLs2 { // IQ2_S
|
||||
template<> inline constexpr bool kSplit<18> = true;
|
||||
template<> struct Split<18> { // IQ3_XXS
|
||||
struct W { int g[8]; int ls; float dw; };
|
||||
__device__ static W load(const void* __restrict__ vbq, int kbx, int iqs) {
|
||||
__device__ static W load(const void* __restrict__ vbq, int kbx, int iqs, const void* __restrict__ s_grid = nullptr) {
|
||||
const block_iq3_xxs* bq3 = (const block_iq3_xxs*) vbq + kbx;
|
||||
const int2 q3_packed = make_int2(get_int_b2(bq3->qs, iqs), get_int_b2(bq3->qs, iqs + 1));
|
||||
const uint8_t* q3 = (const uint8_t*) &q3_packed;
|
||||
const uint32_t aux32 = get_int_b2(bq3->qs, QK_K / 16 + iqs / 2);
|
||||
const uint32_t* grid_lut = s_grid ? (const uint32_t*) s_grid : iq3xxs_grid;
|
||||
W r;
|
||||
#pragma unroll
|
||||
for (int l0 = 0; l0 < 8; l0 += 2) {
|
||||
const int2 grid_pos = make_int2(iq3xxs_grid[q3[l0 + 0]], iq3xxs_grid[q3[l0 + 1]]);
|
||||
const int2 grid_pos = make_int2(grid_lut[q3[l0 + 0]], grid_lut[q3[l0 + 1]]);
|
||||
const uint32_t signs = unpack_ksigns(aux32 >> (7 * l0 / 2));
|
||||
const int signs0 = __vcmpne4(signs & 0x08040201, 0);
|
||||
r.g[l0 + 0] = __vsub4(grid_pos.x ^ signs0, signs0);
|
||||
@@ -683,18 +687,19 @@ template<> struct Split<18> { // IQ3_XXS
|
||||
template<> inline constexpr bool kSplit<21> = true;
|
||||
template<> struct Split<21> { // IQ3_S
|
||||
struct W { int g[8]; int ls; float dw; };
|
||||
__device__ static W load(const void* __restrict__ vbq, int kbx, int iqs) {
|
||||
__device__ static W load(const void* __restrict__ vbq, int kbx, int iqs, const void* __restrict__ s_grid = nullptr) {
|
||||
const block_iq3_s* bq3 = (const block_iq3_s*) vbq + kbx;
|
||||
const int2 qs_packed = make_int2(get_int_b2(bq3->qs, iqs + 0), get_int_b2(bq3->qs, iqs + 1));
|
||||
const uint8_t* qs = (const uint8_t*) &qs_packed;
|
||||
const int qh = bq3->qh[iqs / 2];
|
||||
const int signs_packed_32 = get_int_b2(bq3->signs, iqs / 2);
|
||||
const uint8_t* signs_packed_8 = (const uint8_t*) &signs_packed_32;
|
||||
const uint32_t* grid_lut = s_grid ? (const uint32_t*) s_grid : iq3s_grid;
|
||||
W r;
|
||||
#pragma unroll
|
||||
for (int l0 = 0; l0 < 8; l0 += 2) {
|
||||
const int2 grid_pos = make_int2(iq3s_grid[qs[l0 + 0] | ((qh << (8 - l0)) & 0x100)],
|
||||
iq3s_grid[qs[l0 + 1] | ((qh << (7 - l0)) & 0x100)]);
|
||||
const int2 grid_pos = make_int2(grid_lut[qs[l0 + 0] | ((qh << (8 - l0)) & 0x100)],
|
||||
grid_lut[qs[l0 + 1] | ((qh << (7 - l0)) & 0x100)]);
|
||||
const int signs0 = __vcmpne4(((signs_packed_8[l0 / 2] & 0x03) << 7) | ((signs_packed_8[l0 / 2] & 0x0C) << 21), 0x00000000);
|
||||
const int signs1 = __vcmpne4(((signs_packed_8[l0 / 2] & 0x30) << 3) | ((signs_packed_8[l0 / 2] & 0xC0) << 17), 0x00000000);
|
||||
r.g[l0 + 0] = __vsub4(grid_pos.x ^ signs0, signs0);
|
||||
@@ -716,15 +721,16 @@ template<> struct Split<21> { // IQ3_S
|
||||
template<> inline constexpr bool kSplit<29> = true;
|
||||
template<> struct Split<29> { // IQ1_M
|
||||
struct W { int g[8]; float delta[4]; int sc0, sc1; float dw; };
|
||||
__device__ static W load(const void* __restrict__ vbq, int kbx, int iqs) {
|
||||
__device__ static W load(const void* __restrict__ vbq, int kbx, int iqs, const void* __restrict__ s_grid = nullptr) {
|
||||
const block_iq1_m* bq1 = (const block_iq1_m*) vbq + kbx;
|
||||
const int qs_packed = get_int_b4(bq1->qs, iqs);
|
||||
const uint8_t* qs = (const uint8_t*) &qs_packed;
|
||||
const uint32_t* grid_lut = s_grid ? (const uint32_t*) s_grid : iq1s_grid_gpu;
|
||||
W r;
|
||||
#pragma unroll
|
||||
for (int l0 = 0; l0 < 8; l0 += 2) {
|
||||
const int qhl = bq1->qh[2 * iqs + l0 / 4] >> (4 * ((l0 / 2) % 2));
|
||||
const int grid = iq1s_grid_gpu[qs[l0 / 2] | ((qhl & 0x07) << 8)];
|
||||
const int grid = grid_lut[qs[l0 / 2] | ((qhl & 0x07) << 8)];
|
||||
r.g[l0 + 0] = (grid >> 0) & 0x0F0F0F0F;
|
||||
r.g[l0 + 1] = (grid >> 4) & 0x0F0F0F0F;
|
||||
r.delta[l0 / 2] = -1.0f + IQ1M_DELTA - (qhl & 0x08) * (2.0f * IQ1M_DELTA / 0x08);
|
||||
@@ -759,7 +765,7 @@ template<> struct Split<29> { // IQ1_M
|
||||
template<> inline constexpr bool kSplit<20> = true;
|
||||
template<> struct Split<20> { // IQ4_NL
|
||||
struct W { int2 v[2]; float dw; };
|
||||
__device__ static W load(const void* __restrict__ vbq, int kbx, int iqs) {
|
||||
__device__ static W load(const void* __restrict__ vbq, int kbx, int iqs, const void* __restrict__ = nullptr) {
|
||||
const block_iq4_nl* bq4 = (const block_iq4_nl*) vbq + kbx;
|
||||
W r;
|
||||
#pragma unroll
|
||||
@@ -782,7 +788,7 @@ template<> struct Split<20> { // IQ4_NL
|
||||
template<> inline constexpr bool kSplit<23> = true;
|
||||
template<> struct Split<23> { // IQ4_XS
|
||||
struct W { int2 v[4]; int ls; float dw; };
|
||||
__device__ static W load(const void* __restrict__ vbq, int kbx, int iqs) {
|
||||
__device__ static W load(const void* __restrict__ vbq, int kbx, int iqs, const void* __restrict__ = nullptr) {
|
||||
const block_iq4_xs* bq4 = (const block_iq4_xs*) vbq + kbx;
|
||||
W r;
|
||||
#pragma unroll
|
||||
@@ -808,7 +814,7 @@ template<> struct Split<23> { // IQ4_XS
|
||||
template<> inline constexpr bool kSplit<42> = true;
|
||||
template<> struct Split<42> { // Q2_0
|
||||
struct W { int qx[4], qy[4]; float d2; };
|
||||
__device__ static W load(const void* __restrict__ vbq, int kbx, int iqs) {
|
||||
__device__ static W load(const void* __restrict__ vbq, int kbx, int iqs, const void* __restrict__ = nullptr) {
|
||||
const block_q2_0* bq2_0 = (const block_q2_0*) vbq + kbx;
|
||||
W r;
|
||||
r.d2 = bq2_0->d;
|
||||
@@ -841,31 +847,172 @@ template<> struct Split<42> { // Q2_0
|
||||
// One row against the n <= NC activations x + off[0..n) (n >= 1, warp-uniform; offsets in q8_1 blocks, 32-bit to
|
||||
// spare registers), the whole warp. Per activation this is row_dot: the same calls k, lane-strided the same way,
|
||||
// summed in the same order, then the same warp_sum. Only the weight side moves out of the per-activation loop.
|
||||
template<int TY, int NC>
|
||||
template<int TY, int NC, bool EXACT_N = false>
|
||||
__device__ __forceinline__ void row_dot_multi(const uint8_t* row, const block_q8_1* x, const int (&off)[NC], int n,
|
||||
int nb, int lane, float (&s)[NC]) {
|
||||
int nb, int lane, float (&s)[NC], const void* __restrict__ s_grid = nullptr) {
|
||||
using F = Fmt<TY>;
|
||||
using S = Split<TY>;
|
||||
#pragma unroll
|
||||
for (int c = 0; c < NC; ++c) s[c] = 0.0f;
|
||||
for (int k = lane; k < nb * F::ipb; k += 32) {
|
||||
const int kbx = k / F::ipb, iqs = F::step * (k % F::ipb);
|
||||
const typename S::W w = S::load(row, kbx, iqs);
|
||||
const typename S::W w = S::load(row, kbx, iqs, s_grid);
|
||||
#pragma unroll
|
||||
for (int c = 0; c < NC; ++c)
|
||||
if (c < n) s[c] += S::apply(w, x + off[c] + kbx * (F::qk / 32), iqs);
|
||||
if (EXACT_N || c < n) s[c] += S::apply(w, x + off[c] + kbx * (F::qk / 32), iqs);
|
||||
}
|
||||
#pragma unroll
|
||||
for (int c = 0; c < NC; ++c)
|
||||
if (c < n) s[c] = warp_sum(s[c]);
|
||||
if (EXACT_N || c < n) s[c] = warp_sum(s[c]);
|
||||
}
|
||||
|
||||
// 8-lane sub-warp row dot for K = nb * ipb == 40 (TD == 20, IQ4_NL with n_ff == 640):
|
||||
// 4 rows per warp (32 rows per 256-thread block), 100% active lanes, bitwise identical addition tree to row_dot_multi.
|
||||
template<int TY, int NC, bool EXACT_N = false>
|
||||
__device__ __forceinline__ void row_dot_40_sub8(const uint8_t* row, const block_q8_1* x, const int (&off)[NC], int n,
|
||||
int t, float (&s)[NC]) {
|
||||
using F = Fmt<TY>;
|
||||
using S = Split<TY>;
|
||||
#pragma unroll
|
||||
for (int c = 0; c < NC; ++c) s[c] = 0.0f;
|
||||
for (int k = t; k < 40; k += 32) {
|
||||
const int kbx = k / F::ipb, iqs = F::step * (k % F::ipb);
|
||||
const typename S::W w = S::load(row, kbx, iqs);
|
||||
#pragma unroll
|
||||
for (int c = 0; c < NC; ++c)
|
||||
if (EXACT_N || c < n) s[c] += S::apply(w, x + off[c] + kbx * (F::qk / 32), iqs);
|
||||
}
|
||||
{
|
||||
const int k = t + 16, kbx = k / F::ipb, iqs = F::step * (k % F::ipb);
|
||||
const typename S::W w = S::load(row, kbx, iqs);
|
||||
#pragma unroll
|
||||
for (int c = 0; c < NC; ++c) {
|
||||
if (EXACT_N || c < n) {
|
||||
float s16 = 0.0f;
|
||||
s16 += S::apply(w, x + off[c] + kbx * (F::qk / 32), iqs);
|
||||
s[c] = __fadd_rn(s[c], s16);
|
||||
}
|
||||
}
|
||||
}
|
||||
float s8[NC];
|
||||
#pragma unroll
|
||||
for (int c = 0; c < NC; ++c) s8[c] = 0.0f;
|
||||
{
|
||||
const int k = t + 8, kbx = k / F::ipb, iqs = F::step * (k % F::ipb);
|
||||
const typename S::W w = S::load(row, kbx, iqs);
|
||||
#pragma unroll
|
||||
for (int c = 0; c < NC; ++c)
|
||||
if (EXACT_N || c < n) s8[c] += S::apply(w, x + off[c] + kbx * (F::qk / 32), iqs);
|
||||
}
|
||||
{
|
||||
const int k = t + 24, kbx = k / F::ipb, iqs = F::step * (k % F::ipb);
|
||||
const typename S::W w = S::load(row, kbx, iqs);
|
||||
#pragma unroll
|
||||
for (int c = 0; c < NC; ++c) {
|
||||
if (EXACT_N || c < n) {
|
||||
float s24 = 0.0f;
|
||||
s24 += S::apply(w, x + off[c] + kbx * (F::qk / 32), iqs);
|
||||
s[c] = __fadd_rn(s[c], __fadd_rn(s8[c], s24));
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma unroll
|
||||
for (int c = 0; c < NC; ++c) {
|
||||
if (EXACT_N || c < n) {
|
||||
#pragma unroll
|
||||
for (int o = 4; o > 0; o >>= 1) s[c] += __shfl_xor_sync(0xffffffffu, s[c], o);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 16-lane sub-warp row dot for K = nb * ipb == 20 (TD == 42, Q2_0 with n_ff == 640):
|
||||
// 2 rows per warp (16 rows per 256-thread block), bitwise identical addition tree to row_dot_multi.
|
||||
template<int TY, int NC, bool EXACT_N = false>
|
||||
__device__ __forceinline__ void row_dot_20_sub16(const uint8_t* row, const block_q8_1* x, const int (&off)[NC], int n,
|
||||
int t, float (&s)[NC]) {
|
||||
using F = Fmt<TY>;
|
||||
using S = Split<TY>;
|
||||
#pragma unroll
|
||||
for (int c = 0; c < NC; ++c) s[c] = 0.0f;
|
||||
{
|
||||
const int k = t, kbx = k / F::ipb, iqs = F::step * (k % F::ipb);
|
||||
const typename S::W w = S::load(row, kbx, iqs);
|
||||
#pragma unroll
|
||||
for (int c = 0; c < NC; ++c)
|
||||
if (EXACT_N || c < n) s[c] += S::apply(w, x + off[c] + kbx * (F::qk / 32), iqs);
|
||||
}
|
||||
float s16[NC];
|
||||
#pragma unroll
|
||||
for (int c = 0; c < NC; ++c) s16[c] = 0.0f;
|
||||
if (t < 4) {
|
||||
const int k = t + 16, kbx = k / F::ipb, iqs = F::step * (k % F::ipb);
|
||||
const typename S::W w = S::load(row, kbx, iqs);
|
||||
#pragma unroll
|
||||
for (int c = 0; c < NC; ++c)
|
||||
if (EXACT_N || c < n) s16[c] += S::apply(w, x + off[c] + kbx * (F::qk / 32), iqs);
|
||||
}
|
||||
#pragma unroll
|
||||
for (int c = 0; c < NC; ++c) {
|
||||
if (EXACT_N || c < n) {
|
||||
s[c] = __fadd_rn(s[c], s16[c]);
|
||||
#pragma unroll
|
||||
for (int o = 8; o > 0; o >>= 1) s[c] += __shfl_xor_sync(0xffffffffu, s[c], o);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<int TY>
|
||||
__device__ __forceinline__ const void* stage_iq_grid(uint32_t* s_buf, int tid, int nthreads) {
|
||||
if constexpr (TY == 16) {
|
||||
uint2* dst = (uint2*) s_buf;
|
||||
const uint2* src = (const uint2*) iq2xxs_grid;
|
||||
for (int i = tid; i < 256; i += nthreads) dst[i] = src[i];
|
||||
__syncthreads();
|
||||
return dst;
|
||||
} else if constexpr (TY == 17) {
|
||||
uint2* dst = (uint2*) s_buf;
|
||||
const uint2* src = (const uint2*) iq2xs_grid;
|
||||
for (int i = tid; i < 512; i += nthreads) dst[i] = src[i];
|
||||
__syncthreads();
|
||||
return dst;
|
||||
} else if constexpr (TY == 18) {
|
||||
for (int i = tid; i < 256; i += nthreads) s_buf[i] = iq3xxs_grid[i];
|
||||
__syncthreads();
|
||||
return s_buf;
|
||||
} else if constexpr (TY == 21) {
|
||||
for (int i = tid; i < 512; i += nthreads) s_buf[i] = iq3s_grid[i];
|
||||
__syncthreads();
|
||||
return s_buf;
|
||||
} else if constexpr (TY == 22) {
|
||||
uint2* dst = (uint2*) s_buf;
|
||||
const uint2* src = (const uint2*) iq2s_grid;
|
||||
for (int i = tid; i < 1024; i += nthreads) dst[i] = src[i];
|
||||
__syncthreads();
|
||||
return dst;
|
||||
} else if constexpr (TY == 29) {
|
||||
uint2* dst = (uint2*) s_buf;
|
||||
const uint2* src = (const uint2*) iq1s_grid_gpu;
|
||||
for (int i = tid; i < 1024; i += nthreads) dst[i] = src[i];
|
||||
__syncthreads();
|
||||
return dst;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template<int TY>
|
||||
struct IqGridWords {
|
||||
static constexpr int value = (TY == 22 || TY == 29) ? 2048 : (TY == 17) ? 1024 : (TY == 16 || TY == 21) ? 512 : (TY == 18) ? 256 : 1;
|
||||
};
|
||||
|
||||
// mmvq_kernel with the columns taken NC at a time. After warp_sum every lane holds the same sum, so lane c stores
|
||||
// column c.
|
||||
template<int TY, int NC>
|
||||
__global__ void __launch_bounds__(128) mmvq_multi_kernel(const uint8_t* __restrict__ w, size_t row_bytes,
|
||||
const block_q8_1* __restrict__ x, float* __restrict__ y,
|
||||
int n_in, int n_out, int ncols) {
|
||||
__shared__ uint32_t s_grid_buf[IqGridWords<TY>::value];
|
||||
const void* s_grid = stage_iq_grid<TY>(s_grid_buf, threadIdx.y * 32 + threadIdx.x, 128);
|
||||
const int row = blockIdx.x * 4 + threadIdx.y;
|
||||
if (row >= n_out) return;
|
||||
const int lane = threadIdx.x;
|
||||
@@ -877,7 +1024,7 @@ __global__ void __launch_bounds__(128) mmvq_multi_kernel(const uint8_t* __restri
|
||||
#pragma unroll
|
||||
for (int c = 0; c < NC; ++c) off[c] = (c0 + min(c, n - 1)) * xb;
|
||||
float s[NC];
|
||||
row_dot_multi<TY, NC>(wr, x, off, n, nb, lane, s);
|
||||
row_dot_multi<TY, NC>(wr, x, off, n, nb, lane, s, s_grid);
|
||||
#pragma unroll
|
||||
for (int c = 0; c < NC; ++c)
|
||||
if (c < n && lane == c) y[(size_t) (c0 + c) * n_out + row] = s[c];
|
||||
@@ -930,6 +1077,10 @@ __global__ void __launch_bounds__(256) native_gu_multi_kernel(const unsigned lon
|
||||
const int32_t* __restrict__ ent_tok,
|
||||
const block_q8_1* __restrict__ xq, NativeExpertLayout L,
|
||||
float* __restrict__ gate, float* __restrict__ up) {
|
||||
const int ng = *n_groups;
|
||||
if (blockIdx.y >= ng) return;
|
||||
__shared__ uint32_t s_grid_buf[IqGridWords<TG>::value];
|
||||
const void* s_grid = stage_iq_grid<TG>(s_grid_buf, threadIdx.x, 256);
|
||||
const int warp = threadIdx.x >> 5, lane = threadIdx.x & 31;
|
||||
const int row = blockIdx.x * GU_ROWS + warp; // 0 .. 2*n_ff
|
||||
if (row >= 2 * L.n_ff) return;
|
||||
@@ -938,20 +1089,31 @@ __global__ void __launch_bounds__(256) native_gu_multi_kernel(const unsigned lon
|
||||
const size_t w_off = (is_up ? L.up_off : 0) + (size_t) r * L.gu_row;
|
||||
const int nb = (int) (L.n_embd / Fmt<TG>::qk), xb = (int) (L.n_embd / 32);
|
||||
float* dst = is_up ? up : gate;
|
||||
const int ng = *n_groups;
|
||||
for (int g = blockIdx.y; g < ng; g += gridDim.y) { // the group stride, as native_gu_kernel
|
||||
const uint8_t* wr = (const uint8_t*) grp_ptr[g] + w_off;
|
||||
const int e0 = grp_start[g], e1 = grp_start[g + 1];
|
||||
for (int e = e0; e < e1; e += GRP_NC) {
|
||||
const int n = min(GRP_NC, e1 - e);
|
||||
int off[GRP_NC];
|
||||
if (n == 1) {
|
||||
const int off1[1] = { ent_tok[e] * xb };
|
||||
float s1[1];
|
||||
row_dot_multi<TG, 1, true>(wr, xq, off1, 1, nb, lane, s1, s_grid);
|
||||
if (lane == 0) dst[(size_t) e * L.n_ff + r] = s1[0];
|
||||
} else if (n == 2) {
|
||||
const int off2[2] = { ent_tok[e] * xb, ent_tok[e + 1] * xb };
|
||||
float s2[2];
|
||||
row_dot_multi<TG, 2, true>(wr, xq, off2, 2, nb, lane, s2, s_grid);
|
||||
if (lane < 2) dst[(size_t) (e + lane) * L.n_ff + r] = s2[lane];
|
||||
} else {
|
||||
int off[GRP_NC];
|
||||
#pragma unroll
|
||||
for (int c = 0; c < GRP_NC; ++c) off[c] = ent_tok[e + min(c, n - 1)] * xb;
|
||||
float s[GRP_NC];
|
||||
row_dot_multi<TG, GRP_NC>(wr, xq, off, n, nb, lane, s);
|
||||
for (int c = 0; c < GRP_NC; ++c) off[c] = ent_tok[e + min(c, n - 1)] * xb;
|
||||
float s[GRP_NC];
|
||||
row_dot_multi<TG, GRP_NC>(wr, xq, off, n, nb, lane, s, s_grid);
|
||||
#pragma unroll
|
||||
for (int c = 0; c < GRP_NC; ++c)
|
||||
if (c < n && lane == c) dst[(size_t) (e + c) * L.n_ff + r] = s[c];
|
||||
for (int c = 0; c < GRP_NC; ++c)
|
||||
if (c < n && lane == c) dst[(size_t) (e + c) * L.n_ff + r] = s[c];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -995,25 +1157,148 @@ __global__ void __launch_bounds__(256) native_down_multi_kernel(const unsigned l
|
||||
const int32_t* __restrict__ ent_dst,
|
||||
const block_q8_1* __restrict__ hq, NativeExpertLayout L,
|
||||
float* __restrict__ out) {
|
||||
const int ng = *n_groups;
|
||||
if (blockIdx.y >= ng) return;
|
||||
__shared__ uint32_t s_hq_buf[GRP_NC * 20 * 9];
|
||||
const int nb = (int) (L.n_ff / Fmt<TD>::qk), hb = (int) (L.n_ff / 32);
|
||||
if constexpr (TD == 20) {
|
||||
if (hb == 20) {
|
||||
const int r = blockIdx.x * 32 + (threadIdx.x >> 3);
|
||||
const int t = threadIdx.x & 7;
|
||||
const bool valid_r = (r < L.n_embd);
|
||||
const size_t w_off = L.down_off + (size_t) (valid_r ? r : 0) * L.d_row;
|
||||
const block_q8_1* s_hq = reinterpret_cast<const block_q8_1*>(s_hq_buf);
|
||||
for (int g = blockIdx.y; g < ng; g += gridDim.y) {
|
||||
const uint8_t* wr = (const uint8_t*) grp_ptr[g] + w_off;
|
||||
const int e0 = grp_start[g], e1 = grp_start[g + 1];
|
||||
for (int e = e0; e < e1; e += GRP_NC) {
|
||||
const int n = min(GRP_NC, e1 - e);
|
||||
const uint32_t* src = reinterpret_cast<const uint32_t*>(hq + (size_t) e * 20);
|
||||
const int words = n * (20 * 9);
|
||||
__syncthreads();
|
||||
for (int i = threadIdx.x; i < words; i += 256) s_hq_buf[i] = src[i];
|
||||
__syncthreads();
|
||||
if (valid_r) {
|
||||
if (n == 1) {
|
||||
const int off1[1] = { 0 };
|
||||
float s1[1];
|
||||
row_dot_40_sub8<TD, 1, true>(wr, s_hq, off1, 1, t, s1);
|
||||
if (t == 0) out[(size_t) ent_dst[e] * L.n_embd + r] = s1[0];
|
||||
} else if (n == 2) {
|
||||
const int off2[2] = { 0, 20 };
|
||||
float s2[2];
|
||||
row_dot_40_sub8<TD, 2, true>(wr, s_hq, off2, 2, t, s2);
|
||||
if (t < 2) out[(size_t) ent_dst[e + t] * L.n_embd + r] = s2[t];
|
||||
} else {
|
||||
int off[GRP_NC];
|
||||
#pragma unroll
|
||||
for (int c = 0; c < GRP_NC; ++c) off[c] = min(c, n - 1) * 20;
|
||||
float s[GRP_NC];
|
||||
row_dot_40_sub8<TD, GRP_NC>(wr, s_hq, off, n, t, s);
|
||||
#pragma unroll
|
||||
for (int c = 0; c < GRP_NC; ++c)
|
||||
if (c < n && t == c) out[(size_t) ent_dst[e + c] * L.n_embd + r] = s[c];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else if constexpr (TD == 42) {
|
||||
if (hb == 20) {
|
||||
const int r = blockIdx.x * 16 + (threadIdx.x >> 4);
|
||||
const int t = threadIdx.x & 15;
|
||||
const bool valid_r = (r < L.n_embd);
|
||||
const size_t w_off = L.down_off + (size_t) (valid_r ? r : 0) * L.d_row;
|
||||
const block_q8_1* s_hq = reinterpret_cast<const block_q8_1*>(s_hq_buf);
|
||||
for (int g = blockIdx.y; g < ng; g += gridDim.y) {
|
||||
const uint8_t* wr = (const uint8_t*) grp_ptr[g] + w_off;
|
||||
const int e0 = grp_start[g], e1 = grp_start[g + 1];
|
||||
for (int e = e0; e < e1; e += GRP_NC) {
|
||||
const int n = min(GRP_NC, e1 - e);
|
||||
const uint32_t* src = reinterpret_cast<const uint32_t*>(hq + (size_t) e * 20);
|
||||
const int words = n * (20 * 9);
|
||||
__syncthreads();
|
||||
for (int i = threadIdx.x; i < words; i += 256) s_hq_buf[i] = src[i];
|
||||
__syncthreads();
|
||||
if (valid_r) {
|
||||
if (n == 1) {
|
||||
const int off1[1] = { 0 };
|
||||
float s1[1];
|
||||
row_dot_20_sub16<TD, 1, true>(wr, s_hq, off1, 1, t, s1);
|
||||
if (t == 0) out[(size_t) ent_dst[e] * L.n_embd + r] = s1[0];
|
||||
} else if (n == 2) {
|
||||
const int off2[2] = { 0, 20 };
|
||||
float s2[2];
|
||||
row_dot_20_sub16<TD, 2, true>(wr, s_hq, off2, 2, t, s2);
|
||||
if (t < 2) out[(size_t) ent_dst[e + t] * L.n_embd + r] = s2[t];
|
||||
} else {
|
||||
int off[GRP_NC];
|
||||
#pragma unroll
|
||||
for (int c = 0; c < GRP_NC; ++c) off[c] = min(c, n - 1) * 20;
|
||||
float s[GRP_NC];
|
||||
row_dot_20_sub16<TD, GRP_NC>(wr, s_hq, off, n, t, s);
|
||||
#pragma unroll
|
||||
for (int c = 0; c < GRP_NC; ++c)
|
||||
if (c < n && t == c) out[(size_t) ent_dst[e + c] * L.n_embd + r] = s[c];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
const int warp = threadIdx.x >> 5, lane = threadIdx.x & 31;
|
||||
const int r = blockIdx.x * 8 + warp;
|
||||
if (r >= L.n_embd) return;
|
||||
const size_t w_off = L.down_off + (size_t) r * L.d_row;
|
||||
const int nb = (int) (L.n_ff / Fmt<TD>::qk), hb = (int) (L.n_ff / 32);
|
||||
const int ng = *n_groups;
|
||||
const bool valid_r = (r < L.n_embd);
|
||||
const size_t w_off = L.down_off + (size_t) (valid_r ? r : 0) * L.d_row;
|
||||
for (int g = blockIdx.y; g < ng; g += gridDim.y) { // the group stride, as native_gu_kernel
|
||||
const uint8_t* wr = (const uint8_t*) grp_ptr[g] + w_off;
|
||||
const int e0 = grp_start[g], e1 = grp_start[g + 1];
|
||||
for (int e = e0; e < e1; e += GRP_NC) {
|
||||
const int n = min(GRP_NC, e1 - e);
|
||||
int off[GRP_NC];
|
||||
if (hb == 20) {
|
||||
const block_q8_1* s_hq = reinterpret_cast<const block_q8_1*>(s_hq_buf);
|
||||
for (int e = e0; e < e1; e += GRP_NC) {
|
||||
const int n = min(GRP_NC, e1 - e);
|
||||
const uint32_t* src = reinterpret_cast<const uint32_t*>(hq + (size_t) e * 20);
|
||||
const int words = n * (20 * 9);
|
||||
__syncthreads();
|
||||
for (int i = threadIdx.x; i < words; i += 256) s_hq_buf[i] = src[i];
|
||||
__syncthreads();
|
||||
if (valid_r) {
|
||||
if (n == 1) {
|
||||
const int off1[1] = { 0 };
|
||||
float s1[1];
|
||||
row_dot_multi<TD, 1, true>(wr, s_hq, off1, 1, nb, lane, s1);
|
||||
if (lane == 0) out[(size_t) ent_dst[e] * L.n_embd + r] = s1[0];
|
||||
} else if (n == 2) {
|
||||
const int off2[2] = { 0, 20 };
|
||||
float s2[2];
|
||||
row_dot_multi<TD, 2, true>(wr, s_hq, off2, 2, nb, lane, s2);
|
||||
if (lane < 2) out[(size_t) ent_dst[e + lane] * L.n_embd + r] = s2[lane];
|
||||
} else {
|
||||
int off[GRP_NC];
|
||||
#pragma unroll
|
||||
for (int c = 0; c < GRP_NC; ++c) off[c] = (e + min(c, n - 1)) * hb;
|
||||
float s[GRP_NC];
|
||||
row_dot_multi<TD, GRP_NC>(wr, hq, off, n, nb, lane, s);
|
||||
for (int c = 0; c < GRP_NC; ++c) off[c] = min(c, n - 1) * 20;
|
||||
float s[GRP_NC];
|
||||
row_dot_multi<TD, GRP_NC>(wr, s_hq, off, n, nb, lane, s);
|
||||
#pragma unroll
|
||||
for (int c = 0; c < GRP_NC; ++c)
|
||||
if (c < n && lane == c) out[(size_t) ent_dst[e + c] * L.n_embd + r] = s[c];
|
||||
for (int c = 0; c < GRP_NC; ++c)
|
||||
if (c < n && lane == c) out[(size_t) ent_dst[e + c] * L.n_embd + r] = s[c];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (valid_r) {
|
||||
for (int e = e0; e < e1; e += GRP_NC) {
|
||||
const int n = min(GRP_NC, e1 - e);
|
||||
int off[GRP_NC];
|
||||
#pragma unroll
|
||||
for (int c = 0; c < GRP_NC; ++c) off[c] = (e + min(c, n - 1)) * hb;
|
||||
float s[GRP_NC];
|
||||
row_dot_multi<TD, GRP_NC>(wr, hq, off, n, nb, lane, s);
|
||||
#pragma unroll
|
||||
for (int c = 0; c < GRP_NC; ++c)
|
||||
if (c < n && lane == c) out[(size_t) ent_dst[e + c] * L.n_embd + r] = s[c];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1571,7 +1856,8 @@ void native_expert_grouped(const NativeExpertLayout& L, const unsigned long long
|
||||
(int) L.n_ff, hq);
|
||||
}
|
||||
check("native_expert_grouped/swiglu");
|
||||
const dim3 gd((unsigned) ((L.n_embd + 7) / 8), (unsigned) gy);
|
||||
const int d_rows = (!g_old_kernels && L.n_ff == 640) ? (L.d_type == 20 ? 32 : (L.d_type == 42 ? 16 : 8)) : 8;
|
||||
const dim3 gd((unsigned) ((L.n_embd + d_rows - 1) / d_rows), (unsigned) gy);
|
||||
switch (L.d_type) {
|
||||
#define STRATA_DOWN(T) case T: launch_down<T>(gd, s, grp_ptr, grp_start, n_groups, ent_dst, hq, L, out); break;
|
||||
STRATA_D_FMTS(STRATA_DOWN)
|
||||
|
||||
@@ -998,7 +998,7 @@ struct SmallTraits {
|
||||
bool g_multi_exact = true; // until the upstream layout is timed on an idle GPU (plan rule: default only what is measured)
|
||||
|
||||
template<typename F, int NCOLS, int NW, int ROWS>
|
||||
__launch_bounds__(NW * WARP, 1)
|
||||
__launch_bounds__(NW * WARP, (ROWS <= 2 ? 4 : 1))
|
||||
__global__ void native_mmvq_multi_kernel(const typename F::Block* __restrict__ w,
|
||||
const Q81Block* __restrict__ x,
|
||||
float* __restrict__ y, int n_in, int n_out) {
|
||||
@@ -1055,8 +1055,9 @@ void launch_multi_n(const void* weights, const void* x_q8_1, float* y, int n_in,
|
||||
}
|
||||
const dim3 threads(WARP, WARPS);
|
||||
if (n_in / F::DIV < F::BPI) {
|
||||
const unsigned blocks = unsigned((std::size_t(n_out) + WARPS - 1) / WARPS);
|
||||
native_mmvq_multi_kernel<F, NCOLS, WARPS, WARPS><<<blocks, threads, 0, s>>>(w, x, y, n_in, n_out);
|
||||
constexpr int ROWS = 2;
|
||||
const unsigned blocks = unsigned((std::size_t(n_out) + ROWS - 1) / ROWS);
|
||||
native_mmvq_multi_kernel<F, NCOLS, WARPS, ROWS><<<blocks, threads, 0, s>>>(w, x, y, n_in, n_out);
|
||||
} else {
|
||||
native_mmvq_multi_kernel<F, NCOLS, WARPS, 1><<<unsigned(n_out), threads, 0, s>>>(w, x, y, n_in, n_out);
|
||||
}
|
||||
|
||||
@@ -197,6 +197,7 @@ void ck(cudaError_t e, const char* what) {
|
||||
} // namespace
|
||||
|
||||
void ple_set_native_bf16(bool enabled) { native_bf16 = enabled; }
|
||||
bool ple_native_bf16_enabled() { return native_bf16; }
|
||||
void ple_set_native_postops(bool enabled) { native_postops = enabled; }
|
||||
bool ple_native_postops_enabled() { return native_postops; }
|
||||
|
||||
@@ -365,4 +366,21 @@ void ple_block(const float* emb, const float* hidden, const float* hist_rows, co
|
||||
// itself, and the engine's caller does not want that at all.
|
||||
}
|
||||
|
||||
void ple_block_projected(const float* projected_key, const float* projected_value, const float* hidden,
|
||||
const float* hist_rows, const PleWeights& w, PleOut& out, void* scratch, void* stream) {
|
||||
if (!projected_key || !projected_value || !hidden || !hist_rows || !out.result || !scratch) return;
|
||||
const int n_embd = NG_N_EMBD, hc = NG_HC, hc_dim = NG_HC_DIM;
|
||||
float* d_scratch = (float*) scratch;
|
||||
float* d_key = d_scratch;
|
||||
float* d_query = d_key + hc_dim;
|
||||
float* d_norm = d_query + hc_dim;
|
||||
float* d_gated = d_norm + hc_dim;
|
||||
float* d_conv = d_gated + hc_dim;
|
||||
float* d_value = d_conv + hc_dim;
|
||||
float* d_gate = d_value + n_embd;
|
||||
float* norm_dst = out.normalized ? out.normalized : d_norm;
|
||||
NativePlePostopsBuffers buffers{d_query, norm_dst, d_gate, d_gated, norm_dst, d_conv, out.result};
|
||||
native_ple_postops(projected_key, hidden, projected_value, hist_rows, w, buffers, stream);
|
||||
}
|
||||
|
||||
} // namespace strata::kernels
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "strata/kernels/s_gemv.hpp"
|
||||
#include "strata/kernels/native_mmvq.hpp"
|
||||
|
||||
#include <cuda_fp16.h>
|
||||
#include <cuda_runtime.h>
|
||||
|
||||
#include <cmath>
|
||||
@@ -157,13 +158,49 @@ __global__ void moe_combine_kernel(const float* __restrict__ parts, const float*
|
||||
} // namespace
|
||||
|
||||
void shared_expert_set_native_bf16(bool enabled) { native_bf16 = enabled; }
|
||||
bool shared_expert_native_bf16_enabled() { return native_bf16; }
|
||||
|
||||
namespace {
|
||||
struct SharedQ81Block {
|
||||
half2 ds;
|
||||
int8_t qs[32];
|
||||
};
|
||||
|
||||
__launch_bounds__(256, 1)
|
||||
__global__ void native_swiglu_quantize_q8_1_kernel(const float* __restrict__ gate,
|
||||
const float* __restrict__ up,
|
||||
SharedQ81Block* __restrict__ y, int n_in) {
|
||||
const int i = int(blockIdx.x) * 256 + int(threadIdx.x);
|
||||
if (i >= n_in) return;
|
||||
const float gi = gate[i];
|
||||
const float xi = __fdividef(gi, 1.0f + __expf(-gi)) * up[i];
|
||||
float amax = fabsf(xi);
|
||||
float sum = xi;
|
||||
#pragma unroll
|
||||
for (int offset = 16; offset > 0; offset >>= 1) {
|
||||
amax = fmaxf(amax, __shfl_xor_sync(0xffffffffu, amax, offset, 32));
|
||||
sum += __shfl_xor_sync(0xffffffffu, sum, offset, 32);
|
||||
}
|
||||
const float d = amax / 127.0f;
|
||||
const int8_t q = amax == 0.0f ? 0 : roundf(xi / d);
|
||||
y[i / 32].qs[i % 32] = q;
|
||||
if ((i & 31) == 0) y[i / 32].ds = make_half2(d, sum);
|
||||
}
|
||||
|
||||
__global__ void scale_rows_kernel(float* __restrict__ out, const float* __restrict__ g, int n) {
|
||||
const int t = blockIdx.y;
|
||||
const int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) out[(size_t) t * n + i] *= g[t];
|
||||
}
|
||||
|
||||
__global__ void sigmoid_scale_rows_kernel(float* __restrict__ out, const float* __restrict__ g, int n) {
|
||||
const int t = blockIdx.y;
|
||||
const int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
const float gt = __fdividef(1.0f, 1.0f + __expf(-g[t]));
|
||||
out[(size_t) t * n + i] *= gt;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void shared_expert_multi(int n_tok, const float* x, const uint16_t* x_bf16, const NativeSharedWeights& nw,
|
||||
@@ -172,28 +209,38 @@ void shared_expert_multi(int n_tok, const float* x, const uint16_t* x_bf16, cons
|
||||
if (n_tok < 1 || n_tok > 8 || !nw.q8_1 || !nw.gate_data || !nw.up_data || !nw.down_data || !stream)
|
||||
throw std::invalid_argument("shared_expert_multi: needs 1..8 tokens, native weights, scratch and a stream");
|
||||
cudaStream_t cs = (cudaStream_t) stream;
|
||||
native_quantize_q8_1(x, nw.q8_1, (int) n_embd, n_tok, stream);
|
||||
native_mmvq(nw.gate_type, nw.gate_data, nw.q8_1, gate, (int) n_embd, (int) n_ff, n_tok, stream);
|
||||
native_mmvq(nw.up_type, nw.up_data, nw.q8_1, up, (int) n_embd, (int) n_ff, n_tok, stream);
|
||||
const void* x_q8_1 = nw.x_q8_1;
|
||||
if (!x_q8_1) {
|
||||
native_quantize_q8_1(x, nw.q8_1, (int) n_embd, n_tok, stream);
|
||||
x_q8_1 = nw.q8_1;
|
||||
}
|
||||
native_mmvq(nw.gate_type, nw.gate_data, x_q8_1, gate, (int) n_embd, (int) n_ff, n_tok, stream);
|
||||
native_mmvq(nw.up_type, nw.up_data, x_q8_1, up, (int) n_embd, (int) n_ff, n_tok, stream);
|
||||
const int n = (int) (n_ff * n_tok);
|
||||
native_swiglu_kernel<<<(unsigned) ((n + THREADS - 1) / THREADS), THREADS, 0, cs>>>(gate, up, gate, n);
|
||||
native_quantize_q8_1(gate, nw.q8_1, (int) n_ff, n_tok, stream);
|
||||
native_swiglu_quantize_q8_1_kernel<<<(unsigned) ((n + 255) / 256), 256, 0, cs>>>(
|
||||
gate, up, (SharedQ81Block*) nw.q8_1, n);
|
||||
native_mmvq(nw.down_type, nw.down_data, nw.q8_1, out, (int) n_ff, (int) n_embd, n_tok, stream);
|
||||
static const bool batch = [] { const char* v = std::getenv("STRATA_DEC_BATCH"); return v == nullptr || std::atoi(v) != 0; }();
|
||||
if (native_bf16 && batch && n_tok > 1) { // one gemv for all rows (outputs identical), one sigmoid launch
|
||||
if (native_bf16 && batch && n_tok > 1) { // one gemv for all rows (outputs identical), fused sigmoid+scale
|
||||
bf16_gemv_fp32_mmvf_multi(x, n_embd, gate_inp_bf16, g, 1, n_embd, 1, n_tok, stream);
|
||||
native_scalar_sigmoid_multi_kernel<<<1, n_tok, 0, cs>>>(g);
|
||||
} else
|
||||
for (int t = 0; t < n_tok; ++t) {
|
||||
if (native_bf16) {
|
||||
bf16_gemv_fp32_mmvf(x + (size_t) t * n_embd, gate_inp_bf16, g + t, n_embd, 1, stream);
|
||||
native_scalar_sigmoid_kernel<<<1, 1, 0, cs>>>(g + t);
|
||||
} else {
|
||||
scalar_gate_kernel<<<1, 256, 0, cs>>>(x_bf16 + (size_t) t * n_embd, gate_inp_bf16, g + t, (int) n_embd);
|
||||
sigmoid_scale_rows_kernel<<<dim3((unsigned) ((n_embd + THREADS - 1) / THREADS), (unsigned) n_tok), THREADS, 0, cs>>>(
|
||||
out, g, (int) n_embd);
|
||||
} else if (native_bf16 && n_tok == 1) {
|
||||
bf16_gemv_fp32_mmvf(x, gate_inp_bf16, g, n_embd, 1, stream);
|
||||
sigmoid_scale_rows_kernel<<<dim3((unsigned) ((n_embd + THREADS - 1) / THREADS), 1u), THREADS, 0, cs>>>(
|
||||
out, g, (int) n_embd);
|
||||
} else {
|
||||
for (int t = 0; t < n_tok; ++t) {
|
||||
if (native_bf16) {
|
||||
bf16_gemv_fp32_mmvf(x + (size_t) t * n_embd, gate_inp_bf16, g + t, n_embd, 1, stream);
|
||||
native_scalar_sigmoid_kernel<<<1, 1, 0, cs>>>(g + t);
|
||||
} else {
|
||||
scalar_gate_kernel<<<1, 256, 0, cs>>>(x_bf16 + (size_t) t * n_embd, gate_inp_bf16, g + t, (int) n_embd);
|
||||
}
|
||||
}
|
||||
scale_rows_kernel<<<dim3((unsigned) ((n_embd + THREADS - 1) / THREADS), (unsigned) n_tok), THREADS, 0, cs>>>(
|
||||
out, g, (int) n_embd);
|
||||
}
|
||||
scale_rows_kernel<<<dim3((unsigned) ((n_embd + THREADS - 1) / THREADS), (unsigned) n_tok), THREADS, 0, cs>>>(
|
||||
out, g, (int) n_embd);
|
||||
const cudaError_t e = cudaGetLastError();
|
||||
if (e != cudaSuccess) throw std::runtime_error(std::string("shared_expert_multi: ") + cudaGetErrorString(e));
|
||||
}
|
||||
@@ -273,31 +320,37 @@ void shared_expert(const uint8_t* x_q8_0, const uint8_t* x_q8k, const uint16_t*
|
||||
native_mmvq(native->up_type, native->up_data, native->q8_1, up, (int) n_embd, (int) n_ff, 1, stream);
|
||||
else
|
||||
gemv(up_form, up_codes, up_scales, up_off, x_q8_0, x_q8k, up, n_embd, n_ff);
|
||||
if (native_projection)
|
||||
native_swiglu_kernel<<<g_ff, THREADS, 0, (cudaStream_t) stream>>>(gate, up, gate, (int) n_ff);
|
||||
else
|
||||
swiglu_kernel<<<g_ff, THREADS, 0, (cudaStream_t) stream>>>(gate, up, gate, (int) n_ff);
|
||||
|
||||
// down: (n_ff) -> (n_embd), and THE INTERMEDIATE IS QUANTIZED TO THE DOWN WEIGHT'S OWN CONTRACT - which is
|
||||
// what `ggml_mul_mat` does for every matmul in the model. It used to be rounded to fp16 with no
|
||||
// justification beyond "the kernel takes fp16".
|
||||
if (native_down) {
|
||||
native_quantize_q8_1(gate, native->q8_1, (int) n_ff, 1, stream);
|
||||
if (native_projection && native_down) {
|
||||
native_swiglu_quantize_q8_1_kernel<<<(unsigned) ((n_ff + 255) / 256), 256, 0, (cudaStream_t) stream>>>(
|
||||
gate, up, (SharedQ81Block*) native->q8_1, (int) n_ff);
|
||||
native_mmvq(native->down_type, native->down_data, native->q8_1, out, (int) n_ff, (int) n_embd, 1, stream);
|
||||
} else if (down_form.act_kind == 1) {
|
||||
if (n_ff % 256 != 0) {
|
||||
std::fprintf(stderr, "shared_expert: the down weight wants Q8_K but n_ff %lld is not a multiple "
|
||||
"of 256; Q8_K is structurally impossible here\n", (long long) n_ff);
|
||||
std::exit(1);
|
||||
}
|
||||
quantize_q8_K(gate, h_q8k, n_ff, stream);
|
||||
gemv(down_form, down_codes, down_scales, down_off, h_q8_0, h_q8k, out, n_ff, n_embd);
|
||||
} else if (down_form.code_bits == 2) {
|
||||
quantize_q8_0(gate, h_q8_0, n_ff, stream);
|
||||
gemv(down_form, down_codes, down_scales, down_off, h_q8_0, h_q8k, out, n_ff, n_embd);
|
||||
} else {
|
||||
quantize_q8_0(gate, h_q8_0, n_ff, stream);
|
||||
gemv(down_form, down_codes, down_scales, down_off, h_q8_0, h_q8k, out, n_ff, n_embd);
|
||||
if (native_projection)
|
||||
native_swiglu_kernel<<<g_ff, THREADS, 0, (cudaStream_t) stream>>>(gate, up, gate, (int) n_ff);
|
||||
else
|
||||
swiglu_kernel<<<g_ff, THREADS, 0, (cudaStream_t) stream>>>(gate, up, gate, (int) n_ff);
|
||||
|
||||
// down: (n_ff) -> (n_embd), and THE INTERMEDIATE IS QUANTIZED TO THE DOWN WEIGHT'S OWN CONTRACT - which is
|
||||
// what `ggml_mul_mat` does for every matmul in the model. It used to be rounded to fp16 with no
|
||||
// justification beyond "the kernel takes fp16".
|
||||
if (native_down) {
|
||||
native_quantize_q8_1(gate, native->q8_1, (int) n_ff, 1, stream);
|
||||
native_mmvq(native->down_type, native->down_data, native->q8_1, out, (int) n_ff, (int) n_embd, 1, stream);
|
||||
} else if (down_form.act_kind == 1) {
|
||||
if (n_ff % 256 != 0) {
|
||||
std::fprintf(stderr, "shared_expert: the down weight wants Q8_K but n_ff %lld is not a multiple "
|
||||
"of 256; Q8_K is structurally impossible here\n", (long long) n_ff);
|
||||
std::exit(1);
|
||||
}
|
||||
quantize_q8_K(gate, h_q8k, n_ff, stream);
|
||||
gemv(down_form, down_codes, down_scales, down_off, h_q8_0, h_q8k, out, n_ff, n_embd);
|
||||
} else if (down_form.code_bits == 2) {
|
||||
quantize_q8_0(gate, h_q8_0, n_ff, stream);
|
||||
gemv(down_form, down_codes, down_scales, down_off, h_q8_0, h_q8k, out, n_ff, n_embd);
|
||||
} else {
|
||||
quantize_q8_0(gate, h_q8_0, n_ff, stream);
|
||||
gemv(down_form, down_codes, down_scales, down_off, h_q8_0, h_q8k, out, n_ff, n_embd);
|
||||
}
|
||||
}
|
||||
|
||||
// the per-token scalar gate, then the multiply. Note the gate is computed from `x`, the ORIGINAL hidden
|
||||
@@ -307,11 +360,11 @@ void shared_expert(const uint8_t* x_q8_0, const uint8_t* x_q8k, const uint16_t*
|
||||
// round trip. 256 threads is the reduction's width, not the problem's size.
|
||||
if (use_native) {
|
||||
bf16_gemv_fp32_mmvf(x_f32, gate_inp_bf16, g, n_embd, 1, stream);
|
||||
native_scalar_sigmoid_kernel<<<1, 1, 0, (cudaStream_t) stream>>>(g);
|
||||
sigmoid_scale_rows_kernel<<<dim3(g_embd, 1u), THREADS, 0, (cudaStream_t) stream>>>(out, g, (int) n_embd);
|
||||
} else {
|
||||
scalar_gate_kernel<<<1, 256, 0, (cudaStream_t) stream>>>(x_bf16, gate_inp_bf16, g, (int) n_embd);
|
||||
scale_kernel<<<g_embd, THREADS, 0, (cudaStream_t) stream>>>(out, g, (int) n_embd);
|
||||
}
|
||||
scale_kernel<<<g_embd, THREADS, 0, (cudaStream_t) stream>>>(out, g, (int) n_embd);
|
||||
|
||||
if (stream == nullptr) {
|
||||
const cudaError_t e = cudaDeviceSynchronize();
|
||||
|
||||
@@ -67,23 +67,24 @@ __global__ void gdn_conv_commit_kernel(float* __restrict__ hist, const float* __
|
||||
hist[c * 3 + 2] = seq[2];
|
||||
}
|
||||
|
||||
__global__ void __launch_bounds__(256) gdn_ab_multi_kernel(const float* __restrict__ x, const uint16_t* __restrict__ wa,
|
||||
const uint16_t* __restrict__ wb,
|
||||
const float* __restrict__ dt,
|
||||
const float* __restrict__ ssm_a, float* __restrict__ gate,
|
||||
float* __restrict__ beta, int n, int h_v, int T) {
|
||||
const int row = blockIdx.x * 8 + (threadIdx.x >> 5), lane = threadIdx.x & 31;
|
||||
template <int MAX_T = kVerifyMaxT>
|
||||
__global__ void __launch_bounds__(64) gdn_ab_multi_kernel(const float* __restrict__ x, const uint16_t* __restrict__ wa,
|
||||
const uint16_t* __restrict__ wb,
|
||||
const float* __restrict__ dt,
|
||||
const float* __restrict__ ssm_a, float* __restrict__ gate,
|
||||
float* __restrict__ beta, int n, int h_v, int T) {
|
||||
const int row = blockIdx.x * 2 + (threadIdx.x >> 5), lane = threadIdx.x & 31;
|
||||
if (row >= 2 * h_v) return;
|
||||
const bool is_beta = row >= h_v;
|
||||
const int r = is_beta ? row - h_v : row;
|
||||
const uint4* w4 = reinterpret_cast<const uint4*>((is_beta ? wb : wa) + (size_t) r * n);
|
||||
float acc[kVerifyMaxT];
|
||||
float acc[MAX_T];
|
||||
#pragma unroll
|
||||
for (int t = 0; t < kVerifyMaxT; ++t) acc[t] = 0.0f;
|
||||
for (int t = 0; t < MAX_T; ++t) acc[t] = 0.0f;
|
||||
for (int j = lane; j < n / 8; j += 32) {
|
||||
const uint4 wv = __ldg(w4 + j);
|
||||
#pragma unroll
|
||||
for (int t = 0; t < kVerifyMaxT; ++t) {
|
||||
for (int t = 0; t < MAX_T; ++t) {
|
||||
if (t >= T) break;
|
||||
const float* xt = x + (size_t) t * n;
|
||||
const float4 xa = *reinterpret_cast<const float4*>(xt + j * 8);
|
||||
@@ -97,7 +98,7 @@ __global__ void __launch_bounds__(256) gdn_ab_multi_kernel(const float* __restri
|
||||
}
|
||||
}
|
||||
#pragma unroll
|
||||
for (int t = 0; t < kVerifyMaxT; ++t) {
|
||||
for (int t = 0; t < MAX_T; ++t) {
|
||||
if (t >= T) break;
|
||||
float a = acc[t];
|
||||
for (int o = 16; o > 0; o >>= 1) a += __shfl_xor_sync(0xffffffffu, a, o);
|
||||
@@ -112,6 +113,50 @@ __global__ void __launch_bounds__(256) gdn_ab_multi_kernel(const float* __restri
|
||||
}
|
||||
}
|
||||
|
||||
// State-only commit kernel: each head's 128 independent state columns are split across 4 blocks of 32 columns
|
||||
// (48 * 4 = 192 blocks of 128 threads across all SMs, vs 48 blocks of 512 threads), with no sq/o/norm/z/y work.
|
||||
__global__ void __launch_bounds__(32 * RG) gdn_step_commit_kernel(float* __restrict__ state,
|
||||
const float* __restrict__ hbuf, int C,
|
||||
const float* __restrict__ gate,
|
||||
const float* __restrict__ beta,
|
||||
int h_k, int h_v,
|
||||
const int32_t* __restrict__ n_keep) {
|
||||
const int n = *n_keep;
|
||||
if (n <= 0) return;
|
||||
__shared__ float sk[S];
|
||||
__shared__ float red[RG][32];
|
||||
const int head = blockIdx.x;
|
||||
const int col_local = threadIdx.x;
|
||||
const int col = blockIdx.y * 32 + col_local;
|
||||
const int rg = threadIdx.y;
|
||||
const int tid = rg * 32 + col_local; // 0 .. 127
|
||||
const int qh = head % h_k;
|
||||
const int qk = S * h_k;
|
||||
float s[RPG];
|
||||
float* base = state + ((size_t) (rg * RPG) * h_v + head) * S + col;
|
||||
const size_t row_stride = (size_t) h_v * S;
|
||||
#pragma unroll
|
||||
for (int r = 0; r < RPG; ++r) s[r] = base[r * row_stride];
|
||||
for (int t = 0; t < n; ++t) {
|
||||
const float* ht = hbuf + (size_t) t * C;
|
||||
__syncthreads();
|
||||
sk[tid] = ht[qk + qh * S + tid];
|
||||
__syncthreads();
|
||||
const float g = __expf(gate[(size_t) t * h_v + head]);
|
||||
float kv = 0.0f;
|
||||
#pragma unroll
|
||||
for (int r = 0; r < RPG; ++r) kv = fmaf(s[r], sk[rg * RPG + r], kv);
|
||||
red[rg][col_local] = kv;
|
||||
__syncthreads();
|
||||
const float kv_col = red[0][col_local] + red[1][col_local] + red[2][col_local] + red[3][col_local];
|
||||
const float delta = (ht[2 * qk + head * S + col] - g * kv_col) * beta[(size_t) t * h_v + head];
|
||||
#pragma unroll
|
||||
for (int r = 0; r < RPG; ++r) s[r] = fmaf(g, s[r], sk[rg * RPG + r] * delta);
|
||||
}
|
||||
#pragma unroll
|
||||
for (int r = 0; r < RPG; ++r) base[r * row_stride] = s[r];
|
||||
}
|
||||
|
||||
__global__ void __launch_bounds__(S * RG) gdn_step_norm_multi_kernel(float* __restrict__ state,
|
||||
const float* __restrict__ hbuf, int C,
|
||||
const float* __restrict__ gate,
|
||||
@@ -138,8 +183,12 @@ __global__ void __launch_bounds__(S * RG) gdn_step_norm_multi_kernel(float* __re
|
||||
for (int r = 0; r < RPG; ++r) s[r] = base[r * row_stride];
|
||||
for (int t = 0; t < n; ++t) {
|
||||
const float* ht = hbuf + (size_t) t * C;
|
||||
const bool need_out = (t >= t_out_begin);
|
||||
__syncthreads(); // the previous token is done with sk/sq/red/wsum
|
||||
if (tid < S) { sk[tid] = ht[qk + qh * S + tid]; sq[tid] = ht[qh * S + tid]; }
|
||||
if (tid < S) {
|
||||
sk[tid] = ht[qk + qh * S + tid];
|
||||
if (need_out) sq[tid] = ht[qh * S + tid];
|
||||
}
|
||||
__syncthreads();
|
||||
const float g = __expf(gate[(size_t) t * h_v + head]);
|
||||
float kv = 0.0f;
|
||||
@@ -149,6 +198,11 @@ __global__ void __launch_bounds__(S * RG) gdn_step_norm_multi_kernel(float* __re
|
||||
__syncthreads();
|
||||
const float kv_col = red[0][col] + red[1][col] + red[2][col] + red[3][col];
|
||||
const float delta = (ht[2 * qk + head * S + col] - g * kv_col) * beta[(size_t) t * h_v + head];
|
||||
if (!need_out) {
|
||||
#pragma unroll
|
||||
for (int r = 0; r < RPG; ++r) s[r] = fmaf(g, s[r], sk[rg * RPG + r] * delta);
|
||||
continue;
|
||||
}
|
||||
float o = 0.0f;
|
||||
#pragma unroll
|
||||
for (int r = 0; r < RPG; ++r) {
|
||||
@@ -163,7 +217,6 @@ __global__ void __launch_bounds__(S * RG) gdn_step_norm_multi_kernel(float* __re
|
||||
oc = (red[0][col] + red[1][col] + red[2][col] + red[3][col]) * rsqrtf((float) S);
|
||||
sq_part = oc * oc;
|
||||
}
|
||||
if (t < t_out_begin) continue; // a replayed token: its state update is needed, its output is not
|
||||
for (int o2 = 16; o2 > 0; o2 >>= 1) sq_part += __shfl_xor_sync(0xffffffffu, sq_part, o2);
|
||||
if ((tid & 31) == 0) wsum[tid >> 5] = sq_part;
|
||||
__syncthreads();
|
||||
@@ -285,11 +338,13 @@ __global__ void mtp_select_kernel(const float* __restrict__ R_src, int64_t strid
|
||||
const int row = *row_dev;
|
||||
for (int64_t i = (int64_t) blockIdx.x * blockDim.x + threadIdx.x; i < stride; i += (int64_t) gridDim.x * blockDim.x)
|
||||
R_dst[i] = R_src[(size_t) row * stride + i];
|
||||
__syncthreads();
|
||||
if (blockIdx.x == 0 && threadIdx.x == 0) {
|
||||
const int32_t tok = ids[row];
|
||||
*tok_dst = tok;
|
||||
if (out != nullptr) ((volatile int32_t*) out)[j] = tok;
|
||||
if (probs != nullptr && out_p != nullptr) ((volatile float*) out_p)[j] = probs[row];
|
||||
__threadfence_system();
|
||||
if (out != nullptr) ((volatile int32_t*) out)[j] = tok;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,8 +386,8 @@ void ident_hits(const int32_t* ids, int n, int32_t* slot, int32_t* dst, int32_t*
|
||||
|
||||
void mtp_select(const float* R_src, int64_t R_stride, const int32_t* ids, const int32_t* row_dev, float* R_dst,
|
||||
int32_t* tok_dst, int32_t* out, int j, void* stream, const float* probs, float* out_p) {
|
||||
mtp_select_kernel<<<16, 256, 0, (cudaStream_t) stream>>>(R_src, R_stride, ids, row_dev, R_dst, tok_dst, out, j,
|
||||
probs, out_p);
|
||||
mtp_select_kernel<<<1, 256, 0, (cudaStream_t) stream>>>(R_src, R_stride, ids, row_dev, R_dst, tok_dst, out, j,
|
||||
probs, out_p);
|
||||
check("mtp_select");
|
||||
}
|
||||
|
||||
@@ -404,8 +459,14 @@ void gdn_ab_multi(const float* x, const uint16_t* w_alpha, const uint16_t* w_bet
|
||||
std::fprintf(stderr, "gdn_ab_multi: invalid arguments\n");
|
||||
std::exit(1);
|
||||
}
|
||||
gdn_ab_multi_kernel<<<(unsigned) ((2 * h_v + 7) / 8), 256, 0, (cudaStream_t) stream>>>(
|
||||
x, w_alpha, w_beta, dt, ssm_a, gate, beta, n_embd, h_v, n_tok);
|
||||
const unsigned blocks = (unsigned) ((2 * h_v + 1) / 2);
|
||||
if (n_tok <= 4) {
|
||||
gdn_ab_multi_kernel<4><<<blocks, 64, 0, (cudaStream_t) stream>>>(
|
||||
x, w_alpha, w_beta, dt, ssm_a, gate, beta, n_embd, h_v, n_tok);
|
||||
} else {
|
||||
gdn_ab_multi_kernel<kVerifyMaxT><<<blocks, 64, 0, (cudaStream_t) stream>>>(
|
||||
x, w_alpha, w_beta, dt, ssm_a, gate, beta, n_embd, h_v, n_tok);
|
||||
}
|
||||
check("gdn_ab_multi");
|
||||
}
|
||||
|
||||
@@ -417,8 +478,13 @@ void gdn_step_norm_multi(float* state, const float* h, int conv_channels, const
|
||||
std::fprintf(stderr, "gdn_step_norm_multi: invalid arguments\n");
|
||||
std::exit(1);
|
||||
}
|
||||
gdn_step_norm_multi_kernel<<<(unsigned) h_v, dim3(S, RG), 0, (cudaStream_t) stream>>>(
|
||||
state, h, conv_channels, gate, beta, z, gamma, eps, y, h_k, h_v, n_tok, n_keep, t_out_begin);
|
||||
if (n_keep != nullptr && t_out_begin >= n_tok) {
|
||||
gdn_step_commit_kernel<<<dim3((unsigned) h_v, 4u), dim3(32, RG), 0, (cudaStream_t) stream>>>(
|
||||
state, h, conv_channels, gate, beta, h_k, h_v, n_keep);
|
||||
} else {
|
||||
gdn_step_norm_multi_kernel<<<(unsigned) h_v, dim3(S, RG), 0, (cudaStream_t) stream>>>(
|
||||
state, h, conv_channels, gate, beta, z, gamma, eps, y, h_k, h_v, n_tok, n_keep, t_out_begin);
|
||||
}
|
||||
check("gdn_step_norm_multi");
|
||||
}
|
||||
|
||||
@@ -434,11 +500,50 @@ __global__ void resident_plan_kernel(const int32_t* __restrict__ ids, int n, int
|
||||
int n_expert, const uint8_t* cache_base, const unsigned long long* slot_off,
|
||||
long long blob, int32_t* __restrict__ pl, long long capx, uint32_t* skip,
|
||||
uint32_t ring) {
|
||||
// one thread: at most kVerifyMaxT * 10 entries, the host's exact loop
|
||||
for (int i = 0; i < n; ++i) {
|
||||
const int32_t e = ids[i];
|
||||
if (e < 0 || e >= n_expert || res[e] < 0) { *skip = 0; return; }
|
||||
__shared__ int32_t s_ids[64];
|
||||
__shared__ unsigned long long s_ptr[64];
|
||||
__shared__ int32_t s_first[64];
|
||||
__shared__ int32_t s_cnt[64];
|
||||
__shared__ int32_t s_gstart[64];
|
||||
__shared__ int s_bad;
|
||||
const int tid = threadIdx.x;
|
||||
if (tid == 0) s_bad = 0;
|
||||
__syncthreads();
|
||||
|
||||
int32_t eid = -1;
|
||||
if (tid < n) {
|
||||
eid = ids[tid];
|
||||
s_ids[tid] = eid;
|
||||
const int32_t slot = (eid >= 0 && eid < n_expert) ? res[eid] : -1;
|
||||
if (slot < 0) {
|
||||
atomicOr(&s_bad, 1);
|
||||
} else {
|
||||
s_ptr[tid] = (unsigned long long) (cache_base + (slot_off ? (size_t) slot_off[slot] : (size_t) slot * (size_t) blob));
|
||||
}
|
||||
}
|
||||
__syncthreads();
|
||||
if (s_bad) {
|
||||
if (tid == 0 && skip != nullptr) *skip = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
int first_j = tid;
|
||||
int rank_in_group = 0;
|
||||
int count_same = 0;
|
||||
if (tid < n) {
|
||||
for (int j = 0; j < n; ++j) {
|
||||
if (s_ids[j] == eid) {
|
||||
if (j < first_j) first_j = j;
|
||||
if (j < tid) ++rank_in_group;
|
||||
++count_same;
|
||||
}
|
||||
}
|
||||
const bool is_first = (first_j == tid);
|
||||
s_first[tid] = is_first ? 1 : 0;
|
||||
s_cnt[tid] = is_first ? count_same : 0;
|
||||
}
|
||||
__syncthreads();
|
||||
|
||||
int32_t* counts = pl;
|
||||
int32_t* start = pl + 4;
|
||||
int32_t* dst = start + capx + 1;
|
||||
@@ -446,30 +551,38 @@ __global__ void resident_plan_kernel(const int32_t* __restrict__ ids, int n, int
|
||||
const long long ptr_off = ((4 + (capx + 1) + 2 * capx) + 1) & ~1ll;
|
||||
unsigned long long* ptr = (unsigned long long*) (pl + ptr_off);
|
||||
int32_t* start2 = pl + ptr_off + 4 * capx;
|
||||
int groups = 0, entries = 0;
|
||||
for (int i0 = 0; i0 < n; ++i0) {
|
||||
bool first = true;
|
||||
for (int j = 0; j < i0; ++j) if (ids[j] == ids[i0]) { first = false; break; }
|
||||
if (!first) continue;
|
||||
const int32_t slot = res[ids[i0]];
|
||||
ptr[groups] = (unsigned long long) (cache_base + (slot_off ? (size_t) slot_off[slot] : (size_t) slot * (size_t) blob));
|
||||
start[groups] = entries;
|
||||
for (int i = i0; i < n; ++i)
|
||||
if (ids[i] == ids[i0]) {
|
||||
// an entry belongs to i0's group when its first occurrence is i0: the same expert id
|
||||
dst[entries] = i;
|
||||
tok[entries] = i / k;
|
||||
++entries;
|
||||
}
|
||||
++groups;
|
||||
|
||||
if (tid < n && first_j == tid) {
|
||||
int grp_idx = 0;
|
||||
int ent_start = 0;
|
||||
for (int j = 0; j < tid; ++j) {
|
||||
grp_idx += s_first[j];
|
||||
ent_start += s_cnt[j];
|
||||
}
|
||||
ptr[grp_idx] = s_ptr[tid];
|
||||
start[grp_idx] = ent_start;
|
||||
s_gstart[tid] = ent_start;
|
||||
}
|
||||
__syncthreads();
|
||||
|
||||
if (tid < n) {
|
||||
const int out_idx = s_gstart[first_j] + rank_in_group;
|
||||
dst[out_idx] = tid;
|
||||
tok[out_idx] = tid / k;
|
||||
}
|
||||
if (tid == 0) {
|
||||
int groups = 0;
|
||||
for (int j = 0; j < n; ++j) groups += s_first[j];
|
||||
start[groups] = n;
|
||||
start2[0] = n;
|
||||
counts[0] = groups;
|
||||
counts[1] = n;
|
||||
counts[2] = 0;
|
||||
if (skip != nullptr) {
|
||||
__threadfence();
|
||||
*skip = ring;
|
||||
}
|
||||
}
|
||||
start[groups] = entries;
|
||||
start2[0] = entries;
|
||||
counts[0] = groups;
|
||||
counts[1] = entries;
|
||||
counts[2] = 0;
|
||||
__threadfence();
|
||||
*skip = ring;
|
||||
}
|
||||
__global__ void wait_flag_ge_or_kernel(const volatile uint32_t* flag, uint32_t value, const volatile uint32_t* skip) {
|
||||
if (*skip == value) return;
|
||||
@@ -492,8 +605,8 @@ __global__ void copy_or_zero_kernel(float4* __restrict__ dst, const volatile flo
|
||||
void resident_plan(const int32_t* ids, int n_entries, int k, const int32_t* res_layer, int n_expert,
|
||||
const uint8_t* cache_base, const unsigned long long* slot_off, long long blob, int32_t* plan,
|
||||
long long capx, uint32_t* skip, uint32_t ring, void* stream) {
|
||||
resident_plan_kernel<<<1, 1, 0, (cudaStream_t) stream>>>(ids, n_entries, k, res_layer, n_expert, cache_base, slot_off,
|
||||
blob, plan, capx, skip, ring);
|
||||
resident_plan_kernel<<<1, 64, 0, (cudaStream_t) stream>>>(ids, n_entries, k, res_layer, n_expert, cache_base, slot_off,
|
||||
blob, plan, capx, skip, ring);
|
||||
check("resident_plan");
|
||||
}
|
||||
void wait_flag_ge_or(const uint32_t* flag, uint32_t value, const uint32_t* skip, void* stream) {
|
||||
|
||||
@@ -150,6 +150,11 @@ struct PleTable::Impl {
|
||||
bool locked = false;
|
||||
uint32_t rows[PLE_N_HEADS] = {};
|
||||
uint8_t raw[PLE_N_HEADS * PLE_ROW_BYTES_MAX] = {};
|
||||
static constexpr size_t kMaxPrefetch = 16;
|
||||
size_t n_prefetch = 0;
|
||||
strata::ngram::PleReader::Ticket prefetch_tickets[kMaxPrefetch] = {};
|
||||
uint32_t prefetch_keys[kMaxPrefetch][PLE_N_HEADS] = {};
|
||||
uint8_t prefetch_raw[kMaxPrefetch][PLE_N_HEADS * PLE_ROW_BYTES_MAX] = {};
|
||||
bool fp8 = false; // F8_E4M3 rows (tools/ple_fp8_pack.py); else IQ4_NL
|
||||
float scale = 1.0f; // the FP8 table's one scale
|
||||
uint32_t rb = PLE_ROW_BYTES; // bytes per row
|
||||
@@ -275,7 +280,40 @@ bool PleTable::open(const std::string& gguf_path, std::string& err, const PleIoO
|
||||
return true;
|
||||
}
|
||||
|
||||
void PleTable::wait_prefetches() {
|
||||
if (impl_->n_prefetch == 0) return;
|
||||
std::string dummy;
|
||||
for (size_t i = 0; i < impl_->n_prefetch; ++i) (void) impl_->reader.collect(impl_->prefetch_tickets[i], dummy);
|
||||
impl_->n_prefetch = 0;
|
||||
}
|
||||
|
||||
void PleTable::prefetch_rows(const uint32_t* rows16) {
|
||||
if (impl_->mode == PleIo::Direct && impl_->reader.is_open()) {
|
||||
for (size_t i = 0; i < impl_->n_prefetch; ++i)
|
||||
if (std::memcmp(impl_->prefetch_keys[i], rows16, sizeof(uint32_t) * PLE_N_HEADS) == 0) return;
|
||||
if (impl_->n_prefetch >= Impl::kMaxPrefetch) wait_prefetches();
|
||||
const size_t slot = impl_->n_prefetch++;
|
||||
std::memcpy(impl_->prefetch_keys[slot], rows16, sizeof(uint32_t) * PLE_N_HEADS);
|
||||
impl_->prefetch_tickets[slot] = impl_->reader.issue(impl_->prefetch_keys[slot], PLE_N_HEADS, impl_->prefetch_raw[slot]);
|
||||
return;
|
||||
}
|
||||
#if defined(_WIN32)
|
||||
if (impl_->data != nullptr && g_ple_prefetch) {
|
||||
WIN32_MEMORY_RANGE_ENTRY ranges[PLE_N_HEADS];
|
||||
ULONG_PTR n = 0;
|
||||
for (int h = 0; h < PLE_N_HEADS; ++h) {
|
||||
if (rows16[h] >= impl_->n_rows) continue;
|
||||
ranges[n].VirtualAddress = (PVOID) (impl_->data + (size_t) rows16[h] * impl_->rb);
|
||||
ranges[n].NumberOfBytes = impl_->rb;
|
||||
++n;
|
||||
}
|
||||
if (n > 0) (void) PrefetchVirtualMemory(GetCurrentProcess(), n, ranges, 0);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void PleTable::close() {
|
||||
wait_prefetches();
|
||||
impl_->reader.close();
|
||||
impl_->pending = false;
|
||||
impl_->locked = false; // the unmap below releases the lock
|
||||
@@ -318,6 +356,7 @@ void PleTable::read_row(uint32_t row, float* out160) const {
|
||||
}
|
||||
|
||||
bool PleTable::issue(const uint32_t* rows16) {
|
||||
wait_prefetches();
|
||||
std::memcpy(impl_->rows, rows16, sizeof impl_->rows);
|
||||
if (impl_->mode == PleIo::Direct) {
|
||||
if (impl_->pending) return false; // one token in flight per table
|
||||
@@ -360,6 +399,35 @@ bool PleTable::gather_batch(const uint32_t* rows, size_t n_tokens, float* out, s
|
||||
if (impl_->pending) { err = "PleTable::gather_batch while a token is in flight"; return false; }
|
||||
const size_t n = n_tokens * (size_t) PLE_N_HEADS;
|
||||
if (impl_->mode == PleIo::Direct) {
|
||||
// Fast path: if all requested tokens match our in-flight/completed prefetch slots, collect them directly
|
||||
// without issuing a second PleReader ticket or copying through RowCache.
|
||||
if (impl_->n_prefetch >= n_tokens && n_tokens <= Impl::kMaxPrefetch) {
|
||||
bool exact_match = true;
|
||||
for (size_t t = 0; t < n_tokens; ++t) {
|
||||
if (std::memcmp(impl_->prefetch_keys[t], rows + t * PLE_N_HEADS, sizeof(uint32_t) * PLE_N_HEADS) != 0) {
|
||||
exact_match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (exact_match) {
|
||||
for (size_t i = 0; i < impl_->n_prefetch; ++i) {
|
||||
if (!impl_->reader.collect(impl_->prefetch_tickets[i], err)) {
|
||||
impl_->n_prefetch = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
impl_->n_prefetch = 0;
|
||||
for (size_t t = 0; t < n_tokens; ++t) {
|
||||
for (int h = 0; h < PLE_N_HEADS; ++h) {
|
||||
impl_->decode(impl_->prefetch_raw[t] + (size_t) h * impl_->rb,
|
||||
out + (t * PLE_N_HEADS + (size_t) h) * PLE_HEAD_DIM);
|
||||
}
|
||||
}
|
||||
impl_->bytes_read += (uint64_t) n * impl_->rb;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
wait_prefetches();
|
||||
std::vector<uint8_t> raw(n * impl_->rb);
|
||||
const auto ticket = impl_->reader.issue(rows, n, raw.data());
|
||||
if (!impl_->reader.collect(ticket, err)) return false;
|
||||
@@ -367,6 +435,7 @@ bool PleTable::gather_batch(const uint32_t* rows, size_t n_tokens, float* out, s
|
||||
impl_->bytes_read += (uint64_t) n * impl_->rb;
|
||||
return true;
|
||||
}
|
||||
wait_prefetches();
|
||||
for (size_t i = 0; i < n; ++i) read_row(rows[i], out + i * PLE_HEAD_DIM);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2493,7 +2493,7 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
}
|
||||
}
|
||||
strata::core::Verifier::set_commit_async(!multi_gpu); // see Verifier::set_commit_async
|
||||
strata::core::Verifier::set_commit_async(!split_same); // see Verifier::set_commit_async
|
||||
// ---- layer split across GPUs: each later stage's own copy of the dense weights, its session and (the last) the
|
||||
// head, made on its device before the host arena is mapped (as the drafter below, for the same WDDM reason)
|
||||
std::vector<std::unique_ptr<GpuStage>> stages;
|
||||
@@ -2668,7 +2668,7 @@ int main(int argc, char** argv) {
|
||||
// those allocations are already made before a stage's cache is sized, so what has to be held back here is
|
||||
// the windows and - only on the stage that carries them - the drafter and the head.
|
||||
const int64_t kWindowMib = 96; // the verify windows; 75 MiB measured, rounded up
|
||||
const int64_t kDrafterMib = 1000; // the MTP drafter (839 MiB) + the head, on the last stage only
|
||||
const int64_t kDrafterMib = 1320; // the MTP drafter (839 MiB) + the native Q5_K head (466 MiB), on the last stage only
|
||||
// #340: with the own prompt buffers chosen by the split's rule (not asked for with --no-prefill-borrow) the
|
||||
// boundary is searched as the borrowing configuration would (no reserve): the reserve then only makes the caches
|
||||
// smaller, which measured cost no decode (K=28 on 9070 XT + R9700: 58.4 tok/s own vs 58.5 borrowing), while a
|
||||
@@ -2769,7 +2769,7 @@ int main(int argc, char** argv) {
|
||||
for (int i = 0; i + 1 < ns; ++i) {
|
||||
acc += 1.0 / layer_ms[(size_t) i];
|
||||
at[(size_t) i] = std::clamp<int64_t>((int64_t) std::llround(acc / total * (double) L),
|
||||
i == 0 ? 2 : at[(size_t) i - 1] + 1, L - (ns - 1 - i));
|
||||
i == 0 ? 2 : at[(size_t) i - 1] + 1, L - (ns - 1 - i));
|
||||
}
|
||||
consider();
|
||||
}
|
||||
@@ -2883,6 +2883,7 @@ int main(int argc, char** argv) {
|
||||
// with a layer split across GPUs the drafter reads the last stage's residual: it lives on that device
|
||||
const strata::core::OnDevice on_mtp(last_st ? last_st->dev : -1);
|
||||
if (!o.mtp.empty() && !mtp.load(o.mtp, draft_geometry, last_st ? last_st->ss : ss, o.spec, err, o.mtp_window)) { std::fprintf(stderr, "strata generate: %s%s\n", err.c_str(), vram_free_note().c_str()); return 1; }
|
||||
mtp.set_ple_session(&ss);
|
||||
}
|
||||
// THE HEAD BEFORE THE CACHE, AND BEFORE THE ARENA. The expert cache takes what is free minus the reserve, so
|
||||
// everything allocated after it comes out of the reserve. The native head (~0.5 GB with IQ3_S) was loaded after
|
||||
@@ -4749,6 +4750,7 @@ int main(int argc, char** argv) {
|
||||
strata::core::Verifier ver;
|
||||
strata::core::VerifyHits vh;
|
||||
vh.d_res = thits.d_res;
|
||||
vh.h_res = host_res.empty() ? nullptr : host_res.data();
|
||||
vh.cache_base = thits.cache_base;
|
||||
vh.blob = thits.blob;
|
||||
vh.slot_off = xcache.slot_offsets(); // E-6: the device plan's pointers
|
||||
@@ -4795,6 +4797,7 @@ int main(int argc, char** argv) {
|
||||
const strata::core::OnDevice on(gs.dev);
|
||||
strata::core::VerifyHits vs;
|
||||
vs.d_res = gs.d_res;
|
||||
vs.h_res = host_res.empty() ? nullptr : host_res.data();
|
||||
vs.cache_base = gs.cache.device_slot(0);
|
||||
vs.blob = thits.blob;
|
||||
vs.slot_off = gs.cache.slot_offsets();
|
||||
@@ -5049,11 +5052,14 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
drive.d.plan = ver.plan_sink();
|
||||
drive.d.pcie_num = std::max(0, std::min(256, (int) (o.pcie_frac * 256.0 + 0.5)));
|
||||
if (o.adapt_every > 0 && o.adapt_swaps > 0) drive.d.usage.assign((size_t) (g.n_layers * g.n_expert), 0.0f);
|
||||
const bool all_experts_resident = !host_res.empty() &&
|
||||
std::all_of(host_res.begin(), host_res.end(), [](int32_t r) { return r >= 0; });
|
||||
if (o.adapt_every > 0 && o.adapt_swaps > 0 && !all_experts_resident)
|
||||
drive.d.usage.assign((size_t) (g.n_layers * g.n_expert), 0.0f);
|
||||
// #477 --expert-profile-save: what the adaptive tier learned, kept across restarts (opt-in; off: `heat` stays
|
||||
// empty and nothing below runs). It needs the adaptive tier's counts and the residency table.
|
||||
std::vector<double> heat;
|
||||
if (!o.expert_profile_save.empty()) {
|
||||
if (!o.expert_profile_save.empty() && !all_experts_resident) {
|
||||
if (drive.d.usage.empty() || host_res.empty())
|
||||
std::fprintf(stderr, "strata serve: --expert-profile-save needs the adaptive tier (--adapt-every and "
|
||||
"--adapt-swaps above 0) and --expert-profile: nothing will be saved\n");
|
||||
@@ -6860,6 +6866,7 @@ int main(int argc, char** argv) {
|
||||
strata::core::Verifier ver;
|
||||
strata::core::VerifyHits vh;
|
||||
vh.d_res = thits.d_res;
|
||||
vh.h_res = host_res.empty() ? nullptr : host_res.data();
|
||||
vh.cache_base = thits.cache_base;
|
||||
vh.blob = thits.blob;
|
||||
vh.slot_off = xcache.slot_offsets(); // E-6: the device plan's pointers
|
||||
|
||||
Reference in new issue
Block a user