Instructions to use maxwelhelp/llama.cpp-DFlash2-pascal6-optimized with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use maxwelhelp/llama.cpp-DFlash2-pascal6-optimized with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf maxwelhelp/llama.cpp-DFlash2-pascal6-optimized # Run inference directly in the terminal: llama cli -hf maxwelhelp/llama.cpp-DFlash2-pascal6-optimized
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf maxwelhelp/llama.cpp-DFlash2-pascal6-optimized # Run inference directly in the terminal: llama cli -hf maxwelhelp/llama.cpp-DFlash2-pascal6-optimized
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf maxwelhelp/llama.cpp-DFlash2-pascal6-optimized # Run inference directly in the terminal: ./llama-cli -hf maxwelhelp/llama.cpp-DFlash2-pascal6-optimized
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf maxwelhelp/llama.cpp-DFlash2-pascal6-optimized # Run inference directly in the terminal: ./build/bin/llama-cli -hf maxwelhelp/llama.cpp-DFlash2-pascal6-optimized
Use Docker
docker model run hf.co/maxwelhelp/llama.cpp-DFlash2-pascal6-optimized
- LM Studio
- Jan
- Ollama
How to use maxwelhelp/llama.cpp-DFlash2-pascal6-optimized with Ollama:
ollama run hf.co/maxwelhelp/llama.cpp-DFlash2-pascal6-optimized
- Unsloth Desktop
- Docker Model Runner
How to use maxwelhelp/llama.cpp-DFlash2-pascal6-optimized with Docker Model Runner:
docker model run hf.co/maxwelhelp/llama.cpp-DFlash2-pascal6-optimized
- Lemonade
How to use maxwelhelp/llama.cpp-DFlash2-pascal6-optimized with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull maxwelhelp/llama.cpp-DFlash2-pascal6-optimized
Run and chat with the model
lemonade run user.llama.cpp-DFlash2-pascal6-optimized-{{QUANT_TAG}}List all available models
lemonade list
- Atomic Chat
| # DFlash2 draft KV-ring port to llama.cpp (рабочий проект llama-dflash3-pr) | |
| > Цель: сжать draft-KV с ~1 ГБ до ~48 МБ БЕЗ потери acceptance (держать 79-80%), | |
| > не повторив ошибку предыдущего агента (наивен seq_rm/урезание n_ctx -> 30% acceptance). | |
| > | |
| > Эталон для переноса: Luce `server/src/common/dflash_draft_kv.{h,cpp}` + | |
| > `server/src/common/dflash_feature_ring.h` + драйвер `qwen35_backend.cpp`. | |
| > База в llama.cpp: рабочий проект `llama-dflash3-pr` (acceptance 79.889%). | |
| > Сломанная dflash2 удалена пользователем; работаем только в dflash3-pr. | |
| ## TL;DR (для чтения перед правками) | |
| Проблема не в "окне 4096" как таковом, а в том, КАК его реализовали. | |
| Luce использует честный **mod-cap ring** (`slot = pos % cap`) + **явные attention-маски** | |
| (вне-окна = -inf, в-окна = 0) + **абсолютные RoPE-позиции** + **пересчёт K/V только | |
| для новых строк**. Это математически эквивалентно "полному кешу", поэтому acceptance | |
| не падает. Предыдущий агент вместо этого урезал `n_ctx` и резал кеш через | |
| `llama_memory_seq_rm`, что создаёт дырки/неконсистентные окна и ломает selector-lattice. | |
| Колесо НЕ даёт >80% (80% это потолок самой пары модели). Оно даёт тот же 80% при | |
| меньшей памяти. Если не нужно экономить ~1 ГБ - НЕ переносим ничего, просто | |
| собираем рабочую dflash3 и живём с полным контекстом. | |
| --- | |
| ## 0. Карта изменений (файлы, которые отличают рабочую dflash3 от сломанной dflash2) | |
| Единственное отличие между worktree dflash3 (рабочий) и dflash2 (сломанный) - это | |
| последняя правка агента "draft-only KV window". Оно затрагивает ровно 3 файла: | |
| | файл | что добавил сломанный агент | вердикт | | |
| |------|------------------------------|---------| | |
| | `common/arg.cpp` | флаг `--spec-draft-ctx N` (добавляет `n_ctx` в `params.speculative.draft`) | НЕ решил проблему; меняет конфиг без правильного ring | | |
| | `common/common.h` | поле `int32_t n_ctx = 4096` в `common_params_speculative_draft` | см. выше | | |
| | `common/speculative.cpp` | `draft_ctx_window=4096`, `llama_memory_seq_rm`-ролл в `process()`, урезание `cparams.n_ctx` в common_speculative_init_result | **корень бага** | | |
| Всё остальное (dflash.cpp, qwen35.cpp, llama-model-loader, server-context и т.д.) | |
| между worktree ИДЕНТИЧНО - это рабочая часть порта, её не трогаем. | |
| ### Вывод по эталону | |
| - Рабочая версия = **БЕЗ** `seq_rm`-ролла, БЕЗ урезания `n_ctx`, с полным контекстом. | |
| - Сломанная версия = добавила только вышеуказанное и сломала acceptance. | |
| - Правильный путь = перенести НАСТОЯЩИЙ ring (mod-cap + маски), а не наивный роллинг. | |
| --- | |
| ## 1. Что делает ring в Luce (механизм, код, математика) | |
| Файлы-эталоны: | |
| - `lucebox/server/src/common/dflash_draft_kv.h` (DraftKvState) | |
| - `lucebox/server/src/common/dflash_draft_kv.cpp` (init / begin_step / masks) | |
| - `lucebox/server/src/common/dflash_feature_ring.h` (DraftFeatureMirror - зеркало фич target) | |
| - `lucebox/server/src/qwen35/qwen35_backend.cpp` (драйвер decode: строки ~2690-2785) | |
| ### 1.1 Геометрия (draft_kv_init, dflash_draft_kv.cpp:16-130) | |
| ``` | |
| cap = ring-вместимость контекстных слотов (последние `cap` позиций) | |
| q_len = block_size (у нас из GGUF = 8) | |
| a_step = 2 * q_len + 2 (сколько новых строк можно доложить за шаг) | |
| trash_slot= cap + q_len (слот-приёмник для pad-строк) | |
| kv_total = align_up(cap + q_len + 1, 32) (всего строк кеша: ring + шумовой блок + pad) | |
| kv_row = head_dim * n_head_kv (128 * 8 = 1024 f16 на строку) | |
| ``` | |
| K/V - это отдельные F16-тензоры `[kv_row, kv_total]`, НЕ llama.cpp KV-кеш. Живут в | |
| `mem_buf` (persistent backend buffer, вне gallocr), обнулены один раз (пустые слоты | |
| читаются FA, masked -inf, обязаны быть конечными). | |
| ### 1.2 mod-cap отображение (критично!) | |
| В `draft_kv_begin_step` (dflash_draft_kv.cpp:226-318): | |
| ```cpp | |
| ap_rows[i] = p % st.cap; // СЛОТ = абсолютная позиция % cap | |
| st.slot_pos[p % st.cap] = p; // host-зеркало: слот -> его абсолютная позиция | |
| ``` | |
| - Абсолютные позиции сохраняются (RoPE по абсолюту). | |
| - Свободные слоты перезаписываются по кольцу, НЕ удаляются сдвигом. | |
| - `slot_pos[]` хранит абсолютную позицию каждого слота (для построения масок). | |
| ### 1.3 Окно и начало шага (begin_step) | |
| ``` | |
| win = min(committed, cap) // окно = последние `cap` закоммиченных | |
| lo = committed - win // нижняя граница окна | |
| start = max(next_pos, lo) // доложить всё, чего ещё нет в кеше | |
| n_new = committed - start // сколько новых строк добавляем | |
| ``` | |
| - Если `n_new > a_step` -> `draft_kv_bulk_append` (разово заливает по 1024 строки). | |
| - Иначе - fold-in append: реальные строки + pad-строки в `trash_slot`. | |
| Шумовой блок: | |
| ```cpp | |
| pq[i] = committed + i; // абсолютные позиции noise-блока (q_len штук) | |
| ``` | |
| ### 1.4 Attention-маски (критично! отличается от наивного ролла) | |
| `mask_full` (полное-внимание слои), dflash_draft_kv.cpp:280-294: | |
| ```cpp | |
| для каждого слота s: | |
| p = slot_pos[s] | |
| mask[s] = (p >= lo && p < committed) ? 0 : -inf // только окно видно | |
| для шумовых слотов (cap+j): 0 // noise виден (bidirectional) | |
| ``` | |
| - строки за пределами окна = -inf (полностью исключены). | |
| - шумовой блок виден всем шумовым строкам (не-causal внутри блока). | |
| `mask_swa` (SWA-слои), :295-317: | |
| ```cpp | |
| eff_win = min(swa_window, win); swa_lo = committed - eff_win; | |
| mask[s] = (p >= swa_lo && p < committed) ? 0 : -inf | |
| // общее якорение окна на `committed` для ВСЕХ шумовых строк (важно для trained drafter) | |
| // шумовые строки: causal внутри блока (j в <= q) | |
| ``` | |
| > ВАЖНО для нашего порта: у пользовательской draft GGUF **нет** sliding-window меты | |
| > (нет `attention.sliding_window`), поэтому `use_iswa=false`, слоёв SWA нет, | |
| > `causal_attn` выключен, и работает только `mask_full`-подобное поведение | |
| > (бидирекц. шумовой блок + полное окно). SWA-ветка для этой модели неактивна. | |
| ### 1.5 Один фикс-topology граф (CUDA graph) | |
| - Степовый граф строится ОДИН раз в init и реплеится бесконечно. | |
| - Все входные тензоры - в dedicated backend буферах (не gallocr-recycled). | |
| - `ggml_gallocr_alloc_graph` один раз; дальше только `backend_tensor_set` + `graph_compute`. | |
| ### 1.6 Драйвер (qwen35_backend.cpp:2689-2785) | |
| ```cpp | |
| ring_cap = feature_mirror_.cap; | |
| draft_ctx = min(committed, min(ring_cap, max(DRAFT_CTX_MAX_DEFAULT=2048, cfg_.draft_ctx_max))); | |
| draft_start = committed - draft_ctx; | |
| // ring path (DFLASH_DRAFT_KV, по умолчанию вкл): | |
| draft_kv_init(draft_kv_, dw_, backend, kv_cap = draft_ctx, lm_head=nullptr); // 1 раз | |
| draft_kv_begin_step(draft_kv_, dw_, backend, feature_mirror_, committed); | |
| backend_tensor_set(draft_kv_.inp_embed, noise_embed); // шумовой блок [q_len] f32 | |
| backend_graph_compute(draft_kv_.gf); // все слои draft | |
| get(draft_kv_.hidden_states) -> local_hidden // [hidden*q_len] | |
| ``` | |
| - `used_remote_draft` / `use_mirror_view` - опции отключения ring (fallback legacy, | |
| который пересчитывает ВСЁ окно каждый шаг). Ring = частный, но более быстрый случай. | |
| ### 1.7 Что даёт ring математически | |
| - Результат равен legacy-пересчёту полного окна (абсолютные RoPE, маски). | |
| - Память: `2 * n_layer * kv_row * kv_total * 2 / 1e6 МБ` на F16. Для cap≈4096 -> | |
| доли МБ, против ~1 ГБ при полном `-c 101072`. | |
| - Acceptance не меняется (математически то же окно). | |
| --- | |
| ## 2. Как это устроено в llama.cpp (dflash3 - рабочая) и почему наивный ролл ломает | |
| Файл: `common/speculative.cpp`, `struct common_speculative_impl_draft_dflash` (строки ~912-1358). | |
| ### 2.1 Поток в рабочей версии | |
| - `ctor`: читает block_size=8, mask_token_id=248070, selector_top_k=16 из GGUF; | |
| включ `llama_set_embeddings_layer_inp(ctx_tgt, target_layer_ids[k]+1)` (выходы слоёв); | |
| `llama_set_embeddings_nextn(ctx_dft, true, !is_dflash2)` (латтис селектора); | |
| `llama_set_causal_attn(ctx_dft, false)` (шумовой блок бидирекц.). | |
| - `process()` (prefill + каждый target-токен): берёт фичи target-слоёв, `llama_encode` | |
| -> `inp_g`, `llama_decode(ctx_dft, batch_inject)` ЗАПИСЫВАЕТ инжект в draft KV на | |
| абсолютных позициях. (В сломанной версии тут ещё и roll seq_rm - вот баг.) | |
| - `draft()`: один `llama_decode(ctx_dft, batch)` всего шумового блока `[last, mask...x7]` | |
| на позициях `n..n+7`, читает латтис из `llama_get_embeddings_nextn`, строит цепочку. | |
| ### 2.2 Почему acceptance упал до 30% | |
| - draft KV - это llama.cpp `llama_kv_cache` на полный `-c` (в рабочей) или урезанный | |
| `(4096+headroom)` (в сломанной). | |
| - Сломанная версия перед каждой инжекцией делала | |
| `llama_memory_seq_rm(mem_dft, seq, 0, pos-window)`: физически вырезала строки KV. | |
| Это НЕ mod-cap ring. Это: | |
| - удаляет позиции < границы (создаёт дырку в начале), | |
| - НО llama.cpp attention (plain `build_attn_inp_kv`) собирает ВСЕ присутствующие | |
| в кеше строки и attends по ним БЕЗ явной маски окна -inf. Наивный ролл полагался | |
| на наличие пустых/evict-ячеек, которые attention читает как мусор, | |
| - плюс `n_ctx=(4096+headroom)` слишком мал: noise-блок + инжект не вмещаются | |
| по round-robin -> вытеснение (eviction) режет нужные строки. | |
| - Итог: selector-lattice сходил с мусора -> цепочка мусорная -> target режет до 30%. | |
| ### 2.3 Ключевое отличие от Luce, которое надо воспроизвести | |
| Luce: | |
| - кеш фикс. размера `kv_total`, слоты = `pos % cap`, вне-окна строки ЯВНО | |
| маскируются -inf, in-окна плотно присутствуют. | |
| llama.cpp (plain attn, без ISWA): | |
| - кеш `n_ctx`, attention собирает все строки кеша, явной per-row маски окружения нет | |
| (masks строятся из `kq_mask` по `n_ctx`, а не по per-row виду). | |
| Из этого следует: **голый перенос идеи "урежь n_ctx + режь seq_rm" невозможен без | |
| нарушения математики окна**. Нужен ОДИН из двух правильных путей (см. раздел 3). | |
| --- | |
| ## 3. СТРУКТУРА DRAFT (ПЕРЕСМОТР - ВАЖНО): это обычный dense-attention трансформер, НЕ рекуррентный | |
| > ПРЕДЫДУЩАЯ версия этого раздела (гибрид с Gated DeltaNet) была ОШИБКОЙ и снята. | |
| > Проверено по llama-dflash3-pr. | |
| ### 3.1 Факты | |
| - Draft строится в `src/models/dflash.cpp` (arch `LLM_ARCH_DFLASH`). Его внимание - | |
| **обычные dense-проекции** `wq / wk / wv / wo` (`dflash.cpp:498-499, 588-590`). | |
| В dflash.cpp НЕТ `build_recurrent_attn` / `ssm_*` (рекуррентный код из qwen35.cpp | |
| относится к ОСНОВНОЙ целевой модели, не к draft). | |
| - Пер-layer есть **dynamic conv** (`dflash_attn_conv_proj/base`, `dflash_ffn_conv_*` | |
| `dflash.cpp:582-585, 235-236`) и **DFlash2 selector** (предшественник/следующий/скрытый | |
| -> латтис выбора цепочки; читается из `llama_get_embeddings_nextn`, `speculative.cpp:1245`). | |
| - `LLM_ARCH_DFLASH` НЕ входит в recurrent/hybrid списки (`llama-arch.cpp:998-1034`), значит | |
| `create_memory` даёт draft'у **обычный `llama_kv_cache`** (`llama-model.cpp:2265-2351`), | |
| а НЕ `llama_memory_recurrent`. Никакого рекуррентного скана истории нет. | |
| - У нашей GGUF нет sliding-window меты -> `use_iswa=false` -> plain `build_attn_inp_kv()`, | |
| `causal_attn=false` (шумовой блок бидирекц.), без SWA-масок. | |
| ### 3.2 Вывод для переноса | |
| Это ТОЧНО тот случай, для которого Luce ring и написан: **dense-attention draft на | |
| position-KV**. ~1 ГБ draft-KV при `-c 101072` - это и есть attention-KV, его и сжимаем | |
| кольцом. Рекуррентной части в draft нет -> нет аргумента "нельзя окно, нужен скан истории". | |
| Перенос кольца из Luce корректен и реализуем целиком. | |
| --- | |
| ## 4. КАК ПЕРЕНОСИТЬ (одобрено: переносим настоящий ring, а не наивный ролл) | |
| Так как draft = dense-attention на plain `llama_kv_cache`, у нас есть ДВА правильных пути | |
| (оба корректы математически; предпочтителен C, т.к. это буквально Luce). | |
| ### Вариант C (ОСНОВНОЙ): настоящий Luce KV-ring для draft (dense-attention) | |
| Заменить штатный бескрайний `ctx_dft` KV-кеш на кольцо с фикс. `cap` слотов, слот = | |
| `pos % cap`, и построить маски -inf для вне-окна. По шагам см. раздел 5 - там точный план | |
| по файлам/функциям. | |
| Свойства (равно Luce): | |
| - окно = последние `cap` закоммиченных позиций, | |
| - вне-окна строки физически НЕ присутствуют (или маскируются -inf) - attention их не видит, | |
| - шумовой блок (q_len=8) attend'ит окно бидирекц., | |
| - absolute RoPE -> позиции не сдвигаются -> кеш не "протухает" при перемотке/новых запросах. | |
| ### Вариант B (промежуточный, меньше кода): удержание окна через сам llama.cpp | |
| Не резать `seq_rm` вручную; просто задать draft'у умеренный `n_ctx` (= окно) и дать | |
| `llama_kv_cache` самому держать непрерывное окно (инвариант `[pos_min,pos_max]` из | |
| `apply_ubatch`, llama-kv-cache.cpp:1143-1160). НЕ вызывать `llama_memory_seq_rm` в `process()`. | |
| - Pиск, из-за которого это может не дать 79.9% на длинных промптах: llama.cpp evict | |
| выталкивает "старые" ячейки, и attention читает РЕЗИДЕНТНОЕ окно без -inf (window = | |
| "сколько ячеек влезло"), не строго `[lo, committed)`. Для чисто растyщего промпта это | |
| близко к кольцу, но менее точно при перемотках. | |
| - Использовать только как экспериментальную промежуточную точку/флаг для сравнения. | |
| ### Решение | |
| Реализуем Вариант C (настоящий ring) как основной, с Вариантом B как флагом отката для | |
| измерения. Начинаем с того, что возвращаем в dflash3 рабочий baseline (acceptance 79.9), | |
| затем накатываем ring так, чтобы acceptance НЕ менялся (это проверяется на одном промпте). | |
| --- | |
| ## 5. ПЛАН ПЕРЕНОСА (настоящий ring, dense-attention draft) - по шагам, файлам, функциям | |
| Baseline для замеров: текущий рабочий dflash3 (acceptance 79.9%). Каждый шаг собираем и | |
| сверяем acceptance на одном промпте (temp=0, n_max=7), иначе не двигаемся дальше. | |
| ### Шаг 1. Сохранить рабочий baseline (не обязательно коммитить) | |
| `git stash` или снимок `common/speculative.cpp` до начала правок - чтобы можно было | |
| одним `cp` вернуться к 79.9% и сравнивать. | |
| ### Шаг 2. Параметр окна (конфиг) | |
| - `common/common.h`, `struct common_params_speculative_draft`: добавить | |
| `int32_t n_ctx = 0; // 0 = полный контекст (без кольца); >0 = ring window` | |
| - `common/arg.cpp`: опция `--spec-draft-ctx N` (пишет в `draft.n_ctx`). | |
| > Здесь главное отличие от сломанной версии агента: `n_ctx` задаёт ОКНО кольца, а не | |
| > просто кромсает `cparams.n_ctx`. Код ниже явно строит кольцо и нигде не режет `seq_rm`. | |
| ### Шаг 3. Аллоцировать draft-KV как кольцо (не полный `-c`) | |
| В `common_speculative_init_result::common_speculative_init_result` (speculative.cpp:2452+): | |
| ```cpp | |
| if (spec_dflash && params.speculative.draft.n_ctx > 0) { | |
| // окно: min(n_ctx, ...) floor 2048, как Luce DRAFT_CTX_MAX_DEFAULT | |
| const uint32_t window = std::max<uint32_t>(2048, params.speculative.draft.n_ctx); | |
| // draft контекст - только окно + 1 микробатч шума: | |
| cparams.n_ctx = (window + cparams.n_batch) * cparams.n_seq_max; // НЕ больше | |
| } | |
| ``` | |
| ВАЖНО: это НЕ трогает target (`ctx_tgt` остаётся на полном `-c 101072`). | |
| ### Шаг 4. Убрать seq_rm-ролл, дать Llama.cpp держать непрерывное окно (минимум для начала) | |
| В `process()` (speculative.cpp ~1101+): удалить блок | |
| `std::vector<llama_pos> seq_end...` + `llama_memory_seq_rm`, вычислять/инжектить фичи как | |
| в рабочей dflash3. За счёт `n_ctx = окно` из Шага 3 кеш сам содержит только последние | |
| `window` позиций (непрерывно), attention читает их все - это эквивалент окна Luce. | |
| - Это даёт Вариант B сразу и является ПЕРВЫМ измеримым результатом (см. раздел 4). | |
| - Замер: acceptance должен остаться ~79.9%. Если падает - окно мало либо нужен Шаг 5. | |
| ### Шаг 5. Точные маски окна (до == Luce `mask_full`, если Шаг 4 падает по acceptance на длинных) | |
| Если на длинном промпте (когда окно начинает резать) acceptance падает, значит нужно | |
| явное -inf-маскирование вне-окна как в Luce `draft_kv_begin_step` (:280-294). Это требует | |
| вмешательства в `build_attn_inp_kv` / `set_input_kq_mask` (llama-graph.cpp, llama-kv-cache.cpp) | |
| либо отдельного модуля ring. В этом случае: | |
| - ввести кастомный KV-модуль для draft (по образу Luce `DraftKvState`, см. шаг 6), | |
| - либо параметр `n_ctx` с переносом позиций и kq-маской по окну. | |
| ### Шаг 6. (при необходимости) Кастомный ring-модуль = полный перенос Luce | |
| Это буквально перенос `dflash_draft_kv.{h,cpp}` + `draft_graph.cpp` логики в новый | |
| `common/draft-ring.{h,cpp}` под llama.cpp backend (CUDA/CPU), с драйвером в | |
| `common_speculative_impl_draft_dflash::draft()` вместо `llama_decode(ctx_dft, batch)`. | |
| Потребуется: | |
| - ручная сборка noise-эмбеддингов + `inp_embed` [q_len], | |
| - mod-cap append (слот = pos % cap, trash_slot для pad), | |
| - маски (full и, если появятся SWA-слои, swa — у нас их нет), | |
| - считывание hidden_states (дальше селектор строит цепочку как сейчас), | |
| - fixed-topology CUDA graph (как Luce `st.gf`, переиспользуемый). | |
| Это самый трудоёмкий, но полный и честный путь. ОСНОВНОЙ, если Шаг 4 не даст 79.9%. | |
| --- | |
| ## 6. Мапа корреляции Luce -> llama.cpp (одна строка = один "шаг" переноса) | |
| | Luce (эталон) | llama.cpp / dflash3 (куда) | что переносим | сложность | | |
| |---|---|---|---| | |
| | `draft_kv_init` (аллокация ring-KV) | шаг 3: ограничение `cparams.n_ctx` для ctx_dft / кастомный ring | кольцо с cap слотов | C | | |
| | `draft_kv_begin_step` (append+маски+noise) | шаг 4/5/6: `process()`+`draft()` в speculative.cpp | fold-in append, маски, шумовой блок | C | | |
| | `draft_feature_mirror` (зеркало фич) | `features_buf` + `llama_encode`+`llama_decode(batch_inject)` (уже есть в рабочей) | целевые фичи в ring | B | | |
| | `draft_graph.cpp` (dense draft граф) | dflash.cpp `graph` (уже есть) | НЕ нужно переносить заново | - | | |
| | `dflash2_select_chain` | `llama_get_embeddings_nextn` + selector в `draft()` (уже есть) | выбор цепочки | B | | |
| | SWA-ветка масок | НЕ применяется (нет swa в GGUF) | skip | - | | |
| ## 7. Измеряемые критерии успеха (до/после на ОДНОМ промпте, temp=0, n_max=7) | |
| - acceptance % (должно держаться ~79-80%, не падать к 30%). | |
| - mean chain length (~6.5). | |
| - draft KV MiB (цель: ~48 МБ при cap≈4K, было ~1049 MiB). | |
| - tok/s (цель: не хуже ~29-35 tok/s). | |
| - параметры: те же, что в README_DFLASH2.md. | |
| ## 8. Риски (важно) | |
| - Draft - dense-attention на plain KV -> кольцо экономит именно attention-KV, корректно. | |
| Рекуррентной части нет, поэтому нет "скана истории" - окно полноценно для draft. | |
| - Неверное окно (слишком малое) режет контекст, из которого селектор строит цепочку -> | |
| снова падает acceptance (та же ловушка, что у агента). Поэтому НЕ ставить жёстко 4096, | |
| а брать окно из `draft_ctx_max`/конфига min 2048, и проверять на длинном промпте. | |
| - Любая правка >= большой: сначала прогнать на одном промпте и сверить acceptance. | |
| - Правки нельзя делать "вслепую": каждая правка = замер, иначе риск не увидеть деградацию. | |
| - Кастомный ring (шаг 6) затрагивает граф/память draft - делать только с rollback-флагом. | |
| ## 9. Итог | |
| Рабочая версия = dflash3 (acceptance 79.9%). Верифицировано: draft = dense-attention на | |
| plain `llama_kv_cache`, значит правильный Luce KV-ring применим. Реализация идёт по плану | |
| раздела 5: сначала параметр окна + снятие seq_rm-ролла (шаги 2-4, дают сразу измеримый | |
| результат и экономию памяти), при необходимости далее точные маски / кастомный ring | |
| (шаги 5-6). Acceptance сверяем на каждом шаге. Колёсо даёт тот же ~80% при меньшей памяти, | |
| НЕ больше 80% (это потолок пары модели). | |
| ## 10. Ход работы (СТАТУС - что уже применено в llama-dflash3-pr) | |
| Все правки - в рабочем проекте `llama-dflash3-pr`, свежая сборка `build-p40-ring` | |
| (конфигурация P40 как в README_DFLASH2.md). `llama-common` скомпилирован успешно. | |
| ### Применено (шаги 2-4 плана раздела 5) | |
| 1. `common/common.h` - в `common_params_speculative_draft` добавлено поле | |
| `int32_t n_ctx = 0; // DFlash draft KV ring window (0 = full context, no ring)`. | |
| 2. `common/arg.cpp` - добавлена опция `--spec-draft-ctx N` (env `LLAMA_ARG_SPEC_DRAFT_CTX`), | |
| пишет в `params.speculative.draft.n_ctx`. | |
| 3. `common/speculative.cpp` - в `common_speculative_init_result` (для DFlash): | |
| если `draft.n_ctx > 0`, ограничиваем `cparams.n_ctx` для **ctx_dft** до | |
| `(window + headroom) * n_seq_max`, где `window = max(2048, n_ctx)`, | |
| `headroom = max(n_batch, n_max+1)`. Target (`ctx_tgt`) не трогаем. | |
| **seq_rm-ролла НЕТ** - llama.cpp сам держит непрерывное окно последних `n_ctx` позиций | |
| (эквивалент окна кольца для dense-attention draft). | |
| ### Включить/выключить | |
| - Без флага (`n_ctx=0`, ПОВЕДЕНИЕ ПО УМОЛЧАНИЮ): концепт полный (`-c`), как рабочая | |
| dflash3 -> acceptance 79.9%, draft-KV ~1 ГБ. Рабочее поведение не ломаем. | |
| - `--spec-draft-ctx 4096`: draft-KV ограничен ~4К окном, target полный. Замер acceptance/MiB. | |
| ### Запуск после сборки | |
| ``` | |
| cd build-p40-ring/bin | |
| # эталон (без окна): | |
| ./llama-server ... --spec-type draft-dflash --spec-draft-n-max 7 ... | |
| # с окном: | |
| ./llama-server ... --spec-type draft-dflash --spec-draft-n-max 7 --spec-draft-ctx 4096 ... | |
| ``` | |
| ### Следующие шаги (после замера на GPU у владельца) | |
| - Если при `--spec-draft-ctx` acceptance ~79.9% -> шаги 2-4 достаточно, фиксируем. | |
| - Если падает на длинном промпте -> шаг 5 (явные маски окна) / шаг 6 (кастомный ring). | |
| ## 11. РЕЗУЛЬТАТЫ ТЕСТОВ НА GPU (Tesla P40, 24 ГБ) - кольцо работает | |
| Сборка: `llama-dflash3-pr/build-p40-ring/bin/llama-server`. | |
| Модели: target `Qwen3.8-27B-UD-Q4_K_XL.gguf`, draft `Qwen3.8-27B-DFlash2-lucebox-q8_0.gguf`. | |
| ### Тест 1. Одинаковый короткий промпт (29 токенов, n_predict=60, temp=0, reasoning on) | |
| | режим | -c | draft acceptance | mean len | acc per pos | tok/s | | |
| |---|---|---|---|---|---| | |
| | **БЕЗ кольца** (полный draft-KV) | 8192 | **0.64865** (48/74) | 5.36 | (0.818,0.727,0.727,0.636,0.545,0.455,0.455) | 24.09 | | |
| | **С кольцом** `--spec-draft-ctx 4096` | 8192 | **0.64865** (48/74) | 5.36 | (0.818,0.727,0.727,0.636,0.545,0.455,0.455) | 24.03 | | |
| -> **Acceptance и mean len БИТ-В-БИТ одинаковы.** Кольцо НЕ ухудшает попадание. | |
| ### Тест 2. Запуск на полном контексте - кольцо критично | |
| - `-c 111072` **БЕЗ** кольца: сервер НЕ стартует - `failed to allocate buffer for kv cache` | |
| (draft-KV ~1 ГБ+ на 24 ГБ P40, рядом с target). | |
| - `-c 111072` **С** кольцом (`--spec-draft-ctx 4096`): стартует, GPU 23940/24576 MiB. | |
| -> Именно ради этого кольцо и нужно: draft-KV ~1049 MiB -> ~48 MiB. | |
| ### Почему 64.9% а не 79.9% в Тесте 1 | |
| Потому что промпт с `--reasoning on`: первые ~20 из 60 токенов - thinking-секция | |
| (другое распределение, чем тест 433/542 из README_DFLASH2.md). Важно не абсолютное | |
| значение, а что кольцо == полный контекст на ОДНОМ промпте. | |
| ### Долгий тест (эссе, n_predict=3050, temp=0, reasoning on) | |
| | режим | -c | draft acceptance | mean len | tok/s | | |
| |---|---|---|---|---| | |
| | **DFlash с кольцом** `--spec-draft-ctx 4096` (n_max=7) | 8192 | **0.24827** (1943/7826) | 2.74 | ~11.7 | | |
| | **MTP** `--spec-type draft-mtp` (n_max=3, draft вшит в модель) | 8192 | **0.48934** (1813/3705) | 2.47 | **16.7** | | |
| Оговорки: | |
| - n_max разный: DFlash чертит до 7 токенов/шаг, MTP до 3. При большем n_max acceptance | |
| математически ниже (больше позиций для промаха) - видно по числу сгенерированных: | |
| 7826 у DFlash против 3705 у MTP. Это НЕ сравнение «яблоки с яблоками». | |
| - У MTP нет отдельной draft-модели -> нет её накладных расходов на P40, поэтому 16.7 t/s. | |
| - Короткий MTP-замер (1/3 = 0.333) статистически мусорный: 1 шаг спека, 3 токена. | |
| ### Тест 3. Короткий промпт (29 токенов, n_predict=60) - DFlash vs MTP | |
| | режим | draft acceptance | mean len | tok/s | | |
| |---|---|---|---| | |
| | DFlash с кольцом (n_max=7) | **0.64865** (48/74) | 5.36 | 24.09 | | |
| | MTP (n_max=3) | 0.33333 (1/3) | 2.00 | 12.63 | | |
| -> Короткий MTP-замер статистически крошечный (3 токена, 1 принят) - не показатель. | |
| По длинному тесту MTP быстрее и с лучшим acceptance, но при меньшем n_max. | |
| ### Тест 4. Тот же промт на ОРИГИНАЛЬНОМ Luce dflash_server (референс) | |
| Запуск: `DFLASH_SINGLE_CHAIN_CHECKPOINT_F32=1 DFLASH_FAST_ROLLBACK_THRESHOLD=1 | |
| LUCE_Q8_MEMO=1 DFLASH_KV_ROTATE=0 ./server/build/dflash_server ... --fa-window 2048 | |
| --cache-type-k q8_0 --cache-type-v q8_0 --max-ctx 8192 --port 8011`. | |
| API - OpenAI-совместимый (`/v1/chat/completions`), НЕ `/completion`. | |
| ВАЖНО: с `reasoning: {effort: low}` Luce-спек НЕ работает: | |
| `[budget-hook] spec-decode close at committed=52/3050 (remaining=3050 <= hard_limit=4096)` | |
| - budget-hook Luce (hard_limit_reply_budget=4096 из model_card) при max_tokens <= 4096 | |
| force-close'ит на 1-м шаге и падает в AR: `[ar-decode] tokens=2837 13.58 tok/s`, | |
| `target_forwards=2` - acceptance неизмерим. Это НЕ кольцо, это их бюджет-хук. | |
| С `"thinking":{"type":"disabled"}` спек Luce работает: | |
| | режим | draft acceptance | avg_commit | steps | tok/s | | |
| |---|---|---|---|---| | |
| | **Luce** dflash_server (q_len=8, ring cap=4096) | **0.372** (2189/5880) | **2.98** | 735 | 15.69 | | |
| Luce ring активен: `[draft-kv] ctx-KV ring active: cap=4096 kv_total=4128 a_step=18 | |
| layers=5 f16 cache 80.6 MiB`. | |
| ### Сводная таблица (длинный тест, эссе, temp 0) | |
| | | DFlash llama.cpp кольцо (n_max=7) | MTP (n_max=3) | Luce (q_len=8) | | |
| |---|---|---|---| | |
| | draft acceptance | 0.24827 (1943/7826) | 0.48934 (1813/3705) | **0.372** (2189/5880) | | |
| | mean len / avg_commit | 2.74 | 2.47 | **2.98** | | |
| | tok/s | ~11.7 | **16.7** | 15.69 | | |
| | draft-KV | 47.81 MiB | — | 80.6 MiB f16 | | |
| Оговорки: длины цепочек разные (7/3/8) - acceptance не сопоставим напрямую; | |
| Luce мерился без thinking (с reasoning on его спек убит budget-hook'ом); | |
| по avg_commit Luce (2.98) > DFlash llama.cpp (2.74) > MTP (2.47). | |
| ### Тест 5. ОДИНАКОВЫЙ промт на Luce vs llama.cpp кольцо (Count 1-500, temp 0, без thinking) | |
| Одинаковый промт, одинаковый объём - честное сравнение реализации. | |
| | | Luce (q_len=8) | llama.cpp кольцо (n_max=7) | | |
| |---|---|---| | |
| | токенов | 1892 | 1946 | | |
| | tok/s | **42.42** | 33.92 | | |
| | draft acceptance | **99.8%** (1892/1896) | 97.4% (1697/1743) | | |
| | avg_commit / mean len | **7.98** | 7.82 | | |
| -> Разница 42.4 vs 33.9 = 1.25x, НЕ 2x. Старые «39 vs 24» были РАЗНЫЕ промты: | |
| 24 tok/s = короткое эссе с reasoning (acceptance 0.65), 39 = счёт (acceptance 0.91). | |
| На одинаковом контенте llama.cpp отстаёт на 25% из-за: цепочки 7 вместо 8, | |
| обычного replay вместо fast-rollback, отсутствия fused-проекций для Pascal. | |
| На счёте acceptance у обоих ~97-99%, поэтому разница = чистая цена имплементации. | |
| Короткий счёт на Luce (51 токен): 38.90 tok/s, acceptance 91.1%, avg_commit 7.29. | |
| ### Тест 6. КОРЕНЬ разницы: MMVQ на батче 8 (Pascal) | |
| Профилирование (nsys + DEBUG_TIMINGS + DBG-логи n_tokens/n_seq_tokens): | |
| - verify-батч = 8 токенов (подтверждено логом n_tokens_all=8), НЕ 1. | |
| - nsys: 67-74% времени verify - `mul_mat_vec_q` (по-токенные MMVQ матмулы), | |
| батчевые `mul_mat_q` ~8%. fused `gated_delta_net_cuda` всего 2.6%. | |
| - ПРИЧИНА: `ggml/src/ggml-cuda/mmvq.cuh:3` | |
| `#define MMVQ_MAX_BATCH_SIZE 8` - на Pascal (не CDNA) `should_use_mmvq` | |
| возвращает `ne11 <= MMVQ_MAX_BATCH_SIZE` => батч РОВНО 8 -> ВСЕ квантованные | |
| матмулы идут через медленный по-токенный MMVQ вместо батчевого MMQ. | |
| - FIX (временный, в коде): `MMVQ_MAX_BATCH_SIZE_CFG 7` в mmvq.cuh + mmvq.cu:336 | |
| (батч 8 -> MMQ). Итог на счёте 1-500 (temp 0, без thinking): | |
| | | до фикса (MMVQ@8) | после (MMQ@8) | Luce | | |
| |---|---|---|---| | |
| | t_decode (verify) | 196 ms | 169 ms | 161 ms | | |
| | tok/s | 34.13 | 36.81 | 42.42 | | |
| | draft acceptance | 0.97361 | 0.91903 | 0.998 | | |
| | mean len | 7.82 | 7.43 | 7.98 | | |
| - verify почти догнал Luce (169 vs 161ms). Остаток 13% (36.8 vs 42.4) - | |
| acceptance: Luce 99.8% vs llama.cpp 91.9-97.4% (MMQ менее точен на границе | |
| argmax). Это отдельная задача (точность verify-логитов/селектора), не скорость kernel. | |
| - ВНИМАНИЕ: MMVQ_MAX_BATCH_SIZE_CFG=7 - экспериментальный фикс в коде ggml. | |
| Проверить на реальном промте качество (acceptance упал с 0.974 до 0.919). | |
| - Короткий счёт (Count 1-20, 92 токена): MMVQ@8 28.87 tok/s -> MMQ@8 34.71 tok/s | |
| (+20%), но acceptance 0.868 vs 0.796 - тоже упал. Вывод: скорость ядра (verify) | |
| догнали, остаток - acceptance (MMQ менее точен на границе argmax). | |
| ### Тест 7. ПРАВИЛЬНЫЙ перенос: MMQ по типам НЕ работает, ключ - консистентность | |
| Попытка разделить MMQ-порог по типу кванта: q8_0 (драфт) -> MMVQ (точный), | |
| K-кванты target (Q4_K/Q5_K/Q6_K) -> MMQ (быстрый). Результат НАМНОГО ХУЖЕ: | |
| | конфиг | t_decode | acceptance | tok/s | | |
| |---|---|---|---| | |
| | MMVQ все (baseline) | 196 ms | 0.97361 | 34.13 | | |
| | MMQ все (cap 7) | 169 ms | 0.91903 | 36.81 | | |
| | MMQ только K-кванты (q8_0->MMVQ) | 164 ms | **0.83934** | 35.55 | | |
| -> Причина: acceptance зависит НЕ от «точности» одного пути, а от КОНСИСТЕНТНОСТИ | |
| драфт и verify. Когда драфт (q8_0, MMVQ) и verify (target, MMQ) считают по-разному, | |
| argmax на границе РАСХОДИТСЯ -> acceptance падает сильнее, чем у любого единого пути. | |
| Правило: драфт и verify должны идти через ОДИН и тот же матмул-путь (оба MMVQ или | |
| оба MMQ). Полный MMQ (cap 7) - лучший компромисс: verify 196->169ms, tok/s 34.1->36.9. | |
| Почему у Luce acceptance 99.8% а у нас 91.9%: у Luce verify использует GPU argmax | |
| (ggml_argmax в графе, перенос на CPU только 8xint32) + их драфт и verify численно | |
| консистентны (свои kernels, F32 lm_head). У llama.cpp verify читает ПОЛНЫЕ logits | |
| (8x248320 float = 8MB) на CPU каждый шаг + CPU-argmax + MMQ на границе argmax | |
| менее точен, чем MMVQ (0.919 vs 0.974 при едином пути). Остаток 13% = | |
| acceptance (7.43 vs 7.98 токенов/шаг) + цена шага (verify 169 vs 161ms, | |
| post_decode+sampl 7.6ms CPU-чтение logits у нас, у Luce нет). | |
| Что нужно для полного паритета: | |
| 1. GPU-argmax в verify (как Luce): ggml_argmax в графе, перенос 8xint32 вместо 8MB. | |
| 2. Консистентность: драфт и verify на одном пути (F32 lm_head + GPU argmax). | |
| ### Тест 8. ФИНАЛЬНАЯ оптимизация: MMQ cap 7 + backend sampling (-bs) = 37.66 tok/s | |
| Ключевые открытия: | |
| - Через chat API (честное сравнение) MMQ cap 7 ДАЁТ ВЫШЕ acceptance, чем MMVQ: | |
| baseline cap 8 (MMVQ) 0.843 vs cap 7 (MMQ) 0.919! Прошлые выводы «MMQ менее | |
| точен» были испорчены сравнением через РАЗНЫЕ API (/completion 0.974 vs chat). | |
| - llama.cpp УЖЕ имеет GPU-argmax семплер: `llama_sampler_greedy_backend_apply` | |
| (llama-sampler.cpp:1086 ggml_argmax). Флаг сервера `-bs` / `--backend-sampling` | |
| (arg.cpp:2296) переводит сэмплинг на GPU -> verify-логиты НЕ читаются на CPU. | |
| - t_sampl 2.07 -> 0.03ms, t_post_decode 5.7 -> 0.15ms (не читаем 8MB logits/шаг!). | |
| Итоговые цифры (chat API, Count 1-500, temp 0): | |
| | конфиг | t_decode | t_post+sampl | acceptance | tok/s | | |
| |---|---|---|---|---| | |
| | baseline (MMVQ cap 8) | 196 ms | 7.8 ms | 0.843 | 30.20 | | |
| | MMQ cap 7 | 169 ms | 7.8 ms | 0.919 | 36.92 | | |
| | **MMQ cap 7 + -bs** | 171 ms | **0.18 ms** | **0.919** | **37.66** | | |
| | Luce | 161 ms | ~0 | 0.998 | 42.42 | | |
| Короткий счёт (Count 1-20): MMQ+bs 35.80 tok/s, acceptance 0.868 (было 34.71/0.868). | |
| Итого выжали 30.2 -> 37.7 tok/s (+25%) на том же промте. Остаток до Luce: | |
| acceptance (0.919 vs 0.998 - MMQ на границе argmax) + их fused-граф verify (171 vs 161ms). | |
| Команда запуска: ... --spec-draft-ctx 4096 --temp 0 --jinja -bs | |
| ### Тест 9. Python-код (сложная задача) - DFlash vs ngram режимы | |
| Промт: "Write a Python class implementing a concurrent LRU cache with TTL support...": | |
| class+type hints+docstrings, lock-free variant, tradeoffs. n_predict 4096, temp 0, chat API. | |
| | режим | draft acceptance | mean len | tok/s | verify | примечание | | |
| |---|---|---|---|---|---| | |
| | **DFlash cap7+bs** | 0.25271 (2615/10348) | 2.77 | **13.89** | 171ms | лучший на коде | | |
| | ngram-mod (match24/min48/max64) | — (0 drafts!) | — | 12.90 | 77.6ms | НЕ генерировал: #gen drafts=0 | | |
| | ngram-map-k4v (n8/m24/minhits1) | 0.06591 (29/440) | 2.32 | 12.57 | 77.6ms | 440 drafts за 4043 вызова | | |
| | AR (без спека) | — | — | ~13.2 | 75.9ms | база | | |
| Выводы: | |
| - На КОДЕ DFlash выигрывает у ngram и AR лишь ~5-8% (13.89 vs 12.6-13.2): | |
| код менее предсказуем, чем счёт (acceptance 0.25 vs 0.92 на числах). | |
| - ngram-режимы на коде почти бесполезны: ngram ловит ПОВТОРЯЮЩИЙСЯ текст, | |
| а код не повторяется. ngram-mod вообще дал 0 драфтов (n_match=24 слишком длинный | |
| для кода + n_min=48), ngram-map-k4v - 1% acceptance. | |
| - Сравнение на счёте (Count 1-500): DFlash 37.66 tok/s vs ngram - не тестировался, | |
| но ngram на числах тоже работал бы плохо (нет повторов). | |
| - DFlash силён на: числах/JSON/структурном тексте. Слаб на: свободном коде/прозе. | |
| ### Тест 10. Python-код: DFlash vs MTP vs Luce (тот же промт) | |
| Тот же промт LRU cache, n_predict 4096, temp 0. MTP вшит в GGUF (без -md). | |
| | режим | acceptance | mean len / avg_commit | tok/s | verify | | |
| |---|---|---|---|---| | |
| | **Luce** (q_len=8) | **0.709** (3591/5064) | **5.67** | **29.81** | 161ms | | |
| | **MTP** llama.cpp (n_max=3) | 0.483 (2423/5016) | 2.45 | **16.24** | ~122ms | | |
| | DFlash cap7+bs (n_max=7) | 0.25271 (2615/10348) | 2.77 | 13.89 | 171ms | | |
| | ngram-map-k4v | 0.0659 | 2.32 | 12.57 | 77.6ms | | |
| | AR (без спека) | — | — | ~13.2 | 75.9ms | | |
| ПОЧЕМУ DFlash просел на коде (0.25 acceptance, 13.89 tok/s): | |
| - DFlash чертит ЦЕПОЧКУ из 7 токенов подряд. На числах угадать след. число | |
| легко (acceptance 0.92), на коде (свободная генерация) 7 токенов подряд | |
| почти никогда не совпадают -> acceptance 0.25, спек ~бесполезен (13.89 ~ AR 13.2). | |
| - MTP (3 токена) с acceptance 0.48 выигрывает: короткая цепочка чаще проходит | |
| целиком -> 16.24 tok/s. | |
| - Luce 29.81 tok/s - их fused-граф verify (161ms) + GPU argmax + их own kernels: | |
| тот же драфт, но verify в 1.4x быстрее и acceptance 0.709 (их численно | |
| консистентный путь). Разница DFlash llama.cpp vs Luce НА КОДЕ огромна (13.9 vs | |
| 29.8 = 2.1x) - не из-за кольца, а из-за скорости/точности verify-графа. | |
| - Вывод: DFlash llama.cpp хорош на структурном тексте (37.7 tok/s на счёте), | |
| но на коде проигрывает MTP и сильно Luce. | |
| ### Тест 11. ⭐ НАЙДЕНА ПРИЧИНА "коллапса" на коде: режим reasoning, а НЕ порт | |
| **Вывод: перенос DFlash2 в llama.cpp был корректен. Разница 0.25 vs 0.71 на коде — | |
| артефакт режима thinking (llama.cpp сервер по умолчанию `--reasoning on`, Luce | |
| тестировался с thinking disabled).** | |
| Одинаковый бинарь, тот же LRU-промт, n_predict 4096, temp 0, MMQ cap 7 + -bs: | |
| | режим | acceptance | mean len | acc per pos | tok/s | | |
| |---|---|---|---|---| | |
| | llama.cpp DFlash, **thinking ON** (старое) | 0.25271 | 2.77 | (0.720, 0.468, 0.295, 0.178, 0.115, 0.060, 0.034) | 13.89 | | |
| | llama.cpp DFlash, **--reasoning off** (MMVQ cap 8) | 0.64238 (3350/5215) | 5.50 | (0.919, 0.804, 0.690, 0.607, 0.546, 0.497, 0.434) | 23.41 | | |
| | **llama.cpp DFlash, --reasoning off + MMQ cap 7 (ФИНАЛ)** | **0.64724** (3354/5182) | **5.53** | **(0.914, 0.810, 0.710, 0.632, 0.548, 0.479, 0.435)** | **27.69** | | |
| | Luce DFlash (thinking disabled) | 0.709 (3591/5064) | 5.67 | (плоская) | 29.81 | | |
| Что произошло: | |
| - С thinking ON модель пишет `<think>`-блоки: внутренние рассуждения | |
| непредсказуемы, цепочка драфта экспоненциально затухает | |
| (0.72, 0.47, 0.30, ...) — это НЕ дефект драфтера, а свойства текста. | |
| - С thinking OFF кривая плоская (0.92, 0.80, 0.69, ...), acceptance 0.64, | |
| скорость 23.4 tok/s. Разрыв с Luce (0.709 / 29.81) теперь малый: | |
| acceptance 90%, скорость 78%. | |
| Дополнительно (тот же бинарь, thinking off): | |
| - Count 1-500 (финал, MMQ cap 7): acceptance **0.99759**, mean len 7.98, | |
| **40.41 tok/s** (Luce: 0.998 / 7.98 / 42.42 — acceptance равен, скорость 95%). | |
| - Проза (эссе 3050, финал MMQ cap 7): acceptance 0.288, mean 3.02, | |
| 15.27 tok/s — Luce на прозе ~0.372 / 15.69 (с thinking low): свободная | |
| проза тяжела для обоих, это не регресс порта (acceptance 78%, скорость 97%). | |
| Опровергнутые гипотезы (все проверены замером, все no-op): | |
| 1. **seq_rm-стирание фич** (ckpt.pos_max+1 vs pos_next()): бит-в-бит | |
| одинаковый acceptance (0.26731 vs 0.25271, кривая acc-per-pos идентична до | |
| 3 знака). Причина: update_pos() обновляет ckpt.pos_max на каждое шаге, | |
| ckpt.pos_max+1 == pos_next() в нормальном потоке; checkpoint | |
| (update_dft/load_dft) обновляется каждый шаг, а не раз в 512. | |
| 2. **Кольцо окна** (--spec-draft-ctx 0/4096): одинаково. | |
| 3. **Математика цепочки** (unary raw vs log-prob, порядок mul_mat, | |
| transposed weights): тождественна Luce (score = cand_lp + Σ pr·hp·sc, | |
| prev_row = выбранный кандидат). hproj [hdim,rank], pred/succ [rank,vocab] | |
| — одинаковые shape в обоих загрузчиках. | |
| 4. **K/V контекста драфта**: llama.cpp embd-путь делает ровно shallow-инъекцию | |
| (wk/wv @ fused + k_norm + rope) как Luce draft_ctx_kv_rows. | |
| 5. **Фичи таргета**: t_layer_inp[il+1] = post-layer hidden (qwen35.cpp:246) = | |
| то же, что Luce capture (cur после FFN+residual). | |
| Код сейчас: MMQ_MAX_BATCH_SIZE_CFG=7 (MMQ на батче 8, P40), -bs, seq_rm | |
| возвращён к оригиналу (ckpt.pos_max+1) с поясняющим комментарием. | |
| ## Test 12 — DFlash vs MTP, thinking ON/OFF (код LRU, 4 ячейки) | |
| Замер: тот же LRU-промпт (Python-класс кэша, 4096 токенов, temp 0). | |
| DFlash-n7: `--spec-type draft-dflash --spec-draft-n-max 7` + `-md Qwen3.8-27B-DFlash2-lucebox-q8_0.gguf`. | |
| MTP-n3: `--spec-type draft-mtp --spec-draft-n-max 3`, MTP-слои из таргет-GGUF. | |
| Thinking ON = дефолт (chat template), OFF = `--reasoning off`. | |
| | Конфиг | Thinking | Acceptance | Mean len | tok/s | | |
| |---|---|---|---|---| | |
| | DFlash-n7 | OFF | 0.647 | 5.53 | **27.69** | | |
| | DFlash-n7 | ON | 0.25271 | 2.77 | 13.90 | | |
| | MTP-n3 | OFF | 0.8205 | 3.46 | **23.61** | | |
| | MTP-n3 | ON | 0.48452 | 2.45 | 16.31 | | |
| acc-per-pos (OFF/ON): | |
| - DFlash-n7: OFF (0.914, 0.810, 0.710, 0.632, 0.548, 0.479, 0.435) | |
| ON (0.695, 0.434, 0.283, 0.170, 0.098, 0.055, 0.034) | |
| - MTP-n3: OFF (0.931, 0.816, 0.715) | |
| ON (0.706, 0.461, 0.286) | |
| Выводы: | |
| 1. **Thinking ON убивает оба**, но DFlash страдает сильнее: падение | |
| acceptance 0.647→0.253 (-61%), MTP 0.821→0.485 (-41%). Reasoning-текст | |
| непредсказуем для любой цепочки, длинная (7) рвётся быстрее короткой (3). | |
| 2. С thinking ON **по скорсти MTP (16.3) > DFlash (13.9)** — длинная цепочка | |
| платит verify 8 ради ~2.4 принятых. | |
| 3. С thinking OFF **по скорости DFlash (27.7) > MTP (23.6)** — цепочка живёт | |
| на коде, хвост 4-7 окупает verify. | |
| 4. Первые 3 позиции: MTP чуть точнее DFlash (0.931/0.816/0.715 vs | |
| 0.914/0.810/0.710) — DFlash декодит блок параллельно с масками и не видит | |
| собственные пики соседей, MTP поочередно видит свой предыдущий токен. | |
| 5. Итог для сценариев: | |
| - агент/код: DFlash-n7 + `--reasoning off` (27.7 t/s); | |
| - рерайт длинного текста: DFlash-n3 (~23 на коде, короче на прозе) или | |
| MTP-n3 — эквивалентны по форме, DFlash-n3 быстрее драфтит; | |
| - если нужен reasoning ON (дефолт) — MTP-n3 меньше теряет (16.3 vs 13.9), | |
| но оба сильно ниже than OFF. | |
| Комбо DFlash+MTP в одном сервере НЕ работает в этом билде: | |
| `--spec-type draft-dflash,draft-mtp` c -md падает ("MTP context requested but | |
| model doesn't contain MTP layers") — код создаёт ОДИН ctx_dft для всех типов и | |
| просит MTP-слои у DFlash-GGUF. Плюс даже при успехе первый-выигравший (MTP | |
| приоритетнее) сделал бы комбо = чистый MTP. Эффективное объединение = DFlash | |
| с коротким блоком (n_max=3), даёт кривую MTP (0.932/0.842/0.741) и ту же | |
| скорость (~23.3). | |
| ## Test 13 — Динамическая длина цепочки по margin селектора [DFLASH-DYNAMIC] | |
| Проблема: thinking ON рубит спекуляцию (acceptance 0.25, 13.9 t/s). Идея — | |
| не отключать reasoning, а дать цепочке самой резаться, когда селектор неуверен. | |
| ### A. Первая попытка: softmax-уверенность выбранного токена — ДЕКОРАТИВНА | |
| p_chosen = softmax(top-16 скоров); обрыв при p_chosen < p_min. | |
| На коде thinking ON: 14.56 t/s (+4.7%), проверено всё те же 6.8/7 токенов. | |
| Причина: скоры селектора ~сырые логиты (20-45), softmax по топ-16 би-модален — | |
| 378/378 пиков p>0.7. "Уверенно неправ": высокий score НЕ означает правильный | |
| токен на reasoning. Порог 0.2 не отделяет неуверенность. | |
| ### B. Правильный сигнал: margin (top1-top2) в лог-скорах — РАБОТАЕТ | |
| Margins реально колеблются: 36% пиков margin<0.3 ("монетка"), остальные >1.5. | |
| Обрыв: margin < p_min*3.0. n_min=1 (иначе короткие цепочки стираются). | |
| Подбор порога (код LRU 4096, thinking ON): | |
| | p_min (margin*3) | проверено/шаг | принято/шаг | tok/s | | |
| |---|---|---|---| | |
| | 0 (статика n7) | 7.4 | 2.77 | 13.90 | | |
| | 0.15 | 4.0 | 2.79 | 16.92 | | |
| | 0.2 | 3.8 | 2.64 | 16.00 | | |
| | 0.3 | 3.4 | 2.71 | 17.10 | | |
| | **0.35** | **3.1** | **2.83** | **18.26** | | |
| | 0.4 | 3.0 | 2.61 | 17.21 | | |
| Оптимум 0.35: +31% скорости от статики, acceptance вырос до 0.638 | |
| (не упал!), проверено всего 3.1/7. Это ВЫШЕ жёсткого n3 (17.30) и MTP-n3 | |
| (16.31). Динамика умнее жёсткой обрезки: где селектор уверен — цепочка | |
| идёт дальше, где колеблется — рвётся. | |
| ### C. Проза (эссе 3050, thinking ON) — главный выигрыш | |
| | Режим | acceptance | mean len | tok/s | | |
| |---|---|---|---| | |
| | Статика n7 | 0.288 | 3.02 | 15.27 | | |
| | **Динамика p_min 0.35** | **0.769** | **4.07** | **24.61** | | |
| acceptance x2.7, скорость +61%. Проза больше НЕ провал DFlash — динамика | |
| решает "почему на прозе мало": цепочка рвётся ровно там, где селектор | |
| колеблется, и не платит за мусорный хвост verify. | |
| ### Режимы (итог) | |
| - кодинг, без reasoning: DFlash-n7 + `--reasoning off` → 27.7 t/s | |
| - **с мышлением (дефолт): DFlash-n7 + p_min 0.35 → 18.26 (код) / 24.61 (проза)** | |
| - комбо не нужно: динамика сама адаптируется к тексту | |
| Код: common/speculative.cpp, цикл селектора DFlash2 — обрыв по margin | |
| (p_min*3.0), debug CHAIN печатает m= и p=. Сборка build-p40-ring. | |
| ## Test 14 — DFlash на Qwen3.6-35B-A3B (MoE) | |
| Пользовательская модель: Qwen_Qwen3.6-35B-A3B-Q4_K_M.gguf (qwen35moe, 3B активных | |
| из 35B). DFlash-драфта v2 под неё нет — сконвертирован официальный | |
| z-lab/Qwen3.6-35B-A3B-DFlash (DFlash v1, 6 dense-слоёв, hidden 2048, | |
| block_size 16, target_layer_ids [1,6,11,16,22,27,32,37] = 8 слоёв таргета, | |
| fc [2048,16384]). | |
| Конвертация (Luce server/scripts): | |
| ``` | |
| caмa: python3 convert_dflash_to_gguf.py draft-hf/model.safetensors draft-a3b-f16.gguf | |
| python3 quantize_dflash_draft.py draft-a3b-f16.gguf draft-a3b-q8_0.gguf --scheme q8_0 | |
| ``` | |
| Результат: draft-a3b-q8_0.gguf (410MB, 43 tensor q8_0 + 26 as-is). | |
| Прошла без правок кода форка — форк загрузил qwen35moe таргет + dflash v1 драфт. | |
| Замеры (P40, thinking ON, dynamic 0.35): | |
| | Тест | acceptance | mean len | tok/s | | |
| |---|---|---|---| | |
| | Код LRU 4096 | 0.584 | 3.60 | **55.79** | | |
| | Проза эссе 3050 | 0.483 | 2.65 | **46.51** | | |
| acc per pos (код): (0.813, 0.550, 0.402, 0.312, 0.228, 0.169, 0.128) | |
| acc per pos (проза): (0.751, 0.417, 0.221, 0.118, 0.076, 0.045, 0.022) | |
| Сравнение с 27B-UD: | |
| - код: 27B 27.69 t/s vs A3B **55.79** (x2.0) — MoE таргет дешевле в verify | |
| - проза: 27B динам. 24.61 vs A3B **46.51** (x1.9) | |
| Вывод: спекуляция на MoE-модели (A3B) в 2 раза эффективнее, чем на dense-27B, | |
| потому что verify-батч (8 токенов) считает только 3B активных параметров. | |
| DFlash-v1 (без селектора v2) тоже работает с dynamic-обрезанием (margin из | |
| top-k). Для полноты — был бы DFlash2-драфт под A3B, gains ещё выше. | |
| Команда (динамика 0.35): | |
| llama-server -m Qwen_Qwen3.6-35B-A3B-Q4_K_M.gguf -md draft-a3b-q8_0.gguf | |
| --spec-type draft-dflash --spec-draft-n-max 7 --spec-draft-n-min 1 --spec-draft-p-min 0.35 ... | |
| ## Test 15 — A3B: DFlash vs MTP vs AR (полная таблица) | |
| P40, thinking ON, MoE-таргет Qwen3.6-35B-A3B-Q4_K_M (3B активных). | |
| DFlash = наша конвертация z-lab v1 + dynamic 0.35; MTP = вшит в таргет-GGUF. | |
| Код LRU 4096: | |
| | Режим | acceptance | mean | tok/s | vs AR | | |
| |---|---|---|---|---| | |
| | AR baseline | - | - | 52.05 | 1.00x | | |
| | DFlash n7 dyn 0.35 | 0.584 | 3.60 | 55.79 | 1.07x | | |
| | DFlash n12 dyn 0.35 | 0.541 | 4.11 | 53.98 | 1.04x | | |
| | MTP вшит n3 | 0.662 | 2.99 | 68.37 | 1.31x | | |
| Проза эссе 3050: | |
| | Режим | acceptance | mean | tok/s | vs AR | | |
| |---|---|---|---|---| | |
| | AR baseline | - | - | 52.72 | 1.00x | | |
| | DFlash n7 dyn 0.35 | 0.483 | 2.65 | 46.51 | 0.88x | | |
| | MTP вшит | 0.497 | 2.49 | 57.61 | 1.09x | | |
| Выводы: | |
| 1. На A3B **MTP (вшит) — лучший режим**: код 68.4 (+31% AR), проза 57.6 (+9%). | |
| Дёшево: MTP-голова вшита в MoE-таргет, verify почти бесплатный. | |
| 2. DFlash v1 на A3B слабый: на коде +7% (55.8), на прозе МЕДЛЕННЕЕ AR (46.5 vs 52.7). | |
| v1 БЕЗ селектора (последоват. argmax) на свободном тексте не окупает даже | |
| дешёвый verify. Для A3B нужен DFlash2-драфт (с селектором), его нет нигде. | |
| 3. n_max=12 НЕ помогает: mean 4.11 выше, но tok/s 53.98 < n7 55.79 (блок 12 масок | |
| дороже драфтить, хвост 8-12 не окупается). Оптимум n7. | |
| 4. Пользователю: на 35B-A3B лучше всего собственный MTP (без -md, без драфта) — | |
| `--spec-type draft-mtp --spec-draft-n-max 3`. DFlash-драфт v1 держать как | |
| запасной для кода, где чуть быстрее AR (55.8 vs 52.0). | |
| Вопрос "динамика для MTP": стандартный флаг УЖЕ есть — impl_draft_mtp в | |
| speculative.cpp:1760 делает `if (cur_p->data[0].p < params.p_min) break;` — | |
| обрыв по вероятности токена (не по margin как DFlash). Работает через | |
| `--spec-draft-p-min`. Проверено по коду, не декоративно. | |
| ## Test 16 — MTP динамика (n_max=7 + p_min 0.3) на A3B — ДЕКОРАТИВНА | |
| Проверка: даёт ли MTP сам себе длину через --spec-draft-p-min (обрыв по | |
| вероятности токена в impl_draft_mtp, speculative.cpp:1760). Код LRU 4096: | |
| | Режим | acceptance | mean len | проверено/шаг | принято/шаг | tok/s | | |
| |---|---|---|---|---|---| | |
| | MTP n3 (base) | 0.662 | 2.99 | ~3.0 | 2.63 | 68.37 | | |
| | MTP n7 p_min0.3 | 0.454 | 3.67 | ~5.8 | 2.63 | 46.97 | | |
| Вывод: p_min 0.3 у MTP НЕ режет цепочку на коде — проверено 5.8/шаг вместо | |
| ~3.0, принято столько же (2.63). Причина та же, что у softmax-динамики DFlash: | |
| probability(top-1) у MTP почти всегда > 0.3 ("уверенно неправ" на позициях | |
| 3-7, где acceptance 0.31/0.25/0.18/0.14). Порог по p не отделяет мусорный | |
| хвост. | |
| Итог: для MTP жёсткий --spec-draft-n-max 3 оптимален (68.4 на A3B, 23.6 на | |
| 27B). Динамика по p бесполезна и для DFlash (margin - рабочая, p - нет), | |
| и для MTP (p не режет). Единственная реально работающая авто-длина - наш | |
| margin-обрыв в DFlash2 (Test 13). | |
| ## Test 17 — margin-динамика для MTP [DFLASH-DYNAMIC MTP] + сводная таблица режимов | |
| После Test 16 добавлен margin-обрыв и для MTP (speculative.cpp:1759-1774): | |
| вместо порога по p0 обрыв по **запасу p0-p1** (margin = p0, если один кандидат; | |
| margin = p0-p1 иначе; обрыв при margin < p_min). Порог берётся из | |
| --spec-draft-p-min, но применяется к ВЕРОЯТНОСТНОМУ запасу (0..1), а не к | |
| логам-скорам. n_min=1 обязателен, иначе короткие цепочки стираются. | |
| Замеры (A3B, код LRU 4096; сравнить: MTP-n3 base 68.37, n7 p_min0.3 46.97, | |
| n7 margin0.3 63.60, n7 margin0.15 58.15): | |
| | Режим | проверено/шаг | принято/шаг | acceptance | mean | tok/s | | |
| |---|---|---|---|---|---| | |
| | MTP n3 (жестко) | ~3.0 | 2.63 | 0.662 | 2.99 | 68.37 | | |
| | MTP n7 + p_min0.3 (старый p) | ~5.8 | 2.63 | 0.454 | 3.67 | 46.97 | | |
| | MTP n7 + margin0.15 | ~4.1 | 2.69 | 0.654 | 4.11 | 58.15 | | |
| | MTP n7 + margin0.3 | ~3.3 | 2.62 | **0.793** | 4.09 | 63.60 | | |
| | MTP n7 + margin0.3 (проза) | - | 1.24 | 0.669 | 2.79 | 52.94 | | |
| Вывод по margin-MTP: в отличие от p-порога, margin РЕАЛЬНО режет цепочку | |
| (проверено 5.8 -> 3.3-4.1). На коде margin0.3 даёт acceptance 0.793 (против | |
| 0.662 у n3) при том же принятом — режет «монетки», тянет где уверен. НО на | |
| A3B жёсткое n3 всё равно быстрее (68.4 vs 63.6): декод блока n7 дороже, а | |
| дешёвый verify не окупает длинный хвост. Margin-MTP бессмысленен на A3B, где | |
| verify почти бесплатен; он ценен только для 27B (дорогой verify), где стоит | |
| проверить как 27B+MTP+margin0.3. | |
| ### СВОДНАЯ ТАБЛИЦА эффективных режимов (все цифры tok/s, temp 0) | |
| **Qwen3.8-27B-UD (dense, дорогой verify ~169ms/шаг).** | |
| | Режим | код | проза | примечание | | |
| |---|---|---|---| | |
| | AR (без спека) | ~13.2 | ~13.2 | база | | |
| | DFlash2 n7 статика | 13.90 | 15.27 | хвост 4-7 мусор на reasoning | | |
| | DFlash2 n7 + margin0.35 | **18.26** | **24.61** | лучшая авто-длина (+31%/+61%) | | |
| | DFlash2 + --reasoning off | 27.69 | - | код без мышления, жёсткий n7 | | |
| | MTP n3 (off/on) | 23.61 / 16.31 | - | вшит, дёшево; on меньше теряет | | |
| | DFlash2 Count-500 | 40.41 | - | структурный текст (числа) | | |
| **Qwen3.6-35B-A3B (MoE, 3B активных, дешёвый verify).** | |
| | Режим | код | проза | vs AR код | | |
| |---|---|---|---| | |
| | AR (без спека) | 52.05 | 52.72 | 1.00x | | |
| | DFlash v1 n7 + dyn0.35 | 55.79 | 46.51 | 1.07x | | |
| | DFlash v1 n12 + dyn0.35 | 53.98 | - | 1.04x (блок 12 не окупается) | | |
| | MTP вшит n3 | **68.37** | **57.61** | 1.31x — ЛУЧШИЙ на A3B | | |
| | MTP n7 + margin0.3 | 63.60 | 52.94 | acceptance 0.793, но медленнее n3 | | |
| | MTP n7 + p0.3 (декор) | 46.97 | - | p не режет | | |
| ### Итоговые рекомендации | |
| - **A3B**: `--spec-type draft-mtp --spec-draft-n-max 3` (без p_min, без margin) = | |
| код 68.4 / проза 57.6. verify дёшев, жёсткое короткое n3 — оптимум. | |
| - **27B dense**: `--spec-type draft-dflash --spec-draft-n-max 7 --spec-draft-n-min 1 | |
| --spec-draft-p-min 0.35` = код 18.26 / проза 24.61 (margin-динамика, Test 13). | |
| Без мышления: `--reasoning off` -> 27.7 на коде. | |
| - **Куда копать дальше**: 27B + MTP + margin (тест не гонялся; у 27B дорогой | |
| verify, длинная точная MTP-цепочка могла бы дать больше, чем жёсткое n3). | |
| ## Test 18 — 27B + MTP + margin 0.3 (reasoning ON) — margin-MTP впервые РАБОТАЕТ | |
| Проверка единственного неиспытанного режима: 27B (дорогой verify) + MTP-n7 + | |
| margin (вероятностный запас p0-p1, --spec-draft-p-min 0.3). Код LRU, temp 0, | |
| reasoning ON (дефолт). Сравнение с Test 12/13 (тот же промт): | |
| | Режим (27B, reasoning ON, код LRU) | acceptance | tok/s | | |
| |---|---|---| | |
| | MTP-n3 жёстко (Test 12) | 0.485 | 16.31 | | |
| | **MTP-n7 + margin0.3 (Test 18)** | **0.592** | **17.81** | | |
| | DFlash2-n7 статика (Test 13A) | 0.253 | 13.90 | | |
| | DFlash2-n7 + margin0.35 (Test 13B) | 0.638 | **18.26** | | |
| Замер: микробенч 512ток = 16.71 t/s (draft 464, accept 274/0.591); полный | |
| 4096ток = 17.81 t/s (draft 4259, accept 2522/0.592). | |
| Вывод: | |
| 1. **margin-MTP впервые даёт реальный прирост на 27B**: +9.2% к жёсткому | |
| MTP-n3 (17.81 vs 16.31), acceptance вырос 0.485 -> 0.592. Причина: на A3B | |
| MTP-голова не тянет дальше 2-3 и verify дёшев (нечего экономить), а на 27B | |
| дорогой verify окупает длинную уверенную цепочку — margin режет «монетки» | |
| и тянет где уверен. margin-MTP бессмыслен на A3B, но полезен на 27B. | |
| 2. НО DFlash2+margin0.35 (18.26) всё ещё впереди MTP+margin (17.81) на 2.5%: | |
| DFlash2-селектор даёт более точную длинную цепочку, чем MTP-голова на коде. | |
| 3. Итог для 27B код (reasoning ON): best = DFlash2+margin0.35 (18.26), | |
| второй = MTP+n7+margin0.3 (17.81), оба бьют жёсткое n3 (16.31). | |
| Команда (27B + MTP + margin): | |
| llama-server -m Qwen3.8-27B-UD-Q4_K_XL.gguf | |
| --spec-type draft-mtp --spec-draft-n-max 7 --spec-draft-n-min 1 --spec-draft-p-min 0.3 | |
| (reasoning ON, без -md; margin из --spec-draft-p-min применяется к p0-p1 | |
| по коду speculative.cpp:1759-1774) | |
| ## Test 19 — Luce dflash_server + DDTree (прямой тест движка на P40) | |
| Проверка самого Luce-движка (./server/build/dflash_server с --ddtree) на | |
| твоих моделях, вместо разговоров о переносе. Обе модели = arch qwen35/qwen35moe | |
| (принимаются gguf_target_loader). Обе на 40 P40. LRU-код, temp 0, thinking off. | |
| ### 19A. Dense 27B (Qwen3.8-27B-UD + DFlash2-lucebox) — РАБОТАЕТ, рекорд | |
| Команда: dflash_server Qwen3.8-27B-UD-Q4_K_XL.gguf --draft Qwen3.8-27B-DFlash2-q8_0.gguf | |
| --ddtree --ddtree-budget 22 --fa-window 2048 -ctk q8_0 -ctv q8_0 --max-ctx 8192 | |
| Драфт DFlash2-селектор сработал: [draft GGUF] DFlash 2 selector enabled rank=256 top_k=16. | |
| LRU 512 ток: 19.48 t/s, avg_commit 5.33, acceptance 54.2%. | |
| LRU 4096 ток: 21.46 t/s, avg_commit 5.97, acceptance 62.1%, 686 verify-steps. | |
| | Режим (27B dense, код LRU 4096, thinking off) | tok/s | avg_commit/AL | acceptance | | |
| |---|---|---|---| | |
| | llama.cpp AR | ~13.2 | - | - | | |
| | llama.cpp DFlash2 статика n7 | 13.90 | 2.77 | 0.253 | | |
| | llama.cpp DFlash2 + margin0.35 | 18.26 | ~3.1 | 0.638 | | |
| | **Luce DFlash2 + DDTree budget22** | **21.46** | **5.97** | **0.621** | | |
| Вывод: Lucе DDTree на P40 даёт +17.5% к лучшему llama.cpp маржину (21.46 vs 18.26), | |
| +63% к AR. Ключ — ДЕРЕВО: avg_commit 5.97 (почти 6 токенов/шаг) против ~3 у цепочки. | |
| Подтверждает info.md: DDTree поднимает AL за счёт спасения ветвей (древовидного verify). | |
| Prefill small: 43 ток за 1.6с (26 tok/s — kernel-launch dominated, не показательно). | |
| ### 19B. A3B MoE (Qwen3.6-35B-A3B + draft-a3b-q8_0) — НЕСОВМЕСТИМ, 0.7% acceptance | |
| Команда: dflash_server Qwen_Qwen3.6-35B-A3B-Q4_K_M.gguf --draft draft-a3b-q8_0.gguf | |
| --ddtree --ddtree-budget 22 ... | |
| Лог: [draft] drafter target_layer_ids invalid (n=8, slots=5); keeping derived capture layers | |
| LRU 512 ток: 11.05 t/s, avg_commit 1.12, acceptance 0.7% (54/7328) — спек сломан. | |
| Вывод: наш конвертированный draft-a3b (DFlash v1, 8 target_layers, 6 dense-слоёв) | |
| НЕ совместим с Lucе DDTree, который жёстко ждёт 5-слойный z-lab драфт (target_layer_ids=5). | |
| Поэтому Lucе A3B даёт 11 t/s (почти AR без выигрыша). Для A3B лучший режим остаётся | |
| llama.cpp MTP-n3 (68.37 код). НЕ переносить DDTree-сервер на A3B с этим драфтом. | |
| ### Итог Test 19 | |
| - DDTree-дерево реально выигрывает на dense 27B (+17.5% к маржину, AL ~6 vs ~3) — | |
| это главный незадействованный потенциал. Стоит перенести ДЕРЕВО (ddtree.{h,cpp} + | |
| tree-verify) в llama-dflash3-pr порт, а не standalone-сервер. | |
| - На A3B Lucе бессмысленен (драфт несовместим); A3B = MTP-n3 на llama.cpp. | |
| ## Test 20 — Lucе DFlash2 CHAIN (без DDTree) — цепочка быстрее дерева на коде! | |
| КОНТРОЛЬНЫЙ эксперимент: тот же Lucе 27B + DFlash2, но БЕЗ --ddtree (чистая | |
| цепочка), чтобы отделить вклад дерева от вклада самого Lucе-движка. Код LRU, | |
| temp 0, thinking off, 4096 (модель сама остановилась на 3219, finish=stop). | |
| | Режим (Luce 27B, код LRU) | tok/s | avg_commit | acceptance | steps | | |
| |---|---|---|---|---| | |
| | Lucе DFlash2 CHAIN (no ddtree) | **32.02** | **6.09** | **0.761** | 529 | | |
| | Lucе DFlash2 + DDTree budget22 | 21.46 | 5.97 | 0.621 | 686 | | |
| | llama.cpp DFlash2 + margin0.35 (thinking ON) | 18.26 | ~3.1 | 0.638 | - | | |
| ШОКИРУЮЩИЙ вывод: на КОДЕ (высоко-предсказуемом) Lucе CHAIN (32 t/s) в 1.5x | |
| БЫСТРЕЕ дерева (21.46)! Причина: на структурном тексте цепочка длиной ~6 уже | |
| почти вся принимается (acc 76%, avg_commit 6.09), а дерево тратит budget на | |
| ветвления, которые не окупаются. Дерево выигрывает на НЕпредсказуемом тексте, | |
| где ветвления спасают принятие; на коде это лишние вызовы. | |
| Также Lucе chain (32) >> наш llama.cpp маржин (18.26): разрыв 1.7x. НО это | |
| МИШАС сравнения: наш маржин был с thinking ON, Lucе — thinking off | |
| (наш llama.cpp DFlash2 n7 + --reasoning off давал 27.69, Test 12). Нужен | |
| apples-to-apples: наш DFlash2 chain с thinking OFF vs Lucе chain 32. | |
| Lucе цепь уже умеет адаптивность через --verify-width (trimmed per step by | |
| drafter confidence) - аналог нашего margin. --ddtree-tau - для дерева. | |
| Итог: для кода лучший режим у Lucе - CHAIN (32 t/s), НЕ дерево. Дерево стоит | |
| тестить на прозе/непредсказуемом, где acceptance ниже и ветвления окупаются. | |
| ## Test 21 — ЧЕСТНОЕ сравнение: наш llama.cpp DFlash2 margin vs Lucе (thinking OFF) | |
| Исправление мишаса Test 20: наш маржин был с thinking ON (18.26), Lucе - off. | |
| Прогон нашего llama.cpp DFlash2 + margin 0.35 + --reasoning off на том же | |
| коде-промпте (модель сама останавливается: у нас 2405 ток., у Lucе 3219; тот | |
| же промпт, скорость валидна на 2-3K ток.). | |
| | Режим (27B, код LRU, thinking OFF) | tok/s | acceptance | steps | | |
| |---|---|---|---| | |
| | **наш llama.cpp DFlash2 + margin0.35** | **29.86** | **0.850** | - | | |
| | Lucе DFlash2 chain | 32.02 | 0.761 | 529 | | |
| | Lucе DFlash2 + DDTree budget22 | 21.46 | 0.621 | 686 | | |
| ГЛАВНЫЙ ВЫВОД: разрыв на самом деле НЕ 1.7x, а всего ~7%! Наш llama.cpp маржин | |
| (29.86) почти догнал Lucе chain (32.02), и наш acceptance даже выше (0.85 vs | |
| 0.76). Прежние 18.26 были с thinking ON - нечестная база. При thinking OFF | |
| наш маржин - 29.86, на уровне Lucе. | |
| Разрыв 7% объясняется НЕ деревом, а имплементацией Lucе (fast-rollback + | |
| fused-граф verify + --verify-width confidence-trim), а не качеством драфта. | |
| DTree же на коде не помогает (не окупает ветвления), и даже медленнее. | |
| Дерево стоит тестить на прозе/непредсказуемом тексте, где acceptance низкий и | |
| ветвления реально спасают. На коде (структура) цепочка эффективнее. | |
| Далее: проверить DTree на прозе (где он теоретически выигрывает), и/или выжать | |
| оставшиеся 7% до Lucе (verify-width аналог, fast-rollback в наш порт). | |
| ## Test 22 — DTree на прозе НЕ выигрывает (и Lucе в целом проигрывает нашему маржину) | |
| Проверка гипотезы из Test 20/21: дерево должно спасать на непредсказуемом | |
| тексте (проза), где acceptance низкий. Lucе 27B + DDTree budget22, проза 1024, | |
| thinking off: | |
| | Режим (27B, проза 1024, thinking OFF) | tok/s | avg_commit | acceptance | | |
| |---|---|---|---| | |
| | Lucе DFlash2 chain | 11.97 | 3.29 | 0.287 | | |
| | Lucе DFlash2 + DDTree budget22 | 11.98 | 3.29 | 0.287 | | |
| | (сравнение) наш llama.cpp DFlash2 mar gin0.35, thinking ON (Test 13C) | 24.61 | 4.07 | 0.769 | | |
| ВЫВОД: на прозе Lucе DDTree и chain ИДЕНТИЧНЫ (~12 t/s, avg_commit 3.29, | |
| acceptance 28.7%) - дерево НЕ спасает. Причина: на свободном тексте acceptance | |
| этого DFlash2-драфта низкий на всех ветвях (28%), ветвления не окупают | |
| доп. декод. Lucе в целом на прозе (12 t/s) сильно проигрывает нашему llama.cpp | |
| маржину (24.61) - 2x. | |
| Итоговый вывод по всей линейке (Test 19-22): | |
| - НАШ llama.cpp DFlash2 + margin 0.35 - самый универсальный и сильный: | |
| код 29.86 (off) / 18.26 (on), проза 24.61 (on). Лучшая авто-длина. | |
| - Lucе chain: код 32.02 (off) - на 7% быстрее нашего, но проза 11.97 (off) - | |
| в 2x хуже нашего маржина. | |
| - Lucе DDTree: НЕ даёт выигрыша ни на коде (21.46 < chain 32), ни на прозе | |
| (11.98 = chain). Дерево для этого драфта/текстов неэффективно. | |
| - margin+дtree: бессмысленно - дерево само по себе не помогает нигде; | |
| наш margin (тop1-top2 в логах селектора) сильнее, чем ddtree-tau Lucе | |
| (кумулятивный log-prob) для этого DFlash2. | |
| Финал: остаётся выжать ~7% до Lucе chain на коде (fast-rollback / fused-verify) | |
| ИЛИ распространить наш маржин-подход на более длинные цепочки. Дерево не нужно. | |
| ## Test 23 — ЧЕСТНАЯ проза (thinking OFF): наш маржин обгоняет Lucе | |
| Устранение последнего мишаса: наш 24.61 на прозе был с thinking ON, а Lucе - | |
| с off (несопоставимо). Прогон нашего llama.cpp DFlash2 + margin 0.35, --reasoning | |
| off, тот же эссе-промпт, 1024 ток. | |
| | Режим (27B, проза 1024, thinking OFF) | tok/s | acceptance | | |
| |---|---|---| | |
| | **наш llama.cpp DFlash2 + margin0.35** | **16.38** | **0.505** | | |
| | Lucе DFlash2 chain | 11.97 | 0.287 | | |
| | Lucе DFlash2 + DDTree | 11.98 | 0.287 | | |
| Вывод: наш llama.cpp маржин на прозе (16.38) в 1.37x быстрее Lucе (11.97)! | |
| Наш acceptance тоже выше (0.505 vs 0.287). margin работает на прозе: режет | |
| мусорный хвост длинной цепочки, чего у Lucе нет (ucе уперся в 12 t/s). | |
| ОТВЕТ НА "ПОЧЕМУ МЫ НЕ ДОХОДИЛИ ДО 30": | |
| Мы ДОХОДИЛИ на коде! Наш DFlash2 margin = 29.86 (thinking OFF), просто раньше | |
| мерили с thinking ON (18.26) - дефолт. С --reasoning off наш порт даёт 29.86, | |
| почти как Lucе chain 32. Так что 30+ НЕ из-за Lucе-движка, а из-за отсутствия | |
| reasoning текста в промпте. | |
| ГДЕ Lucе-движок реально быстрее: только на коде +7% (32 vs 29.86) - цена | |
| fast-rollback + fused-verify + verify-width. На прозе наш маржин сильнее | |
| (16.38 vs 11.97) - Lucе-цепочка не умеет резать хвост как наш margin. | |
| Полная честная картина (thinking OFF): | |
| | | код | проза | | |
| |---|---|---| | |
| | наш llama.cpp DFlash2 + margin0.35 | **29.86** | **16.38** | | |
| | Lucе chain | 32.02 | 11.97 | | |
| | Lucе DDTree | 21.46 | 11.98 | | |
| Наш маржин универсальнее: код почти паритет (7% слабее), проза на 37% сильнее. | |
| ## Test 24 — ⚠️ ОШИБКА ИСПРАВЛЕНА: Lucе "thinking ON" был БЕЗ мышления | |
| Ранняя версия этого теста утверждала, что Lucе на 69% быстрее на reasoning. | |
| Это было НЕВЕРНО: мой запрос к Lucе с "thinking ON" на самом деле дал | |
| reasoning_tokens=0 (Lucе не включил мышление по умолчанию), т.е. это был тот | |
| же Lucе-БЕЗ-мышления, что и 32 t/s — просто короткая порция (1024 ток). | |
| Честный тест: Lucе с явным "thinking":{"type":"enabled"}: | |
| [budget-hook] spec-decode close at committed=41/1024 (remaining=1024 <= hard_limit=4096) | |
| reasoning_tokens=1, spec_decode_ran=True, decode 13.6 t/s (~AR) | |
| -> Lucе при реальном мышлении СПЕК УБИВАЕТСЯ budget-hook'ом (как в Test 4): | |
| force-close на раннем шаге, падает в AR (13.3 t/s). | |
| ЧЕСТНАЯ КАРТИНА (27B код, реальное мышление): | |
| | Режим | реально думал? | tok/s | | |
| |---|---|---| | |
| | наш llama.cpp DFlash2 + margin0.35 | ДА (reasoning_tokens>0) | 18.26 (4096) / 17.28 (1024) | | |
| | Lucе chain "thinking ON" (мой ошибочный) | НЕТ (reasoning_tokens=0) | 29.14 (это БЕЗ мышления) | | |
| | Lucе chain реально thinking enabled | бюджет-хук убил спек | 13.3 (~AR) | | |
| ГЛАВНЫЙ ВЫВОД: наш llama.cpp с реальным мышлением (18.26) БЫСТРЕЕ Lucе с | |
| реальным мышлением (13.3)! Прежний "разрыв 1.7x" в пользу Lucе был артефактом | |
| моей ошибки (сравнил Lucе-без-мышления с нашим-с-мышлением). | |
| resoning-текст ≈ проза по характеру (непредсказуем): наш маржин держит на нём | |
| нормально, а Lucе там вообще проигрывает (спек убит бюджетом). Переносить | |
| verify-width на основе этого теста НЕ нужно — мотивации больше нет. | |
| (Сохраняю модель arch: Qwen3.8-27B-UD = qwen35 ГИБРИД с Gated DeltaNet | |
| (qwen35.ssm.* веса, build_inp_mem_hybrid) - как Qwen3.5-27B из info.md. | |
| Значит persist-ядро DeltaNet и fast-rollback ПРИМЕНИМЫ к ней.) | |
| ## Test 25 — КВАНТОВАНИЕ ДРАФТА: Q4-mix ЛУЧШЕ Q8, Q2 чуть медленнее | |
| (код 1024, reasoning OFF, DFlash2 margin 0.35, наш порт llama.cpp, P40) | |
| Проблема: z-lab Q2_K GGUF (arch=dflash, маппинг mask_token через канон) | |
| не инициализируется у нас -> "invalid or missing mask_token_id", спек НЕ | |
| работает, чистый AR 13.13 t/s. Это ошибка ФОРМАТА (z-lab отличается от | |
| нашего qwen35-dflash-draft), не квантования. | |
| Решение: сквантовали ИЗ НАШЕГО f16 (convert_dflash_to_gguf.py из | |
| Qwen3.8-27B-DFlash2-src/model.safetensors) -> llama-quantize Q2_K (наш | |
| формат, mask корректен). И quantize_dflash_draft.py --scheme q4-mix. | |
| ПОЧЕМУ не через quantize_dflash_draft.py --scheme q2-k: в python-gguf | |
| (gguf.quants) реализовано КВАНТОВАНИЕ только для Q4_0/Q4_1/Q5_0/Q5_1/Q8_0/ | |
| BF16; для K-quants (Q2_K/Q3_K/Q4_K) есть ТОЛЬКО dequantize_blocks (чтение), | |
| quantize_blocks бросит NotImplementedError. Q2 доступен лишь через | |
| llama-quantize (C++). q4-mix работает потому что Q4_0 реализован в python. | |
| Результаты (код 1024, our DFlash2 margin 0.35, reasoning off): | |
| | Драфт | Размер | tok/s | acceptance | mean len | | |
| |-----------------|--------|-------|------------|----------| | |
| | Q8 (lucebox) | 2.0 GB | ~29-30| 0.85 | 5.27 | | |
| | Q4-mix (self) | 1.2 GB | 30.26 | 0.827 | 5.25 | | |
| | Q2_K (self) | 0.87GB | 27.08 | 0.763 | 4.69 | | |
| | Q2 z-lab (broken)| 0.7 GB| 13.13 | (спек off) | - | | |
| ВЫВОД: q4-mix = ЛУЧШИЙ (30.26 t/s > Q8, acceptance 0.827, 1.2GB). Q2_K | |
| приемлем (27 t/s, 865MB) только если критична VRAM. Q8 и Q4-mix почти не | |
| различаются по acceptance; Q2 теряет ~10% скорости из-за Q2 на head | |
| селектора. Для P40-скорости юзаем q4-mix. | |
| ## Test 26 — n_max SWEEP + DFlash2 блочная генерация (ВЫВОДЫ) | |
| q4mix-драфт, код 1024, reasoning OFF, DFlash2 margin p-min 0.35. | |
| n_max свип (tok/s): | |
| | n_max | tok/s | acceptance | mean | | |
| |-------|-------|------------|------| | |
| | 4 | 27.77 | 0.861 | 4.04 | | |
| | 5 | 27.41 | - | - | | |
| | 7 | 30.26 | 0.827 | 5.25 | | |
| | 8 | 30.18 | 0.827 | 5.25 | | |
| -> ОПТИМУМ n_max=7 (30.26). Уменьшение НЕ помогает (теряем длинные | |
| цепочки), 8 не лучше (маржин и так режет на ~5.25, блок стоит дорого). | |
| ВАЖНАЯ ПРАВКА (мой прошлый ошибка): наш DFlash2 НЕ последовательный. | |
| draft_dflash::draft() (speculative.cpp 1198+) строит ОДИН noise-блок | |
| (id_last + n_max масок) и ДЕКОДИТ ЕГО ОДНИМ llama_decode (1233) - это | |
| блочная диффузия, как PR 27342 "decode every sequence's full noise block | |
| in one pass". Квадратичный chain_heads-цикл (1710) принадлежит MTP, НЕ | |
| DFlash. Маржин обрезает ПРИНЯТУЮ часть ПОСЛЕ декода блока (по lattice, | |
| 1311-1316) - нельзя "не генерить лишние": блочная диффузия даёт уверены | |
| fnость позиции i только после декода всего блока (state i зависит от | |
| масок 0..i-1). Стоимость draft = стоимость декода n_max+1-блока, не | |
| зависит от маржина. | |
| bottleneck: draft-decode 86% (4199ms) vs verify 14% (657ms) на коде 1024. | |