Skip to content

feat(spark): support physical CPU requests and limits for Spark Connect - #896

Open
DUKartik wants to merge 11 commits into
kubeflow:mainfrom
DUKartik:feat/sparkconnect-cpu-resources
Open

DUKartik wants to merge 11 commits into
kubeflow:mainfrom
DUKartik:feat/sparkconnect-cpu-resources

Conversation

@DUKartik

@DUKartik DUKartik commented Oct 23, 2026 •

Copy link
Copy Markdown

Closes #578
Part of #837

Summary

Implements the SDK side of #578, Track SparkConnect CPU resource support after SparkConnect CRD enhancements.

For Spark Connect sessions, the existing cpu resource now represents physical Kubernetes CPU rather than Spark cores. The SDK maps it to equal coreRequest and coreLimit values on the SparkConnect CR:

SDK setting SparkConnect field Meaning
Driver.resources["cpu"] spec.server.coreRequest and spec.server.coreLimit Physical Kubernetes CPU request and limit for the server pod
resources_per_executor["cpu"] or Executor.resources_per_executor["cpu"] spec.executor.coreRequest and spec.executor.coreLimit Physical Kubernetes CPU request and limit for executor pods

The SDK no longer sets Spark cores for Spark Connect. When cpu is omitted, the physical CPU fields are left unset and Kubernetes/Spark defaults apply.

The operator-side support is kubeflow/spark-operator#3096 (the implementation for #2957). The SDK behavior requires an operator version containing #3096.

Motivation

The SparkConnect CRD previously represented CPU only through cores, so the SDK could only express Spark task parallelism. Users could not independently request a fractional or guaranteed Kubernetes CPU amount for the server and executor pods.

The SparkConnect CRD now has coreRequest and coreLimit. This PR exposes those fields through the existing Driver.resources and Executor.resources_per_executor dictionaries while keeping the SDK API unchanged:

  • Existing cpu is reused for physical Kubernetes CPU.
  • No new public resource keys are introduced.
  • Requests and limits are always equal, matching the simpler Trainer SDK behavior.
  • Spark cores are not configured by the SDK for Spark Connect.
  • Batch jobs remain unchanged.

What changed

Resource mapping

  • get_spark_connect_driver_spec() maps Driver.resources["cpu"] to:
    • spec.server.coreRequest
    • spec.server.coreLimit
  • get_spark_connect_executor_spec() maps executor cpu to:
    • spec.executor.coreRequest
    • spec.executor.coreLimit
  • Request and limit always receive the same value.
  • Spark cores is left unset for both server and executor specs.
  • If cpu is omitted, neither coreRequest nor coreLimit is set.
  • Existing driver/executor memory defaults and executor precedence rules are preserved.

The CPU value can be a Kubernetes quantity such as "500m", "0.5", or "2". Existing SDK CPU validation remains in place.

No public API changes

The following changes from the earlier version of this PR were removed:

  • New cpu_request and cpu_limit resource keys.
  • Public API/type/docstring changes.
  • Changes to SparkClient.connect() and submit_job() signatures.
  • Changes to backend interfaces or backend resource types.
  • New resource constants and separate request/limit resolution.
  • Effective CPU request/limit validation and fallback logic.
  • The backend serialization test.
  • Vendor CRD changes.
  • Papermill parameters for separate CPU request/limit values.
  • The batch-job note about unsupported new keys.

Batch jobs

Batch-job behavior is unchanged:

  • Batch jobs continue to use cpu as Spark cores.
  • Batch jobs keep their existing memory handling and 1-core default.
  • This PR does not modify batch-job resource mapping.

Docs, examples, and tests

  • Documented the distinction between Spark cores and physical Kubernetes CPU in:
    • docs/source/spark/sessions.rst
    • docs/source/spark/index.rst
  • Added a physical CPU table and example to the session documentation.
  • Updated the advanced-options example to keep existing CPU parameters and assert that the resulting coreRequest/coreLimit values match.
  • Updated unit tests to cover:
    • Equal physical CPU request/limit mapping for server and executor.
    • Fractional CPU quantities.
    • Absence of cores in Spark Connect specs.
    • No physical CPU fields when cpu is omitted.
    • Driver/executor resource independence and existing precedence rules.

Usage

from kubeflow.spark import Driver, Executor

spark = client.connect(
    driver=Driver(
        resources={
            "cpu": "500m",
            "memory": "1Gi",
        },
    ),
    executor=Executor(
        num_instances=3,
        resources_per_executor={
            "cpu": "2",
            "memory": "4Gi",
        },
    ),
)

In this example:

  • The server pod receives a 500m CPU request and a 500m CPU limit.
  • Each executor pod receives a 2 CPU request and a 2 CPU limit.
  • Spark cores is not set by the SDK for Spark Connect.
  • Spark task slots remain at Spark's default configuration unless configured through spark_conf.

Existing usage with only cpu and memory remains valid, but cpu now controls physical Kubernetes CPU for Spark Connect rather than Spark cores. This is a behavioral change for existing Spark Connect users.

Compatibility and operational notes

  • Users need a Spark Operator release containing kubeflow/spark-operator#3096.
  • Server CPU changes take effect only after the server pod is recreated.
  • Spark Connect sessions no longer have spec.server.cores or spec.executor.cores set by the SDK.
  • Users who need Spark task-slot tuning can set Spark configuration properties through spark_conf.
  • Batch jobs retain their existing cpu-as-Spark-cores behavior.
  • The SDK does not infer a physical 1-CPU default for Spark Connect. Kubernetes and Spark defaults apply when cpu is omitted.

Validation

Local validation:

make verify
uv run pytest -q kubeflow/spark

Results:

  • make verify: passed
  • Spark unit tests: 286 passed
  • Sphinx documentation build: succeeded, with existing unrelated warnings
  • Notebook JSON validation: passed

The end-to-end validation from the earlier version of this PR used separate cpu_request/cpu_limit keys and is not applicable to the simplified mapping. E2E should be rerun after the released Spark Operator and kubeflow-spark-api versions are available.

Dependency and release readiness

The CR builders require kubeflow-spark-api fields core_request and core_limit, which are not available in the current stable PyPI release. Development currently resolves the API from the Spark Operator master revision containing #3096 through the existing [tool.uv] override-dependencies entry and uv.lock.

Before merge/release:

  • Land kubeflow/spark-operator#3226 and publish kubeflow-spark-api v2.6.0rc0.
  • Bump pyproject.toml to the released kubeflow-spark-api version and align uv.lock.
  • Pin the e2e setup to a Spark Operator release containing #3096.
  • Rerun the advanced-options notebook e2e against the released operator/API versions.
  • Trigger CI against the released dependencies.

Key files

Area Files
Resource mapping kubeflow/spark/backends/kubernetes/utils.py
Unit tests kubeflow/spark/backends/kubernetes/utils_test.py
Documentation docs/source/spark/sessions.rst, docs/source/spark/index.rst
Example examples/spark/spark_advanced_options.ipynb
Development lock uv.lock

Signed-off-by: Kartik Nath Khatri <masterbro770@gmail.com>
Signed-off-by: Kartik Nath Khatri <masterbro770@gmail.com>
Signed-off-by: Kartik Nath Khatri <masterbro770@gmail.com>
Signed-off-by: Kartik Nath Khatri <masterbro770@gmail.com>
Signed-off-by: Kartik Nath Khatri <masterbro770@gmail.com>
Signed-off-by: Kartik Nath Khatri <masterbro770@gmail.com>
@review-notebook-app

Copy link
Copy Markdown

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

@google-oss-prow

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign electronic-waste for approval. For more information see the Kubernetes Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@github-actions

Copy link
Copy Markdown
Contributor

🎉 Welcome to the Kubeflow SDK! 🎉

Thanks for opening your first PR! We're happy to have you as part of our community 🚀

Here's what happens next:

  • If you haven't already, please check out our Contributing Guide for repo-specific guidelines and the Kubeflow Contributor Guide for general community standards
  • Our team will review your PR soon! cc @kubeflow/kubeflow-sdk-team

Join the community:

Feel free to ask questions in the comments if you need any help or clarification!
Thanks again for contributing to Kubeflow! 🙏

@tariq-hasan tariq-hasan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for raising the PR @DUKartik!

Let's keep the implementation fairly simple and make CPU requests equal CPU limits - same as what is done in the Trainer SDK. A data scientist should not have to worry about requests and limits. The overall goal of this PR is primarily to fix what resource specification means and how it's mapped to the control plane. The complexity of requests vs limits falls outside the scope of use cases currently considered for Spark SDK.

On another note I will ask you to bump kubeflow-spark-api to v2.6.0rc0 in pyproject.toml once kubeflow/spark-operator#3226 is merged and v2.6.0-rc0 is cut in preparation for v2.6.0 release. We will then trigger the CI and test the flow in this PR. For now my inline comments are primarily focused on the general shape of the PR.

Comment thread kubeflow/spark/api/spark_client.py Resolved
Comment thread kubeflow/spark/types/types.py Resolved
@github-project-automation github-project-automation Bot moved this from Ready to Merge to Changes Requested in Kubeflow SDK Oct 25, 2026
@DUKartik

Copy link
Copy Markdown
Author

hi @tariq-hasan,
Thanks for the review! I just had one doubt.
In PR#3096 of sparkconnect : cores and coreRequest/coreLimit are independent. Should the SDK’s existing cpu key now control only physical CPU request/limit and leave Spark cores at Spark defaults, or should cpu also continue setting Spark task slots via cores?

@tariq-hasan

Copy link
Copy Markdown
Member

There is no need to configure Spark cores from the SDK. This was a workaround for a missing feature. Now that we have requests and limits we do not need to use this workaround any longer. So Spark cores should not be configured in the SDK.

Update resource types in SparkClient, RuntimeBackend, and KubernetesBackend to accept only string CPU values.

Simplify driver and executor resource documentation, remove deprecated CPU request and limit fields, and update tests to reflect the revised validation.

Allow any valid Kubernetes resource name in Driver and Executor resource configurations.

Signed-off-by: Kartik Nath Khatri <masterbro770@gmail.com>
@google-oss-prow google-oss-prow Bot added size/L and removed size/XL labels Oct 27, 2026
@DUKartik
DUKartik requested a review from tariq-hasan October 27, 2026 11:34
@DUKartik

Copy link
Copy Markdown
Author

Hi @tariq-hasan,
I've updated PR #896 based on your feedback and simplified the implementation.

I noticed the e2e setup still uses a vendored SparkConnect CRD that lacks coreRequest and coreLimit. Since this CRD is planned for removal in #777, would you recommend using the CRD from the Spark Operator checkout instead?

Could you please review the latest changes and share your thoughts?
Thanks!

@tariq-hasan

Copy link
Copy Markdown
Member

/ok-to-test
/retest

@tariq-hasan tariq-hasan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks @DUKartik for addressing the prior comments!

Correcting my prior comment pinning to the latest commit on the spark operator master branch is good enough to pull and test with the latest Python models.

I have added some additional review comments.

Comment thread docs/source/spark/index.rst Resolved
Comment thread docs/source/spark/sessions.rst Resolved
DUKartik and others added 3 commits November 5, 2026 22:39
Co-authored-by: Tariq Hasan <mmtariquehsn@gmail.com>
Signed-off-by: Kartik Nath Khatri <kartikkhatri8210@gmail.com>
…and remove unused CPU validation code

Signed-off-by: Kartik Nath Khatri <kartikkhatri8210@gmail.com>
…-resources

Signed-off-by: Kartik Nath Khatri <kartikkhatri8210@gmail.com>

# Conflicts:
#	kubeflow/spark/backends/kubernetes/utils.py
@DUKartik

DUKartik commented Nov 6, 2026

Copy link
Copy Markdown
Author

Hi @tariq-hasan,

I've addressed all the requested changes and updated the PR accordingly.

I'm currently waiting for Spark Operator release PR #3287 to be merged and kubeflow-spark-api v2.6.0rc0 to be published on PyPI. Once it's available, I'll update the dependency, run the final validations, and push the changes.

Thanks for your feedback and guidance!

Use the published 2.6.0rc0 models for the physical CPU request/limit fields and remove the Spark API master override so development and CI resolve the release artifact from PyPI.

Signed-off-by: Kartik Nath Khatri <kartikkhatri8210@gmail.com>
@tariq-hasan

Copy link
Copy Markdown
Member

If you include the latest commits from master once #930 merges that should help pass the tests - #930 pins the latest commit from spark operator, updates the Python models and also updates the E2E tests to more accurately reflect upstream operator behavior.

@tariq-hasan tariq-hasan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I have a few additional comments. Otherwise lgtm from me.
Thanks for working on the PR.

Comment on lines +513 to +514
core_request=quantity,
core_limit=quantity,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thinking again we should include cores as well since they determine the number of tasks that can run.

Suggested change
core_request=quantity,
core_limit=quantity,
cores=_validate_cpu_value(cpu),
core_request=quantity,
core_limit=quantity,

Comment on lines +710 to +711
core_request=cpu,
core_limit=cpu,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same here.

Suggested change
core_request=cpu,
core_limit=cpu,
cores=_validate_cpu_value(cpu),
core_request=cpu,
core_limit=cpu,

assert spark_connect.spec.spark_version == constants.DEFAULT_SPARK_VERSION
assert spark_connect.spec.executor.instances == constants.DEFAULT_NUM_EXECUTORS
assert spark_connect.spec.executor.cores == constants.DEFAULT_EXECUTOR_CPU
assert spark_connect.spec.executor.cores is None

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
assert spark_connect.spec.executor.cores is None
assert spark_connect.spec.executor.cores == constants.DEFAULT_EXECUTOR_CPU


elif test_case.name == "spark connect cr with executor resources":
assert spark_connect.spec.executor.cores == 2
assert spark_connect.spec.executor.cores is None

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
assert spark_connect.spec.executor.cores is None
assert spark_connect.spec.executor.cores == 2

assert spark_connect.spec.server.core_limit.actual_instance == "500m"

elif test_case.name == "spark connect cr with fractional executor cpu":
assert spark_connect.spec.executor.cores is None

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
assert spark_connect.spec.executor.cores is None
assert spark_connect.spec.executor.cores == 1

elif test_case.name == "kep107 level2 simple mode":
assert spark_connect.spec.executor.instances == 5
assert spark_connect.spec.executor.cores == 5
assert spark_connect.spec.executor.cores is None

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
assert spark_connect.spec.executor.cores is None
assert spark_connect.spec.executor.cores == 5

assert spark_connect.spec.server.template.spec.service_account_name == "spark-driver-prod"
assert spark_connect.spec.executor.instances == 20
assert spark_connect.spec.executor.cores == 8
assert spark_connect.spec.executor.cores is None

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
assert spark_connect.spec.executor.cores is None
assert spark_connect.spec.executor.cores == 8


assert spec.instances == constants.DEFAULT_NUM_EXECUTORS
assert spec.cores == constants.DEFAULT_EXECUTOR_CPU
assert spec.cores is None

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
assert spec.cores is None
assert spec.cores == constants.DEFAULT_EXECUTOR_CPU

elif test_case.name == "file job with executor configuration":
assert app.spec.executor.instances == 3
assert app.spec.executor.cores == 2
assert app.spec.executor.cores is None

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
assert app.spec.executor.cores is None
assert app.spec.executor.cores == 2

elif test_case.name == "function job with executor configuration":
assert app.spec.executor.instances == 3
assert app.spec.executor.cores == 2
assert app.spec.executor.cores is None

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
assert app.spec.executor.cores is None
assert app.spec.executor.cores == 2

This branch has not been deployed

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

Projects

Status: Changes Requested

Development

Successfully merging this pull request may close these issues.

Track SparkConnect CPU resource support after SparkConnect CRD enhancements

2 participants