#include <bits/stdc++.h>
using namespace std;
template <class X, class Y>
void minimize(X &x, const Y &y) {
  if (x > y) x = y;
}
template <class X, class Y>
void maximize(X &x, const Y &y) {
  if (x < y) x = y;
}
template <class T>
T Abs(const T &x) {
  return (x < 0 ? -x : x);
}
const int mod = (int)1e9 + 7;
vector<pair<int, int> > haveAt[1000100];
int numRoom, numType, types[1000100];
int frac[1000100];
bool compType(const int &x, const int &y) {
  vector<pair<int, int> > &a = haveAt[x];
  vector<pair<int, int> > &b = haveAt[y];
  if (a.size() != b.size()) return a.size() < b.size();
  for (int i = 0, _n = (a.size()); i < _n; i = i + 1)
    if (a[i] != b[i]) return a[i] < b[i];
  return false;
}
void process(void) {
  frac[0] = 1;
  for (int i = (1), _b = (1000100 - 1); i <= _b; i = i + 1)
    frac[i] = 1LL * frac[i - 1] * i % mod;
  scanf("%d%d", &numRoom, &numType);
  for (int i = (1), _b = (numRoom); i <= _b; i = i + 1) {
    map<int, int> cnt;
    int t;
    scanf("%d", &t);
    for (int love = 0, _n = (t); love < _n; love = love + 1) {
      int x;
      scanf("%d", &x);
      cnt[x]++;
    }
    for (__typeof((cnt).begin()) it = (cnt).begin(); it != (cnt).end(); it++)
      haveAt[it->first].push_back(make_pair(i, it->second));
  }
  for (int i = (1), _b = (numType); i <= _b; i = i + 1) types[i] = i;
  sort(types + 1, types + numType + 1, compType);
  vector<int> comps;
  for (int i = (1), _b = (numType); i <= _b; i = i + 1) {
    if (i == 1 || compType(types[i - 1], types[i]))
      comps.push_back(1);
    else
      comps.back()++;
  }
  int res = 1;
  for (__typeof((comps).begin()) it = (comps).begin(); it != (comps).end();
       it++)
    res = 1LL * res * frac[*it] % mod;
  cout << res << endl;
}
int main(void) {
  process();
  return 0;
}
