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

using ll = long long;
using ull = unsigned long long;
using ld = long double;
#define REP(NAME, NUM) for (int NAME = 0; NAME < (NUM); ++NAME)
#define BREP(NAME, NUM) for (int NAME = (NUM)-1; NAME >= 0; --NAME)
#define ALL(NAME) (NAME).begin(), (NAME).end()

#define cMOD 998244353ULL
#define cINF ((1ull<<62)-1)
#define cINFINT ((1<<30)-1)


template <typename T>
void calcFactional_mod( size_t size, 
	vector<T>& fac, vector<T>& inv, vector<T>& facInv, T mod )
{
	for( T i = 2; i < size; ++i )
	{
		fac[i] = fac[i - 1] * i % mod; // n! % mod
		inv[i] = mod - inv[mod % i] * ( mod / i ) % mod; // 逆元計算 (n)^-1 % mod
		facInv[i] = facInv[i - 1] * inv[i] % mod; // (n!)^-1 % mod
	}
}
template <typename T>
T pow_mod(T x, T n, T mod)
{
	T res = 1;
	while (n > 0)
	{
		if (n & 1)
			res = (res * x) % mod;
		x = (x * x) % mod;
		n >>= 1;
	}
	return res;
}


template< typename T >
class Matrix
{
public:
	explicit Matrix( size_t row_size, size_t col_size )
	{
		assign( row_size, col_size );
	}

	void assign( size_t row_size, size_t col_size )
	{
		mElement.assign( row_size, vector<T>() );
		REP( i, row_size ) mElement[i].assign( col_size, T(0) );
	}

	const vector<T>& operator[] ( size_t row ) const
	{ return mElement[row]; }
	vector<T>& operator[] ( size_t row )
	{ return mElement[row]; }

	const size_t rowSize() const
	{ return mElement.size(); }
	const size_t colSize() const
	{ return mElement[0].size(); }

	/*T get( size_t row, size_t col ) const
	{ return mElement[row][col]; }
	void set( size_t row, size_t col, T value )
	{ mElement[row][col] = value; }*/

	Matrix<T> operator* ( const Matrix& rhs ) const
	{
		size_t k_ = min( this->colSize(), rhs.rowSize() );
		Matrix ret( this->rowSize(), rhs.colSize() );
		REP( i, ret.rowSize() )
		REP( k, k_ )
		REP( j, ret.colSize() )
			ret[i][j] = ret[i][j] + (*this)[i][k] * rhs[k][j];
		return ret;
	}

	vector<T> operator* ( const vector<T>& rhs ) const
	{
		size_t k_ = min( this->colSize(), rhs.size() );
		vector<T> ret( this->rowSize(), T() );
		REP( i, ret.size() )
		REP( k, k_ )
			ret[i] = ret[i] + (*this)[i][k] * rhs[k];
		return ret;
	}

	// O( N^3 )
	// Ax = b を解く
	// T = double にしないと割り算で狂うので注意
	struct GaussJordanData
	{
		int rank = 0;
		int result = 0; // -1:解なし 0:不定 1:解あり
		vector<T> solution;
	};
	GaussJordanData gaussJordan() const
	{
		vector<T> b( rowSize(), 0 );
		return gaussJordan( b );
	}
	GaussJordanData gaussJordan( const vector<T>& b ) const
	{
		GaussJordanData data;
		data.result = 1;

		// 1列余分に取って、そこにbを入れる
		Matrix<T> B( rowSize(), colSize() + 1 );
		REP( i, rowSize() ) REP( j, colSize() + 1 )
		{
			if( j < colSize() ) B[i][j] = (*this)[i][j];
			else B[i][j] = b[i];
		}

		for( int i = 0, j = 0; i < rowSize() && j < colSize(); ++i, ++j )
		{
			// j列目の値の絶対値が大きい行をi行目に持ってくる
			size_t pivot = i;
			for( int k = i + 1; k < rowSize(); ++k )
				if( abs( B[k][j] )> abs( B[pivot][j] ) ) pivot = k;
			swap( B[i], B[pivot] );
			// 解が一意でない
			if( abs( B[i][j] ) <= numeric_limits<T>::epsilon() )
			{
				--i;
				data.result = 0;
				continue;
			}
			// Rankを1増やす
			++data.rank;
			// [i][j]を1にするようにi行目を割る
			for( int l = j + 1; l <= colSize(); ++l )
				B[i][l] /= B[i][j];
			B[i][j] = 1;
			// k番目の行からi番目の行を引いて、j番目の列の数を消去
			REP( k, rowSize() ) if( k != i )
			{
				T coef = B[k][j];
				for( int l = j; l <= colSize(); ++l )
					if( coef & B[i][l] == 1 )
						B[k][l] ^= 1;
			}
		}

		// 解xを作る
		data.solution.assign( colSize(), T() );
		for( int i = 0, j = 0; j < colSize(); ++i, ++j )
		{
			if( i >= rowSize() || B[i][j] == 0 )
			{
				data.solution[j] = 0;
				--i; continue;
			}
			else data.solution[j] = *( B[i].rbegin() );
		}

		// 解なしかをチェック
		if( data.result == 0 || ( data.rank ) < b.size() )
		{
			auto m = ( *this ) * data.solution;
			REP( i, m.size() ) if( m[i] != b[i] )
			{ data.result = -1; break; }
		}
		if( data.result != -1 )
		{
			// 解が一意
			if( data.rank == colSize() ) data.result = 1;
			// 解が不定
			else data.result = 0;
		}
		
		return data;
	}

protected:
	vector<vector<T>> mElement;
};


int main()
{
	auto mod = cMOD;
	auto add = [mod]      ( ll a, ll b ) { return ( a + b ) % mod; };
	auto sub = [mod]      ( ll a, ll b ) { return ( a + mod -b ) % mod; };
	auto mul = [mod]      ( ll a, ll b ) { return a * b % mod; };
	auto pow = [mod]      ( ll a, ll b ) { return pow_mod<ll>( a, b, mod ); };
	
	ll n, m; cin >> n >> m;
	Matrix<int> mtx( n, m );
	REP( i, n ) REP( j, m ) cin >> mtx[i][j];
	
	auto data = mtx.gaussJordan();
	auto ans = sub( pow( 2, n + m - 1 ), pow( 2, n + m - data.rank - 1 ) );
	cout << ans << endl;
	
	return 0;
}

Measured memory_bytes: 1024