Skip to main content

rusty_stack/installers/components/
fastvideo.rs

1//! FastVideo installer — native Rust build for ROCm gfx11 support.
2//!
3//! Clones scooter-lacroix/FastVideo and checks out an audited immutable commit,
4//! then builds both Python distributions against Rusty's managed ROCm environment.
5//!
6//! Instead of delegating to `./build.sh --rocm` (which calls `uv pip install`
7//! without `--system` and fails outside a venv), this module replicates the
8//! build steps individually with proper env var injection and pip prefix
9//! handling consistent with the rest of the rusty-stack installer ecosystem.
10//!
11//! # Validation Assertion
12//!
13//! - **VAL-INSTALL-050**: FastVideo builds from fork with ROCm gfx11 support
14
15use std::path::{Path, PathBuf};
16
17const FASTVIDEO_REPO: &str = "https://github.com/scooter-lacroix/FastVideo.git";
18pub const FASTVIDEO_COMMIT: &str = "22e448771ebf5c81108f1c57b9c7ef4d5c26d182";
19const FASTVIDEO_BUILD_DIR: &str = "/tmp/FastVideo_ROCm_build";
20
21/// Leave one CPU free and never run more than four compile jobs.
22pub fn max_jobs_for_parallelism(nproc: usize) -> usize {
23    nproc.saturating_sub(1).clamp(1, 4)
24}
25
26fn is_valid_gpu_arch(arch: &str) -> bool {
27    arch.strip_prefix("gfx")
28        .is_some_and(|suffix| !suffix.is_empty() && suffix.chars().all(|ch| ch.is_ascii_hexdigit()))
29}
30
31fn push_normalized_archs(archs: &mut Vec<String>, value: &str) {
32    for arch in value
33        .split(|ch: char| ch == ';' || ch == ',' || ch.is_ascii_whitespace())
34        .map(str::trim)
35        .filter(|arch| is_valid_gpu_arch(arch))
36    {
37        if !archs.iter().any(|existing| existing == arch) {
38            archs.push(arch.to_string());
39        }
40    }
41}
42
43/// Apply the first configured visibility mask to enumerated discrete GPUs.
44pub fn select_visible_gpu_archs(
45    enumerated_archs: &[String],
46    rocr_visible_devices: Option<&str>,
47    hip_visible_devices: Option<&str>,
48    cuda_visible_devices: Option<&str>,
49) -> Vec<String> {
50    let mask = [
51        rocr_visible_devices,
52        hip_visible_devices,
53        cuda_visible_devices,
54    ]
55    .into_iter()
56    .flatten()
57    .map(str::trim)
58    .find(|value| !value.is_empty());
59
60    let mut selected = Vec::new();
61    if let Some(mask) = mask {
62        for index in mask
63            .split(|ch: char| ch == ',' || ch.is_ascii_whitespace())
64            .filter_map(|value| value.trim().parse::<usize>().ok())
65        {
66            if let Some(arch) = enumerated_archs.get(index) {
67                push_normalized_archs(&mut selected, arch);
68            }
69        }
70    } else {
71        for arch in enumerated_archs {
72            push_normalized_archs(&mut selected, arch);
73        }
74    }
75    selected
76}
77
78fn discrete_pci_id_to_gfx(device_id: &str) -> Option<&'static str> {
79    match device_id
80        .trim()
81        .trim_start_matches("0x")
82        .to_ascii_lowercase()
83        .as_str()
84    {
85        "7550" | "7551" => Some("gfx1201"),
86        "7590" => Some("gfx1200"),
87        "744c" | "7448" | "7449" | "744a" | "744b" | "745e" => Some("gfx1100"),
88        "747e" | "7470" | "7460" | "7461" => Some("gfx1101"),
89        "7480" | "7483" | "7489" | "749f" | "73f0" => Some("gfx1102"),
90        "73bf" | "73af" | "73a5" | "73a1" | "73a2" | "73a3" => Some("gfx1030"),
91        "73df" | "73c3" => Some("gfx1031"),
92        "73ff" | "73ef" | "73e0" | "73e1" | "73e3" => Some("gfx1032"),
93        "743f" | "7424" | "7421" | "7422" | "7423" => Some("gfx1034"),
94        _ => None,
95    }
96}
97
98/// Discover known discrete AMD GPUs directly from DRM sysfs in stable card order.
99///
100/// Matching known discrete PCI IDs excludes APUs without relying on the global
101/// HSA override, which can otherwise collapse heterogeneous GPUs to one arch.
102pub fn detect_discrete_gpu_archs_from_sysfs(drm_root: &Path) -> Vec<String> {
103    let mut cards = match std::fs::read_dir(drm_root) {
104        Ok(entries) => entries
105            .filter_map(Result::ok)
106            .filter_map(|entry| {
107                let name = entry.file_name().to_string_lossy().into_owned();
108                let index = name.strip_prefix("card")?.parse::<usize>().ok()?;
109                Some((index, entry.path()))
110            })
111            .collect::<Vec<_>>(),
112        Err(_) => return Vec::new(),
113    };
114    cards.sort_by_key(|(index, _)| *index);
115
116    cards
117        .into_iter()
118        .filter_map(|(_, card)| {
119            let device = card.join("device");
120            let vendor = std::fs::read_to_string(device.join("vendor")).ok()?;
121            if vendor.trim() != "0x1002" {
122                return None;
123            }
124            let device_id = std::fs::read_to_string(device.join("device")).ok()?;
125            discrete_pci_id_to_gfx(&device_id).map(str::to_string)
126        })
127        .collect()
128}
129
130/// Resolve a canonical, ordered, semicolon-delimited architecture list.
131pub fn resolve_gpu_archs(
132    sysfs_archs: &[String],
133    gpu_archs_env: Option<&str>,
134    gpu_arch_env: Option<&str>,
135    detected_primary: Option<&str>,
136) -> String {
137    let mut archs = Vec::new();
138    for value in sysfs_archs {
139        push_normalized_archs(&mut archs, value);
140    }
141    if !archs.is_empty() {
142        return archs.join(";");
143    }
144
145    for source in [gpu_archs_env, gpu_arch_env, detected_primary]
146        .into_iter()
147        .flatten()
148    {
149        archs.clear();
150        push_normalized_archs(&mut archs, source);
151        if !archs.is_empty() {
152            return archs.join(";");
153        }
154    }
155
156    "gfx1100".to_string()
157}
158
159/// Select the effective non-root install user.
160pub fn resolve_install_user(sudo_user: Option<&str>, user: Option<&str>) -> Option<String> {
161    [sudo_user, user]
162        .into_iter()
163        .flatten()
164        .map(str::trim)
165        .find(|value| !value.is_empty())
166        .map(str::to_string)
167}
168
169/// Explain the bounded cost before kernel compilation begins.
170pub fn fastvideo_build_warning(gpu_archs: &str) -> String {
171    format!("[FastVideo] Heavy dual-arch HIP compile for {gpu_archs}; MAX_JOBS capped at 4")
172}
173
174/// Functional FastVideo verification run through the selected Python interpreter.
175///
176/// Each physical ROCr visibility token gets its own child process. Removing the
177/// inherited HIP/CUDA masks prevents them from filtering the child's renumbered
178/// logical device 0.
179pub fn fastvideo_verification_snippet() -> &'static str {
180    r#"import os
181import subprocess
182import sys
183
184probe = r"""
185import fastvideo
186import torch
187from fastvideo_kernel import int8_quant
188
189if not torch.cuda.is_available():
190    raise SystemExit("FastVideo verification requires an available ROCm GPU")
191device_count = torch.cuda.device_count()
192if device_count != 1:
193    raise SystemExit(f"FastVideo verification expected one isolated ROCm GPU, found {device_count}")
194
195device = "cuda:0"
196source = torch.randn(
197    (2, 128),
198    device=device,
199    dtype=torch.float16,
200).contiguous()
201quantized, scale = int8_quant(source)
202torch.cuda.synchronize()
203
204shape_ok = quantized.shape == source.shape
205dtype_ok = quantized.dtype == torch.int8
206scale_finite = bool(torch.isfinite(scale).all().item())
207if not (shape_ok and dtype_ok and scale_finite):
208    raise SystemExit(
209        "FastVideo int8_quant invariant failed on device 0: "
210        f"shape_ok={shape_ok} dtype={quantized.dtype} scale_finite={scale_finite}"
211    )
212
213name = torch.cuda.get_device_name(0)
214print(
215    f"[FastVideo] device=0 name={name} int8_quant=ok "
216    f"shape={tuple(quantized.shape)} dtype={quantized.dtype} "
217    f"scale_finite={scale_finite}"
218)
219"""
220
221visible = os.environ.get("ROCR_VISIBLE_DEVICES", "")
222devices = [device.strip() for device in visible.split(",") if device.strip()]
223if not devices:
224    env = os.environ.copy()
225    env.pop("HIP_VISIBLE_DEVICES", None)
226    env.pop("CUDA_VISIBLE_DEVICES", None)
227    env.pop("GPU_DEVICE_ORDINAL", None)
228    discovered = subprocess.run(
229        [sys.executable, "-c", "import torch; print(torch.cuda.device_count())"],
230        check=True,
231        capture_output=True,
232        text=True,
233        env=env,
234    )
235    devices = [str(device) for device in range(int(discovered.stdout.strip()))]
236if not devices:
237    raise SystemExit("FastVideo verification requires at least one visible ROCm GPU")
238
239for device in devices:
240    env = os.environ.copy()
241    env["ROCR_VISIBLE_DEVICES"] = device
242    env.pop("HIP_VISIBLE_DEVICES", None)
243    env.pop("CUDA_VISIBLE_DEVICES", None)
244    env.pop("GPU_DEVICE_ORDINAL", None)
245    subprocess.run([sys.executable, "-c", probe], check=True, env=env)
246"#
247}
248
249/// Read-only preflights and dependency-free install operations after checkout.
250#[derive(Debug, Clone, Copy, PartialEq, Eq)]
251pub enum FastVideoInstallStep {
252    Chown,
253    ManagedDependenciesPreflight,
254    FlashAttentionPreflight,
255    SourcePolicyPreflight,
256    KernelInstall,
257    PackageInstall,
258}
259
260impl FastVideoInstallStep {
261    pub const fn requires_sudo(self) -> bool {
262        matches!(self, Self::Chown)
263    }
264}
265
266pub const fn post_checkout_plan() -> [FastVideoInstallStep; 6] {
267    [
268        FastVideoInstallStep::Chown,
269        FastVideoInstallStep::ManagedDependenciesPreflight,
270        FastVideoInstallStep::FlashAttentionPreflight,
271        FastVideoInstallStep::SourcePolicyPreflight,
272        FastVideoInstallStep::KernelInstall,
273        FastVideoInstallStep::PackageInstall,
274    ]
275}
276
277/// A constructed shell command.
278#[derive(Debug, Clone, PartialEq, Eq)]
279pub struct ShellCommand {
280    /// The program to run.
281    pub program: String,
282    /// Arguments to pass.
283    pub args: Vec<String>,
284    /// Environment variables to set.
285    pub env: Vec<(String, String)>,
286    /// Working directory for the command.
287    pub working_dir: Option<PathBuf>,
288}
289
290/// Configuration for the FastVideo installer.
291#[derive(Debug, Clone)]
292pub struct FastVideoConfig {
293    /// Semicolon-delimited GPU architectures (e.g., "gfx1100;gfx1101").
294    pub gpu_archs: String,
295    /// Python binary to use.
296    pub python_bin: String,
297}
298
299impl Default for FastVideoConfig {
300    fn default() -> Self {
301        Self {
302            gpu_archs: "gfx1100".to_string(),
303            python_bin: "python3".to_string(),
304        }
305    }
306}
307
308/// FastVideo installer.
309#[derive(Debug, Clone)]
310pub struct FastVideoInstaller {
311    repo_url: String,
312    commit: String,
313    build_dir: String,
314    config: FastVideoConfig,
315}
316
317impl Default for FastVideoInstaller {
318    fn default() -> Self {
319        Self::new(FastVideoConfig::default())
320    }
321}
322
323impl FastVideoInstaller {
324    pub fn new(config: FastVideoConfig) -> Self {
325        Self {
326            repo_url: FASTVIDEO_REPO.to_string(),
327            commit: FASTVIDEO_COMMIT.to_string(),
328            build_dir: FASTVIDEO_BUILD_DIR.to_string(),
329            config,
330        }
331    }
332
333    pub fn mkdir_build_dir(&self) -> ShellCommand {
334        ShellCommand {
335            program: "mkdir".to_string(),
336            args: vec!["-p".to_string(), self.build_dir.clone()],
337            env: vec![],
338            working_dir: None,
339        }
340    }
341
342    pub fn git_clone(&self) -> ShellCommand {
343        ShellCommand {
344            program: "git".to_string(),
345            args: vec!["clone".to_string(), self.repo_url.clone(), ".".to_string()],
346            env: vec![],
347            working_dir: Some(PathBuf::from(&self.build_dir)),
348        }
349    }
350
351    /// Check out only the audited immutable fork commit in detached-HEAD mode.
352    pub fn git_checkout(&self) -> ShellCommand {
353        ShellCommand {
354            program: "git".to_string(),
355            args: vec![
356                "checkout".to_string(),
357                "--detach".to_string(),
358                self.commit.clone(),
359            ],
360            env: vec![],
361            working_dir: Some(PathBuf::from(&self.build_dir)),
362        }
363    }
364
365    /// Return ownership of the sudo-created clone to the install user.
366    pub fn chown_build_dir(&self, run_user: &str) -> ShellCommand {
367        ShellCommand {
368            program: "chown".to_string(),
369            args: vec![
370                "-R".to_string(),
371                format!("{run_user}:{run_user}"),
372                self.build_dir.clone(),
373            ],
374            env: vec![],
375            working_dir: None,
376        }
377    }
378
379    /// Verify that Rusty's centrally owned dependencies are present at exact pins.
380    ///
381    /// This preflight is deliberately read-only: FastVideo may consume the managed
382    /// environment, but it must never install, upgrade, or resolve dependencies.
383    pub fn managed_dependencies_preflight(&self) -> ShellCommand {
384        let script = r#"import importlib.metadata
385import os
386from pathlib import Path
387import sys
388
389required = (
390    "setuptools==81.0.0",
391    "cmake==4.4.4",
392    "scikit-build-core==1.1.1",
393    "pathspec==1.1.1",
394    "wheel==0.48.0",
395    "ninja==1.13.2",
396    "imageio==2.38.1",
397    "diffusers==0.41.0",
398    "remote-pdb==2.1.0",
399    "ftfy==6.3.1",
400    "wcwidth==0.2.13",
401)
402problems = []
403for pin in required:
404    name, expected = pin.rsplit("==", 1)
405    try:
406        actual = importlib.metadata.version(name)
407    except importlib.metadata.PackageNotFoundError:
408        problems.append(f"{name}: missing (expected {expected})")
409    else:
410        if actual != expected:
411            problems.append(f"{name}: found {actual}, expected {expected}")
412
413try:
414    import torch
415except Exception as exc:
416    problems.append(f"torch: import failed ({exc})")
417else:
418    if not getattr(torch.version, "hip", None):
419        problems.append("torch: Rusty-managed build is not ROCm (torch.version.hip is empty)")
420    torch_root = Path(torch.__file__).resolve().parent
421    for relative in ("include/ATen/ATen.h", "include/torch/extension.h"):
422        header = torch_root / relative
423        if not header.is_file():
424            problems.append(f"torch header missing: {header}")
425
426rocm_root = Path(os.environ.get("ROCM_PATH", "/opt/rocm"))
427hipcc = Path(os.environ.get("HIPCC", "/opt/rocm/bin/hipcc"))
428if not hipcc.is_file() or not os.access(hipcc, os.X_OK):
429    problems.append(f"HIP compiler missing or not executable: {hipcc}")
430for relative in ("include/hip/hip_runtime.h", "include/hip/hip_fp16.h"):
431    header = rocm_root / relative
432    if not header.is_file():
433        problems.append(f"ROCm header missing: {header}")
434
435scripts_dir = Path(sys.executable).resolve().parent
436for executable_name in ("cmake", "ninja"):
437    executable = scripts_dir / executable_name
438    if not executable.is_file() or not os.access(executable, os.X_OK):
439        problems.append(f"managed build executable missing: {executable}")
440
441if problems:
442    details = "\\n  - ".join(problems)
443    raise SystemExit(
444        "FastVideo requires dependencies owned by Rusty Stack; FastVideo will not "
445        "modify the managed environment. Repair/reinstall Rusty's managed Python "
446        f"dependencies, then retry:\\n  - {details}"
447    )
448print("[FastVideo] Rusty-managed dependency pins verified")
449"#;
450        ShellCommand {
451            program: self.config.python_bin.clone(),
452            args: vec!["-c".to_string(), script.to_string()],
453            env: vec![],
454            working_dir: None,
455        }
456    }
457
458    /// Fail closed if the immutable fork violates the audited ROCm-only source policy.
459    pub fn source_policy_preflight(&self) -> ShellCommand {
460        let script = r#"from pathlib import Path
461import os
462import re
463import subprocess
464
465root = Path(".")
466violations = []
467
468expected_commit = os.environ.get("FASTVIDEO_EXPECTED_COMMIT", "")
469if not expected_commit:
470    violations.append("FASTVIDEO_EXPECTED_COMMIT is missing")
471else:
472    # git rev-parse proves the checkout is the immutable Rusty pin.
473    actual_commit = subprocess.run(
474        ["git", "rev-parse", "HEAD"],
475        cwd=root,
476        check=True,
477        text=True,
478        capture_output=True,
479    ).stdout.strip()
480    if actual_commit != expected_commit:
481        violations.append(
482            f"commit drift: expected {expected_commit}, found {actual_commit}"
483        )
484
485# git status must report no tracked, ignored, or untracked residue.
486dirty = subprocess.run(
487    ["git", "status", "--porcelain=v1", "--untracked-files=all"],
488    cwd=root,
489    check=True,
490    text=True,
491    capture_output=True,
492).stdout
493if dirty:
494    violations.append("working tree is not pristine:\\n" + dirty.rstrip())
495
496if (root / ".gitmodules").exists():
497    violations.append(".gitmodules is forbidden (CUTLASS/TK gitlinks are not allowed)")
498
499staged_records = subprocess.run(
500    ["git", "ls-files", "--stage", "-z"],
501    cwd=root,
502    check=True,
503    text=True,
504    capture_output=True,
505).stdout.split("\0")
506tracked_paths = []
507gitlinks = []
508for record in staged_records:
509    if not record:
510        continue
511    metadata, relative = record.split("\t", 1)
512    if metadata.startswith("160000 "):
513        gitlinks.append(relative)
514    else:
515        tracked_paths.append(Path(relative))
516if gitlinks:
517    violations.append("gitlinks (mode 160000) are forbidden: " + ", ".join(gitlinks))
518
519forbidden_path_terms = ("cutlass", "thunderkittens", "thunder-kittens")
520for path in tracked_paths:
521    relative = path.as_posix()
522    if any(term in relative.lower() for term in forbidden_path_terms):
523        violations.append(f"forbidden CUTLASS/TK/ThunderKittens path: {relative}")
524
525expected_native = {
526    "fastvideo-kernel/csrc/common_extension.cpp",
527    "fastvideo-kernel/csrc/hip_native/gemm_rocm.cpp",
528    "fastvideo-kernel/csrc/turbodiffusion/quant/quant.cu",
529    "fastvideo-kernel/csrc/turbodiffusion/norm/rmsnorm.cu",
530    "fastvideo-kernel/csrc/turbodiffusion/norm/layernorm.cu",
531}
532native_suffixes = {".cu", ".cpp", ".cc", ".cxx", ".hip"}
533actual_native = {
534    path.relative_to(root).as_posix()
535    for path in (root / "fastvideo-kernel/csrc").rglob("*")
536    if path.is_file() and path.suffix.lower() in native_suffixes
537}
538if actual_native != expected_native:
539    violations.append(
540        "unexpected native sources: expected "
541        + repr(sorted(expected_native))
542        + ", found "
543        + repr(sorted(actual_native))
544    )
545
546dependency_suffixes = {".toml", ".cfg", ".ini", ".txt", ".cmake", ".lock"}
547dependency_names = {
548    "cmakelists.txt",
549    "pip.conf",
550    "pipfile",
551    "pipfile.lock",
552    "poetry.lock",
553    "pyproject.toml",
554    "setup.cfg",
555    "setup.py",
556    "uv.lock",
557    "uv.toml",
558}
559dependency_patterns = (
560    re.compile(r"(?im)^\s*(?:dependencies|requires-dist)\s*=.*(?:nvidia|cuda)"),
561    re.compile(r"(?im)^\s*(?:--)?(?:extra-index-url|index-url)\b.*(?:nvidia|cuda)"),
562    re.compile(r"(?i)\bnvidia[-_.][a-z0-9_.-]+"),
563    re.compile(r"(?i)\b(?:cupy-cuda|cuda-python|pytorch-cuda|torch[^\s\"']*\+cu\d|jax[^\s\"']*cuda)"),
564)
565workflow_root = Path(".github/workflows")
566kernel_root = Path("fastvideo-kernel")
567for relative_path in tracked_paths:
568    path = root / relative_path
569    if not path.is_file():
570        continue
571    relative = relative_path.as_posix()
572    name_lower = path.name.lower()
573    is_dependency_metadata = (
574        path.suffix.lower() in dependency_suffixes
575        or name_lower in dependency_names
576        or name_lower.startswith("requirements")
577    )
578    is_kernel_workflow = (
579        relative_path.is_relative_to(workflow_root)
580        and path.suffix.lower() in {".yml", ".yaml"}
581        and path.stem.lower().startswith("fastvideo-kernel")
582    )
583    is_kernel_template = (
584        relative_path.is_relative_to(kernel_root) and "template" in relative.lower()
585    )
586    if not (is_dependency_metadata or is_kernel_workflow or is_kernel_template):
587        continue
588    try:
589        source_text = path.read_text(errors="strict")
590    except (OSError, UnicodeError):
591        continue
592    if is_dependency_metadata and any(
593        pattern.search(source_text) for pattern in dependency_patterns
594    ):
595        violations.append(f"CUDA/NVIDIA dependency or index URL in {relative}")
596    if (is_kernel_workflow or is_kernel_template) and re.search(
597        r"(?i)\b(?:cuda|nvidia|cutlass|thunderkittens)\b", source_text
598    ):
599        violations.append(f"unexpected CUDA/NVIDIA native workflow/template: {relative}")
600
601if violations:
602    raise SystemExit("FastVideo ROCm source policy failed:\\n  - " + "\\n  - ".join(violations))
603print("[FastVideo] immutable ROCm source policy verified")
604"#;
605        ShellCommand {
606            program: self.config.python_bin.clone(),
607            args: vec![
608                "-I".to_string(),
609                "-S".to_string(),
610                "-c".to_string(),
611                script.to_string(),
612            ],
613            env: vec![("FASTVIDEO_EXPECTED_COMMIT".to_string(), self.commit.clone())],
614            working_dir: Some(PathBuf::from(&self.build_dir)),
615        }
616    }
617
618    /// Verify the selected managed flash-attention backend before compiling FastVideo.
619    pub fn flash_attention_preflight(&self) -> ShellCommand {
620        let script = r#"from pathlib import Path
621
622marker = Path.home() / ".mlstack/flash-attention/.backend"
623try:
624    backend = marker.read_text().strip()
625except OSError as exc:
626    raise SystemExit(f"FastVideo requires managed flash-attention backend marker {marker}: {exc}")
627
628if backend not in {"ck", "triton"}:
629    raise SystemExit(
630        f"FastVideo requires exact flash-attention backend marker 'ck' or 'triton'; found {backend!r}"
631    )
632
633import flash_attn
634from flash_attn import (
635    flash_attn_func,
636    flash_attn_varlen_func,
637    flash_attn_varlen_qkvpacked_func,
638)
639from flash_attn.flash_attn_interface import (
640    _flash_attn_varlen_backward,
641    _flash_attn_varlen_forward,
642)
643
644selected = "CK" if backend == "ck" else "Triton fallback"
645print(f"[FastVideo] selected flash_attn backend: {selected}")
646"#;
647        ShellCommand {
648            program: self.config.python_bin.clone(),
649            args: vec!["-c".to_string(), script.to_string()],
650            env: vec![],
651            working_dir: None,
652        }
653    }
654
655    /// Build and install fastvideo-kernel with ROCm support.
656    ///
657    /// Sets only the HIP architecture list and selected managed Python interpreter
658    /// through CMake. The pinned fork is HIP-only and has no CUDA/TK backend switch.
659    ///
660    /// Uses `pip install --no-build-isolation --no-deps --no-index .` so the kernel
661    /// consumes Rusty's managed build/runtime dependencies without resolving or
662    /// modifying them.
663    ///
664    /// # Pip Target Directory
665    ///
666    /// The working directory is the clone's `fastvideo-kernel` subdirectory,
667    /// whose `pyproject.toml` installs the native `fastvideo_kernel` module.
668    pub fn pip_install_kernel(&self) -> ShellCommand {
669        let mut args = vec![
670            "-m".to_string(),
671            "pip".to_string(),
672            "install".to_string(),
673            "--break-system-packages".to_string(),
674        ];
675        args.extend([
676            "-v".to_string(),
677            "--no-build-isolation".to_string(),
678            "--no-deps".to_string(),
679            "--no-index".to_string(),
680            ".".to_string(),
681        ]);
682
683        let cmake_args = format!(
684            "-DCMAKE_HIP_ARCHITECTURES={} -DPython_EXECUTABLE={}",
685            self.config.gpu_archs, self.config.python_bin
686        );
687
688        let max_jobs = std::thread::available_parallelism()
689            .map(|n| max_jobs_for_parallelism(n.get()))
690            .unwrap_or(1);
691
692        let env = vec![
693            ("CMAKE_ARGS".to_string(), cmake_args),
694            ("GPU_ARCHS".to_string(), self.config.gpu_archs.clone()),
695            ("MAX_JOBS".to_string(), max_jobs.to_string()),
696            (
697                "PYTHON_EXECUTABLE".to_string(),
698                self.config.python_bin.clone(),
699            ),
700            ("PIP_NO_INDEX".to_string(), "1".to_string()),
701            ("PIP_DISABLE_PIP_VERSION_CHECK".to_string(), "1".to_string()),
702            ("UV_OFFLINE".to_string(), "1".to_string()),
703        ];
704
705        ShellCommand {
706            program: self.config.python_bin.clone(),
707            args,
708            env,
709            working_dir: Some(PathBuf::from(&self.build_dir).join("fastvideo-kernel")),
710        }
711    }
712
713    /// Install the root FastVideo Python package without resolving CUDA dependencies.
714    pub fn pip_install_package(&self) -> ShellCommand {
715        ShellCommand {
716            program: self.config.python_bin.clone(),
717            args: [
718                "-m",
719                "pip",
720                "install",
721                "--break-system-packages",
722                "--no-build-isolation",
723                "--no-deps",
724                "--no-index",
725                ".",
726            ]
727            .map(String::from)
728            .to_vec(),
729            env: vec![
730                ("PIP_NO_INDEX".to_string(), "1".to_string()),
731                ("PIP_DISABLE_PIP_VERSION_CHECK".to_string(), "1".to_string()),
732                ("UV_OFFLINE".to_string(), "1".to_string()),
733            ],
734            working_dir: Some(PathBuf::from(&self.build_dir)),
735        }
736    }
737
738    /// Snapshot all distributions before installation or validate the after-state.
739    ///
740    /// Only FastVideo's two own distributions may change. CUDA/NVIDIA distributions
741    /// fail closed in either phase.
742    pub fn distribution_snapshot(&self, phase: &str, runtime_prefixes: &[&str]) -> ShellCommand {
743        let script = r#"import importlib.metadata
744import json
745import os
746from pathlib import Path
747import re
748import sys
749
750phase = sys.argv[1]
751state_path = Path("/tmp/rusty-fastvideo-distributions.json")
752normalize = lambda value: re.sub(r"[-_.]+", "-", value).lower()
753current = {
754    normalize(dist.metadata["Name"]): dist.version
755    for dist in importlib.metadata.distributions()
756    if dist.metadata.get("Name")
757}
758runtime_prefixes = tuple(
759    prefix
760    for prefix in os.environ["RUSTY_NVIDIA_RUNTIME_PREFIXES"].split(",")
761    if prefix
762)
763forbidden_accelerator = sorted(
764    name
765    for name in current
766    if any(name.startswith(prefix) for prefix in runtime_prefixes) or "cuda" in name
767)
768if forbidden_accelerator:
769    raise SystemExit(
770        "CUDA/NVIDIA distributions are forbidden in Rusty's ROCm environment: "
771        + ", ".join(forbidden_accelerator)
772    )
773
774if phase == "before":
775    state_path.write_text(json.dumps(current, sort_keys=True))
776elif phase == "after":
777    before = json.loads(state_path.read_text())
778    changed = {
779        name
780        for name in set(before) | set(current)
781        if before.get(name) != current.get(name)
782    }
783    unexpected = sorted(changed - {"fastvideo", "fastvideo-kernel"})
784    if unexpected:
785        raise SystemExit(
786            "FastVideo install modified Rusty-managed distributions: " + ", ".join(unexpected)
787        )
788else:
789    raise SystemExit(f"unknown distribution snapshot phase: {phase!r}")
790"#;
791        ShellCommand {
792            program: self.config.python_bin.clone(),
793            args: vec!["-c".to_string(), script.to_string(), phase.to_string()],
794            env: vec![(
795                "RUSTY_NVIDIA_RUNTIME_PREFIXES".to_string(),
796                runtime_prefixes.join(","),
797            )],
798            working_dir: None,
799        }
800    }
801
802    pub fn cleanup(&self) -> ShellCommand {
803        ShellCommand {
804            program: "rm".to_string(),
805            args: vec!["-rf".to_string(), "--".to_string(), self.build_dir.clone()],
806            env: vec![],
807            working_dir: None,
808        }
809    }
810}
811
812#[cfg(test)]
813mod tests {
814    use super::*;
815
816    #[test]
817    fn remediation_contract_is_immutable_read_only_and_offline() {
818        let inst = FastVideoInstaller::new(FastVideoConfig {
819            gpu_archs: "gfx1100;gfx1101".to_string(),
820            python_bin: "/managed/bin/python".to_string(),
821        });
822
823        assert_eq!(FASTVIDEO_COMMIT, "22e448771ebf5c81108f1c57b9c7ef4d5c26d182");
824        assert_eq!(
825            inst.git_checkout().args,
826            ["checkout", "--detach", FASTVIDEO_COMMIT].map(String::from)
827        );
828        assert!(!inst
829            .git_clone()
830            .args
831            .iter()
832            .any(|arg| arg.contains("submodule")));
833
834        let managed = inst.managed_dependencies_preflight();
835        let managed_script = &managed.args[1];
836        for pin in [
837            "setuptools==81.0.0",
838            "cmake==4.4.4",
839            "scikit-build-core==1.1.1",
840            "pathspec==1.1.1",
841            "wheel==0.48.0",
842            "ninja==1.13.2",
843            "imageio==2.38.1",
844            "diffusers==0.41.0",
845            "remote-pdb==2.1.0",
846            "ftfy==6.3.1",
847            "wcwidth==0.2.13",
848        ] {
849            assert!(managed_script.contains(pin), "missing managed pin {pin}");
850        }
851        assert!(managed_script.contains("importlib.metadata"));
852        for anchor in [
853            "torch.version.hip",
854            "/opt/rocm/bin/hipcc",
855            "hip/hip_runtime.h",
856            "ATen/ATen.h",
857            "torch/extension.h",
858        ] {
859            assert!(
860                managed_script.contains(anchor),
861                "missing ROCm prerequisite anchor: {anchor}"
862            );
863        }
864        assert!(!managed_script.contains("pip"));
865        assert!(managed_script.contains("Rusty"));
866
867        let flash = inst.flash_attention_preflight();
868        let flash_script = &flash.args[1];
869        assert!(flash_script.contains("from flash_attn import"));
870        assert!(flash_script.contains("from flash_attn.flash_attn_interface import"));
871        assert!(flash_script.contains("_flash_attn_varlen_forward"));
872        assert!(flash_script.contains("_flash_attn_varlen_backward"));
873
874        let source = inst.source_policy_preflight();
875        assert_eq!(&source.args[..3], ["-I", "-S", "-c"].map(String::from));
876        assert!(source.env.contains(&(
877            "FASTVIDEO_EXPECTED_COMMIT".to_string(),
878            FASTVIDEO_COMMIT.to_string()
879        )));
880        let source_script = &source.args[3].to_ascii_lowercase();
881        for clean_tree_anchor in [
882            "git status",
883            "--porcelain=v1",
884            "--untracked-files=all",
885            "git rev-parse",
886            "fastvideo_expected_commit",
887        ] {
888            assert!(
889                source_script.contains(clean_tree_anchor),
890                "missing clean-tree proof: {clean_tree_anchor}"
891            );
892        }
893        for forbidden in [
894            ".gitmodules",
895            "160000",
896            "cutlass",
897            "thunderkittens",
898            "nvidia",
899            "cuda",
900            "extra-index-url",
901            "index-url",
902            ".github/workflows",
903            "common_extension.cpp",
904            "gemm_rocm.cpp",
905            "quant.cu",
906            "rmsnorm.cu",
907            "layernorm.cu",
908        ] {
909            assert!(
910                source_script.contains(forbidden),
911                "missing source policy {forbidden}"
912            );
913        }
914
915        assert!(source_script.contains("is_kernel_workflow"));
916        assert!(source_script.contains("path.stem.lower().startswith(\"fastvideo-kernel\")"));
917        let dependency_suffixes = source_script
918            .lines()
919            .find(|line| line.starts_with("dependency_suffixes ="))
920            .expect("source policy must define dependency metadata suffixes");
921        assert!(!dependency_suffixes.contains(".yml"));
922        assert!(!dependency_suffixes.contains(".yaml"));
923
924        for cmd in [inst.pip_install_kernel(), inst.pip_install_package()] {
925            assert_eq!(cmd.program, "/managed/bin/python");
926            for required in ["--no-deps", "--no-build-isolation", "--no-index"] {
927                assert!(
928                    cmd.args.contains(&required.to_string()),
929                    "missing {required}"
930                );
931            }
932            for forbidden in ["--upgrade", "--extra-index-url", "--index-url"] {
933                assert!(!cmd.args.contains(&forbidden.to_string()));
934            }
935            assert!(cmd
936                .env
937                .contains(&("PIP_NO_INDEX".to_string(), "1".to_string())));
938            assert!(cmd
939                .env
940                .contains(&("UV_OFFLINE".to_string(), "1".to_string())));
941        }
942
943        let runtime_prefixes = [
944            "nvidia",
945            "cuda",
946            "cudnn",
947            "cublas",
948            "cufft",
949            "curand",
950            "cusolver",
951            "cusparse",
952            "cusparselt",
953            "nccl",
954            "nvtx",
955            "nvjitlink",
956            "nv-pic",
957            "tensorrt",
958            "pytorch-cuda",
959            "torch-cuda",
960        ];
961        let snapshot = inst.distribution_snapshot("before", &runtime_prefixes);
962        assert_eq!(snapshot.program, "/managed/bin/python");
963        assert!(snapshot.args[1].contains("fastvideo-kernel"));
964        assert!(snapshot.args[1].contains("RUSTY_NVIDIA_RUNTIME_PREFIXES"));
965        let encoded_prefixes = snapshot
966            .env
967            .iter()
968            .find(|(name, _)| name == "RUSTY_NVIDIA_RUNTIME_PREFIXES")
969            .map(|(_, value)| value.as_str())
970            .expect("snapshot must receive canonical runtime prefixes");
971        for forbidden in runtime_prefixes {
972            assert!(
973                encoded_prefixes
974                    .split(',')
975                    .any(|prefix| prefix == forbidden),
976                "missing canonical runtime distribution pattern {forbidden}"
977            );
978        }
979    }
980
981    /// Source-policy regression guard: the pinned FastVideo fork carries
982    /// unrelated upstream CUDA workflows, and the source policy must accept the
983    /// pinned fork while still rejecting stale/untracked source.
984    ///
985    /// Historically this test was gated on a hardcoded personal path and silently
986    /// `return`ed when absent — so it never ran in CI or for other contributors.
987    /// It now reads the FastVideo fork location from `FASTVIDEO_POLICY_TEST_REPO`
988    /// and is **skipped** (via an explicit eprintln + return, since cargo has no
989    /// first-class runtime skip without `#[ignore]`) when that var is unset. Set
990    /// the var in CI (or locally) to exercise the policy for real.
991    #[test]
992    fn source_policy_accepts_pinned_fork_with_unrelated_cuda_workflows() {
993        use std::process::Command;
994        use std::time::{SystemTime, UNIX_EPOCH};
995
996        let repo_str = match std::env::var("FASTVIDEO_POLICY_TEST_REPO") {
997            Ok(s) if !s.trim().is_empty() => s,
998            _ => {
999                eprintln!(
1000                    "skip: FASTVIDEO_POLICY_TEST_REPO unset; set it to a FastVideo fork checkout \
1001                     to exercise source_policy_accepts_pinned_fork_with_unrelated_cuda_workflows"
1002                );
1003                return;
1004            }
1005        };
1006        let repo = Path::new(&repo_str);
1007        if !repo.is_dir() {
1008            eprintln!(
1009                "skip: FASTVIDEO_POLICY_TEST_REPO={} is not a directory",
1010                repo.display()
1011            );
1012            return;
1013        }
1014
1015        let unique = SystemTime::now()
1016            .duration_since(UNIX_EPOCH)
1017            .expect("system clock must be after Unix epoch")
1018            .as_nanos();
1019        let clone = std::env::temp_dir().join(format!(
1020            "rusty-fastvideo-policy-{}-{unique}",
1021            std::process::id()
1022        ));
1023        let checkout_status = Command::new("git")
1024            .arg("-C")
1025            .arg(repo)
1026            .args(["worktree", "add", "--detach"])
1027            .arg(&clone)
1028            .arg(FASTVIDEO_COMMIT)
1029            .status()
1030            .expect("git must create an immutable FastVideo worktree");
1031        assert!(
1032            checkout_status.success(),
1033            "immutable FastVideo worktree must succeed"
1034        );
1035
1036        let cmd = FastVideoInstaller::default().source_policy_preflight();
1037        let run_policy = || {
1038            Command::new(&cmd.program)
1039                .args(&cmd.args)
1040                .envs(cmd.env.iter().cloned())
1041                .current_dir(&clone)
1042                .output()
1043                .expect("configured Python should execute the source policy")
1044        };
1045        let clean_output = run_policy();
1046        assert!(
1047            clean_output.status.success(),
1048            "pinned fork must pass source policy despite unrelated upstream workflows: {}",
1049            String::from_utf8_lossy(&clean_output.stderr)
1050        );
1051
1052        std::fs::write(
1053            clone.join("sitecustomize.py"),
1054            "raise RuntimeError('must never execute')\n",
1055        )
1056        .expect("write stale untracked source");
1057        let dirty_output = run_policy();
1058        assert!(
1059            !dirty_output.status.success(),
1060            "source policy must reject stale untracked files"
1061        );
1062        assert!(
1063            String::from_utf8_lossy(&dirty_output.stderr).contains("working tree is not pristine"),
1064            "dirty-tree failure must identify the source residue"
1065        );
1066
1067        let remove_status = Command::new("git")
1068            .arg("-C")
1069            .arg(repo)
1070            .args(["worktree", "remove", "--force"])
1071            .arg(&clone)
1072            .status()
1073            .expect("git must remove temporary FastVideo worktree");
1074        assert!(
1075            remove_status.success(),
1076            "temporary worktree removal must succeed"
1077        );
1078    }
1079
1080    #[test]
1081    fn remediation_plan_has_no_patch_or_dependency_write_phase() {
1082        use FastVideoInstallStep::*;
1083        assert_eq!(
1084            post_checkout_plan(),
1085            [
1086                Chown,
1087                ManagedDependenciesPreflight,
1088                FlashAttentionPreflight,
1089                SourcePolicyPreflight,
1090                KernelInstall,
1091                PackageInstall,
1092            ]
1093        );
1094    }
1095
1096    #[test]
1097    fn test_verification_snippet_isolates_each_visible_gpu_with_rocr_only() {
1098        use std::process::Command;
1099
1100        let snippet = fastvideo_verification_snippet();
1101        for anchor in [
1102            "import os",
1103            "import subprocess",
1104            "import fastvideo",
1105            "import torch",
1106            "from fastvideo_kernel import int8_quant",
1107            "torch.cuda.is_available()",
1108            "torch.cuda.device_count()",
1109            "visible = os.environ.get(\"ROCR_VISIBLE_DEVICES\", \"\")",
1110            "devices = [device.strip() for device in visible.split(\",\") if device.strip()]",
1111            "for device in devices:",
1112            "env = os.environ.copy()",
1113            "env[\"ROCR_VISIBLE_DEVICES\"] = device",
1114            "env.pop(\"HIP_VISIBLE_DEVICES\", None)",
1115            "env.pop(\"CUDA_VISIBLE_DEVICES\", None)",
1116            "env.pop(\"GPU_DEVICE_ORDINAL\", None)",
1117            "subprocess.run(",
1118            "device = \"cuda:0\"",
1119            "(2, 128)",
1120            "dtype=torch.float16",
1121            ".contiguous()",
1122            "int8_quant(source)",
1123            "torch.cuda.synchronize()",
1124            "quantized.shape == source.shape",
1125            "quantized.dtype == torch.int8",
1126            "torch.isfinite(scale).all()",
1127            "torch.cuda.get_device_name(0)",
1128            "raise SystemExit",
1129        ] {
1130            assert!(
1131                snippet.contains(anchor),
1132                "missing verification anchor: {anchor}"
1133            );
1134        }
1135
1136        for forbidden in ["rocminfo", "/sys/class/drm"] {
1137            assert!(
1138                !snippet.contains(forbidden),
1139                "snippet must use Python/ROCr visibility, found {forbidden}"
1140            );
1141        }
1142
1143        let compile = format!("compile({snippet:?}, '<fastvideo-verification>', 'exec')");
1144        let output = Command::new(&FastVideoConfig::default().python_bin)
1145            .args(["-c", &compile])
1146            .output()
1147            .expect("configured python3 should run");
1148        assert!(
1149            output.status.success(),
1150            "verification snippet must compile as Python: {}",
1151            String::from_utf8_lossy(&output.stderr)
1152        );
1153    }
1154
1155    #[test]
1156    fn test_git_clone_uses_fork() {
1157        let inst = FastVideoInstaller::new(FastVideoConfig::default());
1158        let clone_cmd = inst.git_clone();
1159        assert!(clone_cmd.args.contains(&"clone".into()));
1160        assert!(
1161            clone_cmd.args.contains(&FASTVIDEO_REPO.into()),
1162            "Should clone from scooter-lacroix fork"
1163        );
1164    }
1165
1166    #[test]
1167    fn test_chown_build_dir_targets_install_user() {
1168        let inst = FastVideoInstaller::new(FastVideoConfig::default());
1169        let cmd = inst.chown_build_dir("ml-user");
1170        assert_eq!(cmd.program, "chown");
1171        assert_eq!(
1172            cmd.args,
1173            ["-R", "ml-user:ml-user", FASTVIDEO_BUILD_DIR].map(String::from)
1174        );
1175    }
1176
1177    #[test]
1178    fn test_pip_install_has_multi_arch_rocm_cmake_args() {
1179        let inst = FastVideoInstaller::new(FastVideoConfig {
1180            gpu_archs: "gfx1100;gfx1101".to_string(),
1181            python_bin: "python3".to_string(),
1182        });
1183        let cmd = inst.pip_install_kernel();
1184        assert!(cmd.args.contains(&"--no-build-isolation".into()));
1185        assert!(cmd.args.contains(&"--no-deps".into()));
1186        let cmake_env = cmd.env.iter().find(|(k, _)| k == "CMAKE_ARGS").unwrap();
1187        assert!(cmake_env
1188            .1
1189            .contains("CMAKE_HIP_ARCHITECTURES=gfx1100;gfx1101"));
1190        assert!(!cmake_env.1.contains("GPU_BACKEND"));
1191        assert!(!cmd.env.iter().any(|(key, _)| key == "GPU_BACKEND"));
1192        assert!(!cmake_env.1.to_ascii_lowercase().contains("tk"));
1193    }
1194
1195    #[test]
1196    fn test_pip_install_sets_same_gpu_archs() {
1197        let inst = FastVideoInstaller::new(FastVideoConfig {
1198            gpu_archs: "gfx1100;gfx1101".to_string(),
1199            python_bin: "python3".to_string(),
1200        });
1201        let cmd = inst.pip_install_kernel();
1202        let gpu_env = cmd.env.iter().find(|(k, _)| k == "GPU_ARCHS").unwrap();
1203        assert_eq!(gpu_env.1, "gfx1100;gfx1101");
1204    }
1205
1206    #[test]
1207    fn test_both_pip_commands_always_allow_selected_interpreter() {
1208        let inst = FastVideoInstaller::default();
1209        assert!(inst
1210            .pip_install_package()
1211            .args
1212            .contains(&"--break-system-packages".to_string()));
1213        assert!(inst
1214            .pip_install_kernel()
1215            .args
1216            .contains(&"--break-system-packages".to_string()));
1217    }
1218
1219    #[test]
1220    fn test_max_jobs_is_cpu_spare_and_capped() {
1221        assert_eq!(max_jobs_for_parallelism(0), 1);
1222        assert_eq!(max_jobs_for_parallelism(1), 1);
1223        assert_eq!(max_jobs_for_parallelism(2), 1);
1224        assert_eq!(max_jobs_for_parallelism(4), 3);
1225        assert_eq!(max_jobs_for_parallelism(8), 4);
1226    }
1227
1228    #[test]
1229    fn test_resolve_gpu_archs_prefers_sysfs() {
1230        let sysfs = vec![" gfx1100 ".to_string(), "gfx1101".to_string()];
1231        assert_eq!(
1232            resolve_gpu_archs(&sysfs, Some("gfx1200"), Some("gfx1030"), Some("gfx900")),
1233            "gfx1100;gfx1101"
1234        );
1235    }
1236
1237    #[test]
1238    fn test_resolve_gpu_archs_normalizes_and_deduplicates_env() {
1239        assert_eq!(
1240            resolve_gpu_archs(&[], Some(" gfx1100, gfx1101;gfx1100\tgfx1101 "), None, None),
1241            "gfx1100;gfx1101"
1242        );
1243        assert_eq!(
1244            resolve_gpu_archs(&[], None, Some("gfx1100, gfx1101"), None),
1245            "gfx1100;gfx1101"
1246        );
1247    }
1248
1249    #[test]
1250    fn test_resolve_gpu_archs_detected_primary_then_default() {
1251        assert_eq!(
1252            resolve_gpu_archs(&[], Some(" , ; "), None, Some(" gfx1101 ")),
1253            "gfx1101"
1254        );
1255        assert_eq!(resolve_gpu_archs(&[], None, None, None), "gfx1100");
1256    }
1257
1258    #[test]
1259    fn test_resolve_install_user_precedence_and_blanks() {
1260        assert_eq!(
1261            resolve_install_user(Some(" sudo-user "), Some("user")),
1262            Some("sudo-user".to_string())
1263        );
1264        assert_eq!(
1265            resolve_install_user(Some("   "), Some(" user ")),
1266            Some("user".to_string())
1267        );
1268        assert_eq!(resolve_install_user(None, None), None);
1269        assert_eq!(resolve_install_user(Some(""), Some(" \t ")), None);
1270    }
1271
1272    #[test]
1273    fn test_heavy_build_warning_is_complete() {
1274        assert_eq!(
1275            fastvideo_build_warning("gfx1100;gfx1101"),
1276            "[FastVideo] Heavy dual-arch HIP compile for gfx1100;gfx1101; MAX_JOBS capped at 4"
1277        );
1278    }
1279
1280    #[test]
1281    fn test_post_checkout_plan_order_and_privileges() {
1282        use FastVideoInstallStep::*;
1283
1284        let plan = post_checkout_plan();
1285        assert_eq!(
1286            plan,
1287            [
1288                Chown,
1289                ManagedDependenciesPreflight,
1290                FlashAttentionPreflight,
1291                SourcePolicyPreflight,
1292                KernelInstall,
1293                PackageInstall,
1294            ]
1295        );
1296        assert!(Chown.requires_sudo());
1297        for step in [
1298            ManagedDependenciesPreflight,
1299            FlashAttentionPreflight,
1300            SourcePolicyPreflight,
1301            KernelInstall,
1302            PackageInstall,
1303        ] {
1304            assert!(!step.requires_sudo());
1305        }
1306    }
1307
1308    #[test]
1309    fn test_flash_attention_preflight_uses_marker_and_required_api() {
1310        let inst = FastVideoInstaller::new(FastVideoConfig {
1311            gpu_archs: "gfx1100".to_string(),
1312            python_bin: "python3.12".to_string(),
1313        });
1314        let cmd = inst.flash_attention_preflight();
1315
1316        assert_eq!(cmd.program, "python3.12");
1317        assert_eq!(cmd.args[0], "-c");
1318        assert_eq!(cmd.working_dir, None);
1319        let script = &cmd.args[1];
1320        for anchor in [
1321            "Path.home()",
1322            ".mlstack/flash-attention/.backend",
1323            "ck",
1324            "triton",
1325            "from flash_attn import",
1326            "flash_attn_func",
1327            "flash_attn_varlen_func",
1328            "flash_attn_varlen_qkvpacked_func",
1329            "_flash_attn_varlen_forward",
1330            "_flash_attn_varlen_backward",
1331        ] {
1332            assert!(
1333                script.contains(anchor),
1334                "missing preflight anchor: {anchor}"
1335            );
1336        }
1337        assert!(!script.contains("fastvideo_kernel"));
1338        assert!(!script.contains("common_extension"));
1339    }
1340
1341    #[test]
1342    fn test_root_package_install_is_dependency_free_from_clone_root() {
1343        let inst = FastVideoInstaller::new(FastVideoConfig {
1344            gpu_archs: "gfx1100".to_string(),
1345            python_bin: "python3.12".to_string(),
1346        });
1347        let cmd = inst.pip_install_package();
1348
1349        assert_eq!(cmd.program, "python3.12");
1350        assert_eq!(
1351            cmd.args,
1352            [
1353                "-m",
1354                "pip",
1355                "install",
1356                "--break-system-packages",
1357                "--no-build-isolation",
1358                "--no-deps",
1359                "--no-index",
1360                ".",
1361            ]
1362            .map(String::from)
1363        );
1364        assert_eq!(cmd.working_dir, Some(PathBuf::from(FASTVIDEO_BUILD_DIR)));
1365        assert!(cmd
1366            .env
1367            .contains(&("PIP_NO_INDEX".to_string(), "1".to_string())));
1368        assert!(cmd
1369            .env
1370            .contains(&("UV_OFFLINE".to_string(), "1".to_string())));
1371        assert!(!cmd.args.iter().any(|arg| {
1372            let arg = arg.to_ascii_lowercase();
1373            arg.contains("cuda") || arg.contains("nvidia") || arg == "torch"
1374        }));
1375    }
1376
1377    #[test]
1378    fn test_visibility_masks_select_discrete_archs() {
1379        let archs = vec!["gfx1100".to_string(), "gfx1101".to_string()];
1380        assert_eq!(
1381            select_visible_gpu_archs(&archs, Some("0,1"), None, None),
1382            archs
1383        );
1384        assert_eq!(
1385            select_visible_gpu_archs(&archs, Some("1"), Some("0"), Some("0")),
1386            vec!["gfx1101".to_string()]
1387        );
1388        assert!(select_visible_gpu_archs(&archs, Some("bad,9"), None, None).is_empty());
1389    }
1390
1391    #[test]
1392    fn test_gpu_arch_validation_rejects_garbage() {
1393        assert_eq!(
1394            resolve_gpu_archs(
1395                &["not-gfx".to_string(), "gfx1101".to_string()],
1396                Some("garbage,gfx12zz,gfx1100"),
1397                None,
1398                None
1399            ),
1400            "gfx1101"
1401        );
1402        assert_eq!(
1403            resolve_gpu_archs(&[], Some("garbage,gfx12zz,gfx1100"), None, None),
1404            "gfx1100"
1405        );
1406    }
1407
1408    #[test]
1409    fn test_cleanup_removes_build_dir() {
1410        let inst = FastVideoInstaller::new(FastVideoConfig::default());
1411        let cleanup = inst.cleanup();
1412        assert_eq!(
1413            cleanup.args,
1414            ["-rf", "--", FASTVIDEO_BUILD_DIR].map(String::from)
1415        );
1416    }
1417
1418    #[test]
1419    fn test_sysfs_detection_excludes_apu_and_preserves_discrete_order() {
1420        use std::time::{SystemTime, UNIX_EPOCH};
1421
1422        let unique = SystemTime::now()
1423            .duration_since(UNIX_EPOCH)
1424            .expect("system clock must be after Unix epoch")
1425            .as_nanos();
1426        let root = std::env::temp_dir().join(format!(
1427            "rusty-fastvideo-sysfs-{}-{unique}",
1428            std::process::id()
1429        ));
1430
1431        for (card, device_id) in [
1432            ("card0", "0x15bf"),
1433            ("card1", "0x744c"),
1434            ("card2", "0x747e"),
1435        ] {
1436            let device = root.join(card).join("device");
1437            std::fs::create_dir_all(&device).expect("create fake DRM device");
1438            std::fs::write(device.join("vendor"), "0x1002\n").expect("write fake vendor");
1439            std::fs::write(device.join("device"), format!("{device_id}\n"))
1440                .expect("write fake device id");
1441        }
1442
1443        assert_eq!(
1444            detect_discrete_gpu_archs_from_sysfs(&root),
1445            vec!["gfx1100".to_string(), "gfx1101".to_string()]
1446        );
1447        std::fs::remove_dir_all(&root).expect("remove fake DRM tree");
1448    }
1449
1450    #[test]
1451    fn test_config_propagates_python_bin() {
1452        let inst = FastVideoInstaller::new(FastVideoConfig {
1453            gpu_archs: "gfx1100".to_string(),
1454            python_bin: "python3.12".to_string(),
1455        });
1456        let cmd = inst.pip_install_kernel();
1457        assert_eq!(cmd.program, "python3.12");
1458    }
1459}