Skip to content

Commit 2881e55

Browse files
committed
Fix ModelParallel layout map axis order for Phi3 QKV kernels
Phi3's query/key/value kernels are shaped (hidden, heads, head_dim), with hidden as the contracting dim and heads as the output dim -- the opposite axis order from Gemma's (heads, hidden, head_dim) kernels. The layout map had the data/model axes swapped for these kernels (and for token_embedding/reverse_embeddings), which put the small, GQA-sized key/value heads axis on the model-parallel dim instead of hidden, and the large hidden axis on the batch axis instead of the smaller heads axis for query -- both backwards from the intended Megatron-style column-parallel sharding, and prone to IndivisibleError whenever the model-mesh dimension didn't evenly divide the (typically much smaller) key/value head count. Corrected the axis order so heads land on the model-parallel dim (matching attention_output.kernel's existing convention) and hidden lands on the data-parallel dim, with key/value heads left fully replicated on the model dim regardless of divisibility (mirrors Gemma's kv convention). Also expands phi3_backbone_test.py with a mesh-shape sweep across this repo's capped device-mesh shapes, covering both the real "mini" (1:1 query:kv head ratio) and a synthetic GQA-ratio width class, plus a live-preset sweep that fetches real config.json files from every registered Phi3 preset and validates the layout map against their actual dims (skipping any build whose estimated memory footprint would be unsafe to run locally).
1 parent 8375767 commit 2881e55

2 files changed

Lines changed: 442 additions & 0 deletions

File tree

keras_hub/src/models/phi3/phi3_backbone.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,3 +207,92 @@ def get_config(self):
207207
}
208208
)
209209
return config
210+
211+
@staticmethod
212+
def get_layout_map(
213+
device_mesh,
214+
model_parallel_dim_name="model",
215+
data_parallel_dim_name="batch",
216+
):
217+
"""Get a `keras.distribution.LayoutMap` for model parallel distribution.
218+
219+
The returned `LayoutMap` contains the sharding spec for the Phi3
220+
backbone weights.
221+
222+
Args:
223+
device_mesh: keras.distribution.DeviceMesh. The device mesh
224+
instance for distribution.
225+
model_parallel_dim_name: str. The axis name of the device mesh,
226+
where the weights should be partitioned on.
227+
data_parallel_dim_name: str. The axis name of the device mesh,
228+
where the data should be partitioned on.
229+
230+
Returns:
231+
`keras.distribution.LayoutMap` that contains the sharding spec
232+
for all the model weights.
233+
"""
234+
if not isinstance(device_mesh, keras.distribution.DeviceMesh):
235+
raise ValueError(
236+
"Invalid device_mesh type. Expected "
237+
f"`keras.distribution.DeviceMesh`, got {type(device_mesh)}"
238+
)
239+
if model_parallel_dim_name not in device_mesh.axis_names:
240+
raise ValueError(
241+
f"{model_parallel_dim_name} is not found in the "
242+
f"device_mesh.axis_names. {device_mesh.axis_names=}"
243+
)
244+
if data_parallel_dim_name not in device_mesh.axis_names:
245+
raise ValueError(
246+
f"{data_parallel_dim_name} is not found in the "
247+
f"device_mesh.axis_names. {device_mesh.axis_names=}"
248+
)
249+
250+
data_dim = data_parallel_dim_name
251+
model_dim = model_parallel_dim_name
252+
layout_map = keras.distribution.LayoutMap(device_mesh)
253+
layout_map["token_embedding/embeddings"] = (model_dim, data_dim)
254+
layout_map["token_embedding/reverse_embeddings"] = (
255+
data_dim,
256+
model_dim,
257+
)
258+
# Query/key/value kernels are `(hidden, heads, head_dim)` (einsum
259+
# `bqm,muh->bquh` / `bkm,mvh->bkvh`), i.e. hidden is the contracting
260+
# dim and heads is the output dim -- the opposite axis order from
261+
# Gemma's `(heads, hidden, head_dim)` kernels. Column-parallel
262+
# (Megatron) sharding therefore puts the *heads* axis on the model
263+
# dim and the *hidden* (contracting) axis on the data dim, matching
264+
# `attention_output.kernel`'s already-heads-on-model convention
265+
# below. Key/value kernels are sized by num_key_value_heads (GQA),
266+
# which is independent of and typically much smaller than
267+
# num_query_heads -- sharding that small heads axis on the model dim
268+
# would raise an IndivisibleError whenever the model mesh dimension
269+
# doesn't divide it, so key/value heads are left fully replicated on
270+
# that axis, while hidden is still sharded on the data dim for
271+
# memory savings (mirrors Gemma's kv convention).
272+
layout_map["transformer_layer.*attention.*query.kernel"] = (
273+
data_dim,
274+
model_dim,
275+
None,
276+
)
277+
layout_map["transformer_layer.*attention.*(key|value).kernel"] = (
278+
data_dim,
279+
None,
280+
None,
281+
)
282+
layout_map["transformer_layer.*attention_output.kernel"] = (
283+
model_dim,
284+
None,
285+
data_dim,
286+
)
287+
layout_map[
288+
"transformer_layer.*feedforward_intermediate_dense.kernel"
289+
] = (data_dim, model_dim)
290+
layout_map["transformer_layer.*feedforward_gate_dense.kernel"] = (
291+
data_dim,
292+
model_dim,
293+
)
294+
layout_map["transformer_layer.*feedforward_output_dense.kernel"] = (
295+
model_dim,
296+
data_dim,
297+
)
298+
return layout_map

0 commit comments

Comments
 (0)