File size: 5,371 Bytes
08071f9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
export const MODES = [
  {id:'standard',label:'標準 NCHW',layout:'NCHW',gpuIO:false,capture:false},
  {id:'gpu-io',label:'GPUバッファ再利用 NCHW',layout:'NCHW',gpuIO:true,capture:false},
  {id:'nhwc',label:'GPUバッファ再利用 NHWC',layout:'NHWC',gpuIO:true,capture:false},
  {id:'capture',label:'Graph Capture NHWC',layout:'NHWC',gpuIO:true,capture:true},
];
export const GROUPS={body:Array.from({length:23},(_,i)=>i),hips:[11,12],knees:[13,14],ankles:[15,16],feet:[17,18,19,20,21,22]};
export function percentile(values,p){const a=[...values].sort((x,y)=>x-y);return a.length?a[Math.floor((a.length-1)*p)]:null;}
export function compare(observed,reference,ids){
  const xy=[],z=[];let lost=0;
  for(let frame=0;frame<observed.length;frame++)for(const i of ids){
    const ref=reference[frame],out=observed[frame];if(ref.scores[i]<=.3)continue;
    xy.push(Math.hypot(out.points[i*3]-ref.points[i*3],out.points[i*3+1]-ref.points[i*3+1]));
    z.push(Math.abs(out.points[i*3+2]-ref.points[i*3+2]));if(out.scores[i]<=.3)lost++;
  }
  return {observations:xy.length,xyP95_px:percentile(xy,.95),zP95_m:percentile(z,.95),confidenceLossCount:lost};
}
export function qualityGate(parts){
  return Object.keys(GROUPS).every(name=>{const p=parts[name];return p?.observations>0&&p.xyP95_px<=3&&p.zP95_m<=.05&&p.confidenceLossCount===0;});
}
export function validateOutput(points,scores){
  if(points.length!==399||scores.length!==133||points.some(v=>!Number.isFinite(v))||scores.some(v=>!Number.isFinite(v)))throw Error('全133関節の出力契約または有限値検査に失敗');
}
// Fixed GPU input/output addresses are required by graph replay. Read fresh
// buffers directly: Tensor.getData() changes its location to CPU and caches data.
export async function createExecution(runtime,modelBytes,mode){
  const options={executionProviders:[{name:'webgpu',preferredLayout:mode.layout}],graphOptimizationLevel:'all'};
  if(mode.gpuIO)options.preferredOutputLocation='gpu-buffer';
  if(mode.capture){options.enableGraphCapture=true;options.extra={session:{disable_cpu_ep_fallback:'1'}};}
  let session,device,input,fetches,inputBuffer,staging;
  const buffers=[];
  const release=async()=>{
    try{if(session)await session.release();}
    finally{session=null;input?.dispose();input=null;for(const t of Object.values(fetches??{}))t.dispose();fetches=null;for(const b of buffers.splice(0))b.destroy();}
  };
  try{
    session=await runtime.InferenceSession.create(modelBytes,options);
    device=await runtime.env.webgpu.device;
    if(mode.gpuIO){
      const make=(size,usage)=>{const b=device.createBuffer({size:Math.ceil(size/16)*16,usage});buffers.push(b);return b;};
      const usage=GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_SRC|GPUBufferUsage.COPY_DST;
      inputBuffer=make(3*384*288*4,usage);
      input=runtime.Tensor.fromGpuBuffer(inputBuffer,{dataType:'float32',dims:[1,3,384,288]});
      fetches={
        keypoints:runtime.Tensor.fromGpuBuffer(make(399*4,usage),{dataType:'float32',dims:[1,133,3]}),
        scores:runtime.Tensor.fromGpuBuffer(make(133*4,usage),{dataType:'float32',dims:[1,133]}),
      };
      staging=make(532*4,GPUBufferUsage.COPY_DST|GPUBufferUsage.MAP_READ);
    }
    return {session,device,release,async run(data){
      const started=performance.now();let points,scores,runMs,readbackMs=0;
      if(mode.gpuIO){
        device.queue.writeBuffer(inputBuffer,0,data);
        await session.run({input},fetches);runMs=performance.now()-started;
        const read=performance.now(),encoder=device.createCommandEncoder();
        encoder.copyBufferToBuffer(fetches.keypoints.gpuBuffer,0,staging,0,399*4);
        encoder.copyBufferToBuffer(fetches.scores.gpuBuffer,0,staging,399*4,133*4);
        device.queue.submit([encoder.finish()]);
        await staging.mapAsync(GPUMapMode.READ);
        try{const packed=new Float32Array(staging.getMappedRange().slice(0));points=packed.slice(0,399);scores=packed.slice(399);}
        finally{staging.unmap();}
        readbackMs=performance.now()-read;
      }else{
        const tensor=new runtime.Tensor('float32',data,[1,3,384,288]);let out;
        try{out=await session.run({input:tensor});points=out.keypoints.data.slice();scores=out.scores.data.slice();runMs=performance.now()-started;}
        finally{for(const t of Object.values(out??{}))t.dispose();tensor.dispose();}
      }
      validateOutput(points,scores);
      return {points,scores,totalMs:performance.now()-started,runMs,readbackMs};
    }};
  }catch(error){await release().catch(()=>{});throw error;}
}
export function summarizeProfile(events){
  const entries=new Map();let totalNs=0;
  for(const e of events){
    const duration=e.endTime-e.startTime;if(!Number.isFinite(duration)||duration<0)continue;
    totalNs+=duration;const key=[e.kernelType,e.kernelName,e.programName].join('|');
    if(!entries.has(key))entries.set(key,{kernelType:e.kernelType,kernelName:e.kernelName,programName:e.programName,dispatches:0,totalMs:0});
    const item=entries.get(key);item.dispatches++;item.totalMs+=duration/1e6;
  }
  return {available:events.length>0,dispatches:events.length,summedKernelMs:totalNs/1e6,topKernels:[...entries.values()].sort((a,b)=>b.totalMs-a.totalMs).slice(0,25),
    note:'Separate instrumented run; summed dispatch durations do not include CPU execution, transfers or queue gaps.'};
}