#include "strata/core/remote_experts.hpp" #include "strata/kernels/cpu/expert_layout.hpp" #include "strata/kernels/iq_kernels.hpp" #include "strata/kernels/quantize_act.hpp" #include "strata/kernels/s2_expert_grouped.hpp" #include #include #include #include namespace strata::core { namespace { constexpr int64_t H = strata::kernels::cpu::H; constexpr int64_t FF = strata::kernels::cpu::FF; constexpr int64_t CAP = strata::kernels::cpu::MAXT * 10; // Small enough to copy as one pinned buffer per layer. The kernels read the // individual arrays through pointers into the same device allocation. struct RemoteMeta { unsigned long long ptr[CAP]; int32_t start[CAP + 1]; int32_t dst[CAP]; int32_t tok[CAP]; int32_t count; }; struct DeviceScope { int previous = -1; bool ok = false; cudaError_t status = cudaSuccess; const char* failed_step = nullptr; explicit DeviceScope(int device) { status = cudaGetDevice(&previous); if (status != cudaSuccess) { previous = -1; failed_step = "cudaGetDevice"; return; } status = cudaSetDevice(device); if (status != cudaSuccess) { failed_step = "cudaSetDevice"; return; } ok = true; } ~DeviceScope() { if (previous >= 0) cudaSetDevice(previous); } std::string error(int device) const { return std::string("CUDA") + std::to_string(device) + " experts: " + (failed_step ? failed_step : "device switch") + "(" + std::to_string(device) + ") failed: " + cudaGetErrorString(status) + " (CUDA error " + std::to_string((int) status) + ")"; } }; bool check(cudaError_t result, const char* what, std::string& err, int device) { if (result == cudaSuccess) return true; err = "CUDA" + std::to_string(device) + " experts: " + what + ": " + cudaGetErrorString(result); return false; } } // namespace RemoteExperts::~RemoteExperts() { close(); } bool RemoteExperts::preflight(int device, double& free_gib, std::string& err) { int count = 0; if (!check(cudaGetDeviceCount(&count), "cudaGetDeviceCount", err, device)) return false; if (device < 1 || device >= count) { err = "CUDA" + std::to_string(device) + " experts: CUDA device is not visible"; return false; } // The layer waits for this GPU on the CPU pool's critical path: spin instead of sleeping, whose wake-up // costs more than a small expert batch takes (measured: ~0.3 ms per round trip on Windows). Only possible // before the device's context exists, so first thing; STRATA_REMOTE_SPIN=0 keeps the driver's default. #if !defined(STRATA_USE_HIP) const char* spin = std::getenv("STRATA_REMOTE_SPIN"); if (!(spin && spin[0] == '0')) cudaInitDevice(device, cudaDeviceScheduleSpin | cudaDeviceMapHost, 0); cudaGetLastError(); #endif // HIP has no cudaInitDevice equivalent; retain its default scheduling policy. DeviceScope scope(device); if (!scope.ok) { err = scope.error(device); return false; } size_t free_bytes = 0, total_bytes = 0; if (!check(cudaMemGetInfo(&free_bytes, &total_bytes), "preflight free memory", err, device)) return false; free_gib = (double) free_bytes / 1073741824.0; return true; } void RemoteExperts::close() { if (device_ < 0) return; DeviceScope scope(device_); if (scope.ok) { if (stream_) cudaStreamSynchronize(stream_); cache_.close(); if (d_x_) cudaFree(d_x_); if (d_out_) cudaFree(d_out_); if (d_q8_) cudaFree(d_q8_); if (d_scales_) cudaFree(d_scales_); if (d_scratch_) cudaFree(d_scratch_); if (d_meta_) cudaFree(d_meta_); if (h_x_) cudaFreeHost(h_x_); if (h_out_) cudaFreeHost(h_out_); if (h_meta_) cudaFreeHost(h_meta_); if (stream_) cudaStreamDestroy(stream_); } device_ = -1; stream_ = nullptr; h_x_ = h_out_ = d_x_ = d_out_ = nullptr; h_meta_ = d_meta_ = nullptr; d_q8_ = nullptr; d_scales_ = nullptr; d_scratch_ = nullptr; d_start_ = d_dst_ = d_tok_ = d_count_ = nullptr; d_ptr_ = nullptr; original_row_.clear(); layers_present_.clear(); } bool RemoteExperts::open(int device, int slots, int64_t layers, int64_t experts, const std::vector>& ranked, const ExpertCache& primary, ExpertSource& source, std::vector& claimed, std::string& err) { close(); int count = 0; if (!check(cudaGetDeviceCount(&count), "cudaGetDeviceCount", err, device)) return false; if (device < 1 || device >= count || slots <= 0 || ranked.empty() || layers <= 0 || experts <= 0 || claimed.size() != (size_t) layers * (size_t) experts) { err = "CUDA" + std::to_string(device) + " experts: need the device, ranked experts and positive slot count"; return false; } DeviceScope scope(device); if (!scope.ok) { err = scope.error(device); return false; } device_ = device; n_expert_ = experts; const auto& lay = strata::kernels::cpu::expert_layout(); std::vector> selected; selected.reserve((size_t) slots); std::vector picked(claimed.size(), 0); for (const auto& pair : ranked) { if (pair.first < 0 || pair.first >= layers || pair.second < 0 || pair.second >= experts) continue; const size_t index = (size_t) pair.first * (size_t) experts + (size_t) pair.second; if (primary.slot_of(pair.first, pair.second) < 0 && !claimed[index] && !picked[index]) { selected.push_back(pair); picked[index] = 1; if ((int) selected.size() >= slots) break; } } if (selected.empty()) { err = "CUDA" + std::to_string(device) + " experts: no unclaimed experts remain"; close(); return false; } std::vector sizes; if (lay.native) { sizes.reserve(selected.size()); for (const auto& pair : selected) sizes.push_back((int64_t) lay.blob_bytes(pair.first)); } size_t free_bytes = 0, total_bytes = 0; if (!check(cudaMemGetInfo(&free_bytes, &total_bytes), "free memory", err, device)) { close(); return false; } uint64_t needed = 0; for (const auto& pair : selected) needed += lay.native ? (lay.blob_bytes(pair.first) + 255) / 256 * 256 : lay.max_blob; // Leave room for the CUDA context, staging and later driver allocations, especially under WDDM. if (needed + (512ull << 20) > free_bytes) { err = "CUDA" + std::to_string(device) + " experts: slots leave less than 512 MiB free; reduce --expert-cache-device" + std::to_string(device); close(); return false; } const bool cache_ok = lay.native ? cache_.open_sized(sizes, layers, experts, err) : cache_.open((int64_t) selected.size(), layers, experts, (int64_t) lay.max_blob, err); if (!cache_ok) { err = "CUDA" + std::to_string(device) + " experts: " + err; close(); return false; } for (const auto& pair : selected) { const int32_t slot = cache_.admit(pair.first, pair.second); const uint8_t* blob = source.blob(pair.first, pair.second); if (slot < 0 || !blob || !cache_.fill_slot_blocking(slot, blob, err, (int64_t) lay.blob_bytes(pair.first))) { err = "CUDA" + std::to_string(device) + " experts: " + (err.empty() ? "cache fill failed" : err); close(); return false; } } const auto& first = selected.front(); if (!cache_.verify_slot(cache_.slot_of(first.first, first.second), source.blob(first.first, first.second), err, (int64_t) lay.blob_bytes(first.first))) { err = "CUDA" + std::to_string(device) + " experts: " + err; close(); return false; } const size_t scratch = std::max( (size_t) strata::kernels::moe_hit_grouped_scratch_bytes(CAP, H, FF), strata::kernels::native_expert_scratch_bytes(CAP, FF)); const bool allocated = check(cudaStreamCreateWithFlags(&stream_, cudaStreamNonBlocking), "stream", err, device) && check(cudaHostAlloc((void**) &h_x_, (size_t) CAP * H * sizeof(float), cudaHostAllocPortable | cudaHostAllocMapped), "input staging", err, device) && check(cudaHostAlloc((void**) &h_out_, (size_t) CAP * H * sizeof(float), cudaHostAllocPortable | cudaHostAllocMapped), "result staging", err, device) && check(cudaHostAlloc(&h_meta_, sizeof(RemoteMeta), cudaHostAllocPortable), "metadata staging", err, device) && check(cudaMalloc((void**) &d_x_, (size_t) CAP * H * sizeof(float)), "input", err, device) && check(cudaMalloc((void**) &d_out_, (size_t) CAP * H * sizeof(float)), "result", err, device) && check(cudaMalloc((void**) &d_q8_, (size_t) CAP * (H / 32) * 36), "activation", err, device) && check(cudaMalloc((void**) &d_scales_, (size_t) CAP * (H / 32) * sizeof(float)), "activation scales", err, device) && check(cudaMalloc(&d_scratch_, scratch), "scratch", err, device) && check(cudaMalloc(&d_meta_, sizeof(RemoteMeta)), "group metadata", err, device); if (!allocated) { close(); return false; } // Zero-copy: the helper reads its input from, and writes its compact rows into, the pinned host buffers // directly - two copies fewer per layer, each of which is a PCIe round trip. STRATA_REMOTE_ZEROCOPY=0 copies. const char* zc = std::getenv("STRATA_REMOTE_ZEROCOPY"); zero_copy_ = !(zc && zc[0] == '0') && cudaHostGetDevicePointer((void**) &z_x_, h_x_, 0) == cudaSuccess && cudaHostGetDevicePointer((void**) &z_out_, h_out_, 0) == cudaSuccess; cudaGetLastError(); auto* meta = (RemoteMeta*) d_meta_; d_ptr_ = meta->ptr; d_start_ = meta->start; d_dst_ = meta->dst; d_tok_ = meta->tok; d_count_ = &meta->count; owned_.resize(CAP); layers_present_.assign((size_t) layers, 0); group_of_.resize(CAP); group_id_.reserve(CAP); ptr_.reserve(CAP); start_.reserve(CAP + 1); dst_.reserve(CAP); tok_.reserve(CAP); original_row_.reserve(CAP); computed_ = 0; launched_layers_ = 0; returned_bytes_ = full_row_bytes_ = 0; for (const auto& pair : selected) { layers_present_[(size_t) pair.first] = 1; claimed[(size_t) pair.first * (size_t) experts + (size_t) pair.second] = 1; } return true; } bool RemoteExperts::begin(int64_t layer, const float* x, const int32_t* ids, int64_t n_tok, int64_t k, const int32_t* kind, const int32_t* primary_res, std::string& err) { // cumulative host time in here (staging and launches), reported per request by the driver struct Timer { double& acc; std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now(); ~Timer() { acc += std::chrono::duration(std::chrono::steady_clock::now() - t0).count(); } } timer{ms_begin_}; const int64_t n = n_tok * k; if (n <= 0 || n > CAP || n_tok > strata::kernels::cpu::MAXT || k != 10 || layer < 0 || (size_t) layer >= layers_present_.size() || device_ < 0) { err = "CUDA" + std::to_string(device_) + " experts: invalid layer, routing width or window size"; return false; } std::fill(owned_.begin(), owned_.begin() + n, 0); group_id_.clear(); ptr_.clear(); start_.clear(); dst_.clear(); tok_.clear(); original_row_.clear(); if (!layers_present_[(size_t) layer]) return true; std::fill(group_of_.begin(), group_of_.begin() + n, -1); for (int64_t i = 0; i < n; ++i) { const int32_t e = ids[i]; if ((kind && kind[i] != -1) || e < 0 || e >= n_expert_ || (primary_res && primary_res[(size_t) layer * (size_t) n_expert_ + (size_t) e] >= 0)) continue; const int32_t slot = cache_.slot_of(layer, e); if (slot < 0) continue; owned_[(size_t) i] = 1; int32_t group = -1; for (size_t g = 0; g < group_id_.size(); ++g) if (group_id_[g] == e) { group = (int32_t) g; break; } if (group < 0) { group = (int32_t) group_id_.size(); group_id_.push_back(e); ptr_.push_back((unsigned long long) cache_.device_slot(slot)); } group_of_[(size_t) i] = group; ++computed_; } if (group_id_.empty()) return true; for (size_t g = 0; g < group_id_.size(); ++g) { start_.push_back((int32_t) dst_.size()); for (int64_t i = 0; i < n; ++i) if (group_of_[(size_t) i] == (int32_t) g) { // The grouped kernels read the activation from `tok`, so their // output row can instead be packed densely for the USB4 return. original_row_.push_back((int32_t) i); dst_.push_back((int32_t) dst_.size()); tok_.push_back((int32_t) (i / k)); } } start_.push_back((int32_t) dst_.size()); // Private pinned buffers survive until this GPU has consumed them. The CPU // pool can write other output rows without a cross-device race. std::memcpy(h_x_, x, (size_t) n_tok * H * sizeof(float)); auto* meta = (RemoteMeta*) h_meta_; std::memcpy(meta->ptr, ptr_.data(), ptr_.size() * sizeof(ptr_[0])); std::memcpy(meta->start, start_.data(), start_.size() * sizeof(start_[0])); std::memcpy(meta->dst, dst_.data(), dst_.size() * sizeof(dst_[0])); std::memcpy(meta->tok, tok_.data(), tok_.size() * sizeof(tok_[0])); meta->count = (int32_t) group_id_.size(); DeviceScope scope(device_); if (!scope.ok) { err = scope.error(device_); return false; } groups_ = (int32_t) group_id_.size(); const cudaStream_t s = stream_; const bool staged = (zero_copy_ || check(cudaMemcpyAsync(d_x_, h_x_, (size_t) n_tok * H * sizeof(float), cudaMemcpyHostToDevice, s), "copy input", err, device_)) && check(cudaMemcpyAsync(d_meta_, h_meta_, sizeof(RemoteMeta), cudaMemcpyHostToDevice, s), "copy group metadata", err, device_); if (!staged) return false; const auto& lay = strata::kernels::cpu::expert_layout(); if (lay.native) { strata::kernels::quantize_q8_1_rows(zero_copy_ ? z_x_ : d_x_, n_tok, H, d_q8_, s); const auto& fmt = lay.fmt[(size_t) layer]; auto L = strata::kernels::native_expert_layout(fmt.gu_type, fmt.d_type, fmt.n_embd, fmt.n_ff); strata::kernels::native_expert_grouped(L, d_ptr_, d_start_, d_count_, d_dst_, d_tok_, groups_, (int64_t) dst_.size(), d_q8_, d_scratch_, zero_copy_ ? z_out_ : d_out_, s); } else { strata::kernels::quantize_q8_0_scaled(zero_copy_ ? z_x_ : d_x_, d_q8_, d_scales_, n_tok * H, s); strata::kernels::moe_grouped_s2(d_ptr_, d_start_, d_count_, d_dst_, d_tok_, groups_, (int64_t) dst_.size(), d_q8_, d_scales_, d_scratch_, zero_copy_ ? z_out_ : d_out_, s); } const uint64_t compact_bytes = (uint64_t) dst_.size() * H * sizeof(float); if (!zero_copy_ && !check(cudaMemcpyAsync(h_out_, d_out_, (size_t) compact_bytes, cudaMemcpyDeviceToHost, s), "copy results", err, device_)) return false; ++launched_layers_; returned_bytes_ += compact_bytes; full_row_bytes_ += (uint64_t) n * H * sizeof(float); return true; } bool RemoteExperts::finish(float* out, std::string& err) { if (group_id_.empty()) return true; DeviceScope scope(device_); if (!scope.ok) { err = scope.error(device_); return false; } const auto w0 = std::chrono::steady_clock::now(); if (!check(cudaStreamSynchronize(stream_), "finish", err, device_)) return false; ms_wait_ += std::chrono::duration(std::chrono::steady_clock::now() - w0).count(); for (size_t i = 0; i < original_row_.size(); ++i) std::memcpy(out + (size_t) original_row_[i] * H, h_out_ + i * H, (size_t) H * sizeof(float)); return true; } } // namespace strata::core