Skip to content

Commit ccf96d6

Browse files
justinchubyCopilot
andauthored
examples/gemma4_multimodal.py: pass per_layer_inputs to decoder (#320)
## Summary After #296 ("Move Gemma4 per-layer embeddings to embedding model") the Gemma4 embedding sub-model emits a second output `per_layer_inputs` (`[B, S, num_layers * per_layer_dim]`) that the decoder consumes on every step. `examples/gemma4_multimodal.py` wasn't updated to forward this output, so any Gemma 4 build with `hidden_size_per_layer_input > 0` (e.g. `google/gemma-4-E2B-it`) crashes at the first `decoder.run()`: ``` ValueError: Required inputs (['per_layer_inputs']) are missing from input feed (['inputs_embeds', ..., 'attention_mask', 'position_ids']) ``` ## Fix - `prepare_decoder_feeds()`: accept an optional `per_layer_inputs` argument and add it to the feeds dict when present. Builds without per-layer inputs (`hidden_size_per_layer_input == 0`, e.g. larger Gemma 4 variants) keep working — the kwarg is optional. - `generate()`: pull `embed_out.get("per_layer_inputs")` and pass it to `prepare_decoder_feeds` on every step. ## Verification ``` $ python examples/gemma4_multimodal.py --mode text --prompt "What is 2+2?" ... 📝 TEXT-ONLY GENERATION ================================================================ Prompt: What is 2+2? ---------------------------------------------------------------- 2 + 2 = **4** ``` ## Related This is the example-side counterpart to #318 which fixed the same gap in the L4/L5 e2e test harness. --------- Signed-off-by: justinchuby <11205048+justinchuby@users.noreply.github.com> Signed-off-by: Justin Chu <justinchuby@users.noreply.github.com> Co-authored-by: justinchuby <11205048+justinchuby@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent 3084bf4 commit ccf96d6

1 file changed

Lines changed: 22 additions & 4 deletions

File tree

examples/gemma4_multimodal.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ def prepare_decoder_feeds(
227227
past_seq_len: int,
228228
past_kv: dict[str, np.ndarray],
229229
input_ids: np.ndarray,
230+
per_layer_inputs: np.ndarray | None = None,
230231
) -> dict[str, np.ndarray]:
231232
"""Prepare feeds for the **decoder** session.
232233
@@ -239,23 +240,34 @@ def prepare_decoder_feeds(
239240
past_seq_len: Number of tokens already stored in the KV cache.
240241
past_kv: Mapping of ``"past_key_values.{i}.key/value"`` arrays
241242
from the previous decoder step.
242-
input_ids: ``int64[1, cur_seq_len]`` — original token ids (needed for
243-
per-layer token embeddings when ``hidden_size_per_layer_input > 0``).
243+
input_ids: ``int64[1, cur_seq_len]`` — original token ids (legacy
244+
decoder input, retained for backward compat). On builds where
245+
the embedding model emits ``per_layer_inputs`` the decoder
246+
does not consume ``input_ids``.
247+
per_layer_inputs: Optional ``[1, cur_seq_len, num_layers *
248+
per_layer_dim]`` tensor with the same dtype as
249+
``inputs_embeds`` (that is, the model/config dtype), emitted
250+
by the embedding model when
251+
``hidden_size_per_layer_input > 0``. The decoder requires
252+
this input on every step (prefill + decode).
244253
245254
Returns:
246255
Complete feeds dict for the decoder ONNX model.
247256
"""
248257
batch_size, cur_seq_len, _ = inputs_embeds.shape
249258
total_seq_len = past_seq_len + cur_seq_len
250259

251-
return {
260+
feeds: dict[str, np.ndarray] = {
252261
"inputs_embeds": inputs_embeds,
253262
# Attend to all tokens (past + current)
254263
"attention_mask": np.ones((batch_size, total_seq_len), dtype=np.int64),
255264
"position_ids": np.arange(past_seq_len, total_seq_len, dtype=np.int64)[np.newaxis, :],
256265
"input_ids": input_ids,
257266
**past_kv,
258267
}
268+
if per_layer_inputs is not None:
269+
feeds["per_layer_inputs"] = per_layer_inputs
270+
return feeds
259271

260272

261273
# ---------------------------------------------------------------------------
@@ -479,10 +491,16 @@ def generate(
479491
)
480492
)
481493
inputs_embeds: np.ndarray = embed_out["inputs_embeds"]
494+
# Gemma4 builds with hidden_size_per_layer_input > 0 also emit a
495+
# second embedding output (``per_layer_inputs``) that the decoder
496+
# consumes on every step. Pass it through transparently when present.
497+
per_layer_inputs = embed_out.get("per_layer_inputs")
482498

483499
# ---- Decoder session ----
484500
decoder_out = decoder_session.run(
485-
prepare_decoder_feeds(inputs_embeds, past_seq_len, past_kv, cur_ids)
501+
prepare_decoder_feeds(
502+
inputs_embeds, past_seq_len, past_kv, cur_ids, per_layer_inputs
503+
)
486504
)
487505

488506
# Greedy: pick the token with the highest logit at the last position

0 commit comments

Comments
 (0)