Sign inSign up

orthozany/vllm-jasl-dsv4

By orthozany

•Updated 6 months ago

DeepSeek-V4-Flash on RTX PRO 6000 Blackwell (sm_120), vLLM jasl PR #41834.

Image
1

3.9K

orthozany/vllm-jasl-dsv4 repository overview

⁠orthozany/vllm-jasl-dsv4

DeepSeek-V4-Flash inference on SM12x (RTX PRO 6000 Blackwell, sm_120) via a snapshot of jasl's vLLM fork.

This image bundles a torch 2.11+cu130 venv with a from-source build of jasl/vllm against the PR that adds sparse-MLA + MTP self-speculation support for Blackwell workstation GPUs. Built on the nvidia/cuda:13.0.0-devel-ubuntu24.04 base.

⁠Tags

  • pr41834-2026-05-13 — latest. Built from jasl/vllm branch codex/ds4-sm120-min-enable @ 8f0d8b630 (vLLM PR #41834⁠, 2026-05-12 head). Includes:
    • nvidia-cutlass-dsl 4.5.0
    • One small in-tree patch: cute.nvgpu.LoadCacheMode → cpasync.LoadCacheMode in deepseek_v4_ops/dequant_gather_k_cutedsl.py:106 (the PR head referenced a path that exists only inside the cpasync submodule)
    • MTP self-spec ready (--speculative-config '{"method":"mtp","num_speculative_tokens":2}')
  • tmp-2026-05-05-snap — earlier snapshot at jasl/vllm@843fe9eb6 (pre-PR-rebase). No MTP=2 support; kept as rollback.

⁠Hardware requirements

  • 2× NVIDIA RTX PRO 6000 Blackwell (Max-Q or Workstation), 96 GB each, no NVLink (PCIe Gen5)
  • Driver supporting CUDA 13.x (tested on 595.x)
  • ~150 GB host disk for deepseek-ai/DeepSeek-V4-Flash weights
  • ≥ 64 GB system RAM
  • nvidia-container-toolkit installed and configured for Docker

⁠Reference numbers — 2× RTX PRO 6000 Blackwell Max-Q, TP=2, MTP=2

Measured with voipmonitor/llm-inference-bench⁠ v0.4.13 (--duration 30 --max-tokens 512, unique-prompt mode → no prefix-cache benefit).

⁠Decode — aggregate tok/s (across concurrent streams)
ctx \ conc124
0 (chat)74.0109.398.7
16K73.0107.624.5 ★
32K68.6101.923.8 ★
64K58.887.123.3 ★
128K49.680.723.9 ★

★ Scheduling falls over at concurrency ≥ 3 with long context + MTP=2 on --max-num-seqs=4. Raise that knob if you need c=4 long-context.

⁠Decode — per-request tok/s
ctx \ conc124
074.054.624.7
16K73.053.86.1
32K68.650.96.0
64K58.843.55.8
128K49.640.36.0
⁠Prefill tok/s
ctxtokensTTFT stok/s
8K8,1915.81,410
16K16,24912.11,341
32K32,34126.71,213
64K64,55862.81,028
128K128,991162.2795
⁠MTP self-speculation

Acceptance rate during the matrix: 64.5% (pos0 81%, pos1 48%) — MTP is doing real work.

⁠Caveat — Max-Q vs Workstation

PR #41834's own claim is 148.86 tok/s mt-bench mixed-context average on the Workstation variant. PR comments flag a ~20% regression on Max-Q, which is what this image was benched on. With prefix-cache hits (e.g. typical chat workloads with shared system prompts) the c=1 30K cell rises to 130–145 tok/s.

⁠1. Download the model

# ~150 GB.  HF_HUB_ENABLE_HF_TRANSFER=1 uses the fast Rust downloader.
mkdir -p /path/to/models/deepseek-ai
HF_HUB_ENABLE_HF_TRANSFER=1 huggingface-cli download \
  deepseek-ai/DeepSeek-V4-Flash \
  --local-dir /path/to/models/deepseek-ai/DeepSeek-V4-Flash

⁠2. docker-compose.yml

Save this in a folder, set MODEL_DIR and the two GPU indices, then docker compose up -d.

services:
  vllm-dsv4-flash:
    image: orthozany/vllm-jasl-dsv4:pr41834-2026-05-13
    container_name: vllm-dsv4-flash
    init: true
    runtime: nvidia
    privileged: true
    ipc: host
    shm_size: "32g"
    restart: unless-stopped

    # The image's ENTRYPOINT is the venv's `vllm` binary, but the venv needs
    # to be `source activate`d so its env (PYTHONPATH for editable install,
    # LD_LIBRARY_PATH for cuda libs in /home/user/jasl-dsv4/.venv) is set.
    entrypoint:
      - "/bin/bash"
      - "-c"
      - |
        set -euo pipefail
        source /home/user/jasl-dsv4/.venv/bin/activate
        exec vllm serve "$$@"
      - "--"

    environment:
      # ----- GPU pinning -----------------------------------------------------
      # IMPORTANT: NVIDIA_VISIBLE_DEVICES alone does not always restrict on
      # every docker / nvidia-container-runtime combination. Also set
      # CUDA_VISIBLE_DEVICES to the in-container indices you want CUDA to use.
      # Do NOT additionally set deploy.resources.reservations.devices — it
      # can conflict and accidentally select a non-Blackwell GPU as cuda:0.
      - NVIDIA_VISIBLE_DEVICES=0,1
      - CUDA_VISIBLE_DEVICES=0,1
      - CUDA_DEVICE_ORDER=PCI_BUS_ID

      # ----- SM 12.0 workarounds (required) ----------------------------------
      # FlashInfer JIT mis-parses TORCH_CUDA_ARCH_LIST=12.0a; fall back to
      # the PyTorch sampler.
      - VLLM_USE_FLASHINFER_SAMPLER=0
      # Sparse-MLA warmup adds many seconds to startup with no benefit.
      - VLLM_ENABLE_DEEPSEEK_V4_SPARSE_MLA_WARMUP=0
      # Wide timeout: the v1 engine compiles many TileLang sparse-MLA kernels
      # on first start; 3600 s comfortably covers cold start.
      - VLLM_ENGINE_READY_TIMEOUT_S=3600

      # ----- NCCL: PCIe-only Blackwell, no NVLink ----------------------------
      # LL + Ring + 8 channels + 512 threads is the small-message tuning that
      # keeps TTFT low on Blackwell workstation cards. Safe because we
      # disable custom-allreduce (see --disable-custom-all-reduce in command:).
      - NCCL_DEBUG=WARN
      - NCCL_P2P_DISABLE=1
      - NCCL_SHM_DISABLE=0
      - NCCL_IB_DISABLE=1
      - NCCL_PROTO=LL
      - NCCL_ALGO=Ring
      - NCCL_MIN_NCHANNELS=8
      - NCCL_NTHREADS=512

      # ----- Standard --------------------------------------------------------
      - PYTHONUNBUFFERED=1
      - VLLM_WORKER_MULTIPROC_METHOD=spawn

    volumes:
      # Read-only mount for the downloaded weights.
      - /path/to/models/deepseek-ai/DeepSeek-V4-Flash:/model:ro
      # Persistent caches so torch.compile, TileLang, triton don't re-JIT on
      # every container restart.  Local subdirectories OK.
      - ./cache/huggingface:/root/.cache/huggingface
      - ./cache/vllm:/root/.cache/vllm
      - ./cache/torch:/root/.cache/torch
      - ./cache/triton:/root/.cache/triton
      - ./cache/torchinductor:/root/.cache/torchinductor
      - ./cache/flashinfer:/root/.cache/flashinfer

    ports:
      - "8000:8000"

    command:
      # Positional: the model directory inside the container.
      - "/model"

      # ----- Names the API responds to ---------------------------------------
      - "--served-model-name"
      - "deepseek-v4-flash"
      - "deepseek-v4-flash-mtp"
      - "deepseek-ai/DeepSeek-V4-Flash"

      # ----- Parallelism + quant ---------------------------------------------
      - "--tensor-parallel-size=2"
      - "--kv-cache-dtype=fp8"
      - "--block-size=256"

      # ----- Context window --------------------------------------------------
      # 131072 (128K) is a safe default with --gpu-memory-utilization=0.93.
      # For the model's native max (524288) bump to 0.95.
      - "--max-model-len=131072"
      - "--max-num-seqs=4"
      - "--max-num-batched-tokens=8192"
      - "--gpu-memory-utilization=0.93"

      # ----- DeepSeek-V4 tokenizer + parsers (required) ----------------------
      - "--tokenizer-mode=deepseek_v4"
      - "--tool-call-parser=deepseek_v4"
      - "--enable-auto-tool-choice"
      - "--reasoning-parser=deepseek_v4"
      - "--trust-remote-code"

      # ----- Custom all-reduce off (no NVLink on workstation Blackwell) ------
      - "--disable-custom-all-reduce"

      # ----- MTP self-speculation: ~+40% decode throughput -------------------
      # Set num_speculative_tokens=1 instead if you see acceptance below 25%.
      - "--speculative-config={\"method\":\"mtp\",\"num_speculative_tokens\":2}"

      - "--host=0.0.0.0"
      - "--port=8000"

    ulimits:
      memlock: -1
      stack: 67108864

⁠3. Bring it up + smoke

docker compose up -d

# Cold start takes 2–4 minutes (weight load + warmup + TileLang JIT).
# Wait for "Application startup complete" in the logs.
docker compose logs -f vllm-dsv4-flash | grep -E "Application startup|ERROR"

# Smoke test
curl -sS -X POST http://localhost:8000/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{"model":"deepseek-v4-flash","stream":false,"max_tokens":64,
       "temperature":0.0,"messages":[{"role":"user","content":"Say OK."}]}'

The deepseek_v4 reasoning parser splits output into content and reasoning_content; clients that read only content will see "" on thinking responses. Either read both fields or strip the reasoning prefix on the client side.

⁠Tuning notes

  • Native 524K context: change --max-model-len=131072 → 524288 and --gpu-memory-utilization=0.93 → 0.95. The KV cache budget grows to ~1.8 M tokens.
  • Single-stream long context (≥128K): --max-num-seqs=1 lets the scheduler hand all KV memory to one request.
  • Heavier concurrency: raise --max-num-seqs (e.g. 8) and --max-num-batched-tokens (e.g. 16384).
  • No MTP: drop --speculative-config=…. Expect ~−30 to −40% decode tok/s vs MTP=2.
  • Different attention backend: the image forces DEEPSEEK_SPARSE_SWA for DSV4 on sm_120; the --attention-backend flag is logged but ignored by jasl's path.

⁠Image internals (for the curious)

The image is a tarball snapshot of a known-good bare-metal build at:

/home/user/jasl-dsv4/                  ← venv + vllm-jasl/ + DeepGEMM-jasl/
/home/user/.local/share/uv/python/     ← uv-managed cpython-3.13.13

The editable install was performed on a host where /home/user/jasl-dsv4 was a symlink to /mnt/raid0-…/builds/jasl-dsv4-epyc01, so the editable finder hardcodes that resolved path. The Dockerfile recreates the path as a symlink inside the container so the editable resolution still works.

⁠Sources

⁠License

The image bundles vLLM (Apache-2.0). The DeepSeek-V4-Flash weights are not redistributed; download them separately from Hugging Face.

Tag summary

Content type

Image

Digest

sha256:3155da95a…

Size

7.5 GB

Last updated

6 months ago

docker pull orthozany/vllm-jasl-dsv4:pr41834-2026-05-13