Skip to content

[ICS] RESHAPE result that crosses a graph-split boundary is bound with the source tensor's shape #300

Open

Description

@haarika-madaka

ggml-org#27205

Upstream issue draft — ggml-openvino: reshape-aliased split-boundary input bound with source tensor's shape

Target repo: ggml-org/llama.cpp (OpenVINO backend, ggml/src/ggml-openvino/)

Related existing issues (searched 2026-08-16)

Title

ggml-openvino: RESHAPE result that crosses a graph-split boundary is bound with
the source tensor's shape → GPU plugin rejects input ("tensor size is not equal
to model") on hybrid MoE models (e.g. qwen35moe 35B)

Summary

On hybrid-attention MoE models (qwen35moe family, e.g. "Ornith 1.0 35B"), every
inference fails at the first compute with an OpenVINO shape mismatch:

[GPU] The tensor size is not equal to model, can't set input tensor with index: 7,
because model input (shape=[1,2,8,1]) and tensor (shape=[1,1,2,8]) are incompatible

(src/plugins/intel_gpu/src/plugin/sync_infer_request.cpp:184, ov::Exception →
ggml_backend_sched_graph_compute_async failed with error -1 → HTTP 500).

The failing input is a RESHAPE result that crosses a subgraph split boundary:
ffn_moe_weights_norm-0 (reshaped) — the ggml_reshape_3d(ffn_moe_weights_norm, 1, n_expert_used, n_tokens) node (n_expert_used=8).

  • Compiled model input shape (baked at compile time): [1,2,8,1] (OV order) =
    ggml ne=[1,8,2,1] (the reshape result's shape, 2 = n_tokens).
  • Runtime tensor bound: [1,1,2,8] (OV order) = ggml ne=[8,2,1,1] — the
    source 2D tensor's shape (ffn_moe_weights_norm, ne=[8, n_tokens]).

Same element count, same data pointer — only the shape descriptor is wrong.
Cache-INDEPENDENT: fails on the first compute in the same process that just
compiled. Model-agnostic in the sense that it depends on whether a reshape
result lands on a split boundary (shape-sensitive; a smaller model of the same
architecture, ornith:9b, computes fine).

Root cause

ggml_backend_openvino_buffer_init_tensor() (ggml-openvino.cpp) shares the
tensor extra (the ov::Tensor descriptor) from view_src for any tensor
with view_src != nullptr:

// Views share the extra from view_src
if (tensor->view_src != nullptr) {
    GGML_ASSERT(tensor->view_src->buffer->buft == buffer->buft);
    if (tensor->view_src->extra != nullptr) {
        tensor->extra = tensor->view_src->extra;
    }
    return GGML_STATUS_SUCCESS;
}

ggml_new_tensor_impl() sets view_src for reshape results as well as
views. A reshape result therefore inherits the source tensor's ov::Tensor,
whose shape is the source's shape ([8, n_tokens] → OV [1,1,n_tokens,8]).

At inference time convert_ggml_input_to_ov() (utils.cpp) returns the tensor's
extra directly when it exists:

if (ggml_tensor->extra != nullptr && !ggml_decoder->is_splited_model()) {
    ...
    return *tensor_extra->tensor;   // shape = the SOURCE's shape for reshape aliases
}

When the reshape result is a model input (it crossed a scheduler split
boundary — compute_model_inputs() registers it because it is not an
intermediate node of the current subgraph), the compiled model's Parameter has
the reshape result's shape while the runtime binding carries the source's
shape → shape check fails.

The data is identical in both cases (reshape is a memory no-op in ggml:
[8,n_tokens] → [1,8,n_tokens,1] keeps dim0 fastest), so wrapping the same
pointer with the reshape's shape is exactly correct.

Minimal repro

  1. Build llama.cpp master with -DGGML_OPENVINO=ON (any recent commit; verified
    on 885c5bb, build b10428) and run llama-server with -dev OPENVINO0 -ngl 99 -lv 5.
  2. Load a qwen35moe-arch GGUF where a ffn_moe_weights_norm reshape lands on a
    split boundary (reproduced with "Ornith 1.0 35B", 20G GGUF, -c 16384).
  3. Any completion → the ov::Exception above; server returns HTTP 500 "Compute error".

Log signature (before the exception):

W ggml-openvino: cannot determine dynamic dim for RESHAPE node 'conv_states_reshaped-0'
W ggml-openvino: dynamic dim value mismatch for VIEW node 'conv_state_last-0', src[0]: 'conv_input-0'
...
E GGML OpenVINO backend ov::Exception: ... [GPU] The tensor size is not equal to model,
  can't set input tensor with index: 7, because model input (shape=[1,2,8,1]) and tensor
  (shape=[1,1,2,8]) are incompatible

Note: the dynamic-dim warnings for conv-state tensors are benign noise — a
smaller model of the same architecture (ornith:9b) prints the identical
warnings and computes fine. The failure is the reshape-input shape mismatch.

Proposed fix

In ggml_backend_openvino_buffer_init_tensor(), only share the view_src
extra for true views; let reshape results fall through to create an extra with
their own shape:

// Views share the extra from view_src. RESHAPE nodes also alias the source
// data (ggml_new_tensor_impl sets view_src for reshape results), but they have
// a DIFFERENT shape: sharing the source's ov::Tensor would bind inputs with
// the source's shape and fail the plugin's shape check at graph-compute time.
if (tensor->view_src != nullptr && tensor->op == GGML_OP_VIEW) {
    GGML_ASSERT(tensor->view_src->buffer->buft == buffer->buft);
    if (tensor->view_src->extra != nullptr) {
        tensor->extra = tensor->view_src->extra;
    }
    return GGML_STATUS_SUCCESS;
}

(Only GGML_OP_VIEW and GGML_OP_RESHAPE set view_src in ggml.)

Optionally also guard convert_ggml_input_to_ov() against returning an extra
whose shape differs from get_shape(ggml_tensor).

Environment

  • llama.cpp 885c5bb (b10428), OpenVINO 2026.4.0-git intel_gpu plugin
  • Intel Core Ultra 7 356H (Panther Lake), iGPU, 32 GB shared RAM
  • X1 Carbon Gen 14

Verification (with the fix applied) — 2026-08-16, TASK-30 wrap-up

Fixed binary: llama.cpp 885c5bb + the patch below (built 2026-08-16 21:13),
run with -ngl 99 -dev OPENVINO0 -lv 5 --no-cache-prompt, env
GGML_OPENVINO_DEVICE=GPU, GGML_OPENVINO_MEMORY_OPTIMIZE=1, scoped cgroup
(MemoryHigh=29G/MemoryMax=55G/SwapMax=16G, CPUQuota=400%).

  • ornith:35b: cold compile completes (574 cache blobs), /health ok, and a full
    completion returns: stopped by limit, n_gen = 8, n_predict = 8 (tokens
    2, <think>, Here, 's, a, thinking...) — direct curl /completion
    → HTTP 200 {"content":"2\n\n<think>\nHere's a thinking","stop":true, "tokens_predicted":8,"tokens_evaluated":4}. Zero ov::Exception /
    Compute error / repack asserts in the log (grep count 0). Runtime memory
    profile clean: model 1452 MiB retained + context 571 + compute 60 on the
    OPENVINO0 device pool; 41/41 layers offloaded to GPU.
  • ornith:9b (same architecture): still computes fine (no regression).
  • qwen3.5:4b: still computes fine.

Also verified: the patch applies cleanly (git apply --check, exit 0) to a
fresh clone at 885c5bb; the combined patch set reproduces the verified build
exactly.

Two observations for the maintainers, neither blocking:

  • A minority of compiled-blob imports fail at plugin compile with could not create a primitive descriptor for the reduction primitive (intel_gpu
    plugin, plugin.cpp:80) while the same graph compiles fine via the
    fresh-compile path — the backend falls back to recompile (warm loads still
    reach /health in ~37s).
  • Split-graph blobs are compiled with static token-count shapes; each distinct
    token count gets its own cache blob (by design of the related fingerprint
    fix — see the companion issue draft, upstream-issue-openvino-split-boundary-compute.md).

Activity

  1. haarika-madaka commented on Sep 3, 2026

    @haarika-madaka
    Author

    @ravi9 is there an update on this topic? Thanks

  2. wine99 commented on Sep 3, 2026

    @wine99
    Collaborator

    Hi, the fix looks good: reusing view_src->extra also reuses the source tensor’s shape, which is wrong when an alias has a different logical shape.

    One correction: view_src is not limited to VIEW and RESHAPE; it is also used by operations such as CPY, SET_ROWS, PERMUTE, and TRANSPOSE.

  3. haarika-madaka commented on Oct 21, 2026

    @haarika-madaka
    Author

    @ravi9 please advice how to take these changes and recommendations back to customer who submitted code changes?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions