Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
a527b6b
Studio: install flashinfer on demand for NVFP4 loads
danielhanchen Sep 23, 2026
8bff995
Studio: no flashinfer install on local-only loads; keep the NVFP4 fal…
danielhanchen Sep 23, 2026
89db625
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 23, 2026
a013923
Studio: bind the NVFP4 fallback reason to the loaded device, and reco…
danielhanchen Sep 23, 2026
edc1b41
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 23, 2026
804edab
Studio: install flashinfer only for model kinds the NVFP4 dense path …
danielhanchen Sep 23, 2026
b301c4a
Studio: honour UV_OFFLINE before the flashinfer install probes an index
danielhanchen Sep 24, 2026
4101a0c
Studio: serialise the flashinfer install across processes sharing one…
danielhanchen Sep 24, 2026
aae80d4
Studio: hand a pip-only index mirror to uv for the flashinfer install
danielhanchen Sep 24, 2026
915d0f7
Studio: one index per uv flashinfer step, and report a transient pref…
danielhanchen Sep 24, 2026
6af4e68
Studio: hand a uv-only index to the pip fallback of the flashinfer in…
danielhanchen Sep 24, 2026
90bc915
Studio: wait for an in-flight flashinfer install and isolate the jit-…
danielhanchen Sep 24, 2026
d013b50
Studio: do not import flashinfer after a timed-out install wait, and …
danielhanchen Sep 24, 2026
766a216
Studio: honor pip and uv configuration files in the flashinfer instal…
danielhanchen Sep 24, 2026
dd105df
Studio: wait for the install lock when both flashinfer distributions …
danielhanchen Sep 24, 2026
838bfc0
Studio: count pip extra indexes and find-links as a mirror in the fla…
danielhanchen Sep 24, 2026
1f0fb82
Merge remote-tracking branch 'origin/studio-nvfp4-kernels' into studi…
danielhanchen Sep 24, 2026
256cf94
Bind the video NVFP4 install reason only when the load commits
danielhanchen Sep 24, 2026
5221079
Studio: install FlashInfer only when a hosted NVFP4 checkpoint will load
danielhanchen Sep 24, 2026
46cf5af
Merge remote-tracking branch 'origin/studio-nvfp4-kernels' into studi…
danielhanchen Sep 24, 2026
28e8255
studio: count UV_FIND_LINKS as a mirror, refuse reinstall settings, r…
danielhanchen Sep 24, 2026
bf80962
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 24, 2026
1a14dbd
Merge remote-tracking branch 'origin/studio-nvfp4-kernels' into studi…
danielhanchen Sep 24, 2026
0caa7cd
Merge branch 'studio-nvfp4-kernels' into studio-nvfp4-flashinfer-auto…
danielhanchen Sep 24, 2026
5b24f5b
Studio: no flashinfer install under the NVFP4 kill switch
danielhanchen Sep 24, 2026
5b6b074
Merge branch 'studio-nvfp4-kernels' into studio-nvfp4-flashinfer-auto…
danielhanchen Sep 24, 2026
4c4def9
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 24, 2026
0932f7d
Studio: keep pip.conf extra sources out of the pinned jit-cache install
danielhanchen Sep 24, 2026
be0b4ab
Studio: do not wait on another FlashInfer install for a load that dec…
danielhanchen Sep 24, 2026
6e0663c
Studio: install FlashInfer only for an NVFP4 seed the live-memory pla…
danielhanchen Sep 24, 2026
30b251a
Merge branch 'studio-nvfp4-kernels' into studio-nvfp4-flashinfer-auto…
danielhanchen Sep 24, 2026
dd7ec47
Merge remote-tracking branch 'origin/studio-nvfp4-flashinfer-autoinst…
danielhanchen Sep 24, 2026
3c6db72
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 24, 2026
2dc7869
Roll back only the packages the FlashInfer install itself added
danielhanchen Sep 24, 2026
ca8ec12
Merge remote-tracking branch 'origin/studio-nvfp4-kernels' into studi…
danielhanchen Sep 24, 2026
227cc8f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 24, 2026
1e0a63c
Merge remote-tracking branch 'origin/studio-nvfp4-kernels' into studi…
danielhanchen Sep 24, 2026
b5a327b
Merge remote-tracking branch 'origin/studio-nvfp4-kernels' into studi…
danielhanchen Sep 25, 2026
10ec2ad
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 25, 2026
cd031eb
Merge remote-tracking branch 'origin/studio-nvfp4-kernels' into studi…
danielhanchen Sep 25, 2026
4f10020
Merge remote-tracking branch 'origin/studio-nvfp4-kernels' into studi…
danielhanchen Sep 25, 2026
1c0598f
Studio: finish an interrupted flashinfer install, scope its rollback …
danielhanchen Sep 25, 2026
6564efc
Merge remote-tracking branch 'origin/studio-nvfp4-kernels' into studi…
danielhanchen Sep 25, 2026
cfba066
Studio: redact credentials from quoted flashinfer installer output
danielhanchen Sep 25, 2026
7b56e24
Merge remote-tracking branch 'origin/studio-nvfp4-kernels' into studi…
danielhanchen Sep 25, 2026
d452adf
Studio: keep a concurrent upgrade when an unreported flashinfer step …
danielhanchen Sep 25, 2026
2500bf8
Merge remote-tracking branch 'origin/studio-nvfp4-kernels' into studi…
danielhanchen Sep 25, 2026
5e12a53
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 25, 2026
6074d16
Merge remote-tracking branch 'origin/studio-nvfp4-kernels' into studi…
danielhanchen Sep 25, 2026
87abedd
Merge remote-tracking branch 'origin/studio-nvfp4-kernels' into studi…
danielhanchen Sep 25, 2026
0f24d52
Merge remote-tracking branch 'origin/studio-nvfp4-kernels' into studi…
danielhanchen Sep 25, 2026
e6acf2f
Studio: keep the flashinfer install module on the 3.10 floor and orde…
danielhanchen Sep 25, 2026
777e7b7
Studio: trim comments in the flashinfer autoinstall layer
danielhanchen Sep 25, 2026
796be23
Merge branch 'studio-nvfp4-kernels' of https://github.com/unslothai/u…
danielhanchen Sep 25, 2026
eac82e1
Studio: no flashinfer install for an image load whose plan declined t…
danielhanchen Sep 25, 2026
3519005
Merge remote-tracking branch 'origin/studio-nvfp4-flashinfer-autoinst…
danielhanchen Sep 25, 2026
8015b7b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 25, 2026
d3092ac
Merge branch 'studio-nvfp4-kernels' of https://github.com/unslothai/u…
danielhanchen Sep 25, 2026
41b9935
Merge branch 'studio-nvfp4-flashinfer-autoinstall' of https://github.…
danielhanchen Sep 25, 2026
75c2299
Merge branch 'studio-nvfp4-kernels' of https://github.com/unslothai/u…
danielhanchen Sep 25, 2026
9a456c7
Merge remote-tracking branch 'origin/studio-nvfp4-kernels' into studi…
danielhanchen Sep 25, 2026
2254718
Merge branch 'studio-nvfp4-kernels' into studio-nvfp4-flashinfer-auto…
danielhanchen Sep 25, 2026
bd9f1cf
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 159 additions & 24 deletions studio/backend/core/inference/diffusion.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@
resident_bytes_from_declared,
resolve_dense_quant_candidate,
)
from .diffusion_nvfp4_flag import nvfp4_diffusion_enabled
from .diffusion_nvfp4_install import nvfp4_backend_fields as _nvfp4_backend_fields
from .diffusion_transformer_quant import (
TQ_AUTO,
TQ_NVFP4,
Expand Down Expand Up @@ -3329,6 +3331,43 @@ def _prequant_source_hub_entry(
)
return None

def _nvfp4_checkpoint_will_load(
self,
fam: Any,
base: Optional[str],
path_override: Optional[str],
hf_token: Optional[str],
*,
local_files_only: bool = False,
loras: Any = None,
) -> bool:
"""Whether an NVFP4 load opens a pre-quantised checkpoint (the only FlashInfer path); unanswerable -> False."""
if not nvfp4_diffusion_enabled():
return False
try:
if _has_active_lora(loras):
return False
source = usable_prequant_source(
fam, TQ_NVFP4, path_override = path_override, base_repo = base
)
if source is None:
return False
if getattr(source, "kind", None) != "repo":
return True
if prequant_checkpoint_cached(source, cache_dir = hub_cache_dir()):
return True
if local_files_only:
return False
return self._prequant_source_hub_entry(source, hf_token, scheme = TQ_NVFP4) is not None
except Exception as exc: # noqa: BLE001 -- a refused or unreachable listing is no checkpoint
logger.info(
"diffusion.nvfp4_install: no reachable NVFP4 checkpoint for %s, so the load runs on "
"torchao and FlashInfer is not installed (%s)",
base,
type(exc).__name__,
)
return False

@staticmethod
def _estimate_download_bytes(
repo_id: str,
Expand Down Expand Up @@ -4409,6 +4448,51 @@ def load_pipeline(
_ensure_attention_backend_installed(preinstall_backend, logger)
except Exception: # noqa: BLE001 - the locked path re-resolves and validates
pass
# Install FlashInfer only when a pre-quantised checkpoint will load: the on-the-fly build is torchao.
if (
dense_quant_supported_kind(kind)
and nvfp4_diffusion_enabled()
and TQ_NVFP4
in (
normalize_transformer_quant(transformer_quant),
_pipeline_prequant_planned,
)
# A declined seed loads the released denoiser (torchao or no quant), so no checkpoint for FlashInfer.
and _pipeline_prequant_planned != PIPELINE_SEED_DECLINED
and (
_pipeline_prequant_planned == TQ_NVFP4
or self._nvfp4_checkpoint_will_load(
Comment on lines +4462 to +4464

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Skip installation when the image seed was declined

When an explicit image transformer_quant="nvfp4" has _pipeline_prequant_planned == PIPELINE_SEED_DECLINED (for example, the artifact-sized prefetch plan still requires offload), the explicit request satisfies the earlier tuple check and this Hub probe can return true, so FlashInfer and its roughly 1.5 GB cache are installed. The loader later explicitly excludes PIPELINE_SEED_DECLINED from pipeline_seed_scheme at lines 4575-4578 and either performs the on-the-fly torchao conversion or declines quantization under offload, meaning FlashInfer cannot serve this load. Short-circuit this sentinel as the video gate already does before invoking the installer.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Correct: a declined seed loads the released denoiser, so the install bought nothing. Fixed in eac82e1, which skips the install when the plan declined the seed, with a regression test.

fam,
base,
transformer_prequant_path,
hf_token,
local_files_only = local_files_only,
loras = loras,
)
)
# The locked re-plan (after teardown) may drop the capacity-settled seed; ask it now with teardown memory credited.
and not (
kind == "pipeline"
and _pipeline_prequant_planned == TQ_NVFP4
and not self._seed_plan_stays_resident(
TQ_NVFP4,
target,
base,
fam,
memory_mode,
cpu_offload,
repo_id = repo_id,
base_local_dir = _base_local_dir,
fetch_base = fetch_base,
)
)
):
from .diffusion_nvfp4_install import ensure_flashinfer_for_nvfp4
_nvfp4_install_outcome = ensure_flashinfer_for_nvfp4(
device, logger = logger, local_files_only = local_files_only
)
Comment on lines +4491 to +4493

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Cancel the installer when its model load is superseded

When an image NVFP4 load is superseded or unloaded while this call is downloading the roughly 1.5 GB JIT cache, cancellation is not checked until ensure_flashinfer_for_nvfp4 returns, and the installer receives no cancellation event. The obsolete load therefore continues for up to 30 minutes and mutates the shared environment even while a newer load proceeds; the video path at video.py:4417 has the same behavior. Make the install subprocess cancellable or poll the load token during installation.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Leaving this as is. The install runs once per process under the install lock, rolls back on failure, and only adds the package the next NVFP4 load wants anyway, so a superseded load finishing it does no harm; the newer load's own ensure call sees the finished outcome. Killing pip mid-install would be the riskier path for the environment.

else:
_nvfp4_install_outcome = None

with self._lock:
self._raise_if_load_cancelled(_load_token)
Expand All @@ -4424,6 +4508,10 @@ def load_pipeline(
self._unload_locked()
finally:
self._release_teardown_locked()
# Bind even when the gate skipped, so no stale install reason survives.
from .diffusion_nvfp4_install import record_install_reason

record_install_reason(self, *(_nvfp4_install_outcome or (True, None)), device)

# Single-file kinds resolve a checkpoint path; the pipeline kind has none.
single_file_path = (
Expand Down Expand Up @@ -4462,29 +4550,16 @@ def load_pipeline(
PIPELINE_SEED_DECLINED,
):
pipeline_seed_scheme = _pipeline_prequant_planned
seed_estimate = estimate_dense_quant(
fam, pipeline_seed_scheme, base_repo = base, prequant_available = True
)
seeded_plan = (
self._plan_memory(
target,
single_file_path,
base,
fam,
memory_mode,
cpu_offload,
kind = kind,
repo_id = repo_id,
base_local_dir = _base_local_dir,
fetch_base = fetch_base,
transformer_resident_override_mib = (
seed_estimate.steady_transformer_mib
),
companion_override_mib = seed_estimate.companions_mib,
text_encoder_override_mib = seed_estimate.text_encoders_mib,
)
if seed_estimate is not None
else None
seeded_plan = self._seeded_pipeline_plan(
pipeline_seed_scheme,
target,
base,
fam,
memory_mode,
cpu_offload,
repo_id = repo_id,
base_local_dir = _base_local_dir,
fetch_base = fetch_base,
)
if seeded_plan is None or seeded_plan.offload_policy != OFFLOAD_NONE:
# Offload hooks use Module.to(), which torchao tensors reject, and live free
Expand Down Expand Up @@ -6365,6 +6440,65 @@ def _precast_scaled_companions_mib(
except Exception: # noqa: BLE001 -- sizing aid only; the dense total still refuses safely
return int(companions)

def _seeded_pipeline_plan(
self,
scheme: str,
target: Any,
base: str,
fam: Any,
memory_mode: Optional[str],
cpu_offload: bool,
*,
repo_id: str,
base_local_dir: Optional[str],
fetch_base: Optional[str],
device_memory_override: Optional[DeviceMemory] = None,
):
"""Full-pipeline plan priced on the pre-quantised ``scheme`` seed, or None when the family table cannot size it."""
seed_estimate = estimate_dense_quant(fam, scheme, base_repo = base, prequant_available = True)
if seed_estimate is None:
return None
return self._plan_memory(
target,
None,
base,
fam,
memory_mode,
cpu_offload,
kind = "pipeline",
repo_id = repo_id,
base_local_dir = base_local_dir,
fetch_base = fetch_base,
transformer_resident_override_mib = seed_estimate.steady_transformer_mib,
companion_override_mib = seed_estimate.companions_mib,
text_encoder_override_mib = seed_estimate.text_encoders_mib,
device_memory_override = device_memory_override,
)

def _seed_plan_stays_resident(
self, scheme: str, target: Any, *args: Any, **kwargs: Any
) -> bool:
"""Whether the post-teardown seeded plan keeps ``scheme``, asked before teardown with this process's allocation
credited as free (errs toward keeping the seed). Unanswerable keeps it."""
try:
memory = snapshot_device_memory(target)
if getattr(target, "device", None) == "cuda" and memory.free_mib is not None:
import torch

free = int(memory.free_mib) + int(torch.cuda.memory_reserved()) // (1024 * 1024)
if memory.total_mib is not None:
free = min(free, int(memory.total_mib))
memory = DeviceMemory(
memory.backend, memory.device, memory.memory_kind, free, memory.total_mib
)
plan = self._seeded_pipeline_plan(
scheme, target, *args, device_memory_override = memory, **kwargs
)
except Exception as exc: # noqa: BLE001 - a gate on an optional install must not fail the load
logger.debug("diffusion.nvfp4_install: seed plan preview skipped: %r", exc)
return True
return plan is not None and plan.offload_policy == OFFLOAD_NONE

def _resident_sized_plan(
self,
plan: Any,
Expand Down Expand Up @@ -7974,6 +8108,7 @@ def status(self) -> dict[str, Any]:
"text_encoder_quant": None,
"transformer_quant": None,
"transformer_quant_backend": None,
"transformer_quant_backend_reason": None,
"attention_backend": None,
"transformer_cache": None,
"workflows": [],
Expand Down Expand Up @@ -8007,7 +8142,7 @@ def status(self) -> dict[str, Any]:
"speed_optims": list(state.speed_optims),
"text_encoder_quant": state.text_encoder_quant,
"transformer_quant": state.transformer_quant,
"transformer_quant_backend": _transformer_quant_backend(state),
**_nvfp4_backend_fields(_transformer_quant_backend(state), owner = self),
"attention_backend": state.attention_backend,
"transformer_cache": state.transformer_cache,
"resolved": state.resolved,
Expand Down
7 changes: 7 additions & 0 deletions studio/backend/core/inference/diffusion_nvfp4_dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,13 @@ def _build_plan(key: tuple, device: Any, operands: list):
return _GEMM_PLAN.setdefault(key, plan)


def forget_availability() -> None:
"""Re-probe on the next ``available()``."""
global _AVAILABLE
with _LOCK:
_AVAILABLE = None


def reset() -> None:
"""Called on unload: the transposed views would pin a freed model."""
global _AVAILABLE
Expand Down
Loading