// https://atcoder.jp/contests/dp/tasks/dp_g
#include <bits/stdc++.h>
using namespace std;
template<class C>constexpr int sz(const C&c){return int(c.size());}
using ll=long long;constexpr const char nl='\n',sp=' ';

struct StaticGraph {
  vector<int> ST, TO, A, B; StaticGraph(int V) : ST(V + 1, 0) {}
  void reserveDiEdges(int maxEdges) {
    TO.reserve(maxEdges); A.reserve(maxEdges); B.reserve(maxEdges);
  }
  void addDiEdge(int from, int to) {
    ST[from]++; A.push_back(from); B.push_back(to);
  }
  void addBiEdge(int v, int w) { addDiEdge(v, w); addDiEdge(w, v); }
  void build() {
    partial_sum(ST.begin(), ST.end(), ST.begin()); TO = B;
    for (int e = 0; e < int(A.size()); e++) TO[--ST[A[e]]] = B[e];
  }
  struct Iterator {
    const StaticGraph &G; int i;
    Iterator(const StaticGraph &G, int i) : G(G), i(i) {}
    Iterator &operator ++ () { i++; return *this; }
    int operator * () const { return G.TO[i]; }
    bool operator != (const Iterator &it) const { return i != it.i; }
  };
  struct Adj {
    const StaticGraph &G; int v;
    Adj(const StaticGraph &G, int v) : G(G), v(v) {}
    const Iterator begin() const { return Iterator(G, G.ST[v]); }
    const Iterator end() const { return Iterator(G, G.ST[v + 1]); }
  };
  const Adj operator [] (int v) const { return Adj(*this, v); }
  int size() const { return int(ST.size()) - 1; }
};

struct TopologicalOrder {
  int V; vector<int> ind, ord, inDeg;
  int getTo(int e) { return e; }
  template <class T> int getTo(const pair<int, T> &e) { return e.first; }
  template <class DAG> TopologicalOrder(const DAG &G)
      : V(G.size()), ind(V, -1), ord(V), inDeg(V, 0) {
    int front = 0, back = 0;
    for (int v = 0; v < V; v++) for (auto &&e : G[v]) inDeg[getTo(e)]++;
    for (int v = 0; v < V; v++) if (inDeg[v] == 0) ord[back++] = v;
    while (front < back) {
      int v = ord[front]; ind[v] = front++;
      for (auto &&e : G[v]) if (--inDeg[getTo(e)] == 0) ord[back++] = getTo(e);
    }
  }
};

template <class T, class Comparator = less<T>> struct DAGSP {
  using Edge = tuple<int, int, T>; vector<T> dist; vector<int> par; T INF;
  TopologicalOrder ord;
  int getTo(int e) { return e; }
  T getWeight(int) { return 1; }
  int getTo(const pair<int, T> &e) { return e.first; }
  T getWeight(const pair<int, T> &e) { return e.second; }
  template <class DAG> DAGSP(const DAG &G, const vector<int> &srcs, T INF)
      : dist(G.size(), INF), par(G.size(), -1), INF(INF), ord(G) {
    for (int s : srcs) dist[s] = T();
    for (int v : ord.ord) for (auto &&e : G[v]) {
      int w = getTo(e); T weight = getWeight(e);
      if (dist[v] != INF && Comparator()(dist[v] + weight, dist[w]))
        dist[w] = dist[par[w] = v] + weight;
    }
  }
  template <class DAG> DAGSP(const DAG &G, int s, T INF)
      : DAGSP(G, vector<int>{s}, INF) {}
  vector<Edge> getPath(int v) {
    vector<Edge> path; for (; par[v] != -1; v = par[v])
      path.emplace_back(par[v], v, dist[v] - dist[par[v]]);
    reverse(path.begin(), path.end()); return path;
  }
};

int main() {
  // freopen("in.txt", "r", stdin);
  // freopen("out.txt", "w", stdout);
  // freopen("err.txt", "w", stderr);
  ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
  int N, M;
  cin >> N >> M;
  StaticGraph G(N);
  vector<int> inDeg(N, 0);
  for (int i = 0; i < M; i++) {
    int a, b;
    cin >> a >> b;
    G.addDiEdge(--a, --b);
    inDeg[b]++;
  }
  vector<int> srcs;
  for (int v = 0; v < N; v++) if (inDeg[v] == 0) srcs.push_back(v);
  G.build();
  DAGSP<int, greater<int>> LP(G, srcs, INT_MIN);
  cout << *max_element(LP.dist.begin(), LP.dist.end()) << nl;
  return 0;
}


Measured memory_bytes: 7424