@@ -326,6 +326,21 @@ struct Builder {
326326 return addNode (" Add" , {maskName, c}, uniq (nameBase + " /gpmaskshift" ), nameBase + " /gpmaskshift" );
327327 }
328328
329+ // SGF metadata encoder (HumanSL nets, metaEncoderVersion > 0): a small MLP over the [N,metaC,1,1]
330+ // metadata input producing a [N,trunkC,1,1] bias that gets added into the trunk's initial bias
331+ // alongside the global-input matmul. Mirrors SGFMetadataEncoder::apply in eigenbackend.cpp and
332+ // ModelParser::buildSGFMetadataEncoder in trtbackend.cpp. All ops are NC11 channel matmuls/biases,
333+ // so this is layout-independent (it runs before any NCHW->NHWC trunk conversion).
334+ string buildSGFMetadataEncoder (const string& input, const SGFMetadataEncoderDesc& desc) {
335+ string x = buildMatMul (input, desc.mul1 );
336+ x = buildMatBias (x, desc.bias1 );
337+ x = buildActivation (x, desc.act1 );
338+ x = buildMatMul (x, desc.mul2 );
339+ x = buildMatBias (x, desc.bias2 );
340+ x = buildActivation (x, desc.act2 );
341+ return buildMatMul (x, desc.mul3 );
342+ }
343+
329344 // ---- Residual block builders ----
330345 // useNHWC: input and output are channel-last [N,H,W,C], and the block's internals run NHWC. The
331346 // elementwise BN/activation/mask ops and 1x1 convs are layout-free; spatial convs (k>1) bubble to
@@ -814,9 +829,6 @@ Result build(
814829 bool transformerNHWC,
815830 Logger* logger
816831) {
817- if (desc.metaEncoderVersion > 0 )
818- throw StringError (" OnnxModelBuilder: SGF metadata encoder not yet supported" );
819-
820832 if (logger != NULL )
821833 logger->write (" Building internal onnx model, requireExactNNLen=" + Global::boolToString (requireExactNNLen) + " transformerNHWC=" + Global::boolToString (transformerNHWC));
822834
@@ -875,6 +887,12 @@ Result build(
875887 addInput (" InputMask" , 1 );
876888 addInput (" InputSpatial" , numInputChannels);
877889 addInputNC11 (" InputGlobal" , numInputGlobalChannels);
890+ // HumanSL-style nets additionally take a per-row SGF metadata vector. Only declare the input when
891+ // the model actually has an encoder - an unused graph input would just be dead weight (and the
892+ // backend only allocates/binds an InputMeta buffer when numInputMetaChannels > 0).
893+ bool hasMetaEncoder = desc.metaEncoderVersion > 0 ;
894+ if (hasMetaEncoder)
895+ addInputNC11 (" InputMeta" , desc.numInputMetaChannels );
878896
879897 // ---- Mask-derived features ----
880898 if (!requireExactNNLen) {
@@ -934,6 +952,12 @@ Result build(
934952 string initialConv = b.buildConv (" InputSpatial" , trunk.initialConv , false );
935953 string initialMatMul = b.buildMatMul (" InputGlobal" , trunk.initialMatMul );
936954 string cur = b.elementwise (" Add" , initialConv, initialMatMul, trunk.name + " /initbias" );
955+ if (hasMetaEncoder) {
956+ testAssert (trunk.metaEncoderVersion > 0 );
957+ testAssert (trunk.sgfMetadataEncoder .mul3 .outChannels == trunk.initialMatMul .outChannels );
958+ string initialMeta = b.buildSGFMetadataEncoder (" InputMeta" , trunk.sgfMetadataEncoder );
959+ cur = b.elementwise (" Add" , cur, initialMeta, trunk.name + " /initmetabias" );
960+ }
937961
938962 // When transformerNHWC, run the entire trunk block stack channel-last: one NCHW->NHWC conversion
939963 // here and one NHWC->NCHW conversion before the trunk tip. Every block (convnet/gpool/nbt/
0 commit comments