Skip to content

[pull] master from ray-project:master - #1193

Merged
pull[bot] merged 8 commits into
garymm:masterfrom
ray-project:master
Aug 4, 2026
Merged

[pull] master from ray-project:master#1193
pull[bot] merged 8 commits into
garymm:masterfrom
ray-project:master

Conversation

@pull

@pull pull Bot commented Aug 4, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

ArturNiederfahrenhorst and others added 8 commits August 3, 2026 12:24
…ies (#65036)

TorchSquashedGaussian.logp() recovered the pre-squash value via
atanh(clamp(action)), which caps the recovered value at ~7.25 and
corrupts logp (large negative) once the policy mean saturates tanh. This
breaks SAC/TQC alpha tuning and the critic entropy target on envs where
the policy saturates (e.g. Humanoid).

This is also part of the picture why TQC can't solve humanoid.
The solution involves some magic math ( see
https://github.com/openai/spinningup/blob/master/spinup/algos/pytorch/sac/core.py#L59-L60
)

Adds a regression unit test.

---------

Signed-off-by: Artur Niederfahrenhorst <artur@anyscale.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…#64799)

## Description

`ray::StatusOr<T>` keeps its value in a union member `data_` that is
only a live object when `ok()`; in the error state `data_` is left
unconstructed. Two paths ignored that and touched the unconstructed
storage:

- `swap()` swapped `data_` unconditionally, move-constructing from /
assigning into unconstructed storage when either operand was an error.
- `operator=(const&)` and `operator=(&&)` set `status_` to OK before
calling `AssignValue`, which destroys the old value only when `ok()`.
With the status flipped first, it ran `data_.~T()` on unconstructed
storage whenever the destination was previously an error.

Both assignment operators now branch on the current state: when this
already holds a value, assign through `T`'s own `operator=` to reuse its
resources and stay exception-safe (a throwing assignment leaves the
object intact instead of status-OK-with-no-value); otherwise
placement-new the value and only then set `status_` to OK. `swap` is
specialized for the four `ok()`/error combinations: swap the underlying
values (or statuses) directly when both sides match, and move the lone
value across when only one side holds it — O(1) for swappable `T`, with
no touch of unconstructed storage.

Added tests covering assignment and swap across the error/value
combinations using a type with a live-instance counter, so a stray or
missing ctor/dtor is caught in a normal test run without relying on a
sanitizer. Each branch was verified to fail against the pre-fix code.

## Related issues

Fixes #64798

## Additional information

`bazel test //src/ray/common/tests:status_or_test` passes, including
under `--config=asan`. Verified the new tests fail when the fix is
reverted.

---------

Signed-off-by: yangjie01 <yangjie01@baidu.com>
Co-authored-by: Chi-Sheng Liu <chishengliu@chishengliu.com>
## Root cause

Verl SGLang crashes on Intel GPU because Ray sets
ONEAPI_DEVICE_SELECTOR=level_zero:N in workers, but SGLang suppresses
Ray's env var via RAY_EXPERIMENTAL_NOSET_ONEAPI_DEVICE_SELECTOR and
sets its own bare IDs (e.g. ONEAPI_DEVICE_SELECTOR=0,1). That format
is invalid — the level_zero: prefix is required — causing an immediate
torch import crash in SGLang subprocesses.

## Changes

**python/ray/_private/accelerators/intel_gpu.py**
- ZE_AFFINITY_MASK is now the primary env var (bare IDs, consistent
  with CUDA_VISIBLE_DEVICES)
- get_current_process_visible_accelerator_ids() reads ZE_AFFINITY_MASK
  first, falls back to ONEAPI_DEVICE_SELECTOR for backward compat
  (handles both level_zero:0,1 and bare 0,1 formats)
- set_current_process_visible_accelerator_ids() writes both
  ZE_AFFINITY_MASK (physical bare IDs) and ONEAPI_DEVICE_SELECTOR
  (level_zero: prefixed re-indexed sequential IDs)
- Added RAY_EXPERIMENTAL_NOSET_ZE_AFFINITY_MASK flag; existing
  RAY_EXPERIMENTAL_NOSET_ONEAPI_DEVICE_SELECTOR still works
  independently

**Bug found and fixed:** initial implementation wrote
ONEAPI_DEVICE_SELECTOR=level_zero:1 for physical GPU 1, but
ZE_AFFINITY_MASK re-indexes devices before ONEAPI_DEVICE_SELECTOR
applies — intersection was empty, worker saw 0 GPUs. Fixed:
ONEAPI_DEVICE_SELECTOR always uses sequential re-indexed IDs
(level_zero:0, level_zero:0,1) regardless of physical IDs.
Hardware-confirmed on 2x Intel GPU:
  GPU 1 before fix: ZE=1 + ONEAPI=level_zero:1 → 0 devices visible
  GPU 1 after fix:  ZE=1 + ONEAPI=level_zero:0 → 1 device visible ✓

**doc/source/ray-core/scheduling/accelerators.rst**
- Updated tip and code examples to use ZE_AFFINITY_MASK
- Added backward compat note for ONEAPI_DEVICE_SELECTOR users

**doc/source/serve/llm/user-guides/sglang.md**
- Added Intel GPU prerequisite entry:
  RAY_EXPERIMENTAL_NOSET_ZE_AFFINITY_MASK=1 and
  RAY_EXPERIMENTAL_NOSET_ONEAPI_DEVICE_SELECTOR=1
  (both needed since Ray now writes both vars)

**python/ray/tests/accelerators/test_intel_gpu.py**
- Added parametrize cases for non-zero physical GPU IDs (["1"],
  ["1","3"]) — these are the cases that catch the re-indexing bug
- Replaced manual del os.environ cleanup with clean_accelerator_env
  fixture that restores env vars even on test failure
- Split noset flag tests into two independent functions

**python/ray/tests/accelerators/test_intel_gpu_e2e.py**
- Updated gpu_task and cluster_probe_task to return ze_affinity_mask
- Updated _validate_gpu_binding_common to check ZE_AFFINITY_MASK
  for physical GPU IDs (ONEAPI_DEVICE_SELECTOR now carries re-indexed
  IDs so physical ID comparison against it would be wrong)

## Test plan
- `python -m pytest python/ray/tests/accelerators/test_intel_gpu.py -v`
  No GPU needed, passes on any machine
- `RAY_PYTEST_USE_GPU=1 python -m pytest
python/ray/tests/accelerators/test_intel_gpu_e2e.py -v`
  Requires Intel GPU + dpctl. Single-node 2x GPU tests pass.
  Multi-node test (test_scale_out_task_distribution) is implemented
  but not yet validated — no multi-node Intel GPU cluster available.

---------

Signed-off-by: Kah Lun teoh <kah.lun.teoh@intel.com>
Co-authored-by: Edward Oakes <ed.nmi.oakes@gmail.com>
…65182)

## Description
Fix the test_tpu.py on windows 

```

[2026-08-03T18:43:13Z] ================================== FAILURES ===================================
--
  | [2026-08-03T18:43:13Z] _________________ test_autodetect_num_tpus_vfio_mixed_groups __________________
  | [2026-08-03T18:43:13Z]
  | [2026-08-03T18:43:13Z] path = '/sys/kernel/iommu_groups/10/devices\\0000:01:00.0\\vendor', args = ()
  | [2026-08-03T18:43:13Z] kwargs = {'encoding': 'ascii'}
  | [2026-08-03T18:43:13Z]
  | [2026-08-03T18:43:13Z]     def fake_open(path, *args, **kwargs):
  | [2026-08-03T18:43:13Z]         try:
  | [2026-08-03T18:43:13Z] >           return mock.mock_open(read_data=vendor_results[path])()
  | [2026-08-03T18:43:13Z] E           KeyError: '/sys/kernel/iommu_groups/10/devices\\0000:01:00.0\\vendor'
  | [2026-08-03T18:43:13Z]
  | [2026-08-03T18:43:13Z] python\ray\tests\accelerators\test_tpu.py:120: KeyError
  | [2026-08-03T18:43:13Z]
  | [2026-08-03T18:43:13Z] During handling of the above exception, another exception occurred:
  | [2026-08-03T18:43:13Z]
  | [2026-08-03T18:43:13Z] mock_list = <MagicMock name='listdir' id='2259299081216'>
  | [2026-08-03T18:43:13Z] mock_glob = <MagicMock name='glob' id='2259298850976'>
  | [2026-08-03T18:43:13Z]
  | [2026-08-03T18:43:13Z]     @patch("glob.glob")
  | [2026-08-03T18:43:13Z]     @patch("os.listdir")
  | [2026-08-03T18:43:13Z]     def test_autodetect_num_tpus_vfio_mixed_groups(mock_list, mock_glob):
  | [2026-08-03T18:43:13Z]         # Two VFIO groups: one is a Google TPU (vendor 0x1ae0), the other is the
  | [2026-08-03T18:43:13Z]         # BlueField-3 SoC (vendor 0x15b3). Only the TPU-backed group is counted.
  | [2026-08-03T18:43:13Z]         mock_glob.return_value = []
  | [2026-08-03T18:43:13Z]         listdir_results = {
  | [2026-08-03T18:43:13Z]             "/dev/vfio": ["vfio", "10", "96"],
  | [2026-08-03T18:43:13Z]             "/sys/kernel/iommu_groups/10/devices": ["0000:01:00.0"],
  | [2026-08-03T18:43:13Z]             "/sys/kernel/iommu_groups/96/devices": ["0016:03:00.2"],
  | [2026-08-03T18:43:13Z]         }
  | [2026-08-03T18:43:13Z]         vendor_results = {
  | [2026-08-03T18:43:13Z]             "/sys/kernel/iommu_groups/10/devices/0000:01:00.0/vendor": "0x1ae0\n",
  | [2026-08-03T18:43:13Z]             "/sys/kernel/iommu_groups/96/devices/0016:03:00.2/vendor": "0x15b3\n",
  | [2026-08-03T18:43:13Z]         }
  | [2026-08-03T18:43:13Z]
  | [2026-08-03T18:43:13Z]         def fake_listdir(path):
  | [2026-08-03T18:43:13Z]             try:
  | [2026-08-03T18:43:13Z]                 return listdir_results[path]
  | [2026-08-03T18:43:13Z]             except KeyError:
  | [2026-08-03T18:43:13Z]                 raise AssertionError(f"unexpected listdir: {path}")
  | [2026-08-03T18:43:13Z]
  | [2026-08-03T18:43:13Z]         def fake_open(path, *args, **kwargs):
  | [2026-08-03T18:43:13Z]             try:
  | [2026-08-03T18:43:13Z]                 return mock.mock_open(read_data=vendor_results[path])()
  | [2026-08-03T18:43:13Z]             except KeyError:
  | [2026-08-03T18:43:13Z]                 raise AssertionError(f"unexpected open: {path}")
  | [2026-08-03T18:43:13Z]
  | [2026-08-03T18:43:13Z]         mock_list.side_effect = fake_listdir
  | [2026-08-03T18:43:13Z]         with patch("builtins.open", side_effect=fake_open):
  | [2026-08-03T18:43:13Z]             TPUAcceleratorManager.get_current_node_num_accelerators.cache_clear()
  | [2026-08-03T18:43:13Z] >           assert TPUAcceleratorManager.get_current_node_num_accelerators() == 1
  | [2026-08-03T18:43:13Z]
  | [2026-08-03T18:43:13Z] python\ray\tests\accelerators\test_tpu.py:127:
  | [2026-08-03T18:43:13Z] _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
  | [2026-08-03T18:43:13Z] C:\rayci\python\ray\_private\accelerators\tpu.py:678: in get_current_node_num_accelerators
  | [2026-08-03T18:43:13Z]     if _is_vfio_group_a_tpu(group):
  | [2026-08-03T18:43:13Z] C:\rayci\python\ray\_private\accelerators\tpu.py:601: in _is_vfio_group_a_tpu
  | [2026-08-03T18:43:13Z]     with open(vendor_path, encoding="ascii") as f:
  | [2026-08-03T18:43:13Z] C:\Miniconda3\lib\unittest\mock.py:1114: in __call__
  | [2026-08-03T18:43:13Z]     return self._mock_call(*args, **kwargs)
  | [2026-08-03T18:43:13Z] C:\Miniconda3\lib\unittest\mock.py:1118: in _mock_call
  | [2026-08-03T18:43:13Z]     return self._execute_mock_call(*args, **kwargs)
  | [2026-08-03T18:43:13Z] C:\Miniconda3\lib\unittest\mock.py:1179: in _execute_mock_call
  | [2026-08-03T18:43:13Z]     result = effect(*args, **kwargs)
  | [2026-08-03T18:43:13Z] _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
  | [2026-08-03T18:43:13Z]
  | [2026-08-03T18:43:13Z] path = '/sys/kernel/iommu_groups/10/devices\\0000:01:00.0\\vendor', args = ()
  | [2026-08-03T18:43:13Z] kwargs = {'encoding': 'ascii'}
  | [2026-08-03T18:43:13Z]
  | [2026-08-03T18:43:13Z]     def fake_open(path, *args, **kwargs):
  | [2026-08-03T18:43:13Z]         try:
  | [2026-08-03T18:43:13Z]             return mock.mock_open(read_data=vendor_results[path])()
  | [2026-08-03T18:43:13Z]         except KeyError:
  | [2026-08-03T18:43:13Z] >           raise AssertionError(f"unexpected open: {path}")
  | [2026-08-03T18:43:13Z] E           AssertionError: unexpected open: /sys/kernel/iommu_groups/10/devices\0000:01:00.0\vendor
  | [2026-08-03T18:43:13Z]
  | [2026-08-03T18:43:13Z] python\ray\tests\accelerators\test_tpu.py:122: AssertionError


```

Signed-off-by: Rueian Huang <rueiancsie@gmail.com>
## Description
Adds a collapsed Serve LLM grafana row for native vLLM KV offload/reload
observability: GPU ↔ CPU throughput, operation rate, transfer bandwidth,
CPU transfer pressure, external prefix-cache hit rate, and P90 lookup
delay. Panels join vLLM worker metrics with serve deployment/replica
labels.

<img width="3417" height="953" alt="Screenshot 2026-07-29 at 5 29 57 PM"
src="https://github.com/user-attachments/assets/313975d2-a1cd-4834-8d6c-bb5ccefcb62a"
/>


## Related issues
> Link related issues: "Fixes #1234", "Closes #1234", or "Related to
#1234".

## Additional information
> Optional: Add implementation details, API changes, usage examples,
screenshots, etc.

---------

Signed-off-by: Jeffrey Wang <jeffreywang@anyscale.com>
…5131)

## Why are these changes needed?

Fixes #64273

The Java dependency `com.google.code.gson:gson` is currently pinned to
version `2.9.1`, which is outdated. This PR upgrades it to `2.11.0`,
which includes bug fixes, performance improvements, and security
enhancements over the previous version.

Key changes in gson 2.10.0–2.11.0:
- Improved `TypeToken` API safety
- Fixed edge cases in JSON parsing
- Performance improvements in serialization/deserialization

## Changes

- Updated `java/dependencies.bzl`: bumped `com.google.code.gson:gson`
  from `2.9.1` to `2.11.0`
- No lock file update required (Ray uses dynamic Maven resolution via
  `rules_jvm_external`, no `maven_install.json` pins gson)

## Checks

- [ ] I've run `scripts/format.sh` to lint the changes in this PR.
- [ ] I've made sure the tests are passing.

## Testing

Dependency version bump only. No API-level changes to Ray source code.
Gson 2.11.0 is backward compatible with 2.9.1 for all usages present
in this codebase.

Signed-off-by: tqKhanh1712 <tonhannhan223@gmail.com>
…#65187)

## Why are these changes needed?

Follow-up to #64765 / #63017. `ArrowBlockAccessor.to_pandas()` maps
Arrow types to `pd.ArrowDtype` via a `_types_mapper`, with carve-outs
for extension and dictionary types. Arrow's `null` type needs the same
carve-out.

`null[pyarrow]` is unusable from pandas: the type carries no type
information, so pandas cannot box a non-null value into such a column.
`fillna` and masked assignment raise `ArrowInvalid`, and in some
pyarrow/pandas combinations the failure comes out of Arrow C++ and
aborts the worker process rather than raising.

This is easy to hit in practice because the type is assigned per block,
not per dataset. A column that has values overall can still be entirely
null within one block — which is common with small blocks — so a pandas
UDF like `map_batches(lambda df: df.fillna(...), batch_format="pandas")`
fails on whichever block happens to hold only nulls:

```python
ds = ray.data.from_items(
    [{"a": 1.0, "b": 2.0}, {"a": 3.0, "b": None}, {"a": None, "b": 4.0}],
    override_num_blocks=3,
)
ds.map_batches(lambda df: df.fillna({"a": 0.0, "b": 0.0}), batch_format="pandas").take_all()
# ArrowInvalid on the block where "a" (or "b") is all-null
```

The fix returns `None` from `_types_mapper` for null-typed columns,
falling back to pandas' default conversion. The Arrow round-trip is
unchanged: `PandasBlockAccessor.to_arrow()` already coerces all-null
columns back to `pa.null()`. The one behavioral consequence, noted in a
comment: a column that is all-null in *every* block stays null-typed
instead of being promoted — same as before this change.

## Related issue number

Follow-up to #64765.


Two tests added:
- `test_arrow_block_to_pandas_null_type_is_not_arrow_backed` — a
`pa.null()` column is not `pd.ArrowDtype`, still round-trips back to
`pa.null()` untouched, and `fillna` now works and adopts the fill
value's type.
- `test_pandas_udf_can_fill_per_block_null_columns` — end-to-end
regression test for the `map_batches` + `fillna` failure above.

Signed-off-by: Goutam <goutam@anyscale.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
## Motivation
Batch inference via `map_batches` with a GPU actor has a lot of the same
boilerplate code: move data to and from the device, convert data into
tensors that can be processed by the GPU, convert the resultant output
tensors back to a format that Ray Data can process. A lot of this is
duplicated code, and similar to how we have sensible defaults in other
parts of our API (such as `DefaultCollateFn` for `iter_torch_batches`),
we should provide a short-hand framework for batch inference on
`map_batches`.

## API
The user has to subclass this class:
```
@publicapi(stability="alpha")
class TorchInference:

    def initialize(self, *args: Any, **kwargs: Any) -> None:
        pass

    def get_device(self) -> "torch.device":
        ...

    def collate(
        self, input_batch: "DataBatch"
    ) -> Union["TensorBatchType", Tuple["TensorBatchType", Any]]:
        ...

    def process_on_device(
        self,
        input_batch: "DataBatch",
        collated_tensors: "TensorBatchType",
        collated_other: Any,
    ) -> Union["TensorBatchType", Tuple["TensorBatchType", Any]]:
        raise NotImplementedError()

    def finalize(
        self,
        input_batch: "DataBatch",
        output_tensors: "TensorBatchType",
        output_other: Any,
    ) -> "DataBatch":
        ...
```
and mandatorily implement `process_on_device` but can also optionally
implement the other 4 methods. On the Ray Data side, we manage moving
data to and from device and also providing sensible defaults for
`get_device`, `collate` and `finalize`.

### The Defaults
- `get_device`: `torch.device("cuda")`
- `collate`: take a numpy batch, convert and concatenate tensors as
necessary.
- `fianlize`: recursively take every Tensor in the batch and convert
them to numpy via `.numpy()`.

## What Happens Underneath?
After initialization, for each batch, the following occurs:
1. The `DataBatch` is fed into `collate`
2. The tensor output of `collate` is moved to device.
3. `process_on_device` is called on GPU device.
4. The tensor output of `process_on_device` is moved back to CPU.
5. `finalize` is called on CPU tensor.

Each stage may need to reference 1) additional information created in
the steps that are not tensors and 2) some other columns in the original
batch. Therefore, for each function invocation, we provide the original
batch as `input_batch` and any other miscellaneous output as
`output_other`.

---------

Signed-off-by: Daniel Shin <kyuds@anyscale.com>
Signed-off-by: Daniel Shin <88547237+kyuds@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@pull pull Bot locked and limited conversation to collaborators Aug 4, 2026
@pull pull Bot added the ⤵️ pull label Aug 4, 2026
@pull
pull Bot merged commit 77abc0c into garymm:master Aug 4, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants