Skip to content

Commit b749aa5

Browse files
Merge pull request #264 from runpod-workers/fix/max_num_batched_tokens
fix: max num batched tokens
2 parents c45ac42 + 4705ba8 commit b749aa5

6 files changed

Lines changed: 219 additions & 28 deletions

File tree

.runpod/hub.json

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@
187187
"name": "Max Model Length",
188188
"type": "number",
189189
"description": "Model context length.",
190+
"default": null,
190191
"advanced": true
191192
}
192193
},
@@ -206,7 +207,8 @@
206207
"value": "mp"
207208
}
208209
],
209-
"advanced": true
210+
"advanced": true,
211+
"default": "mp"
210212
}
211213
},
212214
{
@@ -293,6 +295,7 @@
293295
"name": "Max Num Batched Tokens",
294296
"type": "number",
295297
"description": "Maximum number of batched tokens per iteration.",
298+
"default": null,
296299
"advanced": true
297300
}
298301
},
@@ -490,6 +493,61 @@
490493
"advanced": true
491494
}
492495
},
496+
{
497+
"key": "SPECULATIVE_CONFIG",
498+
"input": {
499+
"name": "Speculative Config (JSON)",
500+
"type": "string",
501+
"description": "Full speculative decoding configuration as a JSON string. Overrides individual speculative env vars.",
502+
"advanced": true
503+
}
504+
},
505+
{
506+
"key": "SPECULATIVE_METHOD",
507+
"input": {
508+
"name": "Speculative Method",
509+
"type": "string",
510+
"description": "Speculative decoding method to use.",
511+
"options": [
512+
{ "label": "None", "value": "" },
513+
{ "label": "Draft Model", "value": "draft_model" },
514+
{ "label": "N-gram", "value": "ngram" },
515+
{ "label": "EAGLE", "value": "eagle" },
516+
{ "label": "EAGLE3", "value": "eagle3" },
517+
{ "label": "Medusa", "value": "medusa" },
518+
{ "label": "MLP Speculator", "value": "mlp_speculator" }
519+
],
520+
"default": "",
521+
"advanced": true
522+
}
523+
},
524+
{
525+
"key": "SPECULATIVE_MODEL",
526+
"input": {
527+
"name": "Speculative Model",
528+
"type": "string",
529+
"description": "The name of the draft model to be used in speculative decoding.",
530+
"advanced": true
531+
}
532+
},
533+
{
534+
"key": "NUM_SPECULATIVE_TOKENS",
535+
"input": {
536+
"name": "Num Speculative Tokens",
537+
"type": "number",
538+
"description": "The number of speculative tokens to sample from the draft model.",
539+
"advanced": true
540+
}
541+
},
542+
{
543+
"key": "NGRAM_PROMPT_LOOKUP_MAX",
544+
"input": {
545+
"name": "Ngram Prompt Lookup Max",
546+
"type": "number",
547+
"description": "Max size of window for ngram prompt lookup in speculative decoding.",
548+
"advanced": true
549+
}
550+
},
493551
{
494552
"key": "MODEL_LOADER_EXTRA_CONFIG",
495553
"input": {

Dockerfile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ ARG BASE_PATH="/runpod-volume"
2323
ARG QUANTIZATION=""
2424
ARG MODEL_REVISION=""
2525
ARG TOKENIZER_REVISION=""
26+
ARG VLLM_NIGHTLY="false"
2627

2728
ENV MODEL_NAME=$MODEL_NAME \
2829
MODEL_REVISION=$MODEL_REVISION \
@@ -44,6 +45,11 @@ ENV MODEL_NAME=$MODEL_NAME \
4445

4546
ENV PYTHONPATH="/:/vllm-workspace"
4647

48+
RUN if [ "${VLLM_NIGHTLY}" = "true" ]; then \
49+
pip install -U vllm --pre --index-url https://pypi.org/simple --extra-index-url https://wheels.vllm.ai/nightly && \
50+
apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/* && \
51+
pip install git+https://github.com/huggingface/transformers.git; \
52+
fi
4753

4854
COPY src /src
4955
RUN --mount=type=secret,id=HF_TOKEN,required=false \

docs/configuration.md

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -60,22 +60,32 @@ Complete guide to all environment variables and configuration options for worker
6060
6161
## Speculative Decoding Settings
6262

63-
| Variable | Default | Type/Choices | Description |
64-
| ------------------------------------------------ | ------------------- | --------------------------------------------------- | ----------------------------------------------------------------------------------------- |
65-
| `SCHEDULER_DELAY_FACTOR` | 0.0 | `float` | Apply a delay before scheduling next prompt. |
66-
| `ENABLE_CHUNKED_PREFILL` | False | `bool` | Enable chunked prefill requests. |
67-
| `SPECULATIVE_MODEL` | None | `str` | The name of the draft model to be used in speculative decoding. |
68-
| `NUM_SPECULATIVE_TOKENS` | None | `int` | The number of speculative tokens to sample from the draft model. |
69-
| `SPECULATIVE_DRAFT_TENSOR_PARALLEL_SIZE` | None | `int` | Number of tensor parallel replicas for the draft model. |
70-
| `SPECULATIVE_MAX_MODEL_LEN` | None | `int` | The maximum sequence length supported by the draft model. |
71-
| `SPECULATIVE_DISABLE_BY_BATCH_SIZE` | None | `int` | Disable speculative decoding if the number of enqueue requests is larger than this value. |
72-
| `NGRAM_PROMPT_LOOKUP_MAX` | None | `int` | Max size of window for ngram prompt lookup in speculative decoding. |
73-
| `NGRAM_PROMPT_LOOKUP_MIN` | None | `int` | Min size of window for ngram prompt lookup in speculative decoding. |
74-
| `SPEC_DECODING_ACCEPTANCE_METHOD` | 'rejection_sampler' | ['rejection_sampler', 'typical_acceptance_sampler'] | Specify the acceptance method for draft token verification in speculative decoding. |
75-
| `TYPICAL_ACCEPTANCE_SAMPLER_POSTERIOR_THRESHOLD` | None | `float` | Set the lower bound threshold for the posterior probability of a token to be accepted. |
76-
| `TYPICAL_ACCEPTANCE_SAMPLER_POSTERIOR_ALPHA` | None | `float` | A scaling factor for the entropy-based threshold for token acceptance. |
77-
78-
## System Performance Settings
63+
Speculative decoding can be configured in two ways:
64+
65+
### Option 1: JSON Configuration
66+
67+
Set `SPECULATIVE_CONFIG` to a JSON string with your full speculative decoding configuration:
68+
69+
```bash
70+
SPECULATIVE_CONFIG='{"method": "ngram", "num_speculative_tokens": 5, "prompt_lookup_max": 4}'
71+
```
72+
73+
### Option 2: Individual Environment Variables
74+
75+
| Variable | Default | Type/Choices | Description |
76+
| ---------------------------------------- | ------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------- |
77+
| `SPECULATIVE_METHOD` | None | ['draft_model', 'ngram', 'eagle', 'eagle3', 'medusa', 'mlp_speculator'] | Speculative decoding method to use. |
78+
| `SPECULATIVE_MODEL` | None | `str` | The name of the draft model to be used in speculative decoding. |
79+
| `NUM_SPECULATIVE_TOKENS` | None | `int` | The number of speculative tokens to sample from the draft model. |
80+
| `SPECULATIVE_DRAFT_TENSOR_PARALLEL_SIZE` | None | `int` | Number of tensor parallel replicas for the draft model. |
81+
| `SPECULATIVE_MAX_MODEL_LEN` | None | `int` | The maximum sequence length supported by the draft model. |
82+
| `SPECULATIVE_DISABLE_BY_BATCH_SIZE` | None | `int` | Disable speculative decoding if the number of enqueue requests is larger than this value. |
83+
| `NGRAM_PROMPT_LOOKUP_MAX` | None | `int` | Max size of window for ngram prompt lookup in speculative decoding. |
84+
| `NGRAM_PROMPT_LOOKUP_MIN` | None | `int` | Min size of window for ngram prompt lookup in speculative decoding. |
85+
86+
If `SPECULATIVE_CONFIG` is set, it takes priority over individual env vars. When using individual env vars without `SPECULATIVE_METHOD`, the method is auto-detected from the model name or configuration.
87+
88+
## Scheduling & Performance Settings
7989

8090
| Variable | Default | Type/Choices | Description |
8191
| ------------------------------ | ------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------- |

src/engine.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ def _initialize_llm(self):
175175
class OpenAIvLLMEngine(vLLMEngine):
176176
def __init__(self, vllm_engine):
177177
super().__init__(vllm_engine)
178-
self.served_model_name = os.getenv("OPENAI_SERVED_MODEL_NAME_OVERRIDE") or self.engine_args.model
178+
self.served_model_name = os.getenv("OPENAI_SERVED_MODEL_NAME_OVERRIDE") or self.engine_args.served_model_name or self.engine_args.model
179179
self.response_role = os.getenv("OPENAI_RESPONSE_ROLE") or "assistant"
180180
self.lora_adapters = self._load_lora_adapters()
181181

@@ -233,7 +233,7 @@ async def _ensure_engines_initialized(self):
233233
async def _initialize_engines(self):
234234
self.model_config = self.llm.model_config
235235
self.base_model_paths = [
236-
BaseModelPath(name=self.engine_args.model, model_path=self.engine_args.model)
236+
BaseModelPath(name=self.served_model_name, model_path=self.engine_args.model)
237237
]
238238

239239
self.serving_models = OpenAIServingModels(

src/engine_args.py

Lines changed: 122 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,110 @@
100100
"disable_logprobs_during_spec_decoding": os.getenv('DISABLE_LOGPROBS_DURING_SPEC_DECODING', None),
101101
"otlp_traces_endpoint": os.getenv('OTLP_TRACES_ENDPOINT', None),
102102
}
103+
104+
def get_speculative_config():
105+
"""Build speculative decoding configuration from environment variables.
106+
107+
Supports two modes:
108+
1. Full JSON config via SPECULATIVE_CONFIG env var
109+
2. Individual env vars for common settings
110+
"""
111+
# Option 1: Full JSON configuration
112+
spec_config_json = os.getenv('SPECULATIVE_CONFIG')
113+
if spec_config_json:
114+
try:
115+
config = json.loads(spec_config_json)
116+
logging.info(f"Using speculative config from SPECULATIVE_CONFIG: {config}")
117+
return config
118+
except json.JSONDecodeError as e:
119+
logging.error(f"Failed to parse SPECULATIVE_CONFIG JSON: {e}")
120+
return None
121+
122+
# Option 2: Build config from individual environment variables
123+
spec_method = os.getenv('SPECULATIVE_METHOD')
124+
spec_model = os.getenv('SPECULATIVE_MODEL')
125+
num_spec_tokens = os.getenv('NUM_SPECULATIVE_TOKENS')
126+
ngram_max = os.getenv('NGRAM_PROMPT_LOOKUP_MAX')
127+
ngram_min = os.getenv('NGRAM_PROMPT_LOOKUP_MIN')
128+
129+
if not any([spec_method, spec_model, ngram_max]):
130+
return None
131+
132+
config = {}
133+
134+
# Determine method
135+
if spec_method:
136+
config['method'] = spec_method
137+
elif ngram_max and not spec_model:
138+
config['method'] = 'ngram'
139+
elif spec_model:
140+
model_lower = spec_model.lower()
141+
if 'eagle3' in model_lower:
142+
config['method'] = 'eagle3'
143+
elif 'eagle' in model_lower:
144+
config['method'] = 'eagle'
145+
elif 'medusa' in model_lower:
146+
config['method'] = 'medusa'
147+
else:
148+
config['method'] = 'draft_model'
149+
150+
if spec_model:
151+
config['model'] = spec_model
152+
if num_spec_tokens:
153+
config['num_speculative_tokens'] = int(num_spec_tokens)
154+
if ngram_max:
155+
config['prompt_lookup_max'] = int(ngram_max)
156+
if ngram_min:
157+
config['prompt_lookup_min'] = int(ngram_min)
158+
159+
draft_tp = os.getenv('SPECULATIVE_DRAFT_TENSOR_PARALLEL_SIZE')
160+
if draft_tp:
161+
config['draft_tensor_parallel_size'] = int(draft_tp)
162+
163+
spec_max_len = os.getenv('SPECULATIVE_MAX_MODEL_LEN')
164+
if spec_max_len:
165+
config['max_model_len'] = int(spec_max_len)
166+
167+
disable_batch = os.getenv('SPECULATIVE_DISABLE_BY_BATCH_SIZE')
168+
if disable_batch:
169+
config['disable_by_batch_size'] = int(disable_batch)
170+
171+
spec_quant = os.getenv('SPECULATIVE_QUANTIZATION')
172+
if spec_quant:
173+
config['quantization'] = spec_quant
174+
175+
spec_revision = os.getenv('SPECULATIVE_MODEL_REVISION')
176+
if spec_revision:
177+
config['revision'] = spec_revision
178+
179+
spec_eager = os.getenv('SPECULATIVE_ENFORCE_EAGER')
180+
if spec_eager:
181+
config['enforce_eager'] = spec_eager.lower() == 'true'
182+
183+
if config:
184+
logging.info(f"Built speculative config from env vars: {config}")
185+
return config
186+
187+
return None
188+
189+
def _resolve_max_model_len(model, trust_remote_code=False, revision=None):
190+
"""Resolve max_model_len from the model's HuggingFace config."""
191+
try:
192+
from transformers import AutoConfig
193+
config = AutoConfig.from_pretrained(
194+
model,
195+
trust_remote_code=trust_remote_code,
196+
revision=revision,
197+
)
198+
for attr in ('max_position_embeddings', 'n_positions', 'max_seq_len', 'seq_length'):
199+
val = getattr(config, attr, None)
200+
if val is not None:
201+
logging.info(f"Resolved max_model_len={val} from model config ({attr})")
202+
return val
203+
except Exception as e:
204+
logging.warning(f"Could not resolve max_model_len from model config: {e}")
205+
return None
206+
103207
limit_mm_env = os.getenv('LIMIT_MM_PER_PROMPT')
104208
if limit_mm_env is not None:
105209
DEFAULT_ARGS["limit_mm_per_prompt"] = convert_limit_mm_per_prompt(limit_mm_env)
@@ -182,11 +286,19 @@ def get_engine_args():
182286
# os.environ["VLLM_ATTENTION_BACKEND"] = "FLASHINFER"
183287
# logging.info("Using FLASHINFER for gemma-2 model.")
184288

185-
# When max_num_batched_tokens is None (env var was 0), set to max_model_len
186-
# to preserve "unlimited" behavior. vLLM defaults None to 2048.
187-
if args.get("max_num_batched_tokens") is None and args.get("max_model_len") is not None:
188-
args["max_num_batched_tokens"] = args["max_model_len"]
189-
logging.info(f"Setting max_num_batched_tokens to max_model_len ({args['max_model_len']}) for unlimited batching.")
289+
# Set max_num_batched_tokens to max_model_len for unlimited batching.
290+
# vLLM defaults max_num_batched_tokens to 2048 when None, which is too low.
291+
if args.get("max_num_batched_tokens") is None:
292+
max_model_len = args.get("max_model_len")
293+
if max_model_len is None:
294+
max_model_len = _resolve_max_model_len(
295+
args.get("model"),
296+
trust_remote_code=args.get("trust_remote_code", False),
297+
revision=args.get("revision"),
298+
)
299+
if max_model_len is not None:
300+
args["max_num_batched_tokens"] = max_model_len
301+
logging.info(f"Setting max_num_batched_tokens to {max_model_len}")
190302

191303
# VLLM_ATTENTION_BACKEND is deprecated, migrate to attention_backend
192304
if os.getenv('VLLM_ATTENTION_BACKEND'):
@@ -207,4 +319,9 @@ def get_engine_args():
207319
if os.getenv('DISABLE_LOG_REQUESTS', 'False').lower() == 'true':
208320
args['enable_log_requests'] = False
209321

322+
# Add speculative decoding configuration if present
323+
speculative_config = get_speculative_config()
324+
if speculative_config:
325+
args["speculative_config"] = speculative_config
326+
210327
return AsyncEngineArgs(**args)

src/utils.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
try:
88
from vllm.utils import random_uuid
9-
from vllm.entrypoints.openai.engine.protocol import ErrorResponse, RequestResponseMetadata
9+
from vllm.entrypoints.openai.engine.protocol import ErrorResponse, ErrorInfo, RequestResponseMetadata
1010
from vllm import SamplingParams
1111
except ImportError:
1212
logging.warning("Error importing vllm, skipping related imports. This is ONLY expected when baking model into docker image from a machine without GPUs")
@@ -87,9 +87,9 @@ def update(self):
8787
self.current_batch_size = min(self.current_batch_size*self.batch_size_growth_factor, self.max_batch_size)
8888

8989
def create_error_response(message: str, err_type: str = "BadRequestError", status_code: HTTPStatus = HTTPStatus.BAD_REQUEST) -> ErrorResponse:
90-
return ErrorResponse(message=message,
91-
type=err_type,
92-
code=status_code.value)
90+
return ErrorResponse(error=ErrorInfo(message=message,
91+
type=err_type,
92+
code=status_code.value))
9393

9494
def get_int_bool_env(env_var: str, default: bool) -> bool:
9595
return int(os.getenv(env_var, int(default))) == 1

0 commit comments

Comments
 (0)