Cion-lab commited on
Commit
398d16e
·
verified ·
1 Parent(s): 1878839

perm_sha: identify the visit order by hashing the permutation, not just its seed (E-035 residual) -- cursor gains shuffle_perm_sha, asserted on resume; session repins the two files

Browse files
Files changed (1) hide show
  1. train/shard_dataset.py +32 -8
train/shard_dataset.py CHANGED
@@ -80,6 +80,22 @@ class PackedTokenStore:
80
  return out
81
 
82
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  def make_dataset(store, seq_len, seed, start_sample=0, count=None, shuffle=True):
84
  """A map-style torch Dataset of (input_ids, labels, attention_mask) with an exact resume cursor.
85
 
@@ -91,11 +107,14 @@ def make_dataset(store, seq_len, seed, start_sample=0, count=None, shuffle=True)
91
 
92
  n_samples = store.total_tokens // seq_len
93
  perm = None
 
94
  if shuffle:
95
  # Seed must be identical on every resume or the two runs read different data orders. Stored in
96
- # the checkpoint (cursor.json) and asserted by the trainer.
97
- perm = np.random.default_rng(np.random.PCG64(seed)).permutation(n_samples)
98
- perm = perm[start_sample:]
 
 
99
  else:
100
  perm = np.arange(start_sample, n_samples, dtype=np.int64)
101
  if count is not None:
@@ -117,20 +136,25 @@ def make_dataset(store, seq_len, seed, start_sample=0, count=None, shuffle=True)
117
  # these names keeps it from dropping anything.
118
  column_names = ["input_ids", "labels", "attention_mask"]
119
 
120
- _DS.perm = perm
121
- return _DS()
 
 
 
 
122
 
123
 
124
- def cursor_dict(seq_len, samples_consumed, seed, dataset_files_sha=None, step=None):
125
  """The whole of the data-position state. Written into every checkpoint, checked on every resume.
126
 
127
  `step` is recorded alongside `samples_consumed` so the two can be cross-checked on resume: they are
128
  redundant by construction (samples = step x sequences-per-step), and a cursor where they disagree is a
129
- corrupted or hand-edited file, not a position to continue from.
 
130
  """
131
  return {"seq_len": seq_len, "samples_consumed": samples_consumed,
132
  "tokens_consumed": samples_consumed * seq_len, "shuffle_seed": seed,
133
- "step": step, "dataset_files_sha": dataset_files_sha}
134
 
135
 
136
  def load_cursor(dirpath):
 
80
  return out
81
 
82
 
83
+ def perm_sha(n_samples, seed, base=None):
84
+ """Identity of the visit order itself, not just the seed that produced it.
85
+
86
+ `seed` pins the order only for a fixed numpy. Kaggle updates its notebook image underneath us, and a
87
+ session that resumed after such a bump would read a different permutation from the same seed -- some
88
+ windows twice, others never -- while every other guard (weights, cursor arithmetic, fingerprint,
89
+ batch size) still agreed. Hashing the full permutation is ~10 ms and makes that state impossible to
90
+ enter quietly. Computed before the resume slice, so two sessions at different offsets agree on it.
91
+ `base` lets a caller that already generated the permutation avoid generating it twice.
92
+ """
93
+ import hashlib
94
+ if base is None:
95
+ base = np.random.default_rng(np.random.PCG64(seed)).permutation(n_samples)
96
+ return hashlib.sha256(np.ascontiguousarray(base).tobytes()).hexdigest()[:16]
97
+
98
+
99
  def make_dataset(store, seq_len, seed, start_sample=0, count=None, shuffle=True):
100
  """A map-style torch Dataset of (input_ids, labels, attention_mask) with an exact resume cursor.
101
 
 
107
 
108
  n_samples = store.total_tokens // seq_len
109
  perm = None
110
+ order_sha = "unshuffled"
111
  if shuffle:
112
  # Seed must be identical on every resume or the two runs read different data orders. Stored in
113
+ # the checkpoint (cursor.json) and asserted by the trainer -- alongside perm_sha(), which catches
114
+ # the case the seed alone cannot: the same seed on a different numpy.
115
+ base = np.random.default_rng(np.random.PCG64(seed)).permutation(n_samples)
116
+ order_sha = perm_sha(n_samples, seed, base=base)
117
+ perm = base[start_sample:]
118
  else:
119
  perm = np.arange(start_sample, n_samples, dtype=np.int64)
120
  if count is not None:
 
136
  # these names keeps it from dropping anything.
137
  column_names = ["input_ids", "labels", "attention_mask"]
138
 
139
+ ds = _DS()
140
+ # Instance attributes, not class attributes: `_DS.perm = perm` would let a second make_dataset() call
141
+ # in the same process overwrite the first one's order.
142
+ ds.perm = perm
143
+ ds.order_sha = order_sha
144
+ return ds
145
 
146
 
147
+ def cursor_dict(seq_len, samples_consumed, seed, dataset_files_sha=None, step=None, order_sha=None):
148
  """The whole of the data-position state. Written into every checkpoint, checked on every resume.
149
 
150
  `step` is recorded alongside `samples_consumed` so the two can be cross-checked on resume: they are
151
  redundant by construction (samples = step x sequences-per-step), and a cursor where they disagree is a
152
+ corrupted or hand-edited file, not a position to continue from. `shuffle_perm_sha` is the identity of
153
+ the visit order, which the seed alone does not pin (see perm_sha).
154
  """
155
  return {"seq_len": seq_len, "samples_consumed": samples_consumed,
156
  "tokens_consumed": samples_consumed * seq_len, "shuffle_seed": seed,
157
+ "shuffle_perm_sha": order_sha, "step": step, "dataset_files_sha": dataset_files_sha}
158
 
159
 
160
  def load_cursor(dirpath):