From f9f3bc6f8576abeae81829d6b6bd6c57375e81d2 Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Tue, 8 Jul 2025 11:19:10 +0900 Subject: [PATCH 01/42] dalotia: add tensorflow C loading interface (draws on https://github.com/serizba/cppflow ) --- CMakeLists.txt | 28 +++ cmake/modules/Findtensorflow.cmake | 48 +++++ data/tensorflow_model/saved_model.pb | Bin 0 -> 48550 bytes .../variables/variables.data-00000-of-00001 | Bin 0 -> 1652 bytes .../variables/variables.index | Bin 0 -> 387 bytes src/CMakeLists.txt | 10 + src/dalotia.cpp | 21 +- src/dalotia.hpp | 3 + src/dalotia_tensor_file.hpp | 18 ++ src/dalotia_tensorflow_file.cpp | 197 ++++++++++++++++++ src/dalotia_tensorflow_file.hpp | 41 ++++ test/CMakeLists.txt | 7 + test/test_tensorflow.cpp | 60 ++++++ 13 files changed, 431 insertions(+), 2 deletions(-) create mode 100644 cmake/modules/Findtensorflow.cmake create mode 100644 data/tensorflow_model/saved_model.pb create mode 100644 data/tensorflow_model/variables/variables.data-00000-of-00001 create mode 100644 data/tensorflow_model/variables/variables.index create mode 100644 src/dalotia_tensorflow_file.cpp create mode 100644 src/dalotia_tensorflow_file.hpp create mode 100644 test/test_tensorflow.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d1c016..6883b7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,7 @@ option(DALOTIA_BUILD_TESTS "Build tests" ON) option(DALOTIA_WITH_CPP_PMR "use polymorphic memory resources (pmr) C++17 feature for dalotia" ON) option(DALOTIA_WITH_OPENMP "Build with OpenMP support" OFF) option(DALOTIA_WITH_SAFETENSORS_CPP "use safetensors-cpp for tensor I/O" ON) +option(DALOTIA_WITH_TENSORFLOW "use the Tensorflow C backend for tensor I/O" OFF) option(DALOTIA_WITH_FORTRAN "Build Fortran interface" ON) if (DALOTIA_WITH_FORTRAN) enable_language(Fortran) @@ -19,6 +20,7 @@ if (DALOTIA_WITH_FORTRAN) endif (DALOTIA_WITH_FORTRAN) set(CMAKE_CXX_STANDARD 17) +set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${CMAKE_SOURCE_DIR}/cmake/modules") # if this is empty, will be set at config time # (build with --config Release or --config Debug etc.) @@ -61,6 +63,32 @@ if (DALOTIA_WITH_SAFETENSORS_CPP) endif() endif (DALOTIA_WITH_SAFETENSORS_CPP) +# tensorflow +if (DALOTIA_WITH_TENSORFLOW) + if (NOT DEFINED tensorflow_DIR OR tensorflow_DIR MATCHES "fetch") + include(FetchContent) + FetchContent_Declare( + tensorflow + URL https://storage.googleapis.com/tensorflow/versions/2.18.0/libtensorflow-gpu-linux-x86_64.tar.gz + OVERRIDE_FIND_PACKAGE + ) + FetchContent_MakeAvailable(tensorflow) + + FetchContent_GetProperties(tensorflow SOURCE_DIR tensorflow_SRC_DIR) + set(tensorflow_INCLUDE_DIRS ${tensorflow_SRC_DIR}/include) + find_library(tensorflow_LIBRARIES + NAMES tensorflow + PATHS ${tensorflow_SRC_DIR}/lib + NO_DEFAULT_PATH + ) + message(STATUS "tensorflow_LIBRARIES: ${tensorflow_LIBRARIES}") + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(tensorflow DEFAULT_MSG tensorflow_INCLUDE_DIRS tensorflow_LIBRARIES) + endif() + find_package(tensorflow REQUIRED CMAKE_FIND_ROOT_PATH_BOTH) +endif (DALOTIA_WITH_TENSORFLOW) + add_subdirectory(src) # target dalotia_cpp is generated here if (DALOTIA_WITH_OPENMP) find_package(OpenMP) diff --git a/cmake/modules/Findtensorflow.cmake b/cmake/modules/Findtensorflow.cmake new file mode 100644 index 0000000..4aa23e9 --- /dev/null +++ b/cmake/modules/Findtensorflow.cmake @@ -0,0 +1,48 @@ +# file shamelessly taken from https://github.com/serizba/cppflow/blob/master/cmake/modules/Findtensorflow.cmake +# so here goes their license: + +# MIT License +# +# Copyright (c) 2019 Sergio Izquierdo +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# installation: https://izquierdo.dev/cppflow/installation.html + +find_path(tensorflow_INCLUDE_DIRS + NAMES tensorflow/c/c_api.h +) +mark_as_advanced(tensorflow_INCLUDE_DIRS) + +find_library(tensorflow_LIBRARIES + NAMES tensorflow +) +mark_as_advanced(tensorflow_LIBRARIES) + + +if(NOT tensorflow_INCLUDE_DIRS) + message(STATUS "Could NOT find tensorflow/c/c_api.h") +endif() +if(NOT tensorflow_LIBRARIES) + message(STATUS "Could NOT find tensorflow library") +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(tensorflow DEFAULT_MSG tensorflow_INCLUDE_DIRS tensorflow_LIBRARIES) + diff --git a/data/tensorflow_model/saved_model.pb b/data/tensorflow_model/saved_model.pb new file mode 100644 index 0000000000000000000000000000000000000000..9cd005803833fe7fd5731eabbc3e4fe0f42f2908 GIT binary patch literal 48550 zcmeG_YiwKBdAcMeQXeH*Iu0Y$t181_JmtpmC)U4IPNeiazH z?K}5*?u)!6l|*)#HWBYV=brPO?{&U&&i8#MA&^h~PXu0#k?%)g9R@E{D{6xtf}v`& zQEN6*gT=ki1-$3(C!Exv12= z?KnS69fm>mrd(5q{)fi$k!bJ;YO`KYrVrCG0~ixH zXQMwW2=JEz9KF(z8_IUGd|9qHN{v#rq7*O4$~6DpV`mr5!S)HtMCy4vmn36onD}tyHU5 z8&yZy42&vwl|r*otxK#MzK{*zML_)|w=7#nWLrHWGLKTI8iB%-sf z6s3y1>uA$OuN0z?M?7|!8dR0~Z6!S@E{cm8G2X1)s#Nb()&=r2pNK#MMv6#yW$Bhu zuPBH_4!ZOa7?X5%k+eot5`##7Ae}UMB$5%*lV*>QPmoArko}uJ?gV&@IC_CynCE+4 zVqBs9hhq&Ft58P^={XN*BftZ|g9v2+y}KJFS?ywoC?q;khw?FPcr(u7jS#MVP+-pP zE!5DsuhY~sumqUhItFzY#Mpn`F`gf9>-%cjtWMK1OlJE}#BohLOm*sm({agm76A^M)dywvw8aJgo zN@?e2L&cILmKfEtSS{*XEsNEnW;GE;6EQMetu;!!rI(aCBv2Q`%2ic`V~HU$RabVJ zWz_sH$xLTUtUyuWD1tWGsH4hbGlF#6m>N`UEJ23($6+#zI!ab0xmJR)1Q1Y5I~BRn z#0dlj;3hyKNrrSahJBBa7~>S|do;N3v7o*WIr|>x`(`~&a6J*Lr^(=+4hQu#&M;>2i3BAx`YV4D&m?BZ(F&401epimIdToguASeycz*NgC!}lVpMLfU=j1_G z{TSBJflG_$uU_4{xcThWC$20xTY;-&%hNKadFirmyNroiwK@Pe{Tx2z>@Xs0*Iv3Dn;v`Rn6i0t}RB)(*0hq#}2>OU3y4**Z-0Mog z@4qj>QD*9)nJN{PZMj))NTmwe`cjtg#(Vl59us2{nfO2RA3-y7czYv_YDhPN z{HnrNBf*Awki|}ez;)VslowJrBRZHTJ+52%!w?q-g~RwydekuS2O%m7LfD}T=`%p3 zI$EVtwNk!^zw|YVj!%`KNk(HrBpOSnU26eKrXdc|CoexcKEQt9oINgxk*5aM1+0q; zi-oOIa8kmCww1b4DJYUPi%ECta;>J+B{3($B^XoX+xW0uDl3}N?;v3a%RJ63D4k+x zv%^nC;aI@R3Hc+CL@p?3Tf3dMF@9+X+tKKyh3Q3g+bBI@EbAa44#frhC+?(pJkVU4 zZ%N3h)F`EB3|1suLKn4t z0l92gnNm(%Apk>~HdLntl7hKixv|?UYcg(Hnbn#l=+lSEDLBFAe!0jRmW#fX*!sPM z(BMAAy=eg#+w|uIn0iKTJku<6_s=|;C4l({x0}J%HFPM34)qu;4 zI0#n>Ti=JAEd&vjqTVF^m;gsM5qIZ{ZCCZ}a2$FSB~AzRNqaVus7dg_*ib{2F$%-RgOqVTW}S~BZg#p=$DR;43BS>eab*HYO#rD4 zoc%Lv{}d0n`Sc{4Ha5531&_C+QtO!@yS5=73p7>f#|4|;IU$-4u$(OBr_+QE;Qh&W0 zK{r+BLo=zLVUzmFtrKvZpVW=5kfNm9VC(%kyGucSc;@Vo2D+GCdmCiAZ=kdN&3!S@ z#ms8oK<{qWet3lan+f=B!MV=U_x!lkc$;-zX4zr8KlnR-gAR{~1F+*a_?GYZxlnN6 zcKilKZpZIAh;1q5Cei-84|{!sBKqR77U{O{9tB}#dAZOi-PSxT)Gqe>2E`8c9v^+mxc0?T zz|PX1aKdOYs0RH;1X{NdxZ6Xmm4-biYW-YGkLd?*aTpY%c+%=TbqjrHFql5w@)!;| zo<@X7M@Mn~R)~(NPNokL*CZ|RPk%ZJ?I;k8-!m=Swl@}euA$t+@;;G3coRr)kuB=p zy|vlnsa-ag74eX;9h0L^uysbk?b)7f^PP?2x$7-yLFe^yJdO^K#DlrV`*<7vC9T<8 z+-!?_(vCI{(Vp@DQutD&{fRZIdFHW!p5e-N>F#u#oP^`D%6vSy17w{ubyr243H+*L zCB4!OJVwPT3vklYifB~HN>)pFX6>n#v#RNBp=&&puyJ-~Ev-z)@zi=x7(cJUc+K+6 z-xG-Ym6Y{+Df+$4?FqwM`Z@kJtdO*ROIiBu2pqAVm-MIu&8=MxaCi;&h>r0laXkHB zg6Z`7hOqf>0(zD*Z|&G-mkxuQT{#5$_7=i5&bPO=j&CB`!2Zajc)~&LBJD_4^O8`@ z=u6x)u4hbClg)7p(F2>>|HRo=J<~4=U^54?e^Op@=m{Hko=0r0eaf!j9-LLl?K^$i zb2dz1+&Yjp4#(02hP2=q&Cg4WF}ETy$-zvY^PO-eEr9Ym#10F5+S4@}H`)e0XL2+5 z5sxSm&l}oWrBI*4`NN*>nTr$e;3py~+#*ju2yoHV6?wRI%&6Qkv<(_#E1R5cZ^wVJ!Y}fF^d$v47_6&ea75g;dA2_dmgvw7B{~CizxC((9{Ule(fo6+}bb+tp9-dtdu3Poo8a`0(dQzRHAc$gto@`sV_t)|NTZam;8F*FEZW((?)pB%ha}-^&~wyw19J z!)kc$1$uLeyb@lfuk*mKaKPJIb=gM!{ttp9CLOT}D_QeNxPh#sPV#mwv7ukFpieKj z-DPkF$Pc&;0P(bUCiE&Q@vM(R%M!1W%lg(7eQJunHFG{SbK()sAmKy~6^KI^`G@=k z^x*m7AO|^E2yoaLB|ICyZB9GAp9$nL51SXTYeW3)_X6&p33#Gt`ey>($RbPQ9wbL2 z_|GAD2^M%i6nzgS>v96l<(y}WE&KvI@$&uKZXAMIc>euhY!E+0&b%ESs6FtWeek_; z_-Mp=%-`W5oVb#49P^)r)4I&=whQlh1Qzs~Y*(~pkPk_Nepn-ArMAsJ3hywLM%#ux z%7Old05jU>OL}xlyBMNSgKZN@C{~EA5agtJLH0dzG&t{ z;T#kUpp4G#C6lq541c7{mY4bfHD`t25{zRg+*=99)FFkf4O=_5Vg&0jHwX&)Yl59m z#l_mw_Hdm371Us=fRS}gGC>@714DmPFw)B42>dyOxHIO2)kH0r-i5{ab}-OS3vft7 zmkp-P_&Opj5^$_<(mY^jJz#Re0}s7tzcPa3?3g?Y#GVB~5V@|6e&~+>$$^P6quQGv ztJh`RQ2rp<>pcYqgbe}O2yN=~(SN;#`}ISB`p(4n{32|-l-dV=q0p10fByu0KGHfb z1M|gmdTpQ?&yqIWHhnB~4+||3fGZmS#sK3|!cqAKTBHG0&%q;(s;2KoKy^15S*bv%72PWZ|f_P1CG%)ob0g||`=JHERi)q~3 z_V1$5Dl=MeFWf0NJ4-MQxkfkjv}l8iHB2Naq9@}8Ek!0C!N;{rG~OwlXAI&yebyAmE5+nyjm!%E2~N_x3RpsEN{rG%Epog4f+uqFwl<* zFmy#JD}@Fz{?g-qWU)+kI;MSUKaU$tS#R9rX?tRXsU36>XanGimI?DIWE{f%cF><5 z(yO=X@%ZpuCR8M;(Lr#J4;$WzQhlee$TJh0p_sp+YiM^heNIs;(kFVZERRbrE{*&binQM zNqd;qODF9E$HR9dAA|~AO!6f|!mfluV#Eg_R<*jnq zVwi(#d~x6SXX z4xVb=66EU};Oj)j)ToZa4>3YJwGJ+ek>>ScfH(!DHEE~q1TC1(zbE>80!(B^`f=E1tmuP!_C#9NQXQ`ROoiu94CO3d86ZjMFsO+1RXp= zg3g4~$5JOg@90uEY}mx%S350+=BwF6^qKMYM={^8>4nMp5IH)Uyzc$Z2b(PISg_9z z?=6doy*PJLu3N7IEILqmJGYpU!-4797z_dLK9zNN9c4MMqpXGP^qSl2=nR}juOlm4 zx}H?rdKR>2AwR&pfxJNbaRBZY8#9Y3;YcM*V2Jf=e>{LrW1cPC3HEptBVw#U4Cd!C z-xfN8durh%Xbku?8szQT5wM+D%XS3Z9T;qp>}Nmn2Vx|W`QA5OfMbHBj& zV~=yep3pkpFGM);*SilVHvN=_jiavBbt{^9EwfLaO8sc!fcR9)6yXAh*K+*B%07{x zA5BdA(ZqhYGwn+>@k4X^ybswYR>h#~6D!8)v!Q8M+_S^Vs%yGf!TIIQm@f7+`@{++ zsNqJ5FThtT-uV-q|%GM)3m zBBXU+YF%sYw)2Nxf=BuK%+H{;S9As*<39-D8=Zx7&M(?_;+d7`A7M6Etr5Hwja^$9 z&ZV>iRJ3s`b$J2fT`p~*p}&edx!ziq=d_h+=@D-wmjig$evpG~*VfxS{X+p#{Oiqd z&eWZ-N(=8&7cx(WfrW9vqz}lpm+35Ds`CfuX{xh=!9D)N?E%YpuFLmsv`>Ew#`)Kr z+qDB&qq?b?mzGzaE>Rhz7R4;DOZWdyW0hO&+?{ioI9a0J-v5* z&%=)SRzA4?-VcujwGXjotLu&p+J3B=ku8k7H>=Go;F|FJv1YF8wXL&J-mOPq8CY~{ z*I$|TW6cz8?x+1&vu^Hf>&Kem^?r8d!yZf%F5t_M)PAg4Cf#M!%EJ?D*8ABoUW(u_ zJ?z8a4QHz94OgmZG2=Qc7Bl{L&mE+*;f)n*uYaSTX&P@geTy?qBh%P>{m3x=y?&*5 zfsvt~Y1-+5W8Nls5Hd|aG-t!#9fG^gi%9hyd@grAUPPM9h7L^jY@)ZgY4j@xGY8Ru zKB+y}8HnD@D?J|gz~ajRTl8p@qi*Qa*d+SO@9_62Jt53+oj45 zCU@VKo8<<l^E+!@!IOeKgT+NKYj|@>@`QT38`DUNX=JRHT4 z+{s2ADI?#&Z2e|#eia)_kYWCDm<(g^HwJXewGxaafPjk0{AIjpUQr>PI7803@1JMa z(HA^-(8EaL9V8*?H%Ut{lQ>I`n#9w1L9*IS{rxdGBsI2KUVrRZ+cGAxgn)$mi(@aR z3T0VUwT$_x{Cw(}D&A6^dTc&bk#`mLLaWYRSi{4&je1kTSIS+yJG-GuS`p(OxxQnU zYUy!V6G%t7krP=XH(10sf2}u`S1=MBi*StFCHDEt-o7>Bs#x{`H@p`swPxdKcBwXN zo@>LeC-=va)M(a_UK0DZgz?l`7dkvEFWkqMeq&HEq*`UT*_O*HLqlEO*-?tz3ln$7 zz1}I-lKUO&v2vr@tQ4gR;#}*Qb?Juj-RxZ2H8!&?O~P224{iJ)Z=zmc9j)Le_RUWC zB2~w;F4hp~qF-kKTX(rx;ssWz+H5d3;cIE_S&XCcOzK9nR4%#{r&|iIqN=FYF%+|=DGXnQ zR#K&%x?H;{Rg}h^YWi@C+jxyCkQm>U@Ps8pDaOWAWXE6}C~ zHb0rVJ77BDYM9!~npy0zyGPX5?hMC$?XFK6zWTeb-F0d0Yj@2|?sgJ?8?`$#C@c$_ z)01q`VH+d63g*cva4+;#4qFzwOW23zBFJt)m3Nt~tAD1g0_-wtxf86(x1PCDt5<7G zA8|06x|}O5D{{TU76Wzk#KkT$iENsZCkBNlYf7f z8ysf$r$N6}Ko{$m`|x@kBjuB1NH>>pRXjps%;d&Z@o4a>cr0jDe8{;f9_LrZ>BJ)) ztb)__DmeA&A%3B&nH2^fd}PSY2k$o5RVE9VR-E5-8?ewd7l3|P_M@M%u23-xnzG$2 zo6OAk+cu!Wz~k3pe{rX8%K?E>Ll60}1428_goU!pfXfGbv({STn+{m1Lz@ zV%p6p+?DT2t~a=)XS@-yUHPuIAMkSOTo%7tL0yM0_hkieAHK>2d4dyUVhQqOupl1} z667hTARloG@}nIH@?)GJeywPi6{pe-*E9I2n@DmuJ@& z<$ah;O@O^!txtC_pPGXsXj2w$Y5UP^1wwfR;m?!cf0kze)!G!j`sM^;CVpGeI9_OLJ{|3s};Fy96zLH@v5yIrJ~>hLBbu} zx>UXKB1`b8ZSuooFqt?<+$TJ#DHyem2=F3`no)aByOGOECNwRGBbsHjWLSRF`6GDt z!fwJH#4;l_zh()#n9heO7M(2BmWev8|2f7sotvY{#4OJ?$2qo{^Rmr}c5E}>iftT* z??{5PUXe4x+r4M7Ztt8ipPxfonzR;>o@2(|qe*R=pEjt?N_9@aD2)#2jY@;55L6o3 zQ1tGkzDDoPt(IisBv0=YNAFW!dY^7b?}b+McGyQF3C_qtdWScU&S2erpm$petRSfb zonjGP=|@rNxBbi|`d!U8t~Pws$D2^&2OiSbqvDsMJ>8jDy$0(?RERFbT{Cs_fE%8VeVtAshl1E;Po`InkW2#c#hQy0T@*#j16Xw1h zE8UgVTbNfVj>rAlS9l{f#!zFv==!ZYggqREgyCMHlH_aA1Vm~BzzQY+Zr!O?%J;PI zqOU~<$P|p}udFYkpT*Y%l5(X|Wogv$z>=_c<3bwSfUifye~dUCz4Wsg-Nw(Qx z78&DMB!Dw8i>8Y+SVbSkNT1n@VnF#TkvZFu$LpyxEx>mK@!PIsW4al@v5t&>3UvkD zR4P8sC=$C071Qayio?f?xg9ce+!+4UYf<8rp_cNppU5zzrG;!z#AA|RjI59a$I zi6Fd7d(p>WVK173`Bx&x!I)&d-QuznG;p=LGp|IZLCAQkA#|Xr+`C~p=2CZHOurJD z^6j4BrGk(1#Pw*JHgbtd6;YEM*>BWn7-UAq?Qoh95z`G2{)oZO$?v!j<1tiPBL*!_ zvSV=h`d)Me7Wbl$hMDLhHG$b%9kEZZuwr7hE@9Pd!;WD6OmBE)x1VkscwO6zF2lLK zXgY*6c7xVlK}DmzJ5W2vDy9W@7z`e6MT3FaxzG2^lKv9I^lTUy#OzwPFmOl-hT$hd z;6Yr^k{{`ng?a>GW4TKZLSf;Pm@$f1>i#qKZ374I+>5S5bT2vp=HzO48j$vOc^C8X F{{zPv+$I13 literal 0 HcmV?d00001 diff --git a/data/tensorflow_model/variables/variables.data-00000-of-00001 b/data/tensorflow_model/variables/variables.data-00000-of-00001 new file mode 100644 index 0000000000000000000000000000000000000000..def833ba27a25cd14edb479fd4c58740e665ebe3 GIT binary patch literal 1652 zcmZ2*C4)I8j$V?B9az|1_mGg)iw;?=kAo6s}gPGQ3~+dcm- z+6mnGZ08ZoYwxk?o!tqwReKd|@7V45S!8F{$zt!z?y}!txBUKD&Ia~7^xf%Mr(=$qnfim1c8K^QtpfYBx$_#vP}#? ze=%^$aIpe?#%KVfc!U@Yfs~*SqY;o25@IwaAiI{4tib2y5Eo+4O)V+POfClcRzipk zoJfkf1UV#ycp(V}CN0k)B_s_I!XCxa9MVGKdB7+qBAOYvB80-Y_`)26JRO~UT;szW zeL`J@cv4dHic|F=aVuqpEx|%1_4OP>LV`S+ zB!$mZ1};9d_(A7`x90#&UaUh2o2l9mCKmlJIpd|Mwp$Lp{;sb|Mydl0g zfJx#DtuUbw46`Az3p5*NEI_32nG1;pV7>t55@5bC1jh<89~=v$XHaeqMdVmeLW%`t l%vew%6bq{OVgaQnVBiu3dW8|Jf4pxN}4H46%6b64$Md9q!qP)}`{F*pGnm|}1#gxlT%RpFB$dE|8 z7!`ULHCRlSRSQ~)DvFX~8KXi4(3P9i9E%u$fDuHb--|d5=N$O7a03G)b1uVLCb*b* Z?8Ggbm^k(`@q>i$gYe%C-72N-w*hEXR0jY6 literal 0 HcmV?d00001 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 881b341..046b8c7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -26,6 +26,16 @@ if (DALOTIA_WITH_SAFETENSORS_CPP) target_sources(dalotia_cpp PRIVATE dalotia_safetensors_file.cpp ) endif (DALOTIA_WITH_SAFETENSORS_CPP) +if (DALOTIA_WITH_TENSORFLOW) + target_link_libraries(dalotia_cpp PUBLIC ${tensorflow_LIBRARIES}) + target_include_directories(dalotia_cpp PUBLIC + $ + $ + ) + target_compile_options(dalotia_cpp PUBLIC "-DDALOTIA_WITH_TENSORFLOW") + target_sources(dalotia_cpp PRIVATE dalotia_tensorflow_file.cpp ) +endif (DALOTIA_WITH_TENSORFLOW) + # not sure if this is elegant, but helps to make this compatible to all languages target_sources(dalotia_cpp PRIVATE dalotia.hpp dalotia.h) target_include_directories(dalotia_cpp PUBLIC diff --git a/src/dalotia.cpp b/src/dalotia.cpp index 2e6ae02..b128899 100644 --- a/src/dalotia.cpp +++ b/src/dalotia.cpp @@ -3,9 +3,20 @@ #include namespace dalotia { using file_exists = std::filesystem::exists; -} -#else // __cpp_lib_filesystem +using is_directory = std::filesystem::is_directory; +} // namespace dalotia +#else // __cpp_lib_filesystem namespace dalotia { +#include + +bool is_directory(const char *path) { + struct stat st; + if (stat(path, &st) == 0) { + return S_ISDIR(st.st_mode); + } + return false; +} + bool file_exists(const std::string &name) { if (FILE *file = fopen(name.c_str(), "r")) { fclose(file); @@ -43,6 +54,12 @@ TensorFile *make_tensor_file(const std::string &filename) { #else // DALOTIA_WITH_SAFETENSORS_CPP throw std::runtime_error("Safetensors support not enabled"); #endif // DALOTIA_WITH_SAFETENSORS_CPP + } else if (extension == "pb" || is_directory(filename.c_str())) { +#ifdef DALOTIA_WITH_TENSORFLOW + return new TensorflowSavedModel(filename); +#else // DALOTIA_WITH_TENSORFLOW + throw std::runtime_error("Tensorflow support not enabled"); +#endif // DALOTIA_WITH_TENSORFLOW } else { throw std::runtime_error("Unsupported file extension: ." + extension); } diff --git a/src/dalotia.hpp b/src/dalotia.hpp index 999a801..8b96cc7 100644 --- a/src/dalotia.hpp +++ b/src/dalotia.hpp @@ -17,6 +17,9 @@ #ifdef DALOTIA_WITH_SAFETENSORS_CPP #include "dalotia_safetensors_file.hpp" #endif +#ifdef DALOTIA_WITH_TENSORFLOW +#include "dalotia_tensorflow_file.hpp" +#endif namespace dalotia { // factory function for the file, selected by file extension and diff --git a/src/dalotia_tensor_file.hpp b/src/dalotia_tensor_file.hpp index 31b522b..b23e656 100644 --- a/src/dalotia_tensor_file.hpp +++ b/src/dalotia_tensor_file.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include "dalotia_formats.hpp" @@ -182,4 +183,21 @@ class TensorFile { // FILE *file_ = nullptr; }; +// helper function to output iterables +template +inline std::string to_string(const Iterable &iterable) { + std::string result; + for (const auto &item : iterable) { + if (!result.empty()) { + result += ", "; + } + if constexpr (std::is_same_v, std::string>) { + result += item; // for strings, just append + } else { + result += std::to_string(item); // for other types, convert to string + } + } + return result; +} + } // namespace dalotia diff --git a/src/dalotia_tensorflow_file.cpp b/src/dalotia_tensorflow_file.cpp new file mode 100644 index 0000000..43f8a8a --- /dev/null +++ b/src/dalotia_tensorflow_file.cpp @@ -0,0 +1,197 @@ +#include "dalotia_tensorflow_file.hpp" + +#include +#include +#include + +#include "dalotia_assignment.hpp" +#include "dalotia_formats.hpp" + +namespace dalotia { + +TF_Output get_operation_from_name(const std::string &tensor_name, + std::shared_ptr graph) { + size_t pos = 0; + TF_Operation *oper = nullptr; + int output_index = 0; + while ((oper = TF_GraphNextOperation(graph.get(), &pos)) != nullptr) { + int num_outputs = TF_OperationNumOutputs(oper); + const char *op_name = TF_OperationName(oper); + + if (num_outputs > 1) { + if (tensor_name.find(op_name) == std::string::npos) { + continue; // not a match + } + for (int i = 0; i < num_outputs; ++i) { + std::string compare_tensor_name = + std::string(op_name) + "/" + std::to_string(i); + if (tensor_name == compare_tensor_name) { + output_index = i; + break; + } + } + } else if (num_outputs == 1) { + if (tensor_name == op_name) { + break; + } + } + } + return {oper, output_index}; +} + +// parts of this code are intensely based on cppflow, esp. tf_status_check and the +// constructor -- so here goes their license for the respective parts: + +// MIT License +// +// Copyright (c) 2019 Sergio Izquierdo +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +inline bool tf_status_check(std::shared_ptr status) { + // cf. https://github.com/serizba/cppflow/blob/master/include/cppflow/context.h#L45 + if (TF_GetCode(status.get()) != TF_OK) { + throw std::runtime_error(TF_Message(status.get())); + } + return true; +} + +int tf_get_num_dimensions(TF_Output output, std::shared_ptr graph, + std::shared_ptr status) { + // TF_DataType dtype = TF_OperationOutputType(output); + int num_dimensions = TF_GraphGetTensorNumDims(graph.get(), output, status.get()); + tf_status_check(status); + return num_dimensions; +} + +TensorflowSavedModel::TensorflowSavedModel(const std::string &filename) + : TensorFile(filename) { + // cf. + // https://github.com/serizba/cppflow/blob/master/include/cppflow/model.h + this->status_ = {TF_NewStatus(), &TF_DeleteStatus}; + this->graph_ = {TF_NewGraph(), TF_DeleteGraph}; + + // Create the session. + std::unique_ptr + session_options = {TF_NewSessionOptions(), TF_DeleteSessionOptions}; + + auto session_deleter = [this](TF_Session *sess) { + TF_DeleteSession(sess, this->status_.get()); + tf_status_check(this->status_); + }; + + std::unique_ptr run_options = { + TF_NewBufferFromString("", 0), TF_DeleteBuffer}; + std::unique_ptr meta_graph = {TF_NewBuffer(), + TF_DeleteBuffer}; + + int tag_len = 1; + const char *tag = "serve"; + this->session_ = {TF_LoadSessionFromSavedModel(session_options.get(), + run_options.get(), filename.c_str(), + &tag, tag_len, this->graph_.get(), + meta_graph.get(), this->status_.get()), + session_deleter}; + tf_status_check(this->status_); + + { // create and fill the tensor names vector + size_t pos = 0; + TF_Operation *oper; + while ((oper = TF_GraphNextOperation(graph_.get(), &pos)) != nullptr) { + int num_outputs = TF_OperationNumOutputs(oper); + const char *op_name = TF_OperationName(oper); + + if (num_outputs > 1) { + for (int i = 0; i < num_outputs; ++i) { + std::string tensor_name = + std::string(op_name) + "/" + std::to_string(i); + tensor_names_.push_back(tensor_name); + } + } else if (num_outputs == 1) { + // If there is only one output, we can just use the operation + // name + tensor_names_.push_back(op_name); + } + } + } +} + +TensorflowSavedModel::~TensorflowSavedModel() = default; + +const std::vector &TensorflowSavedModel::get_tensor_names() const { + return tensor_names_; +} + +bool TensorflowSavedModel::is_sparse(const std::string & /*tensor_name*/) const { + return false; +} + +size_t TensorflowSavedModel::get_num_dimensions(const std::string &tensor_name) const { + TF_Output output = get_operation_from_name(tensor_name, this->graph_); + if (output.oper == nullptr) { + throw std::runtime_error( + "Tensor not found: " + tensor_name + + ". Tensor names in the file: " + to_string(tensor_names_)); + } + int num_dimensions = tf_get_num_dimensions(output, this->graph_, this->status_); + if (num_dimensions < 0) { + throw std::runtime_error("Failed to get number of dimensions for tensor: " + + tensor_name); + } + return num_dimensions; +} + +std::vector +TensorflowSavedModel::get_tensor_extents(const std::string &tensor_name, + const std::vector &permutation) const { + TF_Output output = get_operation_from_name(tensor_name, this->graph_); + if (output.oper == nullptr) { + throw std::runtime_error( + "Tensor not found: " + tensor_name + + ". Tensor names in the file: " + to_string(tensor_names_)); + } + + int num_dimensions = tf_get_num_dimensions(output, this->graph_, this->status_); + std::vector extents_read(num_dimensions); + // use tensor_name only from the last slash onward + size_t last_slash_pos = tensor_name.find_last_of('/'); + auto shortened_tensor_name = (last_slash_pos != std::string::npos) + ? tensor_name.substr(last_slash_pos + 1) + : tensor_name; + TF_GraphGetTensorShape(this->graph_.get(), output, extents_read.data(), + extents_read.size(), this->status_.get()); + tf_status_check(this->status_); + + std::vector extents(extents_read.size()); + + if (!permutation.empty()) { + auto final_permutation_in_c_order = + final_c_permutation_from_permutation_and_order( + permutation, dalotia_Ordering::dalotia_C_ordering, extents.size()); + if (!final_permutation_in_c_order.empty()) { + for (size_t i = 0; i < extents.size(); i++) { + extents[i] = extents_read[final_permutation_in_c_order[i]]; + } + } + } else { + extents.assign(extents_read.begin(), extents_read.end()); + } + return extents; +} +} // namespace dalotia \ No newline at end of file diff --git a/src/dalotia_tensorflow_file.hpp b/src/dalotia_tensorflow_file.hpp new file mode 100644 index 0000000..48fab0e --- /dev/null +++ b/src/dalotia_tensorflow_file.hpp @@ -0,0 +1,41 @@ +#pragma once +#include + +#include +#include + +#include "dalotia_formats.hpp" +#include "dalotia_tensor_file.hpp" + +namespace dalotia { + +class TensorflowSavedModel : public TensorFile { + public: + explicit TensorflowSavedModel(const std::string &filename); + + ~TensorflowSavedModel(); + + const std::vector &get_tensor_names() const override; + + bool is_sparse(const std::string &tensor_name) const override; + + size_t get_num_dimensions(const std::string &tensor_name) const override; + + std::vector + get_tensor_extents(const std::string &tensor_name = "", + const std::vector &permutation = {}) const override; + + // void load_tensor_dense(const std::string &tensor_name, + // dalotia_WeightFormat weightFormat, + // dalotia_Ordering ordering, + // dalotia_byte *__restrict__ tensor, + // const std::vector& permutation = {}) override; + + // cf. https://github.com/serizba/cppflow/blob/master/include/cppflow/model.h + std::shared_ptr status_; + std::shared_ptr graph_; + std::shared_ptr session_; + std::vector tensor_names_; +}; + +} // namespace dalotia \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 9ed462b..9214a65 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -24,3 +24,10 @@ if(DALOTIA_WITH_SAFETENSORS_CPP) add_test( mnist_load_fortran test_mnist_fortran ) endif (DALOTIA_WITH_FORTRAN) endif (DALOTIA_WITH_SAFETENSORS_CPP) + +if (DALOTIA_WITH_TENSORFLOW) + add_executable( test_tensorflow test_tensorflow.cpp ) + target_link_libraries( test_tensorflow dalotia_cpp ${tensorflow_LIBRARIES} ) + target_include_directories( test_tensorflow PUBLIC ${tensorflow_INCLUDE_DIRS}) + add_test( tensorflow-file test_tensorflow ) +endif (DALOTIA_WITH_TENSORFLOW) diff --git a/test/test_tensorflow.cpp b/test/test_tensorflow.cpp new file mode 100644 index 0000000..e47d814 --- /dev/null +++ b/test/test_tensorflow.cpp @@ -0,0 +1,60 @@ +#include +#include + +#include "dalotia.h" +#include "dalotia.hpp" + +void test_names() { + std::string filename = "../data/tensorflow_model"; + constexpr dalotia_WeightFormat weightFormat = + dalotia_WeightFormat::dalotia_float_64; + dalotia_Ordering ordering = dalotia_Ordering::dalotia_C_ordering; + + + // test the TensorflowSavedModel class + std::unique_ptr dalotia_file( + dalotia::make_tensor_file(filename)); + if(dalotia_file == nullptr) { + throw std::runtime_error("Failed to open TensorFlow model file: " + filename); + } + auto tensor_names = dalotia_file->get_tensor_names(); + assert(!tensor_names.empty()); + std::cout << "Tensor names in the file: " << std::endl; + for (const auto &name : tensor_names) { + std::cout << " - " << name << std::endl; + } + + for (const auto &name : tensor_names) { + // for all tensor names, check if they are sparse and get their number of dimensions + bool is_sparse = dalotia_file->is_sparse(name); + size_t num_dimensions = dalotia_file->get_num_dimensions(name); + if (num_dimensions < 0) { + throw std::runtime_error("Tensor " + name + " has " + std::to_string(num_dimensions) + " dimensions, which is unexpected."); + } + if (num_dimensions > 1) { + // test get_tensor_extents + auto extents = dalotia_file->get_tensor_extents(name); + std::cout << "Tensor: " << name << ", extents: " << dalotia::to_string(extents) << std::endl; + // test load_tensor_dense + std::unique_ptr tensor( + new dalotia_byte[dalotia::sizeof_weight_format() * dalotia_file->get_num_tensor_elements(name)]); + dalotia_file->load_tensor_dense(name, weightFormat, ordering, tensor.get()); + std::cout << "Loaded tensor: " << name << ", size: " + << dalotia::sizeof_weight_format() * dalotia_file->get_num_tensor_elements(name) << " bytes, contents: " << tensor.get() << std::endl; + } + } +#ifdef DALOTIA_WITH_CPP_PMR + { + std::string tensor_name = "dense/kernel/Read/ReadVariableOp"; + auto [extents, tensor_cpp] = dalotia::load_tensor_dense( + filename, tensor_name, weightFormat, ordering); + } +#endif // DALOTIA_WITH_CPP_PMR +} + +int main(int, char **) { + test_names(); + std::cout << "test_tensorflow succeded" << std::endl; + throw std::runtime_error("test_tensorflow not implemented yet"); + return 0; +} \ No newline at end of file From 75af027b8d21138dbf5460cc3549eb87006a21c4 Mon Sep 17 00:00:00 2001 From: Freifrau von Bleifrei Date: Tue, 8 Jul 2025 11:23:26 +0900 Subject: [PATCH 02/42] CI: try building w/ tensorflow support --- .github/workflows/ctest.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ctest.yml b/.github/workflows/ctest.yml index 63c33f0..248c90a 100644 --- a/.github/workflows/ctest.yml +++ b/.github/workflows/ctest.yml @@ -31,6 +31,7 @@ jobs: uses: threeal/cmake-action@v1.3.0 with: run-build: true + options: -DDALOTIA_WITH_TENSORFLOW=ON build-args: --config ${{ matrix.build_type }} # cxx-compiler: ${{ matrix.compiler }} build-dir: build-${{ matrix.build_type }} From 944596b057960ef8b9d0c49db85706068709296d Mon Sep 17 00:00:00 2001 From: Freifrau von Bleifrei Date: Tue, 8 Jul 2025 11:26:32 +0900 Subject: [PATCH 03/42] CI: try different syntax (?) --- .github/workflows/ctest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ctest.yml b/.github/workflows/ctest.yml index 248c90a..f165fa4 100644 --- a/.github/workflows/ctest.yml +++ b/.github/workflows/ctest.yml @@ -31,7 +31,7 @@ jobs: uses: threeal/cmake-action@v1.3.0 with: run-build: true - options: -DDALOTIA_WITH_TENSORFLOW=ON + options: DALOTIA_WITH_TENSORFLOW=ON build-args: --config ${{ matrix.build_type }} # cxx-compiler: ${{ matrix.compiler }} build-dir: build-${{ matrix.build_type }} From 56c7c356e154dca518cde399517afe69c0950c2f Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Tue, 8 Jul 2025 11:48:30 +0900 Subject: [PATCH 04/42] tf file: simplify tensor name operations --- src/dalotia_tensorflow_file.cpp | 42 +++------------------------------ 1 file changed, 3 insertions(+), 39 deletions(-) diff --git a/src/dalotia_tensorflow_file.cpp b/src/dalotia_tensorflow_file.cpp index 43f8a8a..1e9a0b7 100644 --- a/src/dalotia_tensorflow_file.cpp +++ b/src/dalotia_tensorflow_file.cpp @@ -11,32 +11,8 @@ namespace dalotia { TF_Output get_operation_from_name(const std::string &tensor_name, std::shared_ptr graph) { - size_t pos = 0; - TF_Operation *oper = nullptr; - int output_index = 0; - while ((oper = TF_GraphNextOperation(graph.get(), &pos)) != nullptr) { - int num_outputs = TF_OperationNumOutputs(oper); - const char *op_name = TF_OperationName(oper); - - if (num_outputs > 1) { - if (tensor_name.find(op_name) == std::string::npos) { - continue; // not a match - } - for (int i = 0; i < num_outputs; ++i) { - std::string compare_tensor_name = - std::string(op_name) + "/" + std::to_string(i); - if (tensor_name == compare_tensor_name) { - output_index = i; - break; - } - } - } else if (num_outputs == 1) { - if (tensor_name == op_name) { - break; - } - } - } - return {oper, output_index}; + TF_Operation* oper = TF_GraphOperationByName(graph.get(), tensor_name.c_str()); + return {oper, 0}; } // parts of this code are intensely based on cppflow, esp. tf_status_check and the @@ -114,20 +90,8 @@ TensorflowSavedModel::TensorflowSavedModel(const std::string &filename) size_t pos = 0; TF_Operation *oper; while ((oper = TF_GraphNextOperation(graph_.get(), &pos)) != nullptr) { - int num_outputs = TF_OperationNumOutputs(oper); const char *op_name = TF_OperationName(oper); - - if (num_outputs > 1) { - for (int i = 0; i < num_outputs; ++i) { - std::string tensor_name = - std::string(op_name) + "/" + std::to_string(i); - tensor_names_.push_back(tensor_name); - } - } else if (num_outputs == 1) { - // If there is only one output, we can just use the operation - // name - tensor_names_.push_back(op_name); - } + tensor_names_.emplace_back(op_name); } } } From 57121fff3c8b64c81c3f4c51a2258236b98b90a5 Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Tue, 8 Jul 2025 13:42:23 +0900 Subject: [PATCH 05/42] assignment: change type of *input_shape --- src/dalotia_assignment.cpp | 12 ++++++------ src/dalotia_assignment.hpp | 12 ++++++------ src/dalotia_safetensors_file.cpp | 4 +++- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/dalotia_assignment.cpp b/src/dalotia_assignment.cpp index 3986f9c..b438b17 100644 --- a/src/dalotia_assignment.cpp +++ b/src/dalotia_assignment.cpp @@ -200,7 +200,7 @@ void assign_linearly(dalotia_byte *__restrict__ dest, */ template std::pair, size_t> get_new_strides_permuted( - const size_t *const input_shape, const int *permutation) { + const int *const input_shape, const int *permutation) { auto desired_shape = std::vector(num_dimensions); size_t total_size = 1; for (size_t i = 0; i < num_dimensions; ++i) { @@ -227,7 +227,7 @@ std::pair, size_t> get_new_strides_permuted( template <> void assign_permuted<1>(dalotia_byte *__restrict__ dest, dalotia_WeightFormat weight_output_format, - const size_t *const input_shape, + const int *const input_shape, const dalotia_byte *__restrict__ tensor_start, dalotia_WeightFormat weight_input_format, [[maybe_unused]] const int *permutation) { @@ -239,7 +239,7 @@ void assign_permuted<1>(dalotia_byte *__restrict__ dest, template <> void assign_permuted<2>(dalotia_byte *__restrict__ dest, dalotia_WeightFormat weight_output_format, - const size_t *const input_shape, + const int *const input_shape, const dalotia_byte *__restrict__ tensor_start, dalotia_WeightFormat weight_input_format, const int *permutation) { @@ -275,7 +275,7 @@ void assign_permuted<2>(dalotia_byte *__restrict__ dest, template <> void assign_permuted<3>(dalotia_byte *__restrict__ dest, dalotia_WeightFormat weight_output_format, - const size_t *const input_shape, + const int *const input_shape, const dalotia_byte *__restrict__ tensor_start, dalotia_WeightFormat weight_input_format, const int *permutation) { @@ -319,7 +319,7 @@ void assign_permuted<3>(dalotia_byte *__restrict__ dest, template <> void assign_permuted<4>(dalotia_byte *__restrict__ dest, dalotia_WeightFormat weight_output_format, - const size_t *const input_shape, + const int *const input_shape, const dalotia_byte *__restrict__ tensor_start, dalotia_WeightFormat weight_input_format, const int *permutation) { @@ -368,7 +368,7 @@ void assign_permuted<4>(dalotia_byte *__restrict__ dest, template <> void assign_permuted<5>(dalotia_byte *__restrict__ dest, dalotia_WeightFormat weight_output_format, - const size_t *const input_shape, + const int *const input_shape, const dalotia_byte *__restrict__ tensor_start, dalotia_WeightFormat weight_input_format, const int *permutation) { diff --git a/src/dalotia_assignment.hpp b/src/dalotia_assignment.hpp index b3ae548..8f30eef 100644 --- a/src/dalotia_assignment.hpp +++ b/src/dalotia_assignment.hpp @@ -63,7 +63,7 @@ void assign_linearly(dalotia_byte *__restrict__ dest, template void assign_permuted(dalotia_byte *__restrict__ /*dest*/, dalotia_WeightFormat /*weight_output_format*/, - const size_t *const /*input_shape*/, + const int *const /*input_shape*/, const dalotia_byte *__restrict__ /*tensor_start*/, dalotia_WeightFormat /*weight_input_format*/, const int * /*permutation*/) { @@ -75,7 +75,7 @@ void assign_permuted(dalotia_byte *__restrict__ /*dest*/, template <> void assign_permuted<1>(dalotia_byte *__restrict__ dest, dalotia_WeightFormat weight_output_format, - const size_t *const input_shape, + const int *const input_shape, const dalotia_byte *__restrict__ tensor_start, dalotia_WeightFormat weight_input_format, const int *permutation); @@ -84,7 +84,7 @@ void assign_permuted<1>(dalotia_byte *__restrict__ dest, template <> void assign_permuted<2>(dalotia_byte *__restrict__ dest, dalotia_WeightFormat weight_output_format, - const size_t *const input_shape, + const int *const input_shape, const dalotia_byte *__restrict__ tensor_start, dalotia_WeightFormat weight_input_format, const int *permutation); @@ -93,7 +93,7 @@ void assign_permuted<2>(dalotia_byte *__restrict__ dest, template <> void assign_permuted<3>(dalotia_byte *__restrict__ dest, dalotia_WeightFormat weight_output_format, - const size_t *const input_shape, + const int *const input_shape, const dalotia_byte *__restrict__ tensor_start, dalotia_WeightFormat weight_input_format, const int *permutation); @@ -102,7 +102,7 @@ void assign_permuted<3>(dalotia_byte *__restrict__ dest, template <> void assign_permuted<4>(dalotia_byte *__restrict__ dest, dalotia_WeightFormat weight_output_format, - const size_t *const input_shape, + const int *const input_shape, const dalotia_byte *__restrict__ tensor_start, dalotia_WeightFormat weight_input_format, const int *permutation); @@ -111,7 +111,7 @@ void assign_permuted<4>(dalotia_byte *__restrict__ dest, template <> void assign_permuted<5>(dalotia_byte *__restrict__ dest, dalotia_WeightFormat weight_output_format, - const size_t *const input_shape, + const int *const input_shape, const dalotia_byte *__restrict__ tensor_start, dalotia_WeightFormat weight_input_format, const int *permutation); diff --git a/src/dalotia_safetensors_file.cpp b/src/dalotia_safetensors_file.cpp index 93898e3..e7dc267 100644 --- a/src/dalotia_safetensors_file.cpp +++ b/src/dalotia_safetensors_file.cpp @@ -128,8 +128,10 @@ void SafetensorsFile::load_tensor_dense(const std::string &tensor_name, reinterpret_cast(databuffer) + safetensor.data_offsets[0]; if (!final_permutation_in_c_order.empty()) { + std::vector input_shape( + safetensor.shape.begin(), safetensor.shape.end()); assign_permuted(num_dimensions, tensor, weightFormat, - safetensor.shape.data(), tensor_start, + input_shape.data(), tensor_start, input_weight_format, final_permutation_in_c_order.data()); } else { From 947367b042278f192274cb430f52cb7de1c18dcb Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Tue, 8 Jul 2025 14:01:54 +0900 Subject: [PATCH 06/42] assignment: change some internal size_t to int (amends last commit) --- src/dalotia_assignment.cpp | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/dalotia_assignment.cpp b/src/dalotia_assignment.cpp index b438b17..d648c37 100644 --- a/src/dalotia_assignment.cpp +++ b/src/dalotia_assignment.cpp @@ -201,7 +201,7 @@ void assign_linearly(dalotia_byte *__restrict__ dest, template std::pair, size_t> get_new_strides_permuted( const int *const input_shape, const int *permutation) { - auto desired_shape = std::vector(num_dimensions); + auto desired_shape = std::vector(num_dimensions); size_t total_size = 1; for (size_t i = 0; i < num_dimensions; ++i) { desired_shape[i] = input_shape[permutation[i]]; @@ -244,7 +244,7 @@ void assign_permuted<2>(dalotia_byte *__restrict__ dest, dalotia_WeightFormat weight_input_format, const int *permutation) { constexpr int num_dimensions = 2; - auto desired_shape = std::vector(num_dimensions); + auto desired_shape = std::vector(num_dimensions); [[maybe_unused]] size_t total_size = 1; for (size_t i = 0; i < num_dimensions; ++i) { desired_shape[i] = input_shape[permutation[i]]; @@ -259,8 +259,8 @@ void assign_permuted<2>(dalotia_byte *__restrict__ dest, auto assign_function = get_assignment_function(weight_output_format, weight_input_format); size_t load_index = 0; - for (size_t i = 0; i < input_shape[1]; ++i) { - for (size_t j = 0; j < input_shape[0]; ++j) { + for (int i = 0; i < input_shape[1]; ++i) { + for (int j = 0; j < input_shape[0]; ++j) { auto store_index = j * input_shape[1] + i; auto input_pointer = tensor_start + load_index * load_item_bytes; auto output_pointer = dest + store_index * store_item_bytes; @@ -291,9 +291,9 @@ void assign_permuted<3>(dalotia_byte *__restrict__ dest, get_assignment_function(weight_output_format, weight_input_format); auto input_pointer = tensor_start; size_t store_index = 0; - for (size_t i = 0; i < input_shape[0]; ++i) { - for (size_t j = 0; j < input_shape[1]; ++j) { - for (size_t k = 0; k < input_shape[2]; ++k) { + for (int i = 0; i < input_shape[0]; ++i) { + for (int j = 0; j < input_shape[1]; ++j) { + for (int k = 0; k < input_shape[2]; ++k) { assert(static_cast(store_index) == std::inner_product(new_strides_permuted.begin(), new_strides_permuted.end(), @@ -335,10 +335,10 @@ void assign_permuted<4>(dalotia_byte *__restrict__ dest, get_assignment_function(weight_output_format, weight_input_format); auto input_pointer = tensor_start; size_t store_index = 0; - for (size_t i = 0; i < input_shape[0]; ++i) { - for (size_t j = 0; j < input_shape[1]; ++j) { - for (size_t k = 0; k < input_shape[2]; ++k) { - for (size_t l = 0; l < input_shape[3]; ++l) { + for (int i = 0; i < input_shape[0]; ++i) { + for (int j = 0; j < input_shape[1]; ++j) { + for (int k = 0; k < input_shape[2]; ++k) { + for (int l = 0; l < input_shape[3]; ++l) { assert(static_cast(store_index) == std::inner_product(new_strides_permuted.begin(), new_strides_permuted.end(), @@ -384,11 +384,11 @@ void assign_permuted<5>(dalotia_byte *__restrict__ dest, get_assignment_function(weight_output_format, weight_input_format); auto input_pointer = tensor_start; size_t store_index = 0; - for (size_t i = 0; i < input_shape[0]; ++i) { - for (size_t j = 0; j < input_shape[1]; ++j) { - for (size_t k = 0; k < input_shape[2]; ++k) { - for (size_t l = 0; l < input_shape[3]; ++l) { - for (size_t m = 0; m < input_shape[4]; ++m) { + for (int i = 0; i < input_shape[0]; ++i) { + for (int j = 0; j < input_shape[1]; ++j) { + for (int k = 0; k < input_shape[2]; ++k) { + for (int l = 0; l < input_shape[3]; ++l) { + for (int m = 0; m < input_shape[4]; ++m) { assert(static_cast(store_index) == std::inner_product(new_strides_permuted.begin(), new_strides_permuted.end(), From 42f990af804d06172166d45ec76711e8d6e70a60 Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Tue, 8 Jul 2025 16:03:21 +0900 Subject: [PATCH 07/42] tf file: add load_tensor_dense() --- src/dalotia_tensorflow_file.cpp | 67 ++++++++++++++++++++++++++++++++- src/dalotia_tensorflow_file.hpp | 29 +++++++++++--- test/test_tensorflow.cpp | 39 ++++++++++--------- 3 files changed, 111 insertions(+), 24 deletions(-) diff --git a/src/dalotia_tensorflow_file.cpp b/src/dalotia_tensorflow_file.cpp index 1e9a0b7..0e1a38a 100644 --- a/src/dalotia_tensorflow_file.cpp +++ b/src/dalotia_tensorflow_file.cpp @@ -11,7 +11,7 @@ namespace dalotia { TF_Output get_operation_from_name(const std::string &tensor_name, std::shared_ptr graph) { - TF_Operation* oper = TF_GraphOperationByName(graph.get(), tensor_name.c_str()); + TF_Operation *oper = TF_GraphOperationByName(graph.get(), tensor_name.c_str()); return {oper, 0}; } @@ -113,6 +113,11 @@ size_t TensorflowSavedModel::get_num_dimensions(const std::string &tensor_name) "Tensor not found: " + tensor_name + ". Tensor names in the file: " + to_string(tensor_names_)); } + if (tensor_name == "NoOp") { + // NoOp is a special operation in TensorFlow, it has no dimensions + // (weird vector error otherwise) + return 0; + } int num_dimensions = tf_get_num_dimensions(output, this->graph_, this->status_); if (num_dimensions < 0) { throw std::runtime_error("Failed to get number of dimensions for tensor: " + @@ -158,4 +163,64 @@ TensorflowSavedModel::get_tensor_extents(const std::string &tensor_name, } return extents; } + +void TensorflowSavedModel::load_tensor_dense(const std::string &tensor_name, + dalotia_WeightFormat weightFormat, + dalotia_Ordering ordering, + dalotia_byte *__restrict__ tensor, + const std::vector &permutation) { + TF_Output output = get_operation_from_name(tensor_name, this->graph_); + if (output.oper == nullptr) { + throw std::runtime_error( + "Tensor not found: " + tensor_name + + ". Tensor names in the file: " + to_string(tensor_names_)); + } + const size_t num_tensor_elements = this->get_num_tensor_elements(tensor_name); + std::cout << "dalotia: loading tensor " << tensor_name << " with " + << num_tensor_elements << std::endl; + + TF_Tensor *tf_tensor = nullptr; + TF_SessionRun(this->session_.get(), nullptr, nullptr, nullptr, 0, &output, &tf_tensor, + 1, nullptr, 0, nullptr, this->status_.get()); + std::cout << "dalotia: loaded tensor " << tensor_name << std::endl; + if (tf_tensor == nullptr) { + throw std::runtime_error("Failed to load tensor: " + tensor_name); + } + tf_status_check(this->status_); + + void *databuffer = TF_TensorData(tf_tensor); + int num_dimensions = TF_NumDims(tf_tensor); + + std::cout << "dalotia: loading tensor " << tensor_name << " with " + << TF_TensorElementCount(tf_tensor) + << " elements, num_dimensions: " << num_dimensions << std::endl; + TF_DataType tf_type = TF_TensorType(tf_tensor); + const dalotia_WeightFormat input_weight_format = tensorflow_type_map.at(tf_type); +#ifndef NDEBUG + assert(databuffer != nullptr); + assert(tf_tensor != nullptr); + assert(num_dimensions == static_cast(this->get_num_dimensions(tensor_name))); + assert(num_dimensions >= 0); + assert(TF_TensorElementCount(tf_tensor) == static_cast(num_tensor_elements)); + size_t num_bytes = TF_TensorByteSize(tf_tensor); + assert(num_bytes == + static_cast(dalotia::sizeof_weight_format(input_weight_format)) * + num_tensor_elements); +#endif // NDEBUG + + auto *tensor_start = reinterpret_cast(databuffer); + + auto final_permutation_in_c_order = final_c_permutation_from_permutation_and_order( + permutation, ordering, num_dimensions); + if (!final_permutation_in_c_order.empty()) { + std::vector input_shape = this->get_tensor_extents(tensor_name); + dalotia::assign_permuted(num_dimensions, tensor, weightFormat, input_shape.data(), + tensor_start, input_weight_format, + final_permutation_in_c_order.data()); + } else { + dalotia::assign_linearly(tensor, weightFormat, num_tensor_elements, tensor_start, + input_weight_format); + } + TF_DeleteTensor(tf_tensor); +} } // namespace dalotia \ No newline at end of file diff --git a/src/dalotia_tensorflow_file.hpp b/src/dalotia_tensorflow_file.hpp index 48fab0e..48a10cf 100644 --- a/src/dalotia_tensorflow_file.hpp +++ b/src/dalotia_tensorflow_file.hpp @@ -8,6 +8,24 @@ #include "dalotia_tensor_file.hpp" namespace dalotia { +// https://github.com/tensorflow/tensorflow/blob/master/tensorflow/c/tf_datatype.h +static const std::map tensorflow_type_map{ + {TF_DOUBLE, dalotia_WeightFormat::dalotia_float_64}, + {TF_FLOAT, dalotia_WeightFormat::dalotia_float_32}, + {TF_HALF, dalotia_WeightFormat::dalotia_float_16}, + {TF_BFLOAT16, dalotia_WeightFormat::dalotia_bfloat_16}, + // {TF_BOOL, dalotia_WeightFormat::dalotia_bool}, + {TF_INT8, dalotia_WeightFormat::dalotia_int_8}, + {TF_UINT8, dalotia_WeightFormat::dalotia_uint_8}, + {TF_INT16, dalotia_WeightFormat::dalotia_int_16}, + {TF_UINT16, dalotia_WeightFormat::dalotia_uint_16}, + {TF_INT32, dalotia_WeightFormat::dalotia_int_32}, + {TF_UINT32, dalotia_WeightFormat::dalotia_uint_32}, + // {TF_INT64, dalotia_WeightFormat::dalotia_int_64}, + // {TF_UINT64, dalotia_WeightFormat::dalotia_uint_64}, + // {TF_FLOAT8_E5M2, dalotia_WeightFormat::dalotia_float_8_e5m2}, + // {TF_INT2, dalotia_WeightFormat::dalotia_int_2}, +}; class TensorflowSavedModel : public TensorFile { public: @@ -25,12 +43,11 @@ class TensorflowSavedModel : public TensorFile { get_tensor_extents(const std::string &tensor_name = "", const std::vector &permutation = {}) const override; - // void load_tensor_dense(const std::string &tensor_name, - // dalotia_WeightFormat weightFormat, - // dalotia_Ordering ordering, - // dalotia_byte *__restrict__ tensor, - // const std::vector& permutation = {}) override; - + void load_tensor_dense(const std::string &tensor_name, + dalotia_WeightFormat weightFormat, dalotia_Ordering ordering, + dalotia_byte *__restrict__ tensor, + const std::vector &permutation = {}) override; + // cf. https://github.com/serizba/cppflow/blob/master/include/cppflow/model.h std::shared_ptr status_; std::shared_ptr graph_; diff --git a/test/test_tensorflow.cpp b/test/test_tensorflow.cpp index e47d814..3653ed2 100644 --- a/test/test_tensorflow.cpp +++ b/test/test_tensorflow.cpp @@ -6,55 +6,60 @@ void test_names() { std::string filename = "../data/tensorflow_model"; - constexpr dalotia_WeightFormat weightFormat = - dalotia_WeightFormat::dalotia_float_64; + constexpr dalotia_WeightFormat weightFormat = dalotia_WeightFormat::dalotia_float_64; dalotia_Ordering ordering = dalotia_Ordering::dalotia_C_ordering; - // test the TensorflowSavedModel class std::unique_ptr dalotia_file( dalotia::make_tensor_file(filename)); - if(dalotia_file == nullptr) { + if (dalotia_file == nullptr) { throw std::runtime_error("Failed to open TensorFlow model file: " + filename); } auto tensor_names = dalotia_file->get_tensor_names(); assert(!tensor_names.empty()); std::cout << "Tensor names in the file: " << std::endl; for (const auto &name : tensor_names) { - std::cout << " - " << name << std::endl; + std::cout << " - " << name << std::endl; } for (const auto &name : tensor_names) { - // for all tensor names, check if they are sparse and get their number of dimensions + // for all tensor names, check if they are sparse and get their number of + // dimensions bool is_sparse = dalotia_file->is_sparse(name); + assert(!is_sparse); size_t num_dimensions = dalotia_file->get_num_dimensions(name); if (num_dimensions < 0) { - throw std::runtime_error("Tensor " + name + " has " + std::to_string(num_dimensions) + " dimensions, which is unexpected."); + throw std::runtime_error("Tensor " + name + " has " + + std::to_string(num_dimensions) + + " dimensions, which is unexpected."); } - if (num_dimensions > 1) { + if (num_dimensions > 0) { // test get_tensor_extents auto extents = dalotia_file->get_tensor_extents(name); - std::cout << "Tensor: " << name << ", extents: " << dalotia::to_string(extents) << std::endl; // test load_tensor_dense - std::unique_ptr tensor( - new dalotia_byte[dalotia::sizeof_weight_format() * dalotia_file->get_num_tensor_elements(name)]); - dalotia_file->load_tensor_dense(name, weightFormat, ordering, tensor.get()); - std::cout << "Loaded tensor: " << name << ", size: " - << dalotia::sizeof_weight_format() * dalotia_file->get_num_tensor_elements(name) << " bytes, contents: " << tensor.get() << std::endl; + if (*std::min_element(extents.begin(), extents.end()) > 0) { + std::unique_ptr tensor( + new dalotia_byte[dalotia::sizeof_weight_format() * + dalotia_file->get_num_tensor_elements(name)]); + dalotia_file->load_tensor_dense(name, weightFormat, ordering, + tensor.get()); + } } } #ifdef DALOTIA_WITH_CPP_PMR { std::string tensor_name = "dense/kernel/Read/ReadVariableOp"; auto [extents, tensor_cpp] = dalotia::load_tensor_dense( - filename, tensor_name, weightFormat, ordering); + filename, tensor_name, weightFormat, ordering); // todo float + std::cout << "Tensor extents: " << dalotia::to_string(extents) << std::endl; + std::cout << "Loaded tensor: " << tensor_name << ", size: " << tensor_cpp.size() + << ", contents: " << dalotia::to_string(tensor_cpp) << std::endl; } -#endif // DALOTIA_WITH_CPP_PMR +#endif // DALOTIA_WITH_CPP_PMR } int main(int, char **) { test_names(); std::cout << "test_tensorflow succeded" << std::endl; - throw std::runtime_error("test_tensorflow not implemented yet"); return 0; } \ No newline at end of file From 95c3f745742ec56fecab9d8a9e92e770f96597e4 Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Tue, 8 Jul 2025 16:12:28 +0900 Subject: [PATCH 08/42] tf file: remove accidental debug output --- src/dalotia_tensorflow_file.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/dalotia_tensorflow_file.cpp b/src/dalotia_tensorflow_file.cpp index 0e1a38a..40f4977 100644 --- a/src/dalotia_tensorflow_file.cpp +++ b/src/dalotia_tensorflow_file.cpp @@ -2,7 +2,6 @@ #include #include -#include #include "dalotia_assignment.hpp" #include "dalotia_formats.hpp" @@ -176,13 +175,10 @@ void TensorflowSavedModel::load_tensor_dense(const std::string &tensor_name, ". Tensor names in the file: " + to_string(tensor_names_)); } const size_t num_tensor_elements = this->get_num_tensor_elements(tensor_name); - std::cout << "dalotia: loading tensor " << tensor_name << " with " - << num_tensor_elements << std::endl; TF_Tensor *tf_tensor = nullptr; TF_SessionRun(this->session_.get(), nullptr, nullptr, nullptr, 0, &output, &tf_tensor, 1, nullptr, 0, nullptr, this->status_.get()); - std::cout << "dalotia: loaded tensor " << tensor_name << std::endl; if (tf_tensor == nullptr) { throw std::runtime_error("Failed to load tensor: " + tensor_name); } @@ -191,9 +187,6 @@ void TensorflowSavedModel::load_tensor_dense(const std::string &tensor_name, void *databuffer = TF_TensorData(tf_tensor); int num_dimensions = TF_NumDims(tf_tensor); - std::cout << "dalotia: loading tensor " << tensor_name << " with " - << TF_TensorElementCount(tf_tensor) - << " elements, num_dimensions: " << num_dimensions << std::endl; TF_DataType tf_type = TF_TensorType(tf_tensor); const dalotia_WeightFormat input_weight_format = tensorflow_type_map.at(tf_type); #ifndef NDEBUG From a475648617691553e6ae6e1aca9622df307df36e Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Tue, 8 Jul 2025 16:20:48 +0900 Subject: [PATCH 09/42] tensor file: add error if format mismatch --- src/dalotia_tensor_file.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/dalotia_tensor_file.hpp b/src/dalotia_tensor_file.hpp index b23e656..feb4e8f 100644 --- a/src/dalotia_tensor_file.hpp +++ b/src/dalotia_tensor_file.hpp @@ -117,6 +117,11 @@ class TensorFile { if constexpr (std::is_same_v) { tensor.resize(total_size * sizeof_weight_format(weight_format)); } else { + if (dalotia::sizeof_weight_format(weight_format) != + sizeof(value_type)) { + throw std::runtime_error( + "load_tensor_dense: weight format size does not match value type size"); + } tensor.resize(total_size); } this->load_tensor_dense(tensor_name, weight_format, ordering, From c59ea8f739123c879fb4d23427e6b75cd70db4db Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Tue, 8 Jul 2025 16:21:41 +0900 Subject: [PATCH 10/42] test tf: try loading to float too --- test/test_tensorflow.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/test/test_tensorflow.cpp b/test/test_tensorflow.cpp index 3653ed2..e4d0f79 100644 --- a/test/test_tensorflow.cpp +++ b/test/test_tensorflow.cpp @@ -49,11 +49,20 @@ void test_names() { #ifdef DALOTIA_WITH_CPP_PMR { std::string tensor_name = "dense/kernel/Read/ReadVariableOp"; - auto [extents, tensor_cpp] = dalotia::load_tensor_dense( - filename, tensor_name, weightFormat, ordering); // todo float - std::cout << "Tensor extents: " << dalotia::to_string(extents) << std::endl; - std::cout << "Loaded tensor: " << tensor_name << ", size: " << tensor_cpp.size() - << ", contents: " << dalotia::to_string(tensor_cpp) << std::endl; + auto [extents, tensor_cpp_double] = dalotia::load_tensor_dense( + filename, tensor_name, weightFormat, ordering); + assert(!extents.empty()); + assert(!tensor_cpp_double.empty()); + auto [extents_float, tensor_cpp_float] = + dalotia::load_tensor_dense(filename, tensor_name, dalotia_WeightFormat::dalotia_float_32, + ordering); + for (size_t i = 0; i < extents.size(); ++i) { + assert(extents[i] == extents_float[i]); + } + for (size_t i = 0; i < tensor_cpp_double.size(); ++i) { + assert (tensor_cpp_float[i] == + static_cast(tensor_cpp_double[i])); + } } #endif // DALOTIA_WITH_CPP_PMR } From ba1740571f5255c7ef411ed557d1fbfea73efdac Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Wed, 9 Jul 2025 08:42:58 +0900 Subject: [PATCH 11/42] data: create own tf model (with reslayer) --- data/generate_tf.py | 68 ++++++++++++++++++ data/tensorflow_model/keras_metadata.pb | 19 +++++ data/tensorflow_model/saved_model.pb | Bin 48550 -> 189249 bytes .../variables/variables.data-00000-of-00001 | Bin 1652 -> 32226 bytes .../variables/variables.index | Bin 387 -> 1338 bytes 5 files changed, 87 insertions(+) create mode 100755 data/generate_tf.py create mode 100644 data/tensorflow_model/keras_metadata.pb diff --git a/data/generate_tf.py b/data/generate_tf.py new file mode 100755 index 0000000..fa8819f --- /dev/null +++ b/data/generate_tf.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +import tf_keras as keras # use keras version 2 by installing tf-keras +from tf_keras import layers, models, initializers + + +def residual_block(inputs, filters, kernel_size=3, stride=1): + x = layers.Conv2D( + filters, + kernel_size, + strides=stride, + padding="same", + kernel_initializer=initializers.Constant(0.1), + )(inputs) + x = layers.BatchNormalization()(x) + x = layers.ReLU()(x) + + x = layers.Conv2D( + filters, + kernel_size, + strides=1, + padding="same", + kernel_initializer=initializers.Constant(0.2), + )(x) + x = layers.BatchNormalization()(x) + + # Shortcut connection + if stride != 1 or inputs.shape[-1] != filters: + shortcut = layers.Conv2D( + filters, + 1, + strides=stride, + padding="same", + kernel_initializer=initializers.Constant(0.3), + )(inputs) + shortcut = layers.BatchNormalization()(shortcut) + else: + shortcut = inputs + + x = layers.Add()([x, shortcut]) + return layers.ReLU()(x) + + +def build_resnet(input_shape=(16, 16, 3), num_classes=10): + inputs = keras.Input(shape=input_shape) + x = layers.Conv2D( + 16, 3, padding="same", kernel_initializer=initializers.Constant(0.4) + )(inputs) + x = layers.BatchNormalization()(x) + x = layers.ReLU()(x) + + x = residual_block(x, 16) + + x = layers.GlobalAveragePooling2D()(x) + outputs = layers.Dense( + num_classes, activation="softmax", kernel_initializer=initializers.GlorotNormal(seed=133) + )(x) + + return models.Model(inputs, outputs) + + +if __name__ == "__main__": + model = build_resnet() + model.save("tensorflow_model", save_format="tf") + + for layer in model.layers: + print(f"{layer.name}:") + for weight in layer.weights: + print(f" {weight.name} = {weight.numpy()}") \ No newline at end of file diff --git a/data/tensorflow_model/keras_metadata.pb b/data/tensorflow_model/keras_metadata.pb new file mode 100644 index 0000000..ef2f891 --- /dev/null +++ b/data/tensorflow_model/keras_metadata.pb @@ -0,0 +1,19 @@ + +˜nroot"_tf_keras_network*öm{"name": "model", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": false, "class_name": "Functional", "config": {"name": "model", "trainable": true, "layers": [{"class_name": "InputLayer", "config": {"batch_input_shape": {"class_name": "__tuple__", "items": [null, 16, 16, 3]}, "dtype": "float32", "sparse": false, "ragged": false, "name": "input_1"}, "name": "input_1", "inbound_nodes": []}, {"class_name": "Conv2D", "config": {"name": "conv2d", "trainable": true, "dtype": "float32", "filters": 16, "kernel_size": {"class_name": "__tuple__", "items": [3, 3]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "Constant", "config": {"value": 0.4}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d", "inbound_nodes": [[["input_1", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization", "inbound_nodes": [[["conv2d", 0, 0, {}]]]}, {"class_name": "ReLU", "config": {"name": "re_lu", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "name": "re_lu", "inbound_nodes": [[["batch_normalization", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_1", "trainable": true, "dtype": "float32", "filters": 16, "kernel_size": {"class_name": "__tuple__", "items": [3, 3]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "Constant", "config": {"value": 0.1}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_1", "inbound_nodes": [[["re_lu", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_1", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_1", "inbound_nodes": [[["conv2d_1", 0, 0, {}]]]}, {"class_name": "ReLU", "config": {"name": "re_lu_1", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "name": "re_lu_1", "inbound_nodes": [[["batch_normalization_1", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_2", "trainable": true, "dtype": "float32", "filters": 16, "kernel_size": {"class_name": "__tuple__", "items": [3, 3]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "Constant", "config": {"value": 0.2}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_2", "inbound_nodes": [[["re_lu_1", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_2", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_2", "inbound_nodes": [[["conv2d_2", 0, 0, {}]]]}, {"class_name": "Add", "config": {"name": "add", "trainable": true, "dtype": "float32"}, "name": "add", "inbound_nodes": [[["batch_normalization_2", 0, 0, {}], ["re_lu", 0, 0, {}]]]}, {"class_name": "ReLU", "config": {"name": "re_lu_2", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "name": "re_lu_2", "inbound_nodes": [[["add", 0, 0, {}]]]}, {"class_name": "GlobalAveragePooling2D", "config": {"name": "global_average_pooling2d", "trainable": true, "dtype": "float32", "data_format": "channels_last", "keepdims": false}, "name": "global_average_pooling2d", "inbound_nodes": [[["re_lu_2", 0, 0, {}]]]}, {"class_name": "Dense", "config": {"name": "dense", "trainable": true, "dtype": "float32", "units": 10, "activation": "softmax", "use_bias": true, "kernel_initializer": {"class_name": "GlorotNormal", "config": {"seed": 133}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "dense", "inbound_nodes": [[["global_average_pooling2d", 0, 0, {}]]]}], "input_layers": [["input_1", 0, 0]], "output_layers": [["dense", 0, 0]]}, "shared_object_id": 33, "input_spec": [{"class_name": "InputSpec", "config": {"dtype": null, "shape": {"class_name": "__tuple__", "items": [null, 16, 16, 3]}, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {}}}], "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 16, 3]}, "is_graph_network": true, "full_save_spec": {"class_name": "__tuple__", "items": [[{"class_name": "TypeSpec", "type_spec": "tf.TensorSpec", "serialized": {"class_name": "__tuple__", "items": [{"class_name": "TensorShape", "items": [null, 16, 16, 3]}, "float32", "input_1"]}}], {}]}, "save_spec": {"class_name": "TypeSpec", "type_spec": "tf.TensorSpec", "serialized": {"class_name": "__tuple__", "items": [{"class_name": "TensorShape", "items": [null, 16, 16, 3]}, "float32", "input_1"]}}, "keras_version": "2.19.0", "backend": "tensorflow", "model_config": {"class_name": "Functional", "config": {"name": "model", "trainable": true, "layers": [{"class_name": "InputLayer", "config": {"batch_input_shape": {"class_name": "__tuple__", "items": [null, 16, 16, 3]}, "dtype": "float32", "sparse": false, "ragged": false, "name": "input_1"}, "name": "input_1", "inbound_nodes": [], "shared_object_id": 0}, {"class_name": "Conv2D", "config": {"name": "conv2d", "trainable": true, "dtype": "float32", "filters": 16, "kernel_size": {"class_name": "__tuple__", "items": [3, 3]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "Constant", "config": {"value": 0.4}, "shared_object_id": 1}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 2}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d", "inbound_nodes": [[["input_1", 0, 0, {}]]], "shared_object_id": 3}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 4}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 5}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 6}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 7}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization", "inbound_nodes": [[["conv2d", 0, 0, {}]]], "shared_object_id": 8}, {"class_name": "ReLU", "config": {"name": "re_lu", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "name": "re_lu", "inbound_nodes": [[["batch_normalization", 0, 0, {}]]], "shared_object_id": 9}, {"class_name": "Conv2D", "config": {"name": "conv2d_1", "trainable": true, "dtype": "float32", "filters": 16, "kernel_size": {"class_name": "__tuple__", "items": [3, 3]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "Constant", "config": {"value": 0.1}, "shared_object_id": 10}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 11}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_1", "inbound_nodes": [[["re_lu", 0, 0, {}]]], "shared_object_id": 12}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_1", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 13}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 14}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 15}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 16}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_1", "inbound_nodes": [[["conv2d_1", 0, 0, {}]]], "shared_object_id": 17}, {"class_name": "ReLU", "config": {"name": "re_lu_1", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "name": "re_lu_1", "inbound_nodes": [[["batch_normalization_1", 0, 0, {}]]], "shared_object_id": 18}, {"class_name": "Conv2D", "config": {"name": "conv2d_2", "trainable": true, "dtype": "float32", "filters": 16, "kernel_size": {"class_name": "__tuple__", "items": [3, 3]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "Constant", "config": {"value": 0.2}, "shared_object_id": 19}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 20}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_2", "inbound_nodes": [[["re_lu_1", 0, 0, {}]]], "shared_object_id": 21}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_2", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 22}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 23}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 24}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 25}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_2", "inbound_nodes": [[["conv2d_2", 0, 0, {}]]], "shared_object_id": 26}, {"class_name": "Add", "config": {"name": "add", "trainable": true, "dtype": "float32"}, "name": "add", "inbound_nodes": [[["batch_normalization_2", 0, 0, {}], ["re_lu", 0, 0, {}]]], "shared_object_id": 27}, {"class_name": "ReLU", "config": {"name": "re_lu_2", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "name": "re_lu_2", "inbound_nodes": [[["add", 0, 0, {}]]], "shared_object_id": 28}, {"class_name": "GlobalAveragePooling2D", "config": {"name": "global_average_pooling2d", "trainable": true, "dtype": "float32", "data_format": "channels_last", "keepdims": false}, "name": "global_average_pooling2d", "inbound_nodes": [[["re_lu_2", 0, 0, {}]]], "shared_object_id": 29}, {"class_name": "Dense", "config": {"name": "dense", "trainable": true, "dtype": "float32", "units": 10, "activation": "softmax", "use_bias": true, "kernel_initializer": {"class_name": "GlorotNormal", "config": {"seed": 133}, "shared_object_id": 30}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 31}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "dense", "inbound_nodes": [[["global_average_pooling2d", 0, 0, {}]]], "shared_object_id": 32}], "input_layers": [["input_1", 0, 0]], "output_layers": [["dense", 0, 0]]}}}2 +† root.layer-0"_tf_keras_input_layer*Ö{"class_name": "InputLayer", "name": "input_1", "dtype": "float32", "sparse": false, "ragged": false, "batch_input_shape": {"class_name": "__tuple__", "items": [null, 16, 16, 3]}, "config": {"batch_input_shape": {"class_name": "__tuple__", "items": [null, 16, 16, 3]}, "dtype": "float32", "sparse": false, "ragged": false, "name": "input_1"}}2 +© +root.layer_with_weights-0"_tf_keras_layer*ò {"name": "conv2d", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv2d", "trainable": true, "dtype": "float32", "filters": 16, "kernel_size": {"class_name": "__tuple__", "items": [3, 3]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "Constant", "config": {"value": 0.4}, "shared_object_id": 1}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 2}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "inbound_nodes": [[["input_1", 0, 0, {}]]], "shared_object_id": 3, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 3}}, "shared_object_id": 35}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 16, 3]}}2 +ô root.layer_with_weights-1"_tf_keras_layer*½ {"name": "batch_normalization", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 4}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 5}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 6}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 7}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "inbound_nodes": [[["conv2d", 0, 0, {}]]], "shared_object_id": 8, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 16}}, "shared_object_id": 36}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 16, 16]}}2 +Ç root.layer-3"_tf_keras_layer*{"name": "re_lu", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "re_lu", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "inbound_nodes": [[["batch_normalization", 0, 0, {}]]], "shared_object_id": 9, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 16, 16]}}2 +° +root.layer_with_weights-2"_tf_keras_layer*ù {"name": "conv2d_1", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv2d_1", "trainable": true, "dtype": "float32", "filters": 16, "kernel_size": {"class_name": "__tuple__", "items": [3, 3]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "Constant", "config": {"value": 0.1}, "shared_object_id": 10}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 11}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "inbound_nodes": [[["re_lu", 0, 0, {}]]], "shared_object_id": 12, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 16}}, "shared_object_id": 37}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 16, 16]}}2 +ÿ root.layer_with_weights-3"_tf_keras_layer*È {"name": "batch_normalization_1", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_1", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 13}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 14}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 15}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 16}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "inbound_nodes": [[["conv2d_1", 0, 0, {}]]], "shared_object_id": 17, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 16}}, "shared_object_id": 38}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 16, 16]}}2 +Î root.layer-6"_tf_keras_layer*¤{"name": "re_lu_1", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "re_lu_1", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "inbound_nodes": [[["batch_normalization_1", 0, 0, {}]]], "shared_object_id": 18, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 16, 16]}}2 +² +root.layer_with_weights-4"_tf_keras_layer*û {"name": "conv2d_2", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv2d_2", "trainable": true, "dtype": "float32", "filters": 16, "kernel_size": {"class_name": "__tuple__", "items": [3, 3]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "Constant", "config": {"value": 0.2}, "shared_object_id": 19}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 20}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "inbound_nodes": [[["re_lu_1", 0, 0, {}]]], "shared_object_id": 21, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 16}}, "shared_object_id": 39}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 16, 16]}}2 +ÿ  root.layer_with_weights-5"_tf_keras_layer*È {"name": "batch_normalization_2", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_2", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 22}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 23}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 24}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 25}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "inbound_nodes": [[["conv2d_2", 0, 0, {}]]], "shared_object_id": 26, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 16}}, "shared_object_id": 40}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 16, 16]}}2 +Ü + root.layer-9"_tf_keras_layer*²{"name": "add", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Add", "config": {"name": "add", "trainable": true, "dtype": "float32"}, "inbound_nodes": [[["batch_normalization_2", 0, 0, {}], ["re_lu", 0, 0, {}]]], "shared_object_id": 27, "build_input_shape": [{"class_name": "TensorShape", "items": [null, 16, 16, 16]}, {"class_name": "TensorShape", "items": [null, 16, 16, 16]}]}2 +½  root.layer-10"_tf_keras_layer*’{"name": "re_lu_2", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "re_lu_2", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "inbound_nodes": [[["add", 0, 0, {}]]], "shared_object_id": 28, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 16, 16]}}2 +”  root.layer-11"_tf_keras_layer*é{"name": "global_average_pooling2d", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "GlobalAveragePooling2D", "config": {"name": "global_average_pooling2d", "trainable": true, "dtype": "float32", "data_format": "channels_last", "keepdims": false}, "inbound_nodes": [[["re_lu_2", 0, 0, {}]]], "shared_object_id": 29, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {}}, "shared_object_id": 41}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 16, 16]}}2 +Á root.layer_with_weights-6"_tf_keras_layer*Š{"name": "dense", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Dense", "config": {"name": "dense", "trainable": true, "dtype": "float32", "units": 10, "activation": "softmax", "use_bias": true, "kernel_initializer": {"class_name": "GlorotNormal", "config": {"seed": 133}, "shared_object_id": 30}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 31}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "inbound_nodes": [[["global_average_pooling2d", 0, 0, {}]]], "shared_object_id": 32, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 2, "axes": {"-1": 16}}, "shared_object_id": 42}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16]}}2 \ No newline at end of file diff --git a/data/tensorflow_model/saved_model.pb b/data/tensorflow_model/saved_model.pb index 9cd005803833fe7fd5731eabbc3e4fe0f42f2908..b82c6d7f8311d266b4ca2eb43968f71b9b5a53be 100644 GIT binary patch literal 189249 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 F{{zPv+$I13 diff --git a/data/tensorflow_model/variables/variables.data-00000-of-00001 b/data/tensorflow_model/variables/variables.data-00000-of-00001 index def833ba27a25cd14edb479fd4c58740e665ebe3..df01897de4698c93fd911408e65478ffbc5e9682 100644 GIT binary patch literal 32226 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 literal 1652 zcmZ2*C4)I8j$V?B9az|1_mGg)iw;?=kAo6s}gPGQ3~+dcm- z+6mnGZ08ZoYwxk?o!tqwReKd|@7V45S!8F{$zt!z?y}!txBUKD&Ia~7^xf%Mr(=$qnfim1c8K^QtpfYBx$_#vP}#? ze=%^$aIpe?#%KVfc!U@Yfs~*SqY;o25@IwaAiI{4tib2y5Eo+4O)V+POfClcRzipk zoJfkf1UV#ycp(V}CN0k)B_s_I!XCxa9MVGKdB7+qBAOYvB80-Y_`)26JRO~UT;szW zeL`J@cv4dHic|F=aVuqpEx|%1_4OP>LV`S+ zB!$mZ1};9d_(A7`x90#&UaUh2o2l9mCKmlJIpd|Mwp$Lp{;sb|Mydl0g zfJx#DtuUbw46`Az3p5*NEI_32nG1;pV7>t55@5bC1jh<89~=v$XHaeqMdVmeLW%`t l%vew%6bq{OVgaQnVBiu3dW8|>dLjD!U=4)B_G{e0vm ztSDrtpOjjXh+jX{Rz8@m3PS0Lxw%AXY5-|U&swxkR81&1zbrE^Jw7)zF^?Gg8u+31 z$tVkzB^G5S<|U^RqwfGnAIM6e?~I5J(}oC`?-b=p4O39~3Me!PXf$}3ZZ8YJ3bfdm z=wNM#gjq~NupR)pmb_p+0JD$8U~Pzk2dl6N(ZPCP5hKidZ!XzbPutrEK7B(X?xVaP>fQ5{P>GZj_ zLZXTi#3W-5P|^irpw^{~8V#(bEAEADWdMQ#W(Ec(5F=r-uoZ}A1Q7>5E!@Ds$ehdY egcB}ids1=hOHPh&octgm{2=^yL$^w)`)vT4S5~6{ delta 183 zcmdnR)y&Mpz`(}AD8G;Y5FRK1PKeMhzCzWz~XKqKcv*rEV~#j0zP% zLpG^77BK(;BZx@97jYQQIq+%W1_nmvT!ytwa53@NiCZ=?aqMT}pDf6#$jK1?yP;d9 H)crO9Jf0%L From 4138b266f0cf225eae18d5eeb50aebd8b3c26fe8 Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Wed, 9 Jul 2025 08:44:14 +0900 Subject: [PATCH 12/42] test: move assert_close to header --- test/test_helper.h | 10 ++++++++++ test/test_load.c | 9 +-------- 2 files changed, 11 insertions(+), 8 deletions(-) create mode 100644 test/test_helper.h diff --git a/test/test_helper.h b/test/test_helper.h new file mode 100644 index 0000000..79ef406 --- /dev/null +++ b/test/test_helper.h @@ -0,0 +1,10 @@ +#pragma once +#include +#include + +void assert_close(volatile float a, volatile float b) { + if (fabsf(a - b) > 1e-4) { + fprintf(stderr, "assert_close: expected %f but got %f\n", b, a); + assert(false); + } +} \ No newline at end of file diff --git a/test/test_load.c b/test/test_load.c index 5604395..89781a9 100644 --- a/test/test_load.c +++ b/test/test_load.c @@ -1,10 +1,10 @@ #include -#include #include #include #include #include "dalotia.h" +#include "test_helper.h" void test_get_tensor_names(const char* filename) { DalotiaTensorFile* dalotia_file = dalotia_open_file(filename); @@ -31,13 +31,6 @@ void test_get_tensor_names(const char* filename) { dalotia_close_file(dalotia_file); } -void assert_close(volatile float a, volatile float b) { - if (fabsf(a - b) > 1e-4) { - fprintf(stderr, "assert_close: expected %f but got %f\n", b, a); - assert(false); - } -} - void test_load(const char* filename, const char* tensor_name) { DalotiaTensorFile* dalotia_file = dalotia_open_file(filename); { From c6b4aef12093a9b6ec8da9b31faba33f1b21b050 Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Wed, 9 Jul 2025 09:33:33 +0900 Subject: [PATCH 13/42] test: test tensor values --- test/test_tensorflow.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/test/test_tensorflow.cpp b/test/test_tensorflow.cpp index e4d0f79..b5b5e2f 100644 --- a/test/test_tensorflow.cpp +++ b/test/test_tensorflow.cpp @@ -3,6 +3,7 @@ #include "dalotia.h" #include "dalotia.hpp" +#include "test_helper.h" void test_names() { std::string filename = "../data/tensorflow_model"; @@ -53,16 +54,21 @@ void test_names() { filename, tensor_name, weightFormat, ordering); assert(!extents.empty()); assert(!tensor_cpp_double.empty()); - auto [extents_float, tensor_cpp_float] = - dalotia::load_tensor_dense(filename, tensor_name, dalotia_WeightFormat::dalotia_float_32, - ordering); + auto [extents_float, tensor_cpp_float] = dalotia::load_tensor_dense( + filename, tensor_name, dalotia_WeightFormat::dalotia_float_32, ordering); for (size_t i = 0; i < extents.size(); ++i) { assert(extents[i] == extents_float[i]); } for (size_t i = 0; i < tensor_cpp_double.size(); ++i) { - assert (tensor_cpp_float[i] == - static_cast(tensor_cpp_double[i])); + assert(tensor_cpp_float[i] == static_cast(tensor_cpp_double[i])); } + std::vector true_values_begin = { + -0.25138268, -0.25613192, 0.16491315, -0.13381714, 0.35687172, -0.35824186, + 0.3529436, -0.55490106, 0.27651784, 0.30784482, -0.2846631}; + for (size_t i = 0; i < true_values_begin.size(); ++i) { + assert_close(tensor_cpp_double[i], true_values_begin[i]); + } + assert_close(tensor_cpp_double.back(), -0.21346514); } #endif // DALOTIA_WITH_CPP_PMR } From 3c152ec2c6f66cb11e5be6afb66c4d1882547832 Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Wed, 9 Jul 2025 09:54:12 +0900 Subject: [PATCH 14/42] tf file: cache tensor pointers --- src/dalotia_tensorflow_file.cpp | 52 +++++++++++++++++++++------------ src/dalotia_tensorflow_file.hpp | 5 ++++ 2 files changed, 39 insertions(+), 18 deletions(-) diff --git a/src/dalotia_tensorflow_file.cpp b/src/dalotia_tensorflow_file.cpp index 40f4977..1184ea3 100644 --- a/src/dalotia_tensorflow_file.cpp +++ b/src/dalotia_tensorflow_file.cpp @@ -168,24 +168,10 @@ void TensorflowSavedModel::load_tensor_dense(const std::string &tensor_name, dalotia_Ordering ordering, dalotia_byte *__restrict__ tensor, const std::vector &permutation) { - TF_Output output = get_operation_from_name(tensor_name, this->graph_); - if (output.oper == nullptr) { - throw std::runtime_error( - "Tensor not found: " + tensor_name + - ". Tensor names in the file: " + to_string(tensor_names_)); - } - const size_t num_tensor_elements = this->get_num_tensor_elements(tensor_name); - - TF_Tensor *tf_tensor = nullptr; - TF_SessionRun(this->session_.get(), nullptr, nullptr, nullptr, 0, &output, &tf_tensor, - 1, nullptr, 0, nullptr, this->status_.get()); - if (tf_tensor == nullptr) { - throw std::runtime_error("Failed to load tensor: " + tensor_name); - } - tf_status_check(this->status_); - + const TF_Tensor *tf_tensor = this->get_tensor_pointer_from_name(tensor_name); void *databuffer = TF_TensorData(tf_tensor); int num_dimensions = TF_NumDims(tf_tensor); + const int64_t num_tensor_elements = TF_TensorElementCount(tf_tensor); TF_DataType tf_type = TF_TensorType(tf_tensor); const dalotia_WeightFormat input_weight_format = tensorflow_type_map.at(tf_type); @@ -194,7 +180,8 @@ void TensorflowSavedModel::load_tensor_dense(const std::string &tensor_name, assert(tf_tensor != nullptr); assert(num_dimensions == static_cast(this->get_num_dimensions(tensor_name))); assert(num_dimensions >= 0); - assert(TF_TensorElementCount(tf_tensor) == static_cast(num_tensor_elements)); + assert(num_tensor_elements == + static_cast(this->get_num_tensor_elements(tensor_name))); size_t num_bytes = TF_TensorByteSize(tf_tensor); assert(num_bytes == static_cast(dalotia::sizeof_weight_format(input_weight_format)) * @@ -214,6 +201,35 @@ void TensorflowSavedModel::load_tensor_dense(const std::string &tensor_name, dalotia::assign_linearly(tensor, weightFormat, num_tensor_elements, tensor_start, input_weight_format); } - TF_DeleteTensor(tf_tensor); +} + +const TF_Tensor * +TensorflowSavedModel::get_tensor_pointer_from_name(const std::string &tensor_name) { + // check if it is already in the cache + auto it = tensors_.find(tensor_name); + if (it != tensors_.end()) { + return it->second.get(); + } else { + // if not, load it from the graph + TF_Output output = get_operation_from_name(tensor_name, this->graph_); + if (output.oper == nullptr) { + throw std::runtime_error( + "Tensor not found: " + tensor_name + + ". Tensor names in the file: " + to_string(tensor_names_)); + } + + TF_Tensor *tf_tensor = nullptr; + TF_SessionRun(this->session_.get(), nullptr, nullptr, nullptr, 0, &output, + &tf_tensor, 1, nullptr, 0, nullptr, this->status_.get()); + if (tf_tensor == nullptr) { + throw std::runtime_error("Failed to load tensor: " + tensor_name); + } + tf_status_check(this->status_); + auto [position, inserted] = this->tensors_.emplace( + tensor_name, std::unique_ptr( + tf_tensor, &TF_DeleteTensor)); + assert(inserted); // should not already exist + return position->second.get(); + } } } // namespace dalotia \ No newline at end of file diff --git a/src/dalotia_tensorflow_file.hpp b/src/dalotia_tensorflow_file.hpp index 48a10cf..277733b 100644 --- a/src/dalotia_tensorflow_file.hpp +++ b/src/dalotia_tensorflow_file.hpp @@ -53,6 +53,11 @@ class TensorflowSavedModel : public TensorFile { std::shared_ptr graph_; std::shared_ptr session_; std::vector tensor_names_; + std::map> + tensors_; // cache for loaded tensor pointers + + private: + const TF_Tensor *get_tensor_pointer_from_name(const std::string &tensor_name); }; } // namespace dalotia \ No newline at end of file From 9212938e91c7a15bc9865f7ec5b87875eae67575 Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Wed, 9 Jul 2025 10:18:03 +0900 Subject: [PATCH 15/42] tf file: get tf data pointers too --- src/dalotia_tensorflow_file.cpp | 7 +++++++ src/dalotia_tensorflow_file.hpp | 2 ++ test/test_tensorflow.cpp | 15 +++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/src/dalotia_tensorflow_file.cpp b/src/dalotia_tensorflow_file.cpp index 1184ea3..756d317 100644 --- a/src/dalotia_tensorflow_file.cpp +++ b/src/dalotia_tensorflow_file.cpp @@ -203,6 +203,13 @@ void TensorflowSavedModel::load_tensor_dense(const std::string &tensor_name, } } +std::vector +TensorflowSavedModel::get_tensor_pointers(const std::string &tensor_name) { + const TF_Tensor *tf_tensor = this->get_tensor_pointer_from_name(tensor_name); + return std::vector( + 1, reinterpret_cast(TF_TensorData(tf_tensor))); +} + const TF_Tensor * TensorflowSavedModel::get_tensor_pointer_from_name(const std::string &tensor_name) { // check if it is already in the cache diff --git a/src/dalotia_tensorflow_file.hpp b/src/dalotia_tensorflow_file.hpp index 277733b..2eac9b9 100644 --- a/src/dalotia_tensorflow_file.hpp +++ b/src/dalotia_tensorflow_file.hpp @@ -48,6 +48,8 @@ class TensorflowSavedModel : public TensorFile { dalotia_byte *__restrict__ tensor, const std::vector &permutation = {}) override; + std::vector get_tensor_pointers(const std::string &tensor_name); + // cf. https://github.com/serizba/cppflow/blob/master/include/cppflow/model.h std::shared_ptr status_; std::shared_ptr graph_; diff --git a/test/test_tensorflow.cpp b/test/test_tensorflow.cpp index b5b5e2f..2e23594 100644 --- a/test/test_tensorflow.cpp +++ b/test/test_tensorflow.cpp @@ -3,6 +3,7 @@ #include "dalotia.h" #include "dalotia.hpp" +#include "dalotia_tensorflow_file.hpp" #include "test_helper.h" void test_names() { @@ -69,6 +70,20 @@ void test_names() { assert_close(tensor_cpp_double[i], true_values_begin[i]); } assert_close(tensor_cpp_double.back(), -0.21346514); + + // check if this is also what is in the original buffer + if (auto dalotia_tensorflow_file = + dynamic_cast(dalotia_file.get())) { + const dalotia_byte *tensor_pointer = + dalotia_tensorflow_file->get_tensor_pointers(tensor_name)[0]; + const float *tensor_float_pointer = + reinterpret_cast(tensor_pointer); + for (size_t i = 0; i < tensor_cpp_float.size(); ++i) { + assert_equal(tensor_cpp_float[i], tensor_float_pointer[i]); + } + } else { + throw std::runtime_error("dalotia_file is not a TensorflowSavedModel"); + } } #endif // DALOTIA_WITH_CPP_PMR } From ed1731166ddf65c22d0171e4fdf1dceff643ee1d Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Wed, 9 Jul 2025 10:20:52 +0900 Subject: [PATCH 16/42] test helper: assert_equal() (amends last commit) --- test/test_helper.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/test_helper.h b/test/test_helper.h index 79ef406..527ef2c 100644 --- a/test/test_helper.h +++ b/test/test_helper.h @@ -7,4 +7,11 @@ void assert_close(volatile float a, volatile float b) { fprintf(stderr, "assert_close: expected %f but got %f\n", b, a); assert(false); } +} + +void assert_equal(volatile float a, volatile float b) { + if (a != b) { + fprintf(stderr, "assert_equal: expected %f but got %f\n", b, a); + assert(false); + } } \ No newline at end of file From 305b44463b8325d5e1820ef6144b5e5de79918bf Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Wed, 9 Jul 2025 13:00:56 +0900 Subject: [PATCH 17/42] test: memcheck suppressions for tensorflow --- test/CMakeLists.txt | 6 ++++++ test/tensorflow.supp | 14 ++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 test/tensorflow.supp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 9214a65..4e3b264 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,3 +1,9 @@ +list(APPEND CMAKE_CTEST_ARGUMENTS "--output-on-failure") + +# cf. https://stackoverflow.com/questions/52730994/how-to-pass-arguments-to-memcheck-with-ctest +set(MEMORYCHECK_COMMAND_OPTIONS + "--suppressions=${CMAKE_CURRENT_SOURCE_DIR}/tensorflow.supp" +) include (CTest) if(DALOTIA_WITH_SAFETENSORS_CPP) diff --git a/test/tensorflow.supp b/test/tensorflow.supp new file mode 100644 index 0000000..dbda913 --- /dev/null +++ b/test/tensorflow.supp @@ -0,0 +1,14 @@ +{ + value8_tensorflow_and_deps + Memcheck:Value8 + ... + fun:_ZN10tensorflow10checkpoint25CheckpointCallbackManager32GetCheckpointIdAndPathFromPrefixB5cxx11ESt17basic_string_viewIcSt11char_traitsIcEE + ... +} +{ + cond_isOnePass_tensorflow_and_deps + Memcheck:Cond + ... + fun:_ZN10tensorflow10checkpoint25CheckpointCallbackManager32GetCheckpointIdAndPathFromPrefixB5cxx11ESt17basic_string_viewIcSt11char_traitsIcEE + ... +} \ No newline at end of file From 2803f55f9e97f1664ce4db9662c69c7f1c231190 Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Wed, 9 Jul 2025 13:40:53 +0900 Subject: [PATCH 18/42] dalotia: optimistically add keras extension too --- src/dalotia.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dalotia.cpp b/src/dalotia.cpp index b128899..3f7946a 100644 --- a/src/dalotia.cpp +++ b/src/dalotia.cpp @@ -54,7 +54,7 @@ TensorFile *make_tensor_file(const std::string &filename) { #else // DALOTIA_WITH_SAFETENSORS_CPP throw std::runtime_error("Safetensors support not enabled"); #endif // DALOTIA_WITH_SAFETENSORS_CPP - } else if (extension == "pb" || is_directory(filename.c_str())) { + } else if (extension == "keras" || extension == "pb" || is_directory(filename.c_str())) { #ifdef DALOTIA_WITH_TENSORFLOW return new TensorflowSavedModel(filename); #else // DALOTIA_WITH_TENSORFLOW From 94c3e4351fde6e88e164c5f035a70c502a5a4b4e Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Wed, 9 Jul 2025 13:59:44 +0900 Subject: [PATCH 19/42] ctest: try to output memcheck logs to terminal (bc disappeared from command line w/ suppressions file) --- .github/workflows/ctest.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ctest.yml b/.github/workflows/ctest.yml index f165fa4..6b15704 100644 --- a/.github/workflows/ctest.yml +++ b/.github/workflows/ctest.yml @@ -41,3 +41,5 @@ jobs: with: test-dir: build-${{ matrix.build_type }} args: --output-on-failure -T memcheck + - name: Output Memcheck Log + run: cat /home/runner/work/dalotia/dalotia/build-*/Testing/Temporary/MemoryChecker.*.log From d7c9be6a70308d50a54f7118626248f14f0d1386 Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Wed, 9 Jul 2025 14:40:30 +0900 Subject: [PATCH 20/42] ctest: try leak-check=full too --- test/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4e3b264..813b0d6 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -2,7 +2,7 @@ list(APPEND CMAKE_CTEST_ARGUMENTS "--output-on-failure") # cf. https://stackoverflow.com/questions/52730994/how-to-pass-arguments-to-memcheck-with-ctest set(MEMORYCHECK_COMMAND_OPTIONS - "--suppressions=${CMAKE_CURRENT_SOURCE_DIR}/tensorflow.supp" + "--suppressions=${CMAKE_CURRENT_SOURCE_DIR}/tensorflow.supp --leak-check=full" ) include (CTest) From d0877a575a4e798b8d6b78bc5da75c1b3862dc7d Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Wed, 9 Jul 2025 15:43:27 +0900 Subject: [PATCH 21/42] tf suppressions: add possible leaks --- test/tensorflow.supp | 127 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/test/tensorflow.supp b/test/tensorflow.supp index dbda913..1581fda 100644 --- a/test/tensorflow.supp +++ b/test/tensorflow.supp @@ -11,4 +11,131 @@ ... fun:_ZN10tensorflow10checkpoint25CheckpointCallbackManager32GetCheckpointIdAndPathFromPrefixB5cxx11ESt17basic_string_viewIcSt11char_traitsIcEE ... +} +{ + possible_leak_tensorflow_sub_I_host + Memcheck:Leak + ... + fun:_GLOBAL__sub_I_host_platform.cc + ... +} +{ + possible_leak_tensorflow_cutlass_gemm + Memcheck:Leak + match-leak-kinds: possible + ... + fun:_GLOBAL__sub_I_cutlass_gemm_fusion.cc + ... +} +{ + possible_leak_tensorflow_shape_refiner + Memcheck:Leak + match-leak-kinds: possible + ... + fun:_ZN10tensorflow12ShapeRefinerC1EiPKNS_19OpRegistryInterfaceE + ... +} +{ + possible_leak_tensorflow_graph_registry + Memcheck:Leak + match-leak-kinds: possible + ... + fun:_ZNK10tensorflow10OpRegistry10LookUpSlowERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE + ... +} +{ + possible_leak_tensorflow_saved_model_internal + Memcheck:Leak + match-leak-kinds: possible + ... + fun:_ZN10tensorflow22LoadSavedModelInternalERKNS_14SessionOptionsERKNS_10RunOptionsERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERKSt13unordered_setISB_St4hashISB_ESt8equal_toISB_ESaISB_EEPNS_16SavedModelBundleE + ... +} +{ + possible_leak_tensorflow_kernel_registrar + Memcheck:Leak + match-leak-kinds: possible + ... + fun:_ZN10tensorflow14kernel_factory17OpKernelRegistrarC2EPKNS_9KernelDefESt17basic_string_viewIcSt11char_traitsIcEEPFPNS_8OpKernelEPNS_20OpKernelConstructionEE + ... +} +{ + possible_leak_tensorflow_register_kernel_builder + Memcheck:Leak + match-leak-kinds: possible + ... + fun:TF_RegisterKernelBuilderWithKernelDef + ... +} +{ + possible_leak_tensorflow_dataset_experiment_registrar + Memcheck:Leak + match-leak-kinds: possible + ... + fun:_ZN10tensorflow4data26DatasetExperimentRegistrarC2ERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESt8functionIFbmEESA_IFblbEE + ... +} +{ + possible_leak_tensorflow_absl_hash_set_policy + Memcheck:Leak + match-leak-kinds: possible + ... + fun:_ZNK4absl12lts_2023080218container_internal12raw_hash_setINS1_17FlatHashSetPolicyINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEEENS1_10StringHashENS1_8StringEqESaIS9_EE19EmplaceDecomposableclIPKcJRKSI_EEESt4pairINSE_8iteratorEbERKT_DpOT0_ + ... +} +{ + possible_leak_tensorflow_absl_container_internal + Memcheck:Leak + match-leak-kinds: possible + ... + fun:_ZN4absl12lts_2023080218container_internal* + ... +} +{ + possible_leak_tensorflow_absl_flags_internal + Memcheck:Leak + match-leak-kinds: possible + ... + fun:_ZN4absl12lts_2023080214flags_internal23RegisterCommandLineFlagERNS0_15CommandLineFlagEPKc + ... +} +{ + possible_leak_tensorflow_proto_parse_from_string + Memcheck:Leak + match-leak-kinds: possible + ... + fun:_ZN10tensorflow20ProtoParseFromStringERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEPNS_9AttrValueE + ... +} +{ + possible_leak_tensorflow_base_gpu_device_factory + Memcheck:Leak + match-leak-kinds: possible + ... + fun:_ZN10tensorflow20BaseGPUDeviceFactory13CreateDevicesERKNS_14SessionOptionsERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEPSt6vectorISt10unique_ptrINS_6DeviceESt14default_deleteISE_EESaISH_EE + ... +} +{ + possible_leak_tensorflow_build_graph + Memcheck:Leak + match-leak-kinds: possible + ... + fun:_ZN10tensorflow19GraphExecutionState10BuildGraphERKNS_17BuildGraphOptionsEPSt10unique_ptrINS_11ClientGraphESt14default_deleteIS5_EE + ... +} +{ + possible_leak_tensorflow_local_Device + Memcheck:Leak + match-leak-kinds: possible + ... + fun:_ZN10tensorflow11LocalDevice19EigenThreadPoolInfoC2ERKNS_14SessionOptionsEiPN3tsl9AllocatorE + ... +} +{ + possible_leak_tensorflow_kernel_def + Memcheck:Leak + match-leak-kinds: possible + ... + fun:_ZN10tensorflow24KernelDef_AttrConstraint9MergeImplERN6google8protobuf7MessageERKS3_ + ... } \ No newline at end of file From 4978d86d3d87b9229261addf4b360738a89b8a1f Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Wed, 9 Jul 2025 15:52:08 +0900 Subject: [PATCH 22/42] ctest: try gen-suppressions=all --- test/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 813b0d6..5863bb0 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -2,7 +2,7 @@ list(APPEND CMAKE_CTEST_ARGUMENTS "--output-on-failure") # cf. https://stackoverflow.com/questions/52730994/how-to-pass-arguments-to-memcheck-with-ctest set(MEMORYCHECK_COMMAND_OPTIONS - "--suppressions=${CMAKE_CURRENT_SOURCE_DIR}/tensorflow.supp --leak-check=full" + "--gen-suppressions=all --suppressions=${CMAKE_CURRENT_SOURCE_DIR}/tensorflow.supp --leak-check=full" ) include (CTest) From d72eb44dbc026552b504060e54f079990d1f7b8e Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Wed, 9 Jul 2025 16:01:20 +0900 Subject: [PATCH 23/42] tf supp: more possible leaks (in all tests) --- test/tensorflow.supp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/tensorflow.supp b/test/tensorflow.supp index 1581fda..340f656 100644 --- a/test/tensorflow.supp +++ b/test/tensorflow.supp @@ -138,4 +138,12 @@ ... fun:_ZN10tensorflow24KernelDef_AttrConstraint9MergeImplERN6google8protobuf7MessageERKS3_ ... -} \ No newline at end of file +} +{ + possible_leak_tensorflow_register_op + Memcheck:Leak + match-leak-kinds: possible + ... + fun:_ZN10tensorflow11register_op* + ... +} From d56d31446fb61874019b8f37bff23d9e4b0257ae Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Wed, 9 Jul 2025 16:03:11 +0900 Subject: [PATCH 24/42] ctest: some more log printing --- .github/workflows/ctest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ctest.yml b/.github/workflows/ctest.yml index 6b15704..287e182 100644 --- a/.github/workflows/ctest.yml +++ b/.github/workflows/ctest.yml @@ -42,4 +42,4 @@ jobs: test-dir: build-${{ matrix.build_type }} args: --output-on-failure -T memcheck - name: Output Memcheck Log - run: cat /home/runner/work/dalotia/dalotia/build-*/Testing/Temporary/MemoryChecker.*.log + run: for i in /home/runner/work/dalotia/dalotia/build-*/Testing/Temporary/MemoryChecker.*.log ; do echo $i ; cat $i ; done From 48b13c218564b6fb513d12fd14106728a78b8438 Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Wed, 9 Jul 2025 16:12:02 +0900 Subject: [PATCH 25/42] tf supp: more possible leaks (in all tests) --- test/tensorflow.supp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/tensorflow.supp b/test/tensorflow.supp index 340f656..0b27b13 100644 --- a/test/tensorflow.supp +++ b/test/tensorflow.supp @@ -19,6 +19,14 @@ fun:_GLOBAL__sub_I_host_platform.cc ... } +{ + possible_leak_tensorflow_sub_I_cuda_platform + Memcheck:Leak + match-leak-kinds: possible + fun:_Znwm + fun:_GLOBAL__sub_I_cuda_platform.cc + ... +} { possible_leak_tensorflow_cutlass_gemm Memcheck:Leak From f91e7712a6686ca2e74ce333639b665a8f4e42d2 Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Wed, 9 Jul 2025 16:14:20 +0900 Subject: [PATCH 26/42] tf supp: more possible leaks (in all tests) --- test/tensorflow.supp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/tensorflow.supp b/test/tensorflow.supp index 0b27b13..7a9c3b9 100644 --- a/test/tensorflow.supp +++ b/test/tensorflow.supp @@ -155,3 +155,11 @@ fun:_ZN10tensorflow11register_op* ... } +{ + possible_leak_tensorflow_new_op_definition_builder + Memcheck:Leak + match-leak-kinds: possible + ... + fun:TF_NewOpDefinitionBuilder + ... +} From 42d17e5f0d3103c23a832081654bb9caf8e7ef69 Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Wed, 9 Jul 2025 16:16:38 +0900 Subject: [PATCH 27/42] tf supp: make general supps more concrete --- test/tensorflow.supp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/tensorflow.supp b/test/tensorflow.supp index 7a9c3b9..cb33895 100644 --- a/test/tensorflow.supp +++ b/test/tensorflow.supp @@ -18,14 +18,19 @@ ... fun:_GLOBAL__sub_I_host_platform.cc ... + fun:_dl_init + ... } { possible_leak_tensorflow_sub_I_cuda_platform Memcheck:Leak match-leak-kinds: possible fun:_Znwm + ... fun:_GLOBAL__sub_I_cuda_platform.cc ... + fun:_dl_init + ... } { possible_leak_tensorflow_cutlass_gemm @@ -154,6 +159,8 @@ ... fun:_ZN10tensorflow11register_op* ... + fun:_dl_init + ... } { possible_leak_tensorflow_new_op_definition_builder @@ -162,4 +169,6 @@ ... fun:TF_NewOpDefinitionBuilder ... -} + fun:_dl_init + ... +} \ No newline at end of file From eaa1c688c827d6756766dc162c9d1bacbdda0c9b Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Wed, 9 Jul 2025 16:37:59 +0900 Subject: [PATCH 28/42] tf supp: make general supps slightly less concrete (partly reverts last commit) --- test/tensorflow.supp | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/tensorflow.supp b/test/tensorflow.supp index cb33895..2be6bbe 100644 --- a/test/tensorflow.supp +++ b/test/tensorflow.supp @@ -159,8 +159,6 @@ ... fun:_ZN10tensorflow11register_op* ... - fun:_dl_init - ... } { possible_leak_tensorflow_new_op_definition_builder From 032a18b8f9afcdcb10762a01d553c286fd69df35 Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Wed, 9 Jul 2025 16:55:26 +0900 Subject: [PATCH 29/42] tf supp: reorder and comment for posterity --- test/tensorflow.supp | 67 ++++++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/test/tensorflow.supp b/test/tensorflow.supp index 2be6bbe..551e3cf 100644 --- a/test/tensorflow.supp +++ b/test/tensorflow.supp @@ -12,6 +12,7 @@ fun:_ZN10tensorflow10checkpoint25CheckpointCallbackManager32GetCheckpointIdAndPathFromPrefixB5cxx11ESt17basic_string_viewIcSt11char_traitsIcEE ... } +# now, possible mem leaks, first the ones that don't reference tf in frame trace at all: { possible_leak_tensorflow_sub_I_host Memcheck:Leak @@ -39,29 +40,36 @@ ... fun:_GLOBAL__sub_I_cutlass_gemm_fusion.cc ... + fun:_dl_init + ... } +# then, tf calls that originate in init and appear in everything linked with tf { - possible_leak_tensorflow_shape_refiner + possible_leak_tensorflow_register_op Memcheck:Leak match-leak-kinds: possible ... - fun:_ZN10tensorflow12ShapeRefinerC1EiPKNS_19OpRegistryInterfaceE + fun:_ZN10tensorflow11register_op* ... } { - possible_leak_tensorflow_graph_registry + possible_leak_tensorflow_new_op_definition_builder Memcheck:Leak match-leak-kinds: possible ... - fun:_ZNK10tensorflow10OpRegistry10LookUpSlowERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE + fun:TF_NewOpDefinitionBuilder + ... + fun:_dl_init ... } { - possible_leak_tensorflow_saved_model_internal + possible_leak_tensorflow_register_kernel_builder Memcheck:Leak match-leak-kinds: possible ... - fun:_ZN10tensorflow22LoadSavedModelInternalERKNS_14SessionOptionsERKNS_10RunOptionsERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERKSt13unordered_setISB_St4hashISB_ESt8equal_toISB_ESaISB_EEPNS_16SavedModelBundleE + fun:TF_RegisterKernelBuilderWithKernelDef + ... + fun:_dl_init ... } { @@ -71,21 +79,42 @@ ... fun:_ZN10tensorflow14kernel_factory17OpKernelRegistrarC2EPKNS_9KernelDefESt17basic_string_viewIcSt11char_traitsIcEEPFPNS_8OpKernelEPNS_20OpKernelConstructionEE ... + fun:_dl_init + ... } { - possible_leak_tensorflow_register_kernel_builder + possible_leak_tensorflow_dataset_experiment_registrar Memcheck:Leak match-leak-kinds: possible ... - fun:TF_RegisterKernelBuilderWithKernelDef + fun:_ZN10tensorflow4data26DatasetExperimentRegistrarC2ERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESt8functionIFbmEESA_IFblbEE + ... + fun:_dl_init ... } +# and then the actual ones that show up when running tensorflow { - possible_leak_tensorflow_dataset_experiment_registrar + possible_leak_tensorflow_shape_refiner Memcheck:Leak match-leak-kinds: possible ... - fun:_ZN10tensorflow4data26DatasetExperimentRegistrarC2ERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESt8functionIFbmEESA_IFblbEE + fun:_ZN10tensorflow12ShapeRefinerC1EiPKNS_19OpRegistryInterfaceE + ... +} +{ + possible_leak_tensorflow_graph_registry + Memcheck:Leak + match-leak-kinds: possible + ... + fun:_ZNK10tensorflow10OpRegistry10LookUpSlowERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE + ... +} +{ + possible_leak_tensorflow_saved_model_internal + Memcheck:Leak + match-leak-kinds: possible + ... + fun:_ZN10tensorflow22LoadSavedModelInternalERKNS_14SessionOptionsERKNS_10RunOptionsERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERKSt13unordered_setISB_St4hashISB_ESt8equal_toISB_ESaISB_EEPNS_16SavedModelBundleE ... } { @@ -152,21 +181,3 @@ fun:_ZN10tensorflow24KernelDef_AttrConstraint9MergeImplERN6google8protobuf7MessageERKS3_ ... } -{ - possible_leak_tensorflow_register_op - Memcheck:Leak - match-leak-kinds: possible - ... - fun:_ZN10tensorflow11register_op* - ... -} -{ - possible_leak_tensorflow_new_op_definition_builder - Memcheck:Leak - match-leak-kinds: possible - ... - fun:TF_NewOpDefinitionBuilder - ... - fun:_dl_init - ... -} \ No newline at end of file From a1cb522ab466192f7302c7128162b091716a1c8b Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Wed, 9 Jul 2025 17:26:50 +0900 Subject: [PATCH 30/42] cmake: add tensorflow target --- CMakeLists.txt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6883b7f..cf6d9a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -70,7 +70,6 @@ if (DALOTIA_WITH_TENSORFLOW) FetchContent_Declare( tensorflow URL https://storage.googleapis.com/tensorflow/versions/2.18.0/libtensorflow-gpu-linux-x86_64.tar.gz - OVERRIDE_FIND_PACKAGE ) FetchContent_MakeAvailable(tensorflow) @@ -85,8 +84,19 @@ if (DALOTIA_WITH_TENSORFLOW) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(tensorflow DEFAULT_MSG tensorflow_INCLUDE_DIRS tensorflow_LIBRARIES) + else() + find_package(tensorflow REQUIRED CMAKE_FIND_ROOT_PATH_BOTH) + endif() + if(NOT TARGET tensorflow::tensorflow) + add_library(tensorflow::tensorflow SHARED IMPORTED) + set_target_properties( + tensorflow::tensorflow + PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${tensorflow_LIBRARIES}" + INTERFACE_INCLUDE_DIRECTORIES "${tensorflow_INCLUDE_DIRS}" + ) endif() - find_package(tensorflow REQUIRED CMAKE_FIND_ROOT_PATH_BOTH) endif (DALOTIA_WITH_TENSORFLOW) add_subdirectory(src) # target dalotia_cpp is generated here From 7cd7a6a48b736790a9da5a7854eef5d6f3cf1012 Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Wed, 9 Jul 2025 18:35:29 +0900 Subject: [PATCH 31/42] cmake: install imported tensorflow dependency --- CMakeLists.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cf6d9a2..0afb682 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,6 +96,13 @@ if (DALOTIA_WITH_TENSORFLOW) IMPORTED_LOCATION "${tensorflow_LIBRARIES}" INTERFACE_INCLUDE_DIRECTORIES "${tensorflow_INCLUDE_DIRS}" ) + # cf. https://stackoverflow.com/a/41179630/7272382 + install(FILES ${tensorflow_INCLUDE_DIRS}/tensorflow/c/c_api.h + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/tensorflow/c + ) + install(FILES ${tensorflow_LIBRARIES} + DESTINATION ${CMAKE_INSTALL_LIBDIR} + ) endif() endif (DALOTIA_WITH_TENSORFLOW) @@ -118,7 +125,6 @@ if (DALOTIA_CPP_BUILD_EXAMPLES) endif (DALOTIA_CPP_BUILD_EXAMPLES) if (DALOTIA_BUILD_TESTS) - list(APPEND CMAKE_CTEST_ARGUMENTS "--output-on-failure") enable_testing() add_subdirectory(test) endif (DALOTIA_BUILD_TESTS) From 8578598edbbd8000f11985dda83ec457ddb28313 Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Wed, 9 Jul 2025 18:38:12 +0900 Subject: [PATCH 32/42] cmake: use tensorflow target in src and test --- src/CMakeLists.txt | 2 +- test/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 046b8c7..3b8a7db 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -27,7 +27,7 @@ if (DALOTIA_WITH_SAFETENSORS_CPP) endif (DALOTIA_WITH_SAFETENSORS_CPP) if (DALOTIA_WITH_TENSORFLOW) - target_link_libraries(dalotia_cpp PUBLIC ${tensorflow_LIBRARIES}) + target_link_libraries(dalotia_cpp PUBLIC tensorflow::tensorflow) target_include_directories(dalotia_cpp PUBLIC $ $ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5863bb0..2156828 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -33,7 +33,7 @@ endif (DALOTIA_WITH_SAFETENSORS_CPP) if (DALOTIA_WITH_TENSORFLOW) add_executable( test_tensorflow test_tensorflow.cpp ) - target_link_libraries( test_tensorflow dalotia_cpp ${tensorflow_LIBRARIES} ) + target_link_libraries( test_tensorflow dalotia_cpp tensorflow::tensorflow ) target_include_directories( test_tensorflow PUBLIC ${tensorflow_INCLUDE_DIRS}) add_test( tensorflow-file test_tensorflow ) endif (DALOTIA_WITH_TENSORFLOW) From 334904d8dda5a1e2db7d465a1ee3d43349ab9be8 Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Wed, 9 Jul 2025 18:48:48 +0900 Subject: [PATCH 33/42] cmake: use GNUInstallDirs --- CMakeLists.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0afb682..ae03361 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,9 @@ endif (DALOTIA_WITH_FORTRAN) set(CMAKE_CXX_STANDARD 17) set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${CMAKE_SOURCE_DIR}/cmake/modules") +include(GNUInstallDirs) +set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}) + # if this is empty, will be set at config time # (build with --config Release or --config Debug etc.) message(STATUS "CMAKE BUILD TYPE: ${CMAKE_BUILD_TYPE}") @@ -137,12 +140,12 @@ write_basic_package_version_file( COMPATIBILITY SameMajorVersion ) install(FILES $/dalotia-config-version.cmake - DESTINATION lib/cmake/dalotia + DESTINATION ${INSTALL_CONFIGDIR} ) # export full library interface install(EXPORT dalotia FILE dalotia-config.cmake NAMESPACE dalotia:: - DESTINATION lib/cmake/dalotia + DESTINATION ${INSTALL_CONFIGDIR} ) From b0e328ea4b9b77038cd60ef6a2e6ea9753179032 Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Wed, 9 Jul 2025 18:50:08 +0900 Subject: [PATCH 34/42] cmake: rename project to dalotia and export set to dalotia_export_set --- CMakeLists.txt | 6 +++--- src/CMakeLists.txt | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ae03361..fab84b2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.24) -project(DALOTIA CXX C) +project(dalotia CXX C) option(DALOTIA_CPP_BUILD_EXAMPLES "Build examples" ON) option(DALOTIA_BUILD_TESTS "Build tests" ON) @@ -54,7 +54,7 @@ if (DALOTIA_WITH_SAFETENSORS_CPP) ) # TODO this is not good, every package should install its own targets # cf. https://discourse.cmake.org/t/propagation-of-fetchcontent-targets-when-installing/2559/2 - install(TARGETS safetensors_cpp EXPORT dalotia + install(TARGETS safetensors_cpp EXPORT dalotia_export_set LIBRARY DESTINATION lib ARCHIVE DESTINATION lib RUNTIME DESTINATION bin @@ -144,7 +144,7 @@ install(FILES $/dalotia-config-vers ) # export full library interface -install(EXPORT dalotia +install(EXPORT dalotia_export_set FILE dalotia-config.cmake NAMESPACE dalotia:: DESTINATION ${INSTALL_CONFIGDIR} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3b8a7db..91e079a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -50,14 +50,14 @@ else() target_compile_options(dalotia_cpp PRIVATE -Wall -Wextra) endif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") -install(TARGETS dalotia_cpp EXPORT dalotia +install(TARGETS dalotia_cpp EXPORT dalotia_export_set LIBRARY DESTINATION lib INCLUDES DESTINATION include PUBLIC_HEADER DESTINATION include ) if (DALOTIA_WITH_FORTRAN) - install(TARGETS dalotia_fortran EXPORT dalotia + install(TARGETS dalotia_fortran EXPORT dalotia_export_set LIBRARY DESTINATION lib INCLUDES DESTINATION include PUBLIC_HEADER DESTINATION include From c7d914c154b18c3b80ccaf36351f9efc726b68f0 Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Wed, 9 Jul 2025 21:28:12 +0900 Subject: [PATCH 35/42] ci: try dev-build with tests --- .github/workflows/spack.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/spack.yml b/.github/workflows/spack.yml index 971350a..ff56355 100644 --- a/.github/workflows/spack.yml +++ b/.github/workflows/spack.yml @@ -26,5 +26,5 @@ jobs: run: | spack spec dalotia spack info dalotia - spack dev-build dalotia@main + spack dev-build --test=all dalotia@main \ No newline at end of file From 699a98062d085f642903d83d5790ee81eca58e25 Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Wed, 9 Jul 2025 21:58:08 +0900 Subject: [PATCH 36/42] spack package: remove test variant --- .github/workflows/spack.yml | 2 +- spack_repo_dalotia/packages/dalotia/package.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/spack.yml b/.github/workflows/spack.yml index ff56355..774a6c9 100644 --- a/.github/workflows/spack.yml +++ b/.github/workflows/spack.yml @@ -26,5 +26,5 @@ jobs: run: | spack spec dalotia spack info dalotia - spack dev-build --test=all dalotia@main + spack dev-build --test=root dalotia@main \ No newline at end of file diff --git a/spack_repo_dalotia/packages/dalotia/package.py b/spack_repo_dalotia/packages/dalotia/package.py index 5cd634b..fece9db 100644 --- a/spack_repo_dalotia/packages/dalotia/package.py +++ b/spack_repo_dalotia/packages/dalotia/package.py @@ -20,7 +20,6 @@ class Dalotia(CMakePackage): version("main", branch="main") version("1.0.0", tag="v1.0.0") - variant("tests", default=True, description="build dalotia tests") variant("cpp_pmr", default=True, description="use polymorphic memory resources (pmr) C++17 feature for dalotia") variant("openmp", default=True, description="Build with OpenMP support") variant("safetensorscpp", default=True, description="use safetensors-cpp for tensor I/O") @@ -36,7 +35,6 @@ class Dalotia(CMakePackage): def cmake_args(self): args = [ self.define("DALOTIA_CPP_BUILD_EXAMPLES", True), - self.define_from_variant("DALOTIA_BUILD_TESTS", "tests"), self.define_from_variant("DALOTIA_WITH_CPP_PMR", "cpp_pmr"), self.define_from_variant("DALOTIA_WITH_OPENMP", "openmp"), self.define_from_variant("DALOTIA_WITH_SAFETENSORS_CPP", "safetensorscpp"), From b9f5ed5049d092874c9c4dd7699fa4e6ccac8511 Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Thu, 10 Jul 2025 08:02:28 +0900 Subject: [PATCH 37/42] spack: add and test tensorflow variant --- .github/workflows/spack.yml | 6 ++++++ spack_repo_dalotia/packages/dalotia/package.py | 2 ++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/spack.yml b/.github/workflows/spack.yml index 774a6c9..3af8cd7 100644 --- a/.github/workflows/spack.yml +++ b/.github/workflows/spack.yml @@ -27,4 +27,10 @@ jobs: spack spec dalotia spack info dalotia spack dev-build --test=root dalotia@main + - name: info and install dalotia with tensorflow + shell: spack-bash {0} + run: | + spack spec dalotia~safetensorscpp+tensorflow + spack info dalotia~safetensorscpp+tensorflow + spack dev-build --test=root dalotia@main ~safetensorscpp+tensorflow \ No newline at end of file diff --git a/spack_repo_dalotia/packages/dalotia/package.py b/spack_repo_dalotia/packages/dalotia/package.py index fece9db..725982a 100644 --- a/spack_repo_dalotia/packages/dalotia/package.py +++ b/spack_repo_dalotia/packages/dalotia/package.py @@ -24,6 +24,7 @@ class Dalotia(CMakePackage): variant("openmp", default=True, description="Build with OpenMP support") variant("safetensorscpp", default=True, description="use safetensors-cpp for tensor I/O") variant("fortran", default=True, description="Build Fortran interface") + variant("tensorflow", default=False, description="Build with TensorFlow support") depends_on("cxx", type="build") depends_on("c", type="build") @@ -38,6 +39,7 @@ def cmake_args(self): self.define_from_variant("DALOTIA_WITH_CPP_PMR", "cpp_pmr"), self.define_from_variant("DALOTIA_WITH_OPENMP", "openmp"), self.define_from_variant("DALOTIA_WITH_SAFETENSORS_CPP", "safetensorscpp"), + self.define_from_variant("DALOTIA_WITH_TENSORFLOW", "tensorflow"), self.define_from_variant("DALOTIA_WITH_FORTRAN", "fortran"), ] if self.spec.satisfies("+safetensorscpp"): From b76d3950a04838186078095d8101496d64f36fe8 Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Thu, 10 Jul 2025 11:10:29 +0900 Subject: [PATCH 38/42] cmake: better export set handling --- CMakeLists.txt | 33 ++++++++++++++++++++------------- cmake/dalotia-config.cmake.in | 12 ++++++++++++ src/CMakeLists.txt | 6 +----- 3 files changed, 33 insertions(+), 18 deletions(-) create mode 100644 cmake/dalotia-config.cmake.in diff --git a/CMakeLists.txt b/CMakeLists.txt index fab84b2..2801691 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,14 +33,14 @@ message(STATUS "CMAKE BUILD TYPE: ${CMAKE_BUILD_TYPE}") file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/ DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/) # safetensors-cpp +set(DALOTIA_EXTERNAL_SAFETENSORS_CPP OFF) if (DALOTIA_WITH_SAFETENSORS_CPP) - if (NOT DEFINED safetensors-cpp_DIR OR safetensors-cpp_DIR MATCHES "fetch") + if (NOT DEFINED safetensors-cpp_DIR OR safetensors-cpp_DIR MATCHES "fetch" AND (NOT DEFINED safetensors-cpp_FOUND OR NOT safetensors-cpp_FOUND)) include(FetchContent) FetchContent_Declare( safetensors-cpp GIT_REPOSITORY https://github.com/syoyo/safetensors-cpp.git GIT_TAG a88953c981c6773760540592fa97f04619a8f825 - OVERRIDE_FIND_PACKAGE ) set(SAFETENSORS_CPP_CXX_EXCEPTIONS true) #TODO maybe depend on language? set(SAFETENSORS_CPP_BUILD_EXAMPLES false) @@ -52,17 +52,10 @@ if (DALOTIA_WITH_SAFETENSORS_CPP) target_include_directories(safetensors_cpp PUBLIC $ ) - # TODO this is not good, every package should install its own targets - # cf. https://discourse.cmake.org/t/propagation-of-fetchcontent-targets-when-installing/2559/2 - install(TARGETS safetensors_cpp EXPORT dalotia_export_set - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib - RUNTIME DESTINATION bin - INCLUDES DESTINATION include - ) else() # to pass safetensors-cpp_DIR on the command line: find_package(safetensors-cpp REQUIRED CMAKE_FIND_ROOT_PATH_BOTH) + set(DALOTIA_EXTERNAL_SAFETENSORS_CPP ON) endif() endif (DALOTIA_WITH_SAFETENSORS_CPP) @@ -98,7 +91,7 @@ if (DALOTIA_WITH_TENSORFLOW) IMPORTED_LINK_INTERFACE_LANGUAGES "C" IMPORTED_LOCATION "${tensorflow_LIBRARIES}" INTERFACE_INCLUDE_DIRECTORIES "${tensorflow_INCLUDE_DIRS}" - ) + )# $ $ # cf. https://stackoverflow.com/a/41179630/7272382 install(FILES ${tensorflow_INCLUDE_DIRS}/tensorflow/c/c_api.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/tensorflow/c @@ -132,8 +125,22 @@ if (DALOTIA_BUILD_TESTS) add_subdirectory(test) endif (DALOTIA_BUILD_TESTS) -# install version info etc. + +# install dependencies, version info, etc. include(CMakePackageConfigHelpers) +configure_package_config_file( + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/${PROJECT_NAME}-config.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake + INSTALL_DESTINATION ${INSTALL_CONFIGDIR} + PATH_VARS DALOTIA_EXTERNAL_SAFETENSORS_CPP +) +# # Install find modules +# install(DIRECTORY cmake/modules/ DESTINATION ${INSTALL_CONFIGDIR}/modules) + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake + DESTINATION ${INSTALL_CONFIGDIR} +) write_basic_package_version_file( "dalotia-config-version.cmake" VERSION 0.0.1 @@ -145,7 +152,7 @@ install(FILES $/dalotia-config-vers # export full library interface install(EXPORT dalotia_export_set - FILE dalotia-config.cmake + FILE ${PROJECT_NAME}-targets.cmake NAMESPACE dalotia:: DESTINATION ${INSTALL_CONFIGDIR} ) diff --git a/cmake/dalotia-config.cmake.in b/cmake/dalotia-config.cmake.in new file mode 100644 index 0000000..beef9d7 --- /dev/null +++ b/cmake/dalotia-config.cmake.in @@ -0,0 +1,12 @@ +get_filename_component(dalotia_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) + +if(NOT TARGET dalotia::dalotia) + if(@DALOTIA_EXTERNAL_SAFETENSORS_CPP@) + # if we wanted to provide a FindSafetensors-cpp.cmake file: + set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${dalotia_CMAKE_DIR}/modules") + find_package(safetensors-cpp REQUIRED) + else() + include("${dalotia_CMAKE_DIR}/../safetensors-cpp-config.cmake") + endif() + include("${dalotia_CMAKE_DIR}/dalotia-targets.cmake") +endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 91e079a..07e3eab 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,7 +1,7 @@ add_library(dalotia_cpp dalotia.cpp) # Daniel Pfeifer says: no variables target_sources(dalotia_cpp PRIVATE dalotia_assignment.cpp dalotia_formats.cpp ) set_target_properties(dalotia_cpp PROPERTIES PUBLIC_HEADER - "dalotia.h;dalotia_formats.h;dalotia.hpp;dalotia_formats.hpp;dalotia_assignment.hpp;dalotia_tensor_file.hpp;dalotia_safetensors_file.hpp") + "dalotia.h;dalotia_formats.h;dalotia.hpp;dalotia_formats.hpp;dalotia_assignment.hpp;dalotia_tensor_file.hpp;dalotia_safetensors_file.hpp;dalotia_tensorflow_file.hpp") # have one dalotia library target that can be used in C++ and Fortran add_library(dalotia INTERFACE) add_library(dalotia::dalotia_cpp ALIAS dalotia_cpp) @@ -28,10 +28,6 @@ endif (DALOTIA_WITH_SAFETENSORS_CPP) if (DALOTIA_WITH_TENSORFLOW) target_link_libraries(dalotia_cpp PUBLIC tensorflow::tensorflow) - target_include_directories(dalotia_cpp PUBLIC - $ - $ - ) target_compile_options(dalotia_cpp PUBLIC "-DDALOTIA_WITH_TENSORFLOW") target_sources(dalotia_cpp PRIVATE dalotia_tensorflow_file.cpp ) endif (DALOTIA_WITH_TENSORFLOW) From 9491c06804f341291c7bf837a10c90a8d6b81ad9 Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Thu, 10 Jul 2025 11:26:10 +0900 Subject: [PATCH 39/42] ci: load spack package, show env --- .github/workflows/spack.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/spack.yml b/.github/workflows/spack.yml index 3af8cd7..e54c26a 100644 --- a/.github/workflows/spack.yml +++ b/.github/workflows/spack.yml @@ -27,10 +27,12 @@ jobs: spack spec dalotia spack info dalotia spack dev-build --test=root dalotia@main + spack load --sh dalotia@main - name: info and install dalotia with tensorflow shell: spack-bash {0} run: | spack spec dalotia~safetensorscpp+tensorflow spack info dalotia~safetensorscpp+tensorflow spack dev-build --test=root dalotia@main ~safetensorscpp+tensorflow + spack load --sh dalotia@main \ No newline at end of file From 350fb390c6fe88c72a630cb003b2135d84bf1bdd Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Thu, 10 Jul 2025 11:27:05 +0900 Subject: [PATCH 40/42] data files: mark constructors override --- src/dalotia_safetensors_file.hpp | 6 ++++-- src/dalotia_tensorflow_file.hpp | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/dalotia_safetensors_file.hpp b/src/dalotia_safetensors_file.hpp index 9a9a099..b8fd0d6 100644 --- a/src/dalotia_safetensors_file.hpp +++ b/src/dalotia_safetensors_file.hpp @@ -1,10 +1,12 @@ #pragma once #include +#include #include +#include #include "dalotia_formats.hpp" -#include "safetensors.hh" #include "dalotia_tensor_file.hpp" +#include "safetensors.hh" namespace dalotia { @@ -29,7 +31,7 @@ class SafetensorsFile : public TensorFile { public: explicit SafetensorsFile(const std::string &filename); - ~SafetensorsFile(); + ~SafetensorsFile() override; const std::vector &get_tensor_names() const override; diff --git a/src/dalotia_tensorflow_file.hpp b/src/dalotia_tensorflow_file.hpp index 2eac9b9..5ed8920 100644 --- a/src/dalotia_tensorflow_file.hpp +++ b/src/dalotia_tensorflow_file.hpp @@ -31,7 +31,7 @@ class TensorflowSavedModel : public TensorFile { public: explicit TensorflowSavedModel(const std::string &filename); - ~TensorflowSavedModel(); + ~TensorflowSavedModel() override; const std::vector &get_tensor_names() const override; From 7c28de5f4f614a4410076ab62bcdf2bccd1bcb0c Mon Sep 17 00:00:00 2001 From: Theresa Pollinger Date: Thu, 10 Jul 2025 11:31:21 +0900 Subject: [PATCH 41/42] ci: load only one spack package (amends 9491c0680) --- .github/workflows/spack.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/spack.yml b/.github/workflows/spack.yml index e54c26a..0bbf722 100644 --- a/.github/workflows/spack.yml +++ b/.github/workflows/spack.yml @@ -34,5 +34,4 @@ jobs: spack spec dalotia~safetensorscpp+tensorflow spack info dalotia~safetensorscpp+tensorflow spack dev-build --test=root dalotia@main ~safetensorscpp+tensorflow - spack load --sh dalotia@main - \ No newline at end of file + spack load --sh dalotia@main+tensorflow From f0a92deb6cad2a7d984fc394b11437e8636c3aac Mon Sep 17 00:00:00 2001 From: Freifrau von Bleifrei Date: Thu, 24 Jul 2025 15:53:42 +0900 Subject: [PATCH 42/42] tensorflow_file: remove unused lines --- src/dalotia_tensorflow_file.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/dalotia_tensorflow_file.cpp b/src/dalotia_tensorflow_file.cpp index 756d317..3aaa58d 100644 --- a/src/dalotia_tensorflow_file.cpp +++ b/src/dalotia_tensorflow_file.cpp @@ -137,11 +137,6 @@ TensorflowSavedModel::get_tensor_extents(const std::string &tensor_name, int num_dimensions = tf_get_num_dimensions(output, this->graph_, this->status_); std::vector extents_read(num_dimensions); - // use tensor_name only from the last slash onward - size_t last_slash_pos = tensor_name.find_last_of('/'); - auto shortened_tensor_name = (last_slash_pos != std::string::npos) - ? tensor_name.substr(last_slash_pos + 1) - : tensor_name; TF_GraphGetTensorShape(this->graph_.get(), output, extents_read.data(), extents_read.size(), this->status_.get()); tf_status_check(this->status_); @@ -239,4 +234,4 @@ TensorflowSavedModel::get_tensor_pointer_from_name(const std::string &tensor_nam return position->second.get(); } } -} // namespace dalotia \ No newline at end of file +} // namespace dalotia