Skip to content

Eval bug: GGML_ASSERT in ggml_cuda_op_concat with DeepSeek V4 + quantized KV cache (non-contiguous src) #25530

Description

@adubovikov

Name and Version

version: 9939 (92b187c)
built with GNU 13.3.0 for Linux aarch64

Also reproduced on a build from 7a63fde. The backtrace below is from a build from early July, where the same condition trips a slightly different assert (see "Problem description").

Operating systems

Linux

GGML backends

CUDA, RPC

Hardware

2x NVIDIA GB10 (aarch64, CUDA 13), 2-node RPC cluster

Models

DeepSeek-V4-Flash UD-Q4_K_XL (unsloth GGUF)

Problem description & steps to reproduce

Enabling KV cache quantization (--cache-type-k q8_0 --cache-type-v q8_0) for DeepSeek V4 crashes the CUDA backend during prompt processing. The same setup with default f16 KV cache works.

Root cause: the DeepSeek V4 graph (#24162, lightning indexer #24231) feeds a non-contiguous view of the quantized KV cache into GGML_OP_CONCAT. The quantized branch of ggml_cuda_op_concat (ggml/src/ggml-cuda/concat.cu) only supports contiguous inputs: on current master this trips GGML_ASSERT(ggml_is_contiguous(src1)); on the early-July build from the backtrace below the same case reaches the assert inside concat_cuda (GGML_ASSERT(!ggml_is_quantized(src0->type)), concat.cu:207). The non-quantized branch handles non-contiguous inputs fine.

The CUDA supports_op mirrors the contiguity requirement, but the RPC backend's supports_op returns true unconditionally, so on an RPC worker the op reaches CUDA and aborts. Note the CPU backend has the same assert in ggml_compute_forward_concat, so a single-node setup where the scheduler falls back to CPU for this op would likely abort as well (not verified).

Repro:

# worker:
./build/bin/ggml-rpc-server -H 10.10.10.2 -p 50052

# head:
./build/bin/llama-server \
  --model DeepSeek-V4-Flash-UD-Q4_K_XL-00001-of-00005.gguf \
  --rpc 10.10.10.2:50052 \
  --ctx-size 196608 --parallel 2 \
  --cache-type-k q8_0 --cache-type-v q8_0 \
  --no-mmap --jinja --port 8080

curl http://localhost:8080/v1/chat/completions -H "Content-Type: application/json" \
  -d '{"model":"x","messages":[{"role":"user","content":"hello"}]}'

The worker aborts, then the head node fails in ggml_backend_rpc_buffer_get_tensor.

I have a working fix on a branch: pack non-contiguous quantized inputs into a contiguous scratch buffer first (a single cudaMemcpy2DAsync when the row stride is uniform, otherwise a small packing kernel in one launch), then reuse the existing byte-level concat path; the contiguity asserts are relaxed to ggml_is_contiguous_rows (CUDA, supports_op, and the CPU backend, whose generic path already handles strided rows). With it:

  • test-backend-ops -o CONCAT passes 252/252, including new non-contiguous quantized cases, on GB10 (aarch64, CUDA 13) and RTX 4070 (x86_64, CUDA 13.1);
  • the setup above survives 33K-token prompts and multi-turn conversations, prompt processing stays at ~194 t/s at 33K ctx (vs f16 KV cache degrading 210 -> 82 t/s past ~18K on the same cluster).

Branch: https://github.com/adubovikov/llama.cpp/tree/cuda-concat-noncont-quant — happy to open a PR if the approach looks acceptable.

Disclosure: this report and the fix branch were prepared with heavy AI (LLM agent) assistance; the debugging, reproduction and benchmarks were run on real hardware as described. Given the project's AI usage policy, feel free to treat the branch as a reference for a maintainer-authored fix instead of a PR.

First Bad Commit

Not bisected; the op pattern comes with DeepSeek V4 support (#24162).

Relevant log output

Logs
ggml_backend_cuda_graph_compute: CUDA graph warmup complete
/home/shurik/llama.cpp/ggml/src/ggml-cuda/concat.cu:207: GGML_ASSERT(!ggml_is_quantized(src0->type)) failed
[New LWP 4511]
[New LWP 4507]
...
#0  0x0000f7a805cd7b74 in __GI___wait4 (pid=4528, stat_loc=0x0, options=0, usage=0x0) at ../sysdeps/unix/sysv/linux/wait4.c:30
#1  0x0000b9542e95595c in ggml_print_backtrace ()
#2  0x0000b9542e955b00 in ggml_abort ()
#3  0x0000b9542e63b50c in ggml_cuda_op_concat(ggml_backend_cuda_context&, ggml_tensor*) ()
#4  0x0000b9542df4d66c in ggml_backend_cuda_graph_compute(ggml_backend*, ggml_cgraph*) ()
#5  0x0000b9542e96df10 in ggml_backend_graph_compute ()
#6  0x0000b9542e94b680 in rpc_server::graph_compute(std::vector<unsigned char, std::allocator<unsigned char> > const&) ()
#7  0x0000b9542e94fca4 in rpc_serve_client(std::vector<ggml_backend*, std::allocator<ggml_backend*> > const&, char const*, std::shared_ptr<socket_t>) ()
#8  0x0000b9542e952030 in ggml_backend_rpc_start_server ()
#9  0x0000b9542de24a08 in main ()
[Inferior 1 (process 4506) detached]
./notmux-spark2-eae8-start-worker.sh: line 12:  4506 Aborted                 (core dumped) $HOME/llama.cpp/ggml-rpc-server -H $BIND_HOST -p $PORT -c

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions