import std.stdio, std.conv, std.string, std.bigint;
import std.math, std.random, std.datetime;
import std.array, std.range, std.algorithm, std.container, std.format;
string read(){ static string[] ss; while(!ss.length) ss = readln.chomp.split; string res = ss[0]; ss.popFront; return res; }

/*

有向グラフ
トポロジカルソートみたいなもの
※トポロジカルソートみたいなものは先日の日経予選にもあり

ソース、シンクを考えると、題意のパスはどれかのソースからどれかのシンクに至っている

入次数を調べることでソースたちを見つける
他の点にもすべて入次数を持たせておく

ソースから幅優先探索
ソースの値は0とする
自分の子たちに自分の値＋１をつける
ただしすでに値がある場合は、改善される場合だけ上書き
いずれにしてもこれらをされた子は入次数を減らす
入次数が残り0になった子はその子から続きの探索をする

*/

class Node{
	Node[] kids;
	int parentcount;
	int value;
	int id;
	this(int id){
		this.id = id;
	}
}

void main(){
	int n = read.to!int;
	int m = read.to!int;
	
	Node[] nodes;
	foreach(i; 0 .. n) nodes ~= new Node(i);
	
	foreach(j; 0 .. m){
		int x = read.to!int - 1;
		int y = read.to!int - 1;
		nodes[x].kids ~= nodes[y];
		nodes[y].parentcount += 1;
	}
	
	Node[] queue;
	int iq;
	foreach(nd; nodes) if(nd.parentcount == 0){
		nd.value = 0;
		queue ~= nd;
	}
	
	while(iq < queue.length){
		Node nd = queue[iq];
		foreach(kid; nd.kids){
			kid.value = nd.value + 1;
			kid.parentcount -= 1;
			if(kid.parentcount == 0) queue ~= kid;
		}
	
	iq ++;
	}
	
	int ans = 0;
	foreach(nd; nodes) if(ans < nd.value) ans = nd.value;
	
	ans.writeln;
}
