#include <bits/stdc++.h>
#define BIT(n) (1LL << (n))
#define REP(i, n) for (int i = 0; i < n; i++)
#define REPB(i, n) for (int i = 0; i < BIT(n); i++)
#define REPS(i, x) for (int i = 1; i <= (int)(x); i++)
#define REPR(i, n) for (int i = n; i >= 0; i--)
#define FOR(i, m, n) for (int i = m; i < n; i++)
#define FORA(i, n) for (auto &&i : n)
#define FORS(i, m, n) for (int i = m; i <= n; i++)
#define DEBUG(x) cerr << #x << ": " << x << "\n"
#define PRINTALL(V)                                                            \
  for (auto v : (V)) {                                                         \
    cerr << v << " ";                                                          \
  }                                                                            \
  cerr << "\n";
#define ALL(v) v.begin(), v.end()
#define fst first
#define snd second
#define mp make_pair
#define pb push_back
#define epb emplace_back
#define int long long
using namespace std;
template < class T > bool chmax(T &a, const T &b) {
  if (a < b) {
    a = b;
    return 1;
  }
  return 0;
}
template < class T > bool chmin(T &a, const T &b) {
  if (a > b) {
    a = b;
    return 1;
  }
  return 0;
}
struct UnionFind {
  vector< int > par; // par[i]:iの親の番号　(例) par[3] = 2 : 3の親が2
  vector< int > siz;
  vector< int > diffW;
  int parsize;

  UnionFind(int N) : par(N) { //最初は全てが根であるとして初期化
    for (int i = 0; i < N; i++)
      par[i] = i;
    siz.assign(N, 1);
    diffW.assign(N, 0);
    parsize = N;
  }

  int root(int x) { // データxが属する木の根を再帰で得る：root(x) = {xの木の根}
    if (x < 0 || parsize <= x)
      return -1;
    if (par[x] == x)
      return x;
    else {
      int r = root(par[x]);
      diffW[x] += diffW[par[x]];
      return par[x] = r;
    }
  }
  int weight(int x) {
    root(x);
    return diffW[x];
  }
  int diff(int x, int y) { return weight(y) - weight(x); }
  bool unite(int x, int y) { // xとyの木を併合
    if (x < 0 || parsize <= x || y < 0 || parsize <= y)
      return false;
    int rx = root(x); // xの根をrx
    int ry = root(y); // yの根をry
    if (rx == ry)
      return false; // xとyの根が同じ(=同じ木にある)時はそのまま
    if (siz[rx] < siz[ry])
      swap(rx, ry);
    siz[rx] += siz[ry];
    par[ry] = rx;
    // xとyの根が同じでない(=同じ木にない)時：xの根rxをyの根ryにつける
    return true;
  }
  bool unite(int x, int y, int w) { // xとyの木を併合
    if (x < 0 || parsize <= x || y < 0 || parsize <= y)
      return false;
    w += weight(x);
    w -= weight(y);
    int rx = root(x); // xの根をrx
    int ry = root(y); // yの根をry
    if (rx == ry)
      return false; // xとyの根が同じ(=同じ木にある)時はそのまま
    if (siz[rx] < siz[ry]) {
      w = -w;
      swap(rx, ry);
    }
    siz[rx] += siz[ry];
    par[ry] = rx;
    diffW[ry] = w;
    // xとyの根が同じでない(=同じ木にない)時：xの根rxをyの根ryにつける
    return true;
  }
  bool same(int x, int y) { // 2つのデータx, yが属する木が同じならtrueを返す
    if (x < 0 || parsize <= x || y < 0 || parsize <= y)
      return false;
    int rx = root(x);
    int ry = root(y);
    return rx == ry;
  }
  int size(int x) { return (x < 0 || parsize <= x) ? -1 : siz[root(x)]; }
};
template < class T > using vec = std::vector< T >;
template < class T > void print(const T &x) { cout << x << "\n"; }
const int MOD = 1000000007, INF = 1061109567;
const double EPS = 1e-10, PI = acos(-1.0);
const int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
signed main() {
  cin.tie(0), ios::sync_with_stdio(false);
  cout << fixed << setprecision(10);
  int N, M;
  cin >> N >> M;
  UnionFind T(N);
  vec< int > dis(N, 0);
  REP(i, M) {
    int l, r, d;
    cin >> l >> r >> d;
    l--, r--;
    if (T.same(l, r)) {
      if (T.diff(l, r) != d) {
        print("No");
        return 0;
      }
    } else {
      T.unite(l, r, d);
    }
  }
  print("Yes");
}

Measured memory_bytes: 3456