meta-models/Muse-Glimmer-30B
29.6B dense vision-language agent model (3:1 sliding/global attention, image input), BF16, 128K context on two Gaudi 2 cards.
curl -s http://localhost:8000/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model": "meta-models/Muse-Glimmer-30B", "messages": [{"role": "user", "content": "Say hello in one sentence."}], "max_tokens": 512}'vllm bench serve --model meta-models/Muse-Glimmer-30B \
--dataset-name random --random-input-len 1024 --random-output-len 256 \
--num-prompts 64 --max-concurrency 192export PT_HPU_LAZY_MODE=1
export PT_HPU_ENABLE_LAZY_COLLECTIVES=true
export VLLM_SKIP_WARMUP=true
export VLLM_MULTIMODAL_BUCKETS=None
export VLLM_GRAPH_RESERVED_MEM=0.3
export VLLM_GRAPH_PROMPT_RATIO=0.15
export VLLM_GRAPH_PROMPT_STRATEGY=max_bs
export VLLM_GRAPH_DECODE_STRATEGY=max_bs
export VLLM_EXPONENTIAL_BUCKETING=True
export VLLM_PROMPT_BS_BUCKET_MIN=1
export VLLM_PROMPT_BS_BUCKET_STEP=1
export VLLM_PROMPT_BS_BUCKET_MAX=16
export VLLM_DECODE_BS_BUCKET_MIN=1
export VLLM_DECODE_BS_BUCKET_STEP=1
export VLLM_DECODE_BS_BUCKET_MAX=192
export VLLM_PROMPT_SEQ_BUCKET_MIN=128
export VLLM_PROMPT_SEQ_BUCKET_STEP=4096
export VLLM_PROMPT_SEQ_BUCKET_MAX=131072
export VLLM_DECODE_BLOCK_BUCKET_MIN=128
export VLLM_DECODE_BLOCK_BUCKET_STEP=1024
export VLLM_DECODE_BLOCK_BUCKET_MAX=6144
export VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS=1800
export VLLM_ENGINE_ITERATION_TIMEOUT_S=3600
export VLLM_RPC_TIMEOUT=3600000
# Fixes: the v0.30.0 profile plus this recipe's opt-in fixes, in the pack's order.
# Stops with a non-zero exit code if the image does not match.
python3 "$COOKBOOK/fixes/apply_all.py" --profile v0.30.0 topk-subset-sampler || exit 1
vllm serve meta-models/Muse-Glimmer-30B \
--api-server-count 4 \
--tensor-parallel-size 2 \
--max-model-len 131072 \
--max-num-seqs 192 \
--gpu-memory-utilization 0.85 \
--block-size 128 \
--max-num-batched-tokens 8192 \
--enable-chunked-prefill \
--enable-prefix-caching \
--enable-prompt-tokens-details \
--enable-auto-tool-choice \
--tool-call-parser muse_glimmer \
--reasoning-parser muse_glimmer \
--limit-mm-per-prompt '{"image":8,"video":0}'
$COOKBOOK is a checkout of this repository there.At a glance
Checks that passed
- Port image path (install.sh on the stock image, then the v0.30.0 profile plus topk-subset-sampler): 11/11 device gates in tests/hpu/run_gates.sh
- 32 concurrent requests 32/32 correct; stock v0.30.0 stops the engine on the first two concurrent requests
- Needles at 8K, 32K (5% and 95% depth), 120K and 127K prompt tokens: 5/5
- Vision: shape count and OCR of a rendered image, 8 image sizes 8/8, text-only request after images correct
- tool_choice required 24/24 (8 greedy, 16 sampled at the generation defaults), named call with schema-valid arguments, auto still answers in prose; streaming required call has id, name and arguments
- logprobs 5 on completions 3/3 clean with greedy top-1 at every position; chat top_logprobs; seeded requests 128/128 at 64 concurrent, same-seed repeats identical
- KV cache 1,571,584 tokens (11.99 sequences of 131,072)
Notes
- The port adds three model-only patches to the stock image: the attention output keeps its 3D shape (batch > 1 stops the engine without it), the sliding layers read only their window in chunked prefill (without it a 120K prompt returned garbage and corrupted later requests), and tool_choice required/named are enforced with a grammar.
- Sampling defaults from generation_config.json are temperature 1.0, top_p 0.95, top_k 64.
- response_format json_schema is not enforced on this model: the grammar waits for a reasoning end that a plain answer never sends.
- tool_choice none can leak ATEM tool markup into content when the model calls a tool anyway.
Guide
Meta's 29.6B dense vision-language agent model (52 layers, 3:1 sliding-window and global attention, a
50-layer vision encoder), served in BF16 at 128K context on two Gaudi 2 cards with up to eight images per
prompt. vLLM ced6857a already has the model and both parsers; a small port adds three model-only patches
that the stock releases/v0.30.0 image needs on Gaudi.
| Model | meta-models/Muse-Glimmer-30B (Apache 2.0, with Meta's usage policy) |
| Hardware | 2x Gaudi 2 (TP 2) |
| Image | port image built from vllm-gaudi releases/v0.30.0 (058f107), vLLM ced6857a, Gaudi SW 1.24.1 |
| Precision | BF16 (59.5 GB of weights, vision tower included) |
| Context | 131,072; KV pool 1,571,584 tokens |
| Concurrency | --max-num-seqs 192, --api-server-count 4 |
| Parsers | tools and reasoning muse_glimmer (vLLM, with the port's grammar for forced calls) |
| Port | ports/muse-glimmer-30b/ |
| Recipe | recipes/meta-models/Muse-Glimmer-30B/recipe.yaml |
Build the image
From a checkout of this repository, on top of a stock releases/v0.30.0 image built as in
Getting started:
docker build -f ports/muse-glimmer-30b/Dockerfile --build-arg BASE=<your-vllm-gaudi-v0.30.0-image> -t <your-muse-glimmer-30b-image> .
install.sh checks the vllm-gaudi and vLLM commits and applies three patches, each fail-closed with
--check:
- Attention output rank. The model multiplies its sigmoid output gate, computed from the 3D
[batch, query, hidden]layer input, by the HPU attention output, which comes back flattened as[batch * query, hidden]. Batch 1 broadcasts by accident, so single requests work and the first two concurrent requests stop the engine (The size of tensor a (128) must match the size of tensor b (256)). The patch restores the rank once, after the attention call. - Sliding-window blocks in chunked prefill. vllm-gaudi builds the window block list for sliding layers
only when
model_typeisgemma4; the patch addsmuse_glimmer. Without it the 39 sliding layers read the whole context in every 8K prefill chunk. On the same image without the patch, a 120K-token prompt returned one word repeated tomax_tokens, every later request on that server returned garbage, and the engine then failed withPT_DEVMEM Allocation failed(reproduced twice; not root-caused further). - Forced tool calls. The
muse_glimmertool parser skips vLLM's JSON-schema path fortool_choice"required" and named functions (the model writes ATEM XML) and installs nothing instead, so those choices decode like "auto". The patch installs an xgrammar structural tag in the model's own channel format, applied from the first token.
The image carries this repository at /opt/gaudi-cookbook. Details: the
port README.
Required fixes
The v0.30.0 profile plus one opt-in fix. The ones this configuration depends on:
| Fix | Why this model needs it |
|---|---|
| topk-subset-sampler (opt-in) | generation_config.json sets top_k 64 and top_p 0.95, so every request that does not override them sorted the 202,048-entry vocabulary on every decode step |
| seeded-sampling | requests with seed can segfault a tensor-parallel worker |
| gather-logprobs-int32, logprobs-decode-guard | one logprobs request could stop the API server |
| prompt-logprobs | multimodal models return flat prefill states, which the stock runner indexes as 3D |
| mm-merge-idxcopy | multi-image steps fail to graph-compile |
| structured-output-rows, so-row-owners | forced tool calls are grammar-constrained, so their rows must keep their own parameters |
| prompt-order, sampling-rows-refresh, bad-words-index, logitsprocs-rows, async-inputs, rank-exception-fatal | generic request and engine fixes from the profile |
Client usage
- The model card's sampling is temperature 1.0, top_p 0.95, top_k 64; these are the server defaults.
- The model reasons in a separate channel before answering, even for one-word answers. Give it room: a
reply cut off in the reasoning channel has
content: null. tool_choice"required" and named functions are enforced; the model may still reason first, which costs about 150 to 300 tokens per forced call.
Verified
On the port image path exactly as written (stock releases/v0.30.0 image, install.sh, the fix step,
the serve command), two Gaudi 2 cards, ports/muse-glimmer-30b/tests/hpu/run_gates.sh: 11/11 PASS.
| Gate | Result |
|---|---|
| Smoke | Paris; 157*23+89 = 3700 |
| Concurrency | 32 concurrent requests, 32/32 correct (stock v0.30.0: the engine stops on the first two) |
| Needles | 8,078 tokens; 32,078 at 5% and 95% depth; 120,086; 127,070 at 95% depth: 5/5 |
| Vision | three shapes counted, "GAUDI 4729" read; 8 image sizes from 448x448 to 1568x1008 read 8/8 |
| Text after images | Tokyo (no image state carried into a text-only request) |
| Forced tools | "required" 24/24 (8 greedy, 4 at a time; 16 sampled at the defaults), named call with {"city": "Rome", "unit": "celsius"}, "auto" answers in prose. Same image without the grammar patch: 0/8 greedy |
| Streaming tools | "required" streamed: one call with a string id, name and parseable arguments |
| Logprobs | logprobs: 5 on completions 3/3 with no bad ids and the sampled token top-1 at every position; chat top_logprobs: 5 |
| Seeded traffic | 128 seeded requests at 64 concurrent, 0 errors; same-seed repeats identical |
| Reasoning split | content holds the answer, no channel markers |
| Health afterwards | 200 |
Performance
One server, two cards, the Launch command above, synthetic chat prompts of about 285 tokens, 512 output
tokens per request (ignore_eos), sampling at the generation-config defaults, measured after a warm-up
round. Per stream is output tokens over each request's wall time, first token included. Single runs.
| Streams | Aggregate output | Per stream |
|---|---|---|
| 1 | 50 tok/s | 50.1 tok/s |
| 64 | 1,955 tok/s | 30.6 tok/s |
| 192 | 3,362 tok/s | 17.6 tok/s |
Known issues
response_formatwith a JSON schema is not enforced: the grammar waits for the reasoning parser's end signal, which a plain answer never sends. Asked for{name, year: integer}, the model returned a fenced block with other keys and"753 BC"as a string.tool_choice: "none"keeps the tool schemas in the prompt and runs no parser, so a model that calls a tool anyway returns its raw ATEM markup incontent.- DFlash speculative decoding (the model's draft head in vLLM) was not tried on Gaudi.
What not to do
- Do not drop the port and serve the stock image: one concurrent request pair stops the engine.
- Do not lower
--api-server-countto 1 or droptopk-subset-samplerat high concurrency. On a vLLM 0.26 build of this model each one capped a pod's aggregate throughput (the single frontend process, and the full-vocabulary sort at the defaulttop_k); neither was re-measured on this image. - Do not enable only one of the two
muse_glimmerparsers.