autoprogrammer commited on
Commit
9384b5d
·
verified ·
1 Parent(s): 3d20c97

Update generation_utils.py

Browse files
Files changed (1) hide show
  1. generation_utils.py +67 -87
generation_utils.py CHANGED
@@ -1,18 +1,5 @@
1
  # coding=utf-8
2
- # Copyright 2024 The Dream team, HKUNLP Group and the HuggingFace Inc.
3
- #
4
- # Licensed under the Apache License, Version 2.0 (the "License");
5
- # You may not use this file except in compliance with the License.
6
- # You may obtain a copy of the License at
7
- #
8
- # http://www.apache.org/licenses/LICENSE-2.0
9
- #
10
- # Unless required by applicable law or agreed to in writing, software
11
- # distributed under the License is distributed on an "AS IS" BASIS,
12
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- # See the License for the specific language governing permissions and
14
- # limitations under the License.
15
-
16
  import warnings
17
  import copy
18
  from dataclasses import dataclass
@@ -34,10 +21,8 @@ def top_p_logits(logits, top_p=None):
34
  sorted_logits, sorted_indices = torch.sort(logits, descending=True)
35
  cumulative_probs = torch.cumsum(F.softmax(sorted_logits, dim=-1), dim=-1)
36
  sorted_indices_to_remove = cumulative_probs > top_p
37
- # Shift the indices to the right to keep the first token above the threshold
38
  sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone()
39
  sorted_indices_to_remove[..., 0] = 0
40
-
41
  mask = torch.zeros_like(logits, dtype=torch.bool, device=logits.device)
42
  mask = mask.scatter_(-1, sorted_indices, sorted_indices_to_remove)
43
  logits = logits.masked_fill(mask, torch.finfo(logits.dtype).min)
@@ -47,10 +32,9 @@ def top_p_logits(logits, top_p=None):
47
  def top_k_logits(logits, top_k=None):
48
  if top_k is None:
49
  return logits
50
- top_k = int(min(top_k, logits.size(-1))) # Safety check
51
  if top_k <= 0:
52
  return logits
53
- # Remove all tokens with a probability less than the last token of the top-k
54
  indices_to_remove = logits < torch.topk(logits, top_k)[0][..., -1, None]
55
  logits = logits.masked_fill(indices_to_remove, torch.finfo(logits.dtype).min)
56
  return logits
@@ -85,7 +69,7 @@ def sample_tokens(logits, temperature=0.0, top_p=None, top_k=None, margin_confid
85
  confidence = top1_probs - top2_probs
86
 
87
  if neg_entropy:
88
- # 负熵(数值 ≤ 0;越接近 0 越大代表越确定)
89
  epsilon = 1e-10
90
  log_probs = torch.log(probs + epsilon)
91
  confidence = torch.sum(probs * log_probs, dim=-1)
@@ -116,12 +100,11 @@ class DreamGenerationConfig(GenerationConfig):
116
  self.alg: str = kwargs.pop("alg", 'origin') # 'origin' | 'maskgit_plus' | 'topk_margin' | 'entropy'
117
  self.alg_temp: Optional[float] = kwargs.pop("alg_temp", None)
118
 
119
- # === RCR 参数(新增,默认关闭,不影响原逻辑) ===
120
  self.rcr: bool = kwargs.pop("rcr", False)
121
- # 仅在 rcr=True 时用于选择置信度算法;rcr=False 不读取它
122
  self.conf_alg: str = kwargs.pop("conf_alg", 'maskgit_plus')
123
 
124
- # generate outputs
125
  self.num_return_sequences: int = kwargs.pop("num_return_sequences", 1)
126
  self.return_dict_in_generate: bool = kwargs.pop("return_dict_in_generate", False)
127
  self.output_history: bool = kwargs.pop("output_history", False)
@@ -169,85 +152,91 @@ class DreamGenerationMixin:
169
  attention_mask = attention_mask.repeat_interleave(expand_size, dim=0)
170
  return input_ids, attention_mask
171
 
172
- # === 新版:RCR 核心(历史置信度) ===
 
 
173
  def _apply_rcr_logic(
174
  self,
175
  x: torch.Tensor,
176
  x0: torch.Tensor,
177
- conf_now: torch.Tensor,
178
- mask_index: torch.Tensor,
179
- fixed_conf: torch.Tensor,
180
- gen_mask: torch.Tensor,
181
- init_mask_count: torch.Tensor,
 
 
182
  mask_token_id: int,
183
  step: int,
184
  total_steps: int,
185
  s: torch.Tensor,
186
  t: torch.Tensor,
 
187
  ):
188
  """
189
- Running Confidence Remasking(历史置信度版):
190
- 1) mask 子集内以当步置信度 conf_now 选择 top-k_j 个位置“确认”(写 token);
191
- 2) 更新历史置信度 fixed_conf = max(fixed_conf, conf_now)(仅对新选入位置);
192
- 3) 按“累计允许确认配额” target_cum = init_mask_count * (1 - s/t) 若超额,
193
- 在已确认集合 gen_mask 内按 fixed_conf 最低回遮 over 个位置。
194
-
195
- 说明:
196
- - conf_now 用 float32 维护,避免与 bfloat16 混写导致 dtype 报错;
197
- - 对 entropy:conf_now = 负熵(≤0 且越接近 0 越大代表越确定),配合 topk(largest=True) 没问题。
198
  """
199
  device = x.device
200
  B, L = x.shape
201
 
202
- # 计算“当步”选入规模(与 vanilla 同口径:平均剩余 mask * (1 - s/t))
203
  avg_mask_now = (mask_index.sum().item() / max(1, mask_index.shape[0]))
204
  ratio = (1.0 - (s.item() / t.item())) if step < total_steps - 1 else 1.0
205
  number_transfer_tokens = int(avg_mask_now * ratio)
206
 
207
- # 确保当步置信度是 float32
208
- conf_now = conf_now.to(torch.float32)
209
-
210
- # 仅在 mask 处有效的“全长”视图
211
- full_conf_now = torch.full((B, L), float("-inf"), dtype=torch.float32, device=device)
212
  full_x0 = torch.full((B, L), mask_token_id, dtype=torch.long, device=device)
213
  full_conf_now[mask_index] = conf_now
214
  full_x0[mask_index] = x0
215
 
216
- # 逐样本处理
217
  for j in range(B):
218
  masked_j = int(mask_index[j].sum().item())
219
  k_j = min(number_transfer_tokens, masked_j)
220
  if k_j > 0:
221
  conf_row = full_conf_now[j] # float32
222
- # 选当步 top-k_j
223
  _, sel_idx = torch.topk(conf_row, k=k_j, largest=True)
224
- # 写 token & 标记确认
 
225
  x[j, sel_idx] = full_x0[j, sel_idx]
226
  gen_mask[j, sel_idx] = True
227
- # 历史置信度取 running max
 
228
  fixed_conf[j, sel_idx] = torch.maximum(fixed_conf[j, sel_idx], conf_row[sel_idx])
 
 
229
 
230
- # 累计允许确认配额(以初始 mask 为基数)
231
  init_m = int(init_mask_count[j].item())
232
- if step < total_steps - 1:
233
- target_cum = int(init_m * (1.0 - (s.item() / t.item())))
234
- else:
235
- target_cum = init_m # 最后一步允许全确认
236
 
237
  current_gen = int(gen_mask[j].sum().item())
238
  over = max(0, current_gen - target_cum)
239
  if over > 0:
240
- # 在已确认集合里按历史置信度最低回遮
241
  gen_idx = torch.where(gen_mask[j])[0]
242
  if gen_idx.numel() > 0:
243
- hist_vals = fixed_conf[j, gen_idx] # float32
244
- over = min(over, int(gen_idx.numel()))
245
- _, low_local = torch.topk(hist_vals, k=over, largest=False)
246
- low_global = gen_idx[low_local]
247
- # 回遮:恢复为 MASK,并撤销确认标记 & 清空历史置信度
248
- x[j, low_global] = mask_token_id
249
- gen_mask[j, low_global] = False
250
- fixed_conf[j, low_global] = float("-inf")
 
 
 
 
 
 
 
 
251
 
252
  def _validate_generated_length(self, generation_config, input_ids_length, has_default_max_length):
253
  if is_torchdynamo_compiling():
@@ -363,10 +352,7 @@ class DreamGenerationMixin:
363
  warnings.warn(
364
  "You are calling .generate() with the `input_ids` being on a device type different"
365
  f" than your model's device. `input_ids` is on {input_ids.device.type}, whereas the model"
366
- f" is on {self.device.type}. You may experience unexpected behaviors or slower generation."
367
- " Please make sure that you have put `input_ids` to the"
368
- f" correct device by calling for example input_ids = input_ids.to('{self.device.type}') before"
369
- " running `.generate()`.",
370
  UserWarning,
371
  )
372
  if (
@@ -403,7 +389,6 @@ class DreamGenerationMixin:
403
  generation_tokens_hook_func,
404
  generation_logits_hook_func
405
  ) -> Union[DreamModelOutput, torch.LongTensor]:
406
- # === 基本变量 ===
407
  output_history = generation_config.output_history
408
  return_dict_in_generate = generation_config.return_dict_in_generate
409
  max_length = generation_config.max_length
@@ -416,7 +401,7 @@ class DreamGenerationMixin:
416
  top_p = generation_config.top_p
417
  top_k = generation_config.top_k
418
 
419
- # === RCR 控制变量 ===
420
  rcr = generation_config.rcr
421
  conf_alg = generation_config.conf_alg
422
 
@@ -439,27 +424,26 @@ class DreamGenerationMixin:
439
 
440
  timesteps = torch.linspace(1, eps, steps + 1, device=x.device)
441
 
442
- # === RCR 缓冲(仅 rcr=True 时启用) ===
443
  if rcr:
444
- init_mask_count = (x == mask_token_id).sum(dim=1) # [B]
445
- fixed_conf = torch.full(
446
- x.shape, float("-inf"), dtype=torch.float32, device=x.device
447
- ) # 历史置信度
448
- gen_mask = torch.zeros_like(x, dtype=torch.bool) # 已确认集合
449
  else:
450
  init_mask_count = None
451
  fixed_conf = None
 
452
  gen_mask = None
 
453
 
454
- # hooks:允许用户中间控制
455
  x = generation_tokens_hook_func(None, x, None)
456
 
457
  for i in range(steps):
458
  mask_index = (x == mask_token_id)
459
  logits = self(x, attention_mask, tok_idx).logits
460
- # 右移一位(Dream 原实现)
461
  logits = torch.cat([logits[:, :1], logits[:, :-1]], dim=1)
462
-
463
  logits = generation_logits_hook_func(i, x, logits)
464
 
465
  mask_logits = logits[mask_index]
@@ -467,7 +451,6 @@ class DreamGenerationMixin:
467
  s = timesteps[i + 1]
468
 
469
  if alg == 'origin':
470
- # === 原版 origin:随机按比例转移(不涉及置信度) ===
471
  p_transfer = 1 - s / t if i < steps - 1 else 1
472
  x0 = torch.zeros_like(x[mask_index], device=self.device, dtype=torch.long) + mask_token_id
473
  transfer_index_t_s = torch.rand(*x0.shape, device=self.device) < p_transfer
@@ -476,7 +459,6 @@ class DreamGenerationMixin:
476
  )
477
  x[mask_index] = x0.clone()
478
  else:
479
- # === 置信度算法选择(vanilla 与 RCR 复用此处) ===
480
  use_alg = conf_alg if rcr else alg
481
  if use_alg == 'maskgit_plus':
482
  confidence, x0 = sample_tokens(mask_logits, temperature=temperature, top_p=top_p, top_k=top_k)
@@ -492,23 +474,25 @@ class DreamGenerationMixin:
492
  raise RuntimeError(f"Unknown alg/conf_alg: {use_alg}")
493
 
494
  if rcr:
495
- # === 历史置信度版 RCR ===
496
  self._apply_rcr_logic(
497
  x=x,
498
  x0=x0,
499
- conf_now=confidence,
500
  mask_index=mask_index,
501
  fixed_conf=fixed_conf,
 
502
  gen_mask=gen_mask,
 
503
  init_mask_count=init_mask_count,
504
  mask_token_id=mask_token_id,
505
  step=i,
506
  total_steps=steps,
507
  s=s, t=t,
 
508
  )
509
  else:
510
- # === 原版 Dream(vanilla):本步 top-k,永久确认,不回遮 ===
511
- # number_transfer_tokens 基于“当前平均剩余 mask * (1 - s/t)”
512
  avg_mask_now = (mask_index.sum().item() / max(1, mask_index.shape[0]))
513
  ratio = (1.0 - (s.item() / t.item())) if i < steps - 1 else 1.0
514
  number_transfer_tokens = int(avg_mask_now * ratio)
@@ -529,14 +513,10 @@ class DreamGenerationMixin:
529
  x[row_indices, transfer_index] = x_[row_indices, transfer_index]
530
 
531
  x = generation_tokens_hook_func(i, x, logits)
532
-
533
  if histories is not None:
534
  histories.append(x.clone())
535
 
536
  if return_dict_in_generate:
537
- return DreamModelOutput(
538
- sequences=x,
539
- history=histories,
540
- )
541
  else:
542
  return x
 
1
  # coding=utf-8
2
+ # Copyright 2024 The Dream team, HKUNLP Group and...
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  import warnings
4
  import copy
5
  from dataclasses import dataclass
 
21
  sorted_logits, sorted_indices = torch.sort(logits, descending=True)
22
  cumulative_probs = torch.cumsum(F.softmax(sorted_logits, dim=-1), dim=-1)
23
  sorted_indices_to_remove = cumulative_probs > top_p
 
24
  sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone()
25
  sorted_indices_to_remove[..., 0] = 0
 
26
  mask = torch.zeros_like(logits, dtype=torch.bool, device=logits.device)
27
  mask = mask.scatter_(-1, sorted_indices, sorted_indices_to_remove)
28
  logits = logits.masked_fill(mask, torch.finfo(logits.dtype).min)
 
32
  def top_k_logits(logits, top_k=None):
33
  if top_k is None:
34
  return logits
35
+ top_k = int(min(top_k, logits.size(-1)))
36
  if top_k <= 0:
37
  return logits
 
38
  indices_to_remove = logits < torch.topk(logits, top_k)[0][..., -1, None]
39
  logits = logits.masked_fill(indices_to_remove, torch.finfo(logits.dtype).min)
40
  return logits
 
69
  confidence = top1_probs - top2_probs
70
 
71
  if neg_entropy:
72
+ # 负熵(≤0;越接近 0 越“确定”)
73
  epsilon = 1e-10
74
  log_probs = torch.log(probs + epsilon)
75
  confidence = torch.sum(probs * log_probs, dim=-1)
 
100
  self.alg: str = kwargs.pop("alg", 'origin') # 'origin' | 'maskgit_plus' | 'topk_margin' | 'entropy'
101
  self.alg_temp: Optional[float] = kwargs.pop("alg_temp", None)
102
 
103
+ # RCR
104
  self.rcr: bool = kwargs.pop("rcr", False)
 
105
  self.conf_alg: str = kwargs.pop("conf_alg", 'maskgit_plus')
106
 
107
+ # outputs
108
  self.num_return_sequences: int = kwargs.pop("num_return_sequences", 1)
109
  self.return_dict_in_generate: bool = kwargs.pop("return_dict_in_generate", False)
110
  self.output_history: bool = kwargs.pop("output_history", False)
 
152
  attention_mask = attention_mask.repeat_interleave(expand_size, dim=0)
153
  return input_ids, attention_mask
154
 
155
+ # =========================
156
+ # 历史置信度 RCR(贴近 vanilla)
157
+ # =========================
158
  def _apply_rcr_logic(
159
  self,
160
  x: torch.Tensor,
161
  x0: torch.Tensor,
162
+ conf_now: torch.Tensor, # [M] 仅 mask 位置的置信度(已为 float32)
163
+ mask_index: torch.Tensor, # [B, L] bool
164
+ fixed_conf: torch.Tensor, # [B, L] float32(历史 max)
165
+ ema_conf: torch.Tensor, # [B, L] float32(EMA)
166
+ gen_mask: torch.Tensor, # [B, L] bool(已确认集合)
167
+ written_step: torch.Tensor, # [B, L] int32(写入的步骤,-1=未写)
168
+ init_mask_count: torch.Tensor, # [B] 初始 mask 数
169
  mask_token_id: int,
170
  step: int,
171
  total_steps: int,
172
  s: torch.Tensor,
173
  t: torch.Tensor,
174
+ ema_beta: float = 0.8 # EMA 平滑系数(越大越稳定)
175
  ):
176
  """
177
+ 策略要点(接近 vanilla):
178
+ 1) 当步确认:沿用 vanilla 配额计算,按 conf_now(负熵/概率差等)选 top-k 写入;
179
+ 2) 历史维护:fixed_conf 取历史 max;ema_conf 做滑动平均,写入步 recorded;
180
+ 3) 超额回遮:若当前已确认数 > 目标累计配额,仅在 gen_mask 内、且不是“本步刚写”的位置,
181
+ EMA 最低的 over 个回遮(轻量、稳定)。
 
 
 
 
182
  """
183
  device = x.device
184
  B, L = x.shape
185
 
186
+ # 1) 配额(与 vanilla 一致)
187
  avg_mask_now = (mask_index.sum().item() / max(1, mask_index.shape[0]))
188
  ratio = (1.0 - (s.item() / t.item())) if step < total_steps - 1 else 1.0
189
  number_transfer_tokens = int(avg_mask_now * ratio)
190
 
191
+ # 把当步局部置信度/候选整到全长
192
+ full_conf_now = torch.full((B, L), -1e9, dtype=torch.float32, device=device) # 用 -1e9 更稳妥
 
 
 
193
  full_x0 = torch.full((B, L), mask_token_id, dtype=torch.long, device=device)
194
  full_conf_now[mask_index] = conf_now
195
  full_x0[mask_index] = x0
196
 
197
+ # 2) 逐样本选择当步 top-k
198
  for j in range(B):
199
  masked_j = int(mask_index[j].sum().item())
200
  k_j = min(number_transfer_tokens, masked_j)
201
  if k_j > 0:
202
  conf_row = full_conf_now[j] # float32
 
203
  _, sel_idx = torch.topk(conf_row, k=k_j, largest=True)
204
+
205
+ # 写入
206
  x[j, sel_idx] = full_x0[j, sel_idx]
207
  gen_mask[j, sel_idx] = True
208
+
209
+ # 历史 max & EMA(仅对当步写入位置更新)
210
  fixed_conf[j, sel_idx] = torch.maximum(fixed_conf[j, sel_idx], conf_row[sel_idx])
211
+ ema_conf[j, sel_idx] = ema_beta * ema_conf[j, sel_idx] + (1 - ema_beta) * conf_row[sel_idx]
212
+ written_step[j, sel_idx] = step
213
 
214
+ # 3) 目标累计配额(与 vanilla 同口径)
215
  init_m = int(init_mask_count[j].item())
216
+ target_cum = init_m if step >= total_steps - 1 else int(init_m * (1.0 - (s.item() / t.item())))
 
 
 
217
 
218
  current_gen = int(gen_mask[j].sum().item())
219
  over = max(0, current_gen - target_cum)
220
  if over > 0:
221
+ # 只能从“非本步写入”的已确认里回遮,避免抖动
222
  gen_idx = torch.where(gen_mask[j])[0]
223
  if gen_idx.numel() > 0:
224
+ # 排除刚写入的
225
+ not_just_written = written_step[j, gen_idx] < step
226
+ candidates = gen_idx[not_just_written]
227
+ if candidates.numel() > 0:
228
+ over = min(over, int(candidates.numel()))
229
+ cand_ema = ema_conf[j, candidates] # float32
230
+ _, low_local = torch.topk(cand_ema, k=over, largest=False)
231
+ low_global = candidates[low_local]
232
+
233
+ # 回遮
234
+ x[j, low_global] = mask_token_id
235
+ gen_mask[j, low_global] = False
236
+ # 适度清理 EMA,max 保留帮助后续稳定
237
+ ema_conf[j, low_global] = 0.0
238
+ written_step[j, low_global] = -1 # 重置写入步
239
+ # fixed_conf 不清零,保留历史峰值作为“锚”信息
240
 
241
  def _validate_generated_length(self, generation_config, input_ids_length, has_default_max_length):
242
  if is_torchdynamo_compiling():
 
352
  warnings.warn(
353
  "You are calling .generate() with the `input_ids` being on a device type different"
354
  f" than your model's device. `input_ids` is on {input_ids.device.type}, whereas the model"
355
+ f" is on {self.device.type}. You may experience unexpected behaviors or slower generation.",
 
 
 
356
  UserWarning,
357
  )
358
  if (
 
389
  generation_tokens_hook_func,
390
  generation_logits_hook_func
391
  ) -> Union[DreamModelOutput, torch.LongTensor]:
 
392
  output_history = generation_config.output_history
393
  return_dict_in_generate = generation_config.return_dict_in_generate
394
  max_length = generation_config.max_length
 
401
  top_p = generation_config.top_p
402
  top_k = generation_config.top_k
403
 
404
+ # RCR
405
  rcr = generation_config.rcr
406
  conf_alg = generation_config.conf_alg
407
 
 
424
 
425
  timesteps = torch.linspace(1, eps, steps + 1, device=x.device)
426
 
427
+ # ===== RCR 缓冲初始化(关键:float32,避免 dtype 冲突) =====
428
  if rcr:
429
+ init_mask_count = (x == mask_token_id).sum(dim=1) # [B]
430
+ fixed_conf = torch.full(x.shape, -1e9, dtype=torch.float32, device=x.device) # 历史 max
431
+ ema_conf = torch.zeros_like(fixed_conf, dtype=torch.float32) # EMA
432
+ gen_mask = torch.zeros_like(x, dtype=torch.bool) # 已确认集合
433
+ written_step = torch.full(x.shape, -1, dtype=torch.int32, device=x.device) # 写入步
434
  else:
435
  init_mask_count = None
436
  fixed_conf = None
437
+ ema_conf = None
438
  gen_mask = None
439
+ written_step = None
440
 
 
441
  x = generation_tokens_hook_func(None, x, None)
442
 
443
  for i in range(steps):
444
  mask_index = (x == mask_token_id)
445
  logits = self(x, attention_mask, tok_idx).logits
 
446
  logits = torch.cat([logits[:, :1], logits[:, :-1]], dim=1)
 
447
  logits = generation_logits_hook_func(i, x, logits)
448
 
449
  mask_logits = logits[mask_index]
 
451
  s = timesteps[i + 1]
452
 
453
  if alg == 'origin':
 
454
  p_transfer = 1 - s / t if i < steps - 1 else 1
455
  x0 = torch.zeros_like(x[mask_index], device=self.device, dtype=torch.long) + mask_token_id
456
  transfer_index_t_s = torch.rand(*x0.shape, device=self.device) < p_transfer
 
459
  )
460
  x[mask_index] = x0.clone()
461
  else:
 
462
  use_alg = conf_alg if rcr else alg
463
  if use_alg == 'maskgit_plus':
464
  confidence, x0 = sample_tokens(mask_logits, temperature=temperature, top_p=top_p, top_k=top_k)
 
474
  raise RuntimeError(f"Unknown alg/conf_alg: {use_alg}")
475
 
476
  if rcr:
477
+ # —— 贴近 vanilla 的历史置信度 RCR ——
478
  self._apply_rcr_logic(
479
  x=x,
480
  x0=x0,
481
+ conf_now=confidence.to(torch.float32),
482
  mask_index=mask_index,
483
  fixed_conf=fixed_conf,
484
+ ema_conf=ema_conf,
485
  gen_mask=gen_mask,
486
+ written_step=written_step,
487
  init_mask_count=init_mask_count,
488
  mask_token_id=mask_token_id,
489
  step=i,
490
  total_steps=steps,
491
  s=s, t=t,
492
+ ema_beta=0.8,
493
  )
494
  else:
495
+ # —— vanilla:本步 top-k 永久确认 ——
 
496
  avg_mask_now = (mask_index.sum().item() / max(1, mask_index.shape[0]))
497
  ratio = (1.0 - (s.item() / t.item())) if i < steps - 1 else 1.0
498
  number_transfer_tokens = int(avg_mask_now * ratio)
 
513
  x[row_indices, transfer_index] = x_[row_indices, transfer_index]
514
 
515
  x = generation_tokens_hook_func(i, x, logits)
 
516
  if histories is not None:
517
  histories.append(x.clone())
518
 
519
  if return_dict_in_generate:
520
+ return DreamModelOutput(sequences=x, history=histories)
 
 
 
521
  else:
522
  return x