/*input
9 10
9 6
8 7
7 6
6 5
5 4
1 2
1 3
3 2
2 4
3 4
*/
 
// author - Madhav Thakker
 
#include <bits/stdc++.h>
#pragma comment(linker, "/stack:200000000")
#pragma GCC optimize ("O3")
#pragma GCC optimize ("O2")
#pragma GCC optimize("Ofast")
#pragma GCC optimize ("unroll-loops")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
 
using namespace std;
 
#define ll long long
#define ld long double
#define int ll
#define endl '\n'

typedef pair<int, int> pii; 
typedef pair<ld, ld> pdd;
 
#define fastio ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
#define FileIn(file) freopen(file, "r", stdin)
#define FileOut(file) freopen(file, "w", stdout)
#define all(c) c.begin(), c.end() 
#define tr(container, it )\
for(__typeof__(container.begin()) it = container.begin(); it != container.end(); it++) 
#define present(container, element) (container.find(element) != container.end())  //map, set
#define cpresent(container, element) (find(aint(container),element) != container.end()) //vector
#define what_is(x) cout << #x << " is " << x << endl; 
#define all(c) c.begin(), c.end() 
 
#define TRACE
#ifdef TRACE
#define trace(...) __f(#__VA_ARGS__, __VA_ARGS__)
template <typename Arg1>
void __f(const char* name, Arg1&& arg1){
    cout << name << " : " << arg1 << endl;
}
template <typename Arg1, typename... Args>
void __f(const char* names, Arg1&& arg1, Args&&... args){
    const char* comma = strchr(names + 1, ',');cout.write(names, comma - names) << " : " << arg1<<" | ";__f(comma+1, args...);
}
#else
#define trace(...)
#endif
 
int dx[4] = {1, -1, 0, 0};
int dy[4] = {0, 0, 1, -1};
const int MOD = 1e9+7;
 
 
int fast_pow(int a, int b){
	int res = a, ret = 1;
	while(b>0){
		if(b%2)
			ret = (ret*res)%MOD;
		res = (res*res)%MOD;b/=2;
	}
	return ret;
}
 
// --
// -- 
// -- 
// std::cout << std::fixed;
// std::cout << std::setprecision(26) << f << '\n';
// str = to_string(n)
// stoi , stoll, stol
// sort(arr, arr+n, greater<int>());
// fill(prefix.begin(), prefix.end(), 0);
// 
// 
 
 
 
////
const int maxN = 1e5+7;
std::vector<int> g[maxN];
int visited[maxN];
std::vector<int> root, topological;
int dp[maxN];
std::vector<int> pars[maxN];
////

void get_root(int u){
	if(visited[u]){
		return;
	}

	visited[u] = true;
	
	if(pars[u].size()==0){
		root.push_back(u);
	}
	else{
		for(int v:g[u]){
			if(visited[v]==false){
				get_root(v);
			}
		}
	}
}

void topological_sort(int u){
	visited[u] = true;
	for(int v:g[u]){
		if(visited[v]==false){
			topological_sort(v);
		}
	}
	topological.push_back(u);
}

signed main(){
 
	fastio;
	time_t time_t1, time_t2;
	time_t1 = clock();
 
	////////////////////////////////

	int t = 1;
	// cin >> t ;

	while(t--){

		int n, m;
		cin >> n >> m;
		int x, y;

		for (int i = 0; i < m; ++i){
			cin >> x >> y;
			g[x].push_back(y);
			pars[y].push_back(x);
		}

		memset(visited, false, sizeof visited);

		for(int i = 1; i <= n; i++){
			if(visited[i]==false){
				topological_sort(i);
			}
		}
		reverse(all(topological));
		// for(int x : topological){
		// 	cout << x << " ";
		// }
		// memset(visited, false, sizeof visited);
		// return 0;
		// memset(dp, -1, sizeof dp);

		// for(int i : root){
		// 	trace(i);
		// 	dp[i] = 0;
		// 	if(visited[i]==true){
		// 		continue;
		// 	}

		// 	topological.clear();
		// 	topological_sort(i);
		// 	reverse(all(topological));
			
		// 	assert(pars[topological[0]].size() == 0);

		for(int v : topological){
			// trace(v);
			assert(visited[v]==true);
			 for(int u : pars[v]){
			 	// trace(v, u, dp[u]);
			 	// assert(dp[u] != -1);
			 	dp[v] = max(dp[v], dp[u]+1);
			 }
		}
		
		int ans = 0;

		for(int i = 1; i <= n; i++){
			ans = max(ans, dp[i]);
		}

		cout << ans << endl;
	}
 	
	///////////////////////////////
	// 
 
	time_t2 = clock();
	// cerr << "time taken :" << time_t2 - time_t1 << endl;
 
	return 0;
}


Measured memory_bytes: 15740