runpod: send the HuggingFace token to a pod, as environment and nothing else

pyannote is gated. On a pod it loaded from its baked venv, reached
from_pretrained, and stopped: 'the model is gated/unavailable'. The local
install has HF_TOKEN in its environment; a pod was never sent one — every gated
model was unservable remotely, not just this one.

It is resolved the way pyannote_worker already resolves it (request, env, token
file) and travels as pod ENVIRONMENT only. A test proves it reaches the pod and
appears nowhere else in the plan — not the seed list the pod parses, not the
catalogue, not the args.

Fixing this exposed a precedence bug: at provision time the plan's env was
applied OVER the model's own env, so a token a model was explicitly configured
with was silently replaced by whatever this process happened to have. The
model's own env wins now.
Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0182DhR1eFNPQyGmDrpHbedY
parent ec7939d2
...@@ -739,6 +739,20 @@ def _plan(engine, image, args, mcfg, api_key, entry, served, ...@@ -739,6 +739,20 @@ def _plan(engine, image, args, mcfg, api_key, entry, served,
choice = str((entry or {}).get("audio_backend") or "").strip().lower() choice = str((entry or {}).get("audio_backend") or "").strip().lower()
if choice in ("audiocraft", "transformers"): if choice in ("audiocraft", "transformers"):
env["CODERAI_AUDIO_BACKEND"] = choice env["CODERAI_AUDIO_BACKEND"] = choice
# A HuggingFace token, for gated models. The local install has one in its
# environment; a pod has nothing unless it is sent — so pyannote, which is
# gated, loaded from its venv, reached from_pretrained, and stopped there
# with "the model is gated/unavailable". Sent as pod ENVIRONMENT only: it is
# a secret, and never belongs in an image, a log, or the catalogue.
if "HF_TOKEN" not in env:
try:
from codai.api.pyannote_worker import _resolve_hf_token
_tok = _resolve_hf_token(entry or {})
except Exception:
_tok = ""
if _tok:
env["HF_TOKEN"] = _tok
# Subsystems whose image bakes a venv: tell the worker where it is, or it # Subsystems whose image bakes a venv: tell the worker where it is, or it
# tries to build one on a machine rented by the second. Harmless on an image # tries to build one on a machine rented by the second. Harmless on an image
# that has no such venv — the worker falls back to its own search. # that has no such venv — the worker falls back to its own search.
...@@ -1492,8 +1506,12 @@ class RunpodPodPool: ...@@ -1492,8 +1506,12 @@ class RunpodPodPool:
entry=self.entry, seed_entries=self.seed_entries) entry=self.entry, seed_entries=self.seed_entries)
image, args = plan["image"], plan["args"] image, args = plan["image"], plan["args"]
self.health_path = plan["health_path"] self.health_path = plan["health_path"]
env = dict(self.mcfg.env or {}) # The plan's env, then the model's OWN env on top: an explicit
env.update(plan.get("env") or {}) # per-model value must beat one the plan resolved from the ambient
# environment. The other order silently replaced a model's configured
# HF_TOKEN with whatever this process happened to have.
env = dict(plan.get("env") or {})
env.update(self.mcfg.env or {})
# A network volume, when configured: caches and upload targets move onto # A network volume, when configured: caches and upload targets move onto
# it so weights survive the pod. RunPod requires Secure Cloud and the # it so weights survive the pod. RunPod requires Secure Cloud and the
......
...@@ -764,3 +764,36 @@ def test_the_weight_estimate_counts_what_a_load_fetches_not_the_whole_repo(monke ...@@ -764,3 +764,36 @@ def test_the_weight_estimate_counts_what_a_load_fetches_not_the_whole_repo(monke
monkeypatch.setattr("huggingface_hub.model_info", _fake_info) monkeypatch.setattr("huggingface_hub.model_info", _fake_info)
gb = rw.estimate_weights_gb({"path": "org/sdxl"}, rw.parse_model_runpod({"engine": "coderai"})) gb = rw.estimate_weights_gb({"path": "org/sdxl"}, rw.parse_model_runpod({"engine": "coderai"}))
assert abs(gb - 10.0) < 0.01, f"counted dupes: {gb} GB" assert abs(gb - 10.0) < 0.01, f"counted dupes: {gb} GB"
def test_a_gated_model_pod_gets_the_hf_token_and_nothing_else_does(monkeypatch):
"""pyannote is gated. The local install has HF_TOKEN in its environment; a
pod had nothing, so it loaded from its venv, reached from_pretrained and
stopped at 'the model is gated'. The token is a secret: it travels as pod
ENVIRONMENT and must never land in the seed list or the catalogue."""
import json
from codai.api.runpod_worker import pod_plan, parse_model_runpod
monkeypatch.setenv("HF_TOKEN", "hf_secret_for_test")
mcfg = parse_model_runpod({"engine": "coderai", "image": "x:1"})
entry = {"path": "pyannote/speaker-diarization-3.1", "model_type": "audio_models"}
plan = pod_plan(mcfg, "speaker", entry=entry)
assert plan["env"]["HF_TOKEN"] == "hf_secret_for_test"
# The seed list is JSON the pod parses; it must carry the model, not the secret.
seeds = json.loads(plan["env"].get("CODERAI_SEED_MODELS", "[]"))
assert "hf_secret_for_test" not in json.dumps(seeds)
# And nothing else in the plan carries it either.
for key, val in plan.items():
if key != "env":
assert "hf_secret_for_test" not in json.dumps(val, default=str), key
# An explicit per-model token beats the ambient one. The merge happens at
# provision time, and it used to run the other way round — the plan's
# resolved token silently replaced the one a model was configured with.
monkeypatch.setenv("HF_TOKEN", "hf_ambient")
mcfg2 = parse_model_runpod({"engine": "coderai", "image": "x:1",
"env": {"HF_TOKEN": "hf_explicit"}})
plan2 = pod_plan(mcfg2, "speaker", entry=entry)
merged = dict(plan2["env"]); merged.update(mcfg2.env or {})
assert merged["HF_TOKEN"] == "hf_explicit"
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment