Jedno číslo pro čtyři modely — strop, který nikdo neporovnal se zdí
- Deklarováno všem
- 65 536
- Skutečná zeď lokálního
- 32 768
- Po pouhém smazání
- 131 072
- Cena skutečné opravy
- +2,4 GB
Stalo se 10. 9. 2026. Řešil jsem s agentem přes Telegram velikost kontextového okna a on navrhl nastavit globálně 272 000 — tolik, kolik má cloudový model, na kterém běží hlavní chat. To by bylo špatně a odmítl jsem to: pod tímtéž nastavením běží i naplánované úlohy na lokálním modelu a těm by to lhalo. Návrh jsme společně přepsali na „zrušit globální strop a nechat každý běh, ať si okno určí podle svého modelu".
Byl to správný závěr, ale stálo na něm ještě jedno číslo, kterého se nikdo
nedotkl: v konfiguraci bylo context_length: 65536. Ne 272 000, ne
32 768. Číslo, které kdysi někdo napsal a od té doby ho nikdo neporovnal
s ničím.
Co je vlastně zeď
Lokální server hlásí u nahraného modelu dvě různá čísla:
max_context_length = 262 144
loaded_context_length = 32 768
První je, co model umí. Druhé je, co mu server v běžící instanci opravdu
alokoval — a to je ta zeď, o kterou se požadavky rozbíjejí. Přes standardní
OpenAI-kompatibilní /v1/models se ani jedno nedozvíte, ten endpoint
metadata nevrací ani s autorizací; jsou až na nativním API serveru. To
je nejspíš důvod, proč to číslo nikdo nikdy neověřil: první místo, kam
člověk sáhne, mlčí.
Deklarovaných 65 536 tedy bylo dvojnásobkem skutečné zdi.
Co se dělo, když se do zdi narazilo
V logu lokálního serveru je 67 odmítnutých požadavků:
request (34833 tokens) exceeds the available context size (32768 tokens)
request (42496 tokens) exceeds …
Rozložené do devíti dnů, od 15 741 do 903 064 tokenů.
Na straně agenta ta chyba není zamlčená — v jeho logu je dvaadvacetkrát, zabalená do obecného varování o selhání volání API:
error_type=APIError … summary=Engine protocol predict stream returned an
error: {"code":500,"message":"Context size has been exceeded.", …}
Jenže chybí to jediné, čím by šlo problém rozpoznat: čísla. Věta „kontext byl překročen" vypadá jako popis příliš dlouhé konverzace, tedy něčeho, co se prostě stává. Teprve dvojice 34 833 proti 32 768 říká, že strop v konfiguraci je jinde než zeď — a ta dvojice existovala jen na druhé straně, v logu lokálního serveru, kam se nikdo nedíval, protože selhávalo přece „připojení k modelu".
Proč samotné smazání nestačí
Nabízí se ten strop prostě zahodit a nechat runtime, ať si poradí. Zkusil jsem, co by se stalo, a je to horší:
Using hardcoded context length 131,072 for model '…-27b'
(custom endpoint, catalog match on 'qwen')
Bez explicitní hodnoty spadne odvozování až na zadrátovaný katalog, který hádá podle jména modelu — a uhodne 131 072, tedy čtyřnásobek. Jedno číslo bylo tedy nahrazeno jiným, taky vymyšleným, jen o řád horším.
Správný nástroj není globální strop ani jeho absence, ale override klíčovaný poskytovatelem a modelem. Sedí v řetězci odvozování hned na začátku, před sondami i katalogem, a je ze své podstaty selektivní: co nepojmenuje, toho se nedotkne. Po nasazení se z jednoho čísla staly dvě pravdivé odpovědi:
| běh | okno | skutečná zeď |
|---|---|---|
| hlavní chat · cloudový model | 272 000 | 272 000 |
| naplánované úlohy · lokální 27B | 65 536 | 65 536 |
Pořadí kroků přitom není libovolné. Kdybych nejdřív smazal a teprve pak deklaroval, mezistav by běžel na těch nesmyslných 131 072.
Model, který nikdo neobjednal
Při té příležitosti vyšlo najevo, proč dvě naplánované úlohy měly roky v konfiguraci nesmysl: poskytovatel lokální server, ale model cloudové mini. Takový model na tom serveru neexistuje. Vyzkoušel jsem, co odpoví:
{ "model": "…-27b", "choices": [ … ] }
Požadavek na neexistující model tiše obslouží ten nahraný. Žádná chyba, žádné varování. Proto ty úlohy roky končily úspěšně a nikoho nenapadlo se na ně podívat.
Log agenta to neodhalí, protože zapisuje co se zeptalo, ne co odpovědělo. Poznat to jde jedině nepřímo — po latenci: první volání trvala čtyři minuty, což je lokální model, ne cloudové mini, které odpovídá v sekundách.
Dokud platil globální strop, byl ten nesoulad kosmetický. Ve chvíli, kdy okno začalo záviset na jménu modelu, se z něj stala díra: jméno cloudového mini se odvodí na 400 000 proti zdi 65 536. Moje vlastní oprava tedy tu chybu nejdřív zvětšila a teprve dodatečná kontrola ji odhalila.
Co jsem přitom udělal špatně
Změřil jsem no-op a ohlásil to jako nález. Měl jsem hypotézu, že server drží čtyři paralelní sloty, z nichž se reálně používají dva, a že se dá okno zdvojnásobit zdarma výměnou za nevyužitou paralelitu. Načetl jsem model se změněnými parametry, změřil paměť, viděl skoro stejné číslo a napsal „sloty jsou zdarma". Jenže načtení modelu, který už je načtený, server tiše ignoruje. Identifikátor procesu se nezměnil — měřil jsem pořád tentýž běh. Kdybych se na to číslo podíval dřív než na svůj závěr, ušetřím si to.
A nástroj na odhad odpověděl na jinou otázku. Server má funkci „spočítej, kolik to zabere, ale nenačítej". Vrátila 16,52 GiB. Pro všechny tři varianty, které jsem zkoušel, stejně — počítá totiž jen váhy modelu, ne cache, tedy právě to jediné, na co jsem se ptal. Odpověď vypadala jako odpověď.
Skutečná cena se ukázala až měřením běžícího procesu: 19,81 GB → 22,18 GB, tedy +2,4 GB za dvojnásobné okno. Váhy z toho tvoří 15,66 GB, zbytek je cache a buffery. Není to zadarmo, jak jsem tvrdil.
- configglobální `model.context_length` zrušen; okno se odvozuje z modelu a provideru každého běhu
- configskutečné okno lokálního serveru deklarováno v `model_overrides`, klíčované provider→model
- postupběhový stav (kolik má nahraná instance alokováno) se čte z běžící instance, ne z toho, co model umí