#646 on 0.1.39b: the layer split and the CPU path as before, the resident plan for 8-token windows

- layer split: commit stays synchronous on a split (set_commit_async(!multi_gpu), the `|| next_ != nullptr`),
  no pre-staging of the next stage's inputs, kDrafterMib 1000 - the split's sizing and order as in 0.1.39
  (untested here: one GPU);
- the CPU expert kernels keep cpu_avx512_ok() (F/BW/VL/VNNI/VBMI): #646's AVX-512BW-only check would send a
  CPU without VNNI/VBMI to kernels that use them;
- resident_plan_kernel: one thread per window entry up to 128 (64 lost a 7- or 8-token window's last entries
  on the all-resident path);
- the host keeps zeroing the GPU's rows under STRATA_VERIFY_DEVICE_PLAN=1 (copy_or_zero_from_mapped copies
  every row there).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Niko1221andClaude Opus 5.5 committed 2026-10-04 00:22:59 +02:00
1 parent 3a64d02a5e
commit deee4473e8
5 files changed
+21 -14

No files matched your search

+4 -1
View File
@@ -2100,7 +2100,10 @@ void expert_pool_dispatch_multi(ExpertDispatch& d, const float* x_f, const int32
}
}
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);
// the GPU's copy_rows_from_mapped zeroes the rows it computed itself (dec_batch); STRATA_VERIFY_DEVICE_PLAN's
// copy_or_zero_from_mapped copies every row, so there the host still zeroes them
static const bool device_plan = [] { const char* v = std::getenv("STRATA_VERIFY_DEVICE_PLAN"); return v != nullptr && std::atoi(v) != 0; }();
const bool gpu_zeroes_hits = (d.plan != nullptr && n <= kMaxWindowEntries && n <= d.plan->cap && dec_batch && !device_plan);
bool any_cpu = false;
for (int64_t i = 0; i < n; ++i)
if (kind[i] < 0) { any_cpu = true; break; }
+2 -2
View File
@@ -1423,7 +1423,7 @@ bool Verifier::run(int T, const int32_t* tokens, int64_t pos0, PoolMultiFn pool,
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);
// (#646 staged the next stage's inputs here; 0.1.39b keeps the layer split's order: each stage stages its own)
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
@@ -1642,7 +1642,7 @@ 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) {
if (!g_commit_async || next_ != nullptr) {
const cudaError_t se = cudaStreamSynchronize(cs_);
if (se != cudaSuccess) { err = std::string("verify: commit: ") + cudaGetErrorString(se); return false; }
} else {
+2 -2
View File
@@ -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_avx512bw_ok() && std::getenv("STRATA_NO_IQ512") == nullptr;
static const bool avx512 = cpu_avx512_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_avx512bw_ok();
static const bool cpu512 = cpu_avx512_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);
+11 -7
View File
@@ -500,15 +500,19 @@ __global__ void wait_flag_ge_kernel(const volatile uint32_t* flag, uint32_t valu
} // namespace
namespace {
__global__ void resident_plan_kernel(const int32_t* __restrict__ ids, int n, int k, const int32_t* __restrict__ res,
// one thread per window entry: up to kVerifyMaxT tokens x 10 routed experts (80), so 128 (#646 had 64: a window of
// 7+ tokens lost its last entries)
constexpr int kResidentPlanMax = 128;
static_assert(kVerifyMaxT * 10 <= kResidentPlanMax, "resident_plan: one thread per entry");
__global__ void __launch_bounds__(kResidentPlanMax) resident_plan_kernel(const int32_t* __restrict__ ids, int n, int k, const int32_t* __restrict__ res,
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) {
__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__ int32_t s_ids[kResidentPlanMax];
__shared__ unsigned long long s_ptr[kResidentPlanMax];
__shared__ int32_t s_first[kResidentPlanMax];
__shared__ int32_t s_cnt[kResidentPlanMax];
__shared__ int32_t s_gstart[kResidentPlanMax];
__shared__ int s_bad;
const int tid = threadIdx.x;
if (tid == 0) s_bad = 0;
@@ -609,7 +613,7 @@ __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, 64, 0, (cudaStream_t) stream>>>(ids, n_entries, k, res_layer, n_expert, cache_base, slot_off,
resident_plan_kernel<<<1, kResidentPlanMax, 0, (cudaStream_t) stream>>>(ids, n_entries, k, res_layer, n_expert, cache_base, slot_off,
blob, plan, capx, skip, ring);
check("resident_plan");
}
+2 -2
View File
@@ -2505,7 +2505,7 @@ int main(int argc, char** argv) {
}
}
}
strata::core::Verifier::set_commit_async(!split_same); // see Verifier::set_commit_async
strata::core::Verifier::set_commit_async(!multi_gpu); // 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;
@@ -2680,7 +2680,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 = 1320; // the MTP drafter (839 MiB) + the native Q5_K head (466 MiB), on the last stage only
const int64_t kDrafterMib = 1000; // the MTP drafter (839 MiB) + the head, 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