#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
#define int ll

#define REP(i,n) for(int i=0;i<n;++i)
#define SORT(name) sort(name.begin(), name.end())
#define ZERO(p) memset(p, 0, sizeof(p))
#define MINUS(p) memset(p, -1, sizeof(p))
#if 1
#  define DBG(fmt, ...) printf(fmt, ##__VA_ARGS__)
#else
#  define DBG(fmt, ...)
#endif

const ll LLINF = (1LL<<60);
const int INF = (1LL<<30);
const int MOD = 1000000007;
#define MAX_N 100010

class UnionFind {
    public:
        // size: Union-Find で扱いたい要素の数
        UnionFind(int size) { m_Data.assign(size, -1); }

        // x と y の属する集合を併合
        // 既に一緒な集合内にいるなら false を
        // 新たに併合されたなら true を返す
        bool Unite(int x, int y) {
            x = Find(x), y = Find(y);
            if(x == y) { return (false); }
            if(m_Data[x] > m_Data[y]) { swap(x, y); }
            m_Data[x] += m_Data[y];
            m_Data[y] = x;
            return (true);
        }

        // 要素 k が属する集合の根を返す
        int Find(int k) {
            if(m_Data[k] < 0) { return (k); }
            return (m_Data[k] = Find(m_Data[k]));
        }

        // 要素 k と同じ集合の要素数を返す
        int Size(int k) { return (-m_Data[Find(k)]); }

    private:
        vector<int> m_Data;
};

template <typename IndexType, typename CostType>
class Dijkstra {
    private:
        struct Edge {
            IndexType to;      // 有向辺の行先頂点
            CostType cost;    // 辺の重み
        };
        typedef pair<CostType, IndexType> P;       // first: 最短距離 second: 頂点 id
    public:
        Dijkstra(IndexType v, CostType inf, bool isEnablePath) : m_V(v), m_Inf(inf), m_IsEnablePath(isEnablePath) {
            m_G = new vector<Edge>[m_V];
            m_D = new CostType[m_V];
            if(m_IsEnablePath) { m_PrevP = new IndexType[m_V]; }
        }
        ~Dijkstra() {
            delete[] m_G;
            delete[] m_D;
            if(m_IsEnablePath) { delete[] m_PrevP; }
        }
        // 有向辺の追加
        void AddDirEdge(IndexType from, IndexType to, CostType cost) { m_G[from].push_back({to, cost}); }
        // 無向辺の追加
        void AddUndirEdge(IndexType from, IndexType to, CostType cost) {
            AddDirEdge(from, to, cost);
            AddDirEdge(to, from, cost);
        }
        // 始点 s から各頂点への最短経路の導出 O(E log V)
        bool Calc(IndexType s) {
            // greater<P>を指定することでfirstが小さい順に取り出せるようにする
            priority_queue<P, vector<P>, greater<P> > que;
            fill(m_D, m_D + m_V, m_Inf);
            if(m_IsEnablePath) { fill(m_PrevP, m_PrevP + m_V, -1); }
            m_D[s] = 0;
            que.push(P(0, s));
            ll cnt = 0;
            const ll limit = 1000000;
            while(!que.empty()) {
                P p = que.top(); que.pop();
                IndexType v = p.second;
                if(m_D[v] < p.first){
                    cnt++;
                    if(cnt > limit) { return false; }
                    continue;
                }
                for(int i = 0; i < m_G[v].size(); ++i) {
                    Edge e = m_G[v][i];
                    if(m_D[e.to] > m_D[v] + e.cost) {
                        m_D[e.to] = m_D[v] + e.cost;
                        if(m_IsEnablePath) { m_PrevP[e.to] = v; }
                        que.push(P(m_D[e.to], e.to));
                    }
                    cnt++;
                    if(cnt > limit) { return false; }
                }
                cnt++;
                if(cnt > limit) { return false; }
            }
            return true;
        }
        // 始点 s から頂点 t への最短距離を返す O(1)
        // 前処理として Calc(s) が必要
        CostType GetDist(IndexType t) { return m_D[t]; }
        // 始点 s から頂点 t までの経路を vector に入れて返す (s と t 含む)
        // O(V') … V' は最短経路の頂点数
        // 前処理として Calc(s) が必要
        vector<IndexType> GetPath(IndexType t) {
            assert(m_IsEnablePath);
            vector<IndexType> path;
            for(; t != -1; t = m_PrevP[t]) { path.push_back(t); }  // t が s になるまで prev_p[t] を辿っていく
            // このままだと t->s の順になっているので逆順にする
            reverse(path.begin(), path.end());
            return path;
        }
    private:
        const IndexType m_V;                // 頂点の数
        const CostType m_Inf;              // 無限の代わりとして扱う値
        const bool m_IsEnablePath;  // 経路も求める場合は true
        vector<Edge>* m_G;          // 頂点 i から延びている辺の隣接リスト
        CostType* m_D;                     // 各頂点への最短コストが入る配列
        IndexType* m_PrevP;                 // 最短経路の各頂点を保存
};

ll N, M;
vector< pair< pair<ll, ll>, ll> > Lrd;
vector< pair< pair<ll, ll>, ll> > group[MAX_N]; // グループ i に属する Lrd
set<ll> exist;   // group に要素がひとつでもある id

bool check(vector< pair< pair<ll, ll>, ll> >& v, ll root) {
    Dijkstra<ll, ll> dk(N, LLINF, false);
    // 辺の追加
    for(auto& edge : v) {
        ll l = edge.first.first;
        ll r = edge.first.second;
        ll d = edge.second;
        dk.AddDirEdge(l, r, d);
        dk.AddDirEdge(r, l, -d);
    }
    return dk.Calc(root);
}

signed main()
{
    cin >> N >> M;

    UnionFind uf(N);
    REP(i, M) {
        ll l, r, d;
        cin >> l >> r >> d;
        l--; r--;
        Lrd.push_back({{l, r}, d});
        // Union-Find を使ったグループ分け
        uf.Unite(l, r);
    }

    REP(i, M) {
        ll id = uf.Find(Lrd[i].first.first);
        group[id].push_back(Lrd[i]);
        exist.insert(id);
    }

    for(auto& id : exist) {
        if(!check(group[id], id)) {
            printf("No\n");
            return 0;
        }
    }

    printf("Yes\n");
    return 0;
}


Measured memory_bytes: 49384