Skip to content

fix(qwen3.5): better support for hybrid models and auto device map - #2373

Merged
EricLBuehler merged 12 commits into
masterfrom
candle_fa3_fixes
Aug 15, 2026
Merged

fix(qwen3.5): better support for hybrid models and auto device map#2373
EricLBuehler merged 12 commits into
masterfrom
candle_fa3_fixes

Conversation

@EricLBuehler

Copy link
Copy Markdown
Owner

No description provided.

Snapshots were keyed on exact token multiples of the block size, which
prefill never lands on since it advances by the whole prompt in one step.
The first key written was always past the largest reachable block offer,
so no lookup could ever hit and every decode-time snapshot leaked.

Block-align the prefill chunk tail so the existing recurrent_boundaries
path fires at the offer size, and restrict the engine hook to prompt steps.
Device mapping derives a budget that subtracts the activation reserve, then
the pipelines called calculate_cache_config again with Utilization and no
model weight size, recomputing 0.9 * total - used. Since used already covers
the weights, the reserve was dropped and the leftover was always a flat
fraction of the card regardless of model or workload.

Write the derived budget back so both consumers read the same number.
Sizing multiplied a single per-layer element count by the number of paged
layers, so any model whose layers differ had to flatten itself to an average
first. Gemma4 summed per layer then divided by the layer count purely to fit
that interface.

Sum per layer instead, with layers that hold no paged cache reporting None.
The activation reserve is predicted from max_seq_len and never checked against
what a prefill costs. Run one synthetic prefill after the cache is allocated
and log the resident bytes next to the prediction.

Reported only; nothing is sized off it yet. The mempool release threshold
keeps freed memory out of the driver, so free memory after the forward is
already the high-water mark.
A chunk late in a long sequence attends over the whole KV cache, so measuring
only at offset zero would miss any growth. Probe again further in and report
the slope.

Measured flat on both a dense and a hybrid model up to 64k of context, which
is what paged attention should give: the KV is streamed, not materialised.
The repacked weights are a second full copy held for the process lifetime.
Leaving it on by default spent that memory on every run, and because the
repack claimed it after the KV cache was already sized, it silently switched
itself off whenever the card was full. Same binary, same model, different
behaviour depending on what else was resident.

Default it off behind MISTRALRS_GGUF_AFFINE_BACKEND=on, and when it is on
subtract the exact size as a KV cache budget term rather than correcting the
cache afterwards.
OpenAI sends tool_calls[].function.arguments as a JSON string, while chat
templates are written against the map transformers passes. Templates doing
`arguments|items` therefore failed to render with "cannot convert value into
pairs", which surfaced to clients as a 200 carrying the Jinja error.

Parsing was already done, but only for three templates matched by name.
Key it on the template actually iterating arguments instead.
@github-actions

Copy link
Copy Markdown
Code Metrics Report
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 Language              Files        Lines         Code     Comments       Blanks
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 C Header                 24         4497         3151          790          556
 CSS                       3          281          252            5           24
 CUDA                    124        26446        21759         1707         2980
 Dockerfile                1           35           19            9            7
 HTML                      2           27           27            0            0
 JavaScript                3          582          567           12            3
 Jinja2                    7          694          656            5           33
 JSON                     27        18278        18275            0            3
 Makefile                  1           18           16            0            2
 MDX                      37         6771            0         4994         1777
 Metal Shading Lan|       37        14416        11408         1136         1872
 PowerShell                1          657          571           31           55
 Python                  152        12617        10486          480         1651
 Shell                     3         1071          852          115          104
 Plain Text               53        10687            0         9209         1478
 TOML                     28         1396         1212           43          141
 TypeScript               11         1658         1418           66          174
 YAML                      3           25           23            2            0
─────────────────────────────────────────────────────────────────────────────────
 Jupyter Notebooks         3          122           83           23           16
 |- Markdown               1           60           30           22            8
 |- Python                 1          122          113            1            8
 (Total)                              304          226           46           32
─────────────────────────────────────────────────────────────────────────────────
 Markdown                275        12389            0         9260         3129
 |- BASH                  25          306          225           48           33
 |- Dockerfile             2           14           12            0            2
 |- JSON                   6          289          289            0            0
 |- PowerShell             1            1            1            0            0
 |- Python               135         7349         6119          306          924
 |- Rust                  62         3845         2851          396          598
 |- TOML                   7          116           97            0           19
 (Total)                            24309         9594        10010         4705
─────────────────────────────────────────────────────────────────────────────────
 Rust                    728       398957       360640         5474        32843
 |- Markdown             425        10117          452         8470         1195
 (Total)                           409074       361092        13944        34038
─────────────────────────────────────────────────────────────────────────────────
 Svelte                   19         1974         1832           50           92
 |- CSS                    1            4            4            0            0
 |- JavaScript            19          921          767           25          129
 (Total)                             2899         2603           75          221
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 Total                  1542       536742       444207        42679        49856
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Defaulting the backend off left the packed kernels untested: the unit tests
drive that path directly and silently fell through to canonical dispatch.
Default to on under cfg(test); the env var still overrides either way.
@EricLBuehler
EricLBuehler merged commit d1ffbe7 into master Aug 15, 2026
19 checks passed
@EricLBuehler
EricLBuehler deleted the candle_fa3_fixes branch August 15, 2026 23:44
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.

1 participant