Skip to content

Commit fc67323

Browse files
authored
Added support for shared spatial decoders (ecmwf#2017)
* Added support for shared spatial decoders * Fixed some issues to ensure support for arbitrary stream orders * Fixed bug where streams are not handled correctly * Addressed reviewer comments
1 parent 7c5981a commit fc67323

2 files changed

Lines changed: 133 additions & 72 deletions

File tree

src/weathergen/model/engines.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,7 @@ def __init__(
548548
tr_dim_head_proj,
549549
tr_mlp_hidden_factor,
550550
softcap,
551-
stream_name: str,
551+
stream_config: dict,
552552
):
553553
"""
554554
Initialize the TargetPredictionEngine with the configuration.
@@ -561,7 +561,7 @@ def __init__(
561561
:param softcap: Softcap value for the attention layers.
562562
"""
563563
super(TargetPredictionEngineClassic, self).__init__()
564-
self.name = f"TargetPredictionEngine_{stream_name}"
564+
self.name = f"TargetPredictionEngine_{stream_config['name']}"
565565

566566
self.cf = cf
567567
self.dims_embed = dims_embed
@@ -577,7 +577,7 @@ def __init__(
577577
MultiCrossAttentionHeadVarlen(
578578
dim_embed_q=self.dims_embed[i],
579579
dim_embed_kv=self.cf.ae_global_dim_embed,
580-
num_heads=self.cf.streams[0]["target_readout"]["num_heads"],
580+
num_heads=stream_config["target_readout"]["num_heads"],
581581
dim_head_proj=self.tr_dim_head_proj,
582582
with_residual=True,
583583
with_qk_lnorm=True,
@@ -596,7 +596,7 @@ def __init__(
596596
self.tte.append(
597597
MultiSelfAttentionHeadVarlen(
598598
dim_embed=self.dims_embed[i],
599-
num_heads=self.cf.streams[0]["target_readout"]["num_heads"],
599+
num_heads=stream_config["target_readout"]["num_heads"],
600600
dropout_rate=0.1, # Assuming dropout_rate is 0.1
601601
with_qk_lnorm=True,
602602
with_flash=self.cf.with_flash_attention,

src/weathergen/model/model.py

Lines changed: 129 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -399,83 +399,144 @@ def create(self) -> "Model":
399399
if is_stream_forcing(si):
400400
continue
401401

402-
# extract and setup relevant parameters
403-
etc = si["embed_target_coords"]
404-
tr = si["target_readout"]
405-
num_layers = tr["num_layers"]
406-
tr_mlp_hidden_factor = tr["mlp_hidden_factor"] if "mlp_hidden_factor" in tr else 2
407-
tr_dim_head_proj = tr["dim_head_proj"] if "dim_head_proj" in tr else None
408-
softcap = tr["softcap"] if "softcap" in tr else 0.0
409-
410-
dims_embed = [si["embed_target_coords"]["dim_embed"] for _ in range(num_layers + 1)]
411-
412-
if is_root():
413-
logger.info("{} :: coord embed: :: {}".format(si["name"], dims_embed))
414-
415-
dim_coord_in = self.targets_coords_size[i_stream]
416-
417-
# embedding network for coordinates
418-
if etc["net"] == "linear":
419-
self.embed_target_coords[stream_name] = NamedLinear(
420-
f"embed_target_coords_{stream_name}",
421-
in_features=dim_coord_in,
422-
out_features=dims_embed[0],
423-
bias=False,
402+
# skip for the moment to ensure target embedding and tte exist (ordering of
403+
# cf.streams is random)
404+
if si.get("pred_spatial_shared") is None:
405+
# extract and setup relevant parameters
406+
etc = si["embed_target_coords"]
407+
tr = si["target_readout"]
408+
num_layers = tr["num_layers"]
409+
tr_mlp_hidden_factor = (
410+
tr["mlp_hidden_factor"] if "mlp_hidden_factor" in tr else 2
424411
)
425-
elif etc["net"] == "mlp":
426-
self.embed_target_coords[stream_name] = MLP(
427-
dim_coord_in,
428-
dims_embed[0],
429-
hidden_factor=8,
430-
with_residual=False,
431-
dropout_rate=dropout_rate,
432-
norm_eps=self.cf.mlp_norm_eps,
433-
stream_name=f"embed_target_coords_{stream_name}",
434-
)
435-
else:
436-
assert False
412+
tr_dim_head_proj = tr["dim_head_proj"] if "dim_head_proj" in tr else None
413+
softcap = tr["softcap"] if "softcap" in tr else 0.0
414+
415+
dims_embed = [
416+
si["embed_target_coords"]["dim_embed"] for _ in range(num_layers + 1)
417+
]
437418

438-
if cf.decoder_type == "Linear":
439-
tte = BilinearDecoder(
440-
stream_name,
441-
dims_embed[0],
442-
cf.ae_global_dim_embed,
419+
if is_root():
420+
logger.info("{} :: coord embed: :: {}".format(si["name"], dims_embed))
421+
422+
dim_coord_in = self.targets_coords_size[i_stream]
423+
424+
# embedding network for coordinates
425+
if etc["net"] == "linear":
426+
self.embed_target_coords[stream_name] = NamedLinear(
427+
f"embed_target_coords_{stream_name}",
428+
in_features=dim_coord_in,
429+
out_features=dims_embed[0],
430+
bias=False,
431+
)
432+
elif etc["net"] == "mlp":
433+
self.embed_target_coords[stream_name] = MLP(
434+
dim_coord_in,
435+
dims_embed[0],
436+
hidden_factor=8,
437+
with_residual=False,
438+
dropout_rate=dropout_rate,
439+
norm_eps=self.cf.mlp_norm_eps,
440+
stream_name=f"embed_target_coords_{stream_name}",
441+
)
442+
else:
443+
assert False
444+
445+
if cf.decoder_type == "Linear":
446+
tte = BilinearDecoder(
447+
stream_name,
448+
dims_embed[0],
449+
cf.ae_global_dim_embed,
450+
self.targets_num_channels[i_stream],
451+
)
452+
else:
453+
# target prediction engines
454+
tte_version = (
455+
TargetPredictionEngine
456+
if cf.decoder_type != "PerceiverIOCoordConditioning"
457+
else TargetPredictionEngineClassic
458+
)
459+
tte = tte_version(
460+
cf,
461+
dims_embed,
462+
dim_coord_in,
463+
tr_dim_head_proj,
464+
tr_mlp_hidden_factor,
465+
softcap,
466+
stream_config=si,
467+
)
468+
469+
self.target_token_engines[stream_name] = tte
470+
471+
# ensemble prediction heads to provide probabilistic prediction
472+
final_activation = si["pred_head"].get("final_activation", "Identity")
473+
if is_root():
474+
logger.debug(
475+
f"{final_activation} activation of pred head of {si['name']} stream"
476+
)
477+
self.pred_heads[stream_name] = EnsPredictionHead(
478+
dims_embed[-1],
443479
self.targets_num_channels[i_stream],
444-
)
445-
else:
446-
# target prediction engines
447-
tte_version = (
448-
TargetPredictionEngine
449-
if cf.decoder_type != "PerceiverIOCoordConditioning"
450-
else TargetPredictionEngineClassic
451-
)
452-
tte = tte_version(
453-
cf,
454-
dims_embed,
455-
dim_coord_in,
456-
tr_dim_head_proj,
457-
tr_mlp_hidden_factor,
458-
softcap,
480+
si["pred_head"]["num_layers"],
481+
si["pred_head"]["ens_size"],
482+
norm_type=cf.norm_type,
483+
final_activation=final_activation,
459484
stream_name=stream_name,
460485
)
461486

462-
self.target_token_engines[stream_name] = tte
487+
# iterate again to setup shared spatial pred heads if specified in config
488+
for i_stream, si in enumerate(cf.streams):
489+
stream_name = self.stream_names[i_stream]
463490

464-
# ensemble prediction heads to provide probabilistic prediction
465-
final_activation = si["pred_head"].get("final_activation", "Identity")
466-
if is_root():
491+
# skip decoder if channels are empty
492+
if is_stream_forcing(si):
493+
continue
494+
495+
pred_spatial_shared = si.get("pred_spatial_shared")
496+
if pred_spatial_shared is not None:
497+
if pred_spatial_shared not in self.stream_names:
498+
msg = f"Stream {stream_name} has pred_spatial_shared={pred_spatial_shared}"
499+
msg += " but no stream with that name found."
500+
raise ValueError(msg)
501+
if pred_spatial_shared == stream_name:
502+
msg = f"Stream {stream_name} has pred_spatial_shared={pred_spatial_shared}"
503+
msg += "but cannot share with itself."
504+
raise ValueError(msg)
467505
logger.debug(
468-
f"{final_activation} activation of predictionhead of {si['name']} stream"
506+
f"{stream_name} shares spatial prediction head with {pred_spatial_shared}."
507+
)
508+
509+
self.embed_target_coords[stream_name] = self.embed_target_coords[
510+
pred_spatial_shared
511+
]
512+
self.target_token_engines[stream_name] = self.target_token_engines[
513+
pred_spatial_shared
514+
]
515+
516+
idx_shared_s = [
517+
i for i, so in enumerate(cf.streams) if so["name"] == pred_spatial_shared
518+
]
519+
assert (len(idx_shared_s)) == 1
520+
si_other = cf.streams[idx_shared_s[0]]
521+
dims_embed = [
522+
si_other["embed_target_coords"]["dim_embed"] for _ in range(num_layers + 1)
523+
]
524+
525+
# ensemble prediction heads to provide probabilistic prediction
526+
final_activation = si["pred_head"].get("final_activation", "Identity")
527+
if is_root():
528+
logger.debug(
529+
f"{final_activation} activation of pred head of {si['name']} stream"
530+
)
531+
self.pred_heads[stream_name] = EnsPredictionHead(
532+
dims_embed[-1],
533+
self.targets_num_channels[i_stream],
534+
si["pred_head"]["num_layers"],
535+
si["pred_head"]["ens_size"],
536+
norm_type=cf.norm_type,
537+
final_activation=final_activation,
538+
stream_name=stream_name,
469539
)
470-
self.pred_heads[stream_name] = EnsPredictionHead(
471-
dims_embed[-1],
472-
self.targets_num_channels[i_stream],
473-
si["pred_head"]["num_layers"],
474-
si["pred_head"]["ens_size"],
475-
norm_type=cf.norm_type,
476-
final_activation=final_activation,
477-
stream_name=stream_name,
478-
)
479540

480541
# Latent heads for losses
481542
self.latent_heads = nn.ModuleDict()

0 commit comments

Comments
 (0)