#include <bits/stdc++.h>
using namespace std;

struct it {
    int v = 0;
    int w = 0;
};
const int N = 1e4 + 5;
const int M = 1e9 + 7;
it a[N];
int f[N];  
int cnt[N];

int main() {
    int T;
    cin >> T;
    while (T--) {  // 处理多组测试数据
        int n, m;
        cin >> n >> m;  // n:物品数量,m:背包最大容量
        for (int i = 1; i <= n; i++) {
            cin >> a[i].w >> a[i].v;  // 输入每个物品的体积(w)和价值(v)
        }
        // 初始化:重置f和cnt数组,避免上一组数据残留影响当前组计算
        memset(f,0,sizeof f);   // f数组置0:初始无物品,任意体积的最大价值为0
        memset(cnt,0,sizeof cnt);// cnt数组置0:初始无合法方案(除体积0外)
        cnt[0] = 1;  // 体积0、价值0的方案数为1(不选任何物品,唯一初始方案)

        // 逆序遍历物品(保持原逻辑,等价于正序遍历物品,对应考虑第i到n个物品的状态)
        for (int i = n; i >= 1; i--) {
            // 逆序遍历体积(01背包一维优化核心!避免同一物品被重复选取)
            // 保证更新f[j]时,f[j-a[i].w]是未选当前物品的上一轮状态
            for (int j = m; j >= a[i].w;j--) {  
                // 情况1:选当前物品后的价值 = 体积j当前的最大价值 → 累加方案数
                if (f[j] == f[j - a[i].w] + a[i].v) {
                    cnt[j] = (cnt[j] + cnt[j - a[i].w]) % M;  // 方案数累加,取模防溢出
                } 
                // 情况2:选当前物品后的价值 > 体积j当前的最大价值 → 更新最大价值并重置方案数
                else if (f[j] < f[j - a[i].w] + a[i].v) {
                    f[j] = f[j - a[i].w]+a[i].v;  // 更新体积j的最大价值
                    cnt[j] = cnt[j - a[i].w] % M;  // 重置为选当前物品对应的方案数
                }
                // 情况3:选当前物品后的价值 < 现有最大价值 → 不做任何操作,保持原状态
            }

        }

        // 统计全局最大价值对应的总方案数
        int tmp = *max_element(f, f + m + 1);  // 找到0~m体积范围内的最大价值
        int res = 0;
        for (int j = 0; j <= m; j++) {
            if (f[j] == tmp) {  // 累加所有达到最大价值的体积对应的方案数
                res = (res + cnt[j]) % M;
            }
        }
        cout << res << endl;
    }

    return 0;
}