Skip to content

[serve][llm] Add Native Anthropic Messages API (/v1/messages) Support - #65486

Open
wanadzhar913 wants to merge 3 commits into
ray-project:masterfrom
wanadzhar913:ray-serve-llm/anthropic-messages-api
Open

[serve][llm] Add Native Anthropic Messages API (/v1/messages) Support#65486
wanadzhar913 wants to merge 3 commits into
ray-project:masterfrom
wanadzhar913:ray-serve-llm/anthropic-messages-api

Conversation

@wanadzhar913

Copy link
Copy Markdown
Contributor

Description

Adds first-class Anthropic Messages API support to Ray Serve LLM for vLLM-based deployments. This allows Anthropic-compatible clients, including Claude Code, to communicate directly with Ray Serve LLM without an additional translation proxy such as LiteLLM or Claude Code Router. The initial implementation supports:

  • POST /v1/messages
  • POST /v1/messages/count_tokens
  • Streaming and non-streaming message responses.
  • Standard ingress and direct-streaming deployments.
  • Vanilla vLLM deployments. SGlang fallbacks on import failure, P/D disaggregation and data parallel deployments are outside this PR's scope.

Related issues

Implementation

  • Added Ray wrappers and alpha public APIs for vLLM's Anthropic request, response, token-counting, and error models.
  • Extended LLMEngine, LLMServerProtocol, and LLMServer with messages and count_tokens operations.
  • Initialized and delegated requests to vLLM's AnthropicServingMessages.
  • Added AnthropicIngress (which inherits from OpenaiIngress), reusing the existing ingress model resolution, LoRA routing, handle caching, session affinity, and deployment configuration.
  • Translateed internal vLLM errors into Anthropic-compatible error payloads at the HTTP boundary.
  • Preserved vLLM-generated Anthropic SSE events for streaming responses.
  • Added build_anthropic_app with support for both standard ingress and direct streaming.
  • Extended the mock vLLM engine with Anthropic routes and responses.
  • Added tests covering model imports, route registration, builder behavior, error responses, token counting, and streaming/non-streaming requests.

Testing

pytest

PYTHONPATH=python python -m pytest -v \
  python/ray/llm/tests/serve/cpu/configs/test_anthropic_api_models_backends.py \
  python/ray/llm/tests/serve/cpu/deployments/routers/test_anthropic_ingress.py \
  python/ray/llm/tests/serve/cpu/deployments/routers/test_anthropic_http.py \
  python/ray/llm/tests/serve/cpu/deployments/routers/test_builder_ingress.py -k Anthropic

API

I tested this using the anthropic==0.106.0 & vllm==0.26.0 python library, and have not used Claude Code (yet). Testing infra had the following specs:

  • GPU: NVIDIA GeForce RTX 4090 (24 GB VRAM)
  • Driver Version: 580.126.20
  • CUDA Version: 13.0
Ray LLMServer Setup Script
from ray import serve
from ray.serve.llm import LLMConfig, build_anthropic_app

llm_config = LLMConfig(
    model_loading_config={
        "model_id": "qwen-0.5b",
        "model_source": "Qwen/Qwen2.5-0.5B-Instruct",
    },
    deployment_config={
        "autoscaling_config": {
            "min_replicas": 1,
            "max_replicas": 2,
        }
    },
    # Pass the desired accelerator type (e.g. A10G, L4, etc.)
    # accelerator_type="A10G",
    # You can customize the engine arguments (e.g. vLLM engine kwargs)
    engine_kwargs={
        "tensor_parallel_size": 1,
    },
)

app = build_anthropic_app({"llm_configs": [llm_config]})
serve.run(app, blocking=True)
Ray LLMServer Setup Logs
(.venv) root@e698b1650b1b:/workspace# bash setup_ray_anthropic_api_pr.sh 
>>> Checking out Ray branch: ray-serve-llm/anthropic-messages-api
branch 'ray-serve-llm/anthropic-messages-api' set up to track 'origin/ray-serve-llm/anthropic-messages-api'.
Switched to a new branch 'ray-serve-llm/anthropic-messages-api'
>>> Starting the Anthropic-compatible Ray Serve API...
>>> Run scripts/test_ray_anthropic_api.sh from another terminal to test it.
/workspace/ray/.venv/lib/python3.12/site-packages/torch/cuda/__init__.py:61: FutureWarning: The pynvml package is deprecated. Please install nvidia-ml-py instead. If you did not install pynvml directly, please report this to the maintainers of the package that installed pynvml for you.
  import pynvml  # type: ignore[import]
Usage stats collection is enabled by default for nightly wheels. To disable this, run the following command: `ray disable-usage-stats` before starting Ray. See https://docs.ray.io/en/master/cluster/usage-stats.html for more details.
2026-08-14 11:53:47,802 ERROR services.py:1431 -- Failed to start the dashboard 
2026-08-14 11:53:47,802 ERROR services.py:1456 -- Error should be written to 'dashboard.log' or 'dashboard.err'. We are printing the last 20 lines for you. See 'https://docs.ray.io/en/master/ray-observability/user-guides/configure-logging.html#logging-directory-structure' to find where the log file is.
2026-08-14 11:53:47,802 ERROR services.py:1500 -- 
The last 20 lines of /tmp/ray/session_2026-08-14_11-52-47_396084_60616/logs/dashboard.log (it contains the error message from the dashboard): 
    loop.run_until_complete(dashboard.run())
  File "/usr/lib/python3.12/asyncio/base_events.py", line 687, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "/workspace/ray/.venv/lib/python3.12/site-packages/ray/dashboard/dashboard.py", line 107, in run
    await self.dashboard_head.run()
  File "/workspace/ray/.venv/lib/python3.12/site-packages/ray/dashboard/head.py", line 513, in run
    await self._configure_http_server(
  File "/workspace/ray/.venv/lib/python3.12/site-packages/ray/dashboard/head.py", line 153, in _configure_http_server
    self.http_server = HttpServerDashboardHead(
                       ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspace/ray/.venv/lib/python3.12/site-packages/ray/dashboard/http_server_head.py", line 122, in __init__
    raise ex
  File "/workspace/ray/.venv/lib/python3.12/site-packages/ray/dashboard/http_server_head.py", line 113, in __init__
    build_dir = setup_static_dir()
                ^^^^^^^^^^^^^^^^^^
  File "/workspace/ray/.venv/lib/python3.12/site-packages/ray/dashboard/http_server_head.py", line 71, in setup_static_dir
    raise dashboard_utils.FrontendNotFoundError(
ray.dashboard.utils.FrontendNotFoundError: [Errno 2] Dashboard build directory not found. If installing from source, please follow the additional steps required to build the dashboard(cd python/ray/dashboard/client && npm ci && npm run build): '/workspace/ray/.venv/lib/python3.12/site-packages/ray/dashboard/client/build'
2026-08-14 11:53:47,803 WARNING utils.py:473 -- Detecting docker specified CPUs. In previous versions of Ray, CPU detection in containers was incorrect. Please ensure that Ray has enough CPUs allocated. As a temporary workaround to revert to the prior behavior, set `RAY_USE_MULTIPROCESSING_CPU_COUNT=1` as an env var before starting Ray. Set the env var: `RAY_DISABLE_DOCKER_CPU_WARNING=1` to mute this warning.
2026-08-14 11:53:47,803 WARNING utils.py:485 -- Ray currently does not support initializing Ray with fractional cpus. Your num_cpus will be truncated from 10.2 to 10.
2026-08-14 11:53:53,032 INFO worker.py:2024 -- Started a local Ray instance.
INFO 2026-08-14 11:53:57,015 serve 60616 -- ============== Deployment Options ==============
INFO 2026-08-14 11:53:57,015 serve 60616 -- {'autoscaling_config': {'max_replicas': 2,
                        'min_replicas': 1,
                        'target_ongoing_requests': 1000000000},
 'health_check_period_s': 10,
 'health_check_timeout_s': 10,
 'max_ongoing_requests': 1000000000,
 'name': 'LLMServer:qwen-0_5b',
 'placement_group_bundles': [{'CPU': 1, 'GPU': 1}],
 'placement_group_strategy': 'PACK',
 'ray_actor_options': {'runtime_env': {'worker_process_setup_hook': 'ray.llm._internal.serve._worker_process_setup_hook'}}}
INFO 2026-08-14 11:53:57,141 serve 60616 -- ============== Ingress Options ==============
INFO 2026-08-14 11:53:57,141 serve 60616 -- {'autoscaling_config': {'target_ongoing_requests': 1000000000},
 'max_ongoing_requests': 1000000000}
(ProxyActor pid=61920) INFO 2026-08-14 11:54:03,929 proxy 172.20.0.2 -- Proxy starting on node 378dbf39e6a51915676f01de61f3bcca7b66933744f44a833683bca7 (HTTP port: 8000).
INFO 2026-08-14 11:54:04,135 serve 60616 -- Started Serve in namespace "serve".
(ProxyActor pid=61920) INFO 2026-08-14 11:54:04,131 proxy 172.20.0.2 -- Got updated endpoints: {}.
(ServeController pid=61919) INFO 2026-08-14 11:54:04,222 controller 61919 -- Registering autoscaling state for deployment Deployment(name='LLMServer:qwen-0_5b', app='default')
(ServeController pid=61919) INFO 2026-08-14 11:54:04,223 controller 61919 -- Deploying new version of Deployment(name='LLMServer:qwen-0_5b', app='default') (initial target replicas: 1).
(ServeController pid=61919) INFO 2026-08-14 11:54:04,224 controller 61919 -- Registering autoscaling state for deployment Deployment(name='AnthropicIngress', app='default')
(ServeController pid=61919) INFO 2026-08-14 11:54:04,225 controller 61919 -- Deploying new version of Deployment(name='AnthropicIngress', app='default') (initial target replicas: 1).
(ProxyActor pid=61920) INFO 2026-08-14 11:54:04,231 proxy 172.20.0.2 -- Got updated endpoints: {Deployment(name='AnthropicIngress', app='default'): EndpointInfo(route='/', app_is_cross_language=False, route_patterns=None)}.
(ProxyActor pid=61920) INFO 2026-08-14 11:54:04,239 proxy 172.20.0.2 -- Started <ray.serve._private.router.SharedRouterLongPollClient object at 0x76763de9e720>.
(ServeController pid=61919) INFO 2026-08-14 11:54:04,332 controller 61919 -- Adding 1 replica to Deployment(name='LLMServer:qwen-0_5b', app='default').
(ServeController pid=61919) INFO 2026-08-14 11:54:04,335 controller 61919 -- Adding 1 replica to Deployment(name='AnthropicIngress', app='default').
(ServeReplica:default:AnthropicIngress pid=61926) /workspace/ray/.venv/lib/python3.12/site-packages/torch/cuda/__init__.py:61: FutureWarning: The pynvml package is deprecated. Please install nvidia-ml-py instead. If you did not install pynvml directly, please report this to the maintainers of the package that installed pynvml for you.
(ServeReplica:default:AnthropicIngress pid=61926)   import pynvml  # type: ignore[import]
(ServeController pid=61919) WARNING 2026-08-14 11:54:34,365 controller 61919 -- Deployment 'LLMServer:qwen-0_5b' in application 'default' has 1 replicas that have taken more than 30s to be scheduled. This may be due to waiting for the cluster to auto-scale or for a runtime environment to be installed. Resources required for each replica: [{"GPU": 1.0, "CPU": 1.0}], total resources available: {}. Use `ray status` for more details.
(ServeController pid=61919) WARNING 2026-08-14 11:54:34,366 controller 61919 -- Deployment 'AnthropicIngress' in application 'default' has 1 replicas that have taken more than 30s to be scheduled. This may be due to waiting for the cluster to auto-scale or for a runtime environment to be installed. Resources required for each replica: {"CPU": 1}, total resources available: {"CPU": 8.0}. Use `ray status` for more details.
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) /workspace/ray/.venv/lib/python3.12/site-packages/torch/cuda/__init__.py:61: FutureWarning: The pynvml package is deprecated. Please install nvidia-ml-py instead. If you did not install pynvml directly, please report this to the maintainers of the package that installed pynvml for you.
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683)   import pynvml  # type: ignore[import]
(ProxyActor pid=61920) INFO 2026-08-14 11:54:48,194 proxy 172.20.0.2 -- Got updated endpoints: {Deployment(name='AnthropicIngress', app='default'): EndpointInfo(route='/', app_is_cross_language=False, route_patterns=[RoutePattern(methods=['GET', 'HEAD'], path='/docs'), RoutePattern(methods=['GET', 'HEAD'], path='/docs/oauth2-redirect'), RoutePattern(methods=['GET', 'HEAD'], path='/openapi.json'), RoutePattern(methods=['GET', 'HEAD'], path='/redoc'), RoutePattern(methods=['POST'], path='/v1/messages'), RoutePattern(methods=['POST'], path='/v1/messages/count_tokens')])}.
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) INFO 2026-08-14 11:54:53,441 default_LLMServer:qwen-0_5b kqnimn62 -- Running tasks to download model files on worker nodes
(pid=63324) /workspace/ray/.venv/lib/python3.12/site-packages/torch/cuda/__init__.py:61: FutureWarning: The pynvml package is deprecated. Please install nvidia-ml-py instead. If you did not install pynvml directly, please report this to the maintainers of the package that installed pynvml for you.
(pid=63324)   import pynvml  # type: ignore[import]
(ServeController pid=61919) WARNING 2026-08-14 11:55:04,382 controller 61919 -- Deployment 'LLMServer:qwen-0_5b' in application 'default' has 1 replicas that have taken more than 30s to initialize.
(ServeController pid=61919) This may be caused by a slow __init__ or reconfigure method.
(initialize_remote_node pid=63324) No cloud storage mirror configured
(_get_vllm_engine_config pid=63324) Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.
(_get_vllm_engine_config pid=63324) {"asctime": "2026-08-14 11:55:32,220", "levelname": "WARNING", "message": "Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.", "filename": "_http.py", "lineno": 904, "process": 63324, "job_id": "01000000", "worker_id": "4695f65c75a37c9cac9b14261922d4d488aba1b0eb9b71dc216a6ee3", "node_id": "378dbf39e6a51915676f01de61f3bcca7b66933744f44a833683bca7", "task_id": "aaf9253a9eca5fc2ffffffffffffffffffffffff01000000", "task_name": "_get_vllm_engine_config", "task_func_name": "ray.llm._internal.serve.engines.vllm.vllm_engine._get_vllm_engine_config", "timestamp_ns": 1786708532220740688}
(ServeController pid=61919) WARNING 2026-08-14 11:55:34,433 controller 61919 -- Deployment 'LLMServer:qwen-0_5b' in application 'default' has 1 replicas that have taken more than 30s to initialize.
(ServeController pid=61919) This may be caused by a slow __init__ or reconfigure method.
(ServeController pid=61919) WARNING 2026-08-14 11:56:04,447 controller 61919 -- Deployment 'LLMServer:qwen-0_5b' in application 'default' has 1 replicas that have taken more than 30s to initialize.
(ServeController pid=61919) This may be caused by a slow __init__ or reconfigure method.
(_get_vllm_engine_config pid=63324) INFO 08-14 11:56:25 [model.py:623] Resolved architecture: Qwen2ForCausalLM
(_get_vllm_engine_config pid=63324) INFO 08-14 11:56:25 [model.py:1788] Using max model len 32768
(_get_vllm_engine_config pid=63324) INFO 08-14 11:56:25 [arg_utils.py:1951] Using ray runtime env (env vars redacted): {'worker_process_setup_hook': 'ray.llm._internal.serve._worker_process_setup_hook'}
(_get_vllm_engine_config pid=63324) INFO 08-14 11:56:25 [scheduler.py:252] Chunked prefill is enabled with max_num_batched_tokens=2048.
(_get_vllm_engine_config pid=63324) INFO 08-14 11:56:25 [vllm.py:1109] Asynchronous scheduling is enabled.
(_get_vllm_engine_config pid=63324) INFO 08-14 11:56:25 [kernel.py:295] Final IR op priority after setting platform defaults: IrOpPriorityConfig(rms_norm=['native'], fused_add_rms_norm=['native'])
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) INFO 2026-08-14 11:56:26,134 default_LLMServer:qwen-0_5b kqnimn62 -- Clearing the current platform cache ...
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) INFO 2026-08-14 11:56:26,159 default_LLMServer:qwen-0_5b kqnimn62 -- Using executor class: <class 'vllm.v1.executor.ray_executor_v2.RayExecutorV2'>
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) WARNING 08-14 11:56:31 [system_utils.py:157] We must use the `spawn` multiprocessing start method. Overriding VLLM_WORKER_MULTIPROC_METHOD to 'spawn'. See https://docs.vllm.ai/en/latest/usage/troubleshooting.html#python-multiprocessing for more information. Reasons: In a Ray actor and can only be spawned
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) /workspace/ray/.venv/lib/python3.12/site-packages/torch/cuda/__init__.py:61: FutureWarning: The pynvml package is deprecated. Please install nvidia-ml-py instead. If you did not install pynvml directly, please report this to the maintainers of the package that installed pynvml for you.
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683)   import pynvml  # type: ignore[import]
(ServeController pid=61919) WARNING 2026-08-14 11:56:34,478 controller 61919 -- Deployment 'LLMServer:qwen-0_5b' in application 'default' has 1 replicas that have taken more than 30s to initialize.
(ServeController pid=61919) This may be caused by a slow __init__ or reconfigure method.
(ServeController pid=61919) WARNING 2026-08-14 11:57:04,556 controller 61919 -- Deployment 'LLMServer:qwen-0_5b' in application 'default' has 1 replicas that have taken more than 30s to initialize.
(ServeController pid=61919) This may be caused by a slow __init__ or reconfigure method.
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) INFO 08-14 11:57:16 [core.py:116] Initializing a V1 LLM engine (v0.26.0) with config: model='Qwen/Qwen2.5-0.5B-Instruct', speculative_config=None, tokenizer='Qwen/Qwen2.5-0.5B-Instruct', skip_tokenizer_init=False, tokenizer_mode=auto, revision=None, tokenizer_revision=None, trust_remote_code=False, dtype=torch.bfloat16, max_seq_len=32768, download_dir=None, load_format=auto, tensor_parallel_size=1, pipeline_parallel_size=1, data_parallel_size=1, decode_context_parallel_size=1, dcp_comm_backend=ag_rs, disable_custom_all_reduce=False, quantization=None, quantization_config=None, enforce_eager=False, enable_return_routed_experts=False, kv_cache_dtype=auto, device_config=cuda, structured_outputs_config=StructuredOutputsConfig(backend='auto', disable_any_whitespace=False, disable_additional_properties=False, reasoning_parser='', reasoning_parser_plugin='', enable_in_reasoning=False), observability_config=ObservabilityConfig(show_hidden_metrics_for_version=None, otlp_traces_endpoint=None, collect_detailed_traces=None, kv_cache_metrics=False, kv_cache_metrics_sample=0.01, cudagraph_metrics=False, enable_layerwise_nvtx_tracing=False, enable_mfu_metrics=False, enable_mm_processor_stats=False, enable_logging_iteration_details=False, jit_monitor_mode='warn', jit_monitor_verbose=False), seed=0, served_model_name=qwen-0.5b, enable_prefix_caching=True, enable_chunked_prefill=True, pooler_config=None, compilation_config={'mode': <CompilationMode.VLLM_COMPILE: 3>, 'debug_dump_path': None, 'cache_dir': '', 'compile_cache_save_format': 'binary', 'backend': 'inductor', 'custom_ops': ['none'], 'ir_enable_torch_wrap': True, 'splitting_ops': ['vllm::unified_attention_with_output', 'vllm::unified_mla_attention_with_output', 'vllm::mamba_mixer2', 'vllm::mamba_mixer', 'vllm::short_conv', 'vllm::linear_attention', 'vllm::plamo2_mamba_mixer', 'vllm::qwen_gdn_attention_core', 'vllm::gdn_attention_core_xpu', 'vllm::olmo_hybrid_gdn_full_forward', 'vllm::kda_attention', 'vllm::sparse_attn_indexer', 'vllm::rocm_aiter_sparse_attn_indexer', 'vllm::deepseek_v4_attention', 'vllm::hpc_rope_norm_forward', 'vllm::unified_kv_cache_update', 'vllm::unified_mla_kv_cache_update'], 'compile_mm_encoder': False, 'cudagraph_mm_encoder': False, 'encoder_cudagraph_token_budgets': [], 'encoder_cudagraph_max_vision_items_per_batch': 0, 'encoder_cudagraph_max_frames_per_batch': None, 'compile_sizes': [], 'compile_ranges_endpoints': [2048], 'inductor_compile_config': {'enable_auto_functionalized_v2': False, 'size_asserts': False, 'alignment_asserts': False, 'scalar_asserts': False, 'combo_kernels': True, 'benchmark_combo_kernel': True}, 'inductor_passes': {}, 'cudagraph_mode': <CUDAGraphMode.FULL_AND_PIECEWISE: (2, 1)>, 'cudagraph_num_of_warmups': 1, 'cudagraph_capture_sizes': [1, 2, 4, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128, 136, 144, 152, 160, 168, 176, 184, 192, 200, 208, 216, 224, 232, 240, 248, 256, 272, 288, 304, 320, 336, 352, 368, 384, 400, 416, 432, 448, 464, 480, 496, 512], 'cudagraph_copy_inputs': False, 'cudagraph_specialize_lora': True, 'use_inductor_graph_partition': False, 'pass_config': {'fuse_norm_quant': False, 'fuse_act_quant': False, 'fuse_attn_quant': False, 'enable_sp': False, 'fuse_gemm_comms': False, 'fuse_allreduce_rms': False, 'enable_qk_norm_rope_fusion': False, 'fuse_rope_kvcache_cat_mla': False, 'fuse_act_padding': False, 'fuse_qk_norm_rope_kvcache': False}, 'max_cudagraph_capture_size': 512, 'dynamic_shapes_config': {'type': <DynamicShapesType.BACKED: 'backed'>, 'evaluate_guards': False, 'assume_32_bit_indexing': False}, 'local_cache_dir': None, 'fast_moe_cold_start': False, 'static_all_moe_layers': []}, kernel_config=KernelConfig(ir_op_priority=IrOpPriorityConfig(rms_norm=['native'], fused_add_rms_norm=['native']), enable_flashinfer_autotune=True, enable_cutedsl_warmup=True, enable_bf16x3_router_gemm=False, moe_backend='auto', linear_backend='auto')
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) 2026-08-14 11:57:16,125     INFO worker.py:1683 -- Using address 172.20.0.2:44883 set in the environment variable RAY_ADDRESS
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) INFO 08-14 11:57:16 [ray_utils.py:602] Using the existing placement group
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) 2026-08-14 11:57:16,138     INFO worker.py:1833 -- Connecting to existing Ray cluster at address: 172.20.0.2:44883...
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) 2026-08-14 11:57:16,157     INFO worker.py:2024 -- Connected to Ray cluster.
(ServeReplica:default:AnthropicIngress pid=61926) INFO 2026-08-14 11:57:22,980 default_AnthropicIngress i763jp1f 178d4f4d-8ab3-4635-9760-364998a841e3 -- Started <ray.serve._private.router.SharedRouterLongPollClient object at 0x7aa6c8b8cda0>.
(pid=65043) /workspace/ray/.venv/lib/python3.12/site-packages/torch/cuda/__init__.py:61: FutureWarning: The pynvml package is deprecated. Please install nvidia-ml-py instead. If you did not install pynvml directly, please report this to the maintainers of the package that installed pynvml for you.
(pid=65043)   import pynvml  # type: ignore[import]
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) (pid=65043) /workspace/ray/.venv/lib/python3.12/site-packages/torch/cuda/__init__.py:61: FutureWarning: The pynvml package is deprecated. Please install nvidia-ml-py instead. If you did not install pynvml directly, please report this to the maintainers of the package that installed pynvml for you.
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) (pid=65043)   import pynvml  # type: ignore[import]
(ServeController pid=61919) WARNING 2026-08-14 11:57:34,580 controller 61919 -- Deployment 'LLMServer:qwen-0_5b' in application 'default' has 1 replicas that have taken more than 30s to initialize.
(ServeController pid=61919) This may be caused by a slow __init__ or reconfigure method.
(RayWorkerProc pid=65043) WARNING 08-14 11:58:02 [worker_base.py:306] Missing `shared_worker_lock` argument from executor. This argument is needed for mm_processor_cache_type='shm'.
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) (RayWorkerProc pid=65043) WARNING 08-14 11:58:02 [worker_base.py:306] Missing `shared_worker_lock` argument from executor. This argument is needed for mm_processor_cache_type='shm'.
(RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:02 [parallel_state.py:1615] world_size=1 rank=0 local_rank=0 distributed_init_method=tcp://172.20.0.2:100 backend=nccl
(RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:02 [parallel_state.py:1946] rank 0 in world size 1 is assigned as DP rank 0, PP rank 0, PCP rank 0, TP rank 0, EP rank N/A, EPLB rank N/A
(RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:02 [gpu_worker.py:378] Using V2 Model Runner
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) (RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:02 [parallel_state.py:1615] world_size=1 rank=0 local_rank=0 distributed_init_method=tcp://172.20.0.2:100 backend=nccl
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) (RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:02 [parallel_state.py:1946] rank 0 in world size 1 is assigned as DP rank 0, PP rank 0, PCP rank 0, TP rank 0, EP rank N/A, EPLB rank N/A
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) (RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:02 [gpu_worker.py:378] Using V2 Model Runner
(RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:03 [model_runner.py:284] Loading model from scratch...
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) (RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:03 [model_runner.py:284] Loading model from scratch...
(RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:04 [cuda.py:482] Using FLASH_ATTN attention backend out of potential backends: ['FLASH_ATTN', 'FLASHINFER', 'TRITON_ATTN', 'FLEX_ATTENTION'].
(RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:04 [flash_attn.py:776] Using FlashAttention version 2
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) (RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:04 [cuda.py:482] Using FLASH_ATTN attention backend out of potential backends: ['FLASH_ATTN', 'FLASHINFER', 'TRITON_ATTN', 'FLEX_ATTENTION'].
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) (RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:04 [flash_attn.py:776] Using FlashAttention version 2
(ServeController pid=61919) WARNING 2026-08-14 11:58:04,666 controller 61919 -- Deployment 'LLMServer:qwen-0_5b' in application 'default' has 1 replicas that have taken more than 30s to initialize.
(ServeController pid=61919) This may be caused by a slow __init__ or reconfigure method.
(ServeController pid=61919) WARNING 2026-08-14 11:58:34,694 controller 61919 -- Deployment 'LLMServer:qwen-0_5b' in application 'default' has 1 replicas that have taken more than 30s to initialize.
(ServeController pid=61919) This may be caused by a slow __init__ or reconfigure method.
(RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:40 [weight_utils.py:530] Time spent downloading weights for Qwen/Qwen2.5-0.5B-Instruct: 35.615082 seconds
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) (RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:40 [weight_utils.py:530] Time spent downloading weights for Qwen/Qwen2.5-0.5B-Instruct: 35.615082 seconds
(RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:40 [weight_utils.py:574] No model.safetensors.index.json found in remote.
(RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:40 [weight_utils.py:869] Filesystem type for checkpoints: NFS4. Checkpoint size: 0.92 GiB. Available RAM: 152.67 GiB.
(RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:40 [weight_utils.py:831] Prefetching checkpoint files into page cache started (in background, num_threads=8, block_size=16777216 bytes)
Loading safetensors checkpoint shards:   0% Completed | 0/1 [00:00<?, ?it/s]
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) (RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:40 [weight_utils.py:574] No model.safetensors.index.json found in remote.
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) (RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:40 [weight_utils.py:869] Filesystem type for checkpoints: NFS4. Checkpoint size: 0.92 GiB. Available RAM: 152.67 GiB.
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) (RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:40 [weight_utils.py:831] Prefetching checkpoint files into page cache started (in background, num_threads=8, block_size=16777216 bytes)
Loading safetensors checkpoint shards:   0% Completed | 0/1 [00:00<?, ?it/s](RayWorkerProc pid=65043) (Worker pid=65043) 
(RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:42 [weight_utils.py:803] Prefetching checkpoint files: 10% (1/1)
(RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:42 [weight_utils.py:826] Prefetching checkpoint files into page cache finished in 1.29s
(RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:42 [default_loader.py:430] Loading weights took 1.35 seconds
Loading safetensors checkpoint shards: 100% Completed | 1/1 [00:01<00:00,  1.29s/it]
Loading safetensors checkpoint shards: 100% Completed | 1/1 [00:01<00:00,  1.29s/it]
(RayWorkerProc pid=65043) (Worker pid=65043) 
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) (RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:42 [weight_utils.py:803] Prefetching checkpoint files: 10% (1/1)
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) (RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:42 [weight_utils.py:826] Prefetching checkpoint files into page cache finished in 1.29s
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) (RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:42 [default_loader.py:430] Loading weights took 1.35 seconds
Loading safetensors checkpoint shards: 100% Completed | 1/1 [00:01<00:00,  1.29s/it]erProc pid=65043) (Worker pid=65043) 
Loading safetensors checkpoint shards: 100% Completed | 1/1 [00:01<00:00,  1.29s/it]erProc pid=65043) (Worker pid=65043) 
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) (RayWorkerProc pid=65043) (Worker pid=65043) 
(RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:42 [model_runner.py:305] Model loading took 0.93 GiB and 39.315232 seconds
(RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:42 [topk_topp_sampler.py:55] Using FlashInfer for top-p & top-k sampling.
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) (RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:42 [model_runner.py:305] Model loading took 0.93 GiB and 39.315232 seconds
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) (RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:42 [topk_topp_sampler.py:55] Using FlashInfer for top-p & top-k sampling.
(RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:52 [backends.py:1094] Using cache directory: /root/.cache/vllm/torch_compile_cache/8ffdbad051/rank_0_0/backbone for vLLM's torch.compile
(RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:52 [backends.py:1155] Dynamo bytecode transform time: 9.53 s
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) (RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:52 [backends.py:1094] Using cache directory: /root/.cache/vllm/torch_compile_cache/8ffdbad051/rank_0_0/backbone for vLLM's torch.compile
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) (RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:52 [backends.py:1155] Dynamo bytecode transform time: 9.53 s
(RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:56 [backends.py:378] Cache the graph of compile range (1, 2048) for later use
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) (RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:56 [backends.py:378] Cache the graph of compile range (1, 2048) for later use
(RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:58 [backends.py:393] Compiling a graph for compile range (1, 2048) takes 5.77 s
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) (RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:58:58 [backends.py:393] Compiling a graph for compile range (1, 2048) takes 5.77 s
(RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:59:00 [decorators.py:708] saved AOT compiled function to /root/.cache/vllm/torch_compile_cache/torch_aot_compile/b68dab0d49da0c8f877964e68ddced248a227ec851f8e93b13e0819b0be6c086/rank_0_0/model
(RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:59:00 [monitor.py:53] torch.compile took 16.88 s in total
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) (RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:59:00 [decorators.py:708] saved AOT compiled function to /root/.cache/vllm/torch_compile_cache/torch_aot_compile/b68dab0d49da0c8f877964e68ddced248a227ec851f8e93b13e0819b0be6c086/rank_0_0/model
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) (RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:59:00 [monitor.py:53] torch.compile took 16.88 s in total
(RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:59:00 [monitor.py:81] Initial profiling/warmup run took 0.30 s
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) (RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:59:00 [monitor.py:81] Initial profiling/warmup run took 0.30 s
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) INFO 08-14 11:59:01 [kv_cache_utils.py:2177] GPU KV cache size: 1,778,288 tokens
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) INFO 08-14 11:59:01 [kv_cache_utils.py:2178] Maximum concurrency for 32,768 tokens per request: 54.27x
(RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:59:01 [gpu_worker.py:560] Available KV cache memory: 20.35 GiB
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) (RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:59:01 [gpu_worker.py:560] Available KV cache memory: 20.35 GiB
(RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:59:02 [cutedsl_warmup.py:101] Skipping CuTeDSL warmup because no compile units were requested.
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) (RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:59:02 [cutedsl_warmup.py:101] Skipping CuTeDSL warmup because no compile units were requested.
Capturing CUDA graphs (PIECEWISE):   0%|          | 0/51 [00:00<?, ?it/s]
Capturing CUDA graphs (PIECEWISE):   0%|          | 0/51 [00:00<?, ?it/s]6) (RayWorkerProc pid=65043) (Worker pid=65043) 
Capturing CUDA graphs (PIECEWISE):  10%|| 5/51 [00:00<00:01, 28.97it/s]
Capturing CUDA graphs (PIECEWISE):  10%|| 5/51 [00:00<00:01, 28.97it/s]orkerProc pid=65043) 
Capturing CUDA graphs (PIECEWISE):  20%|█▉        | 10/51 [00:00<00:01, 37.50it/s]
Capturing CUDA graphs (PIECEWISE):  20%|█▉        | 10/51 [00:00<00:01, 37.50it/s]rkerProc pid=65043) 
Capturing CUDA graphs (PIECEWISE):  33%|███▎      | 17/51 [00:00<00:00, 47.91it/s]
Capturing CUDA graphs (PIECEWISE):  33%|███▎      | 17/51 [00:00<00:00, 47.91it/s]rkerProc pid=65043) 
Capturing CUDA graphs (PIECEWISE):  45%|████▌     | 23/51 [00:00<00:00, 51.07it/s]
Capturing CUDA graphs (PIECEWISE):  45%|████▌     | 23/51 [00:00<00:00, 51.07it/s]rkerProc pid=65043) 
Capturing CUDA graphs (PIECEWISE):  59%|█████▉    | 30/51 [00:00<00:00, 54.98it/s]
Capturing CUDA graphs (PIECEWISE):  59%|█████▉    | 30/51 [00:00<00:00, 54.98it/s]rkerProc pid=65043) 
Capturing CUDA graphs (PIECEWISE):  73%|███████▎  | 37/51 [00:00<00:00, 57.12it/s]
Capturing CUDA graphs (PIECEWISE):  73%|███████▎  | 37/51 [00:00<00:00, 57.12it/s]rkerProc pid=65043) 
Capturing CUDA graphs (PIECEWISE):  86%|████████▋ | 44/51 [00:00<00:00, 58.95it/s]
Capturing CUDA graphs (PIECEWISE):  86%|████████▋ | 44/51 [00:00<00:00, 58.95it/s]rkerProc pid=65043) 
Capturing CUDA graphs (PIECEWISE): 100%|██████████| 51/51 [00:00<00:00, 51.64it/s]
Capturing CUDA graphs (FULL):   0%|          | 0/35 [00:00<?, ?it/s]
Capturing CUDA graphs (PIECEWISE): 100%|██████████| 51/51 [00:00<00:00, 51.64it/s]rkerProc pid=65043) 
Capturing CUDA graphs (FULL):   0%|          | 0/35 [00:00<?, ?it/s]=64296) (RayWorkerProc pid=65043) (Worker pid=65043) 
Capturing CUDA graphs (FULL):   6%|| 2/35 [00:00<00:01, 18.83it/s]
Capturing CUDA graphs (FULL):   6%|| 2/35 [00:00<00:01, 18.83it/s](RayWorkerProc pid=65043) 
Capturing CUDA graphs (FULL):  26%|██▌       | 9/35 [00:00<00:00, 44.40it/s]
Capturing CUDA graphs (FULL):  26%|██▌       | 9/35 [00:00<00:00, 44.40it/s](RayWorkerProc pid=65043) 
Capturing CUDA graphs (FULL):  46%|████▌     | 16/35 [00:00<00:00, 52.91it/s]
Capturing CUDA graphs (FULL):  46%|████▌     | 16/35 [00:00<00:00, 52.91it/s]RayWorkerProc pid=65043) 
Capturing CUDA graphs (FULL):  63%|██████▎   | 22/35 [00:00<00:00, 44.01it/s]
Capturing CUDA graphs (FULL):  63%|██████▎   | 22/35 [00:00<00:00, 44.01it/s]RayWorkerProc pid=65043) 
Capturing CUDA graphs (FULL):  83%|████████▎ | 29/35 [00:00<00:00, 50.64it/s]
Capturing CUDA graphs (FULL):  83%|████████▎ | 29/35 [00:00<00:00, 50.64it/s]RayWorkerProc pid=65043) 
(RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:59:04 [model_runner.py:747] Graph capturing finished in 2 secs, took 0.41 GiB
(RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:59:04 [gpu_worker.py:857] Free memory on device (23.13/23.52 GiB) on startup. Desired GPU memory utilization is (0.92, 21.64 GiB). Actual usage is 0.93 GiB for weight, 0.26 GiB for peak activation, 0.1 GiB for non-torch memory, and 0.41 GiB for CUDAGraph memory. Replace gpu_memory_utilization config with `--kv-cache-memory=21249853666` (19.79 GiB) to fit into requested memory, or `--kv-cache-memory=22856568320` (21.29 GiB) to fully utilize gpu memory. Current kv cache memory in use is 20.35 GiB.
Capturing CUDA graphs (FULL): 100%|██████████| 35/35 [00:00<00:00, 49.24it/s]
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) (RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:59:04 [model_runner.py:747] Graph capturing finished in 2 secs, took 0.41 GiB
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) (RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:59:04 [gpu_worker.py:857] Free memory on device (23.13/23.52 GiB) on startup. Desired GPU memory utilization is (0.92, 21.64 GiB). Actual usage is 0.93 GiB for weight, 0.26 GiB for peak activation, 0.1 GiB for non-torch memory, and 0.41 GiB for CUDAGraph memory. Replace gpu_memory_utilization config with `--kv-cache-memory=21249853666` (19.79 GiB) to fit into requested memory, or `--kv-cache-memory=22856568320` (21.29 GiB) to fully utilize gpu memory. Current kv cache memory in use is 20.35 GiB.
Capturing CUDA graphs (FULL): 100%|██████████| 35/35 [00:00<00:00, 49.24it/s]RayWorkerProc pid=65043) 
(ServeController pid=61919) WARNING 2026-08-14 11:59:04,783 controller 61919 -- Deployment 'LLMServer:qwen-0_5b' in application 'default' has 1 replicas that have taken more than 30s to initialize.
(ServeController pid=61919) This may be caused by a slow __init__ or reconfigure method.
(RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:59:11 [jit_monitor.py:79] Kernel JIT monitor activated; monitored JIT compilations during inference will use mode=warn.
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) (RayWorkerProc pid=65043) (Worker pid=65043) INFO 08-14 11:59:11 [jit_monitor.py:79] Kernel JIT monitor activated; monitored JIT compilations during inference will use mode=warn.
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) INFO 08-14 11:59:11 [core.py:340] init engine (profile, create kv cache, warmup model) took 28.77 s (compilation: 16.88 s)
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) INFO 08-14 11:59:20 [vllm.py:1109] Asynchronous scheduling is enabled.
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) (EngineCore pid=64296) INFO 08-14 11:59:20 [kernel.py:295] Final IR op priority after setting platform defaults: IrOpPriorityConfig(rms_norm=['native'], fused_add_rms_norm=['native'])
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) WARNING 08-14 11:59:20 [model.py:1546] Default vLLM sampling parameters have been overridden by the model's `generation_config.json`: `{'repetition_penalty': 1.1, 'temperature': 0.7, 'top_k': 20, 'top_p': 0.8}`. If this is not intended, please relaunch vLLM instance with `--generation-config vllm`.
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) INFO 08-14 11:59:22 [hf.py:540] Detected the chat template content format to be 'string'. You can set `--chat-template-content-format` to override this.
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) INFO 2026-08-14 11:59:22,925 default_LLMServer:qwen-0_5b kqnimn62 -- Started vLLM engine.
(pid=67341) /workspace/ray/.venv/lib/python3.12/site-packages/torch/cuda/__init__.py:61: FutureWarning: The pynvml package is deprecated. Please install nvidia-ml-py instead. If you did not install pynvml directly, please report this to the maintainers of the package that installed pynvml for you.
(pid=67341)   import pynvml  # type: ignore[import]
(ServeReplica:default:AnthropicIngress pid=61926) INFO 2026-08-14 11:59:32,848 default_AnthropicIngress i763jp1f 178d4f4d-8ab3-4635-9760-364998a841e3 -- POST /v1/messages 200 129876.8ms
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) INFO 2026-08-14 11:59:32,846 default_LLMServer:qwen-0_5b kqnimn62 178d4f4d-8ab3-4635-9760-364998a841e3 -- CALL messages OK 242.2ms
INFO 2026-08-14 11:59:33,048 serve 60616 -- Application 'default' is ready at http://127.0.0.1:8000/.
(ServeReplica:default:AnthropicIngress pid=61926) INFO 2026-08-14 12:00:28,505 default_AnthropicIngress i763jp1f f9e40442-a2d8-460e-b9a5-7957b9625fc5 -- GET / 404 2.2ms
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) INFO 2026-08-14 12:00:28,684 default_LLMServer:qwen-0_5b kqnimn62 9a041229-1885-40aa-b6cc-62c7919c18ad -- CALL messages OK 90.6ms
(ServeReplica:default:AnthropicIngress pid=61926) INFO 2026-08-14 12:00:28,687 default_AnthropicIngress i763jp1f 9a041229-1885-40aa-b6cc-62c7919c18ad -- POST /v1/messages 200 101.5ms
(ServeReplica:default:AnthropicIngress pid=61926) INFO 2026-08-14 12:00:28,738 default_AnthropicIngress i763jp1f eabae3ac-dd5c-4ae1-99e8-c39cb41ff53f -- GET / 404 1.8ms
(ServeReplica:default:AnthropicIngress pid=61926) INFO 2026-08-14 12:01:46,286 default_AnthropicIngress i763jp1f 4c4f943d-3dea-4d47-9262-cf1279ca224e -- POST /v1/messages 200 283.0ms
(ServeReplica:default:LLMServer:qwen-0_5b pid=62683) INFO 2026-08-14 12:01:46,284 default_LLMServer:qwen-0_5b kqnimn62 4c4f943d-3dea-4d47-9262-cf1279ca224e -- CALL messages OK 273.6ms

This sets up the ANTHROPIC_BASE_URL at: http://localhost:8000/v1/messages.

Anthropic API Tests Script
import os

from anthropic import Anthropic

client = Anthropic(
    api_key=os.environ["ANTHROPIC_API_KEY"],
    base_url=os.environ["ANTHROPIC_BASE_URL"],
)

prompts = [
    "Who is Anwar Ibrahim? Answer in one sentence.",
    "What is Ray Serve? Answer in one sentence.",
    "What is Ray Serve? Answer in one sentence.",
]

for prompt in prompts:
    print(f"\nPrompt: {prompt}")
    response = client.messages.create(
        model=os.environ["MODEL_ID"],
        max_tokens=128,
        messages=[{"role": "user", "content": prompt}],
    )

    for block in response.content:
        if block.type == "text":
            print(f"Response: {block.text}")
Anthropic API Tests Logs
root@e698b1650b1b:/# bash test_ray_anthropic_api.sh 
>>> Installing the Anthropic Python SDK...
Requirement already satisfied: anthropic in ./ray/.venv/lib/python3.12/site-packages (0.122.0)
Requirement already satisfied: anyio<5,>=3.5.0 in ./ray/.venv/lib/python3.12/site-packages (from anthropic) (4.14.2)
Requirement already satisfied: distro<2,>=1.7.0 in ./ray/.venv/lib/python3.12/site-packages (from anthropic) (1.9.0)
Requirement already satisfied: docstring-parser<1,>=0.15 in ./ray/.venv/lib/python3.12/site-packages (from anthropic) (0.18.0)
Requirement already satisfied: httpx<1,>=0.25.0 in ./ray/.venv/lib/python3.12/site-packages (from anthropic) (0.28.1)
Requirement already satisfied: jiter<1,>=0.4.0 in ./ray/.venv/lib/python3.12/site-packages (from anthropic) (0.16.0)
Requirement already satisfied: pydantic<3,>=1.9.0 in ./ray/.venv/lib/python3.12/site-packages (from anthropic) (2.13.4)
Requirement already satisfied: sniffio<2,>=1 in ./ray/.venv/lib/python3.12/site-packages (from anthropic) (1.3.1)
Requirement already satisfied: typing-extensions<5,>=4.14 in ./ray/.venv/lib/python3.12/site-packages (from anthropic) (4.16.0)
Requirement already satisfied: idna>=2.8 in ./ray/.venv/lib/python3.12/site-packages (from anyio<5,>=3.5.0->anthropic) (3.18)
Requirement already satisfied: certifi in ./ray/.venv/lib/python3.12/site-packages (from httpx<1,>=0.25.0->anthropic) (2026.7.22)
Requirement already satisfied: httpcore==1.* in ./ray/.venv/lib/python3.12/site-packages (from httpx<1,>=0.25.0->anthropic) (1.0.9)
Requirement already satisfied: h11>=0.16 in ./ray/.venv/lib/python3.12/site-packages (from httpcore==1.*->httpx<1,>=0.25.0->anthropic) (0.16.0)
Requirement already satisfied: annotated-types>=0.6.0 in ./ray/.venv/lib/python3.12/site-packages (from pydantic<3,>=1.9.0->anthropic) (0.8.0)
Requirement already satisfied: pydantic-core==2.46.4 in ./ray/.venv/lib/python3.12/site-packages (from pydantic<3,>=1.9.0->anthropic) (2.46.4)
Requirement already satisfied: typing-inspection>=0.4.2 in ./ray/.venv/lib/python3.12/site-packages (from pydantic<3,>=1.9.0->anthropic) (0.4.4)
>>> Querying http://127.0.0.1:8000/v1/messages with model qwen-0.5b...

Prompt: Who is Anwar Ibrahim? Answer in one sentence.
Response: Anwar Ibrahim is a prominent Malaysian politician and businessman who has served as the Prime Minister of Malaysiasince 2018. He was previously the Deputy Prime Minister from 2013 to 2018 and also holds other positions such as Minister for Finance and Minister for Health. Ibrahim is known for his economic reforms, including his efforts to liberalize the economyand increase investment in infrastructure and education.

Prompt: What is Ray Serve? Answer in one sentence.
Response: Ray Serve is an open-source framework designed to simplify the deployment and management of machine learning models using Ray's GPU-based parallel computing capabilities. It provides tools for deploying models across multiple nodes on GPUs, enabling efficient training and inference processes.

Prompt: What is Ray Serve? Answer in one sentence.
Response: Ray Serve is a distributed computing framework designed to efficiently process and deploy machine learning models across multiple servers or instances, enabling rapid experimentation with new algorithms and data sets while maintaining highperformance for real-time applications.

AI assistance

AI assistance was used during implementation. I reviewed the changed code and am responsible for understanding, testing, and maintaining it. Used Cursor: GPT-5.6 Sol, Cursor Grok 4.6

Signed-off-by: wanadzhar913 <adzhar.faiq@gmail.com>
Signed-off-by: wanadzhar913 <adzhar.faiq@gmail.com>
Signed-off-by: wanadzhar913 <adzhar.faiq@gmail.com>
@wanadzhar913
wanadzhar913 requested review from a team as code owners August 14, 2026 14:20

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for the Anthropic Messages API and token counting endpoints, integrating them with the Ray Serve LLM engine and vLLM. It adds new ingress routing, Pydantic models, and helper utilities, alongside comprehensive tests. The review feedback highlights several critical robustness improvements, including handling potential null values and non-integer status codes in error translation, catching stream-time exceptions during message generation, gracefully handling empty generators or StopAsyncIteration in the ingress endpoints, and supporting string-based class names in configuration comparisons.

Comment on lines +15 to +25
def translate_error_response(response: ErrorResponse) -> JSONResponse:
anthropic_error = AnthropicErrorResponse(
error=AnthropicError(
type=response.error.type,
message=response.error.message,
)
)
return JSONResponse(
status_code=response.error.code,
content=anthropic_error.model_dump(exclude_none=True),
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Accessing properties on response.error without a None check violates defensive programming guidelines. Additionally, response.error.code can be a string or None in OpenAI/vLLM error responses, which will cause a runtime error in Starlette's JSONResponse since status_code must be an integer. We should safely convert the code to an integer and handle potential None values.

def translate_error_response(response: ErrorResponse) -> JSONResponse: 
    error_info = response.error
    if error_info is None:
        return JSONResponse(
            status_code=500,
            content={"error": {"type": "internal_error", "message": "An unknown error occurred."}},
        )

    try:
        status_code = int(error_info.code) if error_info.code is not None else 500
    except (ValueError, TypeError):
        status_code = 500

    anthropic_error = AnthropicErrorResponse(
        error=AnthropicError(
            type=error_info.type or "internal_error",
            message=error_info.message or "An unknown error occurred.",
        )
    )
    return JSONResponse(
        status_code=status_code,
        content=anthropic_error.model_dump(exclude_none=True),
    )

Comment on lines +733 to +740
if isinstance(messages_response, AsyncGenerator):
async for response in messages_response:
if not isinstance(response, str):
raise ValueError(
"Expected create_messages to return a stream of strings, "
f"got an item with type {type(response)}"
)
yield response

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Any exception raised during the iteration of the messages_response async generator (e.g., token generation errors, connection drops, or engine failures) will propagate uncaught because the async for loop is outside the try...except block. We should wrap the iteration in a try...except block to ensure all stream-time exceptions are properly caught and translated into error responses.

        if isinstance(messages_response, AsyncGenerator):
            try:
                async for response in messages_response:
                    if not isinstance(response, str):
                        raise ValueError(
                            "Expected create_messages to return a stream of strings, "
                            f"got an item with type {type(response)}"
                        )
                    yield response
            except Exception as e:
                yield self._make_error_response(self._anthropic_serving_messages, e)

Comment on lines +746 to +760
initial_response, gen = await _peek_at_generator(gen)

if isinstance(initial_response, ErrorResponse):
return translate_error_response(initial_response)

if isinstance(initial_response, str):

async def stream():
yield initial_response
async for item in gen:
yield item

return anthropic_messages_http_response(stream())

return anthropic_messages_http_response(initial_response)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If the generator gen is empty, initial_response will be None. Passing None to anthropic_messages_http_response will result in an invalid StreamingResponse with None content, leading to a broken or hanging connection. We should explicitly handle the None case by raising an HTTPException.

            initial_response, gen = await _peek_at_generator(gen)

            if initial_response is None:
                raise HTTPException(
                    status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                    detail="Engine returned an empty response.",
                )

            if isinstance(initial_response, ErrorResponse):
                return translate_error_response(initial_response)

            if isinstance(initial_response, str):

                async def stream():
                    yield initial_response
                    async for item in gen:
                        yield item

                return anthropic_messages_http_response(stream())

            return anthropic_messages_http_response(initial_response)

Comment on lines +774 to +775
results = model_handle.count_tokens.remote(body, raw_request_info)
result = await results.__anext__()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

await results.__anext__() can raise StopAsyncIteration if the generator is empty. This uncaught exception will bubble up and cause a 500 Internal Server Error. We should wrap it in a try...except StopAsyncIteration block to handle it gracefully.

Suggested change
results = model_handle.count_tokens.remote(body, raw_request_info)
result = await results.__anext__()
raw_request_info = RawRequestInfo.from_starlette_request(request)
results = model_handle.count_tokens.remote(body, raw_request_info)
try:
result = await results.__anext__()
except StopAsyncIteration:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Engine did not return any response for token counting.",
)

Comment on lines +346 to +351
ingress_cls_config = builder_config.ingress_cls_config
if ingress_cls_config.ingress_cls == OpenAiIngress:
ingress_cls_config = IngressClsConfig(
ingress_cls=AnthropicIngress,
ingress_extra_kwargs=ingress_cls_config.ingress_extra_kwargs,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Direct comparison ingress_cls_config.ingress_cls == OpenAiIngress will fail if ingress_cls is configured as a string (e.g., "OpenAiIngress" or "ray.serve.llm.OpenAiIngress" in a YAML configuration file). This will prevent the application from automatically switching to AnthropicIngress, leading to 404 errors on the Anthropic endpoints. We should handle both class and string comparisons.

Suggested change
ingress_cls_config = builder_config.ingress_cls_config
if ingress_cls_config.ingress_cls == OpenAiIngress:
ingress_cls_config = IngressClsConfig(
ingress_cls=AnthropicIngress,
ingress_extra_kwargs=ingress_cls_config.ingress_extra_kwargs,
)
ingress_cls = ingress_cls_config.ingress_cls
if ingress_cls == OpenAiIngress or (isinstance(ingress_cls, str) and ingress_cls.endswith("OpenAiIngress")):
ingress_cls_config = IngressClsConfig(
ingress_cls=AnthropicIngress,
ingress_extra_kwargs=ingress_cls_config.ingress_extra_kwargs,
)

Comment on lines +376 to +381
ingress_cls_config = builder_config.ingress_cls_config
if ingress_cls_config.ingress_cls == OpenAiIngress:
ingress_cls_config = IngressClsConfig(
ingress_cls=AnthropicIngress,
ingress_extra_kwargs=ingress_cls_config.ingress_extra_kwargs,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Direct comparison ingress_cls_config.ingress_cls == OpenAiIngress will fail if ingress_cls is configured as a string (e.g., "OpenAiIngress" or "ray.serve.llm.OpenAiIngress" in a YAML configuration file). This will prevent the application from automatically switching to AnthropicIngress, leading to 404 errors on the Anthropic endpoints. We should handle both class and string comparisons.

Suggested change
ingress_cls_config = builder_config.ingress_cls_config
if ingress_cls_config.ingress_cls == OpenAiIngress:
ingress_cls_config = IngressClsConfig(
ingress_cls=AnthropicIngress,
ingress_extra_kwargs=ingress_cls_config.ingress_extra_kwargs,
)
ingress_cls_config = builder_config.ingress_cls_config
ingress_cls = ingress_cls_config.ingress_cls
if ingress_cls == OpenAiIngress or (isinstance(ingress_cls, str) and ingress_cls.endswith("OpenAiIngress")):
ingress_cls_config = IngressClsConfig(
ingress_cls=AnthropicIngress,
ingress_extra_kwargs=ingress_cls_config.ingress_extra_kwargs,
)

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit ea9fe11. Configure here.

raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Unexpected response type from count_tokens",
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ingress errors not Anthropic-shaped

Medium Severity

Engine ErrorResponse values are translated to Anthropic payloads, but missing-model lookups, FastAPI HTTPExceptions, and the shared exception middleware still emit OpenAI-shaped JSON (error.message/error.code, or detail). Anthropic SDKs and Claude Code expect {type: "error", error: {type, message}}, so common failures such as an unknown model will not parse as API errors.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ea9fe11. Configure here.

@ray-gardener ray-gardener Bot added serve Ray Serve Related Issue llm community-contribution Contributed by the community labels Aug 14, 2026

@eicherseiji eicherseiji left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @wanadzhar913, this is an exciting contribution! Please reach out on Ray Slack so we can land this


if isinstance(initial_response, str):

async def stream():

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we stream gen directly here? _peek_at_generator() returns a replacement generator that already replays initial_response, so yielding it again duplicates the first SSE event—usually message_start—and produces an invalid event sequence for strict Anthropic clients.

DEFAULT_MAX_ONGOING_REQUESTS,
DEFAULT_MAX_TARGET_ONGOING_REQUESTS,
)
from ray.llm._internal.serve.core.configs.anthropic_api_models import (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we keep these vLLM-only imports off the SGLang import path, for example by loading them lazily on the Anthropic path? The supported SGLang image uninstalls vLLM, but ray.serve.llm imports the builder and then this module, so this raises before the existing OpenAI/SGLang code can load. This breaks TestSGLangProtocolDecoupling.test_modules_importable_without_vllm.

default_ingress_options, builder_config.ingress_deployment_config
)

ingress_cls = make_fastapi_ingress(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we install Anthropic-specific validation and HTTPException handlers on this app? make_fastapi_ingress() currently uses the shared init() app, whose validation handler emits an OpenAI error envelope and whose default HTTP error response uses {"detail": ...}. As a result, malformed bodies and unknown-model errors on both Anthropic endpoints return incompatible response shapes instead of Anthropic error envelopes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community-contribution Contributed by the community llm serve Ray Serve Related Issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Ray serve] Add Native Anthropic Messages API (/v1/messages) Support to Ray Serve LLM

2 participants