// Slower C++ solution:
#include<bits/stdc++.h>



#ifdef _PRINTDEBUG

  #include "lib/printdebug.hpp"

#else

  #define printdebug(...) 1

#endif

#define MOD_1_000_000_007 (1000000007LL)

#define LINF (1LL<<60)

#define rep(i, n)   for (long long i = 0; i <  (long long)(n); i++)

#define rep1(i, n)  for (long long i = 1; i <  (long long)(n); i++)

#define rep2(i, n)  for (long long i = 2; i <  (long long)(n); i++)

#define rep0c(i, n) for (long long i = 0; i <= (long long)(n); i++)

#define rep1c(i, n) for (long long i = 1; i <= (long long)(n); i++)

#define rep2c(i, n) for (long long i = 2; i <= (long long)(n); i++)

#define repc0(n, i) for (long long i = (long long)(n); i >= 0; i--)

#define repc1(n, i) for (long long i = (long long)(n); i >= 1; i--)

#define repc2(n, i) for (long long i = (long long)(n); i >= 2; i--)

#define REP(i, n, m) for (long long i = (long long)(n); i < (long long)(m); i++)



namespace solver {

  using namespace std;

  typedef long long ll;



  static const ll MAX = 100010LL;

  ll N, M, indeg[MAX], dp[MAX], ans;

  bool visited[MAX];

  vector<ll> G[MAX], RG[MAX];



  void init() {

    scanf("%lld %lld", &N, &M);

    ll x, y;

    rep(i, M) {

      scanf("%lld %lld", &x, &y);

      x--; y--;

      indeg[y]++;

      G[x].push_back(y);

      RG[y].push_back(x);

    }

  }



  void bfs(ll u) {

    queue<ll> que;

    que.push(u);

    while(!que.empty()) {

      ll s = que.front(); que.pop();

      if(visited[s]) continue;

      visited[s] = true;

      for(const ll& t : RG[s]) {

        dp[s] = max(dp[s], dp[t]+1);

        ans = max(ans, dp[s]);

      }

      for(const ll& t : G[s]) {

        indeg[t]--;

        if(indeg[t] <= 0 && !visited[t]) {

          que.push(t);

        }

      }

    }

  }



  void solve() {

    rep(i, N) if(indeg[i] <= 0 && !visited[i]) bfs(i);

    cout << ans << endl;

  }

}



int main() {

  solver::init();

  solver::solve();

  return 0;

}

// Faster C++ solution for the same problem (measured speedup: 1.6188696962836x):
// 2020-09-19 20:16:20

// clang-format off

#include <bits/stdc++.h>

#ifdef LOCAL

#include "lib/debug.hpp"

#else

#define debug(...) 1

#endif

#define ALL(a) (a).begin(), (a).end()

#define RALL(a) (a).rbegin(), (a).rend()

#define rep(i, n) REP(i, 0, (n))

#define repc(i, n) REPC(i, 0, (n))

#define REP(i, n, m) for (int i = (int)(n); i < (int)(m); i++)

#define REPC(i, n, m) for (int i = (int)(n); i <= (int)(m); i++)

#define REPCM(i, n, m) for (int i = (int)(n); i >= (int)(m); i--)

using namespace std;

using ll = long long;

using ld = long double;

using pr = pair<ll, ll>;

using vll = vector<ll>;

using vpr = vector<pr>;

using P = pair<int, int>;

template<class T> inline bool chmin(T& a, const T& b) { if (a > b) { a = b; return true; } else return false; }

template<class T> inline bool chmax(T& a, const T& b) { if (a < b) { a = b; return true; } else return false; }

// #include <atcoder/convolution>

// #include <atcoder/dsu>

// #include <atcoder/fenwicktree>

// #include <atcoder/lazysegtree>

// #include <atcoder/math>

// #include <atcoder/maxflow>

// #include <atcoder/mincostflow>

// #include <atcoder/modint>

// #include <atcoder/scc>

// #include <atcoder/segtree>

// #include <atcoder/string>

// #include <atcoder/twosat>

// #include <atcoder/all>

// using namespace atcoder;



// clang-format on

const int MX = 100010;

int in[MX], dp[MX];

vector<int> G[MX];

void dfs(int u, int p) {

  if (p == -1) {

    dp[u] = 0;

  } else {

    in[u]--;

    chmax(dp[u], dp[p] + 1);

  }

  if (in[u] == 0) {

    for (auto v : G[u]) dfs(v, u);

  }

}

void answer() {

  int n, m;

  cin >> n >> m;

  rep(i, m) {

    int a, b;

    cin >> a >> b;

    a--;

    b--;

    G[a].push_back(b);

    in[b]++;

  }

  memset(dp, -1, sizeof dp);

  rep(i, n) if (in[i] == 0 && dp[i] == -1) dfs(i, -1);

  int ans = 0;

  rep(i, n) chmax(ans, dp[i]);

  cout << ans << '\n';

}



int main() {

  std::ios::sync_with_stdio(false);

  std::cin.tie(0);

  answer();

  return 0;

}