Line data Source code
1 : // SPDX-License-Identifier: Apache-2.0
2 :
3 : #include "kompute/Tensor.hpp"
4 :
5 : namespace kp {
6 :
7 : std::string
8 0 : Tensor::toString(Tensor::TensorDataTypes dt)
9 : {
10 0 : switch (dt) {
11 0 : case TensorDataTypes::eBool:
12 0 : return "eBool";
13 0 : case TensorDataTypes::eInt:
14 0 : return "eInt";
15 0 : case TensorDataTypes::eUnsignedInt:
16 0 : return "eUnsignedInt";
17 0 : case TensorDataTypes::eFloat:
18 0 : return "eFloat";
19 0 : case TensorDataTypes::eDouble:
20 0 : return "eDouble";
21 0 : default:
22 0 : return "unknown";
23 : }
24 : }
25 :
26 : std::string
27 193 : Tensor::toString(Tensor::TensorTypes dt)
28 : {
29 193 : switch (dt) {
30 175 : case TensorTypes::eDevice:
31 175 : return "eDevice";
32 14 : case TensorTypes::eHost:
33 14 : return "eHost";
34 4 : case TensorTypes::eStorage:
35 4 : return "eStorage";
36 0 : default:
37 0 : return "unknown";
38 : }
39 : }
40 :
41 97 : Tensor::Tensor(std::shared_ptr<vk::PhysicalDevice> physicalDevice,
42 : std::shared_ptr<vk::Device> device,
43 : void* data,
44 : uint32_t elementTotalCount,
45 : uint32_t elementMemorySize,
46 : const TensorDataTypes& dataType,
47 97 : const TensorTypes& tensorType)
48 : {
49 291 : KP_LOG_DEBUG("Kompute Tensor constructor data length: {}, and type: {}",
50 : elementTotalCount,
51 : Tensor::toString(tensorType));
52 :
53 97 : this->mPhysicalDevice = physicalDevice;
54 97 : this->mDevice = device;
55 97 : this->mDataType = dataType;
56 97 : this->mTensorType = tensorType;
57 :
58 97 : this->rebuild(data, elementTotalCount, elementMemorySize);
59 102 : }
60 :
61 96 : Tensor::~Tensor()
62 : {
63 288 : KP_LOG_DEBUG("Kompute Tensor destructor started. Type: {}",
64 : Tensor::toString(this->tensorType()));
65 :
66 96 : if (this->mDevice) {
67 87 : this->destroy();
68 : }
69 :
70 192 : KP_LOG_DEBUG("Kompute Tensor destructor success");
71 96 : }
72 :
73 : void
74 97 : Tensor::rebuild(void* data,
75 : uint32_t elementTotalCount,
76 : uint32_t elementMemorySize)
77 : {
78 194 : KP_LOG_DEBUG("Kompute Tensor rebuilding with size {}", elementTotalCount);
79 :
80 97 : this->mSize = elementTotalCount;
81 97 : this->mDataTypeMemorySize = elementMemorySize;
82 :
83 97 : if (this->mPrimaryBuffer || this->mPrimaryMemory) {
84 0 : KP_LOG_DEBUG(
85 : "Kompute Tensor destroying existing resources before rebuild");
86 0 : this->destroy();
87 : }
88 :
89 97 : this->allocateMemoryCreateGPUResources();
90 :
91 96 : if (this->tensorType() != Tensor::TensorTypes::eStorage) {
92 94 : this->mapRawData();
93 94 : memcpy(this->mRawData, data, this->memorySize());
94 : }
95 96 : }
96 :
97 : Tensor::TensorTypes
98 465 : Tensor::tensorType()
99 : {
100 465 : return this->mTensorType;
101 : }
102 :
103 : bool
104 22 : Tensor::isInit()
105 : {
106 37 : return this->mDevice && this->mPrimaryBuffer && this->mPrimaryMemory &&
107 37 : this->mRawData;
108 : }
109 :
110 : uint32_t
111 1327 : Tensor::size()
112 : {
113 1327 : return this->mSize;
114 : }
115 :
116 : uint32_t
117 0 : Tensor::dataTypeMemorySize()
118 : {
119 0 : return this->mDataTypeMemorySize;
120 : }
121 :
122 : uint32_t
123 1054 : Tensor::memorySize()
124 : {
125 1054 : return this->mSize * this->mDataTypeMemorySize;
126 : }
127 :
128 : kp::Tensor::TensorDataTypes
129 22 : Tensor::dataType()
130 : {
131 22 : return this->mDataType;
132 : }
133 :
134 : void*
135 6 : Tensor::rawData()
136 : {
137 6 : return this->mRawData;
138 : }
139 :
140 : void
141 6 : Tensor::setRawData(const void* data)
142 : {
143 6 : memcpy(this->mRawData, data, this->memorySize());
144 6 : }
145 :
146 : void
147 94 : Tensor::mapRawData()
148 : {
149 :
150 188 : KP_LOG_DEBUG("Kompute Tensor mapping data from host buffer");
151 :
152 94 : std::shared_ptr<vk::DeviceMemory> hostVisibleMemory = nullptr;
153 :
154 94 : if (this->mTensorType == TensorTypes::eHost) {
155 7 : hostVisibleMemory = this->mPrimaryMemory;
156 87 : } else if (this->mTensorType == TensorTypes::eDevice) {
157 87 : hostVisibleMemory = this->mStagingMemory;
158 : } else {
159 0 : KP_LOG_WARN(
160 : "Kompute Tensor mapping data not supported on {} tensor", toString(this->tensorType()));
161 0 : return;
162 : }
163 :
164 94 : vk::DeviceSize bufferSize = this->memorySize();
165 :
166 : // Given we request coherent host memory we don't need to invalidate /
167 : // flush
168 94 : this->mRawData = this->mDevice->mapMemory(
169 94 : *hostVisibleMemory, 0, bufferSize, vk::MemoryMapFlags());
170 :
171 94 : }
172 :
173 : void
174 94 : Tensor::unmapRawData()
175 : {
176 :
177 188 : KP_LOG_DEBUG("Kompute Tensor mapping data from host buffer");
178 :
179 94 : std::shared_ptr<vk::DeviceMemory> hostVisibleMemory = nullptr;
180 :
181 94 : if (this->mTensorType == TensorTypes::eHost) {
182 7 : hostVisibleMemory = this->mPrimaryMemory;
183 87 : } else if (this->mTensorType == TensorTypes::eDevice) {
184 87 : hostVisibleMemory = this->mStagingMemory;
185 : } else {
186 0 : KP_LOG_WARN(
187 : "Kompute Tensor mapping data not supported on {} tensor", toString(this->tensorType()));
188 0 : return;
189 : }
190 :
191 94 : vk::DeviceSize bufferSize = this->memorySize();
192 94 : vk::MappedMemoryRange mappedRange(*hostVisibleMemory, 0, bufferSize);
193 94 : this->mDevice->flushMappedMemoryRanges(1, &mappedRange);
194 94 : this->mDevice->unmapMemory(*hostVisibleMemory);
195 94 : }
196 :
197 : void
198 8 : Tensor::recordCopyFrom(const vk::CommandBuffer& commandBuffer,
199 : std::shared_ptr<Tensor> copyFromTensor)
200 : {
201 :
202 8 : vk::DeviceSize bufferSize(this->memorySize());
203 8 : vk::BufferCopy copyRegion(0, 0, bufferSize);
204 :
205 16 : KP_LOG_DEBUG("Kompute Tensor recordCopyFrom data size {}.", bufferSize);
206 :
207 8 : this->recordCopyBuffer(commandBuffer,
208 8 : copyFromTensor->mPrimaryBuffer,
209 8 : this->mPrimaryBuffer,
210 : bufferSize,
211 : copyRegion);
212 8 : }
213 :
214 : void
215 77 : Tensor::recordCopyFromStagingToDevice(const vk::CommandBuffer& commandBuffer)
216 : {
217 77 : vk::DeviceSize bufferSize(this->memorySize());
218 77 : vk::BufferCopy copyRegion(0, 0, bufferSize);
219 :
220 154 : KP_LOG_DEBUG("Kompute Tensor copying data size {}.", bufferSize);
221 :
222 77 : this->recordCopyBuffer(commandBuffer,
223 77 : this->mStagingBuffer,
224 77 : this->mPrimaryBuffer,
225 : bufferSize,
226 : copyRegion);
227 77 : }
228 :
229 : void
230 78 : Tensor::recordCopyFromDeviceToStaging(const vk::CommandBuffer& commandBuffer)
231 : {
232 78 : vk::DeviceSize bufferSize(this->memorySize());
233 78 : vk::BufferCopy copyRegion(0, 0, bufferSize);
234 :
235 156 : KP_LOG_DEBUG("Kompute Tensor copying data size {}.", bufferSize);
236 :
237 78 : this->recordCopyBuffer(commandBuffer,
238 78 : this->mPrimaryBuffer,
239 78 : this->mStagingBuffer,
240 : bufferSize,
241 : copyRegion);
242 78 : }
243 :
244 : void
245 163 : Tensor::recordCopyBuffer(const vk::CommandBuffer& commandBuffer,
246 : std::shared_ptr<vk::Buffer> bufferFrom,
247 : std::shared_ptr<vk::Buffer> bufferTo,
248 : vk::DeviceSize /*bufferSize*/,
249 : vk::BufferCopy copyRegion)
250 : {
251 :
252 163 : commandBuffer.copyBuffer(*bufferFrom, *bufferTo, copyRegion);
253 163 : }
254 :
255 : void
256 251 : Tensor::recordPrimaryBufferMemoryBarrier(const vk::CommandBuffer& commandBuffer,
257 : vk::AccessFlagBits srcAccessMask,
258 : vk::AccessFlagBits dstAccessMask,
259 : vk::PipelineStageFlagBits srcStageMask,
260 : vk::PipelineStageFlagBits dstStageMask)
261 : {
262 502 : KP_LOG_DEBUG("Kompute Tensor recording PRIMARY buffer memory barrier");
263 :
264 251 : this->recordBufferMemoryBarrier(commandBuffer,
265 251 : *this->mPrimaryBuffer,
266 : srcAccessMask,
267 : dstAccessMask,
268 : srcStageMask,
269 : dstStageMask);
270 251 : }
271 :
272 : void
273 0 : Tensor::recordStagingBufferMemoryBarrier(const vk::CommandBuffer& commandBuffer,
274 : vk::AccessFlagBits srcAccessMask,
275 : vk::AccessFlagBits dstAccessMask,
276 : vk::PipelineStageFlagBits srcStageMask,
277 : vk::PipelineStageFlagBits dstStageMask)
278 : {
279 0 : KP_LOG_DEBUG("Kompute Tensor recording STAGING buffer memory barrier");
280 :
281 0 : this->recordBufferMemoryBarrier(commandBuffer,
282 0 : *this->mStagingBuffer,
283 : srcAccessMask,
284 : dstAccessMask,
285 : srcStageMask,
286 : dstStageMask);
287 0 : }
288 :
289 : void
290 251 : Tensor::recordBufferMemoryBarrier(const vk::CommandBuffer& commandBuffer,
291 : const vk::Buffer& buffer,
292 : vk::AccessFlagBits srcAccessMask,
293 : vk::AccessFlagBits dstAccessMask,
294 : vk::PipelineStageFlagBits srcStageMask,
295 : vk::PipelineStageFlagBits dstStageMask)
296 : {
297 502 : KP_LOG_DEBUG("Kompute Tensor recording buffer memory barrier");
298 :
299 251 : vk::DeviceSize bufferSize = this->memorySize();
300 :
301 251 : vk::BufferMemoryBarrier bufferMemoryBarrier;
302 251 : bufferMemoryBarrier.buffer = buffer;
303 251 : bufferMemoryBarrier.size = bufferSize;
304 251 : bufferMemoryBarrier.srcAccessMask = srcAccessMask;
305 251 : bufferMemoryBarrier.dstAccessMask = dstAccessMask;
306 251 : bufferMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
307 251 : bufferMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
308 :
309 251 : commandBuffer.pipelineBarrier(srcStageMask,
310 : dstStageMask,
311 : vk::DependencyFlags(),
312 : nullptr,
313 : bufferMemoryBarrier,
314 : nullptr);
315 251 : }
316 :
317 : vk::DescriptorBufferInfo
318 84 : Tensor::constructDescriptorBufferInfo()
319 : {
320 252 : KP_LOG_DEBUG("Kompute Tensor construct descriptor buffer info size {}",
321 : this->memorySize());
322 84 : vk::DeviceSize bufferSize = this->memorySize();
323 84 : return vk::DescriptorBufferInfo(*this->mPrimaryBuffer,
324 : 0, // offset
325 84 : bufferSize);
326 : }
327 :
328 : vk::BufferUsageFlags
329 97 : Tensor::getPrimaryBufferUsageFlags()
330 : {
331 97 : switch (this->mTensorType) {
332 88 : case TensorTypes::eDevice:
333 88 : return vk::BufferUsageFlagBits::eStorageBuffer |
334 88 : vk::BufferUsageFlagBits::eTransferSrc |
335 176 : vk::BufferUsageFlagBits::eTransferDst;
336 : break;
337 7 : case TensorTypes::eHost:
338 7 : return vk::BufferUsageFlagBits::eStorageBuffer |
339 7 : vk::BufferUsageFlagBits::eTransferSrc |
340 14 : vk::BufferUsageFlagBits::eTransferDst;
341 : break;
342 2 : case TensorTypes::eStorage:
343 2 : return vk::BufferUsageFlagBits::eStorageBuffer;
344 : break;
345 0 : default:
346 0 : throw std::runtime_error("Kompute Tensor invalid tensor type");
347 : }
348 : }
349 :
350 : vk::MemoryPropertyFlags
351 96 : Tensor::getPrimaryMemoryPropertyFlags()
352 : {
353 96 : switch (this->mTensorType) {
354 87 : case TensorTypes::eDevice:
355 87 : return vk::MemoryPropertyFlagBits::eDeviceLocal;
356 : break;
357 7 : case TensorTypes::eHost:
358 : return vk::MemoryPropertyFlagBits::eHostVisible |
359 7 : vk::MemoryPropertyFlagBits::eHostCoherent;
360 : break;
361 2 : case TensorTypes::eStorage:
362 2 : return vk::MemoryPropertyFlagBits::eDeviceLocal;
363 : break;
364 0 : default:
365 0 : throw std::runtime_error("Kompute Tensor invalid tensor type");
366 : }
367 : }
368 :
369 : vk::BufferUsageFlags
370 87 : Tensor::getStagingBufferUsageFlags()
371 : {
372 87 : switch (this->mTensorType) {
373 87 : case TensorTypes::eDevice:
374 : return vk::BufferUsageFlagBits::eTransferSrc |
375 87 : vk::BufferUsageFlagBits::eTransferDst;
376 : break;
377 0 : default:
378 0 : throw std::runtime_error("Kompute Tensor invalid tensor type");
379 : }
380 : }
381 :
382 : vk::MemoryPropertyFlags
383 87 : Tensor::getStagingMemoryPropertyFlags()
384 : {
385 87 : switch (this->mTensorType) {
386 87 : case TensorTypes::eDevice:
387 : return vk::MemoryPropertyFlagBits::eHostVisible |
388 87 : vk::MemoryPropertyFlagBits::eHostCoherent;
389 : break;
390 0 : default:
391 0 : throw std::runtime_error("Kompute Tensor invalid tensor type");
392 : }
393 : }
394 :
395 : void
396 97 : Tensor::allocateMemoryCreateGPUResources()
397 : {
398 194 : KP_LOG_DEBUG("Kompute Tensor creating buffer");
399 :
400 97 : if (!this->mPhysicalDevice) {
401 0 : throw std::runtime_error("Kompute Tensor phyisical device is null");
402 : }
403 97 : if (!this->mDevice) {
404 0 : throw std::runtime_error("Kompute Tensor device is null");
405 : }
406 :
407 194 : KP_LOG_DEBUG("Kompute Tensor creating primary buffer and memory");
408 :
409 97 : this->mPrimaryBuffer = std::make_shared<vk::Buffer>();
410 98 : this->createBuffer(this->mPrimaryBuffer,
411 : this->getPrimaryBufferUsageFlags());
412 96 : this->mFreePrimaryBuffer = true;
413 96 : this->mPrimaryMemory = std::make_shared<vk::DeviceMemory>();
414 192 : this->allocateBindMemory(this->mPrimaryBuffer,
415 96 : this->mPrimaryMemory,
416 : this->getPrimaryMemoryPropertyFlags());
417 96 : this->mFreePrimaryMemory = true;
418 :
419 96 : if (this->mTensorType == TensorTypes::eDevice) {
420 174 : KP_LOG_DEBUG("Kompute Tensor creating staging buffer and memory");
421 :
422 87 : this->mStagingBuffer = std::make_shared<vk::Buffer>();
423 87 : this->createBuffer(this->mStagingBuffer,
424 : this->getStagingBufferUsageFlags());
425 87 : this->mFreeStagingBuffer = true;
426 87 : this->mStagingMemory = std::make_shared<vk::DeviceMemory>();
427 174 : this->allocateBindMemory(this->mStagingBuffer,
428 87 : this->mStagingMemory,
429 : this->getStagingMemoryPropertyFlags());
430 87 : this->mFreeStagingMemory = true;
431 : }
432 :
433 192 : KP_LOG_DEBUG("Kompute Tensor buffer & memory creation successful");
434 96 : }
435 :
436 : void
437 184 : Tensor::createBuffer(std::shared_ptr<vk::Buffer> buffer,
438 : vk::BufferUsageFlags bufferUsageFlags)
439 : {
440 :
441 184 : vk::DeviceSize bufferSize = this->memorySize();
442 :
443 184 : if (bufferSize < 1) {
444 1 : throw std::runtime_error(
445 2 : "Kompute Tensor attempted to create a zero-sized buffer");
446 : }
447 :
448 549 : KP_LOG_DEBUG("Kompute Tensor creating buffer with memory size: {}, and "
449 : "usage flags: {}",
450 : bufferSize,
451 : vk::to_string(bufferUsageFlags));
452 :
453 : // TODO: Explore having concurrent sharing mode (with option)
454 : vk::BufferCreateInfo bufferInfo(vk::BufferCreateFlags(),
455 : bufferSize,
456 : bufferUsageFlags,
457 183 : vk::SharingMode::eExclusive);
458 :
459 183 : this->mDevice->createBuffer(&bufferInfo, nullptr, buffer.get());
460 183 : }
461 :
462 : void
463 183 : Tensor::allocateBindMemory(std::shared_ptr<vk::Buffer> buffer,
464 : std::shared_ptr<vk::DeviceMemory> memory,
465 : vk::MemoryPropertyFlags memoryPropertyFlags)
466 : {
467 :
468 366 : KP_LOG_DEBUG("Kompute Tensor allocating and binding memory");
469 :
470 : vk::PhysicalDeviceMemoryProperties memoryProperties =
471 183 : this->mPhysicalDevice->getMemoryProperties();
472 :
473 : vk::MemoryRequirements memoryRequirements =
474 183 : this->mDevice->getBufferMemoryRequirements(*buffer);
475 :
476 183 : uint32_t memoryTypeIndex = -1;
477 183 : bool memoryTypeIndexFound = false;
478 183 : for (uint32_t i = 0; i < memoryProperties.memoryTypeCount; i++) {
479 183 : if (memoryRequirements.memoryTypeBits & (1 << i)) {
480 366 : if (((memoryProperties.memoryTypes[i]).propertyFlags &
481 183 : memoryPropertyFlags) == memoryPropertyFlags) {
482 183 : memoryTypeIndex = i;
483 183 : memoryTypeIndexFound = true;
484 183 : break;
485 : }
486 : }
487 : }
488 183 : if (!memoryTypeIndexFound) {
489 0 : throw std::runtime_error(
490 0 : "Memory type index for buffer creation not found");
491 : }
492 :
493 549 : KP_LOG_DEBUG(
494 : "Kompute Tensor allocating memory index: {}, size {}, flags: {}",
495 : memoryTypeIndex,
496 : memoryRequirements.size,
497 : vk::to_string(memoryPropertyFlags));
498 :
499 : vk::MemoryAllocateInfo memoryAllocateInfo(memoryRequirements.size,
500 183 : memoryTypeIndex);
501 :
502 183 : this->mDevice->allocateMemory(&memoryAllocateInfo, nullptr, memory.get());
503 :
504 183 : this->mDevice->bindBufferMemory(*buffer, *memory, 0);
505 183 : }
506 :
507 : void
508 99 : Tensor::destroy()
509 : {
510 198 : KP_LOG_DEBUG("Kompute Tensor started destroy()");
511 :
512 : // Setting raw data to null regardless whether device is available to
513 : // invalidate Tensor
514 99 : this->mRawData = nullptr;
515 99 : this->mSize = 0;
516 99 : this->mDataTypeMemorySize = 0;
517 :
518 99 : if (!this->mDevice) {
519 6 : KP_LOG_WARN(
520 : "Kompute Tensor destructor reached with null Device pointer");
521 3 : return;
522 : }
523 :
524 : // Unmap the current memory data
525 96 : if (this->tensorType() != Tensor::TensorTypes::eStorage) {
526 94 : this->unmapRawData();
527 : }
528 :
529 96 : if (this->mFreePrimaryBuffer) {
530 96 : if (!this->mPrimaryBuffer) {
531 0 : KP_LOG_WARN("Kompose Tensor expected to destroy primary buffer "
532 : "but got null buffer");
533 : } else {
534 192 : KP_LOG_DEBUG("Kompose Tensor destroying primary buffer");
535 96 : this->mDevice->destroy(
536 96 : *this->mPrimaryBuffer,
537 : (vk::Optional<const vk::AllocationCallbacks>)nullptr);
538 96 : this->mPrimaryBuffer = nullptr;
539 96 : this->mFreePrimaryBuffer = false;
540 : }
541 : }
542 :
543 96 : if (this->mFreeStagingBuffer) {
544 87 : if (!this->mStagingBuffer) {
545 0 : KP_LOG_WARN("Kompose Tensor expected to destroy staging buffer "
546 : "but got null buffer");
547 : } else {
548 174 : KP_LOG_DEBUG("Kompose Tensor destroying staging buffer");
549 87 : this->mDevice->destroy(
550 87 : *this->mStagingBuffer,
551 : (vk::Optional<const vk::AllocationCallbacks>)nullptr);
552 87 : this->mStagingBuffer = nullptr;
553 87 : this->mFreeStagingBuffer = false;
554 : }
555 : }
556 :
557 96 : if (this->mFreePrimaryMemory) {
558 96 : if (!this->mPrimaryMemory) {
559 0 : KP_LOG_WARN("Kompose Tensor expected to free primary memory but "
560 : "got null memory");
561 : } else {
562 192 : KP_LOG_DEBUG("Kompose Tensor freeing primary memory");
563 96 : this->mDevice->freeMemory(
564 96 : *this->mPrimaryMemory,
565 : (vk::Optional<const vk::AllocationCallbacks>)nullptr);
566 96 : this->mPrimaryMemory = nullptr;
567 96 : this->mFreePrimaryMemory = false;
568 : }
569 : }
570 :
571 96 : if (this->mFreeStagingMemory) {
572 87 : if (!this->mStagingMemory) {
573 0 : KP_LOG_WARN("Kompose Tensor expected to free staging memory but "
574 : "got null memory");
575 : } else {
576 174 : KP_LOG_DEBUG("Kompose Tensor freeing staging memory");
577 87 : this->mDevice->freeMemory(
578 87 : *this->mStagingMemory,
579 : (vk::Optional<const vk::AllocationCallbacks>)nullptr);
580 87 : this->mStagingMemory = nullptr;
581 87 : this->mFreeStagingMemory = false;
582 : }
583 : }
584 :
585 96 : if (this->mDevice) {
586 96 : this->mDevice = nullptr;
587 : }
588 :
589 192 : KP_LOG_DEBUG("Kompute Tensor successful destroy()");
590 : }
591 :
592 : template<>
593 : Tensor::TensorDataTypes
594 0 : TensorT<bool>::dataType()
595 : {
596 0 : return Tensor::TensorDataTypes::eBool;
597 : }
598 :
599 : template<>
600 : Tensor::TensorDataTypes
601 3 : TensorT<int32_t>::dataType()
602 : {
603 3 : return Tensor::TensorDataTypes::eInt;
604 : }
605 :
606 : template<>
607 : Tensor::TensorDataTypes
608 5 : TensorT<uint32_t>::dataType()
609 : {
610 5 : return Tensor::TensorDataTypes::eUnsignedInt;
611 : }
612 :
613 : template<>
614 : Tensor::TensorDataTypes
615 89 : TensorT<float>::dataType()
616 : {
617 89 : return Tensor::TensorDataTypes::eFloat;
618 : }
619 :
620 : template<>
621 : Tensor::TensorDataTypes
622 0 : TensorT<double>::dataType()
623 : {
624 0 : return Tensor::TensorDataTypes::eDouble;
625 : }
626 :
627 : }
|