605 lines · 20.9 KB · TypeScript
RawDownload
import { randomUUID } from "node:crypto";import { chmod, link, mkdir, readFile, rmdir, stat, unlink, writeFile } from "node:fs/promises";import { tmpdir } from "node:os";import { join } from "node:path";import { Effect, Schema, Semaphore } from "effect";import { getExtraArgument } from "../engines/argument-utilities";import type { GpuInfo, Recipe } from "../models/types"; const fullNvidiaUuid =  /^GPU-[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;const directVisibilityKeys = [  "visible_devices",  "VISIBLE_DEVICES",  "CUDA_VISIBLE_DEVICES",  "cuda_visible_devices",  "cuda-visible-devices",] as const; export type GpuLeaseOwner = "llm" | "speech"; export interface GpuLease {  readonly uuid: string;  readonly owner: GpuLeaseOwner;} export interface GpuVisibilityResolution {  readonly source: "all" | "recipe";  readonly selector: string | null;  readonly uuids: readonly string[];  readonly unresolvedTokens: readonly string[];} export interface GpuLeaseConflictEntry {  readonly uuid: string;  readonly heldBy: GpuLeaseOwner;} export class GpuLeaseConflict extends Error {  readonly _tag = "GpuLeaseConflict";   constructor(    readonly requestedBy: GpuLeaseOwner,    readonly conflicts: readonly GpuLeaseConflictEntry[],  ) {    super(      `GPU lease conflict for ${requestedBy}: ${conflicts        .map(({ uuid, heldBy }) => `${uuid} held by ${heldBy}`)        .join(", ")}`,    );    this.name = "GpuLeaseConflict";  }} export class InvalidGpuLeaseUuid extends Error {  readonly _tag = "InvalidGpuLeaseUuid";   constructor(readonly invalidUuids: readonly string[]) {    super(`GPU leases require full NVIDIA UUIDs: ${invalidUuids.join(", ")}`);    this.name = "InvalidGpuLeaseUuid";  }} export class GpuLeaseLockFailure extends Error {  readonly _tag = "GpuLeaseLockFailure";   constructor(    readonly operation: "acquire" | "release",    cause: unknown,  ) {    super(`Unable to ${operation} the host GPU lease`, { cause });    this.name = "GpuLeaseLockFailure";  }} export interface GpuLeaseRegistryOptions {  readonly lockDirectory?: string;} type GpuLeaseError = GpuLeaseConflict | GpuLeaseLockFailure | InvalidGpuLeaseUuid; export interface GpuLeaseRegistry {  readonly claim: (    owner: GpuLeaseOwner,    uuids: readonly string[],  ) => Effect.Effect<readonly GpuLease[], GpuLeaseError>;  readonly replace: (    owner: GpuLeaseOwner,    uuids: readonly string[],  ) => Effect.Effect<readonly GpuLease[], GpuLeaseError>;  readonly release: (    owner: GpuLeaseOwner,    uuids?: readonly string[],  ) => Effect.Effect<readonly GpuLease[], GpuLeaseLockFailure | InvalidGpuLeaseUuid>;  readonly snapshot: () => Effect.Effect<readonly GpuLease[]>;} const HostGpuLeaseRecordSchema = Schema.Struct({  version: Schema.Literal(1),  uuid: Schema.String,  owner: Schema.Literals(["llm", "speech"]),  pid: Schema.Number,  processStartToken: Schema.Union([Schema.String, Schema.Null]),  registryId: Schema.String,}); interface HostGpuLeaseRecord {  readonly version: 1;  readonly uuid: string;  readonly owner: GpuLeaseOwner;  readonly pid: number;  readonly processStartToken: string | null;  readonly registryId: string;} type HostLockRead =  | { readonly status: "found"; readonly record: HostGpuLeaseRecord }  | { readonly status: "invalid" }  | { readonly status: "missing" }; type HostLockClaim =  | { readonly status: "acquired" }  | { readonly status: "owned" }  | { readonly status: "conflict"; readonly heldBy: GpuLeaseOwner }; interface HostGpuLockStore {  readonly acquire: (uuid: string, owner: GpuLeaseOwner) => Effect.Effect<HostLockClaim, unknown>;  readonly release: (uuid: string) => Effect.Effect<void, unknown>;} type LinuxProcessStart =  | { readonly status: "found"; readonly token: string }  | { readonly status: "missing" }  | { readonly status: "unknown" }; const hostLockAttempts = 128;const staleReaperAgeMs = 5_000; function hasErrorCode(error: unknown): error is Error & { code: string } {  return error instanceof Error && "code" in error && typeof error.code === "string";} function linuxStartToken(stat: string): string | null {  const commandEnd = stat.lastIndexOf(")");  if (commandEnd < 0) return null;  const token = stat    .slice(commandEnd + 1)    .trim()    .split(/\s+/)[19];  return token && /^\d+$/.test(token) ? token : null;} function readLinuxProcessStart(pid: number): Effect.Effect<LinuxProcessStart> {  return Effect.tryPromise({    try: () => readFile(`/proc/${pid}/stat`, "utf8"),    catch: (error) => error,  }).pipe(    Effect.map((contents): LinuxProcessStart => {      const token = linuxStartToken(contents);      return token ? { status: "found", token } : { status: "unknown" };    }),    Effect.catch((error) =>      Effect.succeed(        hasErrorCode(error) && error.code === "ENOENT"          ? ({ status: "missing" } as const)          : ({ status: "unknown" } as const),      ),    ),  );} function processIsAlive(pid: number): boolean {  try {    process.kill(pid, 0);    return true;  } catch (error) {    return !hasErrorCode(error) || error.code !== "ESRCH";  }} function hostRecordIsLive(record: HostGpuLeaseRecord): Effect.Effect<boolean> {  if (!Number.isSafeInteger(record.pid) || record.pid <= 0) return Effect.succeed(false);  if (process.platform !== "linux") return Effect.sync(() => processIsAlive(record.pid));  if (record.processStartToken === null) return Effect.succeed(false);  return readLinuxProcessStart(record.pid).pipe(    Effect.map(      (current) =>        current.status !== "missing" &&        (current.status === "unknown" || current.token === record.processStartToken),    ),  );} function currentProcessStartToken(): Effect.Effect<string | null, Error> {  if (process.platform !== "linux") return Effect.succeed(null);  return readLinuxProcessStart(process.pid).pipe(    Effect.flatMap((current) =>      current.status === "found"        ? Effect.succeed(current.token)        : Effect.fail(new Error("Unable to read the controller process identity")),    ),  );} function validHostRecord(value: unknown): HostGpuLeaseRecord | null {  try {    const record = Schema.decodeUnknownSync(HostGpuLeaseRecordSchema)(value);    if (!Number.isSafeInteger(record.pid) || record.pid <= 0 || !record.registryId) return null;    if (!fullNvidiaUuid.test(record.uuid)) return null;    return record;  } catch {    return null;  }} function readHostLock(path: string): Effect.Effect<HostLockRead, unknown> {  return Effect.tryPromise({ try: () => readFile(path, "utf8"), catch: (error) => error }).pipe(    Effect.flatMap((contents) =>      Effect.try({ try: () => JSON.parse(contents) as unknown, catch: (error) => error }),    ),    Effect.map((value): HostLockRead => {      const record = validHostRecord(value);      return record ? { status: "found", record } : { status: "invalid" };    }),    Effect.catch((error): Effect.Effect<HostLockRead, unknown> => {      if (hasErrorCode(error) && error.code === "ENOENT") {        return Effect.succeed({ status: "missing" } as const);      }      if (error instanceof SyntaxError) return Effect.succeed({ status: "invalid" } as const);      return Effect.fail(error);    }),  );} function removeIfPresent(path: string): Effect.Effect<void, unknown> {  return Effect.tryPromise({ try: () => unlink(path), catch: (error) => error }).pipe(    Effect.catch((error) =>      hasErrorCode(error) && error.code === "ENOENT" ? Effect.void : Effect.fail(error),    ),  );} function releaseReaper(path: string): Effect.Effect<void, unknown> {  return Effect.tryPromise({ try: () => rmdir(path), catch: (error) => error }).pipe(    Effect.catch((error) =>      hasErrorCode(error) && error.code === "ENOENT" ? Effect.void : Effect.fail(error),    ),  );} function withCleanup<A, E, R, CleanupError, CleanupRequirements>(  effect: Effect.Effect<A, E, R>,  cleanup: Effect.Effect<void, CleanupError, CleanupRequirements>,): Effect.Effect<A, E | CleanupError, R | CleanupRequirements> {  return Effect.uninterruptibleMask((restore) =>    Effect.exit(restore(effect)).pipe(      Effect.flatMap((exit) => cleanup.pipe(Effect.andThen(exit))),    ),  );} function staleReaper(path: string): Effect.Effect<boolean, unknown> {  return Effect.tryPromise({ try: () => stat(path), catch: (error) => error }).pipe(    Effect.map((metadata) => Date.now() - metadata.mtimeMs >= staleReaperAgeMs),    Effect.catch((error) =>      hasErrorCode(error) && error.code === "ENOENT" ? Effect.succeed(false) : Effect.fail(error),    ),  );} function reclaimStaleHostLock(path: string): Effect.Effect<void, unknown> {  const reaperPath = `${path}.reaper`;  return Effect.gen(function* () {    const claimed = yield* Effect.tryPromise({      try: () => mkdir(reaperPath, { mode: 0o700 }),      catch: (error) => error,    }).pipe(      Effect.as(true),      Effect.catch((error) =>        hasErrorCode(error) && error.code === "EEXIST" ? Effect.succeed(false) : Effect.fail(error),      ),    );    if (!claimed) {      if (yield* staleReaper(reaperPath)) yield* releaseReaper(reaperPath);      else yield* Effect.sleep(5);      return;    }    yield* withCleanup(      Effect.gen(function* () {        const current = yield* readHostLock(path);        if (current.status === "invalid") {          return yield* Effect.fail(new Error("Host GPU lease record is invalid"));        }        if (current.status === "found" && !(yield* hostRecordIsLive(current.record))) {          yield* removeIfPresent(path);        }      }),      releaseReaper(reaperPath),    );  });} function hostLockPath(directory: string, uuid: string): string {  return join(directory, `${uuid.toLowerCase()}.lock`);} function createHostGpuLockStore(directory: string): HostGpuLockStore {  const registryId = randomUUID();  const ensureDirectory = Effect.tryPromise({    try: () => mkdir(directory, { recursive: true, mode: 0o700 }),    catch: (error) => error,  }).pipe(    Effect.andThen(      Effect.tryPromise({ try: () => chmod(directory, 0o700), catch: (error) => error }),    ),    Effect.asVoid,  );  const acquire = (uuid: string, owner: GpuLeaseOwner): Effect.Effect<HostLockClaim, unknown> =>    Effect.gen(function* () {      yield* ensureDirectory;      const path = hostLockPath(directory, uuid);      const temporaryPath = join(directory, `.${registryId}-${randomUUID()}.lock`);      const record = {        version: 1,        uuid,        owner,        pid: process.pid,        processStartToken: yield* currentProcessStartToken(),        registryId,      } satisfies HostGpuLeaseRecord;      yield* Effect.tryPromise({        try: () => writeFile(temporaryPath, JSON.stringify(record), { flag: "wx", mode: 0o600 }),        catch: (error) => error,      });      return yield* withCleanup(        Effect.gen(function* () {          for (let attempt = 0; attempt < hostLockAttempts; attempt += 1) {            const linked = yield* Effect.tryPromise({              try: () => link(temporaryPath, path),              catch: (error) => error,            }).pipe(              Effect.as(true),              Effect.catch((error) =>                hasErrorCode(error) && error.code === "EEXIST"                  ? Effect.succeed(false)                  : Effect.fail(error),              ),            );            if (linked) return { status: "acquired" } as const;            const current = yield* readHostLock(path);            if (current.status === "missing") continue;            if (current.status === "found") {              if (current.record.registryId === registryId) {                return current.record.owner === owner                  ? ({ status: "owned" } as const)                  : ({ status: "conflict", heldBy: current.record.owner } as const);              }              if (yield* hostRecordIsLive(current.record)) {                return { status: "conflict", heldBy: current.record.owner } as const;              }            }            yield* reclaimStaleHostLock(path);          }          return yield* Effect.fail(new Error(`Unable to settle host GPU lease ${uuid}`));        }),        removeIfPresent(temporaryPath),      );    });  const release = (uuid: string): Effect.Effect<void, unknown> =>    Effect.gen(function* () {      const path = hostLockPath(directory, uuid);      const current = yield* readHostLock(path);      if (current.status === "found" && current.record.registryId === registryId) {        yield* removeIfPresent(path);      }    });  return { acquire, release };} export function perUserGpuLeaseLockDirectory(): string {  const user = typeof process.getuid === "function" ? process.getuid() : "user";  return join(tmpdir(), `local-studio-${user}`, "gpu-leases");} function isUnknownRecord(value: unknown): value is Record<string, unknown> {  return typeof value === "object" && value !== null;} function directVisibilitySelector(recipe: Recipe): string | null {  for (const key of directVisibilityKeys) {    const value = getExtraArgument(recipe.extra_args, key);    if (value === undefined || value === null) continue;    return value === false ? null : String(value);  }  return null;} function environmentVisibilitySelector(recipe: Recipe): string | null {  let selector = recipe.env_vars?.["CUDA_VISIBLE_DEVICES"] ?? null;  const extraEnvironment =    getExtraArgument(recipe.extra_args, "env_vars") ?? recipe.extra_args["envVars"];  if (!isUnknownRecord(extraEnvironment)) return selector;  const value = extraEnvironment["CUDA_VISIBLE_DEVICES"];  if (value !== undefined && value !== null) selector = String(value);  return selector;} function recipeVisibilitySelector(recipe: Recipe): string | null {  return directVisibilitySelector(recipe) ?? environmentVisibilitySelector(recipe);} function canonicalNvidiaUuid(uuid: string): string {  return `GPU-${uuid.slice(4).toLowerCase()}`;} function leaseableUuid(gpu: GpuInfo): string | null {  const uuid = gpu.uuid?.trim();  return uuid && fullNvidiaUuid.test(uuid) ? canonicalNvidiaUuid(uuid) : null;} function appendUnique(values: string[], value: string): void {  if (!values.includes(value)) values.push(value);} export function resolveRecipeGpuUuids(  recipe: Recipe,  gpus: readonly GpuInfo[],): GpuVisibilityResolution {  const byIndex = new Map<number, string>();  const byUuid = new Map<string, string>();  const allUuids: string[] = [];  for (const gpu of gpus) {    const uuid = leaseableUuid(gpu);    if (!uuid) continue;    if (!byIndex.has(gpu.index)) byIndex.set(gpu.index, uuid);    byUuid.set(uuid.toLowerCase(), uuid);    appendUnique(allUuids, uuid);  }   const selector = recipeVisibilitySelector(recipe);  if (selector === null) {    return { source: "all", selector, uuids: allUuids, unresolvedTokens: [] };  }   const uuids: string[] = [];  const unresolvedTokens: string[] = [];  const tokens = selector    .split(",")    .map((token) => token.trim())    .filter(Boolean);  for (const token of tokens) {    const uuid = /^\d+$/.test(token) ? byIndex.get(Number(token)) : byUuid.get(token.toLowerCase());    if (uuid) appendUnique(uuids, uuid);    else appendUnique(unresolvedTokens, token);  }  return { source: "recipe", selector, uuids, unresolvedTokens };} function uniqueUuids(uuids: readonly string[]): string[] {  return [...new Set(uuids)];} function invalidUuidRequest(uuids: readonly string[]): InvalidGpuLeaseUuid | null {  const invalidUuids = uniqueUuids(uuids).filter((uuid) => !fullNvidiaUuid.test(uuid));  return invalidUuids.length > 0 ? new InvalidGpuLeaseUuid(invalidUuids) : null;} function leaseSnapshot(leases: ReadonlyMap<string, GpuLeaseOwner>): readonly GpuLease[] {  return [...leases]    .map(([uuid, owner]) => ({ uuid, owner }))    .sort((left, right) => left.uuid.localeCompare(right.uuid));} function conflictingLeases(  leases: ReadonlyMap<string, GpuLeaseOwner>,  owner: GpuLeaseOwner,  uuids: readonly string[],): GpuLeaseConflictEntry[] {  const conflicts: GpuLeaseConflictEntry[] = [];  for (const uuid of uuids) {    const heldBy = leases.get(uuid);    if (heldBy && heldBy !== owner) conflicts.push({ uuid, heldBy });  }  return conflicts;} function releaseOwnerLeases(  leases: Map<string, GpuLeaseOwner>,  owner: GpuLeaseOwner,  uuids?: readonly string[],): void {  for (const [uuid, heldBy] of leases) {    if (heldBy === owner && (!uuids || uuids.includes(uuid))) leases.delete(uuid);  }} export function createGpuLeaseRegistry(options: GpuLeaseRegistryOptions = {}): GpuLeaseRegistry {  const leases = new Map<string, GpuLeaseOwner>();  const semaphore = Semaphore.makeUnsafe(1);  const hostLocks = options.lockDirectory ? createHostGpuLockStore(options.lockDirectory) : null;  const acquireHostLeases = (

Lines 1–500 of 605.