Files
esh-pfi-infrastructure/stacks/mog-sec/compose.yaml
T
vh 8842ffe1fe Pin mog-sec's KV in bytes at 16.48 GiB and take it to 320k context
Operator: "yes, pin the kv and take it to 320k."

The real finding, which took three crashes and two failed attempts to reach:
--gpu-memory-utilization does not bound actual usage. It sizes the KV
calculation, but peak activation is measured at profiling time and real
long-context work exceeds the profile. vLLM's own budget line showed mog-sec
running 0.9 GiB over its 47.48 GiB reservation -- 26.44 consumed plus 3.53 peak
activation plus 0.89 CUDAGraph plus 17.52 KV equals 48.38 -- and gen was over by
0.33 on the same card. That overage came out of the shared card's slack, which is
what kept OOMing after the utilization drop.

The fix is the one vLLM printed itself: --kv-cache-memory=17697765376, its own
recommended figure to fit inside the requested budget. Same discipline erp-seat
already uses, and for the same stated reason -- an explicit figure is
reproducible where a ratio silently yields a different cache depending on what
else is resident at start time.

The KV pin and the context length are coupled. 16.48 GiB yields about 383,730
tokens, so a 393,216 max_model_len falls under the 1.0x floor and vLLM refuses to
start rather than crashing later; pinning the KV while keeping 384k was never an
available combination. 327,680 leaves 1.15x, up from 1.03x.

Verified: the engine now logs "reserved 16.48 GiB memory for KV Cache as
specified by kv_cache_memory_bytes config and skipped memory profiling", KV
375,901 tokens, GPU0 down to 90,561 MiB from 91,313, RestartCount 0, and both sec
and sec-reasoning return 200 through the gateway.

Also records the BabyBronte eyeball A/B, whose result is the operator's own: the
voice transferred and the sense did not. Curly quotes went 1 of 18 to 18 of 18
and worksheet collapse 3 of 18 to 0 of 18 between arms. That voice is separable
from coherence at 0.6B is the premise the lightweight-adapter regime rests on, so
this is the informative outcome rather than a disappointing one. A corpus-prep
defect surfaced with it: the tuned output is hard-wrapped at about 70 characters
because the Gutenberg source kept its line breaks and the adapter learned the
typography too.

Cost: 320k of context instead of 420k, on a seat whose crashes happened at 151k.
2026-09-10 15:19:47 -07:00

193 lines
9.8 KiB
YAML

# mog-sec — the pen-test seat on ana-ml2 GPU 0 (:8019).
#
# MOVED GPU 1 -> GPU 0 on 2026-08-28 (operator-directed). GPU 1 carries the five
# resident fleet seats (gen 46 GB + embed + coder + rerank + reward = ~69.9 GB of
# 97.9), leaving ~28 GB — less than the ~51 GB this seat reserves at
# MOG_GPU_MEM_UTIL=0.52, so it could no longer start there. GPU 0 has been idle
# since run 3c was stopped.
#
# ⚠ POWER: bringing this up re-arms the two-GPU load condition that tripped the
# Anaheim rack breaker on 2026-08-26. One circuit feeds the whole rack including
# ana-gw and ana-wg, so a trip costs the site AND the remote path in. Idle draw is
# negligible (~6-13 W/card); the risk is sec and gen under concurrent load.
#
# Serves Blackfrost-Research/M.O.G.-SEC-27B-1M-CTX-BF16 (stock-Qwen3.8-27B-based,
# vision-intact Qwen3_5ForConditionalGeneration, base-graft MTP head), quantized
# in-house to mixed NVFP4 W4A4 (bulk MLP) + FP8 W8A8 (attn / linear_attn / lm_head /
# top MLP layers), with the bf16 MTP head grafted back. ⚠ the grafted MTP requires
# `re:^mtp.*` in config.json quantization_config.ignore or vLLM loads it uninitialised
# (0% accept) — handled by post_quant.py, verified present at build time.
#
# CONTEXT: served at native 262K, NOT the card's 1M. The 1M needs YaRN rope_scaling
# (absent from the weights' config) plus the SGLang/DFlash2 attention path the repo
# ships a deployment kit for — neither is our vLLM serving surface. 262K is the honest
# native ceiling here; a real 1M seat would be a separate SGLang project.
#
# ⚠⚠ GPU0 HEADROOM — the 2026-09-10 crash pair, and why the live .env now reads
# MOG_GPU_MEM_UTIL=0.50 / MOG_MAX_MODEL_LEN=393216 rather than 0.52 / 420000.
#
# At util 0.52 this seat and `gen` (0.38) together reserve 0.90 of the card, leaving
# ~4.6 GiB. vLLM's utilization figure covers weights and the KV pool but NOT all
# transient activation memory, and a long-context prefill chunk with the 7-wide dflash
# drafter lives in what is left. Twice in ten minutes (20:20:30Z and 20:30:02Z) a request
# ~151,700 tokens deep scheduling a further ~15,700-token chunk asked for ~1.04 GiB with
# ~600 MB free, EngineCore took a fatal error, and `restart: unless-stopped` bounced the
# seat. Each bounce is a hard 500 to every in-flight caller.
#
# ⚠ 0.50 AND 420000 ARE MUTUALLY EXCLUSIVE — dropping util alone does NOT work and the
# seat will crash-loop at startup instead of at runtime. A single 420,000-token sequence
# needs 17.88 GiB of KV; at 0.50 the pool is 17.4-17.5 GiB, so vLLM refuses:
# "To serve at least one request with the model's max seq len (420000), 17.88 GiB KV
# cache is needed, which is larger than the available KV cache memory (17.41 GiB)."
# The context length and the crash were directly coupled: 420k was only reachable at the
# utilization that left no transient headroom.
#
# ⚠ DO NOT pin max_model_len to vLLM's suggested maximum. It estimated 406,352 on one
# boot and 409,840 on the next -- the available-KV figure drifts ~0.1 GiB boot to boot, so
# the edge value fails to start on an unlucky one. 393,216 (384k) sits 3% under the lower
# estimate and leaves ~0.7 GiB of the pool unspent, which IS the transient headroom this
# change exists to buy. Measured after: KV 405,612 tokens, concurrency 1.03x at 393,216.
#
# ⚠ NOT the lever: `num_speculative_tokens`. The crash window logged 17.6% draft
# acceptance with positions 5-7 at 1.5-4.9%, which reads as an obvious cut from 7 to 3.
# Across 180 samples of that counter the median acceptance LENGTH is 3.12 of 7 (range
# 1.83-6.75) and median draft acceptance is 30.4% (range 11.9-82.1%). The crash window sat
# near the minimum; cutting to 3 would cap the workloads accepting nearly the full draft.
#
# Two served-names (base + `-thinking`): LiteLLM keys deployments by (model, api_base),
# so mog-sec and mog-sec-reasoning use distinct names to avoid the shared-config
# enable_thinking clobber. Same pinned nightly as the gen seat (carries the #51113
# qwen3_5_mtp x GDN partial-accept fix that the MTP-on config depends on).
name: mog-sec
services:
vllm-mog-sec:
image: ${MOG_IMAGE:-vllm/vllm-openai:latest}
container_name: ${MOG_CONTAINER_NAME:-vllm-mog-sec}
restart: unless-stopped
ipc: host
ports:
- "${MOG_PORT:-8019}:8000"
volumes:
- /tank/aimodels/huggingface:/hfcache
- ${MOG_MODEL:-/tank/aimodels/mog-sec-27b-nvfp4-mixed}:/model:ro
# DFlash2 speculative drafter. Mounted unconditionally — it is inert if
# MOG_SPEC_CONFIG selects an MTP method that does not reference /drafter.
- ${MOG_DRAFT_MODEL:-/tank/aimodels/qwen38-27b-dflash2-drafter}:/drafter:ro
environment:
- HF_HOME=/hfcache
- HF_HUB_CACHE=/hfcache/hub
- VLLM_API_KEY=${API_KEY:-}
# ⚠ DEFAULTS TO UNSET, deliberately. This seat previously hardcoded
# PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True. The DFlash2 config
# validated 2026-08-22 ran WITHOUT it, and the quant playbook §3.10
# records expandable_segments corrupting retained tensors in another
# context. Do not re-enable it casually — that would ship a variable the
# tested configuration did not have.
- PYTORCH_CUDA_ALLOC_CONF=${MOG_ALLOC_CONF:-}
command:
- /model
- --served-model-name
- ${MOG_SERVED_NAME:-mog-sec-27b}
- ${MOG_SERVED_NAME_THINK:-mog-sec-27b-thinking}
- --host
- 0.0.0.0
- --port
- "8000"
- --quantization
- ${MOG_QUANT:-compressed-tensors}
- --gpu-memory-utilization
- ${MOG_GPU_MEM_UTIL:-0.44}
# ⚠ KV PINNED IN BYTES, added 2026-09-10 after the crash pair above. The
# utilization ratio does NOT bound actual usage -- it sizes the KV calculation,
# but peak activation is measured at profiling time and real long-context work
# exceeds the profile. vLLM's own budget line proved this seat was running 0.9 GiB
# OVER its 47.48 GiB reservation at util 0.50 (26.44 consumed + 3.53 peak activation
# + 0.89 CUDAGraph + 17.52 KV = 48.38), and `gen` was over by 0.33 on the same card.
# That overage came out of the shared card's slack, which is what kept OOMing.
#
# 17,697,765,376 B = 16.48 GiB is vLLM's OWN recommended figure from that line
# ("Replace gpu_memory_utilization config with --kv-cache-memory=17697765376 to fit
# into requested memory"), not a value anyone here invented. Same discipline as
# stacks/erp-seat: an explicit figure is reproducible, a ratio silently yields a
# different cache depending on what else is resident at start time.
- --kv-cache-memory
- ${MOG_KV_CACHE_MEMORY:-17697765376}
- --max-model-len
# ⚠ 320k, NOT 384k, and the two settings are coupled -- 16.48 GiB of KV yields about
# 383,730 tokens, so a 393,216 max_model_len falls under the 1.0x floor and vLLM
# refuses to START rather than crashing later. Pinning the KV and keeping 384k was
# never an available combination. 327,680 leaves ~1.17x.
- ${MOG_MAX_MODEL_LEN:-262144}
- --max-num-seqs
- ${MOG_MAX_NUM_SEQS:-16}
- --max-num-batched-tokens
# ⚠ Raising this costs peak-activation VRAM straight out of the KV pool
# (measured 2026-08-22: 16384 -> 32768 cost ~3 GiB of KV for no benefit).
- ${MOG_MAX_NUM_BATCHED_TOKENS:-16384}
- --trust-remote-code
- --dtype
- auto
- --mamba-cache-dtype
- float32
- --kv-cache-dtype
- ${MOG_KV_CACHE_DTYPE:-fp8}
- --enable-prefix-caching
- --enable-chunked-prefill
- --limit-mm-per-prompt
- '${MOG_LIMIT_MM:-{"image": 4}}'
# ⚠ MANDATORY on a newer vLLM. The model's own preprocessor_config.json
# declares size.longest_edge = 16777216 px (4096x4096), which expands to
# 16384 image tokens — one image eating 6% of a 262K context, and enough
# to kill startup on builds that enforce the text-vs-ids count check.
# This caps the dummy profiling image AND real images. Cost scales as
# (edge/patch)^2 / merge^2, so 2048x2048 -> ~5125 tokens.
- --mm-processor-kwargs
- '${MOG_MM_PROCESSOR_KWARGS:-{"size": {"longest_edge": 4194304, "shortest_edge": 65536}}}'
- --reasoning-parser
- ${MOG_REASONING_PARSER:-qwen3}
- --default-chat-template-kwargs
- '{"reasoning_effort": "${MOG_REASONING_EFFORT:-medium}"}'
- --enable-auto-tool-choice
- --tool-call-parser
- qwen3_coder
# ONE env var carrying the whole JSON, because the two speculative shapes
# are not interchangeable: dflash needs a "model" pointing at the drafter,
# MTP must NOT have one. A method+tokens template cannot express both.
# DFlash2 : {"method": "dflash", "model": "/drafter", "num_speculative_tokens": 7}
# MTP : {"method": "qwen3_5_mtp", "num_speculative_tokens": 3}
# ⚠ Comparing the two requires matching num_speculative_tokens — see the
# quant playbook §5.1: MTP runs a single-module head autoregressively, so
# deeper k improves acceptance and DESTROYS throughput.
- --speculative-config
- '${MOG_SPEC_CONFIG:-{"method": "qwen3_5_mtp", "num_speculative_tokens": 3}}'
deploy:
resources:
reservations:
devices:
- driver: nvidia
device_ids:
- "${MOG_GPU_ID:-0}"
capabilities:
- gpu
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 900s
networks:
- tnet
labels:
- homepage.group=AI - Inference
- homepage.name=M.O.G.-SEC 27B (pen-test)
- homepage.icon=mdi-shield-lock
- homepage.description=Uncensored security model, Qwen3.8-27B NVFP4+MTP, 262K — the `mog-sec` seat (ana-ml2 GPU 0)
- homepage.href=http://10.250.50.54:${MOG_PORT:-8019}/docs
networks:
tnet:
name: traefik-net
external: true