feat: implement hardware-adaptive compute bounding and dynamic entropy routing (Eqs. 3-4)

#2
Files changed (1) hide show
  1. train.py +37 -10
train.py CHANGED
@@ -158,16 +158,40 @@ class StackedLDMHeads(nn.Module):
158
  logits = self.head(forecast)
159
  return logits
160
 
161
- class LogitUncertaintyFilter(nn.Module):
 
 
 
 
 
 
 
162
  def compute_entropy(self, logits: torch.Tensor) -> torch.Tensor:
163
  probs = F.softmax(logits.float(), dim=-1)
164
  entropy = -torch.sum(probs * torch.log(probs + 1e-9), dim=-1)
165
  return entropy
166
 
167
- def forward(self, logits: torch.Tensor, threshold: float):
168
  entropy = self.compute_entropy(logits)
169
- mask = entropy >= threshold
170
- return mask, entropy
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
 
172
  class ActorCriticPruner:
173
  def __init__(self, lm_head, lambda_reg=0.1):
@@ -216,12 +240,12 @@ class ActorCriticPruner:
216
 
217
 
218
  class ADAPTDIFFPipeline(nn.Module):
219
- def __init__(self, base_lm_model, block_size=12, entropy_threshold=1.5):
220
  super().__init__()
221
  self.base_model = base_lm_model.model
222
  self.lm_head = base_lm_model.lm_head
223
  self.block_size = block_size
224
- self.entropy_threshold = entropy_threshold
225
 
226
  self.ldm_heads = StackedLDMHeads(
227
  hidden_size=base_lm_model.config.hidden_size,
@@ -229,10 +253,13 @@ class ADAPTDIFFPipeline(nn.Module):
229
  block_size=block_size
230
  ).to(DEVICE)
231
 
232
- self.router = LogitUncertaintyFilter()
233
  self.pruner = ActorCriticPruner(self.lm_head)
234
 
235
- def generate_adapt_diff(self, input_ids, max_new_tokens=128):
 
 
 
236
  current_seq = input_ids.clone()
237
  generated_count = 0
238
  total_full_transformer_evals = 0
@@ -245,7 +272,7 @@ class ADAPTDIFFPipeline(nn.Module):
245
  block_logits = self.ldm_heads(last_hidden).squeeze(0).squeeze(0)
246
  draft_tokens = torch.argmax(block_logits, dim=-1)
247
 
248
- mask, entropy = self.router(block_logits, self.entropy_threshold)
249
 
250
  if not mask.any():
251
  final_block = draft_tokens
@@ -277,7 +304,7 @@ a2d_model = AutoModelForCausalLM.from_pretrained(
277
  device_map=DEVICE
278
  )
279
 
280
- pipeline = ADAPTDIFFPipeline(a2d_model, block_size=12, entropy_threshold=1.5)
281
  print("Downloading LDM head projection weights for calibration baseline...")
282
  ldm_weights_path = hf_hub_download(repo_id=ADAPT_DIFF_ID, filename="ldm_heads.pt")
283
  pipeline.ldm_heads.load_state_dict(torch.load(ldm_weights_path, map_location=DEVICE))
 
158
  logits = self.head(forecast)
159
  return logits
160
 
161
+ class HardwareAdaptiveRouter(nn.Module):
162
+ def __init__(self, c_base=1.0, c_bf16=5.0):
163
+ super().__init__()
164
+ # c_base: FLOP cost proxy for the LDM block projection
165
+ # c_bf16: FLOP cost proxy for a single token bfloat16 refinement
166
+ self.c_base = c_base
167
+ self.c_bf16 = c_bf16
168
+
169
  def compute_entropy(self, logits: torch.Tensor) -> torch.Tensor:
170
  probs = F.softmax(logits.float(), dim=-1)
171
  entropy = -torch.sum(probs * torch.log(probs + 1e-9), dim=-1)
172
  return entropy
173
 
174
+ def forward(self, logits: torch.Tensor, c_target: float):
175
  entropy = self.compute_entropy(logits)
176
+
177
+ # Equation (3): C_step = C_base + sum(M_i) * C_BF16 <= c_target
178
+ # Calculate the maximum number of bfloat16 refinements we can afford
179
+ max_refinements = max(0, int((c_target - self.c_base) / self.c_bf16))
180
+ L = entropy.shape[-1]
181
+ allowed_refinements = min(max_refinements, L)
182
+
183
+ mask = torch.zeros_like(entropy, dtype=torch.bool)
184
+ dynamic_tau = float('inf')
185
+
186
+ if allowed_refinements > 0:
187
+ # Equation (4): tau = inf { t | C_step(t) <= C_target }
188
+ sorted_entropy, indices = torch.sort(entropy, descending=True)
189
+ dynamic_tau = sorted_entropy[allowed_refinements - 1].item()
190
+
191
+ # Apply the computed hardware-bounded threshold
192
+ mask[indices[:allowed_refinements]] = True
193
+
194
+ return mask, entropy, dynamic_tau
195
 
196
  class ActorCriticPruner:
197
  def __init__(self, lm_head, lambda_reg=0.1):
 
240
 
241
 
242
  class ADAPTDIFFPipeline(nn.Module):
243
+ def __init__(self, base_lm_model, block_size=12, target_budget=15.0):
244
  super().__init__()
245
  self.base_model = base_lm_model.model
246
  self.lm_head = base_lm_model.lm_head
247
  self.block_size = block_size
248
+ self.target_budget = target_budget
249
 
250
  self.ldm_heads = StackedLDMHeads(
251
  hidden_size=base_lm_model.config.hidden_size,
 
253
  block_size=block_size
254
  ).to(DEVICE)
255
 
256
+ self.router = HardwareAdaptiveRouter(c_base=1.0, c_bf16=5.0)
257
  self.pruner = ActorCriticPruner(self.lm_head)
258
 
259
+ def generate_adapt_diff(self, input_ids, max_new_tokens=128, c_target=None):
260
+ if c_target is None:
261
+ c_target = self.target_budget
262
+
263
  current_seq = input_ids.clone()
264
  generated_count = 0
265
  total_full_transformer_evals = 0
 
272
  block_logits = self.ldm_heads(last_hidden).squeeze(0).squeeze(0)
273
  draft_tokens = torch.argmax(block_logits, dim=-1)
274
 
275
+ mask, entropy, dynamic_tau = self.router(block_logits, c_target)
276
 
277
  if not mask.any():
278
  final_block = draft_tokens
 
304
  device_map=DEVICE
305
  )
306
 
307
+ pipeline = ADAPTDIFFPipeline(a2d_model, block_size=12, target_budget=15.0)
308
  print("Downloading LDM head projection weights for calibration baseline...")
309
  ldm_weights_path = hf_hub_download(repo_id=ADAPT_DIFF_ID, filename="ldm_heads.pt")
310
  pipeline.ldm_heads.load_state_dict(torch.load(ldm_weights_path, map_location=DEVICE))