#include <bits/stdc++.h>
#include <vector>
#include<math.h>
#include<string.h>
using namespace std;
#define MAX 300005
#define MOD 998244353
#define SMOD 998244353
#define ROOT 512
#define GMAX 20
#define INF 2000000000000000
#define EPS 0.000000001
#define NIL 0
#define FASTIO ios_base::sync_with_stdio(false);cin.tie(NULL)
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/detail/standard_policies.hpp>
int prime[MAX+1];
void sieve()
{
    int i,j;
    for(i=2; i*i<=MAX; i++)
    {
        if(prime[i]) continue;
        for(j=i; j*i<=MAX; j++)
        {
            if(prime[i*j]==0) prime[i*j]=i;
        }
    }
}
long long int fact[MAX+1],fact_inv[MAX+1];
long long int gcd(long long int a,long long int b)
{
    if(b==0) return a;
    else return gcd(b,a%b);
}
long long int egcd(long long int a, long long int b, long long int & x, long long int & y) {
    if (a == 0) {
        x = 0;
        y = 1;
        return b;
    }
    long long int x1, y1;
    long long int d = egcd(b % a, a, x1, y1);
    x = y1 - (b / a) * x1;
    y = x1;
    return d;
}
long long int ModuloInverse(long long int a,long long int n)
{
    long long int x,y;
    x=gcd(a,n);
    a=a/x;
    n=n/x;
    long long int res = egcd(a,n,x,y);
    x=(x%n+n)%n;
    return x;
}
void precal()
{
    int i;
    fact[0]=fact_inv[0]=1;
    for(i=1;i<=MAX;i++)
    {
        fact[i]=(fact[i-1]*i)%MOD;

    }
    i=MAX;
    fact_inv[i]=ModuloInverse(fact[i],MOD);
    for(i=MAX-1;i>0;i--)
    {
        fact_inv[i]=(fact_inv[i+1]*(i+1))%MOD;
    }
}
long long int C(int n,int r)
{
    long long int res=fact[n];
    res=(res*fact_inv[n-r])%MOD;
    res=(res*fact_inv[r])%MOD;
    return res;
}
int main()
{
    sieve();
    precal();
    int n,m,i;
    scanf("%d %d",&n,&m);
    long long ans=0;
    for(i=1;i<=m;i++)
    {
        int curr=i;
        int j;
        long long tot=1;
        while(curr>1)
        {
            int d=curr;
            if(prime[curr]) d=prime[curr];
            int cnt=0;
            while(curr%d==0)
            {
                curr=curr/d;
                cnt++;
            }
            tot=(tot*C(n-1+cnt,cnt))%MOD;
        }
        ans=(ans+tot)%MOD;
    }
    printf("%lld",ans);
    return 0;
}

//#include <atcoder/maxflow.hpp>
//#include <ext/pb_ds/assoc_container.hpp>
//#include <ext/pb_ds/tree_policy.hpp>


#include <iostream>
#include <map>
#include <list>
#include <set>
#include <algorithm>
#include <vector>
#include <string>
#include <functional>
#include <queue>
#include <deque>
#include <stack>
#include <unordered_map>
#include <unordered_set>
#include <cmath>
#include <iterator>
#include <random>
#include <chrono>
#include <complex>
#include <bitset>
 
 
 
#define forr(i,start,count) for (int i = (start); i < (start)+(count); ++i)
#define set_map_includes(set, elt) (set.find((elt)) != set.end())
#define readint(i) int i; cin >> i
#define readll(i) ll i; cin >> i
#define readdouble(i) double i; cin >> i
#define readstring(s) string s; cin >> s
 
typedef long long ll;
 
//using namespace __gnu_pbds;
//using namespace atcoder;
using namespace std;
 
//const ll modd = (1000LL * 1000LL * 1000LL + 7LL);
const ll modd = 998244353;

template<class T>
T fastpower(T x, ll pw, T id = 1) {
    T w = x, res = id;
    while (pw>0) {
        if (pw&1) {
            res = (res * w) % modd;
        }
        pw >>= 1;
        w = (w * w) % modd;
    }
    return res;
}


class Factorial {
    public:
      vector<ll> bin;

      Factorial(int n) : bin(n+1, 0) {
          bin[0] = 1;
          forr(i,1,n) {
              bin[i] = i*bin[i-1]; bin[i] = bin[i] % modd;
          }
      }

      ll val(int i) {  return bin[i];  }
};

class BinomialWithFactorial {  // inverse_fact forming is very slow; perhaps thorw out and comupte inverse_fact on the fly
    public:
      Factorial fact;
      vector<ll> inverse_fact;

      BinomialWithFactorial(int n) : fact(n) {
          FillInverse();
      }

      BinomialWithFactorial(Factorial& f) : fact(f) {
          FillInverse();
      }

      void FillInverse() {
          forr(i,0,fact.bin.size()) {
              inverse_fact.push_back(fastpower<ll>(fact.val(i), modd-2));
          }
      }

      ll val(int i, int j) {
          if ((j<0) || (j>i)) { return 0; }
          ll temp = (fact.val(i) * inverse_fact[j]) % modd;
          return (temp * inverse_fact[i-j]) % modd;
      }
};

BinomialWithFactorial bin(400005);

class Eratostenes {
    public:
      int N;
      vector<int> lp, pr;  // lp[i] = smallest prime that divides i; pr -- array of primes
    
    Eratostenes(int n) : N(n), lp(n+1, 0) {
      for (int i=2; i<=N; ++i) {
         if (lp[i] == 0) {
         lp[i] = i;
         pr.push_back (i);
      }
      for (int j=0; j<(int)pr.size() && pr[j]<=lp[i] && i*pr[j]<=N; ++j)
        lp[i * pr[j]] = pr[j];
      }
    }
};


Eratostenes erat(200006);


int main(int argc, char *argv[])   {
 
    ios_base::sync_with_stdio(false);
    cin.tie(0);
 
    cout.precision(12);
//    mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
//    uniform_int_distribution<int> rand_gen(0, modd);   // rand_gen(rng) gets the rand no


//    readint(test_cases);
    int test_cases = 1;
    forr(t, 1, test_cases) {
        readint(n); readint(m);
        
        ll ans = 0;
        forr(i,1,m) {
            map<int,int> decomp;
            int j = i;
            while (j>1) {
                ++decomp[erat.lp[j]];
                j /= erat.lp[j];
            }
            ll val = 1;
            for(auto x : decomp) {
                val *= bin.val(n+x.second-1, x.second);
                val %= modd;
            }
            ans += val;
        }
        cout << ans%modd << endl;

    }
 
    return 0;
}

