Phase 10: iGPU passthrough — render/video group GID detection (0.10.0)

- gpu_service: detect host render/video group GIDs from /dev/dri node ownership
  (render node → render GID, paired card node → video GID); added to GPUInfo +
  exposed via /api/system/gpus. inject_dri now emits numeric group_add entries
  (e.g. ["991","44"]) when GIDs are known, falling back to names otherwise;
  remove_gpu strips those GIDs + LIBVA_DRIVER_NAME; dri_group_gids() for cleanup.
- editor set-gpu passes render_gid/video_gid through; GPUSelector shows detected
  GIDs, defaults video group on, and sends them.

Verified: py_compile, unit check (inject→["991","44"] then clean removal),
frontend tsc build, image imports. Live iGPU verify is on the user's hardware.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
menzelj
2026-06-07 22:51:18 +00:00
co-authored by Claude Opus 4.8
parent a4e26f880a
commit ec7e3e706f
8 changed files with 126 additions and 16 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "stackpilot-frontend",
"private": true,
"version": "0.9.0",
"version": "0.10.0",
"type": "module",
"scripts": {
"dev": "vite",
+20 -3
View File
@@ -23,11 +23,15 @@ export function GPUSelector({
// dri
const [vendor, setVendor] = useState<"intel" | "amd">("intel");
const [renderGroup, setRenderGroup] = useState(true);
const [videoGroup, setVideoGroup] = useState(false);
const [videoGroup, setVideoGroup] = useState(true);
const [libva, setLibva] = useState(false);
const nvidia = (gpus ?? []).filter((g) => g.vendor === "nvidia");
const dri = (gpus ?? []).filter((g) => g.vendor !== "nvidia");
// GIDs come from the matching detected GPU (host /dev/dri ownership).
const driGpu = dri.find((g) => g.vendor === vendor) ?? dri[0];
const renderGid = driGpu?.render_gid ?? null;
const videoGid = driGpu?.video_gid ?? null;
const toggleCap = (c: string) =>
setCaps((prev) => (prev.includes(c) ? prev.filter((x) => x !== c) : [...prev, c]));
@@ -46,6 +50,8 @@ export function GPUSelector({
add_render_group: renderGroup,
add_video_group: videoGroup,
set_libva: libva,
render_gid: renderGid,
video_gid: videoGid,
});
};
@@ -134,11 +140,22 @@ export function GPUSelector({
{dri.map((g) => `${g.name}${g.device_path}`).join(", ")}
</p>
)}
{(renderGid != null || videoGid != null) ? (
<p className="text-xs text-slate-500">
Detected host GIDs render: <span className="font-mono">{renderGid ?? "?"}</span>, video:{" "}
<span className="font-mono">{videoGid ?? "?"}</span>. These are added as numeric{" "}
<code>group_add</code> entries.
</p>
) : (
<p className="text-xs text-slate-400">
No iGPU detected render/video groups will be added by name.
</p>
)}
<label className="flex items-center gap-2 text-sm">
<input type="checkbox" checked={renderGroup} onChange={(e) => setRenderGroup(e.target.checked)} /> add render group
<input type="checkbox" checked={renderGroup} onChange={(e) => setRenderGroup(e.target.checked)} /> add render group{renderGid != null ? ` (${renderGid})` : ""}
</label>
<label className="flex items-center gap-2 text-sm">
<input type="checkbox" checked={videoGroup} onChange={(e) => setVideoGroup(e.target.checked)} /> add video group
<input type="checkbox" checked={videoGroup} onChange={(e) => setVideoGroup(e.target.checked)} /> add video group{videoGid != null ? ` (${videoGid})` : ""}
</label>
{vendor === "intel" && (
<label className="flex items-center gap-2 text-sm">
+2
View File
@@ -93,6 +93,8 @@ export interface GPUInfo {
device_path?: string | null;
driver: string;
vram_mb?: number | null;
render_gid?: number | null;
video_gid?: number | null;
}
export interface HostDevice {