Files
wehub-resource-sync 94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:38:16 +08:00

326 lines
12 KiB
C++

// Combined optimal: real AOT .metallib + Primitive integration + optimized
// 3-kernel + 3D-grid dispatch + fused KV pool write.
#include <nanobind/nanobind.h>
#include <nanobind/stl/string.h>
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <stdexcept>
#include <string>
#include "mlx/allocator.h"
#include "mlx/array.h"
#include "mlx/backend/metal/device.h"
#include "mlx/mlx.h"
#include "mlx/primitives.h"
#include "mlx/stream.h"
namespace nb = nanobind;
using namespace mlx::core;
namespace {
constexpr const char* kLibraryName = "sgl_metal_kernels";
MTL::Library* g_library = nullptr;
const char* dtype_suffix(Dtype dt) {
switch (dt) {
case float16:
return "f16";
case bfloat16:
return "bf16";
case float32:
return "f32";
default:
throw std::runtime_error("rope_pool_fused: unsupported dtype");
}
}
void register_library_impl(const std::string& path) {
if (path.empty()) {
throw std::runtime_error("register_library requires a non-empty path");
}
auto& d = metal::device(Device::gpu);
g_library = d.get_library(kLibraryName, path);
if (g_library == nullptr) {
throw std::runtime_error("failed to load .metallib from: " + path);
}
}
MTL::Size pick_tg(uint32_t gx, uint32_t gy, uint32_t gz) {
constexpr uint32_t kMaxThreads = 256;
uint32_t tx = std::min<uint32_t>(gx, 32u);
uint32_t ty = std::min<uint32_t>(gy, kMaxThreads / std::max<uint32_t>(tx, 1u));
uint32_t tz = std::min<uint32_t>(gz, kMaxThreads / std::max<uint32_t>(tx * ty, 1u));
while (ty > 1 && (gy % ty) != 0)
--ty;
while (tz > 1 && (gz % tz) != 0)
--tz;
return MTL::Size::Make(tx, std::max<uint32_t>(ty, 1u), std::max<uint32_t>(tz, 1u));
}
uint32_t pick_heads_per_thread(uint32_t nh) {
if (nh == 0) return 1;
if (const char* e = std::getenv("SGLANG_RPF_N")) {
uint32_t v = static_cast<uint32_t>(std::atoi(e));
if (v >= 1 && nh % v == 0) return v;
}
return 1u;
}
class RopePoolFused : public Primitive {
public:
RopePoolFused(Stream stream, int head_dim, int num_qo_heads, int num_kv_heads, float rope_base)
: Primitive(stream),
head_dim_(head_dim),
num_qo_heads_(num_qo_heads),
num_kv_heads_(num_kv_heads),
rope_base_(rope_base) {}
void eval_cpu(const std::vector<array>&, std::vector<array>&) override {
throw std::runtime_error("rope_pool_fused: CPU eval not supported");
}
void eval_gpu(const std::vector<array>& inputs, std::vector<array>& outputs) override {
if (g_library == nullptr) {
throw std::runtime_error("rope_pool_fused: register_library() not called yet");
}
auto& q = inputs[0];
auto& k = inputs[1];
auto& v = inputs[2];
auto& positions = inputs[3];
auto& slots = inputs[4];
auto& k_pool_in = inputs[5];
auto& v_pool_in = inputs[6];
auto& q_out = outputs[0];
auto& k_out = outputs[1];
auto& k_pool_out = outputs[2];
auto& v_pool_out = outputs[3];
q_out.set_data(allocator::malloc(q_out.nbytes()));
k_out.set_data(allocator::malloc(k_out.nbytes()));
// Donate input pool buffers to outputs - zero-copy in-place semantics.
k_pool_out.copy_shared_buffer(k_pool_in);
v_pool_out.copy_shared_buffer(v_pool_in);
auto& d = metal::device(stream().device);
const uint32_t hd = static_cast<uint32_t>(head_dim_);
const uint32_t nq = static_cast<uint32_t>(num_qo_heads_);
const uint32_t nk = static_cast<uint32_t>(num_kv_heads_);
const uint32_t half_dim = hd / 2;
const uint32_t num_tokens = static_cast<uint32_t>(q.shape(0));
const uint32_t hpt_q = pick_heads_per_thread(nq);
const uint32_t hpt_k = pick_heads_per_thread(nk);
const uint32_t hpt_v = pick_heads_per_thread(nk);
const float inv_dim_log2_base = std::log2(rope_base_) / static_cast<float>(head_dim_);
auto build_consts = [&](const uint32_t& hpt) {
return metal::MTLFCList{
{&hd, MTL::DataType::DataTypeUInt, 0},
{&nq, MTL::DataType::DataTypeUInt, 1},
{&nk, MTL::DataType::DataTypeUInt, 2},
{&inv_dim_log2_base, MTL::DataType::DataTypeFloat, 3},
{&hpt, MTL::DataType::DataTypeUInt, 4},
};
};
auto build_hash = [&](const std::string& kname, uint32_t hpt) {
return kname + "_hd" + std::to_string(head_dim_) + "_q" + std::to_string(num_qo_heads_) + "_k" +
std::to_string(num_kv_heads_) + "_n" + std::to_string(hpt) + "_b" +
std::to_string(static_cast<int>(rope_base_));
};
const std::string rect_kname = std::string("rope_pool_rect_") + dtype_suffix(q.dtype());
const std::string q_kname = std::string("rope_q_") + dtype_suffix(q.dtype());
const std::string k_kname = std::string("rope_k_pool_") + dtype_suffix(k.dtype());
const std::string v_kname = std::string("v_to_pool_") + dtype_suffix(v.dtype());
// Single rectangular dispatch uses one HEADS_PER_THREAD value for both
// Q and KV heads. Keep it valid for both head counts.
uint32_t hpt = std::min(hpt_q, hpt_k);
if (nq % hpt != 0 || nk % hpt != 0) hpt = 1;
auto& enc = metal::get_command_encoder(stream());
const bool use_rect_dispatch = q.dtype() == bfloat16 && hd >= 128 && nk >= 8 && num_tokens >= 256;
if (use_rect_dispatch) {
auto rect_consts = build_consts(hpt);
auto* rect_pipe = d.get_kernel(rect_kname, g_library, build_hash(rect_kname, hpt), rect_consts);
if (!rect_pipe) {
throw std::runtime_error("rope_pool_fused: failed to resolve rectangular kernel");
}
const uint32_t max_heads = std::max(nq, nk);
const uint32_t gz = (max_heads + hpt - 1) / hpt;
enc.set_compute_pipeline_state(rect_pipe);
enc.set_input_array(q, 0);
enc.set_input_array(k, 1);
enc.set_input_array(v, 2);
enc.set_output_array(q_out, 3);
enc.set_output_array(k_out, 4);
enc.set_output_array(k_pool_out, 5);
enc.set_output_array(v_pool_out, 6);
enc.set_input_array(positions, 7);
enc.set_input_array(slots, 8);
enc.dispatch_threads(MTL::Size::Make(hd, num_tokens, gz), pick_tg(hd, num_tokens, gz));
} else {
auto q_consts = build_consts(hpt_q);
auto k_consts = build_consts(hpt_k);
auto v_consts = build_consts(hpt_v);
auto* q_pipe = d.get_kernel(q_kname, g_library, build_hash(q_kname, hpt_q), q_consts);
auto* k_pipe = d.get_kernel(k_kname, g_library, build_hash(k_kname, hpt_k), k_consts);
auto* v_pipe = d.get_kernel(v_kname, g_library, build_hash(v_kname, hpt_v), v_consts);
if (!q_pipe || !k_pipe || !v_pipe) {
throw std::runtime_error("rope_pool_fused: failed to resolve kernels");
}
// Kernel 1: Q rope
{
enc.set_compute_pipeline_state(q_pipe);
enc.set_input_array(q, 0);
enc.set_output_array(q_out, 1);
enc.set_input_array(positions, 2);
const uint32_t gz = (nq + hpt_q - 1) / hpt_q;
enc.dispatch_threads(MTL::Size::Make(half_dim, num_tokens, gz), pick_tg(half_dim, num_tokens, gz));
}
// Kernel 2: K rope + pool write
{
enc.set_compute_pipeline_state(k_pipe);
enc.set_input_array(k, 0);
enc.set_output_array(k_out, 1);
enc.set_output_array(k_pool_out, 2);
enc.set_input_array(positions, 3);
enc.set_input_array(slots, 4);
const uint32_t gz = (nk + hpt_k - 1) / hpt_k;
enc.dispatch_threads(MTL::Size::Make(half_dim, num_tokens, gz), pick_tg(half_dim, num_tokens, gz));
}
// Kernel 3: V copy to pool
{
enc.set_compute_pipeline_state(v_pipe);
enc.set_input_array(v, 0);
enc.set_output_array(v_pool_out, 1);
enc.set_input_array(slots, 2);
enc.dispatch_threads(MTL::Size::Make(hd, num_tokens, nk), pick_tg(hd, num_tokens, nk));
}
}
// No commit / synchronize - MLX's lazy graph batches into one buffer.
}
const char* name() const override {
return "RopePoolFused";
}
bool is_equivalent(const Primitive& other) const override {
auto* o = dynamic_cast<const RopePoolFused*>(&other);
return o != nullptr && o->head_dim_ == head_dim_ && o->num_qo_heads_ == num_qo_heads_ &&
o->num_kv_heads_ == num_kv_heads_ && o->rope_base_ == rope_base_;
}
std::vector<Shape> output_shapes(const std::vector<array>& inputs) override {
return {inputs[0].shape(), inputs[1].shape(), inputs[5].shape(), inputs[6].shape()};
}
private:
int head_dim_;
int num_qo_heads_;
int num_kv_heads_;
float rope_base_;
};
// Python entry: returns 4 arrays (q_rot, k_rot, k_pool_new, v_pool_new).
nb::tuple rope_pool_fused_py(
nb::handle q_h,
nb::handle k_h,
nb::handle v_h,
nb::handle positions_h,
nb::handle slots_h,
nb::handle k_pool_h,
nb::handle v_pool_h,
int head_dim,
int num_qo_heads,
int num_kv_heads,
float rope_base) {
auto& q = *nb::inst_ptr<array>(q_h);
auto& k = *nb::inst_ptr<array>(k_h);
auto& v = *nb::inst_ptr<array>(v_h);
auto& positions = *nb::inst_ptr<array>(positions_h);
auto& slots = *nb::inst_ptr<array>(slots_h);
auto& k_pool = *nb::inst_ptr<array>(k_pool_h);
auto& v_pool = *nb::inst_ptr<array>(v_pool_h);
if (q.ndim() != 3 || k.ndim() != 3 || v.ndim() != 3) throw std::runtime_error("rope_pool_fused: q/k/v must be 3-D");
if (positions.ndim() != 1 || slots.ndim() != 1)
throw std::runtime_error("rope_pool_fused: positions/slots must be 1-D");
if (k_pool.ndim() != 3 || v_pool.ndim() != 3) throw std::runtime_error("rope_pool_fused: pools must be 3-D");
if (positions.dtype() != int32 || slots.dtype() != int32)
throw std::runtime_error("rope_pool_fused: positions/slots must be int32");
if (q.dtype() != k.dtype() || q.dtype() != v.dtype() || q.dtype() != k_pool.dtype() || q.dtype() != v_pool.dtype())
throw std::runtime_error("rope_pool_fused: all float arrays must share dtype");
if ((head_dim & 1) != 0) throw std::runtime_error("rope_pool_fused: head_dim must be even");
// Shape cross-checks (catch any drift between Python pre-flight state
// and actual tensors at dispatch time).
const int num_tokens = q.shape(0);
if (k.shape(0) != num_tokens || v.shape(0) != num_tokens || positions.shape(0) != num_tokens ||
slots.shape(0) != num_tokens)
throw std::runtime_error("rope_pool_fused: q/k/v/positions/slots must agree on token dim");
if (q.shape(1) != num_qo_heads || k.shape(1) != num_kv_heads || v.shape(1) != num_kv_heads)
throw std::runtime_error("rope_pool_fused: head-count mismatch with num_qo_heads/num_kv_heads");
if (q.shape(2) != head_dim || k.shape(2) != head_dim || v.shape(2) != head_dim)
throw std::runtime_error("rope_pool_fused: head_dim mismatch with q/k/v last dim");
if (k_pool.shape(1) != num_kv_heads || v_pool.shape(1) != num_kv_heads || k_pool.shape(2) != head_dim ||
v_pool.shape(2) != head_dim)
throw std::runtime_error("rope_pool_fused: pool layout must be [pool_size, num_kv_heads, head_dim]");
auto stream = default_stream(Device::gpu);
auto primitive = std::make_shared<RopePoolFused>(stream, head_dim, num_qo_heads, num_kv_heads, rope_base);
auto outs = array::make_arrays(
{q.shape(), k.shape(), k_pool.shape(), v_pool.shape()},
{q.dtype(), k.dtype(), k_pool.dtype(), v_pool.dtype()},
primitive,
{q, k, v, positions, slots, k_pool, v_pool});
// Cross-module nb cast doesn't work cleanly - explicitly construct.
nb::module_ mx_core = nb::module_::import_("mlx.core");
nb::object py_array_type = mx_core.attr("array");
nb::list result;
for (auto& a : outs) {
nb::object py_obj = py_array_type(0);
auto* dst = nb::inst_ptr<array>(py_obj);
new (dst) array(std::move(a));
nb::inst_mark_ready(py_obj);
result.append(py_obj);
}
return nb::tuple(result);
}
} // namespace
NB_MODULE(_metal, m) {
m.def("register_library", &register_library_impl, nb::arg("path"));
m.def(
"rope_pool_fused",
&rope_pool_fused_py,
nb::arg("q"),
nb::arg("k"),
nb::arg("v"),
nb::arg("positions"),
nb::arg("slots"),
nb::arg("k_pool"),
nb::arg("v_pool"),
nb::arg("head_dim"),
nb::arg("num_qo_heads"),
nb::arg("num_kv_heads"),
nb::arg("rope_base"));
}