// Slower C++ solution:
# include <cstring>

# include <cstdio>

# include <iostream>

# include <queue>

# define N 100050 //N为顶点个数 

# define M 200050 //N为顶点个数 

# define WQD 0x7f7f7f7f//无穷大 

int n;//图的顶点个数

int m;//图的边数 

struct Edge{int next,to, w;}  e[M<<1];//不用0号单元 

int head[N+1]; 

int dis[N+1];//存放源点到其他点的路径

bool vis[N+1];

bool book[N+1];

bool spfa(int s);//差分约束判断 

using namespace std;

int main()

{

	ios::sync_with_stdio ( false ) ;

	int u,v,w;

	memset(head,-1,sizeof(head));

	cin>>n>>m;

	for(int i=1; i<=m; i++)  //本题约束关系为=，要添加两条边 

	{

		cin>>u>>v>>w;//输入第i条边 ,i为编号 

		e[i]=(Edge){head[u],v,w};head[u]=i;

		e[m+i]=(Edge){head[v],u,-w};head[v]=m+i;

	}

	memset(book,0,sizeof(book));

	memset(dis,0x7f,sizeof(dis));

	for(int i=1; i<=n; i++)//每一片的连通区域都要判断 

		if(!vis[i]) 

		{

			vis[i]=1;

			if(spfa(i)==false)  {cout<<"No";return 0;}//有约束自相矛盾 

		}

	cout<<"Yes";//所以的点都符合条件 

	return 0;

}

bool spfa(int s)

{

 	queue<int>q;

 	//memset(book,0,sizeof(book));

	//memset(dis,0x7f,sizeof(dis));

	dis[s]=0;

	q.push(s);book[s]=1;

	while(q.size()>0)//如果没松弛就松弛，如果松弛过就判断是否符合约束条件，如果自相矛盾就是不可能的数据 

	{

		int k=q.front();q.pop();//对头元素出队 

		book[k]=0;

		for(int i=head[k]; i!=-1; i=e[i].next)

		{

			int v=e[i].to;

			if(dis[v]==WQD && dis[k]<WQD)//如果v点没松弛过，就松弛更新dis[v]的值 				

			{	//题目的约束条件是= 			

				dis[v]=dis[k]+e[i].w;//更新 

				if(book[v]==0)	{q.push(v);	book[v]=1;}//入队标记

				vis[v]=1;//标记该点已经松弛过 

			}

			else if(dis[v]<WQD && dis[k]<WQD && dis[v]!=dis[k]+e[i].w)	return false;//据题意理解差分系统约束条件，不相等，自相矛盾 

		}		

    }

	return true;//别掉了这句 

}

// Faster C++ solution for the same problem (measured speedup: 1.8827852609309852x):
//AT3882 people on a line

//2018/6/23 

#include <cstdio>

#include <iostream>

#include <algorithm>

#include <cstring>

#include <queue>

#define N 100001

#define M 200001

#define INF 0x7fffffff

#define ri register int

#define in inline

using namespace std;

struct edge

{

    int to,w,next;

}e[M<<1];

//vis数组表示已松弛的点

//book数组表示进入队列的点 

int head[N],vis[N],dis[N];

int m,n;

bool book[N];

in bool spfa(int s);

int main()

{

    ios::sync_with_stdio(false);

    int u,v,w;

    cin>>n>>m;

    for(ri i=1;i<=m;i++)

    {

        cin>>u>>v>>w;

        e[i]=(edge) {v,w,head[u]}; head[u]=i;

        e[i+m]=(edge) {u,-w,head[v]}; head[v]=i+m;

    }

    //关于对dis数组放在外面的理解:

	//从第一个点开始时，如果有的点没有被松弛，即该点无法到达，起点与该点没有关系

	//则从没有被松弛的点开始，则是两个完全无关的图，dis数组有没有置0毫无关系 

    fill(dis+1,dis+n+1,INF);

    //所有约束条件需要考虑到

    //可能有多个分块，前10个约束条件与后10个 无关联 

    for(ri i=1;i<=n;i++)

        if(!vis[i])

        {

            if(spfa(i))

            {cout<<"No"; return 0;}

            vis[i]=1;

        }	

    cout<<"Yes";

    return 0;

}

in bool spfa(int s)

{

    queue <int> q;

    int k;

    q.push(s); book[s]=1;

    dis[s]=0;

    while(q.size())

    {

        k=q.front(); q.pop();

        book[k]=0;//出队列应置为0 

        for(ri i=head[k]; i ;i=e[i].next)

        {

            int v=e[i].to;

            if(!vis[v]&& dis[k]<INF)

            {

                dis[v]=dis[k]+e[i].w;

                if(!book[v])

                {q.push(v); book[v]=1;}

                vis[v]=1;

            }

            else if(dis[k]<INF && dis[v]<INF && dis[v]!=dis[k]+e[i].w) return true;

            //此时相当于自相矛盾

            //思想：即先认定一开始所给条件均正确（此时松弛）

            //最后再来验证接下来的条件是否满足原先条件 

        }

    }

    return false;

}