mirror of
https://github.com/morluto/rea
synced 2026-10-28 06:46:47 -04:00
fix(gpu): retain AMD target declaration evidence
This commit is contained in:
@@ -35,6 +35,26 @@ export const gpuObjectSchema = z.strictObject({
|
||||
location: gpuLocationSchema,
|
||||
})
|
||||
.nullable(),
|
||||
amd_target_metadata: z
|
||||
.strictObject({
|
||||
flags: z.string().regex(/^0x[0-9a-f]{8}$/u),
|
||||
processor: z.string().nullable(),
|
||||
header_location: gpuLocationSchema,
|
||||
note_coverage: z
|
||||
.enum(["complete", "partial"])
|
||||
.describe(
|
||||
"Complete only after all declared ELF note sections were examined within supported AMD metadata coverage; partial retains only examined facts.",
|
||||
),
|
||||
notes: z.array(
|
||||
z.strictObject({
|
||||
target: z.string().nullable(),
|
||||
target_state: z.enum(["absent", "declared", "invalid"]),
|
||||
location: gpuLocationSchema,
|
||||
}),
|
||||
),
|
||||
})
|
||||
.nullable()
|
||||
.default(null),
|
||||
target_consistency: z.enum([
|
||||
"unexamined",
|
||||
"single-source",
|
||||
@@ -207,6 +227,48 @@ export const gpuCodeObjectsSchema = gpuCodeObjectsObjectSchema.superRefine(
|
||||
const row = report.objects[index]!;
|
||||
range(row.location, ["objects", index, "location"]);
|
||||
range(row.metadata_location, ["objects", index, "metadata_location"]);
|
||||
const amd = row.amd_target_metadata;
|
||||
if (amd !== null) {
|
||||
range(amd.header_location, [
|
||||
"objects",
|
||||
index,
|
||||
"amd_target_metadata",
|
||||
"header_location",
|
||||
]);
|
||||
if (
|
||||
row.kind !== "amdgpu" ||
|
||||
amd.header_location.container_id !== row.id ||
|
||||
amd.header_location.offset !== 0 ||
|
||||
![52, 64].includes(amd.header_location.size)
|
||||
)
|
||||
issue(
|
||||
["objects", index, "amd_target_metadata"],
|
||||
"AMDGPU header provenance must start at its payload",
|
||||
);
|
||||
for (let note = 0; note < amd.notes.length; note++) {
|
||||
const location = amd.notes[note]!.location;
|
||||
range(location, [
|
||||
"objects",
|
||||
index,
|
||||
"amd_target_metadata",
|
||||
"notes",
|
||||
note,
|
||||
"location",
|
||||
]);
|
||||
if (location.container_id !== row.id)
|
||||
issue(
|
||||
[
|
||||
"objects",
|
||||
index,
|
||||
"amd_target_metadata",
|
||||
"notes",
|
||||
note,
|
||||
"location",
|
||||
],
|
||||
"AMDGPU notes must use their payload coordinate space",
|
||||
);
|
||||
}
|
||||
}
|
||||
const header = row.cuda_target_header;
|
||||
if (header !== null) {
|
||||
range(header.location, [
|
||||
|
||||
+57
-14
@@ -369,6 +369,7 @@ const cudaKernels = async (
|
||||
export interface AmdElfMetadata {
|
||||
readonly target: string | null;
|
||||
readonly targetIdentity: AmdGpuTargetIdentity | null;
|
||||
readonly targetMetadata: import("../domain/gpu/gpuCodeObjects.js").GpuObject["amd_target_metadata"];
|
||||
readonly limitations: string[];
|
||||
readonly notes: {
|
||||
section: NativeSection;
|
||||
@@ -388,12 +389,18 @@ export const readAmdElfMetadata = async (
|
||||
{ kind: "incomplete-amd-metadata"; cause: unknown; partial: AmdElfMetadata }
|
||||
>
|
||||
> => {
|
||||
const targets = new Set<string>(),
|
||||
limitations: string[] = [];
|
||||
const limitations: string[] = [];
|
||||
const notes: AmdElfMetadata["notes"] = [];
|
||||
let processor: string | null = null;
|
||||
let retainedBytes = 0;
|
||||
let unsupportedNotes = false;
|
||||
let targetMetadata: AmdElfMetadata["targetMetadata"] = null;
|
||||
const observed = (): AmdElfMetadata => {
|
||||
const targets = new Set(
|
||||
(targetMetadata?.notes ?? []).flatMap(({ target }) =>
|
||||
target === null ? [] : [target],
|
||||
),
|
||||
);
|
||||
const processor = targetMetadata?.processor ?? null;
|
||||
const noteTarget = targets.size === 1 ? [...targets][0]! : null;
|
||||
const noteIdentity =
|
||||
noteTarget === null ? null : readAmdGpuNoteTarget(noteTarget);
|
||||
@@ -402,12 +409,17 @@ export const readAmdElfMetadata = async (
|
||||
processor !== null &&
|
||||
noteIdentity !== null &&
|
||||
processor !== noteIdentity.processor;
|
||||
const conflict = noteConflict || processorConflict;
|
||||
const limitation = noteConflict
|
||||
? "AMDGPU notes declare conflicting processor targets; processor identity is unknown"
|
||||
: processorConflict
|
||||
? "AMDGPU ELF processor flags conflict with target note metadata; processor identity is unknown"
|
||||
: null;
|
||||
const invalidTarget = (targetMetadata?.notes ?? []).some(
|
||||
({ target_state }) => target_state === "invalid",
|
||||
);
|
||||
const conflict = noteConflict || processorConflict || invalidTarget;
|
||||
const limitation = invalidTarget
|
||||
? "AMDGPU note target is not a string; processor identity is unknown"
|
||||
: noteConflict
|
||||
? "AMDGPU notes declare conflicting processor targets; processor identity is unknown"
|
||||
: processorConflict
|
||||
? "AMDGPU ELF processor flags conflict with target note metadata; processor identity is unknown"
|
||||
: null;
|
||||
if (limitation !== null && !limitations.includes(limitation))
|
||||
limitations.push(limitation);
|
||||
const target = conflict ? null : (noteTarget ?? processor);
|
||||
@@ -418,12 +430,25 @@ export const readAmdElfMetadata = async (
|
||||
(noteTarget === null && processor !== null
|
||||
? { key: processor, processor }
|
||||
: null));
|
||||
return { target, targetIdentity, limitations, notes };
|
||||
return { target, targetIdentity, targetMetadata, limitations, notes };
|
||||
};
|
||||
try {
|
||||
if (directory.machine !== 224)
|
||||
rejectGpu("AMDGPU metadata requires an AMDGPU ELF machine");
|
||||
processor = readAmdGpuElfProcessor(directory.flags);
|
||||
targetMetadata = {
|
||||
flags: `0x${directory.flags.toString(16).padStart(8, "0")}`,
|
||||
processor: readAmdGpuElfProcessor(directory.flags),
|
||||
header_location: {
|
||||
container_id: source.containerId,
|
||||
offset: 0,
|
||||
size: directory.bits === 64 ? 64 : 52,
|
||||
section_index: null,
|
||||
section_name: null,
|
||||
},
|
||||
note_coverage: "partial",
|
||||
notes: [],
|
||||
};
|
||||
|
||||
for (const section of directory.sections) {
|
||||
if (section.type !== 7) continue;
|
||||
const sectionSize = gpuCount(section.size);
|
||||
@@ -458,19 +483,36 @@ export const readAmdElfMetadata = async (
|
||||
const metadata = amdRecord(
|
||||
readAmdMetadata(bytes.subarray(desc, desc + size)),
|
||||
);
|
||||
if (metadata === null) rejectGpu("AMDGPU metadata is not a map");
|
||||
const target = amdString(metadata?.["amdhsa.target"]);
|
||||
const target_state = !Object.hasOwn(metadata!, "amdhsa.target")
|
||||
? "absent"
|
||||
: target === null
|
||||
? "invalid"
|
||||
: "declared";
|
||||
if (target_state === "invalid") unsupportedNotes = true;
|
||||
targetMetadata.notes.push({
|
||||
target,
|
||||
target_state,
|
||||
location: site(source, section, desc, size),
|
||||
});
|
||||
const kernels = metadata?.["amdhsa.kernels"];
|
||||
if (!Array.isArray(kernels))
|
||||
return rejectGpu("AMDGPU metadata has no valid kernel array");
|
||||
const target = amdString(metadata!["amdhsa.target"]);
|
||||
if (target !== null) targets.add(target);
|
||||
notes.push({ section, noteOffset: p, desc, size, kernels });
|
||||
} else if (vendor === "AMD" && (type === 10 || type === 11))
|
||||
} else if (vendor === "AMD" && (type === 10 || type === 11)) {
|
||||
unsupportedNotes = true;
|
||||
limitations.push(
|
||||
"Legacy AMDGPU YAML metadata is outside MessagePack coverage",
|
||||
);
|
||||
}
|
||||
p = end;
|
||||
}
|
||||
}
|
||||
targetMetadata.note_coverage =
|
||||
unsupportedNotes || directory.sections.length === 0
|
||||
? "partial"
|
||||
: "complete";
|
||||
if (notes.length === 0)
|
||||
throw new BinaryFormatFailure(
|
||||
"unsupported",
|
||||
@@ -491,6 +533,7 @@ const amdKernels = (
|
||||
onTarget?.({
|
||||
value: prepared.target,
|
||||
identity: prepared.targetIdentity,
|
||||
amdMetadata: prepared.targetMetadata,
|
||||
scope: "amdgpu",
|
||||
});
|
||||
for (const {
|
||||
|
||||
@@ -455,11 +455,13 @@ export class GpuInventory {
|
||||
this.#retain({
|
||||
payload_target: payload.value,
|
||||
cuda_target_header: payload.cudaHeader ?? null,
|
||||
amd_target_metadata: payload.amdMetadata ?? null,
|
||||
target_consistency: state,
|
||||
target: selectedTarget ?? payload.value,
|
||||
});
|
||||
row.payload_target = payload.value;
|
||||
row.cuda_target_header = payload.cudaHeader ?? null;
|
||||
row.amd_target_metadata = payload.amdMetadata ?? null;
|
||||
row.target_consistency = state;
|
||||
if (selectedTarget === null) row.target = payload.value;
|
||||
if (row.target === null) this.discovery = "partial";
|
||||
@@ -513,6 +515,7 @@ export class GpuInventory {
|
||||
this.#target(row, {
|
||||
value: prepared.target,
|
||||
identity: prepared.targetIdentity,
|
||||
amdMetadata: prepared.targetMetadata,
|
||||
scope: "amdgpu",
|
||||
});
|
||||
if (!inspected.ok) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { GpuSource } from "./GpuSource.js";
|
||||
import type { AmdGpuTargetIdentity } from "./AmdGpuTarget.js";
|
||||
export type GpuPayloadTarget = {
|
||||
readonly value: string | null;
|
||||
readonly amdMetadata?: GpuObject["amd_target_metadata"];
|
||||
readonly cudaHeader?: GpuObject["cuda_target_header"];
|
||||
readonly limitation?: string;
|
||||
} & (
|
||||
@@ -38,6 +39,7 @@ export const gpuObject = (
|
||||
target: null,
|
||||
payload_target: null,
|
||||
cuda_target_header: null,
|
||||
amd_target_metadata: null,
|
||||
target_consistency: "unexamined",
|
||||
bundle_id: null,
|
||||
compression: "none",
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
ccobFixture,
|
||||
ptxFixture,
|
||||
amdFixture,
|
||||
amdNoteFixture,
|
||||
cubinFixture,
|
||||
hipFixture,
|
||||
elfFixture,
|
||||
@@ -1186,3 +1187,150 @@ cliTest(
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
cliTest(
|
||||
"retains AMD target declarations and examined note coverage through CLI and MCP",
|
||||
async ({ cli }) => {
|
||||
const root = await createTestTempDirectory("rea-amd-target-sources-");
|
||||
const { call } = await connectLocalToolsMcp();
|
||||
const first = "amdgcn-amd-amdhsa--gfx900";
|
||||
const second = "amdgcn-amd-amdhsa--gfx1100";
|
||||
const legacyNote = Buffer.alloc(20);
|
||||
legacyNote.writeUInt32LE(4, 0);
|
||||
legacyNote.writeUInt32LE(4, 4);
|
||||
legacyNote.writeUInt32LE(10, 8);
|
||||
legacyNote.write("AMD\0", 12);
|
||||
const headerConflict = amdFixture();
|
||||
headerConflict.writeUInt32LE(0x141, 48);
|
||||
for (const [name, bytes, declarations, coverage, effective] of [
|
||||
[
|
||||
"note-conflict",
|
||||
amdFixture(false, amdNoteFixture(false, second)),
|
||||
[first, second],
|
||||
"complete",
|
||||
null,
|
||||
],
|
||||
["header-conflict", headerConflict, [first], "complete", null],
|
||||
[
|
||||
"duplicate",
|
||||
amdFixture(false, amdNoteFixture()),
|
||||
[first, first],
|
||||
"complete",
|
||||
first,
|
||||
],
|
||||
["legacy-tail", amdFixture(false, legacyNote), [first], "partial", first],
|
||||
[
|
||||
"invalid-target",
|
||||
amdFixture(false, undefined, 123),
|
||||
[null],
|
||||
"partial",
|
||||
null,
|
||||
],
|
||||
[
|
||||
"absent-target",
|
||||
amdFixture(false, undefined, null),
|
||||
[null],
|
||||
"complete",
|
||||
"gfx900",
|
||||
],
|
||||
[
|
||||
"truncated-tail",
|
||||
amdFixture(false, Buffer.alloc(3)),
|
||||
[first],
|
||||
"partial",
|
||||
first,
|
||||
],
|
||||
] as const) {
|
||||
const path = join(root, name);
|
||||
await writeFile(path, bytes);
|
||||
const response = await cli.run({
|
||||
arguments: [
|
||||
"inspect-gpu-code-objects",
|
||||
path,
|
||||
"--analysis",
|
||||
"kernels",
|
||||
"--detail",
|
||||
"complete",
|
||||
"--json",
|
||||
],
|
||||
});
|
||||
expect(response.exitCode, response.stderr).toBe(0);
|
||||
const reply = await call("inspect_gpu_code_objects", {
|
||||
path,
|
||||
analysis: "kernels",
|
||||
detail: "complete",
|
||||
});
|
||||
expect(reply.isError, JSON.stringify(reply)).not.toBe(true);
|
||||
for (const value of [response.json, reply.structuredContent]) {
|
||||
const report = gpuCodeObjectsSchema.parse(
|
||||
parseEvidence(value).normalized_result,
|
||||
);
|
||||
const row = report.objects[0]!;
|
||||
expect(row).toMatchObject({
|
||||
target: effective,
|
||||
payload_target: effective,
|
||||
});
|
||||
const metadata = row.amd_target_metadata!;
|
||||
expect(metadata).toMatchObject({
|
||||
flags: name === "header-conflict" ? "0x00000141" : "0x0000012c",
|
||||
processor: name === "header-conflict" ? "gfx1100" : "gfx900",
|
||||
header_location: { container_id: row.id, offset: 0, size: 64 },
|
||||
note_coverage: coverage,
|
||||
});
|
||||
expect(metadata.notes.map(({ target }) => target)).toEqual(
|
||||
declarations,
|
||||
);
|
||||
expect(
|
||||
new Set(metadata.notes.map(({ location }) => location.offset)).size,
|
||||
).toBe(declarations.length);
|
||||
for (const note of metadata.notes) {
|
||||
expect(note.location.container_id).toBe(row.id);
|
||||
expect(note.location.section_name).toBe(".note");
|
||||
const source = bytes.subarray(
|
||||
note.location.offset,
|
||||
note.location.offset + note.location.size,
|
||||
);
|
||||
if (note.target !== null)
|
||||
expect(source.includes(Buffer.from(note.target))).toBe(true);
|
||||
expect(note.target_state).toBe(
|
||||
name === "invalid-target"
|
||||
? "invalid"
|
||||
: name === "absent-target"
|
||||
? "absent"
|
||||
: "declared",
|
||||
);
|
||||
}
|
||||
expect(report.kernels).toHaveLength(declarations.length);
|
||||
if (coverage === "partial")
|
||||
expect(report.coverage.payloads).toBe("partial");
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it("does not interpret non-AMD ELF flags as AMD processor declarations through MCP", async () => {
|
||||
const root = await createTestTempDirectory("rea-amd-machine-");
|
||||
const path = join(root, "wrong-machine");
|
||||
const payload = amdFixture();
|
||||
payload.writeUInt16LE(62, 18);
|
||||
await writeFile(path, hipFixture(payload, "hip-amdgcn-amd-amdhsa--"));
|
||||
const { call } = await connectLocalToolsMcp();
|
||||
const reply = await call("inspect_gpu_code_objects", {
|
||||
path,
|
||||
analysis: "kernels",
|
||||
detail: "complete",
|
||||
});
|
||||
expect(reply.isError, JSON.stringify(reply)).not.toBe(true);
|
||||
const report = gpuCodeObjectsSchema.parse(
|
||||
parseEvidence(reply.structuredContent).normalized_result,
|
||||
);
|
||||
expect(report.objects.find(({ kind }) => kind === "amdgpu")).toMatchObject({
|
||||
target: null,
|
||||
payload_target: null,
|
||||
amd_target_metadata: null,
|
||||
target_consistency: "unknown",
|
||||
inspection: "malformed",
|
||||
});
|
||||
expect(report.kernels).toEqual([]);
|
||||
expect(report.coverage.payloads).toBe("partial");
|
||||
});
|
||||
|
||||
Vendored
+2
-2
@@ -213,7 +213,7 @@ export const cubinFixture = (
|
||||
/** One producer-shaped AMDGPU MessagePack note, composable with later notes. */
|
||||
export const amdNoteFixture = (
|
||||
hidden = false,
|
||||
target: string | null = "amdgcn-amd-amdhsa--gfx900",
|
||||
target: string | number | null = "amdgcn-amd-amdhsa--gfx900",
|
||||
): Buffer => {
|
||||
const args = [
|
||||
{ ".offset": 0, ".size": 8, ".value_kind": "global_buffer" },
|
||||
@@ -252,7 +252,7 @@ export const amdNoteFixture = (
|
||||
export const amdFixture = (
|
||||
hidden = false,
|
||||
trailingNote?: Buffer,
|
||||
target: string | null = "amdgcn-amd-amdhsa--gfx900",
|
||||
target: string | number | null = "amdgcn-amd-amdhsa--gfx900",
|
||||
): Buffer => {
|
||||
const note = amdNoteFixture(hidden, target);
|
||||
return elfFixture(224, [
|
||||
|
||||
Reference in New Issue
Block a user