md-bogush/Qwen3.5-4B-onnx-int4-cuda
Qwen3.5-4B — ONNX int4 (CUDA, onnxruntime-genai)
int4-сборка Qwen/Qwen3.5-4B для onnxruntime-genai на CUDA EP. Собрана для голосового ввода в Dispatch, где модель работает корректором диктовки: расставляет пунктуацию и регистр и возвращает англоязычные технические термины, которые распознаватель речи свёл к похожим по звучанию русским словам.
У onnx-community genai-сборок Qwen3.5 нет: Qwen3.5-4B/2B/0.8B-ONNX опубликованы в формате transformers.js без genai_config.json, а единственная с genai (9B) весит 15.9 ГБ неквантованной. Эта сборка закрывает пробел.
Отличие от сборки по умолчанию: prune_lm_head
Собрано с prune_lm_head=true — перед LM head вставляются Gather+Unsqueeze, так что на префилле считается только логит последней позиции: вход MatMul [B,1,H] вместо [B,S,H]. Для корректора это принципиально: задача копирующая, выходных токенов десятки, а логиты на всю длину промпта — главный потребитель памяти (при vocab 248 320 это ~607 КБ на позицию).
Замер на RTX 5080, CUDA EP, 23 фразы, дайджест сессии 2000 символов (промпт 1185 токенов):
Качество и вес не меняются, пик VRAM падает вдвое. Побочно снимается класс отказов, где корректор молча умирал на max_length cannot be greater than model context_length: контекст здесь 262 144 токена.
Как собрано
python -m onnxruntime_genai.models.builder \
-m Qwen/Qwen3.5-4B -o <out> -p int4 -e cuda -c <cache> \
--extra_options exclude_embeds=false hf_token=false prune_lm_head=trueСобрано билдером onnxruntime-genai 0.14.1. Важные детали:
- `exclude_embeds=false` обязателен. Qwen3.5 — VL-модель, и по умолчанию билдер делает декодер на
inputs_embeds, несовместимый сappend_tokens. Без флага получается бесполезная сборка, и это выясняется только при запуске. - `hf_token=false` — иначе билдер падает с
LocalTokenNotFoundErrorдаже на публичном репозитории. - `onnx_ir` и `onnxscript` ставить отдельно — в зависимости
onnxruntime-genaiони не входят, билдер падает на импорте. - Билдером 0.15.0-dev эту модель собирать нельзя. Он принудительно добавляет
Gatherв список квантуемых типов и загоняет в int4 то, что обязано остаться точным: таблицу эмбеддингов и кэши RoPE (model.rotary_emb.cos_cache/sin_cache). Тригонометрические значения в диапазоне [-1, 1] на 16 уровнях int4 разрушают позиционное кодирование — модель генерирует мусор (декодируется в'�'), и ошибка ничем не сигнализируется. Проверять сборку стоит по типам: у корректнойmodel.embed_tokens.weight— FLOAT16,rotary_emb.*_cache— FLOAT, узловGatherBlockQuantizedнет.
Что проверено и отброшено: matmul_mixed_precision=last_matmul:int8,linear_attn:int8 — на 0.5 ГБ больше, медленнее, качество то же; shared_embeddings=true вместе с int4 разделяет lm_head и таблицу эмбеддингов, то есть кладёт эмбеддинги в int4 с тем же результатом, что выше.
Архитектура
Гибридная: full_attention чередуется с linear_attention (GatedDeltaNet, рекуррентное состояние вместо KV-кэша). 32 слоя, hidden 2560, 16 голов внимания, 4 KV-головы, head_size 256, vocab 248 320, контекст 262 144.
Лицензия
Производная от Qwen/Qwen3.5-4B, лицензия Apache-2.0 сохраняется. Веса не переобучались — только экспорт в ONNX и int4-квантование весов.
