mirror of
https://github.com/morluto/rea.git
synced 2026-11-06 14:56:32 +00:00
feat(gpu): inspect static CUDA and HIP code objects (#5813)
This commit is contained in:
1 parent
647717c050
commit
efab044f30
81 files changed
+8598
-251
No files matched your search
@@ -0,0 +1,421 @@
|
||||
import { isImmutableJsonSnapshot } from "../immutableJson.js";
|
||||
import { digestSchema } from "../digests.js";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
gpuCodeObjectsSchema,
|
||||
gpuCodeObjectsObjectSchema,
|
||||
gpuCodeObjectsSelectionSchema,
|
||||
gpuObjectSchema,
|
||||
gpuKernelSchema,
|
||||
gpuTargetSchema,
|
||||
type GpuTarget,
|
||||
type GpuCodeObjects,
|
||||
} from "../gpu/gpuCodeObjects.js";
|
||||
import type { AnalysisError } from "../analysisErrorBase.js";
|
||||
import type {
|
||||
AnalysisViewParent,
|
||||
AnalysisViewRequest,
|
||||
UnsignedAnalysisView,
|
||||
} from "./analysisView.js";
|
||||
import { analysisViewInputError } from "./analysisViewErrors.js";
|
||||
import { parseAnalysisViewResult } from "./analysisViewParent.js";
|
||||
import {
|
||||
boundedViewMessages,
|
||||
completeWithinViewCoverage,
|
||||
pageViewCoverage,
|
||||
} from "./analysisViewCoverage.js";
|
||||
import { err, ok, type Result } from "../result.js";
|
||||
import type { JsonValue } from "../jsonValue.js";
|
||||
import { jsonValueByteLength } from "../jsonValueByteLength.js";
|
||||
import { AnalysisResourceConstraintError } from "../analysisErrorCore.js";
|
||||
export const gpuCodeObjectsViewRequestSchema = z.discriminatedUnion(
|
||||
"collection",
|
||||
[
|
||||
gpuCodeObjectsSelectionSchema.options[0].extend({
|
||||
kind: z.literal("gpu-code-objects"),
|
||||
}),
|
||||
gpuCodeObjectsSelectionSchema.options[1].extend({
|
||||
kind: z.literal("gpu-code-objects"),
|
||||
}),
|
||||
gpuCodeObjectsSelectionSchema.options[2].extend({
|
||||
kind: z.literal("gpu-code-objects"),
|
||||
}),
|
||||
gpuCodeObjectsSelectionSchema.options[3].extend({
|
||||
kind: z.literal("gpu-code-objects"),
|
||||
}),
|
||||
],
|
||||
);
|
||||
export const gpuCodeObjectsSummarySchema = gpuCodeObjectsObjectSchema
|
||||
.omit({ objects: true, kernels: true, ptx_artifacts: true })
|
||||
.extend({
|
||||
counts: z.strictObject({
|
||||
objects: z.number().int().nonnegative(),
|
||||
kernels: z.number().int().nonnegative(),
|
||||
ptx_artifacts: z.number().int().nonnegative(),
|
||||
targets: z.number().int().nonnegative(),
|
||||
}),
|
||||
target_preview: z.array(z.string()),
|
||||
});
|
||||
export const gpuCodeObjectsItemsSchema = z.union([
|
||||
z.array(
|
||||
gpuObjectSchema.partial().required({
|
||||
id: true,
|
||||
kind: true,
|
||||
target: true,
|
||||
location: true,
|
||||
metadata_location: true,
|
||||
payload_target: true,
|
||||
target_consistency: true,
|
||||
inspection: true,
|
||||
}),
|
||||
),
|
||||
z.array(
|
||||
gpuKernelSchema.partial().required({
|
||||
id: true,
|
||||
object_id: true,
|
||||
location: true,
|
||||
sources: true,
|
||||
representation: true,
|
||||
}),
|
||||
),
|
||||
z.array(
|
||||
gpuTargetSchema.partial().required({
|
||||
id: true,
|
||||
target: true,
|
||||
confidence: true,
|
||||
kernel_coverage: true,
|
||||
}),
|
||||
),
|
||||
]);
|
||||
export const gpuCodeObjectsPtxPageSchema = z.strictObject({
|
||||
artifact_id: z.string(),
|
||||
object_id: z.string(),
|
||||
sha256: digestSchema,
|
||||
byte_length: z.number().int().nonnegative(),
|
||||
text: z.string(),
|
||||
offset: z.number().int().nonnegative(),
|
||||
offset_unit: z.literal("utf16-code-unit"),
|
||||
});
|
||||
const summarizeGpuTargets = (report: GpuCodeObjects): GpuTarget[] => {
|
||||
const groups = new Map<
|
||||
string | null,
|
||||
{ row: GpuTarget; encoded: bigint; decoded: bigint | null }
|
||||
>();
|
||||
const kernelCounts = new Map<string, number>();
|
||||
for (const kernel of report.kernels)
|
||||
kernelCounts.set(
|
||||
kernel.object_id,
|
||||
(kernelCounts.get(kernel.object_id) ?? 0) + 1,
|
||||
);
|
||||
for (const object of report.objects) {
|
||||
if (
|
||||
object.kind === "cuda-fatbin" ||
|
||||
object.kind === "hip-bundle" ||
|
||||
object.kind === "native-section" ||
|
||||
object.kind === "host"
|
||||
)
|
||||
continue;
|
||||
let group = groups.get(object.target);
|
||||
if (group === undefined) {
|
||||
group = {
|
||||
encoded: 0n,
|
||||
decoded: 0n,
|
||||
row: {
|
||||
id: `target:${JSON.stringify(object.target)}`,
|
||||
target: object.target,
|
||||
confidence: "derived",
|
||||
object_count: 0,
|
||||
kernel_count: 0,
|
||||
encoded_bytes: "0",
|
||||
decoded_bytes: "0",
|
||||
kernel_coverage:
|
||||
report.analysis === "inventory" ? "not-requested" : "complete",
|
||||
by_kind: { ptx: 0, cubin: 0, amdgpu: 0, unknown: 0 },
|
||||
},
|
||||
};
|
||||
groups.set(object.target, group);
|
||||
}
|
||||
group.row.object_count++;
|
||||
group.row.by_kind[object.kind]++;
|
||||
group.row.kernel_count += kernelCounts.get(object.id) ?? 0;
|
||||
group.encoded += BigInt(object.encoded_size);
|
||||
group.decoded =
|
||||
group.decoded === null || object.decoded_size === null
|
||||
? null
|
||||
: group.decoded + BigInt(object.decoded_size);
|
||||
if (report.analysis === "kernels" && object.inspection !== "complete")
|
||||
group.row.kernel_coverage = "partial";
|
||||
}
|
||||
return [...groups.values()].map(({ row, encoded, decoded }) => ({
|
||||
...row,
|
||||
encoded_bytes: encoded.toString(),
|
||||
decoded_bytes: decoded?.toString() ?? null,
|
||||
}));
|
||||
};
|
||||
type CheckedGpu = {
|
||||
report: z.infer<typeof gpuCodeObjectsSchema>;
|
||||
byId: Map<string, z.infer<typeof gpuObjectSchema>>;
|
||||
summary: z.infer<typeof gpuCodeObjectsSummarySchema>;
|
||||
targets: GpuTarget[];
|
||||
};
|
||||
// Evidence owns this immutable snapshot. Collection eviction releases the indexes as well.
|
||||
const checkedGpu = new WeakMap<object, CheckedGpu>();
|
||||
const readGpuFacts = (
|
||||
parent: AnalysisViewParent,
|
||||
): Result<CheckedGpu, AnalysisError> => {
|
||||
const key = parent.normalizedResult;
|
||||
const reusable =
|
||||
parent.sealedResult === true &&
|
||||
key !== null &&
|
||||
typeof key === "object" &&
|
||||
isImmutableJsonSnapshot(key);
|
||||
let checked = reusable ? checkedGpu.get(key) : undefined;
|
||||
if (checked === undefined) {
|
||||
const parsed = parseAnalysisViewResult(gpuCodeObjectsSchema, parent);
|
||||
if (!parsed.ok) return parsed;
|
||||
const report = reusable
|
||||
? (key as z.infer<typeof gpuCodeObjectsSchema>)
|
||||
: parsed.value;
|
||||
const { objects, kernels, ptx_artifacts, ...context } = report;
|
||||
const targets = new Set(
|
||||
objects.flatMap((row) => (row.target === null ? [] : [row.target])),
|
||||
);
|
||||
checked = {
|
||||
report,
|
||||
targets: summarizeGpuTargets(report),
|
||||
byId: new Map(objects.map((row) => [row.id, row])),
|
||||
summary: {
|
||||
...context,
|
||||
counts: {
|
||||
objects: objects.length,
|
||||
kernels: kernels.length,
|
||||
ptx_artifacts: ptx_artifacts.length,
|
||||
targets: targets.size,
|
||||
},
|
||||
target_preview: [...targets]
|
||||
.slice(0, 8)
|
||||
.map((target) => target.slice(0, 256)),
|
||||
},
|
||||
};
|
||||
if (reusable) checkedGpu.set(key, checked);
|
||||
}
|
||||
if (
|
||||
parent.subject != null &&
|
||||
(parent.subject.local_path !== checked.report.artifact.path ||
|
||||
parent.subject.digest.sha256 !== checked.report.artifact.sha256)
|
||||
)
|
||||
return err(
|
||||
analysisViewInputError([
|
||||
{
|
||||
path: ["source", "subject"],
|
||||
reason: "invalid_value",
|
||||
message: "GPU Evidence subject does not match its recorded artifact",
|
||||
},
|
||||
]),
|
||||
);
|
||||
return ok(checked);
|
||||
};
|
||||
export const projectGpuCodeObjectsView = (
|
||||
parent: AnalysisViewParent,
|
||||
view: AnalysisViewRequest,
|
||||
): Result<UnsignedAnalysisView, AnalysisError> => {
|
||||
if (view.kind !== "summary" && view.kind !== "gpu-code-objects")
|
||||
return err(
|
||||
analysisViewInputError([
|
||||
{
|
||||
path: ["view"],
|
||||
reason: "invalid_value",
|
||||
message: "GPU Evidence supports summary and gpu-code-objects pages",
|
||||
},
|
||||
]),
|
||||
);
|
||||
const parsed = readGpuFacts(parent);
|
||||
if (!parsed.ok) return parsed;
|
||||
const { report, byId, summary, targets: targetRows } = parsed.value;
|
||||
const shared = {
|
||||
parent_evidence_id: parent.evidenceId,
|
||||
parent_operation: "inspect_gpu_code_objects" as const,
|
||||
parent_digest: parent.evidenceId.slice(3),
|
||||
artifact: { path: report.artifact.path, sha256: report.artifact.sha256 },
|
||||
limitations: boundedViewMessages(
|
||||
[...parent.limitations, ...report.limitations],
|
||||
parent.evidenceId,
|
||||
"limitations",
|
||||
),
|
||||
unknowns: [
|
||||
"GPU execution, runtime compatibility and host launch sites were not inspected.",
|
||||
...(report.coverage.payloads !== "complete"
|
||||
? [
|
||||
"Kernel facts are incomplete outside successfully decoded selected payloads; consult object inspection states.",
|
||||
]
|
||||
: []),
|
||||
],
|
||||
};
|
||||
if (view.kind === "summary") {
|
||||
return ok({
|
||||
kind: "summary",
|
||||
view,
|
||||
...shared,
|
||||
summary: { ...summary, limitations: shared.limitations },
|
||||
coverage: completeWithinViewCoverage(
|
||||
report.objects.length,
|
||||
report.objects.length,
|
||||
),
|
||||
});
|
||||
}
|
||||
if (view.collection === "ptx") {
|
||||
const artifact = report.ptx_artifacts.find(
|
||||
(row) => row.object_id === view.object_id,
|
||||
);
|
||||
if (artifact === undefined)
|
||||
return err(
|
||||
analysisViewInputError([
|
||||
{
|
||||
path: ["view", "object_id"],
|
||||
reason: "invalid_value",
|
||||
message:
|
||||
"No retained PTX artifact exists for this object; inspect a selected PTX payload with analysis kernels",
|
||||
},
|
||||
]),
|
||||
);
|
||||
const end = Math.min(artifact.text.length, view.offset + view.limit);
|
||||
const split = (offset: number): boolean =>
|
||||
offset > 0 &&
|
||||
offset < artifact.text.length &&
|
||||
/[\uD800-\uDBFF]/u.test(artifact.text[offset - 1]!) &&
|
||||
/[\uDC00-\uDFFF]/u.test(artifact.text[offset]!);
|
||||
if (split(view.offset) || split(end))
|
||||
return err(
|
||||
analysisViewInputError([
|
||||
{
|
||||
path: ["view"],
|
||||
reason: "invalid_value",
|
||||
message:
|
||||
"PTX text page must end and start on Unicode scalar boundaries; offsets use UTF-16 code units",
|
||||
},
|
||||
]),
|
||||
);
|
||||
const text = artifact.text.slice(view.offset, end);
|
||||
if (jsonValueByteLength(text, 1024 * 1024) > 1024 * 1024)
|
||||
return err(
|
||||
new AnalysisResourceConstraintError(
|
||||
"inspect_analysis_view",
|
||||
"memory",
|
||||
"PTX page exceeds the 1 MiB response budget; lower limit",
|
||||
{ maximum_page_bytes: 1024 * 1024 },
|
||||
),
|
||||
);
|
||||
return ok({
|
||||
kind: "gpu-code-objects",
|
||||
view,
|
||||
...shared,
|
||||
source_coverage: report.coverage,
|
||||
items: [],
|
||||
ptx: {
|
||||
artifact_id: artifact.id,
|
||||
object_id: artifact.object_id,
|
||||
sha256: artifact.sha256,
|
||||
byte_length: artifact.byte_length,
|
||||
text,
|
||||
offset: view.offset,
|
||||
offset_unit: "utf16-code-unit",
|
||||
},
|
||||
coverage: pageViewCoverage(
|
||||
view.offset,
|
||||
text.length,
|
||||
artifact.text.length,
|
||||
),
|
||||
});
|
||||
}
|
||||
const targets =
|
||||
view.targets === undefined ? undefined : new Set(view.targets);
|
||||
const ids = view.ids === undefined ? undefined : new Set(view.ids);
|
||||
const objectIds =
|
||||
view.collection === "kernels" && view.object_ids !== undefined
|
||||
? new Set(view.object_ids)
|
||||
: undefined;
|
||||
const names =
|
||||
view.collection === "kernels" && view.names !== undefined
|
||||
? new Set(view.names)
|
||||
: undefined;
|
||||
const rows =
|
||||
view.collection === "objects"
|
||||
? report.objects
|
||||
: view.collection === "targets"
|
||||
? targetRows
|
||||
: report.kernels;
|
||||
const matches = rows.filter(
|
||||
(row) =>
|
||||
(ids === undefined || ids.has(row.id)) &&
|
||||
(objectIds === undefined ||
|
||||
("object_id" in row && objectIds.has(row.object_id))) &&
|
||||
(names === undefined || ("name" in row && names.has(row.name))) &&
|
||||
(targets === undefined ||
|
||||
targets.has(
|
||||
("target" in row ? row.target : byId.get(row.object_id)?.target) ??
|
||||
"",
|
||||
)),
|
||||
);
|
||||
const defaults =
|
||||
view.collection === "kernels"
|
||||
? new Set([
|
||||
"id",
|
||||
"object_id",
|
||||
"name",
|
||||
"parameter_count",
|
||||
"parameter_bytes",
|
||||
"registers",
|
||||
"shared_bytes",
|
||||
"private_bytes",
|
||||
"location",
|
||||
"limitations",
|
||||
])
|
||||
: undefined;
|
||||
const fields =
|
||||
view.fields === undefined ? defaults : new Set<string>(view.fields);
|
||||
const items = matches
|
||||
.slice(view.offset, view.offset + view.limit)
|
||||
.map((row) => {
|
||||
const projected: Record<string, JsonValue> = {};
|
||||
for (const [key, value] of Object.entries(row))
|
||||
if (
|
||||
key === "id" ||
|
||||
key === "object_id" ||
|
||||
key === "location" ||
|
||||
key === "metadata_location" ||
|
||||
key === "kind" ||
|
||||
key === "target" ||
|
||||
key === "payload_target" ||
|
||||
key === "target_consistency" ||
|
||||
key === "inspection" ||
|
||||
key === "sources" ||
|
||||
key === "representation" ||
|
||||
(view.collection === "targets" &&
|
||||
(key === "target" ||
|
||||
key === "confidence" ||
|
||||
key === "kernel_coverage")) ||
|
||||
fields === undefined ||
|
||||
fields.has(key)
|
||||
)
|
||||
projected[key] = value;
|
||||
return projected;
|
||||
});
|
||||
if (jsonValueByteLength(items, 1024 * 1024) > 1024 * 1024)
|
||||
return err(
|
||||
new AnalysisResourceConstraintError(
|
||||
"inspect_analysis_view",
|
||||
"memory",
|
||||
"GPU page exceeds the 1 MiB response budget; select fewer fields or rows",
|
||||
{ maximum_page_bytes: 1024 * 1024 },
|
||||
),
|
||||
);
|
||||
return ok({
|
||||
kind: "gpu-code-objects",
|
||||
view,
|
||||
...shared,
|
||||
source_coverage: report.coverage,
|
||||
items: gpuCodeObjectsItemsSchema.parse(items),
|
||||
ptx: null,
|
||||
coverage: pageViewCoverage(view.offset, items.length, matches.length),
|
||||
});
|
||||
};
|
||||
Reference in new issue
Block a user