Files
massive-cloud/server/assemblyGpuNearest.ts

116 lines
3.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { spawnSync } from 'node:child_process';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { fileURLToPath } from 'node:url';
import type { SampledPoint } from './pointcloudSample.js';
function scriptPath(): string {
const here = path.dirname(fileURLToPath(import.meta.url));
return path.join(here, '..', 'scripts', 'assembly_nearest_gpu.py');
}
function resolvePython3(): string {
const fromEnv = process.env.PYTHON3_PATH?.trim();
if (fromEnv) return fromEnv;
if (process.platform === 'win32') return 'python3';
for (const p of ['/usr/bin/python3', '/usr/local/bin/python3']) {
try {
if (fs.existsSync(p)) return p;
} catch {
/* ignore */
}
}
return 'python3';
}
/** 环境变量 ASSEMBLY_USE_GPU=1 / true 时尝试 GPU(失败则由调用方回退 CPU) */
export function assemblyGpuNearestEnabled(): boolean {
const v = process.env.ASSEMBLY_USE_GPU?.trim().toLowerCase();
return v === '1' || v === 'true' || v === 'yes';
}
export type GpuNearestNumbers = {
assignments: number[];
pointDistances: number[];
};
/**
* 调用 scripts/assembly_nearest_gpu.py(PyTorch3D CUDA)。
* 返回 null:未启用、脚本缺失、无 CUDA、导入失败(exit 2)或其它错误。
*/
export function tryRunAssemblyNearestGpu(manifestDir: string, sampled: SampledPoint[]): GpuNearestNumbers | null {
if (!assemblyGpuNearestEnabled()) return null;
const script = scriptPath();
if (!fs.existsSync(script)) return null;
if (sampled.length === 0) return null;
const py = resolvePython3();
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'asm-gpu-'));
const ptsFile = path.join(tmp, 'points.bin');
const buf = Buffer.allocUnsafe(sampled.length * 12);
for (let i = 0; i < sampled.length; i++) {
const o = i * 12;
buf.writeFloatLE(sampled[i]!.x, o);
buf.writeFloatLE(sampled[i]!.y, o + 4);
buf.writeFloatLE(sampled[i]!.z, o + 8);
}
fs.writeFileSync(ptsFile, buf);
try {
const r = spawnSync(
py,
[script, '--points', ptsFile, '--manifest-dir', manifestDir, '--out-dir', tmp],
{
encoding: 'utf-8',
maxBuffer: 64 * 1024 * 1024,
env: { ...process.env, PYTHONUNBUFFERED: '1' },
},
);
if (r.error) {
console.warn('[AssemblyGPU]', r.error.message);
return null;
}
if (r.status === 2) {
console.warn('[AssemblyGPU] 回退 CPU:', (r.stdout || '').trim().slice(-500) || 'exit 2');
return null;
}
if (r.status !== 0) {
console.warn('[AssemblyGPU] 失败 status=', r.status, (r.stderr || r.stdout || '').slice(0, 800));
return null;
}
const resultJsonPath = path.join(tmp, 'result.json');
if (!fs.existsSync(resultJsonPath)) return null;
const meta = JSON.parse(fs.readFileSync(resultJsonPath, 'utf-8')) as {
ok?: boolean;
n?: number;
assignmentsFile?: string;
distancesFile?: string;
};
if (!meta.ok || !meta.assignmentsFile || !meta.distancesFile) return null;
if (meta.n !== sampled.length) {
console.warn('[AssemblyGPU] 点数不一致', meta.n, sampled.length);
return null;
}
const aBuf = fs.readFileSync(meta.assignmentsFile);
const dBuf = fs.readFileSync(meta.distancesFile);
const n = sampled.length;
if (aBuf.length !== n * 4 || dBuf.length !== n * 4) {
console.warn('[AssemblyGPU] 二进制长度异常');
return null;
}
const assignments = Array.from(new Int32Array(aBuf.buffer, aBuf.byteOffset, n));
const pointDistances = Array.from(new Float32Array(dBuf.buffer, dBuf.byteOffset, n));
return { assignments, pointDistances };
} finally {
try {
fs.rmSync(tmp, { recursive: true, force: true });
} catch {
/* ignore */
}
}
}