Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions server/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ add_library(dflash_common STATIC
src/laguna/laguna_dflash_target.cpp
src/common/backend_ipc.cpp
src/common/domino_head.cpp
src/common/dspark_head.cpp
src/common/target_shard_ipc.cpp
src/common/target_shard_ipc_daemon.cpp
src/common/dflash_feature_ring.cpp
Expand Down
2 changes: 1 addition & 1 deletion server/deps/llama.cpp
88 changes: 88 additions & 0 deletions server/scripts/convert_dflash_to_gguf.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,17 @@ def bytes_to_np(raw: bytes, dtype: str, shape: list[int]) -> np.ndarray:
}


DSPARK_TENSOR_MAP = {
"dspark_markov_head.markov_w1.weight": ("dflash.dspark.markov.w1", gguf.GGMLQuantizationType.F16),
"dspark_markov_head.markov_w2.weight": ("dflash.dspark.markov.w2", gguf.GGMLQuantizationType.F16),
}

DSPARK_CONFIDENCE_TENSOR_MAP = {
"dspark_confidence_head.weight": ("dflash.dspark.confidence.weight", gguf.GGMLQuantizationType.F16),
"dspark_confidence_head.bias": ("dflash.dspark.confidence.bias", gguf.GGMLQuantizationType.F32),
}


def add_domino_aux_heads(writer, arch: str, aux_path: Path | None):
if aux_path is None:
return
Expand All @@ -265,6 +276,9 @@ def add_domino_aux_heads(writer, arch: str, aux_path: Path | None):
print(f"[error] Domino aux heads file is not a tensor dict: {aux_path}", file=sys.stderr)
sys.exit(1)

if not any(k in state for k in DOMINO_TENSOR_MAP):
return

missing = [k for k in DOMINO_TENSOR_MAP if k not in state]
if missing:
print(f"[warn] incomplete Domino aux heads; missing {missing}; skipping Domino tensors")
Expand Down Expand Up @@ -294,6 +308,79 @@ def add_domino_aux_heads(writer, arch: str, aux_path: Path | None):
print(f"[tensor] {gguf_name:50s} aux ->{raw_dtype.name:4s} {tuple(arr.shape)}")


def add_dspark_aux_heads(writer, arch: str, aux_path: Path | None):
if aux_path is None:
return
if not aux_path.exists():
return

try:
import torch
except ImportError as exc:
print(f"[error] --aux-heads requires torch: {exc}", file=sys.stderr)
sys.exit(1)

state = torch.load(aux_path, map_location="cpu")
if isinstance(state, dict) and "state_dict" in state and isinstance(state["state_dict"], dict):
state = state["state_dict"]
if not isinstance(state, dict):
print(f"[error] DSpark aux heads file is not a tensor dict: {aux_path}", file=sys.stderr)
sys.exit(1)

missing = [k for k in DSPARK_TENSOR_MAP if k not in state]
if missing:
return

print(f"[info] reading DSpark aux heads from {aux_path}")
w1 = state["dspark_markov_head.markov_w1.weight"]
w2 = state["dspark_markov_head.markov_w2.weight"]
vocab = int(w1.shape[0])
rank = int(w1.shape[1])
if tuple(w2.shape) != (vocab, rank):
print(f"[error] DSpark markov_w2 shape {tuple(w2.shape)} != {(vocab, rank)}", file=sys.stderr)
sys.exit(1)

writer.add_uint32(f"{arch}.dflash.dspark.enabled", 1)
writer.add_uint32(f"{arch}.dflash.dspark.markov_rank", rank)
writer.add_uint32(f"{arch}.dflash.dspark.vocab_size", vocab)

for st_name, (gguf_name, raw_dtype) in DSPARK_TENSOR_MAP.items():
t = state[st_name]
if hasattr(t, "detach"):
t = t.detach().cpu()
arr = t.float().numpy().astype("<f2")
writer.add_tensor(gguf_name, arr, raw_dtype=raw_dtype)
print(f"[tensor] {gguf_name:50s} aux ->{raw_dtype.name:4s} {tuple(arr.shape)}")

conf_missing = [k for k in DSPARK_CONFIDENCE_TENSOR_MAP if k not in state]
if conf_missing:
print(f"[warn] incomplete DSpark confidence head; missing {conf_missing}; Markov head will still load")
return

conf_w = state["dspark_confidence_head.weight"]
conf_b = state["dspark_confidence_head.bias"]
confidence_dim = int(conf_w.shape[1])
if int(conf_w.shape[0]) != 1 or tuple(conf_b.shape) != (1,):
print(
f"[error] DSpark confidence shapes weight={tuple(conf_w.shape)} bias={tuple(conf_b.shape)}",
file=sys.stderr,
)
sys.exit(1)
writer.add_uint32(f"{arch}.dflash.dspark.confidence_dim", confidence_dim)
writer.add_uint32(f"{arch}.dflash.dspark.confidence.enabled", 1)
for st_name, (gguf_name, raw_dtype) in DSPARK_CONFIDENCE_TENSOR_MAP.items():
t = state[st_name]
if hasattr(t, "detach"):
t = t.detach().cpu()
arr = t.float().numpy()
if raw_dtype == gguf.GGMLQuantizationType.F16:
arr = arr.astype("<f2")
else:
arr = arr.astype("<f4")
writer.add_tensor(gguf_name, arr, raw_dtype=raw_dtype)
print(f"[tensor] {gguf_name:50s} aux ->{raw_dtype.name:4s} {tuple(arr.shape)}")


# ──────────────────────────────────────────────────────────────────────
# Main
# ──────────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -400,6 +487,7 @@ def sort_key(t):
if not args.no_aux_heads:
aux_path = args.aux_heads if args.aux_heads is not None else args.safetensors.parent / "dflash_aux_heads.pt"
add_domino_aux_heads(writer, ARCH, aux_path)
add_dspark_aux_heads(writer, ARCH, aux_path)

print(f"[info] writing {args.out_gguf}")
writer.write_header_to_file()
Expand Down
110 changes: 110 additions & 0 deletions server/scripts/quantize_dflash_draft.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#!/usr/bin/env python3
"""Requantize a DFlash draft GGUF (f16) for faster spec-decode drafting.

Schemes:
q8_0 - all 2D matmul weights to q8_0 (~47% smaller, acceptance-neutral)
q4-mix - backbone (blk.*) weights to q4_0, dflash.* head weights kept at
q8_0 (~67% smaller; the head split protects the Markov/projection
bias precision that near-tie corrections depend on).

Norms, biases and non-f16 tensors are copied as-is. Metadata is preserved,
so the output loads anywhere the input does.

Measured on Laguna-XS.2 + v24 drafter (RTX 3090, HumanEval, verify width 6):
f16 236.6 tok/s -> q8_0 241.3 -> q4-mix 249.6, acceptance unchanged.
"""
import argparse
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "deps" / "llama.cpp" / "gguf-py"))

import numpy as np # noqa: E402
import gguf # noqa: E402
from gguf import GGUFReader, GGUFWriter, GGMLQuantizationType # noqa: E402
from gguf.quants import quantize # noqa: E402


def pick_type(name: str, scheme: str) -> GGMLQuantizationType:
if scheme == "q8_0":
return GGMLQuantizationType.Q8_0
# q4-mix: protect the dflash.* heads
if name.startswith("dflash."):
return GGMLQuantizationType.Q8_0
return GGMLQuantizationType.Q4_0


def copy_metadata(r: GGUFReader, w: GGUFWriter) -> None:
skip = {"GGUF.version", "GGUF.tensor_count", "GGUF.kv_count", "general.architecture"}
T = gguf.GGUFValueType
for f in r.fields.values():
if f.name in skip:
continue
ftype = f.types[0]
val = f.parts[f.data[0]]
if ftype == T.STRING:
w.add_string(f.name, bytes(val).decode())
elif ftype == T.ARRAY:
sub = f.types[1]
vals = [f.parts[i] for i in f.data]
if sub == T.STRING:
w.add_array(f.name, [bytes(v).decode() for v in vals])
else:
w.add_array(f.name, [np.asarray(v)[0].item() for v in vals])
elif ftype == T.BOOL:
w.add_bool(f.name, bool(val[0]))
elif ftype == T.FLOAT32:
w.add_float32(f.name, float(val[0]))
elif ftype == T.FLOAT64:
w.add_float64(f.name, float(val[0]))
else:
fn = {T.UINT32: w.add_uint32, T.INT32: w.add_int32,
T.UINT64: w.add_uint64, T.INT64: w.add_int64,
T.UINT8: w.add_uint8, T.INT8: w.add_int8,
T.UINT16: w.add_uint16, T.INT16: w.add_int16}[ftype]
fn(f.name, val[0].item())


def main() -> int:
ap = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("input", help="f16 draft GGUF")
ap.add_argument("output", help="output GGUF path")
ap.add_argument("--scheme", choices=["q8_0", "q4-mix"], default="q4-mix")
args = ap.parse_args()

r = GGUFReader(args.input)
arch = None
for f in r.fields.values():
if f.name == "general.architecture":
arch = bytes(f.parts[f.data[0]]).decode()
if not arch:
print("error: no general.architecture in input", file=sys.stderr)
return 1

w = GGUFWriter(args.output, arch)
copy_metadata(r, w)

n_q = n_keep = 0
for t in r.tensors:
shape = [int(x) for x in t.shape]
if (t.tensor_type == GGMLQuantizationType.F16 and len(shape) == 2
and shape[0] % 32 == 0 and "norm" not in t.name):
qt = pick_type(t.name, args.scheme)
arr = np.array(t.data, dtype=np.float16).reshape(shape[::-1]).astype(np.float32)
w.add_tensor(t.name, quantize(arr, qt), raw_dtype=qt)
n_q += 1
else:
w.add_tensor(t.name, np.array(t.data), raw_dtype=t.tensor_type)
n_keep += 1

w.write_header_to_file()
w.write_kv_data_to_file()
w.write_tensors_to_file()
w.close()
print(f"{args.scheme}: quantized {n_q} tensors, kept {n_keep} as-is -> {args.output}")
return 0


if __name__ == "__main__":
sys.exit(main())
2 changes: 2 additions & 0 deletions server/src/common/backend_factory.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@
#pragma once

#include "model_backend.h"
#include "internal.h"
#include "placement/placement_config.h"
#include "placement/remote_draft_config.h"
#include "placement/remote_target_shard_config.h"

#include <memory>
#include <string>
#include <vector>

namespace dflash::common {

Expand Down
Loading
Loading