from __future__ import annotations import re from pathlib import Path from .ingest import iter_text_files from .models import Finding def scan_repository(root: Path) -> list[Finding]: files = iter_text_files(root) findings: list[Finding] = [] has_dockerfile = False has_benchmark = False has_vllm_or_sglang = False for relative_path, text in files: path_lower = relative_path.lower() if Path(relative_path).name.lower().startswith("dockerfile"): has_dockerfile = True if "bench" in path_lower or "benchmark" in text.lower(): has_benchmark = True if "vllm" in text.lower() or "sglang" in text.lower(): has_vllm_or_sglang = True findings.extend(_scan_file(relative_path, text)) if not has_dockerfile: findings.append( Finding( id="missing-dockerfile", category="deployment", severity="low", path=".", line=1, message="No Dockerfile was found for a reproducible ROCm deployment.", suggested_fix="Generate Dockerfile.rocm with ROCm/vLLM base image and AMD GPU device mounts.", ) ) if not has_benchmark: findings.append( Finding( id="missing-benchmark", category="benchmark", severity="low", path=".", line=1, message="No benchmark entrypoint was found.", suggested_fix="Add a reproducible latency, throughput, and memory collection command for AMD Developer Cloud.", ) ) if not has_vllm_or_sglang: findings.append( Finding( id="missing-serving-runbook", category="serving", severity="low", path=".", line=1, message="No vLLM or SGLang serving command was found.", suggested_fix="Generate a ROCm serving runbook using vllm/vllm-openai-rocm when LLM serving is needed.", ) ) return findings[:200] def _scan_file(relative_path: str, text: str) -> list[Finding]: findings: list[Finding] = [] suffix = Path(relative_path).suffix.lower() file_name = Path(relative_path).name.lower() for line_number, line in enumerate(text.splitlines(), start=1): stripped = line.strip() lower = stripped.lower() if suffix in {".cu", ".cuh"} or _contains_cuda_kernel_api(stripped): findings.append( Finding( id=f"cuda-kernel-{line_number}", category="code", severity="manual", path=relative_path, line=line_number, message="CUDA kernel or CUDA runtime API usage requires manual HIP review.", suggested_fix="Use HIPIFY or manually port CUDA C++ kernels; the MVP does not rewrite kernels.", remediable=False, ) ) if re.search(r"\.cuda\s*\(", stripped): findings.append( Finding( id=f"python-cuda-call-{line_number}", category="code", severity="high", path=relative_path, line=line_number, message="PyTorch tensor or module is moved with a hardcoded .cuda() call.", suggested_fix="Replace .cuda() with .to(_rocmport_device) and define a runtime device abstraction.", ) ) if re.search(r"torch\.device\(\s*['\"]cuda", stripped): findings.append( Finding( id=f"torch-device-cuda-{line_number}", category="code", severity="high", path=relative_path, line=line_number, message="torch.device is hardcoded to CUDA.", suggested_fix="Use torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\"); ROCm PyTorch reports AMD GPUs through torch.cuda.", ) ) if re.search(r"\.to\(\s*['\"]cuda['\"]\s*\)", stripped): findings.append( Finding( id=f"to-cuda-{line_number}", category="code", severity="high", path=relative_path, line=line_number, message="Tensor or module transfer hardcodes the CUDA device string.", suggested_fix="Replace .to(\"cuda\") with .to(_rocmport_device).", ) ) if "torch.cuda.is_available" in stripped and "rocm" not in lower: findings.append( Finding( id=f"cuda-availability-check-{line_number}", category="code", severity="low", path=relative_path, line=line_number, message="CUDA availability check may confuse ROCm users because PyTorch ROCm still uses the torch.cuda namespace.", suggested_fix="Keep the API call but document that it covers AMD GPUs under ROCm PyTorch.", ) ) if "nvidia-smi" in lower: category = "benchmark" if "bench" in relative_path.lower() or "benchmark" in lower else "environment" findings.append( Finding( id=f"nvidia-smi-{line_number}", category=category, severity="high", path=relative_path, line=line_number, message="NVIDIA-specific GPU inspection command found.", suggested_fix="Use rocm-smi for AMD GPU monitoring and benchmark metadata collection.", ) ) if re.search(r"\bNVIDIA_(VISIBLE_DEVICES|DRIVER_CAPABILITIES)\b", stripped): findings.append( Finding( id=f"nvidia-env-{line_number}", category="environment", severity="medium", path=relative_path, line=line_number, message="NVIDIA container environment variable found.", suggested_fix="Use HIP_VISIBLE_DEVICES or ROCR_VISIBLE_DEVICES for AMD GPU targeting.", ) ) if re.search(r"\bCUDA_VISIBLE_DEVICES\b", stripped): findings.append( Finding( id=f"cuda-visible-devices-{line_number}", category="environment", severity="medium", path=relative_path, line=line_number, message="CUDA_VISIBLE_DEVICES is used for GPU selection.", suggested_fix="Use HIP_VISIBLE_DEVICES or ROCR_VISIBLE_DEVICES for AMD GPU targeting.", ) ) if re.search(r"\bCUDA_(HOME|PATH)\b", stripped): findings.append( Finding( id=f"cuda-path-env-{line_number}", category="environment", severity="medium", path=relative_path, line=line_number, message="CUDA toolkit path environment variable found.", suggested_fix="Remove CUDA toolkit path assumptions or replace with ROCm installation paths when required.", remediable=False, ) ) if file_name.startswith("dockerfile") and re.search(r"^\s*FROM\s+nvidia/cuda", stripped, re.IGNORECASE): findings.append( Finding( id=f"nvidia-docker-base-{line_number}", category="environment", severity="high", path=relative_path, line=line_number, message="Dockerfile uses an NVIDIA CUDA base image.", suggested_fix="Use vllm/vllm-openai-rocm:latest for vLLM serving or rocm/pytorch:latest for PyTorch workloads.", ) ) if "cudatoolkit" in lower or "cupy-cuda" in lower: findings.append( Finding( id=f"cuda-package-{line_number}", category="environment", severity="medium", path=relative_path, line=line_number, message="Dependency references a CUDA-specific package.", suggested_fix="Replace CUDA-specific wheels with ROCm-compatible PyTorch or library builds.", remediable=False, ) ) if "vllm serve" in lower or "vllm.entrypoints" in lower: findings.append( Finding( id=f"vllm-rocm-runbook-{line_number}", category="serving", severity="low", path=relative_path, line=line_number, message="vLLM serving command found without explicit ROCm container guidance.", suggested_fix="Run vLLM inside vllm/vllm-openai-rocm with /dev/kfd, /dev/dri, host IPC, and video group access.", ) ) if "sglang.launch_server" in lower: findings.append( Finding( id=f"sglang-rocm-runbook-{line_number}", category="serving", severity="low", path=relative_path, line=line_number, message="SGLang launch command found without explicit ROCm deployment guidance.", suggested_fix="Document ROCm-compatible serving image, AMD GPU device mounts, and fallback vLLM command.", ) ) return findings def _contains_cuda_kernel_api(line: str) -> bool: return any(token in line for token in ("__global__", "cudaMalloc", "cudaMemcpy", "cudaFree"))