File size: 9,685 Bytes
2f86d9b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# Copyright 2026 DeepMind Technologies Limited.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Text diffusion SFT config — PubMedQA long-answer (detailed explanation).

Flat configuration that contains all setup (model, LoRA, optimizer,
checkpointer, dataset pipeline, and evaluation ).
"""

import dataclasses
from gemma.diffusion.hackable_diffusion_adapter.eval import ar_eval
from kauldron import konfig

# pylint: disable=g-import-not-at-top
with konfig.imports():
  from gemma.diffusion import _models
  from gemma.diffusion import _paths  # pytlint: disable=unused-import
  from gemma.diffusion.hackable_diffusion_adapter.data.pubmedqa import pubmedqa_data
  from gemma.diffusion.hackable_diffusion_adapter.eval import pubmedqa_eval
  from gemma.diffusion.hackable_diffusion_adapter.hd import gemma_checkpointer
  from gemma.diffusion.hackable_diffusion_adapter.hd import hd_gemma_network
  from gemma.diffusion.hackable_diffusion_adapter.hd import lora
  from gemma.diffusion.hackable_diffusion_adapter.hd import sft_model
  from hackable_diffusion import hd
  from hackable_diffusion.kdiff import core
  from hackable_diffusion.lib.training import discrete_loss
  from kauldron import kd
  import optax
  from gemma.diffusion.hackable_diffusion_adapter import safe_writer
# pylint: enable=g-import-not-at-top

CHECKPOINT_PATH = _paths.CheckpointPath.DIFFUSIONGEMMA_26B_A4B_IT

# Paths to the converted PubMedQA JSONL files.
PUBMEDQA_TRAIN_PATH = "gemma/diffusion/hackable_diffusion_adapter/data/pubmedqa/pubmedqa_train.jsonl"
PUBMEDQA_TEST_PATH = "gemma/diffusion/hackable_diffusion_adapter/data/pubmedqa/pubmedqa_test.jsonl"

# Reserve the first 50 training examples for overfitting tracking.
_TRAIN_EVAL_SIZE = 50

# Hyperparameters.
_LORA_RANK = 4
_PEAK_LR = 1e-4
_END_LR = 1e-5
_WARMUP_STEPS = 100


@dataclasses.dataclass(frozen=True, kw_only=True)
class ConfigArgs:
  use_early_stopping: bool = True


def get_config(args: ConfigArgs = ConfigArgs()):
  """SFT config for PubMedQA long-answer (detailed explanation)."""
  use_early_stopping = args.use_early_stopping

  cfg = kd.train.Trainer()
  cfg.seed = 42
  cfg.aux = {}
  cfg.aux.vocab_size = 262_144  # Gemma4 vocabulary size

  cfg.aux.corruption_process = hd.corruption.CategoricalProcess.uniform_process(
      num_categories=cfg.ref.aux.vocab_size,
      schedule=hd.corruption.LinearDiscreteSchedule(),
  )
  cfg.aux.prompt_len = 1024
  cfg.aux.num_canvases = 2
  cfg.aux.canvas_size = 128
  use_lora = True
  lora_rank = _LORA_RANK
  cfg.aux.use_lora = use_lora
  cfg.aux.lora_rank = lora_rank
  cfg.aux.peak_lr = _PEAK_LR
  cfg.aux.end_lr = _END_LR
  cfg.aux.checkpoint_every_n_steps = 1_000
  cfg.aux.eval_num_batches = None
  cfg.aux.stop_gradient_from_denoiser_to_encoder = False
  cfg.aux.encoder_loss_weight = 1.0
  cfg.aux.decoder_loss_weight = 1.0

  cfg.sharding = kd.sharding.ShardingStrategy(
      params=kd.sharding.FSDPSharding(), opt_state=kd.sharding.FSDPSharding()
  )

  base_network = hd_gemma_network.WrappedDiffusionGemmaNetwork(
      gemma_model=_models.DiffusionGemma_26B_A4B(),
  )
  if use_lora:
    gemma_network = lora.LoRA(
        rank=lora_rank,
        model=base_network,
        target_modules="all-linear",
    )
  else:
    gemma_network = base_network

  cfg.model = sft_model.SFTDiffusion(
      x0="batch.canvas",
      prompt="batch.prompt",
      canvas_id="batch.canvas_id",
      canvas_mask="batch.canvas_mask",
      encoder_target="batch.encoder_target",
      encoder_target_mask="batch.encoder_target_mask",
      corruption_process=cfg.ref.aux.corruption_process,
      time_sampler=hd.training.time_sampling.UniformTimeSampler(
          span=hd.jax_helpers.SafeSpan(safety_epsilon=1e-4)
      ),
      gemma_network=gemma_network,
      prompt_len=cfg.ref.aux.prompt_len,
      canvas_size=cfg.ref.aux.canvas_size,
      num_canvases=cfg.ref.aux.num_canvases,
      stop_gradient_from_denoiser_to_encoder=cfg.ref.aux.stop_gradient_from_denoiser_to_encoder,
  )

  cfg.train_losses = {
      "diffusion_loss": core.KauldronLossWrapper(
          loss=discrete_loss.NoWeightDiscreteLoss(
              use_mask=True,
              mask_key="target_mask",
          ),
          weight=cfg.ref.aux.decoder_loss_weight,
      ),
      "encoder_loss": sft_model.EncoderARLoss(
          encoder_logits="preds.encoder_logits",
          encoder_target="preds.encoder_target",
          encoder_target_mask="preds.encoder_target_mask",
          weight=cfg.ref.aux.encoder_loss_weight,
      ),
  }

  cfg.num_train_steps = 2_000

  cfg.schedules = {
      "learning_rate": optax.warmup_cosine_decay_schedule(
          init_value=0.0,
          peak_value=cfg.ref.aux.peak_lr,
          end_value=cfg.ref.aux.end_lr,
          warmup_steps=_WARMUP_STEPS,
          decay_steps=cfg.ref.num_train_steps,
      ),
  }

  _base_optimizer = kd.optim.named_chain(**{
      "clip": optax.clip_by_global_norm(max_norm=1.0),
      "adam": optax.scale_by_adam(b1=0.95, b2=0.99, eps=1e-8),
      "decay": optax.add_decayed_weights(weight_decay=1e-4),
      "lr": optax.scale_by_learning_rate(cfg.ref.schedules["learning_rate"]),
  })

  if cfg.aux.use_lora:
    cfg.optimizer = kd.optim.partial_updates(
        optimizer=_base_optimizer,
        mask=kd.optim.select("lora"),
    )
  else:
    cfg.optimizer = _base_optimizer

  cfg.checkpointer = kd.ckpts.Checkpointer(
      fast=False,
      save_interval_steps=cfg.ref.aux.checkpoint_every_n_steps,
      max_to_keep=5,
  )

  cfg._konfig_experimental_nofreeze = True  # pylint: disable=protected-access
  cfg.rng_streams = kd.train.RngStreams([
      kd.train.RngStream("default", train=True, eval=True),
      kd.train.RngStream("sampling", train=True, eval=True),
  ])

  cfg.init_transform = gemma_checkpointer.GemmaDiffusionCheckpointLoader(
      path=CHECKPOINT_PATH,
  )

  cfg.train_ds = pubmedqa_data.make_pubmedqa_ds(
      training=True,
      batch_size=2,
      prompt_len=cfg.ref.aux.prompt_len,
      num_canvases=cfg.ref.aux.num_canvases,
      canvas_size=cfg.ref.aux.canvas_size,
      use_long_answer=True,
      train_path=PUBMEDQA_TRAIN_PATH,
      test_path=PUBMEDQA_TEST_PATH,
      slice_start=_TRAIN_EVAL_SIZE,
      num_workers=0,
  )
  cfg.eval_ds = pubmedqa_data.make_pubmedqa_ds(
      training=False,
      batch_size=2,
      prompt_len=cfg.ref.aux.prompt_len,
      num_canvases=cfg.ref.aux.num_canvases,
      canvas_size=cfg.ref.aux.canvas_size,
      use_long_answer=True,
      train_path=PUBMEDQA_TRAIN_PATH,
      test_path=PUBMEDQA_TEST_PATH,
      num_workers=0,
  )
  cfg.aux.eval_train_ds = pubmedqa_data.make_pubmedqa_ds(
      training=False,
      batch_size=4,
      prompt_len=cfg.ref.aux.prompt_len,
      num_canvases=cfg.ref.aux.num_canvases,
      canvas_size=cfg.ref.aux.canvas_size,
      use_long_answer=True,
      train_path=PUBMEDQA_TRAIN_PATH,
      test_path=PUBMEDQA_TRAIN_PATH,
      slice_start=0,
      slice_stop=_TRAIN_EVAL_SIZE,
  )

  eval_metrics = {
      "pubmedqa_accuracy": pubmedqa_eval.PubMedQAAccuracy(
          tokens="samples",
          ground_truth="batch.short_answer_tokens",
      ),
      "pubmedqa_bleu": pubmedqa_eval.BLEUScore(
          tokens="samples",
          ground_truth="batch.long_answer_tokens",
      ),
      "processed_denoising_steps": kd.metrics.SingleDimension(
          tensor="processed_denoising_steps", index=None
      ),
      "processed_num_canvases": kd.metrics.SingleDimension(
          tensor="processed_num_canvases", index=None
      ),
      "average_denoising_steps_per_canvas": kd.metrics.SingleDimension(
          tensor="average_denoising_steps_per_canvas", index=None
      ),
  }

  test_evals = {
      f"test_{k}": v
      for k, v in ar_eval.make_ar_evals(
          cfg,
          gemma_network_ref=cfg.ref.model.gemma_network,
          corruption_process_ref=cfg.ref.aux.corruption_process,
          canvas_size_ref=cfg.ref.aux.canvas_size,
          metrics=eval_metrics,
          max_num_canvases=cfg.aux.num_canvases,
          use_early_stopping=use_early_stopping,
      ).items()
  }

  train_evals = {}
  for k, v in ar_eval.make_ar_evals(
      cfg,
      gemma_network_ref=cfg.ref.model.gemma_network,
      corruption_process_ref=cfg.ref.aux.corruption_process,
      canvas_size_ref=cfg.ref.aux.canvas_size,
      metrics=eval_metrics,
      max_num_canvases=cfg.aux.num_canvases,
      use_early_stopping=use_early_stopping,
  ).items():
    v.ds = cfg.ref.aux.eval_train_ds
    train_evals[f"train_{k}"] = v

  if use_lora:
    # We only save the fused parameters.
    checkpointer_kwargs = {
        "write_original_params": False,
        "write_lora_params": False,
        "write_fused_lora_params": True,
    }
  else:
    # We only save the original parameters.
    checkpointer_kwargs = {
        "write_original_params": True,
        "write_lora_params": False,
        "write_fused_lora_params": False,
    }
  cfg.evals = {
      "gemma_checkpointer": gemma_checkpointer.GemmaCheckpointFormatter(
          run=kd.evals.StandaloneEveryCheckpoint(),
          **checkpointer_kwargs,
      ),
  }

  cfg.writer = safe_writer.SafeMetricWriter()

  return cfg