Skip to content

fix(driver): keep Qwen3.5/3.6 linear-attention decay params in f32 so exp() runs on Metal#465

Merged
ingim merged 1 commit into
mainfrom
fix/qwen3.5-metal
Jul 10, 2026
Merged

fix(driver): keep Qwen3.5/3.6 linear-attention decay params in f32 so exp() runs on Metal#465
ingim merged 1 commit into
mainfrom
fix/qwen3.5-metal

Conversation

@zyma98

@zyma98 zyma98 commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Running a bf16 Qwen3.6 checkpoint on Metal warned:

48 graph node(s) fell back to CPU (no Metal kernel): UNARYx48

One per linear-attention layer: the gated-delta-rule block computes exp(A_log) directly on the bf16 linear_attn.A_log weight, and ggml-metal has no bf16 unary kernel, so every exp node fell back to CPU and split the graph. A_log/dt_bias slipped past the load-time f32 promotion, whose suffix match only covers *norm.weight/*.bias.

Fix, scoped to PieArch::Qwen3_5:

  • model.cpp: promote linear_attn.{A_log,dt_bias} to f32 at load time ([num_v_heads] floats per layer, ~9 KiB total on 27B), and exempt them from the f32→bf16 downcast.
  • graph_qwen3_5.cpp: defensive f32 cast before exp(A_log) for checkpoints that bypass the load-time upcast.

Verified on Qwen3.6-27B (M4 Pro): the warning is gone and the whole graph schedules onto MTL0. Qwen3-0.6B smoke test unchanged.

Summary by CodeRabbit

  • Bug Fixes
    • Improved Qwen3.5 and Qwen3.6 linear-attention support on Metal.
    • Prevented unnecessary CPU fallback during model execution.
    • Improved compatibility for models using BF16 linear-decay parameters.

… exp() runs on Metal

Running a bf16 Qwen3.6 checkpoint on Metal warned:

    48 graph node(s) fell back to CPU (no Metal kernel): UNARYx48

One per linear-attention layer: the gated-delta-rule block computes
`exp(A_log)` directly on the bf16 `linear_attn.A_log` weight, and
ggml-metal has no bf16 unary kernel, so every `exp` node fell back to
CPU and split the graph. `A_log`/`dt_bias` slipped past the load-time
f32 promotion, whose suffix match only covers `*norm.weight`/`*.bias`.

Fix, scoped to PieArch::Qwen3_5:

  - model.cpp: promote `linear_attn.{A_log,dt_bias}` to f32 at load
    time ([num_v_heads] floats per layer, ~9 KiB total on 27B), and
    exempt them from the f32→bf16 downcast.
  - graph_qwen3_5.cpp: defensive f32 cast before `exp(A_log)` for
    checkpoints that bypass the load-time upcast.

Verified on Qwen3.6-27B (M4 Pro): the warning is gone and the whole
graph schedules onto MTL0. Qwen3-0.6B smoke test unchanged.
@zyma98 zyma98 requested a review from ingim July 10, 2026 19:45
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Qwen3.5 linear-attention decay weights are now retained as F32 during loading, and non-F32 lin_A_log tensors are cast to F32 before exponentiation.

Changes

Qwen3.5 precision handling

Layer / File(s) Summary
Linear decay weight classification
driver/portable/src/model.cpp
Identifies Qwen3.5 linear-attention A_log and dt_bias weights as small F32 weights and applies the consolidated upcast/downcast logic.
F32 exponentiation input
driver/portable/src/graph_qwen3_5.cpp
Casts non-F32 L.lin_A_log tensors to F32 before applying ggml_exp.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: keeping Qwen3.5/3.6 linear-attention decay parameters in f32 to avoid Metal exp() fallback.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/qwen3.5-metal

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
driver/portable/src/graph_qwen3_5.cpp (1)

65-67: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor: repeated "cast-to-f32-if-needed" pattern.

dt_bias and A_log now both use the identical 3-line ternary-cast idiom. Could extract a small to_f32(ctx, t) helper to avoid duplicating this if a third such param appears later.

♻️ Optional helper extraction
+static ggml_tensor* to_f32(ggml_context* ctx, ggml_tensor* t) {
+    return (t->type == GGML_TYPE_F32) ? t : ggml_cast(ctx, t, GGML_TYPE_F32);
+}
+
 ...
-    auto* dt_bias = (L.lin_dt_bias->type == GGML_TYPE_F32)
-        ? L.lin_dt_bias
-        : ggml_cast(ctx, L.lin_dt_bias, GGML_TYPE_F32);
+    auto* dt_bias = to_f32(ctx, L.lin_dt_bias);
 ...
-    auto* A_log = (L.lin_A_log->type == GGML_TYPE_F32)
-        ? L.lin_A_log
-        : ggml_cast(ctx, L.lin_A_log, GGML_TYPE_F32);
+    auto* A_log = to_f32(ctx, L.lin_A_log);

Also applies to: 70-75

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@driver/portable/src/graph_qwen3_5.cpp` around lines 65 - 67, Extract the
repeated cast-to-F32 ternary used for dt_bias and A_log into a small to_f32(ctx,
tensor) helper, then replace both inline expressions with calls to that helper
while preserving the existing behavior for tensors already of type
GGML_TYPE_F32.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@driver/portable/src/graph_qwen3_5.cpp`:
- Around line 65-67: Extract the repeated cast-to-F32 ternary used for dt_bias
and A_log into a small to_f32(ctx, tensor) helper, then replace both inline
expressions with calls to that helper while preserving the existing behavior for
tensors already of type GGML_TYPE_F32.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b5ebf8c4-597c-447e-a874-5729fc285b82

📥 Commits

Reviewing files that changed from the base of the PR and between 25ecc03 and dec555a.

📒 Files selected for processing (2)
  • driver/portable/src/graph_qwen3_5.cpp
  • driver/portable/src/model.cpp

@ingim ingim merged commit b537a64 into main Jul 10, 2026
8 checks passed
@zyma98 zyma98 deleted the fix/qwen3.5-metal branch July 10, 2026 21:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants