Files
massive-cloud/server/deviationGpuCompute.ts

122 lines
3.5 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', 'deviation_heatmap_gpu.py');
}
function resolvePython3(): string {
const fromEnv = process.env.PYTHON3_PATH?.trim();
if (fromEnv) return fromEnv;
for (const p of ['/usr/bin/python3', '/usr/local/bin/python3']) {
try {
if (fs.existsSync(p)) return p;
} catch {
/* ignore */
}
}
return 'python3';
}
export function deviationGpuEnabled(): boolean {
const v = process.env.ASSEMBLY_USE_GPU?.trim().toLowerCase();
return v === '1' || v === 'true' || v === 'yes';
}
export type GpuDeviationResult = {
colors: Float32Array;
vertexCount: number;
dispMin: number;
dispMax: number;
};
/**
* 调用 scripts/deviation_heatmap_gpu.py(PyTorch CUDA KNN)。
* 返回 null 时由调用方回退 CPU。
*/
export function tryRunDeviationGpu(
cadFilePath: string,
sampled: SampledPoint[],
): GpuDeviationResult | null {
if (!deviationGpuEnabled()) 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(), 'dev-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, '--cad', cadFilePath, '--points', ptsFile, '--out-dir', tmp],
{
encoding: 'utf-8',
maxBuffer: 64 * 1024 * 1024,
timeout: 30 * 60 * 1000,
env: { ...process.env, PYTHONUNBUFFERED: '1' },
},
);
if (r.error) {
console.warn('[DeviationGPU]', r.error.message);
return null;
}
if (r.status === 2) {
console.warn('[DeviationGPU] 回退 CPU:', (r.stdout || '').trim().slice(-500) || 'exit 2');
return null;
}
if (r.status !== 0) {
console.warn('[DeviationGPU] 失败 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;
vertexCount?: number;
dispMin?: number;
dispMax?: number;
};
if (!meta.ok || !meta.vertexCount) return null;
const colorsPath = path.join(tmp, 'colors.bin');
if (!fs.existsSync(colorsPath)) return null;
const cBuf = fs.readFileSync(colorsPath);
const expectedBytes = meta.vertexCount * 3 * 4;
if (cBuf.length !== expectedBytes) {
console.warn('[DeviationGPU] colors.bin 长度异常', cBuf.length, '!=', expectedBytes);
return null;
}
const colors = new Float32Array(cBuf.buffer, cBuf.byteOffset, meta.vertexCount * 3);
return {
colors,
vertexCount: meta.vertexCount,
dispMin: meta.dispMin ?? 0,
dispMax: meta.dispMax ?? 1,
};
} finally {
try {
fs.rmSync(tmp, { recursive: true, force: true });
} catch {
/* ignore */
}
}
}