Skip to content

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

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

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

Conversation

@pull

@pull pull Bot commented Aug 3, 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 11 commits August 3, 2026 13:40
`TestEnvRunnerCallbacks.test_callbacks_on_sample_timesteps` and the
sibling `test_callbacks_on_sample_rollout` asserted

    len(on_episode_start_calls) == sum(is_done) + num_envs

which is off by one whenever an episode terminates exactly on the final
sampled timestep. In that case the env runner eagerly creates the
replacement episode (firing `on_episode_created`) but does not start it:
`on_episode_start` fires only once the replacement receives its reset
observation on the *next* sampled timestep, which never comes within
this `sample()` call. With unseeded `random_actions=True`, CartPole
episode lengths are stochastic, so this boundary is hit intermittently
-- the source of the flakiness observed on master.

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

Deprecate `ray_remote_args_fn` across the public Ray Data transformation
APIs.

- Emits a `RayDeprecationWarning` when `ray_remote_args_fn` is passed to
`Dataset.map`, `map_batches`, `flat_map`, `filter`, or
`GroupedData.map_groups`

- Marks the argument for removal in Ray 2.64.
- Renames `_map_batches_without_batch_size_validation` to the developer
API
  `map_batches_internal`.
- Routes internal Ray Data LLM stages through `map_batches_internal` so
they
  don't trigger a user-facing deprecation warning.
- Adds a warning to the documentation section that recommends creating
placement groups
  through `ray_remote_args_fn`.
---

Follow-up to #64631

---------

Signed-off-by: Alex Chien <alexchien130@gmail.com>
Co-authored-by: Balaji Veeramani <bveeramani@berkeley.edu>
#63858)

## Why are these changes needed?

Adds `Dataset.with_columns(...)`, the plural counterpart to
`with_column`, so multiple expression columns can be added or
overwritten in a single projection instead of chaining several
`with_column` calls (which duplicates work). `with_column` now delegates
to `with_columns` for the projection path.

This is the first PR of the split proposed in #63838. Per discussion
there with @goutamvenkat-anyscale and @AyushKashyapII, the `UnnestExpr`
/ multi-output projection-engine changes are intentionally **out of
scope here** and will follow in a separate PR.

## Changes

- `Dataset.with_columns(exprs: Dict[str, Expr], *, compute=None,
**ray_remote_args)` — builds a single `Project` with `StarExpr()` plus
one aliased expression per entry. Column order follows the mapping's
insertion order.
- `Dataset.with_column` becomes a thin wrapper over `with_columns` for
the standard projection path.
- Tests in `test_with_column.py` covering multiple columns, order
preservation, overwriting an existing column, the `with_column`
delegation (regression guard), and input validation.

## Notes / open questions for reviewers

- **`DownloadExpr` handling**: I deliberately left `with_column`'s
existing `DownloadExpr` branch intact rather than generalizing download
semantics into `with_columns`, to keep this PR scoped and avoid any
regression. `with_columns` handles the expression-projection case only.
Happy to revisit if you'd prefer download support in the plural API.
- **Validation**: `with_columns` raises `ValueError` on a non-dict or
empty mapping and `TypeError` on a non-`Expr` value. Flagging in case
you'd prefer different behaviour (e.g. silently allowing an empty dict
as a no-op).

## Checks

Ran `python/ray/data/tests/test_with_column.py` locally against a
python-only Ray dev build. 98 passed (the existing `with_column` suite
plus the new `with_columns` cases). Verified the new tests actually
execute with PyArrow ≥ 20.

Closes part of #63838.

---------

Signed-off-by: odncode <nnajiodera2@gmail.com>
The docstring said `use_datasource_v2` defaults to False and that "V1
remains the production path while V2 bakes". The default is True:

DEFAULT_USE_DATASOURCE_V2 = env_bool("RAY_DATA_USE_DATASOURCE_V2", True)

so `read_parquet()` has been going through DataSourceV2 by default, and
the docstring sends readers looking for an env var to enable something
that is already on.

Also note the flag's scope, which was easy to over-read:
`use_datasource_v2` is consulted in exactly one place (`read_parquet()`
in read_api.py); every other read API reads through V1 no matter how it
is set.

Docstring only, no behavior change.

> Thank you for contributing to Ray! 🚀
> Please review the [Ray Contribution
Guide](https://docs.ray.io/en/master/ray-contribute/getting-involved.html)
before opening a pull request.

---------

Signed-off-by: Aarrya <aarrya.saraf@anyscale.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…Data context (#65103)

## Description

As title
## 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: You-Cheng Lin <mses010108@gmail.com>
Signed-off-by: You-Cheng Lin <106612301+owenowenisme@users.noreply.github.com>
Co-authored-by: Balaji Veeramani <bveeramani@berkeley.edu>
…ting it (#65040)

## Why

This is an **alternative** to #64514. Same goal — no `@PublicAPI`
subpackage silently escapes the code↔docs consistency walk — but it
walks `ray.data.llm` instead of allowlisting it.

#64514 allowlists `ray.data.llm` on the premise that its eager `import
transformers` (via the vLLM/SGLang engine processor configs) can't be
imported in the docbuild image. That premise predates the
`_mock_uninstalled_backends` wrapper now on `master`, which mocks
exactly the backends the docbuild image lacks. Under that mock,
`ray.data.llm` imports cleanly and can be a walk root like
`ray.serve.llm` — so its documented surface gets checked rather than
merely excluded.

## What

- Add `ray.data.llm` to the data team `head_modules`. Its surface is
documented in `doc/source/data/api/llm.rst`, reachable from `api.rst`'s
toctree, so the walk checks the code surface against the real docs.
- Run the cross-team coverage guard **inside**
`_mock_uninstalled_backends` so its coverage check and import probes see
the same mocks as the walks; a promoted head like `ray.data.llm` is
counted as covered, not flagged.
- Empty `UNWALKED_ANNOTATED_ALLOWLIST`: both known escapees
(`ray.serve.llm`, `ray.data.llm`) are now walked. The list stays as the
reviewed record for any future subpackage that genuinely can't be a walk
root.
- Decouple the guard decision-core unit tests from real module names.

## Relationship to #64514

Pick one, not both. This branch carries #64514's guard commit plus one
delta commit; if this approach is preferred, the two squash into one
before un-drafting.

## Checks

The decisive check is **doc: check API doc consistency** — it exercises
the mocked `ray.data.llm` import path this approach depends on, against
the real docbuild image. Kept as a draft until that check is confirmed
green.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Douglas Strodtman <douglas@anyscale.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…65096)

## Description
I found it quite difficult to understand the autoscaling coordinator
code without type aliasing, so adding it now. No behavior changes

## Motivation
I don't think applying type aliases to everything in Ray Data is
standard, just for the a) terms that can conflate with each other and b)
for abstract interfaces. Declarations like so
`self._subcluster_selectors: Dict[str, Optional[Dict[str, str]]] = {})`
or `cluster_node_resources: Dict[Optional[str], List[ResourceDict]] =
{}` and it's not clear what each str represents. And if we need to
change the definition of the type for a abstract class parameter it can
be done easily by modifying the type alias

## 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: iamjustinhsu <jhsu@anyscale.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…protection to fiber stacks (#64772)

# Description

On Python 3.14 + Linux, every async-actor task permanently leaks ~518
KiB of live malloc (the per-task `asyncio.Task`,
`concurrent.futures.Future`, Cython coroutine + scopes, and two msgpack
`Packer`s with 256 KiB internal buffers).
Closes #63290

### Root cause

**1. CPython 3.14 changed how it avoids stack overflow when freeing
objects.** Freeing one object can recursively free many others (a dict
frees its values, which free their contents, …), and each level is a
nested C call. To keep that from overflowing the C stack, CPython has
long had a safety mechanism (the "trashcan"): when it decides it's too
deep, it doesn't free the object right away. Instead it parks the object
on a per-thread *delete-later* list and drains the list once there's
stack headroom again. Up to 3.13, "too deep" was a simple recursion
counter. In 3.14 it's decided by comparing the actual machine **stack
pointer** against the stack bounds CPython recorded for the thread when
it attached (from pthreads, on Linux).

**2. Ray async actors don't run task code on the thread's normal
stack.** Each task executes on a small 256 KiB boost fiber stack
allocated elsewhere in memory. The problem is that CPython still thinks
the thread runs on its original pthread stack.

So while a task runs on a fiber, every "am I near the stack limit?"
check compares the fiber's stack pointer against the *pthread* stack's
bounds. On Linux, fiber stacks happen to be allocated at lower addresses
than the pthread stack, so CPython concludes the stack is hopelessly
overflowed and parks **every** object freed during the task (including
return-value serialization and end-of-task cleanup) on the delete-later
list.

That list is only ever drained by a later free on the same thread state
at a healthy stack margin, which never happens here as the Ray thread
only runs on fibers and Ray creates a fresh Python thread state per task
and destroys it at task end. This means that CPython destroys a thread
state **without draining its delete-later list** and the parked objects
are orphaned permanently. That's the leak.

Why the confusing symptoms:

- `boost::make_fcontext` in the issue's flamegraphs just marks *where*
the leaked allocations were made (on a fiber stack); the fiber stacks
themselves are freed correctly.
- macOS is unaffected only by luck: fiber stacks there land at *higher*
addresses than the pthread stack, so the check passes.
- 3.13 and earlier are unaffected because their trashcan uses the
counter, not the stack pointer.

### Fix

CPython 3.14.2 added an official API for exactly this situation:
`PyUnstable_ThreadState_SetStackProtection` (python/cpython#141661) lets
an embedder tell CPython "this thread is currently executing on *this*
stack." We call it with the fiber's stack bounds:

- at async-actor task entry in `task_execution_handler`, and
- whenever a fiber resumes after `YieldCurrentFiber` (concurrent fibers
share the thread state, so each must re-register its own stack).

With the bounds correct, the near-limit check returns to normal
behavior: objects are freed immediately, and the rare genuinely-deep
free is parked and then properly drained.

Implementation notes: the symbol is looked up via `dlsym`, so `_raylet`
still imports on 3.14.0/3.14.1 (fix skipped there; those releases have a
more severe, since-fixed stack-check bug anyway, python/cpython#141944).
No-op below 3.14 (preprocessor-gated) and on Windows. Stack bounds are
derived from the current stack pointer minus a conservative allowance
for stack already used, so the protection errs toward triggering
slightly early rather than missing an overflow. Side benefit: fibers
gain real C-stack overflow protection (RecursionError) on 3.14, which
they currently lack entirely (`boost::fibers::fixedsize_stack` has no
guard pages). Also makes `FiberState::kStackSize` public so the
anchoring uses the real fiber stack size.

## Related issue number

Closes #63290. Supersedes #63284 (same diagnosis direction, but
hand-rolled `_PyThreadStateImpl` offsets, a deliberate
`gilstate_counter` leak that freezes non-main threads, and a crash
premise that CPython 3.14.2 already fixed upstream).

## Checks

- Verified with a locally built cp314 Linux (aarch64, python:3.14.6
docker) wheel:
- refcount probe: **+4.00 refs/task → 0.00/task** (100 tasks)
- `__del__` deferral probe: dealloc during return serialization on the
fiber **deferred → immediate**
- live-malloc probe (`mallinfo2`, 300 tasks/shape): **~518 KiB/task → ~3
KiB/task** across async call → dict/bytes, async generator, sync
generator on async actor
- reporter-shaped streaming workload (400 tasks, 10 concurrent
sessions): live-malloc delta **0.2 MB total**, fiber-sized mapped
regions 0 → 0
- async-actor smoke: correctness (echo, state, async generators,
recursion), concurrency (20 overlapping 0.5 s sleeps in 0.51 s)
- throughput A/B (500 sequential echo tasks, 3 runs fixed / 2 runs
baseline, same container image): fixed 5624–6076 tasks/s vs unpatched
4551–4825 tasks/s meaning no regression (the unpatched build is slower
while leaking)
- baseline (unpatched) wheel from the same tree reproduces the bug:
+4.00 refs/task, fiber dealloc deferred=True

note: fable did a majority of the heavy lifting in this investigation
with prompting on what to check next and validate the solution

---------

Signed-off-by: Mark Towers <mark@anyscale.com>
Signed-off-by: myan <myan@anyscale.com>
Co-authored-by: Mark Towers <mark@anyscale.com>
Co-authored-by: myan <myan@anyscale.com>
Co-authored-by: Mengjin Yan <mengjinyan3@gmail.com>
…es (#65137)

## Description
The `PrometheusTimeseries` test util only merges scrapes and never drops
series that disappear from `/metrics`. A mid-transition pending sample
can outlive the agent-side series after gauge TTL and keep the summed
count too high and cause flaky.

This PR uses `flush=True` on each poll (same idea as
`test_stale_view_cleanup_when_job_exits`) so we only assert on the
current scrape.

windows://python/ray/tests:test_task_metrics passes
<img width="1662" height="678" alt="image"
src="https://github.com/user-attachments/assets/e0e9a9a1-b712-4fbd-9ad6-b06018368039"
/>

---------

Signed-off-by: Rueian Huang <rueiancsie@gmail.com>
…DataContext.max_consecutive_actor_init_deaths (#64846)

## Why are these changes needed?

A single actor whose `__init__` fails (e.g. an LLM engine failing to
boot) kills the whole Dataset job: the readiness ref resolves with
`ActorDiedError`, `_ActorPool.pending_to_running` re-raises it, and the
metadata-task branch of `process_completed_tasks` has no guard
(`max_errored_blocks` only protects the `DataOpTask` branch), so the
exception fails the execution. `max_restarts=-1` (Ray Data's own
default) cannot help because Ray Core does not restart actors whose
creation task failed — the death is a `USER_ERROR` and
`gcs_actor_manager` zeroes `remaining_restarts`. At an N-actor pool, any
per-boot flake probability p becomes job-death probability 1-(1-p)^N: at
1,600 vLLM engines, three separate boot-flake classes (port races, VRAM
residue, ramp-wave timeouts) each repeatedly killed full production
runs.

This PR catches the failure at the pool boundary (`_task_done_callback`)
and charges it against a new per-operator budget,
`DataContext.max_consecutive_actor_init_deaths` (env
`RAY_DATA_MAX_CONSECUTIVE_ACTOR_INIT_DEATHS`; default `0` preserves
today's fail-fast behavior; `-1` = unlimited). The counter is
**consecutive**: it resets whenever any actor of the operator
initializes successfully — the same semantics as the existing
`actor_init_max_retries`. Sporadic boot flakes in a progressing pipeline
are tolerated indefinitely within the budget, while a systemically
broken UDF (bad model path) never resets the counter and exhausts the
budget, so no separate systemic-failure heuristic or hardcoded threshold
is needed. Tolerated failures are logged with a running count and the
actor autoscaler starts a replacement (the pool is below target size,
and the below-min branch runs before the no-input guard, so no explicit
re-provision is needed). Counting is per actor incarnation, after any
in-actor `actor_init_retry_on_errors` retries.

## Production-scale evidence

Two byte-identical 1,600-engine / 70M-caption video-captioning runs on
Anyscale (CoreWeave, RTX PRO 6000 fleet), same input corpus and knobs;
the only variable is boot-failure handling (run against the earlier
cumulative-budget revision of this patch; the consecutive-counting
revision is strictly more permissive for the interleaved-flake pattern
observed):

| | Baseline (app-level workaround that swallows pending_to_running
errors) | This patch (workaround disabled, budget=200) |
|---|---|---|
| Outcome | SUCCEEDED | SUCCEEDED |
| Captions written | 69,982,028 | 69,982,028 (identical) |
| Wall time | 5,525 s | 5,778 s |
| Real engine-boot failures absorbed | 2 | 8 (single ramp-wave burst;
every one logged and the pool recovered to its full 1,500 floor) |

A separate fault-injection job validated both semantics
deterministically on the same stack: 3 injected creation-task failures
with budget 5 → dataset completes with correct output; budget 1 →
tolerates one, then re-raises the original `ActorDiedError` after
exactly 2 incarnations.

Companion PR for the GCS-side half of the same incident: #64845.

## Related issue number

None found — searched open PRs/issues for actor init/boot failure
tolerance before opening; no in-flight work. Adjacent context: the vLLM
batch stage's `os._exit(1)` workaround handles post-boot engine death
via `max_restarts` (see #59522), but nothing covers creation-task
failures.

## Checks

- [x] I've signed off every commit (DCO).
- [x] I've run pre-commit on the changed files.
- [x] Testing strategy:
- New tests in `python/ray/data/tests/test_actor_pool_map_operator.py`:
`test_actor_init_death_budget` (parametrized: budget covers / exceeded /
unlimited), `test_actor_init_death_budget_with_in_actor_retries` (proves
incarnation-level counting), `test_on_actor_init_death_unit` (raise
timing against the consecutive budget, reset-on-success, re-raise
identity).
- `python -m pytest
python/ray/data/tests/test_actor_pool_map_operator.py -k "init_death or
init_failure"` → 8 passed (pytest 7.4.4). Existing
`test_actor_init_failure_retry` and `test_actor_pool_map_operator_init`
pass unchanged.

Per the repo's AI contribution policy: AI assistance was used to develop
this change; every changed line was reviewed, the tests above were run
locally, and the change was exercised end-to-end at production scale as
described.

---------

Signed-off-by: xyuzh <xinyzng@gmail.com>
## Description

`ResourceRequest::operator==` / `operator!=` compared only `resources_`,
ignoring the other two fields of the class,
`requires_object_store_memory_` and `label_selector_`. Two requests with
identical quantities but a different object-store-memory flag or label
selector compared equal, so any dedup, change-detection, or map-key use
of `ResourceRequest` silently conflated requests that are not
interchangeable.

`operator==` now compares all three fields (`LabelSelector` already
provides `operator==`), and `operator!=` is defined as its negation.
`operator<=` / `operator>=` are unchanged — comparing quantities only is
their intended resource-containment semantics.

Added a test constructing requests with equal quantities that differ
only by the label selector or the object-store-memory flag and asserting
inequality; it fails against the previous `operator==`.

## Related issues

Fixes #64837

## Additional information

`bazel test //src/ray/common/scheduling/tests:resource_request_test`
passes, and the adjacent scheduling suites (`resource_set_test`,
`resource_instance_set_test`, `cluster_resource_scheduler_test`,
`cluster_resource_manager_test`) still pass. Verified the new test fails
when the fix is reverted.

Signed-off-by: yangjie01 <yangjie01@baidu.com>
@pull pull Bot locked and limited conversation to collaborators Aug 3, 2026
@pull pull Bot added the ⤵️ pull label Aug 3, 2026
@pull
pull Bot merged commit 6ed1350 into garymm:master Aug 3, 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.