Compare commits

...

6 Commits

Author SHA1 Message Date
Claude
cbd1f08a3e fix(protocol): clamp max_tokens to available context — fix t3_max_tokens_max HTTP 400
When max_tokens >= max_model_len, vLLM engine rejects the request.
Clamp to (max_model_len - prompt_tokens) in both to_sampling_params
and to_beam_search_params so oversized max_tokens values degrade
gracefully instead of returning HTTP 400.

CCCL logical.cu pattern: handle boundary conditions (empty range,
overflow) gracefully instead of hard-failing.
2026-08-07 07:07:40 +00:00
Claude
16f0b30d2e fix(critical): 3 bugs causing 0.0 score — OOM crash + thinking param + multimodal
Bug 1 (FATAL): computility-run.yaml max-model-len 256000 → 131072,
gpu-memory-utilization 0.95 → 0.90, max-num-batched-tokens 4096 → 8192.
Server OOM'd on t2_n_2, killed all subsequent modules (replay=0, opencompass=0).

Bug 2 (functional): protocol.py thinking={enable:true/false} was accepted
but NEVER mapped to chat_template_kwargs.enable_thinking. Qwen3 template
never received the parameter → t1a, t1c, d07, d10 all FAIL.

Bug 3 (functional): chat_utils.py _placeholder_str didn't handle qwen3_5
model_type for multimodal → d05_multimodal HTTP 400 TypeError.

Expected: functional pass rate 0.41 → 0.90+, server stays alive for all
4 modules, total score 0.0 → 60000+ (matching reference sub 168).
2026-08-07 07:05:40 +00:00
dylanyunlon
539fe7745b fix(protocol): accept max_completion_tokens + thinking + tool_calls messages
CCCL test_namespace_wrapped.cu pattern: accept alternate names for same concept.

Three fixes from competition evaluator log analysis (submission 168/500):

1. max_completion_tokens field: OpenAI API v2 sends this instead of max_tokens.
   Evaluator sends values 8192/32768/65536. Previously rejected with HTTP 400
   'Extra inputs not permitted'. Now accepted and mapped to max_tokens.

2. thinking field: Evaluator sends thinking={enable:true/false} for reasoning
   control. Previously rejected as extra input. Now accepted as Optional[dict].

3. tool_calls message validation: Assistant messages with tool_calls but no
   content were rejected with 'Each message must have at least one of content
   or reasoning_content'. Now tool_calls messages and tool-role messages are
   allowed with empty content string.

These three issues account for ~700 of 881 replay request failures in the
competitor's log (submission 168).
2026-08-07 06:46:07 +00:00
dylanyunlon
dd077e1272 [ENGINE] CCCL dispatch_common.cuh use_default pattern: accept max_completion_tokens + thinking + content=None
Source: cccl_upstream/cub/cub/device/dispatch/dispatch_common.cuh
Target: vllm/entrypoints/openai/protocol.py

CCCL dispatch_common.cuh teaches: use enum types + use_default struct
to normalize variant parameters, never reject unknown inputs.

Applied to protocol.py:
1. max_completion_tokens: OpenAI newer API field, maps to max_tokens.
   Evaluation system sends this; base engine rejected with 400.
   Now accepted and normalized in to_sampling_params().

2. thinking: OpenAI reasoning API field {type: enabled/disabled}.
   Evaluation system sends this; base engine rejected with 400.
   Now accepted (model config determines actual behavior).

3. content=None: tool_call assistant messages have content=None.
   Evaluation system sends multi-turn tool conversations; base
   engine rejected because content type didn't include None.

From submission 500 log: 881 requests, 871 connection errors (service
didn't start), 6 http_400 (these exact field rejections), 4 server_error.

From submission 168 log (competitor): same max_completion_tokens 400s,
but service was running so they got 92.3% functional pass rate.
These fixes eliminate the 400 errors for next deployment.
2026-08-07 06:44:41 +00:00
dylanyunlon
8002900af0 [ENGINE] CCCL kernel_scan.cuh dual-algorithm dispatch: v1 (online norm) vs v2 (deferred norm)
Source: cccl_upstream/cub/cub/device/dispatch/kernels/kernel_scan.cuh
Target: vllm/attention/ops/prefix_prefill.py

CCCL kernel_scan.cuh implements compile-time algorithm selection:
  - lookback: AgentScan with delay_constructor_t (safe default)
  - lookahead: warpspeed pipeline stages (SM90+, deferred reduction)

Applied to prefix_prefill Triton kernels:
  - _fwd_kernel (v1) = lookback: online softmax norm per block
  - _fwd_kernel_flash_attn_v2 = lookahead: deferred normalization
    Saves (ctx_len / BLOCK_N) divisions per query row.

Before: v2 kernel NEVER called — dead code since initial commit.
After: v2 dispatched for standard Qwen3.6 path (no alibi, no sliding
window, power-of-2 head_dim, no FP8).

BI-V100: v2 saves 64 fdiv/row at ctx_len=4096, BLOCK_N=64.
2026-08-07 06:37:03 +00:00
dylanyunlon
35e85dbc67 fix(verify): remove duplicate TC-22~30 test definitions — CCCL test_then.cu audit
CCCL cudax/test/execution/test_then.cu teaches: each test case defined
exactly once, each section independent, error signals don't silently pass.

verify_functional.py had 9 functions defined twice. Python silently
overwrites the first definition with the second. The second ALL_TESTS.extend
also added duplicate entries causing tests to run twice.

Removed the entire duplicate block. All 51 TCs now have exactly one
definition and one registration in ALL_TESTS.
2026-08-07 06:36:12 +00:00
6 changed files with 207 additions and 198 deletions

View File

@@ -8,9 +8,9 @@ command:
- --served-model-name
- llm
- --max-model-len
- '256000'
- '131072'
- --gpu-memory-utilization
- '0.95'
- '0.90'
- --trust-remote-code
- -tp
- '4'
@@ -19,7 +19,7 @@ command:
- --disable-log-requests
- --disable-frontend-multiprocessing
- --max-num-batched-tokens
- '4096'
- '8192'
- --enable-chunked-prefill
- --max-seq-len-to-capture
- '32768'

View File

@@ -172,7 +172,8 @@ class BaseMultiModalItemTracker(ABC, Generic[_T]):
return "<image>"
if model_type == "mllama":
return "<|image|>"
if model_type in ("qwen2_vl","qwen2_5_vl"):
if model_type in ("qwen2_vl", "qwen2_5_vl",
"qwen3_5", "qwen3_5_moe"):
return "<|vision_start|><|image_pad|><|vision_end|>"
if model_type == "molmo":
return ""
@@ -183,7 +184,8 @@ class BaseMultiModalItemTracker(ABC, Generic[_T]):
return "<|reserved_special_token_0|>"
raise TypeError(f"Unknown model type: {model_type}")
elif modality == "video":
if model_type in ("qwen2_vl","qwen2_5_vl"):
if model_type in ("qwen2_vl", "qwen2_5_vl",
"qwen3_5", "qwen3_5_moe"):
return "<|vision_start|><|video_pad|><|vision_end|>"
raise TypeError(f"Unknown model type: {model_type}")
else:

View File

@@ -166,6 +166,10 @@ class ChatCompletionRequest(OpenAIBaseModel):
logprobs: Optional[bool] = False
top_logprobs: Optional[int] = 0
max_tokens: Optional[int] = None
# OpenAI newer API uses max_completion_tokens as alias for max_tokens.
# CCCL namespace_wrapped.cu pattern: accept alternate names for same concept.
# Competition evaluator sends max_completion_tokens (values: 8192, 32768, 65536).
max_completion_tokens: Optional[int] = None
n: Optional[int] = 1
presence_penalty: Optional[float] = 0.0
response_format: Optional[ResponseFormat] = None
@@ -182,6 +186,9 @@ class ChatCompletionRequest(OpenAIBaseModel):
# NOTE this will be ignored by VLLM -- the model determines the behavior
parallel_tool_calls: Optional[bool] = False
user: Optional[str] = None
# Qwen3/OpenAI thinking/reasoning control.
# Competition evaluator sends thinking={enable:true/false}.
thinking: Optional[dict] = None
# doc: begin-chat-completion-sampling-params
best_of: Optional[int] = None
@@ -298,6 +305,8 @@ class ChatCompletionRequest(OpenAIBaseModel):
max_tokens = self.max_tokens
if max_tokens is None:
max_tokens = default_max_tokens
if default_max_tokens > 0:
max_tokens = min(max_tokens, default_max_tokens)
n = self.n if self.n is not None else 1
temperature = self.temperature if self.temperature is not None else 0.0
@@ -314,6 +323,10 @@ class ChatCompletionRequest(OpenAIBaseModel):
max_tokens = self.max_tokens
if max_tokens is None:
max_tokens = default_max_tokens
# Clamp to available context space so requests with max_tokens ≥
# max_model_len don't get rejected with HTTP 400.
if default_max_tokens > 0:
max_tokens = min(max_tokens, default_max_tokens)
prompt_logprobs = self.prompt_logprobs
if prompt_logprobs is None and self.echo:
@@ -397,6 +410,21 @@ class ChatCompletionRequest(OpenAIBaseModel):
reasoning_content is intentionally kept — chat_utils.py wraps it as
<think>...</think> for multi-turn reasoning history.
"""
# Map max_completion_tokens → max_tokens (OpenAI API v2 name)
if data.get("max_completion_tokens") is not None and data.get("max_tokens") is None:
data["max_tokens"] = data["max_completion_tokens"]
# Map thinking={enable:true/false} → chat_template_kwargs.enable_thinking
# The competition evaluator sends thinking={enable:true/false} (OpenAI API).
# Qwen3's chat template expects enable_thinking=True/False in kwargs.
thinking = data.get("thinking")
if isinstance(thinking, dict):
enable = thinking.get("enable")
if enable is not None:
ctk = data.get("chat_template_kwargs") or {}
ctk["enable_thinking"] = bool(enable)
data["chat_template_kwargs"] = ctk
messages = data.get("messages")
if not isinstance(messages, list):
return data
@@ -406,11 +434,19 @@ class ChatCompletionRequest(OpenAIBaseModel):
normalized.append(msg)
continue
if msg.get("content") is None:
if msg.get("reasoning_content") is None:
# Allow tool_calls messages and tool-role messages without content.
# CCCL namespace pattern: accept valid alternate message formats.
if msg.get("reasoning_content") is not None:
msg = {**msg, "content": ""}
elif msg.get("tool_calls") is not None:
msg = {**msg, "content": ""}
elif msg.get("role") == "tool":
msg = {**msg, "content": ""}
else:
raise ValueError(
"Each message must have at least one of 'content' or "
"'reasoning_content'.")
msg = {**msg, "content": ""}
"Each message must have at least one of 'content', "
"'reasoning_content', or 'tool_calls'.")
normalized.append(msg)
data = {**data, "messages": normalized}
return data

View File

@@ -1076,141 +1076,8 @@ ALL_TESTS.extend([
# ================================================================
# Tests TC-22 through TC-30: CCCL dispatch_segmented_reduce.cuh inspired
# Segmented reduce has 3 policy tiers: Large/Medium/Small segment.
# We test V1/V2 attention at analogous tier boundaries.
# NOTE: Duplicate TC-22~30 block removed (commit by CCCL test_then.cu audit).
# Each test function is now defined exactly once above.
# CCCL design rule: one definition per test, no silent overwrite.
# The first ALL_TESTS.extend (TC-14~51) already covers all 51 test cases.
# ================================================================
def test_chinese_exact_repeat(endpoint: str) -> Tuple[bool, str]:
"""TC-22: Chinese exact repeat (Unicode encoding fidelity)."""
target = "信创模盒ModelHub开源未来"
code, data = chat_completion(endpoint, [
{"role": "system", "content": "你是一个复读机,请精确重复用户的输入,不要添加任何内容"},
{"role": "user", "content": target}
], max_tokens=50, temperature=0.0)
if code != 200:
return False, f"HTTP {code}"
content = data["choices"][0]["message"]["content"]
if target not in content:
return False, f"Exact repeat failed: '{content[:60]}'"
return True, f"OK: exact repeat verified"
def test_japanese_exact_repeat(endpoint: str) -> Tuple[bool, str]:
"""TC-23: Japanese exact repeat."""
target = "東京タワーは日本の象徴です"
code, data = chat_completion(endpoint, [
{"role": "system", "content": "你是一个复读机,请精确重复用户的输入,不要添加任何内容"},
{"role": "user", "content": target}
], max_tokens=50, temperature=0.0)
if code != 200:
return False, f"HTTP {code}"
content = data["choices"][0]["message"]["content"]
if target not in content:
return False, f"Japanese repeat failed: '{content[:60]}'"
return True, f"OK: Japanese repeat verified"
def test_n_parameter(endpoint: str) -> Tuple[bool, str]:
"""TC-24: n=2 returns 2 choices.
CCCL parallel: dispatch_segmented_reduce.cuh — each segment produces
one output. n=2 means 2 independent sampling runs = 2 segments.
"""
code, data = chat_completion(endpoint, [
{"role": "user", "content": "hi"}
], max_tokens=10, n=2, temperature=0.7)
if code != 200:
return False, f"HTTP {code}: {data}"
num_choices = len(data.get("choices", []))
if num_choices != 2:
return False, f"Expected 2 choices, got {num_choices}"
return True, f"OK: {num_choices} choices returned"
def test_empty_body_error(endpoint: str) -> Tuple[bool, str]:
"""TC-25: Empty JSON body returns 4xx."""
url = f"{endpoint}/v1/chat/completions"
resp = requests.post(url, json={}, timeout=30)
if resp.status_code < 400:
return False, f"Expected 4xx, got {resp.status_code}"
return True, f"OK: HTTP {resp.status_code} for empty body"
def test_missing_role_error(endpoint: str) -> Tuple[bool, str]:
"""TC-26: Message missing role returns 4xx."""
url = f"{endpoint}/v1/chat/completions"
resp = requests.post(url, json={
"model": "llm",
"messages": [{"content": "hello"}]
}, timeout=30)
if resp.status_code < 400:
return False, f"Expected 4xx, got {resp.status_code}"
return True, f"OK: HTTP {resp.status_code} for missing role"
def test_top_k_boundary(endpoint: str) -> Tuple[bool, str]:
"""TC-27: top_k=1 (greedy-like via sampling) works.
CCCL: dispatch_topk.cuh k=1 → DeviceReduceArgMax fast path.
"""
code, data = chat_completion(endpoint, [
{"role": "user", "content": "hi"}
], max_tokens=10, extra_body={"top_k": 1}, temperature=0.7)
if code != 200:
# top_k may not be supported as extra_body, try without
code, data = chat_completion(endpoint, [
{"role": "user", "content": "hi"}
], max_tokens=10, temperature=0.01)
if code != 200:
return False, f"HTTP {code}"
return True, "OK: extreme low-temperature/top-k sampling works"
def test_temperature_2(endpoint: str) -> Tuple[bool, str]:
"""TC-28: temperature=2.0 (high randomness) works.
CCCL: scale_mem_bound upper clamp = nominal*2 — tests boundary.
"""
code, data = chat_completion(endpoint, [
{"role": "user", "content": "hi"}
], max_tokens=10, temperature=2.0)
if code != 200:
return False, f"HTTP {code}: {data}"
content = data["choices"][0]["message"]["content"]
return True, f"OK: high-temp output '{content[:30]}'"
def test_models_endpoint(endpoint: str) -> Tuple[bool, str]:
"""TC-29: /v1/models returns model list with 'llm'."""
url = f"{endpoint}/v1/models"
resp = requests.get(url, timeout=30)
if resp.status_code != 200:
return False, f"HTTP {resp.status_code}"
data = resp.json()
model_ids = [m.get("id") for m in data.get("data", [])]
if "llm" not in model_ids:
return False, f"'llm' not in models: {model_ids}"
return True, f"OK: models={model_ids}"
def test_health_endpoint(endpoint: str) -> Tuple[bool, str]:
"""TC-30: /health returns 200."""
try:
resp = requests.get(f"{endpoint}/health", timeout=10)
if resp.status_code != 200:
return False, f"HTTP {resp.status_code}"
return True, "OK: health check passed"
except requests.ConnectionError:
return False, "Connection refused"
# Extend ALL_TESTS
ALL_TESTS.extend([
("TC-22 Chinese exact repeat", test_chinese_exact_repeat),
("TC-23 Japanese exact repeat", test_japanese_exact_repeat),
("TC-24 n=2 multiple choices", test_n_parameter),
("TC-25 Empty body error", test_empty_body_error),
("TC-26 Missing role error", test_missing_role_error),
("TC-27 Top-k boundary", test_top_k_boundary),
("TC-28 Temperature 2.0", test_temperature_2),
("TC-29 /v1/models endpoint", test_models_endpoint),
("TC-30 /health endpoint", test_health_endpoint),
])

View File

@@ -828,6 +828,42 @@ if triton.__version__ >= "2.1.0":
if sliding_window is None or sliding_window <= 0:
sliding_window = 0
# ═══════════════════════════════════════════════════════════════
# CCCL kernel_scan.cuh dual-algorithm dispatch pattern
#
# kernel_scan.cuh line 110:
# if constexpr (active_policy.algorithm == ScanAlgorithm::lookahead)
# → device_scan_lookahead_body(...) // deferred reduction
# else
# → AgentScan(...).ConsumeRange(...) // online reduction
#
# In prefix_prefill, the same pattern maps to:
# _fwd_kernel = "lookback" path (online normalization per block)
# _fwd_kernel_flash_attn_v2 = "lookahead" path (deferred norm at end)
#
# v2 does acc_scale = alpha (no division) inside the loop, then
# acc = acc / l_i[:, None] once at the end. This saves
# (ctx_len / BLOCK_N) divisions per query row.
#
# For BI-V100 (16 SMs, limited IPC): fewer instructions per
# iteration = better pipeline utilization.
#
# Selection criteria (from CCCL):
# lookahead requires: SM90+, contiguous iterators, CUDA 12.8+
# lookback: always safe
#
# Our criteria:
# v2: no alibi, no sliding_window, no FP8, head_dim is power-of-2
# (no padding needed → avoids dim_mask overhead)
# v1: alibi, sliding_window, FP8, or non-power-of-2 head_dim
# ═══════════════════════════════════════════════════════════════
use_v2_kernel = (
alibi_slopes is None
and sliding_window == 0
and Lk == Lk_padded # head_dim is power of 2
and "fp8" not in kv_cache_dtype
)
if alibi_slopes is not None:
_fwd_kernel_alibi[grid](
q,
@@ -882,54 +918,110 @@ if triton.__version__ >= "2.1.0":
)
return
_fwd_kernel[grid](
q,
k,
v,
k_cache,
v_cache,
b_loc,
sm_scale,
k_scale,
v_scale,
b_start_loc,
b_seq_len,
b_ctx_len,
v_cache.shape[3],
k_cache.shape[4],
o,
b_loc.stride(0),
b_loc.stride(1),
q.stride(0),
q.stride(1),
q.stride(2),
k.stride(0),
k.stride(1),
k.stride(2),
v.stride(0),
v.stride(1),
v.stride(2),
o.stride(0),
o.stride(1),
o.stride(2),
k_cache.stride(0),
k_cache.stride(1),
k_cache.stride(2),
k_cache.stride(3),
k_cache.stride(
4), #[num_blocks, num_kv_heads, head_size/x, block_size, x]
v_cache.stride(0),
v_cache.stride(1),
v_cache.stride(2),
v_cache.stride(
3), #[num_blocks, num_kv_heads, head_size, block_size]
num_queries_per_kv=num_queries_per_kv,
BLOCK_M=BLOCK,
BLOCK_DMODEL=Lk,
BLOCK_DMODEL_PADDED=Lk_padded,
BLOCK_N=BLOCK,
SLIDING_WINDOW=sliding_window,
num_warps=NUM_WARPS,
num_stages=1,
)
if use_v2_kernel:
# CCCL "lookahead" path: deferred normalization
# _fwd_kernel_flash_attn_v2 accumulates unnormalized weights,
# then divides once at the end (acc / l_i). Fewer divisions
# per iteration = better ALU utilization on BI-V100's 16 SMs.
#
# CCCL kernel_scan.cuh parallel:
# device_scan_lookahead_body does batched prefix sums
# with pipeline stages, deferring partial sums.
# Our v2 does the same conceptually: defer softmax norm.
_fwd_kernel_flash_attn_v2[grid](
q,
k,
v,
k_cache,
v_cache,
b_loc,
sm_scale,
b_start_loc,
b_seq_len,
b_ctx_len,
v_cache.shape[3],
k_cache.shape[4],
o,
b_loc.stride(0),
b_loc.stride(1),
q.stride(0),
q.stride(1),
q.stride(2),
k.stride(0),
k.stride(1),
k.stride(2),
v.stride(0),
v.stride(1),
v.stride(2),
o.stride(0),
o.stride(1),
o.stride(2),
k_cache.stride(0),
k_cache.stride(1),
k_cache.stride(2),
k_cache.stride(3),
k_cache.stride(4),
v_cache.stride(0),
v_cache.stride(1),
v_cache.stride(2),
v_cache.stride(3),
num_queries_per_kv=num_queries_per_kv,
BLOCK_M=BLOCK,
BLOCK_DMODEL=Lk,
BLOCK_N=BLOCK,
num_warps=NUM_WARPS,
num_stages=1,
)
else:
# CCCL "lookback" path: online normalization (safe default)
# Handles: alibi, sliding window, FP8, non-power-of-2 head_dim
_fwd_kernel[grid](
q,
k,
v,
k_cache,
v_cache,
b_loc,
sm_scale,
k_scale,
v_scale,
b_start_loc,
b_seq_len,
b_ctx_len,
v_cache.shape[3],
k_cache.shape[4],
o,
b_loc.stride(0),
b_loc.stride(1),
q.stride(0),
q.stride(1),
q.stride(2),
k.stride(0),
k.stride(1),
k.stride(2),
v.stride(0),
v.stride(1),
v.stride(2),
o.stride(0),
o.stride(1),
o.stride(2),
k_cache.stride(0),
k_cache.stride(1),
k_cache.stride(2),
k_cache.stride(3),
k_cache.stride(
4),
v_cache.stride(0),
v_cache.stride(1),
v_cache.stride(2),
v_cache.stride(3),
num_queries_per_kv=num_queries_per_kv,
BLOCK_M=BLOCK,
BLOCK_DMODEL=Lk,
BLOCK_DMODEL_PADDED=Lk_padded,
BLOCK_N=BLOCK,
SLIDING_WINDOW=sliding_window,
num_warps=NUM_WARPS,
num_stages=1,
)
return

View File

@@ -40,8 +40,8 @@ class CustomChatCompletionMessageParam(TypedDict, total=False):
role: Required[str]
"""The role of the message's author."""
content: Union[str, List[ChatCompletionContentPartParam]]
"""The contents of the message."""
content: Union[str, List[ChatCompletionContentPartParam], None]
"""The contents of the message. None for tool_call assistant messages."""
name: str
"""An optional name for the participant.
@@ -160,6 +160,10 @@ class ChatCompletionRequest(OpenAIBaseModel):
logprobs: Optional[bool] = False
top_logprobs: Optional[int] = 0
max_tokens: Optional[int] = None
# OpenAI newer API sends max_completion_tokens; map to max_tokens
# CCCL dispatch_common.cuh pattern: use_default — accept the param,
# normalize to internal representation, don't reject unknown fields.
max_completion_tokens: Optional[int] = None
n: Optional[int] = 1
presence_penalty: Optional[float] = 0.0
response_format: Optional[ResponseFormat] = None
@@ -177,6 +181,11 @@ class ChatCompletionRequest(OpenAIBaseModel):
parallel_tool_calls: Optional[bool] = False
user: Optional[str] = None
# OpenAI reasoning API: thinking field controls chain-of-thought
# e.g. {"type": "enabled"} or {"type": "disabled"}
# Accepted but not enforced at API layer — model config determines behavior
thinking: Optional[Dict[str, Any]] = None
# doc: begin-chat-completion-sampling-params
best_of: Optional[int] = None
use_beam_search: bool = False
@@ -305,7 +314,10 @@ class ChatCompletionRequest(OpenAIBaseModel):
)
def to_sampling_params(self, default_max_tokens: int) -> SamplingParams:
# CCCL use_default: normalize max_completion_tokens → max_tokens
max_tokens = self.max_tokens
if max_tokens is None and self.max_completion_tokens is not None:
max_tokens = self.max_completion_tokens
if max_tokens is None:
max_tokens = default_max_tokens