k3-node 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- k3_node/__init__.py +122 -0
- k3_node/applications/__init__.py +17 -0
- k3_node/applications/bio/__init__.py +21 -0
- k3_node/applications/chemistry/__init__.py +155 -0
- k3_node/applications/materials/__init__.py +127 -0
- k3_node/applications/materials/basis.py +449 -0
- k3_node/applications/materials/chgnet.py +360 -0
- k3_node/applications/materials/core.py +351 -0
- k3_node/applications/materials/grace.py +246 -0
- k3_node/applications/materials/io.py +230 -0
- k3_node/applications/materials/m3gnet.py +462 -0
- k3_node/applications/materials/megnet.py +395 -0
- k3_node/applications/materials/qet.py +220 -0
- k3_node/applications/materials/readout.py +235 -0
- k3_node/applications/materials/so3net.py +234 -0
- k3_node/applications/materials/tensornet.py +381 -0
- k3_node/applications/materials/test_materials.py +167 -0
- k3_node/applications/materials/wrappers.py +95 -0
- k3_node/data/__init__.py +47 -0
- k3_node/data/batch.py +102 -0
- k3_node/data/collate.py +282 -0
- k3_node/data/data.py +532 -0
- k3_node/data/database.py +154 -0
- k3_node/data/dataset.py +182 -0
- k3_node/data/download.py +49 -0
- k3_node/data/extract.py +45 -0
- k3_node/data/feature_store.py +70 -0
- k3_node/data/graph_store.py +92 -0
- k3_node/data/hetero_data.py +374 -0
- k3_node/data/hypergraph_data.py +59 -0
- k3_node/data/in_memory_dataset.py +177 -0
- k3_node/data/makedirs.py +7 -0
- k3_node/data/on_disk_dataset.py +77 -0
- k3_node/data/separate.py +115 -0
- k3_node/data/storage.py +593 -0
- k3_node/data/temporal.py +154 -0
- k3_node/data/test_batch.py +67 -0
- k3_node/data/test_data.py +68 -0
- k3_node/data/test_dataset_and_stores.py +111 -0
- k3_node/data/test_hetero_data.py +33 -0
- k3_node/data/test_temporal_and_hyper.py +32 -0
- k3_node/data/view.py +43 -0
- k3_node/datasets/__init__.py +88 -0
- k3_node/datasets/actor.py +101 -0
- k3_node/datasets/airports.py +84 -0
- k3_node/datasets/amazon.py +66 -0
- k3_node/datasets/ba2motif_dataset.py +73 -0
- k3_node/datasets/ba_shapes.py +81 -0
- k3_node/datasets/bitcoin_otc.py +77 -0
- k3_node/datasets/citation_full.py +81 -0
- k3_node/datasets/coauthor.py +66 -0
- k3_node/datasets/dblp.py +106 -0
- k3_node/datasets/digits.py +63 -0
- k3_node/datasets/email_eu_core.py +60 -0
- k3_node/datasets/entities.py +158 -0
- k3_node/datasets/explainer_dataset.py +101 -0
- k3_node/datasets/facebook.py +51 -0
- k3_node/datasets/fake.py +256 -0
- k3_node/datasets/freebase.py +90 -0
- k3_node/datasets/geometric_shapes.py +69 -0
- k3_node/datasets/github.py +51 -0
- k3_node/datasets/graph_generator/__init__.py +6 -0
- k3_node/datasets/graph_generator/ba_graph.py +20 -0
- k3_node/datasets/graph_generator/base.py +29 -0
- k3_node/datasets/graph_generator/er_graph.py +21 -0
- k3_node/datasets/icews.py +58 -0
- k3_node/datasets/imdb.py +96 -0
- k3_node/datasets/jodie.py +56 -0
- k3_node/datasets/karate.py +56 -0
- k3_node/datasets/lastfm_asia.py +51 -0
- k3_node/datasets/mesh_correspondence.py +50 -0
- k3_node/datasets/molecule_net.py +148 -0
- k3_node/datasets/motif_generator/__init__.py +7 -0
- k3_node/datasets/motif_generator/base.py +29 -0
- k3_node/datasets/motif_generator/custom.py +17 -0
- k3_node/datasets/motif_generator/cycle.py +25 -0
- k3_node/datasets/motif_generator/house.py +27 -0
- k3_node/datasets/movielens.py +55 -0
- k3_node/datasets/planetoid.py +137 -0
- k3_node/datasets/polblogs.py +63 -0
- k3_node/datasets/ppi.py +189 -0
- k3_node/datasets/qm7.py +65 -0
- k3_node/datasets/qm9.py +132 -0
- k3_node/datasets/reddit.py +121 -0
- k3_node/datasets/sbm_dataset.py +165 -0
- k3_node/datasets/seal.py +74 -0
- k3_node/datasets/shape_scenes.py +92 -0
- k3_node/datasets/test_datasets.py +322 -0
- k3_node/datasets/tu_dataset.py +131 -0
- k3_node/datasets/twitch.py +66 -0
- k3_node/datasets/webkb.py +102 -0
- k3_node/datasets/wikics.py +85 -0
- k3_node/datasets/word_net.py +184 -0
- k3_node/etl/__init__.py +37 -0
- k3_node/etl/encoders.py +248 -0
- k3_node/etl/graph_builders.py +270 -0
- k3_node/etl/relational_to_graph.py +201 -0
- k3_node/etl/table_to_graph.py +244 -0
- k3_node/etl/test_etl.py +318 -0
- k3_node/export/__init__.py +15 -0
- k3_node/export/cross_backend.py +172 -0
- k3_node/export/onnx_exporter.py +190 -0
- k3_node/export/runtime.py +254 -0
- k3_node/export/tensorrt_exporter.py +201 -0
- k3_node/export/test_export.py +337 -0
- k3_node/export/tflite_exporter.py +112 -0
- k3_node/hub/__init__.py +29 -0
- k3_node/hub/dataset_hub.py +242 -0
- k3_node/hub/hub_mixin.py +599 -0
- k3_node/hub/model_card.py +133 -0
- k3_node/hub/test_hub.py +419 -0
- k3_node/io/__init__.py +22 -0
- k3_node/io/fs.py +117 -0
- k3_node/io/npz.py +45 -0
- k3_node/io/off.py +29 -0
- k3_node/io/planetoid.py +98 -0
- k3_node/io/tu.py +137 -0
- k3_node/io/txt_array.py +58 -0
- k3_node/layers/__init__.py +14 -0
- k3_node/layers/aggr/__init__.py +70 -0
- k3_node/layers/aggr/attention.py +77 -0
- k3_node/layers/aggr/base.py +403 -0
- k3_node/layers/aggr/basic.py +412 -0
- k3_node/layers/aggr/deep_sets.py +65 -0
- k3_node/layers/aggr/deepsets.py +29 -0
- k3_node/layers/aggr/equilibrium.py +107 -0
- k3_node/layers/aggr/fused.py +43 -0
- k3_node/layers/aggr/gmt.py +89 -0
- k3_node/layers/aggr/gru.py +58 -0
- k3_node/layers/aggr/lcm.py +143 -0
- k3_node/layers/aggr/lstm.py +58 -0
- k3_node/layers/aggr/mlp.py +75 -0
- k3_node/layers/aggr/multi.py +154 -0
- k3_node/layers/aggr/patch_transformer.py +137 -0
- k3_node/layers/aggr/quantile.py +125 -0
- k3_node/layers/aggr/resolver.py +68 -0
- k3_node/layers/aggr/scaler.py +133 -0
- k3_node/layers/aggr/set2set.py +87 -0
- k3_node/layers/aggr/set_transformer.py +107 -0
- k3_node/layers/aggr/sort.py +68 -0
- k3_node/layers/aggr/test_aggr.py +337 -0
- k3_node/layers/aggr/utils.py +210 -0
- k3_node/layers/aggr/variance_preserving.py +54 -0
- k3_node/layers/attention/__init__.py +5 -0
- k3_node/layers/attention/pair_attention.py +448 -0
- k3_node/layers/attention/performer.py +187 -0
- k3_node/layers/attention/polynormer.py +160 -0
- k3_node/layers/attention/qformer.py +143 -0
- k3_node/layers/attention/sgformer.py +106 -0
- k3_node/layers/attention/test_attention.py +68 -0
- k3_node/layers/attention/test_pair_attention.py +91 -0
- k3_node/layers/conv/__init__.py +149 -0
- k3_node/layers/conv/agnn_conv.py +120 -0
- k3_node/layers/conv/antisymmetric_conv.py +94 -0
- k3_node/layers/conv/appnp.py +105 -0
- k3_node/layers/conv/appnp_conv.py +157 -0
- k3_node/layers/conv/arma_conv.py +231 -0
- k3_node/layers/conv/cg_conv.py +92 -0
- k3_node/layers/conv/cheb_conv.py +137 -0
- k3_node/layers/conv/cluster_gcn_conv.py +102 -0
- k3_node/layers/conv/conv.py +100 -0
- k3_node/layers/conv/crystal_conv.py +140 -0
- k3_node/layers/conv/cugraph.py +84 -0
- k3_node/layers/conv/diffusion_conv.py +144 -0
- k3_node/layers/conv/dir_gnn_conv.py +93 -0
- k3_node/layers/conv/dna_conv.py +192 -0
- k3_node/layers/conv/edge_conv.py +107 -0
- k3_node/layers/conv/eg_conv.py +155 -0
- k3_node/layers/conv/fa_conv.py +107 -0
- k3_node/layers/conv/feast_conv.py +126 -0
- k3_node/layers/conv/film_conv.py +143 -0
- k3_node/layers/conv/gat_conv.py +244 -0
- k3_node/layers/conv/gated_graph_conv.py +136 -0
- k3_node/layers/conv/gatv2_conv.py +205 -0
- k3_node/layers/conv/gcn.py +144 -0
- k3_node/layers/conv/gcn2_conv.py +126 -0
- k3_node/layers/conv/gcn_conv.py +135 -0
- k3_node/layers/conv/gen_conv.py +163 -0
- k3_node/layers/conv/general_conv.py +218 -0
- k3_node/layers/conv/gin_conv.py +218 -0
- k3_node/layers/conv/gmm_conv.py +172 -0
- k3_node/layers/conv/gps_conv.py +153 -0
- k3_node/layers/conv/graph_attention.py +262 -0
- k3_node/layers/conv/graph_conv.py +84 -0
- k3_node/layers/conv/gravnet_conv.py +93 -0
- k3_node/layers/conv/han_conv.py +175 -0
- k3_node/layers/conv/heat_conv.py +131 -0
- k3_node/layers/conv/hetero_conv.py +128 -0
- k3_node/layers/conv/hgt_conv.py +218 -0
- k3_node/layers/conv/hypergraph_conv.py +182 -0
- k3_node/layers/conv/le_conv.py +81 -0
- k3_node/layers/conv/lg_conv.py +58 -0
- k3_node/layers/conv/meshcnn_conv.py +84 -0
- k3_node/layers/conv/message_passing.py +451 -0
- k3_node/layers/conv/mf_conv.py +95 -0
- k3_node/layers/conv/mixhop_conv.py +108 -0
- k3_node/layers/conv/nn_conv.py +110 -0
- k3_node/layers/conv/pan_conv.py +100 -0
- k3_node/layers/conv/pdn_conv.py +109 -0
- k3_node/layers/conv/pna_conv.py +177 -0
- k3_node/layers/conv/point_conv.py +101 -0
- k3_node/layers/conv/point_gnn_conv.py +90 -0
- k3_node/layers/conv/point_transformer_conv.py +132 -0
- k3_node/layers/conv/ppf_conv.py +135 -0
- k3_node/layers/conv/ppnp.py +89 -0
- k3_node/layers/conv/res_gated_graph_conv.py +126 -0
- k3_node/layers/conv/rgat_conv.py +251 -0
- k3_node/layers/conv/rgcn_conv.py +321 -0
- k3_node/layers/conv/sage_conv.py +154 -0
- k3_node/layers/conv/sg_conv.py +96 -0
- k3_node/layers/conv/signed_conv.py +100 -0
- k3_node/layers/conv/simple_conv.py +75 -0
- k3_node/layers/conv/spline_conv.py +182 -0
- k3_node/layers/conv/ssg_conv.py +101 -0
- k3_node/layers/conv/supergat_conv.py +195 -0
- k3_node/layers/conv/tag_conv.py +98 -0
- k3_node/layers/conv/test_backend_consistency.py +164 -0
- k3_node/layers/conv/test_conv.py +176 -0
- k3_node/layers/conv/test_conv_pyg.py +566 -0
- k3_node/layers/conv/transformer_conv.py +168 -0
- k3_node/layers/conv/utils.py +403 -0
- k3_node/layers/conv/wl_conv.py +151 -0
- k3_node/layers/conv/x_conv.py +187 -0
- k3_node/layers/dense/__init__.py +40 -0
- k3_node/layers/dense/dense_gat_conv.py +149 -0
- k3_node/layers/dense/dense_gcn_conv.py +117 -0
- k3_node/layers/dense/dense_gin_conv.py +88 -0
- k3_node/layers/dense/dense_graph_conv.py +95 -0
- k3_node/layers/dense/dense_sage_conv.py +85 -0
- k3_node/layers/dense/diff_pool.py +76 -0
- k3_node/layers/dense/dmon_pool.py +223 -0
- k3_node/layers/dense/linear.py +327 -0
- k3_node/layers/dense/mincut_pool.py +92 -0
- k3_node/layers/dense/test_dense.py +377 -0
- k3_node/layers/functional/__init__.py +13 -0
- k3_node/layers/functional/bro.py +49 -0
- k3_node/layers/functional/edge_dropout.py +55 -0
- k3_node/layers/functional/gini.py +44 -0
- k3_node/layers/functional/test_functional.py +34 -0
- k3_node/layers/kge/__init__.py +17 -0
- k3_node/layers/kge/base.py +255 -0
- k3_node/layers/kge/complex.py +98 -0
- k3_node/layers/kge/distmult.py +79 -0
- k3_node/layers/kge/loader.py +50 -0
- k3_node/layers/kge/rotate.py +103 -0
- k3_node/layers/kge/test_kge.py +76 -0
- k3_node/layers/kge/transe.py +96 -0
- k3_node/layers/norm/__init__.py +23 -0
- k3_node/layers/norm/batch_norm.py +328 -0
- k3_node/layers/norm/diff_group_norm.py +141 -0
- k3_node/layers/norm/graph_norm.py +105 -0
- k3_node/layers/norm/graph_size_norm.py +57 -0
- k3_node/layers/norm/instance_norm.py +163 -0
- k3_node/layers/norm/layer_norm.py +245 -0
- k3_node/layers/norm/mean_subtraction_norm.py +57 -0
- k3_node/layers/norm/msg_norm.py +58 -0
- k3_node/layers/norm/pair_norm.py +94 -0
- k3_node/layers/norm/test_norm.py +275 -0
- k3_node/layers/pool/__init__.py +83 -0
- k3_node/layers/pool/approx_knn.py +101 -0
- k3_node/layers/pool/asap.py +173 -0
- k3_node/layers/pool/avg_pool.py +165 -0
- k3_node/layers/pool/cluster_pool.py +168 -0
- k3_node/layers/pool/connect/__init__.py +10 -0
- k3_node/layers/pool/connect/base.py +103 -0
- k3_node/layers/pool/connect/filter_edges.py +113 -0
- k3_node/layers/pool/consecutive.py +30 -0
- k3_node/layers/pool/decimation.py +48 -0
- k3_node/layers/pool/edge_pool.py +189 -0
- k3_node/layers/pool/glob.py +139 -0
- k3_node/layers/pool/graclus.py +66 -0
- k3_node/layers/pool/knn.py +253 -0
- k3_node/layers/pool/max_pool.py +159 -0
- k3_node/layers/pool/mem_pool.py +145 -0
- k3_node/layers/pool/pan_pool.py +144 -0
- k3_node/layers/pool/point_cloud.py +212 -0
- k3_node/layers/pool/pool.py +119 -0
- k3_node/layers/pool/sag_pool.py +174 -0
- k3_node/layers/pool/select/__init__.py +10 -0
- k3_node/layers/pool/select/base.py +112 -0
- k3_node/layers/pool/select/topk.py +206 -0
- k3_node/layers/pool/test_pool.py +456 -0
- k3_node/layers/pool/topk_pool.py +103 -0
- k3_node/layers/pool/voxel_grid.py +70 -0
- k3_node/layers/unpool/__init__.py +9 -0
- k3_node/layers/unpool/knn_interpolate.py +57 -0
- k3_node/layers/unpool/test_unpool.py +31 -0
- k3_node/loader/__init__.py +62 -0
- k3_node/loader/base.py +69 -0
- k3_node/loader/cache.py +68 -0
- k3_node/loader/cluster.py +127 -0
- k3_node/loader/data_list_loader.py +45 -0
- k3_node/loader/dataloader.py +117 -0
- k3_node/loader/dense_data_loader.py +62 -0
- k3_node/loader/dynamic_batch_sampler.py +93 -0
- k3_node/loader/graph_saint.py +188 -0
- k3_node/loader/hgt_loader.py +90 -0
- k3_node/loader/imbalanced_sampler.py +87 -0
- k3_node/loader/keras_dataset.py +334 -0
- k3_node/loader/link_loader.py +179 -0
- k3_node/loader/link_neighbor_loader.py +202 -0
- k3_node/loader/mixin.py +190 -0
- k3_node/loader/neighbor_loader.py +159 -0
- k3_node/loader/neighbor_sampler.py +167 -0
- k3_node/loader/node_loader.py +185 -0
- k3_node/loader/prefetch.py +115 -0
- k3_node/loader/random_node_loader.py +89 -0
- k3_node/loader/sampler_utils.py +499 -0
- k3_node/loader/shadow.py +115 -0
- k3_node/loader/temporal_dataloader.py +98 -0
- k3_node/loader/test_dataloader.py +113 -0
- k3_node/loader/test_keras_dataset.py +221 -0
- k3_node/loader/test_neighbor_loader.py +122 -0
- k3_node/loader/test_sampler_utils.py +82 -0
- k3_node/loader/test_samplers.py +96 -0
- k3_node/loader/test_subgraph_loaders.py +89 -0
- k3_node/loader/utils.py +232 -0
- k3_node/loader/zip_loader.py +88 -0
- k3_node/metrics.py +94 -0
- k3_node/models/__init__.py +424 -0
- k3_node/models/attentive_fp.py +232 -0
- k3_node/models/attract_repel.py +108 -0
- k3_node/models/autoencoder.py +318 -0
- k3_node/models/basic_gnn.py +443 -0
- k3_node/models/bio/__init__.py +4 -0
- k3_node/models/captum.py +52 -0
- k3_node/models/chemistry/__init__.py +4 -0
- k3_node/models/correct_and_smooth.py +146 -0
- k3_node/models/deep_graph_infomax.py +113 -0
- k3_node/models/deepgcn.py +121 -0
- k3_node/models/dimenet.py +737 -0
- k3_node/models/dimenet_utils.py +153 -0
- k3_node/models/gnnff.py +263 -0
- k3_node/models/gps_model.py +1122 -0
- k3_node/models/gpse.py +638 -0
- k3_node/models/graph_unet.py +199 -0
- k3_node/models/graphmae2.py +954 -0
- k3_node/models/graphormer.py +1258 -0
- k3_node/models/graphormer_3d.py +868 -0
- k3_node/models/grover.py +1066 -0
- k3_node/models/jumping_knowledge.py +200 -0
- k3_node/models/label_prop.py +110 -0
- k3_node/models/lightgcn.py +171 -0
- k3_node/models/linkx.py +181 -0
- k3_node/models/lpformer.py +404 -0
- k3_node/models/mask_label.py +114 -0
- k3_node/models/materials/__init__.py +33 -0
- k3_node/models/meta.py +133 -0
- k3_node/models/metapath2vec.py +234 -0
- k3_node/models/mlp.py +264 -0
- k3_node/models/mole_bert.py +379 -0
- k3_node/models/neural_fingerprint.py +95 -0
- k3_node/models/node2vec.py +213 -0
- k3_node/models/pmlp.py +157 -0
- k3_node/models/polynormer.py +229 -0
- k3_node/models/rect.py +93 -0
- k3_node/models/renet.py +221 -0
- k3_node/models/rev_gnn.py +128 -0
- k3_node/models/schnet.py +484 -0
- k3_node/models/sgformer.py +195 -0
- k3_node/models/signed_gcn.py +185 -0
- k3_node/models/test_attentive_fp.py +32 -0
- k3_node/models/test_attract_repel.py +33 -0
- k3_node/models/test_autoencoder.py +119 -0
- k3_node/models/test_basic_gnn.py +102 -0
- k3_node/models/test_correct_and_smooth.py +40 -0
- k3_node/models/test_deep_graph_infomax.py +68 -0
- k3_node/models/test_deepgcn.py +21 -0
- k3_node/models/test_dimenet.py +86 -0
- k3_node/models/test_domain_apis.py +138 -0
- k3_node/models/test_gnnff.py +24 -0
- k3_node/models/test_gps_model.py +271 -0
- k3_node/models/test_gpse.py +34 -0
- k3_node/models/test_graph_unet.py +26 -0
- k3_node/models/test_graphmae2.py +226 -0
- k3_node/models/test_graphormer.py +233 -0
- k3_node/models/test_graphormer3d.py +163 -0
- k3_node/models/test_grover.py +287 -0
- k3_node/models/test_jumping_knowledge.py +129 -0
- k3_node/models/test_label_prop.py +37 -0
- k3_node/models/test_lightgcn.py +38 -0
- k3_node/models/test_linkx.py +31 -0
- k3_node/models/test_lpformer.py +22 -0
- k3_node/models/test_mask_label.py +90 -0
- k3_node/models/test_meta.py +159 -0
- k3_node/models/test_metapath2vec.py +45 -0
- k3_node/models/test_mlp.py +62 -0
- k3_node/models/test_mole_bert.py +164 -0
- k3_node/models/test_neural_fingerprint.py +13 -0
- k3_node/models/test_node2vec.py +57 -0
- k3_node/models/test_pmlp.py +81 -0
- k3_node/models/test_polynormer.py +104 -0
- k3_node/models/test_rect.py +23 -0
- k3_node/models/test_renet.py +32 -0
- k3_node/models/test_rev_gnn.py +24 -0
- k3_node/models/test_schnet.py +43 -0
- k3_node/models/test_sgformer.py +48 -0
- k3_node/models/test_signed_gcn.py +28 -0
- k3_node/models/test_tgn.py +77 -0
- k3_node/models/test_unimol.py +179 -0
- k3_node/models/test_unimol2.py +114 -0
- k3_node/models/test_unimol_plus.py +131 -0
- k3_node/models/test_visnet.py +44 -0
- k3_node/models/tgn.py +382 -0
- k3_node/models/unimol.py +1156 -0
- k3_node/models/unimol2.py +616 -0
- k3_node/models/unimol_docking_v2.py +301 -0
- k3_node/models/unimol_plus.py +456 -0
- k3_node/models/utils.py +97 -0
- k3_node/models/visnet.py +759 -0
- k3_node/ops/__init__.py +4 -0
- k3_node/ops/conv.py +56 -0
- k3_node/ops/creation.py +43 -0
- k3_node/ops/graph.py +27 -0
- k3_node/ops/host.py +41 -0
- k3_node/ops/matmul.py +49 -0
- k3_node/ops/numpy.py +24 -0
- k3_node/ops/segment.py +54 -0
- k3_node/ops/sparse.py +51 -0
- k3_node/rag/__init__.py +49 -0
- k3_node/rag/encoders.py +312 -0
- k3_node/rag/pipeline.py +192 -0
- k3_node/rag/projector.py +184 -0
- k3_node/rag/subgraph.py +270 -0
- k3_node/rag/test_rag.py +347 -0
- k3_node/rag/verbalizer.py +162 -0
- k3_node/tasks/__init__.py +19 -0
- k3_node/tasks/backbone_resolver.py +125 -0
- k3_node/tasks/base.py +67 -0
- k3_node/tasks/graph_classification.py +270 -0
- k3_node/tasks/graph_regression.py +228 -0
- k3_node/tasks/link_prediction.py +306 -0
- k3_node/tasks/node_classification.py +194 -0
- k3_node/tasks/node_regression.py +138 -0
- k3_node/tasks/test_tasks.py +319 -0
- k3_node/test_docstring_examples.py +106 -0
- k3_node/test_training_forwarding.py +116 -0
- k3_node/training.py +115 -0
- k3_node/transforms/__init__.py +166 -0
- k3_node/transforms/base_transform.py +32 -0
- k3_node/transforms/compose.py +58 -0
- k3_node/transforms/general.py +676 -0
- k3_node/transforms/graph.py +1070 -0
- k3_node/transforms/spatial.py +797 -0
- k3_node/transforms/test_random_link_split.py +45 -0
- k3_node/transforms/test_spatial_transforms.py +65 -0
- k3_node/transforms/test_transforms.py +253 -0
- k3_node/transforms/utils.py +102 -0
- k3_node/utils/__init__.py +5 -0
- k3_node/utils/backend_import.py +12 -0
- k3_node/utils/graph.py +286 -0
- k3_node/utils/keras.py +94 -0
- k3_node/utils/random.py +103 -0
- k3_node/utils/smiles.py +235 -0
- k3_node-1.0.0.dist-info/METADATA +284 -0
- k3_node-1.0.0.dist-info/RECORD +459 -0
- k3_node-1.0.0.dist-info/WHEEL +5 -0
- k3_node-1.0.0.dist-info/licenses/LICENSE +21 -0
- k3_node-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""Turn-key TensorRT engine exporter and Triton config generator for K3-Node."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
8
|
+
|
|
9
|
+
from k3_node.export.onnx_exporter import export_onnx
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def export_tensorrt(
|
|
13
|
+
model_or_task_or_onnx: Any,
|
|
14
|
+
output_path: Union[str, Path],
|
|
15
|
+
dummy_inputs: Optional[Any] = None,
|
|
16
|
+
precision: str = "fp16",
|
|
17
|
+
workspace_gb: int = 1,
|
|
18
|
+
min_shapes: Optional[Dict[str, Tuple[int, ...]]] = None,
|
|
19
|
+
opt_shapes: Optional[Dict[str, Tuple[int, ...]]] = None,
|
|
20
|
+
max_shapes: Optional[Dict[str, Tuple[int, ...]]] = None,
|
|
21
|
+
verbose: bool = False,
|
|
22
|
+
) -> Path:
|
|
23
|
+
r"""Compiles a K3-Node model or ONNX file into an ultra-low latency NVIDIA TensorRT engine.
|
|
24
|
+
|
|
25
|
+
Uses the Python `tensorrt` API if available on the GPU host, or invokes NVIDIA's
|
|
26
|
+
`trtexec` binary directly.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
model_or_task_or_onnx: K3-Node model/task, or an existing `.onnx` file path.
|
|
30
|
+
output_path: Target path for the compiled `.engine` or `.plan` binary.
|
|
31
|
+
dummy_inputs: Optional input sample to determine shapes and topology.
|
|
32
|
+
precision: Precision mode: `"fp32"`, `"fp16"`, or `"int8"`. (default: `"fp16"`)
|
|
33
|
+
workspace_gb: Max GPU memory in GB allocated for TensorRT engine building. (default: `1`)
|
|
34
|
+
min_shapes: Optional minimum dynamic shapes dictionary (e.g. `{'x': (1, 16)}`).
|
|
35
|
+
opt_shapes: Optional optimal dynamic shapes dictionary (e.g. `{'x': (100, 16)}`).
|
|
36
|
+
max_shapes: Optional maximum dynamic shapes dictionary (e.g. `{'x': (10000, 16)}`).
|
|
37
|
+
verbose: Whether to log engine building progress. (default: `False`)
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Path object pointing to the compiled TensorRT `.engine` file.
|
|
41
|
+
"""
|
|
42
|
+
engine_file = Path(output_path)
|
|
43
|
+
engine_file.parent.mkdir(parents=True, exist_ok=True)
|
|
44
|
+
|
|
45
|
+
# 1. Resolve ONNX file
|
|
46
|
+
if isinstance(model_or_task_or_onnx, (str, Path)) and str(model_or_task_or_onnx).endswith(".onnx"):
|
|
47
|
+
onnx_path = Path(model_or_task_or_onnx)
|
|
48
|
+
else:
|
|
49
|
+
temp_onnx = engine_file.with_suffix(".onnx")
|
|
50
|
+
export_onnx(model_or_task_or_onnx, temp_onnx, dummy_inputs=dummy_inputs, verbose=verbose)
|
|
51
|
+
onnx_path = temp_onnx
|
|
52
|
+
|
|
53
|
+
# 2. Check for TensorRT Python API
|
|
54
|
+
try:
|
|
55
|
+
import tensorrt as trt
|
|
56
|
+
|
|
57
|
+
logger = trt.Logger(trt.Logger.VERBOSE if verbose else trt.Logger.WARNING)
|
|
58
|
+
builder = trt.Builder(logger)
|
|
59
|
+
network_flags = 1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)
|
|
60
|
+
network = builder.create_network(network_flags)
|
|
61
|
+
parser = trt.OnnxParser(network, logger)
|
|
62
|
+
|
|
63
|
+
with open(onnx_path, "rb") as f:
|
|
64
|
+
if not parser.parse(f.read()):
|
|
65
|
+
error_msgs = "\n".join(str(parser.get_error(i)) for i in range(parser.num_errors))
|
|
66
|
+
raise RuntimeError(f"TensorRT failed to parse ONNX graph:\n{error_msgs}")
|
|
67
|
+
|
|
68
|
+
config = builder.create_builder_config()
|
|
69
|
+
config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, workspace_gb * (1 << 30))
|
|
70
|
+
|
|
71
|
+
if precision in ("fp16", "float16") and builder.platform_has_fast_fp16:
|
|
72
|
+
config.set_flag(trt.BuilderFlag.FP16)
|
|
73
|
+
elif precision in ("int8",) and builder.platform_has_fast_int8:
|
|
74
|
+
config.set_flag(trt.BuilderFlag.INT8)
|
|
75
|
+
|
|
76
|
+
# Optimization profile for dynamic shapes if provided
|
|
77
|
+
if min_shapes and opt_shapes and max_shapes:
|
|
78
|
+
profile = builder.create_optimization_profile()
|
|
79
|
+
for name in min_shapes:
|
|
80
|
+
profile.set_shape(name, min_shapes[name], opt_shapes[name], max_shapes[name])
|
|
81
|
+
config.add_optimization_profile(profile)
|
|
82
|
+
|
|
83
|
+
plan = builder.build_serialized_network(network, config)
|
|
84
|
+
if plan is None:
|
|
85
|
+
raise RuntimeError("Failed to build TensorRT serialized network.")
|
|
86
|
+
|
|
87
|
+
with open(engine_file, "wb") as f:
|
|
88
|
+
f.write(plan)
|
|
89
|
+
|
|
90
|
+
if verbose:
|
|
91
|
+
print(f"TensorRT engine compiled successfully via Python API: {engine_file}")
|
|
92
|
+
return engine_file
|
|
93
|
+
|
|
94
|
+
except ImportError:
|
|
95
|
+
# Fall back to trtexec binary if available
|
|
96
|
+
trtexec_bin = shutil.which("trtexec")
|
|
97
|
+
if trtexec_bin:
|
|
98
|
+
cmd = [
|
|
99
|
+
trtexec_bin,
|
|
100
|
+
f"--onnx={onnx_path}",
|
|
101
|
+
f"--saveEngine={engine_file}",
|
|
102
|
+
]
|
|
103
|
+
if precision in ("fp16", "float16"):
|
|
104
|
+
cmd.append("--fp16")
|
|
105
|
+
elif precision in ("int8",):
|
|
106
|
+
cmd.append("--int8")
|
|
107
|
+
|
|
108
|
+
if min_shapes and opt_shapes and max_shapes:
|
|
109
|
+
min_str = ",".join(f"{k}:{'x'.join(map(str, v))}" for k, v in min_shapes.items())
|
|
110
|
+
opt_str = ",".join(f"{k}:{'x'.join(map(str, v))}" for k, v in opt_shapes.items())
|
|
111
|
+
max_str = ",".join(f"{k}:{'x'.join(map(str, v))}" for k, v in max_shapes.items())
|
|
112
|
+
cmd.extend([
|
|
113
|
+
f"--minShapes={min_str}",
|
|
114
|
+
f"--optShapes={opt_str}",
|
|
115
|
+
f"--maxShapes={max_str}",
|
|
116
|
+
])
|
|
117
|
+
|
|
118
|
+
if verbose:
|
|
119
|
+
print(f"Compiling TensorRT engine via trtexec: {' '.join(cmd)}")
|
|
120
|
+
subprocess.run(cmd, check=True)
|
|
121
|
+
return engine_file
|
|
122
|
+
else:
|
|
123
|
+
raise ImportError(
|
|
124
|
+
"Neither NVIDIA `tensorrt` Python package nor `trtexec` binary was found on this system.\n"
|
|
125
|
+
"To compile TensorRT engines, please ensure you are running on an NVIDIA GPU system with:\n"
|
|
126
|
+
" pip install tensorrt\n"
|
|
127
|
+
"or install the NVIDIA TensorRT SDK (providing `trtexec`).\n"
|
|
128
|
+
f"Your ONNX model was successfully exported to: {onnx_path}\n"
|
|
129
|
+
f"You can compile it later on any GPU server with:\n"
|
|
130
|
+
f" trtexec --onnx={onnx_path} --saveEngine={engine_file} --fp16"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def generate_triton_config(
|
|
135
|
+
model_name: str,
|
|
136
|
+
output_dir: Union[str, Path],
|
|
137
|
+
in_channels: int,
|
|
138
|
+
out_channels: int,
|
|
139
|
+
backend: str = "onnxruntime",
|
|
140
|
+
max_batch_size: int = 0,
|
|
141
|
+
) -> Path:
|
|
142
|
+
r"""Generates a complete Triton Inference Server model repository directory structure and `config.pbtxt`.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
model_name: Name of the model in Triton (e.g., `'cora_gcn'`).
|
|
146
|
+
output_dir: Root path for the model repository folder.
|
|
147
|
+
in_channels: Number of node input features.
|
|
148
|
+
out_channels: Number of output features or classes.
|
|
149
|
+
backend: Backend engine (`'onnxruntime'` or `'tensorrt_plan'`).
|
|
150
|
+
max_batch_size: Maximum batch size (default: `0` for dynamic graph sizing).
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Path to the generated `config.pbtxt`.
|
|
154
|
+
"""
|
|
155
|
+
model_dir = Path(output_dir) / model_name
|
|
156
|
+
version_dir = model_dir / "1"
|
|
157
|
+
version_dir.mkdir(parents=True, exist_ok=True)
|
|
158
|
+
|
|
159
|
+
platform = "onnxruntime_onnx" if backend == "onnxruntime" else "tensorrt_plan"
|
|
160
|
+
|
|
161
|
+
config_text = f"""name: "{model_name}"
|
|
162
|
+
platform: "{platform}"
|
|
163
|
+
max_batch_size: {max_batch_size}
|
|
164
|
+
|
|
165
|
+
input [
|
|
166
|
+
{{
|
|
167
|
+
name: "x"
|
|
168
|
+
data_type: TYPE_FP32
|
|
169
|
+
dims: [ -1, {in_channels} ]
|
|
170
|
+
}},
|
|
171
|
+
{{
|
|
172
|
+
name: "edge_index"
|
|
173
|
+
data_type: TYPE_INT64
|
|
174
|
+
dims: [ 2, -1 ]
|
|
175
|
+
}}
|
|
176
|
+
]
|
|
177
|
+
|
|
178
|
+
output [
|
|
179
|
+
{{
|
|
180
|
+
name: "output"
|
|
181
|
+
data_type: TYPE_FP32
|
|
182
|
+
dims: [ -1, {out_channels} ]
|
|
183
|
+
}}
|
|
184
|
+
]
|
|
185
|
+
|
|
186
|
+
instance_group [
|
|
187
|
+
{{
|
|
188
|
+
count: 1
|
|
189
|
+
kind: KIND_GPU
|
|
190
|
+
}}
|
|
191
|
+
]
|
|
192
|
+
|
|
193
|
+
dynamic_batching {{
|
|
194
|
+
max_queue_delay_microseconds: 1000
|
|
195
|
+
}}
|
|
196
|
+
"""
|
|
197
|
+
config_path = model_dir / "config.pbtxt"
|
|
198
|
+
with open(config_path, "w", encoding="utf-8") as f:
|
|
199
|
+
f.write(config_text)
|
|
200
|
+
|
|
201
|
+
return config_path
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"""Unit and integration tests for turn-key ONNX, TFLite, and TensorRT export."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import tempfile
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from unittest.mock import MagicMock, patch
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
import numpy as np
|
|
11
|
+
import keras
|
|
12
|
+
from keras import ops
|
|
13
|
+
|
|
14
|
+
import k3_node
|
|
15
|
+
from k3_node.data import Data
|
|
16
|
+
from k3_node.models import GCN
|
|
17
|
+
from k3_node.tasks import NodeClassifier
|
|
18
|
+
from k3_node.export import (
|
|
19
|
+
export_onnx,
|
|
20
|
+
export_tflite,
|
|
21
|
+
export_tensorrt,
|
|
22
|
+
generate_triton_config,
|
|
23
|
+
ONNXModel,
|
|
24
|
+
TFLiteModel,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _create_sample_graph(num_nodes=6, in_channels=8):
|
|
29
|
+
x = ops.zeros((num_nodes, in_channels), dtype="float32")
|
|
30
|
+
src = np.arange(num_nodes, dtype="int64")
|
|
31
|
+
dst = (src + 1) % num_nodes
|
|
32
|
+
edge_index = ops.convert_to_tensor(np.stack([src, dst], axis=0), dtype="int64")
|
|
33
|
+
y = ops.convert_to_tensor(np.array([i % 2 for i in range(num_nodes)], dtype="int64"), dtype="int64")
|
|
34
|
+
return Data(x=x, edge_index=edge_index, y=y)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_export_module_exports():
|
|
38
|
+
"""Verify all export utilities are exported from top-level and export modules."""
|
|
39
|
+
assert hasattr(k3_node, "export_onnx")
|
|
40
|
+
assert hasattr(k3_node, "export_tflite")
|
|
41
|
+
assert hasattr(k3_node, "export_tensorrt")
|
|
42
|
+
assert hasattr(k3_node, "ONNXModel")
|
|
43
|
+
assert hasattr(k3_node, "TFLiteModel")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_export_onnx_model_and_runtime_inference():
|
|
47
|
+
"""Test exporting GCN to ONNX and serving with ONNXModel."""
|
|
48
|
+
gcn = GCN(in_channels=8, hidden_channels=16, num_layers=2, out_channels=3)
|
|
49
|
+
graph = _create_sample_graph(num_nodes=6, in_channels=8)
|
|
50
|
+
|
|
51
|
+
# Initial Keras prediction
|
|
52
|
+
keras_pred = ops.convert_to_numpy(gcn.predict(graph))
|
|
53
|
+
|
|
54
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
55
|
+
onnx_file = Path(tmpdir) / "gcn.onnx"
|
|
56
|
+
out_path = export_onnx(gcn, onnx_file, dummy_inputs=graph)
|
|
57
|
+
assert out_path.exists()
|
|
58
|
+
assert out_path.stat().st_size > 0
|
|
59
|
+
|
|
60
|
+
# Load into lightweight serving runtime
|
|
61
|
+
serving_model = ONNXModel(onnx_file)
|
|
62
|
+
onnx_pred = serving_model.predict(graph)
|
|
63
|
+
assert onnx_pred.shape == (6, 3)
|
|
64
|
+
|
|
65
|
+
# Numerical parity check
|
|
66
|
+
np.testing.assert_allclose(keras_pred, onnx_pred, atol=1e-4)
|
|
67
|
+
|
|
68
|
+
# Dynamic graph sizing check: serve a graph with different number of nodes & edges!
|
|
69
|
+
large_graph = _create_sample_graph(num_nodes=14, in_channels=8)
|
|
70
|
+
large_pred = serving_model.predict(large_graph)
|
|
71
|
+
assert large_pred.shape == (14, 3)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_export_onnx_task_estimator():
|
|
75
|
+
"""Test exporting NodeClassifier task estimator to ONNX."""
|
|
76
|
+
data = _create_sample_graph(num_nodes=8, in_channels=4)
|
|
77
|
+
clf = NodeClassifier(backbone="gcn", in_channels=4, out_channels=2, hidden_channels=8)
|
|
78
|
+
clf.fit(data, epochs=1, verbose=0)
|
|
79
|
+
|
|
80
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
81
|
+
onnx_file = Path(tmpdir) / "classifier.onnx"
|
|
82
|
+
out_path = clf.export_onnx(onnx_file)
|
|
83
|
+
assert out_path.exists()
|
|
84
|
+
|
|
85
|
+
runtime = ONNXModel(onnx_file)
|
|
86
|
+
preds = runtime.predict(data)
|
|
87
|
+
assert preds.shape == (8, 2)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_export_tflite_fp32_and_runtime_inference():
|
|
91
|
+
"""Test exporting GCN to TFLite (FP32) and serving with TFLiteModel."""
|
|
92
|
+
gcn = GCN(in_channels=8, hidden_channels=16, num_layers=2, out_channels=3)
|
|
93
|
+
graph = _create_sample_graph(num_nodes=6, in_channels=8)
|
|
94
|
+
keras_pred = ops.convert_to_numpy(gcn.predict(graph))
|
|
95
|
+
|
|
96
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
97
|
+
tflite_file = Path(tmpdir) / "gcn.tflite"
|
|
98
|
+
out_path = export_tflite(gcn, tflite_file, dummy_inputs=graph)
|
|
99
|
+
assert out_path.exists()
|
|
100
|
+
assert out_path.stat().st_size > 0
|
|
101
|
+
|
|
102
|
+
# Serve with TFLiteModel runtime
|
|
103
|
+
tflite_runner = TFLiteModel(tflite_file)
|
|
104
|
+
tflite_pred = tflite_runner.predict(graph)
|
|
105
|
+
assert tflite_pred.shape == (6, 3)
|
|
106
|
+
np.testing.assert_allclose(keras_pred, tflite_pred, atol=1e-4)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_export_tflite_fp16_quantization():
|
|
110
|
+
"""Test TFLite FP16 quantization."""
|
|
111
|
+
gcn = GCN(in_channels=8, hidden_channels=16, num_layers=2, out_channels=3)
|
|
112
|
+
graph = _create_sample_graph(num_nodes=6, in_channels=8)
|
|
113
|
+
|
|
114
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
115
|
+
tflite_file = Path(tmpdir) / "gcn_fp16.tflite"
|
|
116
|
+
out_path = gcn.export_tflite(tflite_file, dummy_inputs=graph, quantization="fp16")
|
|
117
|
+
assert out_path.exists()
|
|
118
|
+
assert out_path.stat().st_size > 0
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_export_tflite_int8_dynamic_quantization():
|
|
122
|
+
"""Test TFLite dynamic range INT8 quantization."""
|
|
123
|
+
gcn = GCN(in_channels=8, hidden_channels=16, num_layers=2, out_channels=3)
|
|
124
|
+
graph = _create_sample_graph(num_nodes=6, in_channels=8)
|
|
125
|
+
|
|
126
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
127
|
+
tflite_file = Path(tmpdir) / "gcn_int8.tflite"
|
|
128
|
+
out_path = gcn.export_tflite(tflite_file, dummy_inputs=graph, quantization="int8_dynamic")
|
|
129
|
+
assert out_path.exists()
|
|
130
|
+
assert out_path.stat().st_size > 0
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_generate_triton_config():
|
|
134
|
+
"""Test generating Triton Inference Server model repository and config.pbtxt."""
|
|
135
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
136
|
+
cfg_path = generate_triton_config(
|
|
137
|
+
model_name="cora_gcn",
|
|
138
|
+
output_dir=tmpdir,
|
|
139
|
+
in_channels=1433,
|
|
140
|
+
out_channels=7,
|
|
141
|
+
backend="onnxruntime",
|
|
142
|
+
)
|
|
143
|
+
assert cfg_path.exists()
|
|
144
|
+
with open(cfg_path, "r", encoding="utf-8") as f:
|
|
145
|
+
content = f.read()
|
|
146
|
+
assert 'name: "cora_gcn"' in content
|
|
147
|
+
assert "onnxruntime_onnx" in content
|
|
148
|
+
assert "dims: [ -1, 1433 ]" in content
|
|
149
|
+
assert "dims: [ -1, 7 ]" in content
|
|
150
|
+
assert (Path(tmpdir) / "cora_gcn" / "1").exists()
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_export_tensorrt_mocked():
|
|
154
|
+
"""Test TensorRT exporter fallback and compilation logic with mock."""
|
|
155
|
+
gcn = GCN(in_channels=8, hidden_channels=16, num_layers=2, out_channels=3)
|
|
156
|
+
graph = _create_sample_graph(num_nodes=6, in_channels=8)
|
|
157
|
+
|
|
158
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
159
|
+
engine_file = Path(tmpdir) / "model.engine"
|
|
160
|
+
|
|
161
|
+
real_run = subprocess.run
|
|
162
|
+
with patch("shutil.which", return_value="/usr/local/bin/trtexec"), patch("subprocess.run") as mock_sub:
|
|
163
|
+
# Create a mock engine file to simulate trtexec output
|
|
164
|
+
def fake_run(cmd, *args, **kwargs):
|
|
165
|
+
cmd_str = " ".join(cmd) if isinstance(cmd, list) else str(cmd)
|
|
166
|
+
if "trtexec" in cmd_str:
|
|
167
|
+
with open(engine_file, "wb") as f:
|
|
168
|
+
f.write(b"TRT_ENGINE_BYTES")
|
|
169
|
+
return MagicMock(returncode=0)
|
|
170
|
+
return real_run(cmd, *args, **kwargs)
|
|
171
|
+
|
|
172
|
+
mock_sub.side_effect = fake_run
|
|
173
|
+
out = gcn.export_tensorrt(engine_file, dummy_inputs=graph)
|
|
174
|
+
assert out.exists()
|
|
175
|
+
assert mock_sub.called
|
|
176
|
+
args, _ = mock_sub.call_args
|
|
177
|
+
assert "--saveEngine=" in " ".join(args[0])
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def test_export_tensorrt_python_api_mocked():
|
|
181
|
+
"""Test TensorRT Python API compilation path with optimization profile."""
|
|
182
|
+
gcn = GCN(in_channels=8, hidden_channels=16, num_layers=2, out_channels=3)
|
|
183
|
+
graph = _create_sample_graph(num_nodes=6, in_channels=8)
|
|
184
|
+
|
|
185
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
186
|
+
engine_file = Path(tmpdir) / "model.engine"
|
|
187
|
+
|
|
188
|
+
# Mock tensorrt module
|
|
189
|
+
mock_trt = MagicMock()
|
|
190
|
+
mock_builder = MagicMock()
|
|
191
|
+
mock_trt.Builder.return_value = mock_builder
|
|
192
|
+
mock_builder.platform_has_fast_fp16 = True
|
|
193
|
+
|
|
194
|
+
mock_parser = MagicMock()
|
|
195
|
+
mock_parser.parse.return_value = True
|
|
196
|
+
mock_parser.num_errors = 0
|
|
197
|
+
mock_trt.OnnxParser.return_value = mock_parser
|
|
198
|
+
|
|
199
|
+
mock_config = MagicMock()
|
|
200
|
+
mock_builder.create_builder_config.return_value = mock_config
|
|
201
|
+
mock_profile = MagicMock()
|
|
202
|
+
mock_builder.create_optimization_profile.return_value = mock_profile
|
|
203
|
+
|
|
204
|
+
# Return serialized engine bytes
|
|
205
|
+
mock_builder.build_serialized_network.return_value = b"TENSORRT_SERIALIZED_PLAN"
|
|
206
|
+
|
|
207
|
+
with patch.dict("sys.modules", {"tensorrt": mock_trt}):
|
|
208
|
+
out = export_tensorrt(
|
|
209
|
+
gcn,
|
|
210
|
+
engine_file,
|
|
211
|
+
dummy_inputs=graph,
|
|
212
|
+
precision="fp16",
|
|
213
|
+
min_shapes={"x": (1, 8), "edge_index": (2, 1)},
|
|
214
|
+
opt_shapes={"x": (6, 8), "edge_index": (2, 6)},
|
|
215
|
+
max_shapes={"x": (50, 8), "edge_index": (2, 100)},
|
|
216
|
+
)
|
|
217
|
+
assert out.exists()
|
|
218
|
+
assert out.read_bytes() == b"TENSORRT_SERIALIZED_PLAN"
|
|
219
|
+
assert mock_builder.create_builder_config.called
|
|
220
|
+
assert mock_builder.create_optimization_profile.called
|
|
221
|
+
assert mock_config.add_optimization_profile.called
|
|
222
|
+
assert mock_config.set_flag.called
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def test_export_tensorrt_direct_onnx_input():
|
|
226
|
+
"""Test export_tensorrt when supplied directly with an existing .onnx file."""
|
|
227
|
+
gcn = GCN(in_channels=8, hidden_channels=16, num_layers=2, out_channels=3)
|
|
228
|
+
graph = _create_sample_graph(num_nodes=6, in_channels=8)
|
|
229
|
+
|
|
230
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
231
|
+
onnx_file = Path(tmpdir) / "pre_exported.onnx"
|
|
232
|
+
export_onnx(gcn, onnx_file, dummy_inputs=graph)
|
|
233
|
+
assert onnx_file.exists()
|
|
234
|
+
|
|
235
|
+
engine_file = Path(tmpdir) / "pre_exported.engine"
|
|
236
|
+
|
|
237
|
+
real_run = subprocess.run
|
|
238
|
+
with patch("shutil.which", return_value="/usr/local/bin/trtexec"), patch("subprocess.run") as mock_sub:
|
|
239
|
+
def fake_run(cmd, *args, **kwargs):
|
|
240
|
+
cmd_str = " ".join(cmd) if isinstance(cmd, list) else str(cmd)
|
|
241
|
+
if "trtexec" in cmd_str:
|
|
242
|
+
with open(engine_file, "wb") as f:
|
|
243
|
+
f.write(b"DIRECT_ONNX_TRT_BYTES")
|
|
244
|
+
return MagicMock(returncode=0)
|
|
245
|
+
return real_run(cmd, *args, **kwargs)
|
|
246
|
+
|
|
247
|
+
mock_sub.side_effect = fake_run
|
|
248
|
+
out = export_tensorrt(str(onnx_file), engine_file)
|
|
249
|
+
assert out.exists()
|
|
250
|
+
assert out.read_bytes() == b"DIRECT_ONNX_TRT_BYTES"
|
|
251
|
+
args, _ = mock_sub.call_args
|
|
252
|
+
cmd_line = " ".join(args[0])
|
|
253
|
+
assert f"--onnx={onnx_file}" in cmd_line
|
|
254
|
+
assert f"--saveEngine={engine_file}" in cmd_line
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def test_export_tensorrt_precision_and_dynamic_shapes_flags():
|
|
258
|
+
"""Test trtexec dynamic shape and INT8 precision flag generation."""
|
|
259
|
+
gcn = GCN(in_channels=8, hidden_channels=16, num_layers=2, out_channels=3)
|
|
260
|
+
graph = _create_sample_graph(num_nodes=6, in_channels=8)
|
|
261
|
+
|
|
262
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
263
|
+
engine_file = Path(tmpdir) / "int8_model.engine"
|
|
264
|
+
|
|
265
|
+
real_run = subprocess.run
|
|
266
|
+
with patch("shutil.which", return_value="/usr/bin/trtexec"), patch("subprocess.run") as mock_sub:
|
|
267
|
+
captured_cmd = []
|
|
268
|
+
|
|
269
|
+
def fake_run(cmd, *args, **kwargs):
|
|
270
|
+
cmd_str = " ".join(cmd) if isinstance(cmd, list) else str(cmd)
|
|
271
|
+
if "trtexec" in cmd_str:
|
|
272
|
+
captured_cmd.extend(cmd)
|
|
273
|
+
with open(engine_file, "wb") as f:
|
|
274
|
+
f.write(b"INT8_ENGINE")
|
|
275
|
+
return MagicMock(returncode=0)
|
|
276
|
+
return real_run(cmd, *args, **kwargs)
|
|
277
|
+
|
|
278
|
+
mock_sub.side_effect = fake_run
|
|
279
|
+
out = gcn.export_tensorrt(
|
|
280
|
+
engine_file,
|
|
281
|
+
dummy_inputs=graph,
|
|
282
|
+
precision="int8",
|
|
283
|
+
min_shapes={"x": (1, 8), "edge_index": (2, 2)},
|
|
284
|
+
opt_shapes={"x": (10, 8), "edge_index": (2, 20)},
|
|
285
|
+
max_shapes={"x": (100, 8), "edge_index": (2, 200)},
|
|
286
|
+
)
|
|
287
|
+
assert out.exists()
|
|
288
|
+
cmd_line = " ".join(captured_cmd)
|
|
289
|
+
assert "--int8" in cmd_line
|
|
290
|
+
assert "--minShapes=x:1x8,edge_index:2x2" in cmd_line
|
|
291
|
+
assert "--optShapes=x:10x8,edge_index:2x20" in cmd_line
|
|
292
|
+
assert "--maxShapes=x:100x8,edge_index:2x200" in cmd_line
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def test_export_tensorrt_missing_tools_raises_import_error():
|
|
296
|
+
"""Test that informative ImportError is raised when neither tensorrt nor trtexec is present."""
|
|
297
|
+
gcn = GCN(in_channels=8, hidden_channels=16, num_layers=2, out_channels=3)
|
|
298
|
+
graph = _create_sample_graph(num_nodes=6, in_channels=8)
|
|
299
|
+
|
|
300
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
301
|
+
engine_file = Path(tmpdir) / "model.engine"
|
|
302
|
+
|
|
303
|
+
# Mock absence of trtexec and ensure tensorrt import fails
|
|
304
|
+
with patch("shutil.which", return_value=None):
|
|
305
|
+
with pytest.raises(ImportError, match="Neither NVIDIA `tensorrt` Python package nor `trtexec` binary"):
|
|
306
|
+
export_tensorrt(gcn, engine_file, dummy_inputs=graph)
|
|
307
|
+
|
|
308
|
+
# Confirm intermediate ONNX file was preserved
|
|
309
|
+
intermediate_onnx = engine_file.with_suffix(".onnx")
|
|
310
|
+
assert intermediate_onnx.exists()
|
|
311
|
+
assert intermediate_onnx.stat().st_size > 0
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def test_export_tensorrt_task_estimator():
|
|
315
|
+
"""Test task estimator .export_tensorrt() method."""
|
|
316
|
+
data = _create_sample_graph(num_nodes=8, in_channels=4)
|
|
317
|
+
clf = NodeClassifier(backbone="gcn", in_channels=4, out_channels=2, hidden_channels=8)
|
|
318
|
+
clf.fit(data, epochs=1, verbose=0)
|
|
319
|
+
|
|
320
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
321
|
+
engine_file = Path(tmpdir) / "task_model.engine"
|
|
322
|
+
|
|
323
|
+
real_run = subprocess.run
|
|
324
|
+
with patch("shutil.which", return_value="/usr/local/bin/trtexec"), patch("subprocess.run") as mock_sub:
|
|
325
|
+
def fake_run(cmd, *args, **kwargs):
|
|
326
|
+
cmd_str = " ".join(cmd) if isinstance(cmd, list) else str(cmd)
|
|
327
|
+
if "trtexec" in cmd_str:
|
|
328
|
+
with open(engine_file, "wb") as f:
|
|
329
|
+
f.write(b"TASK_TRT_BYTES")
|
|
330
|
+
return MagicMock(returncode=0)
|
|
331
|
+
return real_run(cmd, *args, **kwargs)
|
|
332
|
+
|
|
333
|
+
mock_sub.side_effect = fake_run
|
|
334
|
+
out = clf.export_tensorrt(engine_file, dummy_inputs=data)
|
|
335
|
+
assert out.exists()
|
|
336
|
+
assert out.read_bytes() == b"TASK_TRT_BYTES"
|
|
337
|
+
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Turn-key TensorFlow Lite / LiteRT exporter for K3-Node models and tasks."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, Callable, Generator, List, Optional, Tuple, Union
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
import keras
|
|
8
|
+
from keras import ops
|
|
9
|
+
|
|
10
|
+
from k3_node.export.onnx_exporter import _extract_model_and_inputs
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def export_tflite(
|
|
14
|
+
model_or_task: Any,
|
|
15
|
+
output_path: Union[str, Path],
|
|
16
|
+
dummy_inputs: Optional[Any] = None,
|
|
17
|
+
quantization: Optional[str] = None,
|
|
18
|
+
representative_dataset: Optional[Callable[[], Generator[List[np.ndarray], None, None]]] = None,
|
|
19
|
+
verbose: bool = False,
|
|
20
|
+
) -> Path:
|
|
21
|
+
r"""Exports a K3-Node GNN model or task to an optimized TensorFlow Lite flatbuffer.
|
|
22
|
+
|
|
23
|
+
Supports float32, float16 (FP16), and dynamic range INT8 quantization for
|
|
24
|
+
low-latency deployment on edge, mobile, and embedded hardware.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
model_or_task: A K3-Node task instance or model instance.
|
|
28
|
+
output_path: Target path for the `.tflite` file.
|
|
29
|
+
dummy_inputs: Optional sample input data (e.g., PyG `Data` object, tuple of tensors).
|
|
30
|
+
quantization: Quantization strategy:
|
|
31
|
+
- `None` or `"fp32"`: Full 32-bit floating point precision.
|
|
32
|
+
- `"fp16"`: 16-bit floating point quantization (~2x smaller & faster on GPU/NPU).
|
|
33
|
+
- `"int8_dynamic"`: Dynamic range integer quantization (~4x smaller with 8-bit weights).
|
|
34
|
+
- `"int8_full"`: Full 8-bit integer quantization (requires `representative_dataset`).
|
|
35
|
+
representative_dataset: Generator of representative calibration inputs for `"int8_full"`.
|
|
36
|
+
verbose: Whether to print verbose progress. (default: `False`)
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Path object pointing to the generated `.tflite` file.
|
|
40
|
+
"""
|
|
41
|
+
out_file = Path(output_path)
|
|
42
|
+
out_file.parent.mkdir(parents=True, exist_ok=True)
|
|
43
|
+
|
|
44
|
+
from k3_node.export.cross_backend import is_tensorflow_backend, export_via_tf_subprocess
|
|
45
|
+
|
|
46
|
+
if not is_tensorflow_backend():
|
|
47
|
+
return export_via_tf_subprocess(
|
|
48
|
+
exporter_type="tflite",
|
|
49
|
+
model_or_task=model_or_task,
|
|
50
|
+
output_path=output_path,
|
|
51
|
+
dummy_inputs=dummy_inputs,
|
|
52
|
+
quantization=quantization,
|
|
53
|
+
verbose=verbose,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
import tensorflow as tf
|
|
58
|
+
except ImportError:
|
|
59
|
+
raise ImportError(
|
|
60
|
+
"TensorFlow is required for TFLite export. Install it via `pip install tensorflow`."
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
model, inputs, inferred_names = _extract_model_and_inputs(model_or_task, dummy_inputs)
|
|
64
|
+
|
|
65
|
+
# Build concrete function with exact input shapes
|
|
66
|
+
specs = []
|
|
67
|
+
for inp in inputs:
|
|
68
|
+
inp_np = ops.convert_to_numpy(inp)
|
|
69
|
+
specs.append(tf.TensorSpec(shape=inp_np.shape, dtype=tf.as_dtype(inp_np.dtype)))
|
|
70
|
+
|
|
71
|
+
concrete_func = tf.function(lambda *args: model(*args)).get_concrete_function(*specs)
|
|
72
|
+
|
|
73
|
+
converter = tf.lite.TFLiteConverter.from_concrete_functions([concrete_func])
|
|
74
|
+
converter.target_spec.supported_ops = [
|
|
75
|
+
tf.lite.OpsSet.TFLITE_BUILTINS,
|
|
76
|
+
tf.lite.OpsSet.SELECT_TF_OPS,
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
# Apply quantization options
|
|
80
|
+
if quantization in ("fp16", "float16"):
|
|
81
|
+
converter.optimizations = [tf.lite.Optimize.DEFAULT]
|
|
82
|
+
converter.target_spec.supported_types = [tf.float16]
|
|
83
|
+
if verbose:
|
|
84
|
+
print("Applying FP16 quantization to TFLite model.")
|
|
85
|
+
elif quantization in ("int8_dynamic", "int8"):
|
|
86
|
+
converter.optimizations = [tf.lite.Optimize.DEFAULT]
|
|
87
|
+
if verbose:
|
|
88
|
+
print("Applying dynamic range INT8 quantization to TFLite model.")
|
|
89
|
+
elif quantization == "int8_full":
|
|
90
|
+
if representative_dataset is None:
|
|
91
|
+
raise ValueError(
|
|
92
|
+
"Full INT8 quantization (`quantization='int8_full'`) requires a "
|
|
93
|
+
"`representative_dataset` generator to calibrate activation ranges."
|
|
94
|
+
)
|
|
95
|
+
converter.optimizations = [tf.lite.Optimize.DEFAULT]
|
|
96
|
+
converter.representative_dataset = representative_dataset
|
|
97
|
+
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
|
|
98
|
+
converter.inference_input_type = tf.int8
|
|
99
|
+
converter.inference_output_type = tf.int8
|
|
100
|
+
if verbose:
|
|
101
|
+
print("Applying full integer INT8 quantization with calibration dataset.")
|
|
102
|
+
|
|
103
|
+
# Convert model to TFLite flatbuffer
|
|
104
|
+
tflite_model_bytes = converter.convert()
|
|
105
|
+
|
|
106
|
+
with open(out_file, "wb") as f:
|
|
107
|
+
f.write(tflite_model_bytes)
|
|
108
|
+
|
|
109
|
+
if verbose:
|
|
110
|
+
print(f"Exported TFLite model ({len(tflite_model_bytes)} bytes) to {out_file}")
|
|
111
|
+
|
|
112
|
+
return out_file
|
k3_node/hub/__init__.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Hugging Face Hub integration for K3-Node GNN models and graph datasets."""
|
|
2
|
+
|
|
3
|
+
from k3_node.hub.hub_mixin import (
|
|
4
|
+
K3NodeHubMixin,
|
|
5
|
+
from_pretrained,
|
|
6
|
+
push_to_hub,
|
|
7
|
+
save_pretrained,
|
|
8
|
+
)
|
|
9
|
+
from k3_node.hub.model_card import generate_model_card
|
|
10
|
+
from k3_node.hub.dataset_hub import (
|
|
11
|
+
generate_dataset_card,
|
|
12
|
+
load_dataset_from_hub,
|
|
13
|
+
load_graph_dataset,
|
|
14
|
+
push_dataset_to_hub,
|
|
15
|
+
save_graph_dataset,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"K3NodeHubMixin",
|
|
20
|
+
"save_pretrained",
|
|
21
|
+
"from_pretrained",
|
|
22
|
+
"push_to_hub",
|
|
23
|
+
"generate_model_card",
|
|
24
|
+
"save_graph_dataset",
|
|
25
|
+
"load_graph_dataset",
|
|
26
|
+
"generate_dataset_card",
|
|
27
|
+
"push_dataset_to_hub",
|
|
28
|
+
"load_dataset_from_hub",
|
|
29
|
+
]
|