Skip to content

[Bug]: qwen36 Vulkan degradation when all experts are resident. #2583

Open

Description

@lloyd-livengood

Commit or version

v2.1.0

Environment

  • colibri: v2.1.0 (Windows runtime, START-HERE.bat default launch)
  • OS: Windows [version]
  • Python: 3.13
  • CPU / RAM: 9800x3d, 32gb DDR5 memory
  • GPU: AMD Radeon RX 9060 XT, AMD proprietary driver 26.10.44 (LLPC)
  • Model: Qwen3.6-35B-A3B, int4 packed (qwen36-35b)
  • Relevant banner lines (default launch):
    • cache=223/layer bits=4 ctx=65536
    • [VK] cooperative matrix off on AMD's own driver ... (COLI_VK_COOP was never set in any run below)
    • [VK] tier qwen36: on, budget 11.84 GiB = 7089 experts
    • [VK] tier qwen36: host pool on, 3151 experts in 5.25 GiB of system RAM
    • [VK] tier qwen36: warm start, 10240 experts ... every expert of the model

Reproduction steps

Title

qwen36 on Vulkan (RX 9060 XT): output degrades into repetition after ~3 requests when all experts are resident; healthy on CPU, with the host pool off, or with the host pool capped below full residency

Summary

With the default launch of Qwen3.6-35B-A3B (int4) on an AMD discrete GPU, generations are fine for the first 2-3 requests and then collapse into repetitive text. Each request is an independent single-message conversation, so this is not conversation growth. The collapse appears to depend on the whole expert set being resident (device VRAM plus host pool). Capping the host pool just below that point gives healthy output.

Expected behavior

Test script:

$base = "http://127.0.0.1:8000/v1"
$model = (Invoke-RestMethod "$base/models").data[0].id
$topics = "a lighthouse keeper","a deep-sea diver","a train conductor","a beekeeper","a night watchman","a ferry captain","a clockmaker","a mountain guide"
$i = 0
foreach ($t in $topics) {
  $i++
  $body = @{ model=$model; messages=@(@{role="user";content="Write a detailed 300 word story about $t."}); max_tokens=400; temperature=0.7 } | ConvertTo-Json -Depth 5
  $sw = [Diagnostics.Stopwatch]::StartNew()
  $r = Invoke-RestMethod "$base/chat/completions" -Method Post -ContentType "application/json" -Body $body
  $sw.Stop()
  $w = ($r.choices[0].message.content -split '\s+') | Where-Object { $_ }
  "{0}: ratio {1:N2} | {2} tokens | {3:N1} t/s" -f $i, (($w | Sort-Object -Unique).Count / [math]::Max($w.Count,1)), $r.usage.completion_tokens, ($r.usage.completion_tokens / $sw.Elapsed.TotalSeconds)
}

Expected

Unique-word ratio stays around 0.6-0.7 for every request.

Actual behavior and logs

## Actual (default launch, cold start)

1: 0.68 | 50.7 t/s     5: 0.11 | 72.7 t/s
2: 0.66 | 51.6 t/s     6: 0.32 | 48.5 t/s
3: 0.60 | 51.1 t/s     7: 0.17 | 65.0 t/s
4: 0.16 | 74.5 t/s     8: 0.18 | 60.7 t/s

Other cold starts with the same prompt showed the same pattern (healthy for requests 1-3, collapsed from request 4 or 5 onwards). One run was already degraded from request 1, so the onset is not perfectly deterministic.

Collapsed replies repeat a word or phrase until the token limit (e.g. "a beacon, a beacon, ..."). Throughput rises to 60-80 t/s during collapse. Lookup drafting accepts almost everything then (`acceptance 98.2%`), versus 0% on healthy prose, so those speeds are loops, not real speedups.

## Steps to reproduce
1. Start the server with the stock `START-HERE.bat` (also reproduces with `coli web --host 0.0.0.0` plus `COLI_VULKAN=1`).
2. Wait for `OpenAI-compatible API listening`.
3. Send 8 independent chat requests (`temperature` 0.7, `max_tokens` 400), each a fresh single-message conversation, e.g. "Write a detailed 300 word story about {topic}." with a different topic each time.
4. Score each reply by unique-word ratio (unique words / total words). Healthy prose scores about 0.6-0.7. Looping text falls below 0.2.

## What changes the behavior
| Configuration | Result (8 requests) |
|---|---|
| Default launch | Collapses around request 4 |
| CPU only (`--gpu none`, `COLI_VULKAN` unset) | Healthy but slow - ~14t/s |
| `COLI_VK_TIER_HOST=0` (host pool off) | Healthy,~40 t/s (two runs) |
| `COLI_VK_TIER_HOST_GB=1` or `2` | Healthy, about 40 t/s |
| `COLI_VK_TIER_HOST_GB=5` | Healthy, about 44-45 t/s |
| `COLI_VK_HOST_POOL_IMPORT=0` | Still collapses |
| `COLI_VK_HYBRID_GATE=0` | Worse (degraded from request 1) |
| `COLI_LOOKUP=0` | Still collapses (not confirmed that `0` disables lookup) |
| Tuning profile removed | Still collapses |
| `.coli_usage` renamed (empty expert history) | Degraded from request 1 |

## Observation and hypothesis
Default launch (broken): the warm start places every expert (`10240`), with `CPU RAM hits 0, disk loads 0, evictions 0`.
`COLI_VK_TIER_HOST_GB=5` (healthy): `10109` of 10240 experts resident, with `disk loads 3`. The in-frame hybrid path is still active in this run (`4776 layer steps with the routed experts in the frame`).

Hypothesis (not confirmed): the fault only appears when the entire expert set is resident, so the engine never exercises its path for non-resident experts. An empty usage history also breaks it, which suggests expert placement affects it. I haven't looked at the engine source, so the underlying cause is unknown.

## Workaround
`COLI_VULKAN=1` plus `COLI_VK_TIER_HOST_GB=5` gives healthy output at about 44-45 t/s. Capping the host pool below full residency is enough.

## Additional notes
- `coli tune` reports "baseline retained" and is not involved.
- Cooperative matrix is off by default per the banner and was never enabled in these runs.
- I can probably fish up some Server log excerpts from healthy and broken runs on request.

Activity

  1. JustVugg commented on Nov 4, 2026

    @JustVugg
    Owner

    On it

  2. JustVugg commented on Nov 4, 2026

    @JustVugg
    Owner

    Thanks for the detailed report, the configuration table pointed straight at it. With experts in the host pool, the Vulkan chain computes the pool's experts on the CPU inside its frame and meets the device on Vulkan events. Two of those barriers were wrong: the step's sum read the device experts' output with no barrier after the dispatch that wrote it, and the event's stage mask held a stage Vulkan does not allow there. An NVIDIA card and Lavapipe serialize around events and hide it, a Radeon need not. #2588 fixes both: the Khronos validation layer's synchronization check found them at every hybrid step on dev and finds none with the fix.

    If you can build the branch fix/vk-hybrid-event-barriers, your 8-request script with the default launch (no COLI_VK_TIER_HOST_GB), and once with COLI_VK_HYBRID_GATE=0, would confirm it on your card. Otherwise it will be in the next release. If it still collapses, the [VK] qwen36 chain: and [VK] tier qwen36 lines from a broken run's server log would help.

  3. lloyd-livengood commented on Nov 4, 2026

    @lloyd-livengood
    Author

    No $env:COLI_VK_TIER_HOST_GB and no $env:COLI_VK_HYBRID_GATE=0:
    1: ratio 0.06 | 400 tokens | 91.4 t/s
    2: ratio 0.08 | 400 tokens | 80.9 t/s
    3: ratio 0.08 | 400 tokens | 88.5 t/s
    4: ratio 0.24 | 400 tokens | 75.1 t/s
    5: ratio 0.20 | 400 tokens | 73.2 t/s
    6: ratio 0.27 | 400 tokens | 57.3 t/s
    7: ratio 0.19 | 400 tokens | 66.5 t/s
    8: ratio 0.07 | 400 tokens | 84.3 t/s
    1: ratio 0.20 | 400 tokens | 70.3 t/s
    2: ratio 0.18 | 400 tokens | 72.4 t/s
    3: ratio 0.12 | 400 tokens | 71.3 t/s
    4: ratio 0.14 | 400 tokens | 79.5 t/s
    5: ratio 0.36 | 400 tokens | 55.6 t/s
    6: ratio 0.65 | 400 tokens | 57.3 t/s
    7: ratio 0.63 | 400 tokens | 57.4 t/s
    8: ratio 0.15 | 400 tokens | 80.6 t/s

    No $env:COLI_VK_TIER_HOST_GB with $env:COLI_VK_HYBRID_GATE=0:
    1: ratio 0.14 | 400 tokens | 82.5 t/s
    2: ratio 0.06 | 400 tokens | 89.9 t/s
    3: ratio 0.06 | 400 tokens | 78.0 t/s
    4: ratio 0.15 | 400 tokens | 73.9 t/s
    5: ratio 0.13 | 400 tokens | 70.3 t/s
    6: ratio 0.14 | 400 tokens | 76.3 t/s
    7: ratio 0.20 | 400 tokens | 61.6 t/s
    8: ratio 0.07 | 400 tokens | 83.4 t/s
    1: ratio 0.29 | 400 tokens | 57.0 t/s
    2: ratio 0.08 | 400 tokens | 70.0 t/s
    3: ratio 0.10 | 400 tokens | 78.0 t/s
    4: ratio 0.06 | 400 tokens | 84.9 t/s
    5: ratio 0.17 | 400 tokens | 72.0 t/s
    6: ratio 0.11 | 400 tokens | 83.2 t/s
    7: ratio 0.32 | 400 tokens | 64.4 t/s
    8: ratio 0.03 | 400 tokens | 86.9 t/s

    Server log for those last 16 fails:

    t1.txt

    out of curiosity I wanted to see if something in my config was tripping up, and the results weren't promising.

    START-HERE.bat test after switch to branch:
    1: ratio 0.09 | 400 tokens | 77.0 t/s
    2: ratio 0.09 | 400 tokens | 74.9 t/s
    3: ratio 0.06 | 400 tokens | 82.5 t/s
    4: ratio 0.06 | 400 tokens | 81.4 t/s
    5: ratio 0.09 | 400 tokens | 80.4 t/s
    6: ratio 0.05 | 400 tokens | 83.3 t/s
    7: ratio 0.06 | 400 tokens | 82.5 t/s
    8: ratio 0.04 | 400 tokens | 84.0 t/s

    START-HERE.bat after full clean installation and switch to branch:
    1: ratio 0.18 | 400 tokens | 70.9 t/s
    2: ratio 0.08 | 400 tokens | 81.9 t/s
    3: ratio 0.06 | 400 tokens | 81.8 t/s
    4: ratio 0.09 | 363 tokens | 81.3 t/s
    5: ratio 0.15 | 400 tokens | 66.2 t/s
    6: ratio 0.08 | 400 tokens | 81.7 t/s
    7: ratio 0.08 | 400 tokens | 79.8 t/s
    8: ratio 0.12 | 400 tokens | 73.6 t/s

    t2.txt

  4. lloyd-livengood commented on Nov 4, 2026

    @lloyd-livengood
    Author

    Sorry for wall of text. I also ran with my own config with these again:

    $env:COLI_VULKAN = "1"
    $env:COLI_VK_COOP = "1"
    $env:COLI_VK_TIER_HOST_GB = "5"
    

    1: ratio 0.59 | 400 tokens | 50.1 t/s
    2: ratio 0.63 | 400 tokens | 50.5 t/s
    3: ratio 0.66 | 400 tokens | 50.6 t/s
    4: ratio 0.61 | 400 tokens | 50.4 t/s
    5: ratio 0.66 | 400 tokens | 50.1 t/s
    6: ratio 0.64 | 400 tokens | 50.5 t/s
    7: ratio 0.59 | 400 tokens | 50.4 t/s
    8: ratio 0.58 | 400 tokens | 49.8 t/s
    1: ratio 0.63 | 400 tokens | 51.0 t/s
    2: ratio 0.62 | 400 tokens | 50.5 t/s
    3: ratio 0.66 | 400 tokens | 50.1 t/s
    4: ratio 0.65 | 400 tokens | 50.1 t/s
    5: ratio 0.63 | 400 tokens | 49.7 t/s
    6: ratio 0.68 | 400 tokens | 50.3 t/s
    7: ratio 0.61 | 400 tokens | 49.9 t/s
    8: ratio 0.67 | 400 tokens | 50.6 t/s

    test.txt

  5. JustVugg commented on Nov 4, 2026

    @JustVugg
    Owner

    Thanks for testing, and sorry the first fix missed it. Your logs and one more experiment point at the cause. In a hybrid step the frame waits on an event that the host sets while the frame is already running. The current Vulkan spec asks for such an event to be set before the frame is submitted (VUID-vkSetEvent-event-09543), so a driver is allowed to let the frame through, and the step then reads the CPU's expert rows before they are written. On our RTX PRO 4000, making the frame skip that wait reproduces your symptoms with every expert resident: "1 1 1 1", broken words, speeds only a loop reaches. It also fits your table: the more host pool work per step (full residency, an empty usage history, COLI_VK_HYBRID_GATE=0), the more often the CPU loses that race; with 5 GB its part is short enough to finish first.

    Two things would confirm it on your card:

    1. Without rebuilding: your default launch with COLI_VK_HYBRID_EVENTS=0, which runs the same steps on timeline semaphores, the form the spec allows.
    2. The branch fix/vk-hybrid-event-barriers again, now updated (vulkan chain: the hybrid steps leave the events where a frame does not wait for the host's, and their barriers #2588). At start the chain checks whether the frame really waits. If it does not, it prints [VK] chain: this driver does not hold a frame at an event the host sets while it runs and uses the timelines by itself, so your default launch should give healthy ratios. Whether that line shows up in the server log tells us what the probe saw.
  6. lloyd-livengood commented on Nov 4, 2026

    @lloyd-livengood
    Author

    With launch params
 $env:COLI_VULKAN = "1"
    $env:COLI_VK_COOP = "1"
    $env:COLI_VK_HYBRID_EVENTS="0"
1: ratio 0.63 | 400 tokens | 56.5 t/s
    2: ratio 0.63 | 400 tokens | 57.9 t/s
    3: ratio 0.63 | 400 tokens | 57.2 t/s
    4: ratio 0.64 | 400 tokens | 57.6 t/s
    5: ratio 0.62 | 400 tokens | 57.1 t/s
    6: ratio 0.67 | 400 tokens | 57.6 t/s
    7: ratio 0.63 | 400 tokens | 57.7 t/s
    8: ratio 0.66 | 400 tokens | 57.5 t/s

    t3.txt

    After git pull and launch from START-HERE.bat
1: ratio 0.17 | 400 tokens | 72.4 t/s
    2: ratio 0.10 | 400 tokens | 79.7 t/s
    3: ratio 0.05 | 400 tokens | 79.8 t/s
    4: ratio 0.08 | 400 tokens | 71.4 t/s
    5: ratio 0.05 | 400 tokens | 83.5 t/s
    6: ratio 0.11 | 400 tokens | 70.1 t/s
    7: ratio 0.07 | 400 tokens | 80.4 t/s
    8: ratio 0.15 | 400 tokens | 78.8 t/s

    t4.txt

    After git pull and launch with params
 $env:COLI_VULKAN = "1"
    $env:COLI_VK_COOP = "1"
    $env:COLI_VK_HYBRID_EVENTS="0"
    1: ratio 0.66 | 400 tokens | 56.3 t/s
    2: ratio 0.61 | 400 tokens | 57.3 t/s
    3: ratio 0.63 | 400 tokens | 57.2 t/s
    4: ratio 0.64 | 400 tokens | 57.3 t/s
    5: ratio 0.62 | 400 tokens | 57.1 t/s
    6: ratio 0.65 | 400 tokens | 57.4 t/s
    7: ratio 0.62 | 400 tokens | 57.8 t/s
    8: ratio 0.66 | 400 tokens | 57.5 t/s

    t5.txt

    PS C:\Users\me\colibri> git status
    On branch fix/vk-hybrid-event-barriers
    Your branch is up to date with 'origin/fix/vk-hybrid-event-barriers'.

    That Hybrid events solves things and nets a huge amount of speed, but issue persists in START-HERE.bat default configuration.

    Oh, and all tests run with that same script with temp 0 and max_tokens 400.

  7. lloyd-livengood commented on Nov 4, 2026

    @lloyd-livengood
    Author

    Out of an abundance of caution, here is my hacky PowerShell script I use to launch/debug it with custom params. I set it up initially to communicate to my Mac mini by ethernet, that's what the allowed host set is for. In case it's user error and I am a dummy.

    Start Colibri server and web dashboard

    $ErrorActionPreference = "Stop"

    Run from the installation folder

    Set-Location $PSScriptRoot

    Ask for the API key without displaying it

    $secureKey = Read-Host "Enter your NEW Colibri API key" -AsSecureString
    $bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureKey)

    try {
    $env:COLI_API_KEY = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr)
    $env:COLI_VULKAN = "1"
    $env:COLI_VK_COOP = "1"
    $env:COLI_VK_HYBRID_EVENTS="0"

    Write-Host ""
    Write-Host "Starting Colibri..." -ForegroundColor Cyan
    Write-Host "API:       http://192.168.50.1:8000/v1"
    Write-Host "Dashboard: http://127.0.0.1:8000"
    Write-Host ""
    
    Get-ChildItem Env: |
    Where-Object { $_.Name -match '^(COLI_|Q36_|QWEN_)' } |
    Sort-Object Name |
    ForEach-Object {
        $val = if ($_.Name -match 'KEY|TOKEN|SECRET') { '********' } else { $_.Value }
        Write-Host ("  {0} = {1}" -f $_.Name, $val)
    }
    
    & .\coli serve `
        --host 0.0.0.0 `
        --port 8000 `
        --model "C:\Users\me\colibri-models\qwen36-35b" `
        --model-id qwen36-35b `
        --api-key $env:COLI_API_KEY `
        --allowed-host 192.168.50.1
    

    }
    finally {
    Remove-Item Env:\COLI_API_KEY -ErrorAction SilentlyContinue
    [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)
    Write-Host "`nColibri process ended."
    }

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

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions