@@ -4615,6 +4615,115 @@ struct test_moe_weighted_combine : public test_case {
46154615 }
46164616};
46174617
4618+ // Ragged 256-expert MoE dispatch gate for serving decode.
4619+ struct test_mul_mat_id_ragged_moe : public test_case {
4620+ const ggml_type type_a;
4621+ const int n_mats;
4622+ const int n_used;
4623+ const int64_t m;
4624+ const int64_t n;
4625+ const int64_t k;
4626+
4627+ std::string vars () override {
4628+ return VARS_TO_STR6 (type_a, n_mats, n_used, m, n, k);
4629+ }
4630+
4631+ double max_nmse_err () override {
4632+ return 5e-4 ;
4633+ }
4634+
4635+ double max_nmse_err (ggml_backend_t backend) override {
4636+ if ((type_a == GGML_TYPE_MXFP4 || type_a == GGML_TYPE_NVFP4 ) && backend_has_feature (backend, " BLACKWELL_NATIVE_FP4" )) {
4637+ return 2e-2 ;
4638+ }
4639+ return max_nmse_err ();
4640+ }
4641+
4642+ uint64_t op_flops (ggml_tensor * t) override {
4643+ GGML_UNUSED (t);
4644+ return 2 * m * k * n * n_used;
4645+ }
4646+
4647+ test_mul_mat_id_ragged_moe (ggml_type type_a = GGML_TYPE_NVFP4 , int n_mats = 256 , int n_used = 8 ,
4648+ int64_t m = 768 , int64_t n = 128 , int64_t k = 2048 )
4649+ : type_a(type_a), n_mats(n_mats), n_used(n_used), m(m), n(n), k(k) {
4650+ GGML_ASSERT (n_used <= n_mats);
4651+ }
4652+
4653+ ggml_tensor * build_graph (ggml_context * ctx) override {
4654+ ggml_tensor * as = ggml_new_tensor_3d (ctx, type_a, k, m, n_mats);
4655+ ggml_set_name (as, " as" );
4656+
4657+ ggml_tensor * ids = ggml_new_tensor_2d (ctx, GGML_TYPE_I32 , n_mats, n);
4658+ ggml_set_name (ids, " ids" );
4659+ if (n_used != n_mats) {
4660+ ids = ggml_view_2d (ctx, ids, n_used, n, ids->nb [1 ], 0 );
4661+ ggml_set_name (ids, " view_of_ids" );
4662+ }
4663+
4664+ ggml_tensor * b = ggml_new_tensor_3d (ctx, GGML_TYPE_F32 , k, n_used, n);
4665+ ggml_set_name (b, " b" );
4666+
4667+ ggml_tensor * out = ggml_mul_mat_id (ctx, as, b, ids);
4668+ ggml_set_name (out, " out" );
4669+
4670+ return out;
4671+ }
4672+
4673+ void initialize_tensors (ggml_context * ctx) override {
4674+ for (ggml_tensor * t = ggml_get_first_tensor (ctx); t != nullptr ; t = ggml_get_next_tensor (ctx, t)) {
4675+ if (ggml_is_view_op (t->op )) {
4676+ continue ;
4677+ }
4678+ if (t->type != GGML_TYPE_I32 ) {
4679+ init_tensor_uniform (t);
4680+ continue ;
4681+ }
4682+
4683+ std::vector<int32_t > data (t->ne [0 ]);
4684+ for (int64_t token = 0 ; token < ggml_nrows (t); ++token) {
4685+ for (int64_t r = 0 ; r < t->ne [0 ]; ++r) {
4686+ data[r] = (int32_t ) ((token * 17 + r * 31 ) % n_mats);
4687+ }
4688+
4689+ if (n_used >= 8 ) {
4690+ // Skew rank 0 heavily to expert 0, exercise max expert id,
4691+ // leave many experts empty, and preserve unique top-k ids.
4692+ std::vector<bool > used (n_mats, false );
4693+ const int64_t seeds[8 ] = {
4694+ 0 ,
4695+ 1 + token % 4 ,
4696+ 4 + (token * 3 ) % 8 ,
4697+ n_mats - 1 ,
4698+ token * 5 + 7 ,
4699+ token * 7 + 11 ,
4700+ token * 13 + 19 ,
4701+ token * 29 + 23 ,
4702+ };
4703+
4704+ for (int64_t r = 0 ; r < 8 ; ++r) {
4705+ int32_t id = (int32_t ) (seeds[r] % n_mats);
4706+ while (used[id]) {
4707+ id = (id + 1 ) % n_mats;
4708+ }
4709+ data[r] = id;
4710+ used[id] = true ;
4711+ }
4712+ }
4713+
4714+ ggml_backend_tensor_set (t, data.data (), token * t->nb [1 ], t->ne [0 ] * sizeof (int32_t ));
4715+ }
4716+ }
4717+ }
4718+
4719+ bool run_whole_graph () override { return true ; }
4720+
4721+ std::string op_desc (ggml_tensor * t) override {
4722+ GGML_UNUSED (t);
4723+ return " MUL_MAT_ID_RAGGED_MOE" ;
4724+ }
4725+ };
4726+
46184727// GGML_OP_OUT_PROD
46194728struct test_out_prod : public test_case {
46204729 const ggml_type type_a;
@@ -8941,6 +9050,15 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_eval() {
89419050 test_cases.emplace_back (new test_moe_weighted_combine (GGML_TYPE_NVFP4 , 128 , 8 , 768 , n, 2048 ));
89429051 }
89439052
9053+ // [paged Phase 8] Ragged 256-expert MoE dispatch gate for live serving decode.
9054+ // Deterministic ids skew many tokens into a few hot experts, include expert 255,
9055+ // and leave many experts empty. n=1 covers single-token decode; n=257 crosses
9056+ // the MMVQ/MMID batch cutoff while preserving top-8 routing.
9057+ test_cases.emplace_back (new test_mul_mat_id_ragged_moe (GGML_TYPE_F32 , 16 , 8 , 32 , 8 , 64 ));
9058+ for (int n : {1 , 8 , 33 , 128 , 257 }) {
9059+ test_cases.emplace_back (new test_mul_mat_id_ragged_moe (GGML_TYPE_NVFP4 , 256 , 8 , 768 , n, 2048 ));
9060+ }
9061+
89449062 // [paged P0 / track B] NVFP4/MXFP4 dense decode-shape mmq_y-down bit-exact gate.
89459063 // The dense FP4 weight GEMM is the track-B target; P1 lowers mmq_y (the weight-row tile) on the
89469064 // NVFP4 decode path to raise resident-CTA occupancy. mmq_y is a pure N-row tiling knob, so a
0 commit comments