├── .gitignore ├── Changes ├── t ├── models │ └── graph.pb └── AI │ ├── TensorFlow.t │ └── TensorFlow │ ├── hello_tf.t │ ├── create_tensor.t │ ├── load_graph.t │ └── allocate_tensor.t ├── maint └── cpanfile-git ├── cpanfile ├── dist.ini ├── README.pod ├── .github └── workflows │ └── orbital-transfer.yml └── lib └── AI └── TensorFlow.pm /.gitignore: -------------------------------------------------------------------------------- 1 | AI-TensorFlow-* 2 | -------------------------------------------------------------------------------- /Changes: -------------------------------------------------------------------------------- 1 | 0.001 2 | 3 | Features 4 | 5 | - First release. 6 | 7 | -------------------------------------------------------------------------------- /t/models/graph.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EntropyOrg/perl-AI-TensorFlow/HEAD/t/models/graph.pb -------------------------------------------------------------------------------- /maint/cpanfile-git: -------------------------------------------------------------------------------- 1 | requires 'Alien::Libtensorflow', 2 | git => 'https://github.com/EntropyOrg/perl-Alien-Libtensorflow.git', 3 | branch => 'master'; 4 | -------------------------------------------------------------------------------- /cpanfile: -------------------------------------------------------------------------------- 1 | requires 'Capture::Tiny'; 2 | requires 'Path::Tiny'; 3 | requires 'FFI::Platypus'; 4 | requires 'FFI::C'; 5 | requires 'FFI::CheckLib'; 6 | requires 'PDL'; 7 | -------------------------------------------------------------------------------- /t/AI/TensorFlow.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Test::More tests => 1; 4 | use strict; 5 | use warnings; 6 | 7 | use lib 't/lib'; 8 | 9 | use AI::TensorFlow; 10 | 11 | pass; 12 | 13 | done_testing; 14 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name = AI-TensorFlow 2 | version = 0.001 3 | author = EntropyOrg 4 | license = Perl_5 5 | copyright_holder = Zakariyya Mughal 6 | copyright_year = 2021 7 | 8 | [@Author::ZMUGHAL] 9 | 10 | [Encoding] 11 | encoding = bytes 12 | match = t/models/graph.pb 13 | -------------------------------------------------------------------------------- /t/AI/TensorFlow/hello_tf.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Test::More tests => 1; 4 | 5 | use lib 't/lib'; 6 | 7 | use AI::TensorFlow; 8 | 9 | subtest "Get version of Tensorflow" => sub { 10 | my $tf = AI::TensorFlow->new; 11 | note $tf->version; 12 | pass; 13 | }; 14 | 15 | done_testing; 16 | -------------------------------------------------------------------------------- /README.pod: -------------------------------------------------------------------------------- 1 | =pod 2 | 3 | =encoding UTF-8 4 | 5 | =head1 NAME 6 | 7 | AI::TensorFlow - Bindings for TensorFlow deep learning library 8 | 9 | =head1 VERSION 10 | 11 | version 0.001 12 | 13 | =head1 AUTHOR 14 | 15 | EntropyOrg 16 | 17 | =head1 COPYRIGHT AND LICENSE 18 | 19 | This software is copyright (c) 2021 by Zakariyya Mughal. 20 | 21 | This is free software; you can redistribute it and/or modify it under 22 | the same terms as the Perl 5 programming language system itself. 23 | 24 | =cut 25 | -------------------------------------------------------------------------------- /t/AI/TensorFlow/create_tensor.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Test::More tests => 1; 4 | 5 | use strict; 6 | use warnings; 7 | 8 | use lib 't/lib'; 9 | 10 | use AI::TensorFlow; 11 | use PDL; 12 | use PDL::Core ':Internal'; 13 | 14 | use FFI::Platypus::Buffer; 15 | 16 | subtest "Create a tensor" => sub { 17 | my $ffi = FFI::Platypus->new( api => 1 ); 18 | my $pdl_closure = $ffi->closure( sub { 19 | my ($pointer, $size, $pdl_addr) = @_; 20 | # noop 21 | }); 22 | 23 | my $p_data = sequence(float, 1, 5, 12); 24 | my $p_dataref = $p_data->get_dataref; 25 | my ($p_pointer, $p_size) = scalar_to_buffer $$p_dataref; 26 | my $tensor = AI::TensorFlow::Tensor->_New( 27 | AI::TensorFlow::DType::FLOAT, 28 | [ $p_data->dims ], $p_data->ndims, 29 | $p_pointer, $p_size, 30 | $pdl_closure, \$p_data, 31 | ); 32 | 33 | pass; 34 | }; 35 | 36 | done_testing; 37 | -------------------------------------------------------------------------------- /t/AI/TensorFlow/load_graph.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use Test::More tests => 1; 7 | 8 | use AI::TensorFlow; 9 | use Path::Tiny; 10 | 11 | use lib 't/lib'; 12 | 13 | subtest "Load graph" => sub { 14 | my $model_file = path("t/models/graph.pb"); 15 | my $ffi = FFI::Platypus->new( api => 1 ); 16 | 17 | my $data = $model_file->slurp_raw; 18 | my $buf = AI::TensorFlow::Buffer->NewFromData($data); 19 | ok $buf; 20 | 21 | my $graph = AI::TensorFlow::Graph->_New; 22 | my $status = AI::TensorFlow::Status->_New; 23 | my $opts = AI::TensorFlow::ImportGraphDefOptions->_New; 24 | 25 | $graph->ImportGraphDef( $buf, $opts, $status ); 26 | 27 | #$opts->_Delete; 28 | #$buf->_Delete; 29 | 30 | if( $status->GetCode eq 'OK' ) { 31 | print "Load graph success\n"; 32 | pass; 33 | } else { 34 | fail; 35 | } 36 | 37 | #$status->_Delete; 38 | #$graph->_Delete; 39 | pass; 40 | }; 41 | 42 | done_testing; 43 | -------------------------------------------------------------------------------- /t/AI/TensorFlow/allocate_tensor.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Test::More tests => 1; 4 | 5 | use strict; 6 | use warnings; 7 | 8 | use AI::TensorFlow; 9 | use List::Util qw(reduce); 10 | use PDL; 11 | use PDL::Core ':Internal'; 12 | 13 | use FFI::Platypus::Memory; 14 | use FFI::Platypus::Buffer qw(scalar_to_pointer); 15 | 16 | subtest "Allocate a tensor" => sub { 17 | my $ffi = FFI::Platypus->new( api => 1 ); 18 | 19 | my @dims = ( 1, 5, 12 ); 20 | my $ndims = scalar @dims; 21 | my $data_size_bytes = howbig(float) * reduce { $a * $b } (1, @dims); 22 | my $tensor = AI::TensorFlow::Tensor->_Allocate( 23 | AI::TensorFlow::DType::FLOAT, 24 | \@dims, $ndims, 25 | $data_size_bytes, 26 | ); 27 | 28 | ok $tensor && $tensor->Data, 'Allocated tensor'; 29 | 30 | my $pdl = sequence(float, @dims ); 31 | my $pdl_ptr = scalar_to_pointer ${ $pdl->get_dataref }; 32 | 33 | memcpy $tensor->Data, $pdl_ptr, List::Util::min( $data_size_bytes, $tensor->ByteSize ); 34 | 35 | is $tensor->Type, AI::TensorFlow::DType::FLOAT, 'Check Type is FLOAT'; 36 | is $tensor->NumDims, $ndims, 'Check NumDims'; 37 | }; 38 | 39 | done_testing; 40 | -------------------------------------------------------------------------------- /.github/workflows/orbital-transfer.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - '*' 8 | pull_request: 9 | branches: 10 | - '*' 11 | 12 | jobs: 13 | build: 14 | name: ${{ matrix.os }} ${{ matrix.joblabel }} 15 | runs-on: ${{ matrix.os }} 16 | env: 17 | ORBITAL_COVERAGE: ${{ matrix.coverage }} 18 | strategy: 19 | fail-fast: true 20 | matrix: 21 | os: [macos-latest, windows-latest, ubuntu-latest] 22 | coverage: [''] 23 | include: 24 | - os: 'ubuntu-latest' 25 | coverage: coveralls 26 | joblabel: '(Coverage)' 27 | steps: 28 | - uses: actions/checkout@v2 29 | 30 | - name: Cache Orbital 31 | uses: actions/cache@v2 32 | env: 33 | cache-name: cache-orbital 34 | with: 35 | path: | 36 | ~/.orbital/extlib 37 | ~/.orbital/_orbital/author-local 38 | ~/.orbital/local 39 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ matrix.coverage }}-${{ github.sha }} 40 | restore-keys: | 41 | ${{ runner.os }}-build-${{ env.cache-name }}-${{ matrix.coverage }}- 42 | 43 | - name: Set up Orbital Transfer 44 | shell: bash 45 | run: | 46 | eval "$(curl https://raw.githubusercontent.com/orbital-transfer/launch-site/master/script/ci/github-actions-orbital.sh)" 47 | - name: Use Orbital Transfer 48 | env: 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | run: | 51 | perl -S orbitalism bootstrap auto 52 | perl -S orbitalism 53 | perl -S orbitalism test 54 | -------------------------------------------------------------------------------- /lib/AI/TensorFlow.pm: -------------------------------------------------------------------------------- 1 | package AI::TensorFlow; 2 | # ABSTRACT: Bindings for TensorFlow deep learning library 3 | 4 | use strict; 5 | use warnings; 6 | 7 | use FFI::CheckLib 0.28 qw( find_lib_or_die ); 8 | use Alien::Libtensorflow; 9 | use Capture::Tiny; 10 | use Path::Tiny; 11 | 12 | use List::Util qw(first); 13 | use FFI::Platypus; 14 | use FFI::C; 15 | 16 | sub lib { 17 | find_lib_or_die( 18 | lib => 'tensorflow', 19 | symbol => ['TF_Version'], 20 | alien => ['Alien::Libtensorflow'] ); 21 | } 22 | 23 | my $ffi = FFI::Platypus->new( api => 1 ); 24 | $ffi->lib( __PACKAGE__->lib ); 25 | $ffi->mangler(sub { 26 | my($name) = @_; 27 | "TF_$name"; 28 | }); 29 | 30 | $ffi->type('(opaque,size_t)->void', 'data_deallocator_t'); 31 | $ffi->type('(opaque,size_t,opaque)->void', 'tensor_deallocator_t'); 32 | 33 | # ::TensorFlow {{{ 34 | sub new { 35 | my ($class) = @_; 36 | bless {}, $class; 37 | } 38 | 39 | $ffi->attach( [ Version => 'version' ] => [], 'string' );#}}} 40 | 41 | # enum TF_Code {{{ 42 | # From 43 | $ffi->load_custom_type('::Enum', 'TF_Code', 44 | OK => 0, 45 | CANCELLED => 1, 46 | UNKNOWN => 2, 47 | INVALID_ARGUMENT => 3, 48 | DEADLINE_EXCEEDED => 4, 49 | NOT_FOUND => 5, 50 | ALREADY_EXISTS => 6, 51 | PERMISSION_DENIED => 7, 52 | UNAUTHENTICATED => 16, 53 | RESOURCE_EXHAUSTED => 8, 54 | FAILED_PRECONDITION => 9, 55 | ABORTED => 10, 56 | OUT_OF_RANGE => 11, 57 | UNIMPLEMENTED => 12, 58 | INTERNAL => 13, 59 | UNAVAILABLE => 14, 60 | DATA_LOSS => 15, 61 | ); 62 | #}}} 63 | # enum TF_DataType {{{ 64 | $ffi->load_custom_type('::Enum', 'TF_DataType', 65 | { rev => 'int', package => 'AI::TensorFlow::DType' }, 66 | # from tensorflow/c/tf_datatype.h 67 | [ FLOAT => 1 ], 68 | [ DOUBLE => 2 ], 69 | [ INT32 => 3 ], #// Int32 tensors are always in 'host' memory. 70 | [ UINT8 => 4 ], 71 | [ INT16 => 5 ], 72 | [ INT8 => 6 ], 73 | [ STRING => 7 ], 74 | [ COMPLEX64 => 8 ], # // Single-precision complex 75 | [ COMPLEX => 8 ], # // Old identifier kept for API backwards compatibility 76 | [ INT64 => 9 ], 77 | [ BOOL => 10 ], 78 | [ QINT8 => 11 ],# // Quantized int8 79 | [ QUINT8 => 12 ],# // Quantized uint8 80 | [ QINT32 => 13 ],# // Quantized int32 81 | [ BFLOAT16 => 14 ],# // Float32 truncated to 16 bits. Only for cast ops. 82 | [ QINT16 => 15 ],# // Quantized int16 83 | [ QUINT16 => 16 ],# // Quantized uint16 84 | [ UINT16 => 17 ], 85 | [ COMPLEX128 => 18 ],# // Double-precision complex 86 | [ HALF => 19 ], 87 | [ RESOURCE => 20 ], 88 | [ VARIANT => 21 ], 89 | [ UINT32 => 22 ], 90 | [ UINT64 => 23 ], 91 | ); 92 | 93 | package AI::TensorFlow::DType { 94 | 95 | }#}}} 96 | 97 | 98 | FFI::C->ffi($ffi); 99 | 100 | package AI::TensorFlow::Buffer {#{{{ 101 | use FFI::Platypus::Buffer; 102 | use FFI::Platypus::Memory; 103 | 104 | FFI::C->struct( 'TF_Buffer' => [ 105 | data => 'opaque', 106 | length => 'size_t', 107 | _data_deallocator => 'opaque', # data_deallocator_t 108 | ]); 109 | 110 | sub data_deallocator { 111 | my ($self, $coderef) = shift; 112 | 113 | return $self->{_data_deallocator_closure} unless $coderef; 114 | 115 | my $closure = $ffi->closure( $coderef ); 116 | 117 | $closure->sticky; 118 | $self->{_data_deallocator_closure} = $closure; 119 | 120 | my $opaque = $ffi->cast('data_deallocator_t', 'opaque', $closure); 121 | $self->_data_deallocator( $opaque ); 122 | } 123 | 124 | $ffi->attach( [ 'NewBuffer' => '_New' ] => [] => 'TF_Buffer' ); 125 | 126 | sub NewFromData { # TODO look at Python high-level API 127 | my ($class, $data) = @_; 128 | 129 | my $buf = $class->_New; 130 | 131 | my ($pointer, $size) = scalar_to_buffer $data; 132 | 133 | $buf->data( $pointer ); 134 | $buf->length( $size ); 135 | $buf->data_deallocator( sub { 136 | my ($pointer, $size) = @_; 137 | free $pointer; 138 | }); 139 | 140 | $buf; 141 | } 142 | 143 | $ffi->attach( [ 'DeleteBuffer' => '_Delete' ] => [ 'TF_Buffer' ], 'void' ); 144 | }#}}} 145 | package AI::TensorFlow::Graph {#{{{ 146 | FFI::C->struct( 'TF_Graph' => [ 147 | ]); 148 | 149 | $ffi->attach( [ 'NewGraph' => '_New' ] => [] => 'TF_Graph' ); 150 | 151 | $ffi->attach( [ 'DeleteGraph' => '_Delete' ] => [ 'TF_Graph' ], 'void' ); 152 | }#}}} 153 | package AI::TensorFlow::Status {#{{{ 154 | FFI::C->struct( 'TF_Status' => [ 155 | ]); 156 | 157 | $ffi->attach( [ 'NewStatus' => '_New' ] => [] => 'TF_Status' ); 158 | 159 | $ffi->attach( 'GetCode' => [ 'TF_Status' ], 'TF_Code' ); 160 | 161 | $ffi->attach( [ 'DeleteStatus' => '_Delete' ] => [ 'TF_Status' ], 'void' ); 162 | }#}}} 163 | package AI::TensorFlow::ImportGraphDefOptions {#{{{ 164 | FFI::C->struct( 'TF_ImportGraphDefOptions' => [ 165 | ]); 166 | 167 | $ffi->attach( [ 'NewImportGraphDefOptions' => '_New' ] => [] => 'TF_ImportGraphDefOptions' ); 168 | 169 | $ffi->attach( [ 'DeleteImportGraphDefOptions' => '_Delete' ] => [] => 'TF_ImportGraphDefOptions' ); 170 | }#}}} 171 | package AI::TensorFlow::Tensor {#{{{ 172 | FFI::C->struct( 'TF_Tensor' => [ 173 | ]); 174 | 175 | # C: TF_NewTensor 176 | # 177 | # Constructor 178 | $ffi->attach( [ 'NewTensor' => '_New' ] => 179 | [ 'TF_DataType', # dtype 180 | 181 | 'int64_t[]', # (dims) 182 | 'int', # (num_dims) 183 | 184 | 'opaque', # (data) 185 | 'size_t', # (len) 186 | 187 | 'opaque', # tensor_deallocator_t (deallocator) 188 | 'opaque', # (deallocator_arg) 189 | ], 190 | => 'TF_Tensor' => sub { 191 | my ($xs, $class, 192 | $dtype, 193 | $dims, $num_dims, 194 | $data, $len, 195 | $deallocator, $deallocator_arg, 196 | ) = @_; 197 | my $deallocator_ptr = $ffi->cast( 'tensor_deallocator_t', 'opaque', $deallocator); 198 | my $obj = $xs->( 199 | $dtype, 200 | $dims, $num_dims, 201 | $data, $len, 202 | $deallocator_ptr, $deallocator_arg, 203 | ); 204 | 205 | $obj->{PDL} = $$deallocator_arg; 206 | 207 | $obj; 208 | }); 209 | 210 | 211 | # C: TF_AllocateTensor 212 | # 213 | # Constructor 214 | $ffi->attach( [ 'AllocateTensor', '_Allocate' ], 215 | [ 'TF_DataType', # dtype' 216 | 'int64_t[]', # (dims) 217 | 'int', # (num_dims) 218 | 'size_t', # (len) 219 | ], 220 | => 'TF_Tensor' => sub { 221 | my ($xs, $class, @rest) = @_; 222 | my $obj = $xs->(@rest); 223 | } 224 | ); 225 | 226 | # C: TF_TensorData 227 | $ffi->attach( [ 'TensorData' => 'Data' ], 228 | [ 'TF_Tensor' ], 229 | => 'opaque' 230 | ); 231 | 232 | # C: TF_TensorByteSize 233 | $ffi->attach( [ 'TensorByteSize' => 'ByteSize' ], 234 | [ 'TF_Tensor' ], 235 | => 'size_t' 236 | ); 237 | 238 | # C: TF_TensorType 239 | $ffi->attach( [ 'TensorType' => 'Type' ], 240 | [ 'TF_Tensor' ], 241 | => 'TF_DataType', 242 | ); 243 | 244 | # C: TF_NumDims 245 | $ffi->attach( [ 'NumDims' => 'NumDims' ], 246 | [ 'TF_Tensor' ], 247 | => 'int', 248 | ); 249 | } 250 | #}}} 251 | 252 | $ffi->attach( [ GraphImportGraphDef => 'AI::TensorFlow::Graph::ImportGraphDef' ], 253 | [ 'TF_Graph', 'TF_Buffer', 'TF_ImportGraphDefOptions', 'TF_Status' ], 254 | => 'void', 255 | ); 256 | 257 | 258 | __END__ 259 | 260 | # ::Status {{{ 261 | package AI::TensorFlow::Status { 262 | } 263 | #}}} 264 | 265 | 1; 266 | # vim:fdm=marker 267 | --------------------------------------------------------------------------------