Qwen3.5-9B simple inference
Chat with Qwen3.5-9B on a single L4.
Hardware: gpu_1x_l4. Save the files under Source files into a folder named
qwen3.5-9b, then follow the steps below.
Downloads Qwen3.5-9B — the largest
dense Qwen3.5 model that fits a single 24 GB L4 in BF16 — into a persistent
volume and runs one chat completion through transformers on one L4 GPU
provisioned via supercomputer. The first run pulls ~19.3 GB of BF16 weights into the
qwen3.5-9b-weights volume; every subsequent run that mounts the volume skips
straight to load (measured: 0 bytes re-downloaded).
Measured on an L4: peak 18.0 GB / 24 GB VRAM, ~11 tok/s for single-prompt decoding. The ~6 GB of headroom is your KV-cache + activation budget.
Fit notes
- 9B BF16 is the dense ceiling for an L4. Weights are ~18 GB; 14B BF16
(~28 GB) will not fit. If you need more capability on the same L4,
Qwen3.5-27B-GPTQ-Int4loads in ~15 GB — but that's a different load path (an INT4 kernel, not the plain BF16from_pretrainedused here). - Context, not weights, is what OOMs an L4. Qwen3.5's native context is
262K. This script uses
transformersgenerate(), which grows the KV cache with the actual sequence length (no 262K pre-allocation), so short prompts stay cheap. Keep prompt +--max-new-tokensto a few thousand tokens and you stay inside the 6 GB headroom.
What's here
supercomputer.json— image config: PyTorch 2.10 + CUDA 12.8 (cuDNN 9) withtransformers,accelerate, andhf_transfer. Declares aqwen3.5-9b-weightsvolume mounted at/workspace/.cache/huggingface(whereHF_HOMEpoints), so the weights are cached once and reused by any future session that attaches the volume.infer.py— runs inside the session. LoadsQwen/Qwen3.5-9Bin BF16 on the single GPU, runs one chat completion, and prints the answer, tokens/s, and peak VRAM.--promptoverrides the default question;--no-thinkingdrops the<think>reasoning blocks.
Usage
From this directory (so supercomputer picks up the local supercomputer.json):
# 0. Sanity check.
super health
# 1. Create the weights volume — one-time per workspace.
super volume create qwen3.5-9b-weights
# 2. One-shot: upload + run in a fresh session. First run downloads ~19.3 GB
# of weights into the volume; subsequent runs skip the download.
super run infer.py -- --no-thinking --max-new-tokens 256
# --- OR --- keep a persistent session for repeated experiments:
super create --sku gpu_1x_l4 --json
# {"session_id": "s-XXXX", ...}
super upload s-XXXX infer.py infer.py
super exec s-XXXX -- python3 /workspace/infer.py --prompt "Explain attention in one sentence."
super stop s-XXXXNote:
super run … -- --prompt "multi word"currently mis-splits the value (therunpath joins argv without shell-quoting). For a multi-word prompt, use a persistent session withsuper execas above, or pass a single-token--prompt. The in-script default prompt works fine withsuper run.
Source files
infer.py
#!/usr/bin/env python3
"""Single-prompt inference with Qwen3.5-9B on a single L4.
Loads Qwen/Qwen3.5-9B in BF16 on one GPU, runs one chat completion
through transformers' generate(), and prints the answer, tokens/s, and
peak VRAM. Qwen3.5-9B is the largest *dense* Qwen3.5 that fits a 24 GB
L4 in BF16 (~18 GB of weights, ~20 GB peak). Meant to run INSIDE a supercomputer
session (see README.md). Weights download once into the qwen3.5-9b-weights
volume; later runs that attach the volume skip straight to load.
"""
from __future__ import annotations
import argparse
import time
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
MODEL = "Qwen/Qwen3.5-9B"
def parse_args() -> argparse.Namespace:
"""Command-line knobs for prompt, output length, and decoding mode."""
p = argparse.ArgumentParser(description="Qwen3.5-9B simple inference")
p.add_argument("--prompt",
default="Give me a short introduction to large language models.")
p.add_argument("--max-new-tokens", type=int, default=512)
p.add_argument("--temperature", type=float, default=1.0)
p.add_argument("--no-thinking", action="store_true",
help="disable Qwen3.5 <think> reasoning blocks")
return p.parse_args()
def main() -> None:
"""Load the model, run one generation, print the answer and tokens/s."""
args = parse_args()
tokenizer = AutoTokenizer.from_pretrained(MODEL)
model = AutoModelForCausalLM.from_pretrained(
MODEL,
torch_dtype=torch.bfloat16,
device_map="auto",
)
messages = [{"role": "user", "content": args.prompt}]
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
enable_thinking=not args.no_thinking,
)
inputs = tokenizer([text], return_tensors="pt").to(model.device)
start = time.perf_counter()
output_ids = model.generate(
**inputs,
max_new_tokens=args.max_new_tokens,
do_sample=args.temperature > 0,
temperature=args.temperature,
top_p=1.0,
)
elapsed = time.perf_counter() - start
new_tokens = output_ids[0][inputs.input_ids.shape[1]:]
response = tokenizer.decode(new_tokens, skip_special_tokens=True)
print("--- prompt ---")
print(args.prompt)
print("\n--- completion ---")
print(response.strip())
print("\n--- stats ---")
print(f"generated tokens: {len(new_tokens)}")
print(f"wall time: {elapsed:.2f}s")
print(f"throughput: {len(new_tokens) / elapsed:.1f} tok/s")
if torch.cuda.is_available():
peak = torch.cuda.max_memory_allocated() / 1e9
print(f"peak VRAM: {peak:.1f} GB / 24 GB (L4)")
if __name__ == "__main__":
main()supercomputer.json
{
"sku": "gpu_1x_l4",
"image": {
"base": "pytorch/pytorch:2.10.0-cuda12.8-cudnn9-devel",
"pip": [
"transformers>=4.51",
"accelerate",
"hf_transfer"
],
"env": {
"PYTHONUNBUFFERED": "1",
"HF_HOME": "/workspace/.cache/huggingface",
"HF_HUB_CACHE": "/workspace/.cache/huggingface/hub",
"TRANSFORMERS_CACHE": "/workspace/.cache/huggingface/hub",
"HF_XET_CACHE_DIR": "/workspace/.cache/huggingface/xet",
"HF_HUB_ENABLE_HF_TRANSFER": "1"
}
},
"volumes": [
{"name": "qwen3.5-9b-weights", "mount": "/workspace/.cache/huggingface"}
]
}