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):
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.
Шумовой блок:
pq[i] = committed + i; // абсолютные позиции noise-блока (q_len штук)
1.4 Attention-маски (критично! отличается от наивного ролла)
mask_full (полное-внимание слои), dflash_draft_kv.cpp:280-294:
для каждого слота 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:
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)
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(archLLM_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-> plainbuild_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 windowcommon/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+):
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)
common/common.h- вcommon_params_speculative_draftдобавлено полеint32_t n_ctx = 0; // DFlash draft KV ring window (0 = full context, no ring).common/arg.cpp- добавлена опция--spec-draft-ctx N(envLLAMA_ARG_SPEC_DRAFT_CTX), пишет вparams.speculative.draft.n_ctx.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-ctxacceptance ~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%. fusedgated_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 нет).
Что нужно для полного паритета:
- GPU-argmax в verify (как Luce): ggml_argmax в графе, перенос 8xint32 вместо 8MB.
- Консистентность: драфт и 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):
- 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.
- Кольцо окна (--spec-draft-ctx 0/4096): одинаково.
- Математика цепочки (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 в обоих загрузчиках.
- K/V контекста драфта: llama.cpp embd-путь делает ровно shallow-инъекцию (wk/wv @ fused + k_norm + rope) как Luce draft_ctx_kv_rows.
- Фичи таргета: 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)
Выводы:
- Thinking ON убивает оба, но DFlash страдает сильнее: падение acceptance 0.647→0.253 (-61%), MTP 0.821→0.485 (-41%). Reasoning-текст непредсказуем для любой цепочки, длинная (7) рвётся быстрее короткой (3).
- С thinking ON по скорсти MTP (16.3) > DFlash (13.9) — длинная цепочка платит verify 8 ради ~2.4 принятых.
- С thinking OFF по скорости DFlash (27.7) > MTP (23.6) — цепочка живёт на коде, хвост 4-7 окупает verify.
- Первые 3 позиции: MTP чуть точнее DFlash (0.931/0.816/0.715 vs 0.914/0.810/0.710) — DFlash декодит блок параллельно с масками и не видит собственные пики соседей, MTP поочередно видит свой предыдущий токен.
- Итог для сценариев:
- агент/код: 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-n7 +
Комбо 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 |
Выводы:
- На A3B MTP (вшит) — лучший режим: код 68.4 (+31% AR), проза 57.6 (+9%). Дёшево: MTP-голова вшита в MoE-таргет, verify почти бесплатный.
- DFlash v1 на A3B слабый: на коде +7% (55.8), на прозе МЕДЛЕННЕЕ AR (46.5 vs 52.7). v1 БЕЗ селектора (последоват. argmax) на свободном тексте не окупает даже дешёвый verify. Для A3B нужен DFlash2-драфт (с селектором), его нет нигде.
- n_max=12 НЕ помогает: mean 4.11 выше, но tok/s 53.98 < n7 55.79 (блок 12 масок дороже драфтить, хвост 8-12 не окупается). Оптимум n7.
- Пользователю: на 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).
Вывод:
- 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.
- НО DFlash2+margin0.35 (18.26) всё ещё впереди MTP+margin (17.81) на 2.5%: DFlash2-селектор даёт более точную длинную цепочку, чем MTP-голова на коде.
- Итог для 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.