Skip to content

Give a replica's remote objects names Kubernetes accepts - #468

Open
negz wants to merge 1 commit into
modelplaneai:mainfrom
negz:long-story-short
Open

negz wants to merge 1 commit into
modelplaneai:mainfrom
negz:long-story-short

Conversation

@negz

@negz negz commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator

Description of your changes

Broken by #99, which named the LeaderWorkerSet from the replica, and #142, which named the endpoint picker's Service {name}-epp (#342 then gave every replica one).

A ModelReplica's name comes from its ModelDeployment's, so it may contain dots or start with a digit, and runs to 63 characters. Several names built from it broke a rule for their kind:

  • The endpoint picker's Service needs a DNS-1035 label for a name. Its pods' app label, also {name}-epp, can have at most 63 characters. A qwen2.5-0.5b deployment couldn't create its picker Service:
    Service "qwen2.5-0.5b-ee8ab-epp" is invalid: metadata.name: Invalid value: "qwen2.5-0.5b-ee8ab-epp": must not contain dots
    
  • LWS rejects a LeaderWorkerSet name that isn't a DNS-1035 label, since it names the set's headless Service, and a StatefulSet named after it can't create pods once its name passes 52 characters (StatefulSet with long name can not create pods kubernetes/kubernetes#64023).
  • Grove names a gang's headless Service after its PodCliqueSet, which kept the replica's dots.
  • The ModelReplica's own name could end its truncated prefix in a . next to the - before its hash.

An invalid name is now replaced by one hashed behind a prefix, as compose-inference-cluster derives its gateway's Service name. A LeaderWorkerSet's name stays short enough that its last group's worker StatefulSet fits in 52 characters.

Every name that already worked stays the same, since provider-kubernetes finds an object only by its name and would leave a renamed one's predecessor behind. A rename does leave behind the old object under a name that never worked. That object can be a LeaderWorkerSet whose worker StatefulSets couldn't create pods, so its leaders may still hold GPUs. Delete that one by hand. It can also be a dotted PodCliqueSet whose headless Service failed, or the old picker Service of a replica whose name starts with a digit on Kubernetes 1.36 or later, which accepts that name. A Grove gang keeps a leading digit, since it may be running on 1.36, so on older clusters that case still fails.

On EKS, a qwen2.5-0.5b deployment and one with a 56-character name both served through the gateway with an earlier version of this PR, whose picker naming matches this one. Only unit tests cover the LeaderWorkerSet, Grove and ModelReplica name changes.

I have:

  • Read and followed Modelplane's contribution process.
  • Run nix flake check (or ./nix.sh flake check) and made sure it passes.
  • Added or updated tests covering any composition function changes.
  • Signed off every commit with git commit -s.

Copilot AI balanced review requested due to automatic review settings September 26, 2026 01:07

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings September 26, 2026 02:03

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@negz negz changed the title Name a replica's picker Service and LeaderWorkerSet validly for any name Give a replica's remote objects names Kubernetes accepts Sep 26, 2026
Copilot AI review requested due to automatic review settings September 26, 2026 02:25

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

name = base.engine_name(replica, engine)
# The last group's worker StatefulSet has the longest name.
name = base.dns_1035_label(
base.engine_name(replica, engine), "lws", _STATEFULSET_NAME_MAX - len(f"-{int(engine.copies or 1) - 1}")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This makes the LWS name depend on copies, which is the LWS spec.replicas and something folks scale.

A 50-char engine name keeps its name at 1–10 copies but becomes lws-…-ad679 at 11 (test_name pins this). provider-kubernetes would then leaves the old LWS behind, still holding GPUs, and the scale-up becomes a recreate?

Could we budget for a fixed max group index instead (e.g. -999)? That renames todays 49–50-char names once, but after that the name never moves on scale.

# 50 characters, and {lws}-0 is 52.
("a" * 39, 1, "a" * 39 + "-main-f8695"),
# The same 50 characters, but {lws}-10 would be 53.
("a" * 39, 11, "lws-" + "a" * 39 + "-ad679"),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Related to the comment on llmd.py:77

this case and the one above it are the same replica getting two different LWS names depending only on copies. If that stays intentional, it might be worth a comment here saying that it renames on scale?

a name LWS accepts; a Grove gang uses grove_pcs_name instead, which budgets
for Grove's own name-length validation.
"""
return resource.child_name(_name(replica.metadata), engine.name)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

child_name only does rstrip("-"), so the trailing-. problem fixed in opaque_name can still happen here. child_name("a"*56 + ".b-12345", "main") gives aaaa….-685b0, which is not a valid subdomain ?

llm-d is now covered because dns_1035_label replaces the dot, but the native Deployment still uses this name as is?

return name
full = f"{prefix}-{name}"
h = hashlib.sha256(full.encode()).hexdigest()[:5]
return f"{full[: max_length - len(h) - 1].rstrip('-')}-{h}".replace(".", "-")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit: if the truncation lands on a ., it survives the rstrip('-') and the replace turns it into foo--abcde. That is still valid, just untidy; rstrip("-.") avoids it.

@negz
negz force-pushed the long-story-short branch from 2c37243 to 289ac58 Compare October 15, 2026 04:20
Copilot AI balanced review requested due to automatic review settings October 15, 2026 04:20

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

A ModelReplica's name comes from its ModelDeployment's, so it may contain
dots or start with a digit, and runs to 63 characters. It and several names
built from it could break a rule for their kind:

- The endpoint picker's Service, {name}-epp, must be a DNS-1035 label, and
  its pods' app label, the same value, at most 63 characters.
- LWS rejects a LeaderWorkerSet name that isn't a DNS-1035 label, since it
  names the set's headless Service. Kubernetes can't create pods for a
  StatefulSet named after it once that name passes 52 characters.
- Grove names a gang's headless Service after its PodCliqueSet, which kept
  the replica's dots.
- The ModelReplica's own name could end its truncated prefix in a '.' next
  to the '-' before its hash.

This derives a valid name wherever the one it would get isn't, and leaves
every name that worked alone, since provider-kubernetes finds an object only
by its name and would leave a renamed one's predecessor behind. There are
two exceptions. A picker Service starting with a digit worked from
Kubernetes 1.36, so there a harmless duplicate Service is left behind. A
LeaderWorkerSet's name budgets for the 64 copies an engine can have, so
scaling an engine never renames it. That renames one whose name is exactly
50 characters, which worked with up to ten copies, and leaves its
predecessor behind holding GPUs.

A Grove gang keeps a leading digit, since it may be running on 1.36, so on
older clusters that name still fails.

Signed-off-by: Nic Cope <nicc@rk0n.org>
@negz
negz force-pushed the long-story-short branch from 289ac58 to e1c78e6 Compare October 15, 2026 17:51
Copilot AI balanced review requested due to automatic review settings October 15, 2026 17:51

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants