Initial import: grid-bot — grid trading bot for BTC-USDT on Cifra Markets
This commit is contained in:
@@ -0,0 +1,456 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include <c10/core/Device.h>
|
||||
#include <c10/core/DeviceType.h>
|
||||
#include <c10/core/alignment.h>
|
||||
#include <c10/macros/Export.h>
|
||||
#include <c10/macros/Macros.h>
|
||||
#include <c10/util/Exception.h>
|
||||
#include <c10/util/ThreadLocalDebugInfo.h>
|
||||
#include <c10/util/UniqueVoidPtr.h>
|
||||
#include <c10/util/irange.h>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
using CaptureId_t = unsigned long long;
|
||||
// first is set if the instance is created by CUDAGraph::capture_begin.
|
||||
// second is set if the instance is created by at::cuda::graph_pool_handle.
|
||||
using MempoolId_t = std::pair<CaptureId_t, CaptureId_t>;
|
||||
|
||||
struct MempoolIdHash {
|
||||
std::size_t operator()(const MempoolId_t& mempool_id) const noexcept {
|
||||
return mempool_id.first != 0 ? mempool_id.first : mempool_id.second;
|
||||
}
|
||||
};
|
||||
|
||||
// A DataPtr is a unique pointer (with an attached deleter and some
|
||||
// context for the deleter) to some memory, which also records what
|
||||
// device is for its data.
|
||||
//
|
||||
// nullptr DataPtrs can still have a nontrivial device; this allows
|
||||
// us to treat zero-size allocations uniformly with non-zero allocations.
|
||||
//
|
||||
class C10_API DataPtr {
|
||||
private:
|
||||
c10::detail::UniqueVoidPtr ptr_;
|
||||
Device device_;
|
||||
|
||||
public:
|
||||
// Choice of CPU here is arbitrary; if there's an "undefined" device
|
||||
// we could use that too
|
||||
DataPtr() : device_(DeviceType::CPU) {}
|
||||
DataPtr(void* data, Device device) : ptr_(data), device_(device) {}
|
||||
DataPtr(void* data, void* ctx, DeleterFnPtr ctx_deleter, Device device)
|
||||
: ptr_(data, ctx, ctx_deleter), device_(device) {}
|
||||
void* operator->() const {
|
||||
return ptr_.get();
|
||||
}
|
||||
C10_ALWAYS_INLINE bool /* success */ unsafe_reset_data_and_ctx(
|
||||
void* new_data_and_ctx) {
|
||||
return ptr_.unsafe_reset_data_and_ctx(new_data_and_ctx);
|
||||
}
|
||||
void clear() {
|
||||
ptr_.clear();
|
||||
}
|
||||
void* get() const {
|
||||
return ptr_.get();
|
||||
}
|
||||
void* mutable_get() {
|
||||
return ptr_.get();
|
||||
}
|
||||
void* get_context() const {
|
||||
return ptr_.get_context();
|
||||
}
|
||||
void* release_context() {
|
||||
return ptr_.release_context();
|
||||
}
|
||||
std::unique_ptr<void, DeleterFnPtr>&& move_context() {
|
||||
return ptr_.move_context();
|
||||
}
|
||||
operator bool() const {
|
||||
return static_cast<bool>(ptr_);
|
||||
}
|
||||
template <typename T>
|
||||
T* cast_context(DeleterFnPtr expected_deleter) const {
|
||||
return ptr_.cast_context<T>(expected_deleter);
|
||||
}
|
||||
DeleterFnPtr get_deleter() const {
|
||||
return ptr_.get_deleter();
|
||||
}
|
||||
/**
|
||||
* Compare the deleter in a DataPtr to expected_deleter.
|
||||
* If it matches, replace the deleter with new_deleter
|
||||
* and return true; otherwise, does nothing and returns
|
||||
* false.
|
||||
*
|
||||
* In general, it is not safe to unconditionally set the
|
||||
* deleter on a DataPtr, because you don't know what
|
||||
* the deleter is, and thus will have a hard time properly
|
||||
* disposing of the deleter without storing the original
|
||||
* deleter (this is difficult to do, because DeleterFnPtr
|
||||
* is not a closure, and because the context on DataPtr is
|
||||
* only a single word, you generally don't have enough
|
||||
* space to store both the original deleter and its context).
|
||||
* However, in some cases, you know /exactly/ what the deleter
|
||||
* is, and you have a new deleter that manually wraps
|
||||
* the old one. In this case, you can safely swap the deleter
|
||||
* after asserting that the deleters line up.
|
||||
*
|
||||
* What are the requirements on new_deleter? It must still
|
||||
* properly dispose of the void* pointer passed in as its argument,
|
||||
* where void* is whatever the context of the original deleter
|
||||
* is. So in general, you expect the new deleter to look something
|
||||
* like this:
|
||||
*
|
||||
* [](void* ptr) {
|
||||
* some_new_stuff(ptr);
|
||||
* get_orig_allocator()->raw_deleter(ptr);
|
||||
* }
|
||||
*
|
||||
* Note that it won't work to close over the original
|
||||
* allocator; you don't have enough space to do that! Also,
|
||||
* it's unsafe to assume that the passed in pointer in
|
||||
* question is the memory pointer in question; it might not
|
||||
* be; be sure to read the source code of the Allocator
|
||||
* in question to confirm this.
|
||||
*/
|
||||
[[nodiscard]] bool compare_exchange_deleter(
|
||||
DeleterFnPtr expected_deleter,
|
||||
DeleterFnPtr new_deleter) {
|
||||
return ptr_.compare_exchange_deleter(expected_deleter, new_deleter);
|
||||
}
|
||||
Device device() const {
|
||||
return device_;
|
||||
}
|
||||
// Unsafely mutates the device on a DataPtr. Under normal use,
|
||||
// you should never actually need to call this function.
|
||||
// We used to need this for the implementation of the hack detailed
|
||||
// in Note [Masquerading as CUDA], but that hack has been removed.
|
||||
// Other uses of this function now exist so it cannot be deprecated.
|
||||
void unsafe_set_device(Device device) {
|
||||
device_ = device;
|
||||
}
|
||||
};
|
||||
|
||||
// NB: Device is NOT tested for here; a CUDA nullptr is as much a nullptr as a
|
||||
// CPU nullptr
|
||||
|
||||
inline bool operator==(const DataPtr& dp, std::nullptr_t) noexcept {
|
||||
return !dp;
|
||||
}
|
||||
inline bool operator==(std::nullptr_t, const DataPtr& dp) noexcept {
|
||||
return !dp;
|
||||
}
|
||||
inline bool operator!=(const DataPtr& dp, std::nullptr_t) noexcept {
|
||||
return dp;
|
||||
}
|
||||
inline bool operator!=(std::nullptr_t, const DataPtr& dp) noexcept {
|
||||
return dp;
|
||||
}
|
||||
|
||||
// Note [raw_allocate/raw_deallocate and Thrust]
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Thrust's support for custom allocators requires us to write something
|
||||
// like this:
|
||||
//
|
||||
// class ThrustAllocator {
|
||||
// char* allocate(size_t);
|
||||
// void deallocate(char*, size_t);
|
||||
// };
|
||||
//
|
||||
// This is not good for our unique_ptr based allocator interface, as
|
||||
// there is no way to get to the context when we free.
|
||||
//
|
||||
// However, in some cases the context is exactly the same as
|
||||
// the data pointer. In this case, we can support the "raw"
|
||||
// allocate and deallocate interface. This is what
|
||||
// raw_deleter signifies. By default, it returns a nullptr, which means that
|
||||
// the raw interface is not implemented. Be sure to implement it whenever
|
||||
// possible, or the raw interface will incorrectly reported as unsupported,
|
||||
// when it is actually possible.
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-special-member-functions)
|
||||
struct C10_API Allocator {
|
||||
virtual ~Allocator() = default;
|
||||
|
||||
virtual DataPtr allocate(size_t n) = 0;
|
||||
|
||||
// Clones an allocation that came from this allocator.
|
||||
//
|
||||
// To perform the copy, this function calls `copy_data`, which
|
||||
// must be implemented by derived classes.
|
||||
//
|
||||
// Note that this explicitly ignores any context that may have been
|
||||
// attached to the input data.
|
||||
//
|
||||
// Requires: input data was allocated by the same allocator.
|
||||
DataPtr clone(const void* data, std::size_t n);
|
||||
|
||||
// Checks if DataPtr has a simple context, not wrapped with any out of the
|
||||
// ordinary contexts.
|
||||
virtual bool is_simple_data_ptr(const DataPtr& data_ptr) const;
|
||||
|
||||
// If this returns a non nullptr, it means that allocate()
|
||||
// is guaranteed to return a unique_ptr with this deleter attached;
|
||||
// it means the rawAllocate and rawDeallocate APIs are safe to use.
|
||||
// This function MUST always return the same BoundDeleter.
|
||||
virtual DeleterFnPtr raw_deleter() const {
|
||||
return nullptr;
|
||||
}
|
||||
void* raw_allocate(size_t n) {
|
||||
auto dptr = allocate(n);
|
||||
AT_ASSERT(dptr.get() == dptr.get_context());
|
||||
return dptr.release_context();
|
||||
}
|
||||
void raw_deallocate(void* ptr) {
|
||||
auto d = raw_deleter();
|
||||
AT_ASSERT(d);
|
||||
d(ptr);
|
||||
}
|
||||
|
||||
// Copies data from one allocation to another.
|
||||
// Pure virtual, so derived classes must define behavior.
|
||||
// Derived class implementation can simply call `default_copy_data`
|
||||
// to use `std::memcpy`.
|
||||
//
|
||||
// Requires: src and dest were allocated by this allocator
|
||||
// Requires: src and dest both have length >= count
|
||||
virtual void copy_data(void* dest, const void* src, std::size_t count)
|
||||
const = 0;
|
||||
|
||||
protected:
|
||||
// Uses `std::memcpy` to copy data.
|
||||
// Child classes can use this as `copy_data` when an alternative copy
|
||||
// API is not needed.
|
||||
void default_copy_data(void* dest, const void* src, std::size_t count) const;
|
||||
};
|
||||
|
||||
// This context is used to generate DataPtr which have arbitrary
|
||||
// std::function deleters associated with them. In some user facing
|
||||
// functions, we give a (user-friendly) interface for constructing
|
||||
// tensors from external data which take an arbitrary std::function
|
||||
// deleter. Grep for InefficientStdFunctionContext to find these
|
||||
// occurrences.
|
||||
//
|
||||
// This context is inefficient because we have to do a dynamic
|
||||
// allocation InefficientStdFunctionContext, on top of the dynamic
|
||||
// allocation which is implied by std::function itself.
|
||||
struct C10_API InefficientStdFunctionContext {
|
||||
void* ptr_{nullptr};
|
||||
std::function<void(void*)> deleter_;
|
||||
InefficientStdFunctionContext(void* ptr, std::function<void(void*)> deleter)
|
||||
: ptr_(ptr), deleter_(std::move(deleter)) {}
|
||||
InefficientStdFunctionContext(const InefficientStdFunctionContext&) = delete;
|
||||
InefficientStdFunctionContext(InefficientStdFunctionContext&& rhs) noexcept
|
||||
: ptr_(std::exchange(rhs.ptr_, nullptr)),
|
||||
deleter_(std::move(rhs.deleter_)) {}
|
||||
InefficientStdFunctionContext& operator=(
|
||||
const InefficientStdFunctionContext&) = delete;
|
||||
// NOLINTNEXTLINE(*-noexcept-move-*)
|
||||
InefficientStdFunctionContext& operator=(
|
||||
InefficientStdFunctionContext&& rhs) {
|
||||
this->~InefficientStdFunctionContext();
|
||||
ptr_ = std::exchange(rhs.ptr_, nullptr);
|
||||
deleter_ = std::move(rhs.deleter_);
|
||||
return *this;
|
||||
}
|
||||
~InefficientStdFunctionContext() {
|
||||
if (deleter_) {
|
||||
deleter_(ptr_);
|
||||
}
|
||||
}
|
||||
static DataPtr makeDataPtr(
|
||||
void* ptr,
|
||||
std::function<void(void*)> deleter,
|
||||
Device device);
|
||||
};
|
||||
|
||||
/** Set the allocator for DeviceType `t`. The passed in allocator pointer is
|
||||
* expected to have static lifetime; this function does NOT take ownership
|
||||
* of the raw pointer. (The reason for this is to prevent existing pointers
|
||||
* to an allocator of a particular device from being invalidated when
|
||||
* SetAllocator is called.)
|
||||
*
|
||||
* Also note that this is not thread-safe, and we assume this function will
|
||||
* only be called during initialization.
|
||||
*
|
||||
* The 'priority' flag is introduced when we want to overwrite the default
|
||||
* allocator, since the allocators are set statically. The default priority
|
||||
* is 0, which means the lowest. Only higher or equal priority can overwrite
|
||||
* existing ones.
|
||||
*/
|
||||
C10_API void SetAllocator(DeviceType t, Allocator* alloc, uint8_t priority = 0);
|
||||
C10_API Allocator* GetAllocator(const DeviceType& t);
|
||||
|
||||
template <DeviceType t>
|
||||
struct AllocatorRegisterer {
|
||||
explicit AllocatorRegisterer(Allocator* alloc) {
|
||||
SetAllocator(t, alloc);
|
||||
}
|
||||
};
|
||||
|
||||
#define REGISTER_ALLOCATOR(t, f) \
|
||||
namespace { \
|
||||
static c10::AllocatorRegisterer<t> g_allocator_d(f); \
|
||||
}
|
||||
|
||||
// An interface for reporting thread local memory usage
|
||||
// per device
|
||||
struct C10_API MemoryReportingInfoBase : public c10::DebugInfoBase {
|
||||
/**
|
||||
* alloc_size corresponds to the size of the ptr.
|
||||
*
|
||||
* total_allocated corresponds to total allocated memory.
|
||||
*
|
||||
* total_reserved corresponds to total size of memory pool, both used and
|
||||
* unused, if applicable.
|
||||
*/
|
||||
virtual void reportMemoryUsage(
|
||||
void* ptr,
|
||||
int64_t alloc_size,
|
||||
size_t total_allocated,
|
||||
size_t total_reserved,
|
||||
Device device) = 0;
|
||||
|
||||
virtual void reportOutOfMemory(
|
||||
int64_t alloc_size,
|
||||
size_t total_allocated,
|
||||
size_t total_reserved,
|
||||
Device device);
|
||||
|
||||
virtual bool memoryProfilingEnabled() const = 0;
|
||||
};
|
||||
|
||||
C10_API bool memoryProfilingEnabled();
|
||||
C10_API void reportMemoryUsageToProfiler(
|
||||
void* ptr,
|
||||
int64_t alloc_size,
|
||||
size_t total_allocated,
|
||||
size_t total_reserved,
|
||||
Device device);
|
||||
|
||||
C10_API void reportOutOfMemoryToProfiler(
|
||||
int64_t alloc_size,
|
||||
size_t total_allocated,
|
||||
size_t total_reserved,
|
||||
Device device);
|
||||
|
||||
// used to hold traceback information in allocators
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-special-member-functions)
|
||||
struct GatheredContext {
|
||||
virtual ~GatheredContext() = default;
|
||||
};
|
||||
|
||||
namespace CachingAllocator {
|
||||
struct Stat {
|
||||
void increase(size_t amount) {
|
||||
current += static_cast<int64_t>(amount);
|
||||
peak = std::max(current, peak);
|
||||
allocated += static_cast<int64_t>(amount);
|
||||
}
|
||||
|
||||
void decrease(size_t amount) {
|
||||
current -= static_cast<int64_t>(amount);
|
||||
TORCH_INTERNAL_ASSERT_DEBUG_ONLY(
|
||||
current >= 0,
|
||||
"Negative tracked stat in device allocator (likely logic error).");
|
||||
freed += static_cast<int64_t>(amount);
|
||||
}
|
||||
|
||||
void reset_accumulated() {
|
||||
allocated = 0;
|
||||
freed = 0;
|
||||
}
|
||||
|
||||
void reset_peak() {
|
||||
peak = current;
|
||||
}
|
||||
|
||||
int64_t current = 0;
|
||||
int64_t peak = 0;
|
||||
int64_t allocated = 0;
|
||||
int64_t freed = 0;
|
||||
};
|
||||
|
||||
enum struct StatType : uint64_t {
|
||||
AGGREGATE = 0,
|
||||
SMALL_POOL = 1,
|
||||
LARGE_POOL = 2,
|
||||
NUM_TYPES = 3 // remember to update this whenever a new stat type is added
|
||||
};
|
||||
|
||||
using StatArray = std::array<Stat, static_cast<size_t>(StatType::NUM_TYPES)>;
|
||||
using StatTypes = std::array<bool, static_cast<size_t>(StatType::NUM_TYPES)>;
|
||||
|
||||
template <typename Func>
|
||||
void for_each_selected_stat_type(const StatTypes& stat_types, Func f) {
|
||||
for (const auto stat_type : c10::irange(stat_types.size())) {
|
||||
if (stat_types[stat_type]) {
|
||||
f(stat_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Structure for keeping timing information
|
||||
struct DurationStat {
|
||||
void increase(int64_t amount) {
|
||||
total += amount;
|
||||
count += 1;
|
||||
max = std::max(amount, max);
|
||||
if (min == 0) {
|
||||
min = amount;
|
||||
} else {
|
||||
min = std::min(amount, min);
|
||||
}
|
||||
}
|
||||
|
||||
void reset_accumulated() {
|
||||
total = 0;
|
||||
count = 0;
|
||||
}
|
||||
|
||||
void reset_peak() {
|
||||
min = 0;
|
||||
max = 0;
|
||||
}
|
||||
|
||||
int64_t total = 0;
|
||||
int64_t max = 0;
|
||||
int64_t min = 0;
|
||||
int64_t count = 0;
|
||||
};
|
||||
|
||||
// Size pretty-printer
|
||||
inline std::string format_size(uint64_t size) {
|
||||
std::ostringstream os;
|
||||
os.precision(2);
|
||||
os << std::fixed;
|
||||
if (size <= 1024) {
|
||||
os << size << " bytes";
|
||||
} else if (size <= 1048576) {
|
||||
os << (static_cast<double>(size) / 1024.0);
|
||||
os << " KiB";
|
||||
} else if (size <= 1073741824ULL) {
|
||||
os << static_cast<double>(size) / 1048576.0;
|
||||
os << " MiB";
|
||||
} else {
|
||||
os << static_cast<double>(size) / 1073741824.0;
|
||||
os << " GiB";
|
||||
}
|
||||
return os.str();
|
||||
}
|
||||
|
||||
} // namespace CachingAllocator
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,399 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/DeviceType.h>
|
||||
#include <c10/util/Exception.h>
|
||||
#include <c10/util/llvmMathExtras.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace c10::CachingAllocator {
|
||||
|
||||
// "small" allocations are packed in 2 MiB blocks
|
||||
constexpr size_t kSmallBuffer = 2097152;
|
||||
// all sizes are rounded to at least 512 bytes
|
||||
constexpr size_t kMinBlockSize = 512;
|
||||
// largest "small" allocation is 1 MiB
|
||||
constexpr size_t kSmallSize = 1048576;
|
||||
// allocations between 1 and 10 MiB may use kLargeBuffer
|
||||
constexpr size_t kMinLargeAlloc = 10485760;
|
||||
// round up large allocations to 2 MiB
|
||||
constexpr size_t kRoundLarge = 2097152;
|
||||
|
||||
// A utility class for tokenizing allocator configuration strings into discrete
|
||||
// parts. For example, the config string:
|
||||
// "key1:val1,key2:[val2,val3]"
|
||||
// is tokenized into:
|
||||
// "key1", ":", "val1", ",", "key2", ":", "[", "val2", ",", "val3", "]",
|
||||
//
|
||||
// Tokens include keys, values, and special characters (':', ',', '[', ']').
|
||||
// Whitespace is ignored.
|
||||
class ConfigTokenizer {
|
||||
public:
|
||||
explicit ConfigTokenizer(const std::string& env) {
|
||||
std::string buffer;
|
||||
for (char ch : env) {
|
||||
if (ch == ',' || ch == ':' || ch == '[' || ch == ']') {
|
||||
if (!buffer.empty()) {
|
||||
config_.emplace_back(std::move(buffer));
|
||||
buffer.clear();
|
||||
}
|
||||
config_.emplace_back(1, ch);
|
||||
} else if (!std::isspace(static_cast<unsigned char>(ch))) {
|
||||
buffer += ch;
|
||||
}
|
||||
}
|
||||
if (!buffer.empty()) {
|
||||
config_.emplace_back(std::move(buffer));
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& operator[](size_t i) const {
|
||||
TORCH_INTERNAL_ASSERT(
|
||||
i < config_.size(), "Index out of bounds in ConfigTokenizer");
|
||||
return config_[i];
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
return config_.size();
|
||||
}
|
||||
|
||||
bool checkToken(size_t i, const std::string& token) const {
|
||||
checkIndex(i);
|
||||
return config_[i] == token;
|
||||
}
|
||||
|
||||
size_t toSizeT(size_t i) const {
|
||||
checkIndex(i);
|
||||
return std::stoull(config_[i]);
|
||||
}
|
||||
|
||||
double toDouble(size_t i) const {
|
||||
checkIndex(i);
|
||||
return std::stod(config_[i]);
|
||||
}
|
||||
|
||||
bool toBool(size_t i) const {
|
||||
checkIndex(i);
|
||||
const auto& token = config_[i];
|
||||
if (token == "True") {
|
||||
return true;
|
||||
} else if (token == "False") {
|
||||
return false;
|
||||
} else {
|
||||
TORCH_CHECK_VALUE(
|
||||
false,
|
||||
"Expected 'True' or 'False' at index ",
|
||||
i,
|
||||
" in ConfigTokenizer but got '",
|
||||
token,
|
||||
"'");
|
||||
}
|
||||
}
|
||||
|
||||
// Skips the current token group and returns the index of the value token.
|
||||
// Assumes the current index `i` points to a key name in a key-value pair.
|
||||
size_t skipKey(size_t i) const {
|
||||
// Expect a colon after the key
|
||||
checkToken(++i, ":");
|
||||
|
||||
++i; // Move to the value
|
||||
checkIndex(i);
|
||||
if (config_[i] != "[") {
|
||||
// Value is a single token (not a list) -> return its index
|
||||
return i;
|
||||
}
|
||||
|
||||
// Skip tokens inside the list until matching ']'
|
||||
// NOLINTNEXTLINE(bugprone-inc-dec-in-conditions)
|
||||
while (++i < config_.size() && config_[i] != "]") {
|
||||
}
|
||||
|
||||
TORCH_INTERNAL_ASSERT(
|
||||
i < config_.size(),
|
||||
"Expected closing bracket ']' in ConfigTokenizer but reached end of config");
|
||||
|
||||
return i; // Return the index of the closing ']'
|
||||
}
|
||||
|
||||
private:
|
||||
void checkIndex(size_t i) const {
|
||||
TORCH_INTERNAL_ASSERT(
|
||||
i < config_.size(), "Index out of bounds in ConfigTokenizer");
|
||||
}
|
||||
|
||||
std::vector<std::string> config_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Note [AcceleratorAllocatorConfig design]
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
* This class configures memory allocation for both device and host memory. A
|
||||
* single `AcceleratorAllocatorConfig` instance is shared across all accelerator
|
||||
* backends, such as CUDA and XPU, under the assumption that relevant
|
||||
* environment variables apply uniformly to all accelerators. Device-specific
|
||||
* configuration extensions are supported via hooks (see
|
||||
* `registerDeviceConfigParserHook`).
|
||||
*
|
||||
* Recommended design:
|
||||
* - Place common configurations in `AcceleratorAllocatorConfig`.
|
||||
* - Extend backend-specific configurations in corresponding device-specific
|
||||
* classes, such as `CUDAAllocatorConfig`, etc.
|
||||
*
|
||||
* Scope:
|
||||
* - Configuration options must be environment-variable driven.
|
||||
*
|
||||
* Naming Convention:
|
||||
* - Public API names in `AcceleratorAllocatorConfig` should be device-generic.
|
||||
* - Members prefixed with `pinned_` are specific to the host/pinned allocator.
|
||||
* - Environment variable names should be generic across backends.
|
||||
* - Comma-separated key-value pairs in the format: `key:value`. Use square
|
||||
* brackets `[]` for list values Example: `key1:123, key2:[val1,val2]`
|
||||
*
|
||||
* Environment Variables:
|
||||
* - The primary environment variable for configuration is `PYTORCH_ALLOC_CONF`.
|
||||
* - For backward compatibility, `PYTORCH_CUDA_ALLOC_CONF` is also supported
|
||||
* with lower priority.
|
||||
*/
|
||||
|
||||
class C10_API AcceleratorAllocatorConfig {
|
||||
public:
|
||||
static AcceleratorAllocatorConfig& instance();
|
||||
|
||||
C10_DISABLE_COPY_AND_ASSIGN(AcceleratorAllocatorConfig);
|
||||
AcceleratorAllocatorConfig(AcceleratorAllocatorConfig&&) = delete;
|
||||
AcceleratorAllocatorConfig& operator=(AcceleratorAllocatorConfig&&) = delete;
|
||||
~AcceleratorAllocatorConfig() = default;
|
||||
|
||||
/* Device allocator settings */
|
||||
|
||||
static size_t large_segment_size() {
|
||||
return instance().large_segment_size_;
|
||||
}
|
||||
|
||||
// Returns the maximum block size (in MB) that is allowed to be split. The
|
||||
// default is unlimited (all blocks can be split).
|
||||
static size_t max_split_size() {
|
||||
return instance().max_split_size_;
|
||||
}
|
||||
|
||||
// Returns the maximum block size (in MB) that is allowed to be rounded up
|
||||
// without requiring splitting when searching for a free block. The default is
|
||||
// 20 MiB.
|
||||
static size_t max_non_split_rounding_size() {
|
||||
return instance().max_non_split_rounding_size_;
|
||||
}
|
||||
|
||||
// Return the number of divisions used when rounding up allocation sizes (in
|
||||
// MB) to the nearest power-of-2 boundary.
|
||||
static size_t roundup_power2_divisions(size_t size);
|
||||
|
||||
// Returns the vector of division factors used for rounding up allocation
|
||||
// sizes. These divisions apply to size intervals between 1MB and 64GB.
|
||||
static const std::vector<size_t>& roundup_power2_divisions() {
|
||||
return instance().roundup_power2_divisions_;
|
||||
}
|
||||
|
||||
// Returns the threshold that triggers garbage collection when the ratio of
|
||||
// used memory to maximum allowed memory exceeds this value. The default is 0,
|
||||
// meaning no garbage collection is triggered. The value should be in the
|
||||
// range (0.0, 1.0).
|
||||
static double garbage_collection_threshold() {
|
||||
return instance().garbage_collection_threshold_;
|
||||
}
|
||||
|
||||
// Returns whether the expandable segment feature is enabled. This allows the
|
||||
// allocator to start with one segment that grows as needed, rather than
|
||||
// creating a new segment for each allocation. Default is false (expandable
|
||||
// segments disabled).
|
||||
static bool use_expandable_segments() {
|
||||
return instance().use_expandable_segments_;
|
||||
}
|
||||
|
||||
/* Host allocator settings */
|
||||
|
||||
// Returns whether the pinned host allocator uses background threads for
|
||||
// processing events. This is useful for improving performance in scenarios
|
||||
// where many small allocations are made. Default is false (background threads
|
||||
// disabled).
|
||||
static bool pinned_use_background_threads() {
|
||||
return instance().pinned_use_background_threads_;
|
||||
}
|
||||
|
||||
/* Settings for both device and host allocator */
|
||||
|
||||
// Returns the current allocator settings as a string. This string is useful
|
||||
// to expand device-specific allocator configurations
|
||||
static std::string last_allocator_settings() {
|
||||
std::lock_guard<std::mutex> lock(instance().last_allocator_settings_mutex_);
|
||||
return instance().last_allocator_settings_;
|
||||
}
|
||||
|
||||
// Use `Construct On First Use Idiom` to avoid `Static Initialization Order`
|
||||
// issue.
|
||||
static std::unordered_set<std::string>& getMutableKeys() {
|
||||
static std::unordered_set<std::string> keys{
|
||||
"large_segment_size_mb",
|
||||
"max_split_size_mb",
|
||||
"max_non_split_rounding_mb",
|
||||
"garbage_collection_threshold",
|
||||
"roundup_power2_divisions",
|
||||
"expandable_segments",
|
||||
"pinned_use_background_threads"};
|
||||
return keys;
|
||||
}
|
||||
|
||||
// Returns the set of valid keys for the allocator configuration.
|
||||
// This set is used to validate the presence and correctness of keys in
|
||||
// device-specific configuration parsers.
|
||||
static const std::unordered_set<std::string>& getKeys() {
|
||||
return getMutableKeys();
|
||||
}
|
||||
|
||||
// Optional hook for parsing additional device-specific allocator settings.
|
||||
// This allows backends (e.g., CUDA, XPU) to register a custom parser for
|
||||
// their own environment configuration extensions.
|
||||
static std::function<void(const std::string&)>& getConfigParserHook() {
|
||||
static std::function<void(const std::string&)> hook{nullptr};
|
||||
return hook;
|
||||
}
|
||||
|
||||
// Registers a device-specific configuration parser hook and its key. This
|
||||
// allows backends to parse additional device-specific configuration options
|
||||
// from the environment variable. The hook should be a function that takes a
|
||||
// string (the environment variable value) and parses it to set
|
||||
// device-specific configuration options. The hook will be called when the
|
||||
// environment variable is parsed. If a hook is already registered, it will be
|
||||
// replaced with the new one.
|
||||
static void registerDeviceConfigParserHook(
|
||||
std::function<void(const std::string&)>&& hook,
|
||||
const std::unordered_set<std::string>& keys) {
|
||||
getConfigParserHook() = std::move(hook);
|
||||
auto& mutable_keys = getMutableKeys();
|
||||
for (auto& key : keys) {
|
||||
TORCH_CHECK_VALUE(
|
||||
mutable_keys.insert(key).second,
|
||||
"Duplicated key '",
|
||||
key,
|
||||
"' found in device-specific configuration parser hook registration");
|
||||
}
|
||||
}
|
||||
|
||||
// Calls the registered device-specific configuration parser hook with the
|
||||
// provided environment string. This allows backends to parse additional
|
||||
// device-specific configuration options from the environment variable.
|
||||
// If no hook is registered, this function does nothing.
|
||||
static void callDeviceConfigParserHook(const std::string& env) {
|
||||
if (getConfigParserHook()) {
|
||||
getConfigParserHook()(env);
|
||||
}
|
||||
}
|
||||
|
||||
// Parses the environment variable `env` to update the allocator settings.
|
||||
// If the environment variable is not set, it does nothing.
|
||||
// The configuration string should be a comma-separated list of key-value
|
||||
// pairs, where each key is a configuration option and the value is the
|
||||
// corresponding setting. For example:
|
||||
// "max_split_size_mb:100,max_non_split_rounding_mb:20,garbage_collection_threshold:0.5,roundup_power2_divisions:[64:8,256:4,1024:4,>:1],expandable_segments:true,pinned_use_background_threads:true"
|
||||
void parseArgs(const std::string& env);
|
||||
|
||||
private:
|
||||
AcceleratorAllocatorConfig();
|
||||
|
||||
/* Internal functions for device allocator */
|
||||
|
||||
// Parse `large_segment_size_mb` from environment variable.
|
||||
size_t parseLargeSegmentSize(const ConfigTokenizer& tokenizer, size_t i);
|
||||
// Parse `max_split_size_mb` from environment variable.
|
||||
size_t parseMaxSplitSize(const ConfigTokenizer& tokenizer, size_t i);
|
||||
// Parse `max_non_split_rounding_mb` from environment variable.
|
||||
size_t parseMaxNonSplitRoundingSize(
|
||||
const ConfigTokenizer& tokenizer,
|
||||
size_t i);
|
||||
// Parse `garbage_collection_threshold` from environment variable.
|
||||
size_t parseGarbageCollectionThreshold(
|
||||
const ConfigTokenizer& tokenizer,
|
||||
size_t i);
|
||||
// Parse `roundup_power2_divisions` from environment variable.
|
||||
size_t parseRoundUpPower2Divisions(
|
||||
const ConfigTokenizer& tokenizer,
|
||||
size_t i);
|
||||
// Parse `expandable_segments` from environment variable.
|
||||
size_t parseExpandableSegments(const ConfigTokenizer& tokenizer, size_t i);
|
||||
|
||||
/* Internal functions for host allocator */
|
||||
|
||||
// Parse `pinned_use_background_threads` from environment variable.
|
||||
size_t parsePinnedUseBackgroundThreads(
|
||||
const ConfigTokenizer& tokenizer,
|
||||
size_t i);
|
||||
|
||||
/* The following members are specifically used for the device allocator. */
|
||||
|
||||
// "large" allocations may be packed in blocks of this size
|
||||
std::atomic<size_t> large_segment_size_{20971520}; // 20 MB by default
|
||||
// The maximum block size that is allowed to be split.
|
||||
std::atomic<size_t> max_split_size_{std::numeric_limits<size_t>::max()};
|
||||
// The maximum allowable extra size of a memory block without requiring
|
||||
// splitting when searching for a free block.
|
||||
std::atomic<size_t> max_non_split_rounding_size_;
|
||||
// Used to store how memory allocations of different sizes should be rounded
|
||||
// up to the nearest power of 2 divisions.
|
||||
std::vector<size_t> roundup_power2_divisions_;
|
||||
// The threshold that triggers garbage collection when the ratio of used
|
||||
// memory to maximum allowed memory exceeds this value.
|
||||
std::atomic<double> garbage_collection_threshold_{0};
|
||||
// A flag to enable expandable segments feature.
|
||||
std::atomic<bool> use_expandable_segments_{false};
|
||||
|
||||
/* The following members are specifically used for the host allocator. */
|
||||
|
||||
// A flag to enable background thread for processing events.
|
||||
std::atomic<bool> pinned_use_background_threads_{false};
|
||||
|
||||
/* The following members are used for both device and host allocator. */
|
||||
|
||||
// Record the last allocator config environment setting.
|
||||
std::mutex last_allocator_settings_mutex_;
|
||||
std::string last_allocator_settings_;
|
||||
};
|
||||
|
||||
C10_API inline void setAllocatorSettings(const std::string& env) {
|
||||
AcceleratorAllocatorConfig::instance().parseArgs(env);
|
||||
AcceleratorAllocatorConfig::callDeviceConfigParserHook(env);
|
||||
}
|
||||
|
||||
C10_API inline std::string getAllocatorSettings() {
|
||||
return AcceleratorAllocatorConfig::instance().last_allocator_settings();
|
||||
}
|
||||
|
||||
struct DeviceConfigParserHookRegistry {
|
||||
explicit DeviceConfigParserHookRegistry(
|
||||
std::function<void(const std::string&)>&& hook,
|
||||
const std::unordered_set<std::string>& keys) {
|
||||
// Use static method to avoid static initialization order fiasco issues
|
||||
AcceleratorAllocatorConfig::registerDeviceConfigParserHook(
|
||||
std::move(hook), keys);
|
||||
}
|
||||
};
|
||||
|
||||
// Assume each config parser has `parseArgs` and `getKeys` methods
|
||||
#define REGISTER_ALLOCATOR_CONFIG_PARSE_HOOK(parser_cls) \
|
||||
namespace { \
|
||||
static at::CachingAllocator::DeviceConfigParserHookRegistry \
|
||||
g_device_config_parse_hook_registry_instance( \
|
||||
[](const std::string& env) { \
|
||||
parser_cls::instance().parseArgs(env); \
|
||||
}, \
|
||||
parser_cls::getKeys()); \
|
||||
}
|
||||
|
||||
} // namespace c10::CachingAllocator
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,90 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/SafePyObject.h>
|
||||
#include <c10/macros/Export.h>
|
||||
#include <optional>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
// Structure used to pack all the thread local boolean
|
||||
// flags used by autograd
|
||||
struct C10_API AutogradState {
|
||||
static AutogradState& get_tls_state();
|
||||
static void set_tls_state(AutogradState state);
|
||||
|
||||
AutogradState(
|
||||
bool grad_mode,
|
||||
bool inference_mode,
|
||||
bool fw_grad_mode,
|
||||
bool multithreading_enabled)
|
||||
: graph_exec_group_(std::nullopt),
|
||||
grad_mode_(grad_mode),
|
||||
inference_mode_(inference_mode),
|
||||
fw_grad_mode_(fw_grad_mode),
|
||||
multithreading_enabled_(multithreading_enabled),
|
||||
view_replay_enabled_(false) {}
|
||||
|
||||
void set_grad_mode(bool enabled) {
|
||||
grad_mode_ = enabled;
|
||||
}
|
||||
|
||||
void set_fw_grad_mode(bool enabled) {
|
||||
fw_grad_mode_ = enabled;
|
||||
}
|
||||
|
||||
void set_inference_mode(bool enabled) {
|
||||
inference_mode_ = enabled;
|
||||
}
|
||||
|
||||
void set_multithreading_enabled(bool multithreading_enabled) {
|
||||
multithreading_enabled_ = multithreading_enabled;
|
||||
}
|
||||
|
||||
void set_view_replay_enabled(bool view_replay_enabled) {
|
||||
view_replay_enabled_ = view_replay_enabled;
|
||||
}
|
||||
|
||||
void set_graph_exec_group(std::optional<SafePyObject> group) {
|
||||
graph_exec_group_ = std::move(group);
|
||||
}
|
||||
|
||||
bool get_grad_mode() const {
|
||||
return grad_mode_;
|
||||
}
|
||||
|
||||
bool get_fw_grad_mode() const {
|
||||
return fw_grad_mode_;
|
||||
}
|
||||
|
||||
bool get_inference_mode() const {
|
||||
return inference_mode_;
|
||||
}
|
||||
|
||||
bool get_multithreading_enabled() const {
|
||||
return multithreading_enabled_;
|
||||
}
|
||||
|
||||
bool get_view_replay_enabled() const {
|
||||
return view_replay_enabled_;
|
||||
}
|
||||
|
||||
const std::optional<SafePyObject>& get_graph_exec_group() const {
|
||||
return graph_exec_group_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::optional<SafePyObject> graph_exec_group_;
|
||||
bool grad_mode_ : 1;
|
||||
bool inference_mode_ : 1;
|
||||
bool fw_grad_mode_ : 1;
|
||||
bool multithreading_enabled_ : 1;
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-use-default-member-init)
|
||||
bool view_replay_enabled_ : 1;
|
||||
};
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,414 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/DeviceType.h>
|
||||
#include <c10/core/DispatchKey.h>
|
||||
#include <c10/core/DispatchKeySet.h>
|
||||
#include <c10/macros/Macros.h>
|
||||
#include <c10/util/Exception.h>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
C10_DIAGNOSTIC_PUSH_AND_IGNORED_IF_DEFINED("-Wswitch-enum")
|
||||
|
||||
namespace c10 {
|
||||
|
||||
/**
|
||||
* This legacy enum class defines the set of backends supported by old school,
|
||||
* code generated Type-based ATen. A "backend" in this sense roughly
|
||||
* corresponds to the cartesian product of (device type, layout), but restricted
|
||||
* only to combinations which we actually have kernels for. Backend does NOT
|
||||
* include dtype.
|
||||
*
|
||||
* The reason we are sunsetting this enum class is because it doesn't allow for
|
||||
* open registration; e.g., if you want to add SparseXLA, you'd have to
|
||||
* edit this enum; you wouldn't be able to do it out of tree. DispatchKey is
|
||||
* the replacement for Backend which supports open registration.
|
||||
*
|
||||
* NB: The concept of 'Backend' here disagrees with the notion of backend
|
||||
* exposed to users in torch.backends. Backend here is something like "CPU"
|
||||
* or "SparseCUDA"; backend in torch.backends is something like "MKL" or
|
||||
* "CUDNN".
|
||||
*/
|
||||
enum class Backend {
|
||||
CPU,
|
||||
CUDA,
|
||||
HIP,
|
||||
VE,
|
||||
FPGA,
|
||||
IPU,
|
||||
XPU,
|
||||
SparseCPU,
|
||||
SparseCUDA,
|
||||
SparseCsrCPU,
|
||||
SparseCsrCUDA,
|
||||
SparseCsrMPS,
|
||||
SparseMPS,
|
||||
SparseHIP,
|
||||
SparseVE,
|
||||
SparseXPU,
|
||||
SparsePrivateUse1,
|
||||
SparseCsrHIP,
|
||||
SparseCsrVE,
|
||||
SparseCsrXPU,
|
||||
SparseCsrPrivateUse1,
|
||||
MAIA,
|
||||
XLA,
|
||||
Vulkan,
|
||||
Metal,
|
||||
Meta,
|
||||
QuantizedCPU,
|
||||
QuantizedCUDA,
|
||||
QuantizedXPU,
|
||||
QuantizedPrivateUse1,
|
||||
Undefined,
|
||||
MkldnnCPU,
|
||||
MPS,
|
||||
HPU,
|
||||
Lazy,
|
||||
MTIA,
|
||||
PrivateUse1,
|
||||
NumOptions
|
||||
};
|
||||
|
||||
inline Backend dispatchKeyToBackend(DispatchKey t) {
|
||||
if (t == DispatchKey::CPU || t == DispatchKey::AutogradCPU) {
|
||||
return Backend::CPU;
|
||||
} else if (t == DispatchKey::CUDA || t == DispatchKey::AutogradCUDA) {
|
||||
return Backend::CUDA;
|
||||
} else if (t == DispatchKey::HIP) {
|
||||
return Backend::HIP;
|
||||
} else if (t == DispatchKey::VE) {
|
||||
return Backend::VE;
|
||||
} else if (t == DispatchKey::FPGA) {
|
||||
return Backend::FPGA;
|
||||
} else if (t == DispatchKey::MAIA || t == DispatchKey::AutogradMAIA) {
|
||||
return Backend::MAIA;
|
||||
} else if (t == DispatchKey::XLA || t == DispatchKey::AutogradXLA) {
|
||||
return Backend::XLA;
|
||||
} else if (t == DispatchKey::Lazy || t == DispatchKey::AutogradLazy) {
|
||||
return Backend::Lazy;
|
||||
} else if (t == DispatchKey::MPS || t == DispatchKey::AutogradMPS) {
|
||||
return Backend::MPS;
|
||||
} else if (t == DispatchKey::Vulkan) {
|
||||
return Backend::Vulkan;
|
||||
} else if (t == DispatchKey::Metal) {
|
||||
return Backend::Metal;
|
||||
} else if (t == DispatchKey::Meta) {
|
||||
return Backend::Meta;
|
||||
} else if (t == DispatchKey::SparseCPU) {
|
||||
return Backend::SparseCPU;
|
||||
} else if (t == DispatchKey::SparseCUDA) {
|
||||
return Backend::SparseCUDA;
|
||||
} else if (t == DispatchKey::SparseMPS) {
|
||||
return Backend::SparseMPS;
|
||||
} else if (t == DispatchKey::SparseCsrMPS) {
|
||||
return Backend::SparseCsrMPS;
|
||||
} else if (t == DispatchKey::SparseHIP) {
|
||||
return Backend::SparseHIP;
|
||||
} else if (t == DispatchKey::SparseVE) {
|
||||
return Backend::SparseVE;
|
||||
} else if (t == DispatchKey::SparsePrivateUse1) {
|
||||
return Backend::SparsePrivateUse1;
|
||||
} else if (t == DispatchKey::SparseCsrCPU) {
|
||||
return Backend::SparseCsrCPU;
|
||||
} else if (t == DispatchKey::SparseCsrCUDA) {
|
||||
return Backend::SparseCsrCUDA;
|
||||
} else if (t == DispatchKey::SparseCsrHIP) {
|
||||
return Backend::SparseCsrHIP;
|
||||
} else if (t == DispatchKey::SparseCsrVE) {
|
||||
return Backend::SparseCsrVE;
|
||||
} else if (t == DispatchKey::SparseCsrPrivateUse1) {
|
||||
return Backend::SparseCsrPrivateUse1;
|
||||
} else if (t == DispatchKey::MkldnnCPU) {
|
||||
return Backend::MkldnnCPU;
|
||||
} else if (t == DispatchKey::QuantizedCPU) {
|
||||
return Backend::QuantizedCPU;
|
||||
} else if (t == DispatchKey::QuantizedCUDA) {
|
||||
return Backend::QuantizedCUDA;
|
||||
} else if (t == DispatchKey::IPU || t == DispatchKey::AutogradIPU) {
|
||||
return Backend::IPU;
|
||||
} else if (t == DispatchKey::XPU || t == DispatchKey::AutogradXPU) {
|
||||
return Backend::XPU;
|
||||
} else if (t == DispatchKey::SparseXPU) {
|
||||
return Backend::SparseXPU;
|
||||
} else if (t == DispatchKey::SparseCsrXPU) {
|
||||
return Backend::SparseCsrXPU;
|
||||
} else if (t == DispatchKey::QuantizedXPU) {
|
||||
return Backend::QuantizedXPU;
|
||||
} else if (t == DispatchKey::QuantizedPrivateUse1) {
|
||||
return Backend::QuantizedPrivateUse1;
|
||||
} else if (t == DispatchKey::HPU || t == DispatchKey::AutogradHPU) {
|
||||
return Backend::HPU;
|
||||
} else if (t == DispatchKey::MTIA || t == DispatchKey::AutogradMTIA) {
|
||||
return Backend::MTIA;
|
||||
} else if (
|
||||
t == DispatchKey::PrivateUse1 || t == DispatchKey::AutogradPrivateUse1) {
|
||||
return Backend::PrivateUse1;
|
||||
} else if (t == DispatchKey::Undefined) {
|
||||
return Backend::Undefined;
|
||||
} else {
|
||||
TORCH_CHECK(false, "Unrecognized tensor type ID: ", t);
|
||||
}
|
||||
}
|
||||
|
||||
inline DispatchKey backendToDispatchKey(Backend b) {
|
||||
switch (b) {
|
||||
case Backend::CPU:
|
||||
return DispatchKey::CPU;
|
||||
case Backend::CUDA:
|
||||
return DispatchKey::CUDA;
|
||||
case Backend::HIP:
|
||||
return DispatchKey::HIP;
|
||||
case Backend::VE:
|
||||
return DispatchKey::VE;
|
||||
case Backend::FPGA:
|
||||
return DispatchKey::FPGA;
|
||||
case Backend::MAIA:
|
||||
return DispatchKey::MAIA;
|
||||
case Backend::XLA:
|
||||
return DispatchKey::XLA;
|
||||
case Backend::Lazy:
|
||||
return DispatchKey::Lazy;
|
||||
case Backend::IPU:
|
||||
return DispatchKey::IPU;
|
||||
case Backend::XPU:
|
||||
return DispatchKey::XPU;
|
||||
case Backend::SparseXPU:
|
||||
return DispatchKey::SparseXPU;
|
||||
case Backend::SparseCsrXPU:
|
||||
return DispatchKey::SparseCsrXPU;
|
||||
case Backend::SparseCPU:
|
||||
return DispatchKey::SparseCPU;
|
||||
case Backend::SparseCUDA:
|
||||
return DispatchKey::SparseCUDA;
|
||||
case Backend::SparseMPS:
|
||||
return DispatchKey::SparseMPS;
|
||||
case Backend::SparseCsrMPS:
|
||||
return DispatchKey::SparseCsrMPS;
|
||||
case Backend::SparseHIP:
|
||||
return DispatchKey::SparseHIP;
|
||||
case Backend::SparseVE:
|
||||
return DispatchKey::SparseVE;
|
||||
case Backend::SparsePrivateUse1:
|
||||
return DispatchKey::SparsePrivateUse1;
|
||||
case Backend::SparseCsrCPU:
|
||||
return DispatchKey::SparseCsrCPU;
|
||||
case Backend::SparseCsrCUDA:
|
||||
return DispatchKey::SparseCsrCUDA;
|
||||
case Backend::SparseCsrHIP:
|
||||
return DispatchKey::SparseCsrHIP;
|
||||
case Backend::SparseCsrVE:
|
||||
return DispatchKey::SparseCsrVE;
|
||||
case Backend::SparseCsrPrivateUse1:
|
||||
return DispatchKey::SparseCsrPrivateUse1;
|
||||
case Backend::MkldnnCPU:
|
||||
return DispatchKey::MkldnnCPU;
|
||||
case Backend::Vulkan:
|
||||
return DispatchKey::Vulkan;
|
||||
case Backend::Metal:
|
||||
return DispatchKey::Metal;
|
||||
case Backend::Meta:
|
||||
return DispatchKey::Meta;
|
||||
case Backend::QuantizedCPU:
|
||||
return DispatchKey::QuantizedCPU;
|
||||
case Backend::QuantizedCUDA:
|
||||
return DispatchKey::QuantizedCUDA;
|
||||
case Backend::QuantizedPrivateUse1:
|
||||
return DispatchKey::QuantizedPrivateUse1;
|
||||
case Backend::Undefined:
|
||||
return DispatchKey::Undefined;
|
||||
case Backend::MPS:
|
||||
return DispatchKey::MPS;
|
||||
case Backend::HPU:
|
||||
return DispatchKey::HPU;
|
||||
case Backend::MTIA:
|
||||
return DispatchKey::MTIA;
|
||||
case Backend::PrivateUse1:
|
||||
return DispatchKey::PrivateUse1;
|
||||
default:
|
||||
TORCH_CHECK(false, "Unknown backend");
|
||||
}
|
||||
}
|
||||
|
||||
inline DeviceType backendToDeviceType(Backend b) {
|
||||
switch (b) {
|
||||
case Backend::CPU:
|
||||
case Backend::MkldnnCPU:
|
||||
case Backend::SparseCPU:
|
||||
case Backend::SparseCsrCPU:
|
||||
case Backend::QuantizedCPU:
|
||||
return DeviceType::CPU;
|
||||
case Backend::CUDA:
|
||||
case Backend::SparseCUDA:
|
||||
case Backend::QuantizedCUDA:
|
||||
case Backend::SparseCsrCUDA:
|
||||
return DeviceType::CUDA;
|
||||
case Backend::HIP:
|
||||
return DeviceType::HIP;
|
||||
case Backend::VE:
|
||||
return DeviceType::VE;
|
||||
case Backend::FPGA:
|
||||
return DeviceType::FPGA;
|
||||
case Backend::MAIA:
|
||||
return DeviceType::MAIA;
|
||||
case Backend::XLA:
|
||||
return DeviceType::XLA;
|
||||
case Backend::Lazy:
|
||||
return DeviceType::Lazy;
|
||||
case Backend::SparseHIP:
|
||||
return DeviceType::HIP;
|
||||
case Backend::SparseVE:
|
||||
return DeviceType::VE;
|
||||
case Backend::SparseCsrHIP:
|
||||
return DeviceType::HIP;
|
||||
case Backend::SparseCsrVE:
|
||||
return DeviceType::VE;
|
||||
case Backend::IPU:
|
||||
return DeviceType::IPU;
|
||||
case Backend::XPU:
|
||||
case Backend::SparseXPU:
|
||||
case Backend::SparseCsrXPU:
|
||||
case Backend::QuantizedXPU:
|
||||
return DeviceType::XPU;
|
||||
case Backend::Vulkan:
|
||||
return DeviceType::Vulkan;
|
||||
case Backend::Metal:
|
||||
return DeviceType::Metal;
|
||||
case Backend::Meta:
|
||||
return DeviceType::Meta;
|
||||
case Backend::MPS:
|
||||
case Backend::SparseMPS:
|
||||
case Backend::SparseCsrMPS:
|
||||
return DeviceType::MPS;
|
||||
case Backend::HPU:
|
||||
return DeviceType::HPU;
|
||||
case Backend::MTIA:
|
||||
return DeviceType::MTIA;
|
||||
case Backend::PrivateUse1:
|
||||
case Backend::SparsePrivateUse1:
|
||||
case Backend::SparseCsrPrivateUse1:
|
||||
case Backend::QuantizedPrivateUse1:
|
||||
return DeviceType::PrivateUse1;
|
||||
case Backend::Undefined:
|
||||
TORCH_CHECK(false, "Undefined backend is not a valid device type");
|
||||
default:
|
||||
TORCH_CHECK(false, "Unknown backend");
|
||||
}
|
||||
}
|
||||
|
||||
inline const char* toString(Backend b) {
|
||||
switch (b) {
|
||||
case Backend::CPU:
|
||||
return "CPU";
|
||||
case Backend::CUDA:
|
||||
return "CUDA";
|
||||
case Backend::HIP:
|
||||
return "HIP";
|
||||
case Backend::VE:
|
||||
return "VE";
|
||||
case Backend::FPGA:
|
||||
return "FPGA";
|
||||
case Backend::XPU:
|
||||
return "XPU";
|
||||
case Backend::IPU:
|
||||
return "IPU";
|
||||
case Backend::MAIA:
|
||||
return "MAIA";
|
||||
case Backend::XLA:
|
||||
return "XLA";
|
||||
case Backend::Lazy:
|
||||
return "Lazy";
|
||||
case Backend::MPS:
|
||||
return "MPS";
|
||||
case Backend::SparseCPU:
|
||||
return "SparseCPU";
|
||||
case Backend::SparseCUDA:
|
||||
return "SparseCUDA";
|
||||
case Backend::SparseMPS:
|
||||
return "SparseMPS";
|
||||
case Backend::SparseCsrMPS:
|
||||
return "SparseCsrMPS";
|
||||
case Backend::SparseHIP:
|
||||
return "SparseHIP";
|
||||
case Backend::SparseVE:
|
||||
return "SparseVE";
|
||||
case Backend::SparseXPU:
|
||||
return "SparseXPU";
|
||||
case Backend::SparsePrivateUse1:
|
||||
return "SparsePrivateUse1";
|
||||
case Backend::SparseCsrCPU:
|
||||
return "SparseCsrCPU";
|
||||
case Backend::SparseCsrCUDA:
|
||||
return "SparseCsrCUDA";
|
||||
case Backend::SparseCsrHIP:
|
||||
return "SparseCsrHIP";
|
||||
case Backend::SparseCsrVE:
|
||||
return "SparseCsrVE";
|
||||
case Backend::SparseCsrXPU:
|
||||
return "SparseCsrXPU";
|
||||
case Backend::SparseCsrPrivateUse1:
|
||||
return "SparseCsrPrivateUse1";
|
||||
case Backend::MkldnnCPU:
|
||||
return "MkldnnCPU";
|
||||
case Backend::Vulkan:
|
||||
return "Vulkan";
|
||||
case Backend::Metal:
|
||||
return "Metal";
|
||||
case Backend::Meta:
|
||||
return "Meta";
|
||||
case Backend::QuantizedCPU:
|
||||
return "QuantizedCPU";
|
||||
case Backend::QuantizedCUDA:
|
||||
return "QuantizedCUDA";
|
||||
case Backend::QuantizedXPU:
|
||||
return "QuantizedXPU";
|
||||
case Backend::QuantizedPrivateUse1:
|
||||
return "QuantizedPrivateUse1";
|
||||
case Backend::HPU:
|
||||
return "HPU";
|
||||
case Backend::MTIA:
|
||||
return "MTIA";
|
||||
case Backend::PrivateUse1:
|
||||
return "PrivateUseOne";
|
||||
default:
|
||||
return "UNKNOWN_BACKEND";
|
||||
}
|
||||
}
|
||||
|
||||
inline bool isSparse(Backend b) {
|
||||
switch (b) {
|
||||
case Backend::SparseXPU:
|
||||
case Backend::SparseCPU:
|
||||
case Backend::SparseCUDA:
|
||||
case Backend::SparseMPS:
|
||||
case Backend::SparseHIP:
|
||||
case Backend::SparseVE:
|
||||
case Backend::SparsePrivateUse1:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool isSparseCsr(Backend b) {
|
||||
switch (b) {
|
||||
case Backend::SparseCsrXPU:
|
||||
case Backend::SparseCsrCPU:
|
||||
case Backend::SparseCsrCUDA:
|
||||
case Backend::SparseCsrHIP:
|
||||
case Backend::SparseCsrVE:
|
||||
case Backend::SparseCsrPrivateUse1:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace c10
|
||||
|
||||
C10_DIAGNOSTIC_POP()
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,64 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <c10/core/Allocator.h>
|
||||
#include <c10/macros/Export.h>
|
||||
#include <c10/util/Flags.h>
|
||||
|
||||
// TODO: rename to c10
|
||||
C10_DECLARE_bool(caffe2_report_cpu_memory_usage);
|
||||
|
||||
namespace c10 {
|
||||
|
||||
using MemoryDeleter = void (*)(void*);
|
||||
|
||||
// A helper function that is basically doing nothing.
|
||||
C10_API void NoDelete(void* /*unused*/);
|
||||
|
||||
// A simple struct that is used to report C10's memory allocation,
|
||||
// deallocation status and out-of-memory events to the profiler
|
||||
class C10_API ProfiledCPUMemoryReporter {
|
||||
public:
|
||||
ProfiledCPUMemoryReporter() = default;
|
||||
void New(void* ptr, size_t nbytes);
|
||||
void OutOfMemory(size_t nbytes);
|
||||
void Delete(void* ptr);
|
||||
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
std::unordered_map<void*, size_t> size_table_;
|
||||
size_t allocated_ = 0;
|
||||
size_t log_cnt_ = 0;
|
||||
};
|
||||
|
||||
C10_API ProfiledCPUMemoryReporter& profiledCPUMemoryReporter();
|
||||
|
||||
// Get the CPU Allocator.
|
||||
C10_API at::Allocator* GetCPUAllocator();
|
||||
// Sets the CPU allocator to the given allocator: the caller gives away the
|
||||
// ownership of the pointer.
|
||||
C10_API void SetCPUAllocator(at::Allocator* alloc, uint8_t priority = 0);
|
||||
|
||||
// Get the Default CPU Allocator
|
||||
C10_API at::Allocator* GetDefaultCPUAllocator();
|
||||
|
||||
// Get the Default Mobile CPU Allocator
|
||||
C10_API at::Allocator* GetDefaultMobileCPUAllocator();
|
||||
|
||||
// The CPUCachingAllocator is experimental and might disappear in the future.
|
||||
// The only place that uses it is in StaticRuntime.
|
||||
// Set the CPU Caching Allocator
|
||||
C10_API void SetCPUCachingAllocator(Allocator* alloc, uint8_t priority = 0);
|
||||
// Get the CPU Caching Allocator
|
||||
C10_API Allocator* GetCPUCachingAllocator();
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
+265
@@ -0,0 +1,265 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/Allocator.h>
|
||||
#include <c10/core/Stream.h>
|
||||
#include <c10/util/ApproximateClock.h>
|
||||
|
||||
namespace c10::CachingDeviceAllocator {
|
||||
|
||||
using namespace c10::CachingAllocator;
|
||||
|
||||
// Struct containing memory allocator summary statistics for a device.
|
||||
struct DeviceStats {
|
||||
// COUNT: allocations requested by client code
|
||||
StatArray allocation;
|
||||
// COUNT: number of allocated segments from device memory allocation.
|
||||
StatArray segment;
|
||||
// COUNT: number of active memory blocks (allocated or used by stream)
|
||||
StatArray active;
|
||||
// COUNT: number of inactive, split memory blocks (unallocated but can't be
|
||||
// released via device memory deallocation)
|
||||
StatArray inactive_split;
|
||||
|
||||
// SUM: bytes allocated by this memory allocator
|
||||
StatArray allocated_bytes;
|
||||
// SUM: bytes reserved by this memory allocator (both free and used)
|
||||
StatArray reserved_bytes;
|
||||
// SUM: bytes within active memory blocks
|
||||
StatArray active_bytes;
|
||||
// SUM: bytes within inactive, split memory blocks
|
||||
StatArray inactive_split_bytes;
|
||||
// SUM: bytes requested by client code
|
||||
StatArray requested_bytes;
|
||||
|
||||
// COUNT: total number of failed calls to device malloc necessitating cache
|
||||
// flushes.
|
||||
int64_t num_alloc_retries = 0;
|
||||
|
||||
// COUNT: total number of OOMs (i.e. failed calls to device memory allocation
|
||||
// after cache flush)
|
||||
int64_t num_ooms = 0;
|
||||
|
||||
// COUNT: total number of oversize blocks allocated from pool
|
||||
Stat oversize_allocations;
|
||||
|
||||
// COUNT: total number of oversize blocks requiring malloc
|
||||
Stat oversize_segments;
|
||||
|
||||
// COUNT: total number of synchronize_and_free_events() calls
|
||||
int64_t num_sync_all_streams = 0;
|
||||
|
||||
// COUNT: total number of device memory allocation calls. This includes both
|
||||
// mapped and malloced memory.
|
||||
int64_t num_device_alloc = 0;
|
||||
|
||||
// COUNT: total number of device memory deallocation calls. This includes both
|
||||
// un-mapped and free memory.
|
||||
int64_t num_device_free = 0;
|
||||
|
||||
// COUNT: total number of allocations rejected by OOM preemption policy
|
||||
int64_t num_oom_rejections = 0;
|
||||
|
||||
// SIZE: maximum block size that is allowed to be split.
|
||||
int64_t max_split_size = 0;
|
||||
};
|
||||
|
||||
using CreateContextFn = std::shared_ptr<GatheredContext> (*)();
|
||||
|
||||
enum struct RecordContext {
|
||||
NEVER = 0,
|
||||
STATE = 1, // only keep stacks for active allocations
|
||||
ALLOC = 2, // additionally keep stacks for allocations in the trace history
|
||||
ALL = 3, // additionally record stacks for when something is freed
|
||||
};
|
||||
|
||||
// Struct containing information about an allocation block, i.e., a subrange
|
||||
// of a device allocation (such as one obtained via cudaMalloc).
|
||||
struct BlockInfo {
|
||||
size_t size = 0;
|
||||
size_t requested_size = 0;
|
||||
int32_t gc_counter = 0;
|
||||
bool allocated = false;
|
||||
bool active = false;
|
||||
std::shared_ptr<GatheredContext>
|
||||
context_when_allocated; // per-watcher context
|
||||
};
|
||||
|
||||
// Struct holding information about a memory segment (i.e., a single contiguous
|
||||
// device allocation, such as one created by cudaMalloc).
|
||||
struct SegmentInfo {
|
||||
c10::DeviceIndex device = 0;
|
||||
int32_t registration_counter = -1;
|
||||
size_t address = 0;
|
||||
size_t total_size = 0;
|
||||
size_t requested_size = 0; // Unrounded, actually requested size
|
||||
size_t allocated_size = 0;
|
||||
size_t active_size = 0;
|
||||
void* stream = nullptr; // Records the address of the underlying stream
|
||||
bool is_large = false;
|
||||
bool is_expandable = false;
|
||||
MempoolId_t owner_private_pool_id = {0, 0};
|
||||
std::vector<BlockInfo> blocks;
|
||||
std::shared_ptr<GatheredContext> context_when_allocated;
|
||||
};
|
||||
|
||||
union trace_time_ {
|
||||
time_t t_;
|
||||
approx_time_t approx_t_;
|
||||
};
|
||||
|
||||
struct TraceEntry {
|
||||
enum Action {
|
||||
ALLOC, // API made to the caching allocator for new memory
|
||||
FREE_REQUESTED, // API call made to the caching allocator to free memory
|
||||
FREE_COMPLETED, // The allocator might have to delay a free because
|
||||
// it is still in use on another stream via record_stream
|
||||
// This event is generated when a free actually completes.
|
||||
SEGMENT_ALLOC, // a call to device allocation to get more memory from the OS
|
||||
SEGMENT_FREE, // a call to device deallocation to return memory to the OS
|
||||
// (e.g. to defragment or empty_caches)
|
||||
SEGMENT_MAP, // a call to cuMemMap (used with expandable_segments)
|
||||
SEGMENT_UNMAP, // unmap part of a segment (used with expandable segments)
|
||||
SNAPSHOT, // a call to snapshot, used to correlate memory snapshots to trace
|
||||
// events
|
||||
OOM // the allocator threw an OutOfMemoryError (addr_ is the amount of free
|
||||
// bytes reported by device memory)
|
||||
};
|
||||
TraceEntry(
|
||||
Action action,
|
||||
c10::DeviceIndex device,
|
||||
size_t addr,
|
||||
size_t size,
|
||||
void* stream,
|
||||
MempoolId_t mempool,
|
||||
approx_time_t time,
|
||||
std::shared_ptr<GatheredContext> context = nullptr,
|
||||
std::string compile_context = "",
|
||||
std::string user_metadata = "")
|
||||
: action_(action),
|
||||
device_(device),
|
||||
addr_(addr),
|
||||
context_(std::move(context)),
|
||||
stream_(stream),
|
||||
size_(size),
|
||||
mempool_(std::move(mempool)),
|
||||
compile_context_(std::move(compile_context)),
|
||||
user_metadata_(std::move(user_metadata)) {
|
||||
time_.approx_t_ = time;
|
||||
}
|
||||
Action action_;
|
||||
c10::DeviceIndex device_;
|
||||
// For most actions, this is a memory address. For OOM, it represents the
|
||||
// amount of free memory (in bytes). For SNAPSHOT, it is an unused parameter
|
||||
// (just set to 0).
|
||||
size_t addr_;
|
||||
std::shared_ptr<GatheredContext> context_;
|
||||
void* stream_{};
|
||||
size_t size_;
|
||||
MempoolId_t mempool_;
|
||||
trace_time_ time_{};
|
||||
std::string compile_context_;
|
||||
std::string user_metadata_;
|
||||
};
|
||||
|
||||
inline TraceEntry::Action parseTraceEntryAction(std::string_view action) {
|
||||
constexpr std::pair<std::string_view, TraceEntry::Action> kActionTable[] = {
|
||||
{"alloc", TraceEntry::Action::ALLOC},
|
||||
{"free_requested", TraceEntry::Action::FREE_REQUESTED},
|
||||
{"free_completed", TraceEntry::Action::FREE_COMPLETED},
|
||||
{"segment_alloc", TraceEntry::Action::SEGMENT_ALLOC},
|
||||
{"segment_free", TraceEntry::Action::SEGMENT_FREE},
|
||||
{"segment_map", TraceEntry::Action::SEGMENT_MAP},
|
||||
{"segment_unmap", TraceEntry::Action::SEGMENT_UNMAP},
|
||||
{"snapshot", TraceEntry::Action::SNAPSHOT},
|
||||
{"oom", TraceEntry::Action::OOM},
|
||||
};
|
||||
for (const auto& [k, v] : kActionTable) {
|
||||
if (action == k)
|
||||
return v;
|
||||
}
|
||||
TORCH_CHECK(false, "Unknown TraceEntry action: ", action);
|
||||
}
|
||||
|
||||
// Calls made by record_function will save annotations
|
||||
struct AnnotationEntry {
|
||||
AnnotationEntry(c10::DeviceIndex device, approx_time_t time)
|
||||
: device_(device) {
|
||||
time_.approx_t_ = time;
|
||||
}
|
||||
|
||||
void recordUserMetadata(const std::string& name, std::string value) {
|
||||
metadata_[name] = std::move(value);
|
||||
}
|
||||
|
||||
c10::DeviceIndex device_;
|
||||
trace_time_ time_{};
|
||||
std::unordered_map<std::string, std::string> metadata_;
|
||||
};
|
||||
|
||||
using AllocatorTraceTracker = std::function<void(const TraceEntry&)>;
|
||||
|
||||
} // namespace c10::CachingDeviceAllocator
|
||||
|
||||
namespace c10 {
|
||||
|
||||
using CaptureId_t = unsigned long long;
|
||||
|
||||
// first is set if the instance is created by Graph mode capture_begin.
|
||||
// second is set if the instance is created by Graph mode graph_pool_handle.
|
||||
using MempoolId_t = std::pair<CaptureId_t, CaptureId_t>;
|
||||
|
||||
struct C10_API DeviceAllocator : public c10::Allocator {
|
||||
DeviceAllocator();
|
||||
~DeviceAllocator() override;
|
||||
|
||||
// Returns true if the allocator has been properly initialized and is ready
|
||||
// for use
|
||||
virtual bool initialized() = 0;
|
||||
|
||||
// Releases all cached device memory from the specified memory pool back to
|
||||
// the system
|
||||
virtual void emptyCache(MempoolId_t mempool_id = {0, 0}) = 0;
|
||||
|
||||
// Associates a memory allocation with a stream to establish dependency
|
||||
// tracking. Prevents memory reuse until all operations on the specified
|
||||
// stream complete
|
||||
virtual void recordStream(const DataPtr& ptr, c10::Stream stream) = 0;
|
||||
|
||||
// Retrieves comprehensive memory statistics for the specified device,
|
||||
// including allocation patterns, usage metrics
|
||||
virtual CachingDeviceAllocator::DeviceStats getDeviceStats(
|
||||
c10::DeviceIndex device) = 0;
|
||||
|
||||
// Resets cumulative allocation statistics for the specified device to zero
|
||||
virtual void resetAccumulatedStats(c10::DeviceIndex device) = 0;
|
||||
|
||||
// Resets peak memory usage statistics for the specified device
|
||||
virtual void resetPeakStats(c10::DeviceIndex device) = 0;
|
||||
|
||||
// Return the free memory size and total memory size in bytes for the
|
||||
// specified device.
|
||||
virtual std::pair<size_t, size_t> getMemoryInfo(c10::DeviceIndex device) {
|
||||
TORCH_CHECK_NOT_IMPLEMENTED(
|
||||
false, "getMemoryInfo is not implemented for this allocator yet.");
|
||||
}
|
||||
};
|
||||
|
||||
// This function is used to get the DeviceAllocator for a specific device type
|
||||
// and keep backward compatibility with c10::GetAllocator.
|
||||
C10_API inline DeviceAllocator* getDeviceAllocator(const DeviceType& t) {
|
||||
TORCH_CHECK(
|
||||
t != DeviceType::CPU,
|
||||
"getDeviceAllocator is not supported for CPU device type.");
|
||||
auto* allocator = c10::GetAllocator(t);
|
||||
auto* device_allocator = dynamic_cast<DeviceAllocator*>(allocator);
|
||||
TORCH_INTERNAL_ASSERT(
|
||||
device_allocator, "Allocator for ", t, " is not a DeviceAllocator.");
|
||||
return device_allocator;
|
||||
}
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/util/TypeTraits.h>
|
||||
#include <type_traits>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
/**
|
||||
* Represent a function pointer as a C++ type.
|
||||
* This allows using the function pointer as a type
|
||||
* in a template and calling it from inside the template
|
||||
* allows the compiler to inline the call because it
|
||||
* knows the function pointer at compile time.
|
||||
*
|
||||
* Example 1:
|
||||
* int add(int a, int b) {return a + b;}
|
||||
* using Add = TORCH_FN_TYPE(add);
|
||||
* template<class Func> struct Executor {
|
||||
* int execute(int a, int b) {
|
||||
* return Func::func_ptr()(a, b);
|
||||
* }
|
||||
* };
|
||||
* Executor<Add> executor;
|
||||
* EXPECT_EQ(3, executor.execute(1, 2));
|
||||
*
|
||||
* Example 2:
|
||||
* int add(int a, int b) {return a + b;}
|
||||
* template<class Func> int execute(Func, int a, int b) {
|
||||
* return Func::func_ptr()(a, b);
|
||||
* }
|
||||
* EXPECT_EQ(3, execute(TORCH_FN(add), 1, 2));
|
||||
*/
|
||||
template <class FuncType_, FuncType_* func_ptr_>
|
||||
struct CompileTimeFunctionPointer final {
|
||||
static_assert(
|
||||
guts::is_function_type<FuncType_>::value,
|
||||
"TORCH_FN can only wrap function types.");
|
||||
using FuncType = FuncType_;
|
||||
|
||||
static constexpr FuncType* func_ptr() {
|
||||
return func_ptr_;
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct is_compile_time_function_pointer : std::false_type {};
|
||||
template <class FuncType, FuncType* func_ptr>
|
||||
struct is_compile_time_function_pointer<
|
||||
CompileTimeFunctionPointer<FuncType, func_ptr>> : std::true_type {};
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#define TORCH_FN_TYPE(func) \
|
||||
::c10::CompileTimeFunctionPointer< \
|
||||
std::remove_pointer_t<std::remove_reference_t<decltype(func)>>, \
|
||||
func>
|
||||
#define TORCH_FN(func) TORCH_FN_TYPE(func)()
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/SymNodeImpl.h>
|
||||
#include <c10/macros/Export.h>
|
||||
#include <c10/util/Exception.h>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
// Unlike other SymNodeImpl, this cannot be "dispatched" conventionally,
|
||||
// as it typically needs to defer to another SymNodeImpl
|
||||
//
|
||||
// Can either represent a bool, int (don't support float yet) this is useful
|
||||
// for representing otherwise unrepresentable large negative integer constant.
|
||||
template <typename T>
|
||||
class C10_API ConstantSymNodeImpl : public SymNodeImpl {
|
||||
static_assert(
|
||||
::std::is_same_v<T, int64_t> || ::std::is_same_v<T, bool>,
|
||||
"ConstantSymNodeImpl can only accept int64_t or bool types");
|
||||
|
||||
public:
|
||||
ConstantSymNodeImpl(T val) : value_(val) {}
|
||||
|
||||
bool is_int() override {
|
||||
return is_int_();
|
||||
}
|
||||
bool is_bool() override {
|
||||
return is_bool_();
|
||||
}
|
||||
bool is_float() override {
|
||||
return false;
|
||||
}
|
||||
int64_t guard_int(
|
||||
const char* file [[maybe_unused]],
|
||||
int64_t line [[maybe_unused]]) override {
|
||||
TORCH_CHECK(is_int(), "not an int");
|
||||
return int_();
|
||||
}
|
||||
bool guard_bool(
|
||||
const char* file [[maybe_unused]],
|
||||
int64_t line [[maybe_unused]]) override {
|
||||
TORCH_CHECK(is_bool(), "not a bool");
|
||||
return bool_();
|
||||
}
|
||||
double guard_float(
|
||||
const char* file [[maybe_unused]],
|
||||
int64_t line [[maybe_unused]]) override {
|
||||
TORCH_CHECK(false, "not a float");
|
||||
}
|
||||
int64_t int_() override {
|
||||
TORCH_CHECK(is_int(), "not an int");
|
||||
return ::std::get<int64_t>(value_);
|
||||
}
|
||||
bool bool_() override {
|
||||
TORCH_CHECK(is_bool(), "not a bool");
|
||||
return ::std::get<bool>(value_);
|
||||
}
|
||||
bool has_hint() override {
|
||||
return true;
|
||||
}
|
||||
c10::SymNode eq(const c10::SymNode& other) override;
|
||||
c10::SymNode ne(const c10::SymNode& other) override;
|
||||
c10::SymNode ge(const c10::SymNode& other) override;
|
||||
c10::SymNode le(const c10::SymNode& other) override;
|
||||
c10::SymNode lt(const c10::SymNode& other) override;
|
||||
c10::SymNode gt(const c10::SymNode& other) override;
|
||||
c10::SymNode mul(const c10::SymNode& other) override;
|
||||
c10::SymNode sym_and(const c10::SymNode& other) override;
|
||||
c10::SymNode sym_or(const c10::SymNode& other) override;
|
||||
::std::string str() override {
|
||||
if constexpr (is_int_()) {
|
||||
return ::std::to_string(::std::get<int64_t>(value_));
|
||||
} else {
|
||||
return ::std::get<bool>(value_) ? "true" : "false";
|
||||
}
|
||||
}
|
||||
std::optional<int64_t> constant_int() override {
|
||||
if constexpr (is_int_()) {
|
||||
return ::std::get<int64_t>(value_);
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
std::optional<bool> constant_bool() override {
|
||||
if constexpr (is_bool_()) {
|
||||
return ::std::get<bool>(value_);
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
bool is_constant() override {
|
||||
return true;
|
||||
}
|
||||
bool is_symbolic() override {
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
::std::variant<int64_t, bool> value_;
|
||||
|
||||
static constexpr bool is_int_() {
|
||||
return ::std::is_same_v<T, int64_t>;
|
||||
}
|
||||
static constexpr bool is_bool_() {
|
||||
return ::std::is_same_v<T, bool>;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,314 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
#include <c10/core/SymBool.h>
|
||||
#include <c10/core/SymInt.h>
|
||||
#include <c10/util/ArrayRef.h>
|
||||
#include <c10/util/SmallVector.h>
|
||||
#include <c10/util/irange.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
template <typename T>
|
||||
bool _compute_contiguous(ArrayRef<T> sizes, ArrayRef<T> strides, T numel) {
|
||||
if (numel == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
T expected_stride = 1;
|
||||
// NB: make sure we do signed arithmetic
|
||||
for (int64_t d = int64_t(sizes.size()) - 1; d >= 0; d--) {
|
||||
const auto& size_d = sizes[d];
|
||||
if (size_d == 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strides[d] != expected_stride) {
|
||||
return false;
|
||||
}
|
||||
expected_stride *= size_d;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Return a SymBool with underlying symbolic expression that represents
|
||||
// contiguity. Guaranteed not to throw DDE, may returns a symbolic expressions
|
||||
// or symbolic True.
|
||||
inline static c10::SymBool _compute_contiguous_sym(
|
||||
ArrayRef<c10::SymInt> sizes,
|
||||
ArrayRef<c10::SymInt> strides,
|
||||
const c10::SymInt& numel) {
|
||||
// If this return true, the tensor is contiguous indeed. Otherwise it could be
|
||||
// either.
|
||||
auto is_contiguous_or_false = [&]() {
|
||||
if (TORCH_GUARD_OR_FALSE(sym_eq(numel, 0))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// When calculating the expected stride, we can choose to multiply
|
||||
// with max(1, size[d]) or size[d]. Regardless, this is ok for this
|
||||
// function. Why?
|
||||
// (1) If size[d] == 0, then the tensor is contiguous and if
|
||||
// we return true or false it won't break this function.
|
||||
// (2) If size[d] is not 0, then max(1,size[d]) and size[d] are equal.
|
||||
// Therefore, if we choose to use max(1, size[d]) or size[d] to
|
||||
// calculate the expected stride, the result is the same.
|
||||
//
|
||||
// We symbolically check both paths to maximize the cases where this
|
||||
// function returns true. This is because make_contiguous_strides_for adds
|
||||
// the max symbolically, and in some other situations the max might not be
|
||||
// there. And we want to ensure we return true in both cases.
|
||||
c10::SymInt expected_stride = 1;
|
||||
c10::SymInt expected_stride_max = 1;
|
||||
// NB: make sure we do signed arithmetic
|
||||
for (int64_t d = int64_t(sizes.size()) - 1; d >= 0; d--) {
|
||||
if (TORCH_GUARD_OR_FALSE(sym_eq(sizes[d], 1))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TORCH_GUARD_OR_TRUE(sym_ne(strides[d], expected_stride)) &&
|
||||
TORCH_GUARD_OR_TRUE(sym_ne(strides[d], expected_stride_max))) {
|
||||
return false;
|
||||
}
|
||||
expected_stride_max *= sizes[d].max(1);
|
||||
expected_stride *= sizes[d];
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// We try to minimize creating large symbolic expressions when not needed to
|
||||
// avoid symbolic evaluation perf issues.
|
||||
if (is_contiguous_or_false()) {
|
||||
return c10::SymBool(true);
|
||||
}
|
||||
|
||||
// Build a single expression that represents contiguity and return it.
|
||||
c10::SymBool is_empty = sym_eq(numel, 0);
|
||||
c10::SymBool is_contiguous_cond = true;
|
||||
|
||||
c10::SymInt expected_stride = 1;
|
||||
for (int64_t d = int64_t(sizes.size()) - 1; d >= 0; d--) {
|
||||
const auto& size_d = sizes[d];
|
||||
is_contiguous_cond = is_contiguous_cond.sym_and(
|
||||
size_d.sym_eq(1).sym_or(sym_eq(strides[d], expected_stride)));
|
||||
expected_stride = expected_stride * size_d;
|
||||
}
|
||||
return is_contiguous_cond.sym_or(is_empty);
|
||||
}
|
||||
|
||||
// When T is SymInt this function may throw a data dependent error.
|
||||
// _compute_channels_last_contiguous_2d_sym does not. Only use this function
|
||||
// when inputs are hinted.
|
||||
template <typename T>
|
||||
bool _compute_channels_last_contiguous_2d(
|
||||
ArrayRef<T> sizes,
|
||||
ArrayRef<T> strides) {
|
||||
// Please don't combine these code, constant array is used here to let
|
||||
// compiler fully unroll the loop to get better performance
|
||||
switch (sizes.size()) {
|
||||
case 4: {
|
||||
T expected = 1;
|
||||
for (auto& d : {1, 3, 2, 0}) {
|
||||
const auto& size_d = sizes[d];
|
||||
if (size_d != 1) {
|
||||
if (strides[d] != expected) {
|
||||
return false;
|
||||
}
|
||||
expected *= size_d;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// NOLINTNEXTLINE(bugprone-branch-clone)
|
||||
case 3:
|
||||
// TODO dim == 3 case will be enabled once it is fully tested
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Return a SymBool with underlying symbolic expression that represents
|
||||
// contiguity. Guaranteed not to throw DDE, may returns a symbolic expressions
|
||||
// or symbolic True.
|
||||
inline static c10::SymBool _compute_channels_last_contiguous_2d_sym(
|
||||
ArrayRef<c10::SymInt> sizes,
|
||||
ArrayRef<c10::SymInt> strides) {
|
||||
switch (sizes.size()) {
|
||||
case 4: {
|
||||
// When this function return True, result always true. When it return
|
||||
// False, result could be False or data dependent.
|
||||
auto guard_or_false = [&]() {
|
||||
c10::SymInt expected = 1;
|
||||
for (auto& d : {1, 3, 2, 0}) {
|
||||
const auto& size_d = sizes[d];
|
||||
// Not taking this branch could make this return False instead of True
|
||||
// but not vice-versa. so its ok.
|
||||
if (TORCH_GUARD_OR_FALSE(sym_eq(sizes[d], 1))) {
|
||||
continue;
|
||||
}
|
||||
// Taking this branch could make this return False instead of True
|
||||
// but not vice-versa. so its ok.
|
||||
if (TORCH_GUARD_OR_TRUE(sym_ne(strides[d], expected))) {
|
||||
return false;
|
||||
}
|
||||
expected *= size_d;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// We try to minimize creating large symbolic expressions when not needed
|
||||
// to avoid symbolic evaluation perf issues.
|
||||
if (guard_or_false()) {
|
||||
return c10::SymBool(true);
|
||||
}
|
||||
|
||||
// Result is either false, or data dependent.
|
||||
c10::SymInt expected_stride = 1;
|
||||
c10::SymBool cond = true;
|
||||
|
||||
for (auto& d : {1, 3, 2, 0}) {
|
||||
const auto& size_d = sizes[d];
|
||||
cond = cond.sym_and(
|
||||
size_d.sym_eq(1).sym_or(sym_eq(strides[d], expected_stride)));
|
||||
expected_stride *= size_d;
|
||||
}
|
||||
return cond;
|
||||
}
|
||||
// NOLINTNEXTLINE(bugprone-branch-clone)
|
||||
case 3:
|
||||
// TODO dim == 3 case will be enabled once it is fully tested
|
||||
return c10::SymBool(false);
|
||||
default:
|
||||
return c10::SymBool(false);
|
||||
}
|
||||
}
|
||||
|
||||
// When T is SymInt this function may throw a data dependent error.
|
||||
// _compute_channels_last_contiguous_3d_sym does not. Only use this function
|
||||
// when inputs are hinted.
|
||||
template <typename T>
|
||||
bool _compute_channels_last_contiguous_3d(
|
||||
ArrayRef<T> sizes,
|
||||
ArrayRef<T> strides) {
|
||||
// Please don't combine these code, constant array is used here to let
|
||||
// compiler fully unroll the loop to get better performance
|
||||
switch (sizes.size()) {
|
||||
case 5: {
|
||||
T expected = 1;
|
||||
for (auto& d : {1, 4, 3, 2, 0}) {
|
||||
const auto& size_d = sizes[d];
|
||||
if (size_d != 1) {
|
||||
if (strides[d] != expected) {
|
||||
return false;
|
||||
}
|
||||
expected *= size_d;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// NOLINTNEXTLINE(bugprone-branch-clone)
|
||||
case 4:
|
||||
// TODO dim == 4 case will be enabled once it is fully tested
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
inline static c10::SymBool _compute_channels_last_contiguous_3d_sym(
|
||||
ArrayRef<c10::SymInt> sizes,
|
||||
ArrayRef<c10::SymInt> strides) {
|
||||
switch (sizes.size()) {
|
||||
case 5: {
|
||||
// When this function return True, result always true. When it return
|
||||
// False, result could be False or data dependent.
|
||||
auto guard_or_false = [&]() {
|
||||
c10::SymInt expected = 1;
|
||||
for (auto& d : {1, 4, 3, 2, 0}) {
|
||||
const auto& size_d = sizes[d];
|
||||
// Not taking this branch could make this return False instead of True
|
||||
// but not vice-versa. so its ok.
|
||||
if (TORCH_GUARD_OR_FALSE(sym_eq(sizes[d], 1))) {
|
||||
continue;
|
||||
}
|
||||
// Taking this branch could make this return False instead of True
|
||||
// but not vice-versa. so its ok.
|
||||
if (TORCH_GUARD_OR_TRUE(sym_ne(strides[d], expected))) {
|
||||
return false;
|
||||
}
|
||||
expected *= size_d;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// We try to minimize creating large symbolic expressions when not needed
|
||||
// to avoid symbolic evaluation perf issues.
|
||||
if (guard_or_false()) {
|
||||
return c10::SymBool(true);
|
||||
}
|
||||
|
||||
// Result is either false, or data dependent.
|
||||
c10::SymInt expected_stride = 1;
|
||||
c10::SymBool cond = true;
|
||||
|
||||
for (auto& d : {1, 4, 3, 2, 0}) {
|
||||
const auto& size_d = sizes[d];
|
||||
cond = cond.sym_and(
|
||||
size_d.sym_eq(1).sym_or(sym_eq(strides[d], expected_stride)));
|
||||
expected_stride *= size_d;
|
||||
}
|
||||
return cond;
|
||||
}
|
||||
// NOLINTNEXTLINE(bugprone-branch-clone)
|
||||
case 4:
|
||||
// TODO dim == 4 case will be enabled once it is fully tested
|
||||
return c10::SymBool(false);
|
||||
default:
|
||||
return c10::SymBool(false);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool _compute_non_overlapping_and_dense(
|
||||
ArrayRef<T> sizes,
|
||||
ArrayRef<T> strides) {
|
||||
auto dim = sizes.size();
|
||||
if (dim == 1) {
|
||||
return sizes[0] < 2 || strides[0] == 1;
|
||||
}
|
||||
SmallVector<int64_t, 5> perm;
|
||||
perm.resize(dim);
|
||||
for (const auto i : c10::irange(dim)) {
|
||||
perm[i] = i;
|
||||
}
|
||||
// Sort by strides, leaving 0 and 1 sized dims at the end of the array
|
||||
std::sort(perm.begin(), perm.end(), [&](int64_t a, int64_t b) {
|
||||
if (sizes[a] < 2) {
|
||||
return false;
|
||||
} else if (sizes[b] < 2) {
|
||||
return true;
|
||||
}
|
||||
return strides[a] < strides[b];
|
||||
});
|
||||
T require_stride = 1;
|
||||
for (const auto i : c10::irange(dim)) {
|
||||
const auto& size_perm_i = sizes[perm[i]];
|
||||
if (size_perm_i < 2) {
|
||||
return true;
|
||||
}
|
||||
if (strides[perm[i]] != require_stride) {
|
||||
return false;
|
||||
}
|
||||
require_stride *= size_perm_i;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,53 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/Device.h>
|
||||
#include <c10/core/DeviceType.h>
|
||||
#include <c10/macros/Export.h>
|
||||
#include <c10/macros/Macros.h>
|
||||
#include <cstddef>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
using CopyBytesFunction = void (*)(
|
||||
size_t nbytes,
|
||||
const void* src,
|
||||
Device src_device,
|
||||
void* dst,
|
||||
Device dst_device);
|
||||
|
||||
struct C10_API _CopyBytesFunctionRegisterer {
|
||||
_CopyBytesFunctionRegisterer(
|
||||
DeviceType from,
|
||||
DeviceType to,
|
||||
CopyBytesFunction func_sync,
|
||||
CopyBytesFunction func_async = nullptr);
|
||||
};
|
||||
|
||||
#define REGISTER_COPY_BYTES_FUNCTION(from, to, ...) \
|
||||
namespace { \
|
||||
static _CopyBytesFunctionRegisterer C10_ANONYMOUS_VARIABLE( \
|
||||
g_copy_function)(from, to, __VA_ARGS__); \
|
||||
}
|
||||
|
||||
/*
|
||||
* WARNING: Implementations for this function are currently registered from
|
||||
* ATen and caffe2, not yet from c10. Don't use this if not either ATen
|
||||
* or caffe2 is present as well.
|
||||
* We can't move them yet, because the CUDA implementations aren't unified yet
|
||||
* between ATen and caffe2.
|
||||
* We're planning to move the implementations into c10/backend/xxx
|
||||
* to make c10 self contained again.
|
||||
*/
|
||||
C10_API void CopyBytes(
|
||||
size_t nbytes,
|
||||
const void* src,
|
||||
Device src_device,
|
||||
void* dst,
|
||||
Device dst_device,
|
||||
bool async);
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,20 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/macros/Export.h>
|
||||
|
||||
namespace caffe2 {
|
||||
class TypeMeta;
|
||||
} // namespace caffe2
|
||||
|
||||
namespace c10 {
|
||||
C10_API void set_default_dtype(caffe2::TypeMeta dtype);
|
||||
C10_API const caffe2::TypeMeta get_default_dtype();
|
||||
C10_API ScalarType get_default_dtype_as_scalartype();
|
||||
C10_API const caffe2::TypeMeta get_default_complex_dtype();
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/Device.h>
|
||||
#include <c10/core/DeviceType.h>
|
||||
#include <c10/core/Layout.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/util/typeid.h>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
struct TensorOptions;
|
||||
|
||||
/// Like TensorOptions, but all fields are guaranteed to be filled.
|
||||
struct DefaultTensorOptions {
|
||||
DefaultTensorOptions() = default;
|
||||
|
||||
caffe2::TypeMeta dtype() const noexcept {
|
||||
return dtype_;
|
||||
}
|
||||
Device device() const noexcept {
|
||||
return device_;
|
||||
}
|
||||
Layout layout() const noexcept {
|
||||
return layout_;
|
||||
}
|
||||
bool requires_grad() const noexcept {
|
||||
return requires_grad_;
|
||||
}
|
||||
|
||||
// Defined in TensorOptions.h
|
||||
inline DefaultTensorOptions& merge(const TensorOptions& options);
|
||||
|
||||
private:
|
||||
caffe2::TypeMeta dtype_ = caffe2::TypeMeta::Make<float>(); // 64-bit
|
||||
Device device_ = at::kCPU; // 32-bit
|
||||
Layout layout_ = at::kStrided; // 8-bit
|
||||
bool requires_grad_ = false; // 8-bit
|
||||
};
|
||||
|
||||
inline const DefaultTensorOptions& getDefaultTensorOptions() {
|
||||
static const auto options = DefaultTensorOptions();
|
||||
return options;
|
||||
}
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,221 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/DeviceType.h>
|
||||
#include <c10/macros/Export.h>
|
||||
#include <c10/util/Exception.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <iosfwd>
|
||||
#include <string>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
/// An index representing a specific device; e.g., the 1 in GPU 1.
|
||||
/// A DeviceIndex is not independently meaningful without knowing
|
||||
/// the DeviceType it is associated; try to use Device rather than
|
||||
/// DeviceIndex directly.
|
||||
using DeviceIndex = int8_t;
|
||||
|
||||
/// Represents a compute device on which a tensor is located. A device is
|
||||
/// uniquely identified by a type, which specifies the type of machine it is
|
||||
/// (e.g. CPU or CUDA GPU), and a device index or ordinal, which identifies the
|
||||
/// specific compute device when there is more than one of a certain type. The
|
||||
/// device index is optional, and in its defaulted state represents (abstractly)
|
||||
/// "the current device". Further, there are two constraints on the value of the
|
||||
/// device index, if one is explicitly stored:
|
||||
/// 1. A negative index represents the current device, a non-negative index
|
||||
/// represents a specific, concrete device,
|
||||
/// 2. When the device type is CPU, the device index must be zero.
|
||||
struct C10_API Device final {
|
||||
using Type = DeviceType;
|
||||
|
||||
/// Constructs a new `Device` from a `DeviceType` and an optional device
|
||||
/// index.
|
||||
/* implicit */ Device(DeviceType type, DeviceIndex index = -1)
|
||||
: type_(type), index_(index) {
|
||||
validate();
|
||||
}
|
||||
|
||||
/// Constructs a `Device` from a string description, for convenience.
|
||||
/// The string supplied must follow the following schema:
|
||||
/// `(cpu|cuda)[:<device-index>]`
|
||||
/// where `cpu` or `cuda` specifies the device type, and
|
||||
/// `:<device-index>` optionally specifies a device index.
|
||||
/* implicit */ Device(const std::string& device_string);
|
||||
|
||||
/// Returns true if the type and index of this `Device` matches that of
|
||||
/// `other`.
|
||||
bool operator==(const Device& other) const noexcept {
|
||||
return this->type_ == other.type_ && this->index_ == other.index_;
|
||||
}
|
||||
|
||||
/// Returns true if the type or index of this `Device` differs from that of
|
||||
/// `other`.
|
||||
bool operator!=(const Device& other) const noexcept {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
/// Sets the device index.
|
||||
void set_index(DeviceIndex index) {
|
||||
index_ = index;
|
||||
}
|
||||
|
||||
/// Returns the type of device this is.
|
||||
DeviceType type() const noexcept {
|
||||
return type_;
|
||||
}
|
||||
|
||||
/// Returns the optional index.
|
||||
DeviceIndex index() const noexcept {
|
||||
return index_;
|
||||
}
|
||||
|
||||
/// Returns true if the device has a non-default index.
|
||||
bool has_index() const noexcept {
|
||||
return index_ != -1;
|
||||
}
|
||||
|
||||
/// Return true if the device is of CUDA type.
|
||||
bool is_cuda() const noexcept {
|
||||
return type_ == DeviceType::CUDA;
|
||||
}
|
||||
|
||||
/// Return true if the device is of PrivateUse1 type.
|
||||
bool is_privateuseone() const noexcept {
|
||||
return type_ == DeviceType::PrivateUse1;
|
||||
}
|
||||
|
||||
/// Return true if the device is of MPS type.
|
||||
bool is_mps() const noexcept {
|
||||
return type_ == DeviceType::MPS;
|
||||
}
|
||||
|
||||
/// Return true if the device is of HIP type.
|
||||
bool is_hip() const noexcept {
|
||||
return type_ == DeviceType::HIP;
|
||||
}
|
||||
|
||||
/// Return true if the device is of VE type.
|
||||
bool is_ve() const noexcept {
|
||||
return type_ == DeviceType::VE;
|
||||
}
|
||||
|
||||
/// Return true if the device is of XPU type.
|
||||
bool is_xpu() const noexcept {
|
||||
return type_ == DeviceType::XPU;
|
||||
}
|
||||
|
||||
/// Return true if the device is of IPU type.
|
||||
bool is_ipu() const noexcept {
|
||||
return type_ == DeviceType::IPU;
|
||||
}
|
||||
|
||||
/// Return true if the device is of XLA type.
|
||||
bool is_xla() const noexcept {
|
||||
return type_ == DeviceType::XLA;
|
||||
}
|
||||
|
||||
/// Return true if the device is of MTIA type.
|
||||
bool is_mtia() const noexcept {
|
||||
return type_ == DeviceType::MTIA;
|
||||
}
|
||||
|
||||
/// Return true if the device is of HPU type.
|
||||
bool is_hpu() const noexcept {
|
||||
return type_ == DeviceType::HPU;
|
||||
}
|
||||
|
||||
/// Return true if the device is of Lazy type.
|
||||
bool is_lazy() const noexcept {
|
||||
return type_ == DeviceType::Lazy;
|
||||
}
|
||||
|
||||
/// Return true if the device is of Vulkan type.
|
||||
bool is_vulkan() const noexcept {
|
||||
return type_ == DeviceType::Vulkan;
|
||||
}
|
||||
|
||||
/// Return true if the device is of Metal type.
|
||||
bool is_metal() const noexcept {
|
||||
return type_ == DeviceType::Metal;
|
||||
}
|
||||
|
||||
/// Return true if the device is of MAIA type.
|
||||
bool is_maia() const noexcept {
|
||||
return type_ == DeviceType::MAIA;
|
||||
}
|
||||
|
||||
/// Return true if the device is of META type.
|
||||
bool is_meta() const noexcept {
|
||||
return type_ == DeviceType::Meta;
|
||||
}
|
||||
|
||||
/// Return true if the device is of CPU type.
|
||||
bool is_cpu() const noexcept {
|
||||
return type_ == DeviceType::CPU;
|
||||
}
|
||||
|
||||
/// Return true if the device supports arbitrary strides.
|
||||
bool supports_as_strided() const noexcept {
|
||||
return type_ != DeviceType::IPU && type_ != DeviceType::XLA &&
|
||||
type_ != DeviceType::Lazy;
|
||||
}
|
||||
|
||||
/// Same string as returned from operator<<.
|
||||
std::string str() const;
|
||||
|
||||
private:
|
||||
DeviceType type_;
|
||||
DeviceIndex index_ = -1;
|
||||
void validate() {
|
||||
// Removing these checks in release builds noticeably improves
|
||||
// performance in micro-benchmarks.
|
||||
// This is safe to do, because backends that use the DeviceIndex
|
||||
// have a later check when we actually try to switch to that device.
|
||||
TORCH_INTERNAL_ASSERT_DEBUG_ONLY(
|
||||
index_ >= -1,
|
||||
"Device index must be -1 or non-negative, got ",
|
||||
static_cast<int>(index_));
|
||||
TORCH_INTERNAL_ASSERT_DEBUG_ONLY(
|
||||
!is_cpu() || index_ <= 0,
|
||||
"CPU device index must be -1 or zero, got ",
|
||||
static_cast<int>(index_));
|
||||
}
|
||||
};
|
||||
|
||||
C10_API std::ostream& operator<<(std::ostream& stream, const Device& device);
|
||||
|
||||
} // namespace c10
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<c10::Device> {
|
||||
size_t operator()(c10::Device d) const noexcept {
|
||||
// Are you here because this static assert failed? Make sure you ensure
|
||||
// that the bitmasking code below is updated accordingly!
|
||||
static_assert(sizeof(c10::DeviceType) == 1, "DeviceType is not 8-bit");
|
||||
static_assert(sizeof(c10::DeviceIndex) == 1, "DeviceIndex is not 8-bit");
|
||||
// Note [Hazard when concatenating signed integers]
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// We must first convert to a same-sized unsigned type, before promoting to
|
||||
// the result type, to prevent sign extension when any of the values is -1.
|
||||
// If sign extension occurs, you'll clobber all of the values in the MSB
|
||||
// half of the resulting integer.
|
||||
//
|
||||
// Technically, by C/C++ integer promotion rules, we only need one of the
|
||||
// uint32_t casts to the result type, but we put in both for explicitness's
|
||||
// sake.
|
||||
uint32_t bits = static_cast<uint32_t>(static_cast<uint8_t>(d.type()))
|
||||
<< 16 |
|
||||
static_cast<uint32_t>(static_cast<uint8_t>(d.index()));
|
||||
return std::hash<uint32_t>{}(bits);
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,33 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#include <c10/core/Allocator.h>
|
||||
#include <c10/util/Exception.h>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
template <typename T>
|
||||
class DeviceArray {
|
||||
public:
|
||||
DeviceArray(c10::Allocator& allocator, size_t size)
|
||||
: data_ptr_(allocator.allocate(size * sizeof(T))) {
|
||||
static_assert(std::is_trivial_v<T>, "T must be a trivial type");
|
||||
TORCH_INTERNAL_ASSERT(
|
||||
0 == (reinterpret_cast<intptr_t>(data_ptr_.get()) % alignof(T)),
|
||||
"c10::DeviceArray: Allocated memory is not aligned for this data type");
|
||||
}
|
||||
|
||||
T* get() {
|
||||
return static_cast<T*>(data_ptr_.get());
|
||||
}
|
||||
|
||||
private:
|
||||
c10::DataPtr data_ptr_;
|
||||
};
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,81 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/macros/Export.h>
|
||||
#include <cstdint>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
constexpr size_t NUMBER_OF_DEVICE_CAPABILITIES = NumScalarTypes;
|
||||
|
||||
// Generate bitfields for each scalar type
|
||||
#define DEFINE_SCALAR_TYPE(_1, n) unsigned int has_##n : 1;
|
||||
|
||||
// Generate enum indices for each scalar type
|
||||
#define DEFINE_SCALAR_ENUM(_1, name) kIndex_##name,
|
||||
|
||||
enum ScalarTypeIndex {
|
||||
AT_FORALL_SCALAR_TYPES_WITH_COMPLEX_AND_QINTS(DEFINE_SCALAR_ENUM)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief DeviceCapability represents the the common capabilities that all
|
||||
* devices should support.
|
||||
*
|
||||
* This struct provides a compact way to represent the common capabilities that
|
||||
* all devices should support. Includes the following capabilities:
|
||||
* - Supported data types
|
||||
*
|
||||
* Purpose
|
||||
* - Enable device-specific optimizations based on supported capabilities
|
||||
*
|
||||
* Contract
|
||||
*
|
||||
* Supported data types:
|
||||
* - Each bitfield represents support for one device capability
|
||||
* - Bit value 1 means the capability is supported, 0 means not supported
|
||||
* - The struct is initialized with all capabilities enabled by default
|
||||
*
|
||||
* @note Adding New Capabilities
|
||||
*
|
||||
* 1. Define the new capability in the `DeviceCapability` struct
|
||||
* 2. Update the support of the new capability in each accelerator
|
||||
* implementation
|
||||
* 3. Add the new capability to the returned PyObject Dictionary
|
||||
*/
|
||||
struct C10_API DeviceCapability {
|
||||
union {
|
||||
struct {
|
||||
AT_FORALL_SCALAR_TYPES_WITH_COMPLEX_AND_QINTS(DEFINE_SCALAR_TYPE)
|
||||
} supported_scalar_types;
|
||||
uint64_t capability_bits; // Allow direct bit manipulation
|
||||
} capability_data;
|
||||
|
||||
// Default constructor with all capabilities enabled.
|
||||
DeviceCapability() {
|
||||
capability_data.capability_bits =
|
||||
((1ULL << NUMBER_OF_DEVICE_CAPABILITIES) - 1);
|
||||
}
|
||||
|
||||
// Iterate supported ScalarTypes without allocating a vector
|
||||
template <typename F>
|
||||
void forEachSupportedScalarType(F&& visitor) const {
|
||||
#define VISIT_SCALAR_TYPE(_1, n) \
|
||||
if (capability_data.supported_scalar_types.has_##n) { \
|
||||
visitor(ScalarType::n); \
|
||||
}
|
||||
|
||||
AT_FORALL_SCALAR_TYPES_WITH_COMPLEX_AND_QINTS(VISIT_SCALAR_TYPE)
|
||||
|
||||
#undef VISIT_SCALAR_TYPE
|
||||
}
|
||||
};
|
||||
|
||||
#undef DEFINE_SCALAR_ENUM
|
||||
#undef DEFINE_SCALAR_TYPE
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,207 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/Device.h>
|
||||
#include <c10/core/impl/DeviceGuardImplInterface.h>
|
||||
#include <c10/core/impl/InlineDeviceGuard.h>
|
||||
#include <c10/core/impl/VirtualGuardImpl.h>
|
||||
#include <c10/util/Optional.h>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
/// RAII guard that sets a certain default device in its constructor, and
|
||||
/// changes it back to the device that was originally active upon destruction.
|
||||
///
|
||||
/// The device is always reset to the one that was active at the time of
|
||||
/// construction of the guard. Even if you `set_device` after construction, the
|
||||
/// destructor will still reset the device to the one that was active at
|
||||
/// construction time.
|
||||
///
|
||||
/// This device guard does NOT have an uninitialized state; it is guaranteed
|
||||
/// to reset a device on exit. If you are in a situation where you *might*
|
||||
/// want to setup a guard (i.e., are looking for the moral equivalent
|
||||
/// of std::optional<DeviceGuard>), see OptionalDeviceGuard.
|
||||
class DeviceGuard {
|
||||
public:
|
||||
/// No default constructor; see Note [Omitted default constructor from RAII]
|
||||
explicit DeviceGuard() = delete;
|
||||
|
||||
/// Set the current device to the passed Device.
|
||||
explicit DeviceGuard(Device device) : guard_(device) {}
|
||||
|
||||
/// This constructor is for testing only.
|
||||
explicit DeviceGuard(
|
||||
Device device,
|
||||
const impl::DeviceGuardImplInterface* impl)
|
||||
: guard_(device, impl) {}
|
||||
|
||||
~DeviceGuard() = default;
|
||||
|
||||
/// Copy is disallowed
|
||||
DeviceGuard(const DeviceGuard&) = delete;
|
||||
DeviceGuard& operator=(const DeviceGuard&) = delete;
|
||||
|
||||
/// Move is disallowed, as DeviceGuard does not have an uninitialized state,
|
||||
/// which is required for moves on types with nontrivial destructors.
|
||||
DeviceGuard(DeviceGuard&& other) = delete;
|
||||
DeviceGuard& operator=(DeviceGuard&& other) = delete;
|
||||
|
||||
/// Sets the device to the given one. The specified device must be consistent
|
||||
/// with the device type originally specified during guard construction.
|
||||
///
|
||||
/// TODO: The consistency check here is inconsistent with StreamGuard's
|
||||
/// behavior with set_stream, where a stream on a different device than
|
||||
/// the original one isn't an error; we just reset the stream and then
|
||||
/// switch devices.
|
||||
void reset_device(at::Device device) {
|
||||
guard_.reset_device(device);
|
||||
}
|
||||
|
||||
/// This method is for testing only.
|
||||
void reset_device(
|
||||
at::Device device,
|
||||
const impl::DeviceGuardImplInterface* impl) {
|
||||
guard_.reset_device(device, impl);
|
||||
}
|
||||
|
||||
/// Sets the device index to the given one. The device type is inferred
|
||||
/// from the original device type the guard was constructed with.
|
||||
void set_index(DeviceIndex index) {
|
||||
guard_.set_index(index);
|
||||
}
|
||||
|
||||
/// Returns the device that was set at the time the guard was constructed.
|
||||
Device original_device() const {
|
||||
return guard_.original_device();
|
||||
}
|
||||
|
||||
/// Returns the most recent device that was set using this device guard,
|
||||
/// either from construction, or via set_device.
|
||||
Device current_device() const {
|
||||
return guard_.current_device();
|
||||
}
|
||||
|
||||
private:
|
||||
impl::InlineDeviceGuard<impl::VirtualGuardImpl> guard_;
|
||||
};
|
||||
|
||||
/**
|
||||
* A OptionalDeviceGuard is an RAII class that sets a device to some value on
|
||||
* initialization, and resets the device to its original value on destruction.
|
||||
* Morally, a OptionalDeviceGuard is equivalent to std::optional<DeviceGuard>,
|
||||
* but with extra constructors and methods as appropriate.
|
||||
*
|
||||
* Besides its obvious use (optionally applying a DeviceGuard),
|
||||
* OptionalDeviceGuard is often also used for the following idiom:
|
||||
*
|
||||
* OptionalDeviceGuard g;
|
||||
* for (const auto& t : tensors) {
|
||||
* g.set_device(t.device());
|
||||
* do_something_with(t);
|
||||
* }
|
||||
*
|
||||
* This usage is marginally more efficient than constructing a DeviceGuard every
|
||||
* iteration of the for loop, as it avoids an unnecessary device reset.
|
||||
*
|
||||
* Unlike DeviceGuard, a OptionalDeviceGuard may be uninitialized. This occurs
|
||||
* when you use the nullary constructor, or pass a nullopt to the constructor.
|
||||
* Uninitialized OptionalDeviceGuards do *nothing*; they do not know what the
|
||||
* original device was and they do not reset on destruction. This is why
|
||||
* original_device() and current_device() return std::optional<Device> rather
|
||||
* than Device (as they do in DeviceGuard), and also is why we didn't just
|
||||
* provide OptionalDeviceGuard by default and hide DeviceGuard from users.
|
||||
*
|
||||
* The semantics of an OptionalDeviceGuard are exactly explained by thinking
|
||||
* of it as an std::optional<DeviceGuard>. In particular, an initialized
|
||||
* OptionalDeviceGuard doesn't restore device to its value at construction; it
|
||||
* restores device to its value *at initialization*. So if you have the
|
||||
* program:
|
||||
*
|
||||
* setDevice(1);
|
||||
* OptionalDeviceGuard g;
|
||||
* setDevice(2);
|
||||
* g.reset_device(Device(DeviceType::CUDA, 3)); // initializes!
|
||||
*
|
||||
* On destruction, g will reset device to 2, rather than 1.
|
||||
*
|
||||
* An uninitialized OptionalDeviceGuard is distinct from a (initialized)
|
||||
* DeviceGuard whose original_device_ and current_device_ match, since the
|
||||
* DeviceGuard will still reset the device to original_device_.
|
||||
*/
|
||||
class OptionalDeviceGuard {
|
||||
public:
|
||||
/// Create an uninitialized guard. Set the guard later using reset_device.
|
||||
explicit OptionalDeviceGuard() = default;
|
||||
|
||||
/// Initialize the guard, setting the current device to the passed Device.
|
||||
explicit OptionalDeviceGuard(Device device) : guard_(device) {}
|
||||
|
||||
/// Initialize the guard if a Device is passed; otherwise leave the
|
||||
/// guard uninitialized.
|
||||
explicit OptionalDeviceGuard(std::optional<Device> device) : guard_(device) {}
|
||||
|
||||
/// Constructor for testing only.
|
||||
explicit OptionalDeviceGuard(
|
||||
Device device,
|
||||
const impl::DeviceGuardImplInterface* impl)
|
||||
: guard_(device, impl) {}
|
||||
|
||||
~OptionalDeviceGuard() = default;
|
||||
/// Copy is disallowed
|
||||
OptionalDeviceGuard(const OptionalDeviceGuard&) = delete;
|
||||
OptionalDeviceGuard& operator=(const OptionalDeviceGuard&) = delete;
|
||||
|
||||
/// Move is disallowed
|
||||
/// See Note [Explicit initialization of optional fields]
|
||||
/// and // Note [Move construction for RAII guards is tricky]
|
||||
/// for rationale.
|
||||
OptionalDeviceGuard(OptionalDeviceGuard&& other) = delete;
|
||||
OptionalDeviceGuard& operator=(OptionalDeviceGuard&& other) = delete;
|
||||
|
||||
/// Sets the device to the given one. The specified device must be consistent
|
||||
/// with the device type originally specified during guard construction.
|
||||
void reset_device(at::Device device) {
|
||||
guard_.reset_device(device);
|
||||
}
|
||||
|
||||
/// For testing only
|
||||
void reset_device(
|
||||
at::Device device,
|
||||
const impl::DeviceGuardImplInterface* impl) {
|
||||
guard_.reset_device(device, impl);
|
||||
}
|
||||
|
||||
/// Returns the device that was set at the time the guard was constructed.
|
||||
std::optional<Device> original_device() const {
|
||||
return guard_.original_device();
|
||||
}
|
||||
|
||||
/// Returns the most recent device that was set using this device guard,
|
||||
/// either from construction, or via reset_device.
|
||||
std::optional<Device> current_device() const {
|
||||
return guard_.current_device();
|
||||
}
|
||||
|
||||
private:
|
||||
impl::InlineOptionalDeviceGuard<impl::VirtualGuardImpl> guard_;
|
||||
};
|
||||
|
||||
// Note [Whither the DeviceGuard boilerplate]
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Design note: in principle, we could avoid these wrappers using:
|
||||
//
|
||||
// using DeviceGuard = impl::InlineDeviceGuard<impl::VirtualGuardImpl>;
|
||||
// using OptionalDeviceGuard =
|
||||
// impl::InlineOptionalDeviceGuard<impl::VirtualGuardImpl>;
|
||||
//
|
||||
// But the error messages are worse, and our users can't just look at the
|
||||
// header file to find out what's going on. Furthermore, for specializations
|
||||
// like CUDAStreamGuard, it can be profitable to replace some interfaces with
|
||||
// refined types (e.g., return CUDAStream instead of Stream). So, we eat
|
||||
// the boilerplate and write out the API explicitly.
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,35 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/macros/Export.h>
|
||||
|
||||
// If you modified DeviceType in caffe2/proto/caffe2.proto, please also sync
|
||||
// your changes into torch/headeronly/core/DeviceType.h.
|
||||
#include <torch/headeronly/core/DeviceType.h>
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
C10_API std::string DeviceTypeName(DeviceType d, bool lower_case = false);
|
||||
|
||||
C10_API bool isValidDeviceType(DeviceType d);
|
||||
|
||||
C10_API std::ostream& operator<<(std::ostream& stream, DeviceType type);
|
||||
|
||||
C10_API void register_privateuse1_backend(const std::string& backend_name);
|
||||
C10_API std::string get_privateuse1_backend(bool lower_case = true);
|
||||
|
||||
C10_API bool is_privateuse1_backend_registered();
|
||||
|
||||
} // namespace c10
|
||||
|
||||
namespace torch {
|
||||
// NOLINTNEXTLINE(misc-unused-using-decls)
|
||||
using c10::DeviceType;
|
||||
} // namespace torch
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,750 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/DeviceType.h>
|
||||
#include <c10/macros/Export.h>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
// Semantically, each value of BackendComponent identifies a "backend" for our
|
||||
// dispatch. Some functionalities that we may dispatch to are allowed to
|
||||
// register different handlers for each backend. The BackendComponent is then
|
||||
// used to figure out which backend implementation to dispatch to.
|
||||
|
||||
// In implementation terms, the backend component identifies a specific "bit" in
|
||||
// a DispatchKeySet. The bits in the DispatchKeySet are split between the bottom
|
||||
// ~12 "BackendComponent" bits, while the remaining upper bits are assigned to
|
||||
// functionalities. When we encounter a functionality bit that is known to be
|
||||
// customizable per-backend, then we also look at the lower BackendComponent
|
||||
// bits and take the highest bit to determine which backend's implementation to
|
||||
// use.
|
||||
|
||||
// WARNING! If you add a new backend component to the end of this list,
|
||||
// make sure you register it before Meta.
|
||||
// Meta must be at the end so that meta key in tls triggers meta kernels.
|
||||
// (But you shouldn't: private use keys should have higher precedence than all
|
||||
// built-in keys)
|
||||
|
||||
// If you add a new (non-privateuse) backend here,
|
||||
// make sure to add an Autograd<Backend> fallthrough kernel
|
||||
// in aten/src/ATen/core/VariableFallbackKernel.cpp
|
||||
|
||||
#define C10_FORALL_BACKEND_COMPONENTS(_, extra) \
|
||||
_(CPU, extra) \
|
||||
_(CUDA, extra) \
|
||||
_(HIP, extra) \
|
||||
_(XLA, extra) \
|
||||
_(MPS, extra) \
|
||||
_(IPU, extra) \
|
||||
_(XPU, extra) \
|
||||
_(HPU, extra) \
|
||||
_(VE, extra) \
|
||||
_(Lazy, extra) \
|
||||
_(MTIA, extra) \
|
||||
_(MAIA, extra) \
|
||||
_(PrivateUse1, extra) \
|
||||
_(PrivateUse2, extra) \
|
||||
_(PrivateUse3, extra) \
|
||||
_(Meta, extra)
|
||||
|
||||
// WARNING! If we add a new per-backend functionality key that has higher
|
||||
// priority than Autograd, then make sure you update EndOfRuntimeBackendKeys
|
||||
|
||||
#define C10_FORALL_FUNCTIONALITY_KEYS(_) \
|
||||
_(Dense, ) \
|
||||
_(Quantized, Quantized) \
|
||||
_(Sparse, Sparse) \
|
||||
_(SparseCsr, SparseCsr) \
|
||||
_(NestedTensor, NestedTensor) \
|
||||
_(AutogradFunctionality, Autograd)
|
||||
|
||||
enum class BackendComponent : uint8_t {
|
||||
|
||||
// A "backend" is colloquially used to refer to handlers for dispatch
|
||||
// which actually implement the numerics of an operation in question.
|
||||
//
|
||||
// Due to the nature of the enum, these backends are specified in
|
||||
// an ordered way, but for most backends this order is not semantically
|
||||
// meaningful (e.g., it's valid to reorder these backends without changing
|
||||
// semantics). The only situation when backend ordering is meaningful
|
||||
// is when the backend participates in multiple dispatch with another
|
||||
// backend; e.g., CPU and CUDA (cuda must have higher priority).
|
||||
|
||||
// These keys don't correspond to individual kernels.
|
||||
// Instead, they represent the backends that are allowed to override specific
|
||||
// pieces of functionality:
|
||||
// - dense kernels (e.g. DispatchKey::CPU)
|
||||
// - sparse kernels (e.g. DispatchKey::SparseCPU)
|
||||
// - quantized kernels (e.g. DispatchKey::QuantizedCPU)
|
||||
// - autograd kernels (e.g. DispatchKey::AutogradCPU)
|
||||
// We reserve space in the runtime operator table for this full cross product
|
||||
// of
|
||||
// [backends in this enum] x [keys below that are explicitly marked as having
|
||||
// per-backend functionality]
|
||||
//
|
||||
// A meta tensor is a tensor without any data associated with it. (They
|
||||
// have also colloquially been referred to as tensors on the "null" device).
|
||||
// A meta tensor can be used to dry run operators without actually doing any
|
||||
// computation, e.g., add on two meta tensors would give you another meta
|
||||
// tensor with the output shape and dtype, but wouldn't actually add anything.
|
||||
|
||||
InvalidBit = 0,
|
||||
#define DEFINE_BACKEND_COMPONENT(n, _) n##Bit,
|
||||
C10_FORALL_BACKEND_COMPONENTS(DEFINE_BACKEND_COMPONENT, unused)
|
||||
#undef DEFINE_BACKEND_COMPONENT
|
||||
|
||||
// Define an alias to represent end of backend dispatch keys.
|
||||
// If you add new backend keys after PrivateUse3, please also update it here.
|
||||
EndOfBackendKeys = MetaBit,
|
||||
};
|
||||
|
||||
// Semantically, a dispatch key identifies a possible "level" in our
|
||||
// dispatch, for which a handler may be registered. Each handler corresponds
|
||||
// to a type of functionality.
|
||||
//
|
||||
// In implementation terms, the dispatch key identifies a specific "bit" in a
|
||||
// DispatchKeySet. Higher bit indexes get handled by dispatching first (because
|
||||
// we "count leading zeros" when we extract the highest priority dispatch
|
||||
// key.)
|
||||
//
|
||||
// Note [DispatchKey Classification]
|
||||
// This enum actually contains several types of keys, which are explained
|
||||
// in more detail further down:
|
||||
// (1) non-customizable backends (e.g. FPGA)
|
||||
// (2) non-customizable functionalities (e.g. Functionalize)
|
||||
// (3) functionalized that are customizable per backend (e.g. Dense, Sparse,
|
||||
// AutogradFunctionality) (4) per-backend instances of customizable
|
||||
// functionalities (e.g. CPU, SparseCPU, AutogradCPU) (5) alias keys (e.g.
|
||||
// CompositeImplicitAutograd)
|
||||
//
|
||||
// Of the categories above, it's important to note:
|
||||
// (a) which keys are assigned individual bits in a DispatchKeySet
|
||||
// (b) which keys are assigned individual slots in the runtime operator table
|
||||
// ("Runtime keys")
|
||||
//
|
||||
// (1), (2) and (3) all get their own dedicated bits in the DispatchKeySet.
|
||||
// (1), (2) and (4) all get their own dedicated slots in the runtime operator
|
||||
// table.
|
||||
|
||||
// See Note [DispatchKeySet Internal Representation] for more details.
|
||||
//
|
||||
// NOTE: Keep the list in sync with `DispatchKey` in torchgen/model.py
|
||||
enum class DispatchKey : uint16_t {
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~ UNDEFINED ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
|
||||
// This is not a "real" functionality, but it exists to give us a "nullopt"
|
||||
// element we can return for cases when a DispatchKeySet contains no elements.
|
||||
// You can think a more semantically accurate definition of DispatchKey is:
|
||||
//
|
||||
// using DispatchKey = std::optional<RealDispatchKey>
|
||||
//
|
||||
// and Undefined == nullopt. We didn't actually represent
|
||||
// it this way because std::optional<RealDispatchKey> would take two
|
||||
// words, when DispatchKey fits in eight bits.
|
||||
|
||||
Undefined = 0,
|
||||
|
||||
// Define an alias for Undefined to represent CatchAll (long term
|
||||
// this will get eliminated, but for now it's convenient)
|
||||
CatchAll = Undefined,
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~ Functionality Keys ~~~~~~~~~~~~~~~~~~~~~~ //
|
||||
// Every value in the enum (up to EndOfFunctionalityKeys)
|
||||
// corresponds to an individual "functionality" that can be dispatched to.
|
||||
// This is represented in the DispatchKeySet by assigning each of these enum
|
||||
// values
|
||||
// to each of the remaining (64 - len(BackendComponent)) bits.
|
||||
//
|
||||
// Most of these functionalities have a single handler assigned to them,
|
||||
// making them "runtime keys".
|
||||
// That map to a single slot in the runtime operator table.
|
||||
//
|
||||
// A few functionalities are allowed to be customizable per backend.
|
||||
// See [Note: Per-Backend Functionality Dispatch Keys] for details.
|
||||
|
||||
// See [Note: Per-Backend Functionality Dispatch Keys]
|
||||
Dense,
|
||||
|
||||
// Below are non-extensible backends.
|
||||
// These are backends that currently don't have their own overrides for
|
||||
// Autograd/Sparse/Quantized kernels,
|
||||
// and we therefore don't waste space in the runtime operator table allocating
|
||||
// space for them.
|
||||
// If any of these backends ever need to customize, e.g., Autograd, then we'll
|
||||
// need to add a DispatchKey::*Bit for them.
|
||||
|
||||
// TODO: put this in BackendComponents
|
||||
FPGA, // Xilinx support lives out of tree at
|
||||
// https://gitlab.com/pytorch-complex/vitis_kernels
|
||||
|
||||
Vulkan, // TODO: put this in BackendComponents
|
||||
Metal, // TODO: put this in BackendComponents
|
||||
|
||||
// See [Note: Per-Backend Functionality Dispatch Keys]
|
||||
Quantized,
|
||||
|
||||
// This backend is to support custom RNGs; it lets you go
|
||||
// to a different kernel if you pass in a generator that is not a
|
||||
// traditional CPUGeneratorImpl/CUDAGeneratorImpl. To make use of this
|
||||
// key:
|
||||
// 1) set it as a second parameter of at::Generator constructor call in
|
||||
// the user-defined PRNG class.
|
||||
// 2) use it as a dispatch key while registering custom kernels
|
||||
// (templatized kernels specialized for user-defined PRNG class)
|
||||
// intended for out of tree use; tested by aten/src/ATen/test/rng_test.cpp
|
||||
CustomRNGKeyId,
|
||||
|
||||
// TODO: Make Mkldnn a functionality key, so we can give it Meta
|
||||
// support
|
||||
// Here are backends which specify more specialized operators
|
||||
// based on the layout of the tensor. Note that the sparse backends
|
||||
// are one case where ordering matters: sparse multi-dispatches with
|
||||
// the corresponding dense tensors, and must be handled before them.
|
||||
MkldnnCPU, // registered at build/aten/src/ATen/RegisterMkldnnCPU.cpp
|
||||
// NB: not to be confused with MKLDNN, which is Caffe2 only
|
||||
|
||||
// See [Note: Per-Backend Functionality Dispatch Keys]
|
||||
Sparse,
|
||||
|
||||
SparseCsr,
|
||||
|
||||
NestedTensor,
|
||||
|
||||
// In some situations, it is not immediately obvious what the correct
|
||||
// backend for function is, because the function in question doesn't
|
||||
// have any "tensor" arguments. In this case, a BackendSelect function
|
||||
// can be registered to implement the custom determination of the
|
||||
// correct backend.
|
||||
BackendSelect,
|
||||
|
||||
Python,
|
||||
|
||||
// Out-of-core key for Fake Tensor in torchdistx.
|
||||
// See https://pytorch.org/torchdistx/latest/fake_tensor.html
|
||||
// TODO: delete this in favor of Python-implemented fake tensor
|
||||
Fake,
|
||||
// See Note [Out-of-tree vmap+grad prototype]. The purpose of this key
|
||||
// is to insert code after the "autograd subsystem" runs, so this key should
|
||||
// be directly after ADInplaceOrView and all of the autograd keys.
|
||||
FuncTorchDynamicLayerBackMode,
|
||||
|
||||
// Alias and mutation removal.
|
||||
// If some backends want to opt into only alias removal or only mutation
|
||||
// removal,
|
||||
// we can consider adding separate keys dedicated to those individual passes.
|
||||
// See Note [Functionalization Pass In Core] for details.
|
||||
Functionalize,
|
||||
|
||||
// The named dispatch key is set for any tensors with named dimensions.
|
||||
// Although we have a dispatch key for named tensors, for historical reasons,
|
||||
// this dispatch key doesn't do any of the substantive functionality for named
|
||||
// tensor (though, hypothetically, it could!) At the moment, it's just
|
||||
// responsible for letting us give good error messages when operations
|
||||
// don't support named tensors.
|
||||
//
|
||||
// NB: If you ever consider moving named tensor functionality into
|
||||
// this dispatch key, note that it might be necessary add another dispatch
|
||||
// key that triggers before composite operators, in case a composite operator
|
||||
// has named dimension propagation that doesn't match that of its
|
||||
// constituent parts.
|
||||
// TODO: delete this once torchdim lands in functorch
|
||||
Named,
|
||||
|
||||
// The Conjugate dispatch key is set for any tensors that need to perform
|
||||
// conjugation
|
||||
// This is implemented at a dispatch level right before any backends run
|
||||
Conjugate,
|
||||
|
||||
// The Negative dispatch key is set for any tensors that need to perform
|
||||
// negation
|
||||
// This is implemented at a dispatch level right before any backends run
|
||||
Negative,
|
||||
|
||||
ZeroTensor, // registered at build/aten/src/ATen/RegisterZeroTensor.cpp
|
||||
|
||||
// Note [ADInplaceOrView key]
|
||||
// ADInplaceOrView key is used by inplace or view ops to register a kernel
|
||||
// that does additional setup for future autograd computation.
|
||||
//
|
||||
// 1. For inplace ops this kernel does version bump
|
||||
// 2. For view ops this kernel does `as_view` setup where we properly setup
|
||||
// DifferentiableViewMeta on the view tensors.
|
||||
//
|
||||
// For other ops it's fallthrough kernel since there's no extra
|
||||
// work to do.
|
||||
//
|
||||
// Note [Dream: skip VariableType kernel when requires_grad=false]
|
||||
//
|
||||
// In an ideal world where we can skip VariableType kernel for inputs
|
||||
// with requires_grad=false, instead of a fallthrough kernel, we'll
|
||||
// register a kernel shown below to all functional ops as well:
|
||||
// torch::Tensor my_functional_op(...) {
|
||||
// {
|
||||
// // Note for every op in VariableType, you need to go through
|
||||
// // `AutoDispatchBelowADInplaceOrView` guard exactly once to add the
|
||||
// // key to TLS excluded set. If you don't go through it at all,
|
||||
// // inplace/view ops called through `at::` inside your backend
|
||||
// // kernel will dispatch to ADInplaceOrView kernels and do a lot
|
||||
// // of extra work.
|
||||
// at::AutoDispatchBelowADInplaceOrView guard;
|
||||
// at::redispatch::my_functional_op(...);
|
||||
// }
|
||||
// }
|
||||
// But this work is currently blocked since it adds an extra dispatch
|
||||
// for all ops and it's non-trivial overhead at model level(a few percents).
|
||||
// Thus our current approach takes advantage of the fact every kernel go
|
||||
// through VariableType kernel first and pulls the
|
||||
// `at::AutoDispatchBelowADInplaceOrView` guard of functional ops
|
||||
// up to the `VariableType` kernel. Thus we only add the extra dispatch
|
||||
// to view/inplace ops to minimize its perf impact to real models.
|
||||
ADInplaceOrView,
|
||||
// Note [Alias Dispatch Key : Autograd]
|
||||
// All backends are oblivious to autograd; autograd is handled as a
|
||||
// layer which happens on top of all backends. It inspects the autograd
|
||||
// metadata of all inputs, determines what autograd metadata should be
|
||||
// constructed by the output, and otherwise defers to the backend to
|
||||
// actually do the numeric computation. Autograd contains
|
||||
// the bulk of this logic.
|
||||
|
||||
// Autograd is now an alias dispatch key which by default maps to all
|
||||
// backend-specific autograd keys.
|
||||
// Backend-specific allow backends to override the default kernel registered
|
||||
// to Autograd key as needed.
|
||||
// For example, XLA wants to define autograd for einsum directly.
|
||||
// Registering a custom autograd implementation at the XLA key won't work
|
||||
// because we process Autograd before XLA. This key has higher priority and
|
||||
// gets processed first. You generally should NOT redispatch after handling
|
||||
// autograd here (since that would result in execution of the Autograd
|
||||
// operator, which you're trying to skip). In AutogradXLA implementations,
|
||||
// you are responsible for handling autograd yourself, or deferring to other
|
||||
// operators which support autograd.
|
||||
|
||||
// Currently we only have backend-specific autograd keys for CPU/CUDA/XLA and
|
||||
// reserved user-defined backends. All other in-tree backends share the
|
||||
// AutogradOther key. We can add specific autograd key for those backends
|
||||
// upon request.
|
||||
AutogradOther,
|
||||
|
||||
// See [Note: Per-Backend Functionality Dispatch Keys]
|
||||
AutogradFunctionality,
|
||||
|
||||
// NestedTensor is an example of something that isn't a "real backend"
|
||||
// (because it mostly consists of redispatching kernels)
|
||||
// but it would like to override autograd functionality in C++.
|
||||
// We can handle cases like this by adding an extra functionality key
|
||||
// exclusively for handling autograd for NestedTensor.
|
||||
// lives out of tree at
|
||||
// https://github.com/pytorch/nestedtensor
|
||||
AutogradNestedTensor,
|
||||
|
||||
Tracer,
|
||||
|
||||
// TODO: make Autocast a functionality key
|
||||
// Autocasting precedes VariableTypeId, to ensure casts are autograd-exposed
|
||||
// and inputs are saved for backward in the post-autocast type.
|
||||
AutocastCPU,
|
||||
AutocastMTIA,
|
||||
AutocastMAIA,
|
||||
AutocastXPU,
|
||||
AutocastIPU,
|
||||
AutocastHPU,
|
||||
AutocastXLA,
|
||||
// AutocastXLA is only being used for TPUs. XLA GPUs continue to use
|
||||
// AutocastCUDA.
|
||||
AutocastMPS,
|
||||
AutocastCUDA,
|
||||
AutocastPrivateUse1,
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~ WRAPPERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
|
||||
// There are a number of alternative modes which may want to handle before
|
||||
// autograd; for example, error checking, tracing, profiling or vmap. They
|
||||
// go here.
|
||||
|
||||
FuncTorchBatched, // See Note [Out-of-tree vmap+grad prototype]
|
||||
|
||||
// Dispatch key for BatchedTensorImpl wrapping a nested tensor.
|
||||
BatchedNestedTensor,
|
||||
|
||||
FuncTorchVmapMode, // See Note [Out-of-tree vmap+grad prototype]
|
||||
|
||||
// This is the dispatch key for BatchedTensorImpl, which is used to implement
|
||||
// batching rules for vmap.
|
||||
Batched,
|
||||
|
||||
// When we are inside a vmap, all tensors dispatch on this key.
|
||||
// See Note: [DispatchKey::VmapMode usage] for more details.
|
||||
VmapMode,
|
||||
|
||||
FuncTorchGradWrapper, // See Note [Out-of-tree vmap+grad prototype]
|
||||
|
||||
// Out-of-core key for Deferred Module Initialization in torchdistx.
|
||||
// See https://pytorch.org/torchdistx/latest/deferred_init.html
|
||||
DeferredInit,
|
||||
|
||||
// Used by Python key logic to know the set of tls on entry to the dispatcher
|
||||
// This kernel assumes it is the top-most non-functorch-related DispatchKey.
|
||||
// If you add a key above, make sure to update the fallback implementation for
|
||||
// this.
|
||||
PythonTLSSnapshot,
|
||||
|
||||
// This key should be at the very top of the dispatcher
|
||||
FuncTorchDynamicLayerFrontMode, // See Note [Out-of-tree vmap+grad prototype]
|
||||
|
||||
// TESTING: This is intended to be a generic testing tensor type id.
|
||||
// Don't use it for anything real; its only acceptable use is within a single
|
||||
// process test. Use it by creating a TensorImpl with this DispatchKey, and
|
||||
// then registering operators to operate on this type id. See
|
||||
// aten/src/ATen/core/dispatch/backend_fallback_test.cpp for a usage example.
|
||||
TESTING_ONLY_GenericWrapper,
|
||||
|
||||
// TESTING: This is intended to be a generic testing tensor type id.
|
||||
// Don't use it for anything real; its only acceptable use is within a ingle
|
||||
// process test. Use it by toggling the mode on and off via
|
||||
// TESTING_ONLY_tls_generic_mode_set_enabled and then registering operators
|
||||
// to operate on this type id. See
|
||||
// aten/src/ATen/core/dispatch/backend_fallback_test.cpp
|
||||
// for a usage example
|
||||
TESTING_ONLY_GenericMode,
|
||||
|
||||
// This key is used for pre-dispatch tracing in make_fx.
|
||||
// It has lower priority than the PythonDispatcher key
|
||||
// because we use the PythonDispatcher to intercept the key from python,
|
||||
// and avoid having to implement it in C++.
|
||||
PreDispatch,
|
||||
|
||||
// This is a bypass that allows you to skip running the C++ dispatcher
|
||||
// entirely
|
||||
PythonDispatcher,
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ FIN ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
|
||||
EndOfFunctionalityKeys, // End of functionality keys.
|
||||
|
||||
// ~~~~~~~~~~~~~~ "Dense" Per-Backend Dispatch keys ~~~~~~~~~~~~~~~~~~~~ //
|
||||
// Here are backends which you think of as traditionally specifying
|
||||
// how to implement operations on some device.
|
||||
|
||||
#define DEFINE_PER_BACKEND_KEYS_FOR_BACKEND(n, prefix) prefix##n,
|
||||
|
||||
#define DEFINE_PER_BACKEND_KEYS(fullname, prefix) \
|
||||
StartOf##fullname##Backends, \
|
||||
C10_FORALL_BACKEND_COMPONENTS( \
|
||||
DEFINE_PER_BACKEND_KEYS_FOR_BACKEND, prefix) \
|
||||
EndOf##fullname##Backends = prefix##Meta,
|
||||
|
||||
C10_FORALL_FUNCTIONALITY_KEYS(DEFINE_PER_BACKEND_KEYS)
|
||||
|
||||
#undef DEFINE_PER_BACKEND_KEYS
|
||||
#undef DEFINE_PER_BACKEND_KEYS_FOR_BACKEND
|
||||
|
||||
EndOfRuntimeBackendKeys = EndOfAutogradFunctionalityBackends,
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~ Alias Dispatch Keys ~~~~~~~~~~~~~~~~~~~~~~~~~~ //
|
||||
// Note [Alias Dispatch Keys]
|
||||
// Alias dispatch keys are synthetic dispatch keys which map to multiple
|
||||
// runtime dispatch keys. Alisa keys have precedence, but they are always
|
||||
// lower precedence than runtime keys. You can register a kernel to an
|
||||
// alias key, the kernel might be populated to the mapped runtime keys
|
||||
// during dispatch table computation.
|
||||
// If a runtime dispatch key has multiple kernels from alias keys, which
|
||||
// kernel wins is done based on the precedence of alias keys (but runtime
|
||||
// keys always have precedence over alias keys).
|
||||
// Alias keys won't be directly called during runtime.
|
||||
|
||||
// See Note [Alias Dispatch Key : Autograd]
|
||||
Autograd,
|
||||
CompositeImplicitAutograd, // registered at
|
||||
// build/aten/src/ATen/RegisterCompositeImplicitAutograd.cpp
|
||||
|
||||
// Note: The alias keyset for FuncTorchBatchedDecomposition is disjoint from
|
||||
// all
|
||||
// other alias keysets
|
||||
// and so precedence order doesn't matter
|
||||
FuncTorchBatchedDecomposition, // registered at
|
||||
// build/aten/src/ATen/RegisterFuncTorchBatchedDecomposition.cpp
|
||||
// Note: The alias keyset for CompositeImplicitAutogradNestedTensor is
|
||||
// disjoint from all other alias keysets
|
||||
CompositeImplicitAutogradNestedTensor, // registered at
|
||||
// build/aten/src/ATen/RegisterCompositeImplicitAutogradNestedTensor.cpp
|
||||
CompositeExplicitAutograd, // registered at
|
||||
// build/aten/src/ATen/RegisterCompositeExplicitAutograd.cpp
|
||||
// See Note [CompositeExplicitAutogradNonFunctional Key]
|
||||
CompositeExplicitAutogradNonFunctional, // registered at
|
||||
// build/aten/src/ATen/RegisterCompositeExplicitAutograd.cpp
|
||||
|
||||
// Define an alias key to represent end of alias dispatch keys.
|
||||
// If you add new alias keys after Autograd, please also update it here.
|
||||
StartOfAliasKeys = Autograd,
|
||||
EndOfAliasKeys = CompositeExplicitAutogradNonFunctional, //
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~ BC ALIASES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
|
||||
// The aliases exist for backwards compatibility reasons, they shouldn't
|
||||
// be used
|
||||
CPUTensorId = CPU,
|
||||
CUDATensorId = CUDA,
|
||||
DefaultBackend = CompositeExplicitAutograd,
|
||||
PrivateUse1_PreAutograd = AutogradPrivateUse1,
|
||||
PrivateUse2_PreAutograd = AutogradPrivateUse2,
|
||||
PrivateUse3_PreAutograd = AutogradPrivateUse3,
|
||||
Autocast = AutocastCUDA,
|
||||
};
|
||||
|
||||
// Note [Private use DispatchKey]
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Private use tensor IDs are preallocated tensor type IDs for use in user
|
||||
// applications. Similar to private use fields in HTTP, they can be used
|
||||
// by end users for experimental or private applications, without needing
|
||||
// to "standardize" the tensor ID (which would be done by submitting a PR
|
||||
// to PyTorch to add your type ID).
|
||||
//
|
||||
// Private use tensor IDs are appropriate to use if you want to experiment
|
||||
// with adding a new tensor type (without having to patch PyTorch first) or
|
||||
// have a private, non-distributed application that needs to make use of a
|
||||
// new tensor type. Private use tensor IDs are NOT appropriate to use for
|
||||
// libraries intended to be distributed to further users: please contact
|
||||
// the PyTorch developers to get a type ID registered in this case.
|
||||
//
|
||||
// We provide two classes of private user tensor id: regular DispatchKeys
|
||||
// and Autograd DispatchKeys. DispatchKeys serve the role of ordinary "backend"
|
||||
// DispatchKeys; if you were adding support for a new type of accelerator, you
|
||||
// would use a backend DispatchKey, and ideally automatically reuse
|
||||
// AutogradOther definitions already defined in PyTorch. AutogradPrivateUse
|
||||
// DispatchKeys serve as "wrapper" DispatchKeys: they are only necessary for
|
||||
// tensors that compose multiple internal tensors, and for cases when the
|
||||
// built-in autograd formulas for operators are not appropriate.
|
||||
|
||||
static_assert(
|
||||
(static_cast<uint8_t>(BackendComponent::EndOfBackendKeys) +
|
||||
static_cast<uint8_t>(DispatchKey::EndOfFunctionalityKeys)) <= 64,
|
||||
"The BackendComponent and DispatchKey enums (below EndOfFunctionalityKeys)"
|
||||
" both map to backend and functionality bits"
|
||||
" into a 64-bit bitmask; you must have less than 64 total entries between them");
|
||||
|
||||
// Check if a DispatchKey is an alias mapping to other runtime keys.
|
||||
constexpr bool isAliasDispatchKey(DispatchKey k) {
|
||||
return k >= DispatchKey::StartOfAliasKeys && k <= DispatchKey::EndOfAliasKeys;
|
||||
}
|
||||
|
||||
// [Note: Per-Backend Functionality Dispatch Keys]
|
||||
// Check if a DispatchKey is a per-backend functionality key
|
||||
// Any functionalities that can be customized per-backend should be added here.
|
||||
// These keys correspond to functionalities that can be customized individually
|
||||
// per backend. While they only take up one bit in the `DispatchKeySet` bitset,
|
||||
// they map to (# backends) slots in the operator table.
|
||||
// Each of these keys also has a separate set of "runtime keys" in the dispatch
|
||||
// key enum, per backend, which *do* map to the individual operator table slots.
|
||||
// For example, the "Sparse" key maps to an individual bit in the
|
||||
// DispatchKeySet, while `SparseCPU`, `SparseCUDA`, etc all map to individual
|
||||
// slots in the runtime operator table.
|
||||
|
||||
constexpr bool isPerBackendFunctionalityKey(DispatchKey k) {
|
||||
if (k == DispatchKey::Dense || k == DispatchKey::Quantized ||
|
||||
k == DispatchKey::Sparse || k == DispatchKey::SparseCsr ||
|
||||
k == DispatchKey::AutogradFunctionality ||
|
||||
k == DispatchKey::NestedTensor) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Note that this includes Undefined in the total count.
|
||||
// BUT EndOfFunctionalityKeys is its own (placeholder) key.
|
||||
// e.g. Undefined=0, Dense=1, Sparse=2, EndOfFunctionalityKeys=3.
|
||||
// In the above example, there are 3 total functionality keys.
|
||||
constexpr uint8_t num_functionality_keys =
|
||||
static_cast<uint8_t>(DispatchKey::EndOfFunctionalityKeys);
|
||||
|
||||
constexpr uint8_t num_backends =
|
||||
static_cast<uint8_t>(BackendComponent::EndOfBackendKeys);
|
||||
|
||||
// Note [No More Than 16 Backends]
|
||||
// Search for this note to find places in the code where the "no more than 16
|
||||
// backends" invariant is baked in.
|
||||
static_assert(
|
||||
static_cast<uint8_t>(BackendComponent::EndOfBackendKeys) <= 16,
|
||||
"BackendComponent currently only supports <= 16 backends. If we really need to extend this, \
|
||||
there are a few places where this invariant is baked in");
|
||||
|
||||
constexpr uint8_t numPerBackendFunctionalityKeys() {
|
||||
uint8_t count = 0;
|
||||
for (uint8_t k = 0; k <= num_functionality_keys; ++k) {
|
||||
if (isPerBackendFunctionalityKey(static_cast<DispatchKey>(k)))
|
||||
++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
#if defined(C10_MOBILE_TRIM_DISPATCH_KEYS)
|
||||
// See [Note: Trimmed Mobile Dispatch Keys]
|
||||
constexpr uint16_t num_runtime_entries = 8;
|
||||
#else
|
||||
constexpr uint16_t num_runtime_entries = num_functionality_keys +
|
||||
(numPerBackendFunctionalityKeys() * (num_backends - 1));
|
||||
#endif
|
||||
|
||||
// See Note [No More Than 16 Backends]
|
||||
constexpr uint16_t full_backend_mask =
|
||||
(static_cast<uint16_t>(1) << num_backends) - 1;
|
||||
|
||||
C10_API const char* toString(DispatchKey /*t*/);
|
||||
C10_API const char* toString(BackendComponent /*t*/);
|
||||
C10_API std::ostream& operator<<(std::ostream& /*str*/, DispatchKey /*rhs*/);
|
||||
C10_API std::ostream& operator<<(
|
||||
std::ostream& /*str*/,
|
||||
BackendComponent /*rhs*/);
|
||||
|
||||
C10_API DispatchKey getAutogradKeyFromBackend(BackendComponent k);
|
||||
|
||||
// Parses a string into a dispatch key.
|
||||
// If the string cannot be correctly parsed, throws an exception.
|
||||
C10_API c10::DispatchKey parseDispatchKey(const std::string& k);
|
||||
|
||||
// These are some convenience identifiers for dispatch keys which are
|
||||
// shorter to type than their long counterparts. Note that some of these
|
||||
// dispatch keys directly correspond to DeviceType; and most APIs that
|
||||
// accept DispatchKey also accept DeviceType; e.g.,
|
||||
// torch::dispatch(torch::kCPU, ...) is also valid.
|
||||
constexpr DispatchKey kAutograd = DispatchKey::Autograd;
|
||||
|
||||
// See Note [The Ordering of Per-Backend Dispatch Keys Matters!]
|
||||
// This function relies on the invariant that the dispatch keys between
|
||||
// StartOfDenseBackends and EndOfRuntimeBackendKeys are ordered by backend
|
||||
// in the same order as `BackendComponent`.
|
||||
constexpr BackendComponent toBackendComponent(DispatchKey k) {
|
||||
if (k >= DispatchKey::StartOfDenseBackends &&
|
||||
k <= DispatchKey::EndOfDenseBackends) {
|
||||
return static_cast<BackendComponent>(
|
||||
static_cast<uint8_t>(k) -
|
||||
static_cast<uint8_t>(DispatchKey::StartOfDenseBackends));
|
||||
} else if (
|
||||
k >= DispatchKey::StartOfQuantizedBackends &&
|
||||
k <= DispatchKey::EndOfQuantizedBackends) {
|
||||
return static_cast<BackendComponent>(
|
||||
static_cast<uint8_t>(k) -
|
||||
static_cast<uint8_t>(DispatchKey::StartOfQuantizedBackends));
|
||||
} else if (
|
||||
k >= DispatchKey::StartOfSparseBackends &&
|
||||
k <= DispatchKey::EndOfSparseBackends) {
|
||||
return static_cast<BackendComponent>(
|
||||
static_cast<uint8_t>(k) -
|
||||
static_cast<uint8_t>(DispatchKey::StartOfSparseBackends));
|
||||
} else if (
|
||||
k >= DispatchKey::StartOfSparseCsrBackends &&
|
||||
k <= DispatchKey::EndOfSparseCsrBackends) {
|
||||
return static_cast<BackendComponent>(
|
||||
static_cast<uint8_t>(k) -
|
||||
static_cast<uint8_t>(DispatchKey::StartOfSparseCsrBackends));
|
||||
} else if (
|
||||
k >= DispatchKey::StartOfNestedTensorBackends &&
|
||||
k <= DispatchKey::EndOfNestedTensorBackends) {
|
||||
return static_cast<BackendComponent>(
|
||||
static_cast<uint8_t>(k) -
|
||||
static_cast<uint8_t>(DispatchKey::StartOfNestedTensorBackends));
|
||||
} else if (
|
||||
k >= DispatchKey::StartOfAutogradFunctionalityBackends &&
|
||||
k <= DispatchKey::EndOfAutogradFunctionalityBackends) {
|
||||
return static_cast<BackendComponent>(
|
||||
static_cast<uint8_t>(k) -
|
||||
static_cast<uint8_t>(
|
||||
DispatchKey::StartOfAutogradFunctionalityBackends));
|
||||
} else {
|
||||
return BackendComponent::InvalidBit;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr DispatchKey toFunctionalityKey(DispatchKey k) {
|
||||
if (k <= DispatchKey::EndOfFunctionalityKeys) {
|
||||
return k;
|
||||
} else if (k <= DispatchKey::EndOfDenseBackends) {
|
||||
return DispatchKey::Dense;
|
||||
} else if (k <= DispatchKey::EndOfQuantizedBackends) {
|
||||
return DispatchKey::Quantized;
|
||||
} else if (k <= DispatchKey::EndOfSparseBackends) {
|
||||
return DispatchKey::Sparse;
|
||||
} else if (k <= DispatchKey::EndOfSparseCsrBackends) {
|
||||
return DispatchKey::SparseCsr;
|
||||
} else if (k <= DispatchKey::EndOfNestedTensorBackends) {
|
||||
return DispatchKey::NestedTensor;
|
||||
} else if (k <= DispatchKey::EndOfAutogradFunctionalityBackends) {
|
||||
return DispatchKey::AutogradFunctionality;
|
||||
} else {
|
||||
return DispatchKey::Undefined;
|
||||
}
|
||||
}
|
||||
|
||||
BackendComponent toBackendComponent(DeviceType device_type);
|
||||
|
||||
// Given (DispatchKey::Dense, BackendComponent::CUDABit), returns
|
||||
// DispatchKey::CUDA.
|
||||
// See Note [The Ordering of Per-Backend Dispatch Keys Matters!]
|
||||
// This function relies on the invariant that the dispatch keys between
|
||||
// StartOfDenseBackends and EndOfRuntimeBackendKeys are ordered by backend
|
||||
// in the same order as `BackendComponent`.
|
||||
constexpr DispatchKey toRuntimePerBackendFunctionalityKey(
|
||||
DispatchKey functionality_k,
|
||||
BackendComponent backend_k) {
|
||||
if (functionality_k == DispatchKey::Dense) {
|
||||
return static_cast<DispatchKey>(
|
||||
static_cast<uint8_t>(DispatchKey::StartOfDenseBackends) +
|
||||
static_cast<uint8_t>(backend_k));
|
||||
}
|
||||
if (functionality_k == DispatchKey::Sparse) {
|
||||
return static_cast<DispatchKey>(
|
||||
static_cast<uint8_t>(DispatchKey::StartOfSparseBackends) +
|
||||
static_cast<uint8_t>(backend_k));
|
||||
}
|
||||
if (functionality_k == DispatchKey::SparseCsr) {
|
||||
return static_cast<DispatchKey>(
|
||||
static_cast<uint8_t>(DispatchKey::StartOfSparseCsrBackends) +
|
||||
static_cast<uint8_t>(backend_k));
|
||||
}
|
||||
if (functionality_k == DispatchKey::Quantized) {
|
||||
return static_cast<DispatchKey>(
|
||||
static_cast<uint8_t>(DispatchKey::StartOfQuantizedBackends) +
|
||||
static_cast<uint8_t>(backend_k));
|
||||
}
|
||||
if (functionality_k == DispatchKey::NestedTensor) {
|
||||
return static_cast<DispatchKey>(
|
||||
static_cast<uint8_t>(DispatchKey::StartOfNestedTensorBackends) +
|
||||
static_cast<uint8_t>(backend_k));
|
||||
}
|
||||
if (functionality_k == DispatchKey::AutogradFunctionality) {
|
||||
return static_cast<DispatchKey>(
|
||||
static_cast<uint8_t>(
|
||||
DispatchKey::StartOfAutogradFunctionalityBackends) +
|
||||
static_cast<uint8_t>(backend_k));
|
||||
}
|
||||
return DispatchKey::Undefined;
|
||||
}
|
||||
|
||||
} // namespace c10
|
||||
|
||||
namespace torch {
|
||||
// Expose the constant, but not the TYPE (DispatchKey is an implementation
|
||||
// detail!)
|
||||
// NOLINTNEXTLINE(misc-unused-using-decls)
|
||||
using c10::kAutograd;
|
||||
} // namespace torch
|
||||
|
||||
// NB: You really shouldn't use this instance; this enum is guaranteed
|
||||
// to be pretty small so a regular array should be acceptable.
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<c10::DispatchKey> {
|
||||
typedef size_t result_type;
|
||||
typedef c10::DispatchKey argument_type;
|
||||
|
||||
size_t operator()(c10::DispatchKey x) const noexcept {
|
||||
return static_cast<size_t>(x);
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,975 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
#include <c10/core/DispatchKey.h>
|
||||
#include <c10/macros/Export.h>
|
||||
#include <c10/macros/Macros.h>
|
||||
#include <c10/util/Exception.h>
|
||||
#include <c10/util/Metaprogramming.h>
|
||||
#include <c10/util/TypeList.h>
|
||||
#include <c10/util/llvmMathExtras.h>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <initializer_list>
|
||||
#include <iterator>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
C10_DIAGNOSTIC_PUSH_AND_IGNORED_IF_DEFINED("-Wswitch-enum")
|
||||
|
||||
namespace c10 {
|
||||
|
||||
struct FunctionalityOffsetAndMask {
|
||||
// empty constructor shouldn't be used; only needed to initialize
|
||||
// the array before populating it.
|
||||
FunctionalityOffsetAndMask() = default;
|
||||
FunctionalityOffsetAndMask(uint16_t offset, uint16_t mask)
|
||||
: offset(offset), mask(mask) {}
|
||||
// This needs to big enough to cover the size of the operator table.
|
||||
uint16_t offset{};
|
||||
// See Note [No More Than 16 Backends]
|
||||
// This mask needs to be big enough to mask all of the backend bits.
|
||||
// We probably don't ever want to have more than 16 backend bits, so uint16_t
|
||||
// should be enough.
|
||||
uint16_t mask{};
|
||||
};
|
||||
static_assert(
|
||||
c10::num_runtime_entries < 65536,
|
||||
"The dispatcher currently only supports up to 2^16 runtime entries");
|
||||
|
||||
C10_API std::array<FunctionalityOffsetAndMask, num_functionality_keys>
|
||||
initializeFunctionalityOffsetsAndMasks();
|
||||
|
||||
C10_ALWAYS_INLINE static const std::
|
||||
array<FunctionalityOffsetAndMask, num_functionality_keys>&
|
||||
offsetsAndMasks() {
|
||||
static auto offsets_and_masks_ = initializeFunctionalityOffsetsAndMasks();
|
||||
return offsets_and_masks_;
|
||||
}
|
||||
|
||||
// A representation of a set of DispatchKeys. A DispatchKeySet contains both
|
||||
// "functionality" bits and "backend bits", and every tensor holds its own
|
||||
// DispatchKeySet. The Dispatcher implements multiple dispatch by grabbing the
|
||||
// keyset on every input tensor, or’ing them together, and dispatching to a
|
||||
// specific piece of functionality. The functionality bits are *ordered*. When
|
||||
// multiple functionality bits are set, we use the highest priority
|
||||
// functionality. Similarly, multiple backend bits can theoretically be set if
|
||||
// you call an operator with multiple tensors from difference devices (e.g. CPU
|
||||
// and CUDA), although support for mixed device dispatch is limited (the only
|
||||
// kernels that gracefully handle mixed device inputs for now are cuda kernels
|
||||
// that take in a scalar cpu tensor).
|
||||
|
||||
// A representation of a set of DispatchKeys. A tensor may have multiple
|
||||
// tensor type ids, e.g., a Variable tensor can also be a CPU tensor; the
|
||||
// DispatchKeySet specifies what type ids apply. The internal representation is
|
||||
// as a 64-bit bit set (this means only 64 tensor type ids are supported).
|
||||
//
|
||||
// As mentioned above, DispatchKeys are ordered; thus, we can ask questions like
|
||||
// "what is the highest priority DispatchKey in the set"? (The set itself is
|
||||
// not ordered; two sets with the same ids will always have the ids ordered in
|
||||
// the same way.)
|
||||
//
|
||||
// Note [DispatchKeySet Internal Representation]
|
||||
// Internally, dispatch keys are packed into 64-bit DispatchKeySet objects
|
||||
// that get passed around at runtime.
|
||||
// However, there isn't necessarily a 1-to-1 mapping between bits in the keyset
|
||||
// and individual dispatch keys.
|
||||
//
|
||||
// First: why do we have this distinction, and why not map every dispatch key
|
||||
// directly to a bit? This is mostly because we have several types of
|
||||
// functionalities that different backends would like to customize. For example,
|
||||
// we have:
|
||||
// - "Dense": CPU, CUDA, XLA, ... (~12 keys)
|
||||
// - "Sparse": SparseCPU, SparseCUDA, ...
|
||||
// - "SparseCsr": SparseCsrCPU, SparseCsrCUDA, ...
|
||||
// - "Quantized": QuantizedCPU, QuantizedCUDA, QuantizedXLA, ...
|
||||
// - "Autograd": AutogradCPU, AutogradCUDA, Autograd XLA, ...
|
||||
// The problem is that total number of keys grows quadratically with [#
|
||||
// backends] x [# functionalities], making it very difficult to map each key
|
||||
// directly to a bit in a bitset without dramatically increasing the size of the
|
||||
// bitset over time.
|
||||
//
|
||||
// The two enums (BackendComponent and DispatchKey) can be divided roughly into
|
||||
// 5 categories.
|
||||
//
|
||||
// (1) "Building block" keys
|
||||
// (a) backends: Everything in the BackendComponent enum (e.g. CPUBit,
|
||||
// CUDABit) (b) functionalities: (per-backend) functionality-bit DispatchKeys
|
||||
// (e.g. AutogradFunctionality, SparseCsr, Sparse, Dense)
|
||||
// (2) "Runtime" keys
|
||||
// (a) "non-customizable backends" (e.g. FPGA)
|
||||
// (b) "non-customizable functionalities" (e.g. Functionalize)
|
||||
// (c) "per-backend instances of customizable functionalities" (e.g. CPU,
|
||||
// SparseCPU, AutogradCPU)
|
||||
// (3) "Alias" DispatchKeys (see Note [Alias Dispatch Keys])
|
||||
//
|
||||
// (1) Building block keys always correspond to individual bits in a
|
||||
// DispatchKeySet. They can also be combined in a DispatchKeySet to form actual
|
||||
// runtime keys. e.g.
|
||||
// auto dense_cpu_ks = DispatchKeySet({DispatchKey::CPUBit,
|
||||
// DispatchKey::Dense});
|
||||
// // The keyset has the runtime dense-cpu key.
|
||||
// dense_cpu_ks.has(DispatchKey::CPU);
|
||||
// // And it contains the building block keys too.
|
||||
// dense_cpu_ks.has(DispatchKey::CPUBit);
|
||||
// dense_cpu_ks.has(DispatchKey::Dense);
|
||||
//
|
||||
// Not every backend and not every functionality counts as a "building block
|
||||
// key". This is mostly to give us more levers to pull in the design space.
|
||||
// Backend keys and functionality keys that count as "building blocks" will
|
||||
// contribute to a full cross product of functionality that can be overridden.
|
||||
//
|
||||
// For example, right now we have at least 12 "backend" building
|
||||
// blocks (CPU, CUDA, XLA, ...) and at least 5 "functionality"
|
||||
// building blocks (Dense, Sparse, SparseCsr, Quantized,
|
||||
// AutogradFunctionality, ...). These keys together allow every
|
||||
// dispatcher operator to be customized in up to 12*4 different
|
||||
// ways. Each of those requires a slot in the operator table of every
|
||||
// dispatcher operator. Not every piece of functionality necessarily
|
||||
// needs to be customizable per-backend, and not every backend
|
||||
// necessarily needs to be able to customize every type of
|
||||
// functionality.
|
||||
//
|
||||
//
|
||||
// (2) Every runtime key corresponds directly to a slot in an operator's runtime
|
||||
// dispatch table, and you can directly register kernels to a runtime dispatch
|
||||
// key.
|
||||
//
|
||||
// For per-backend functionalities like "Dense" or "AutogradFunctionality",
|
||||
// you can think of the corresponding runtime dispatch keys as "instances" of
|
||||
// that functionality, per backend. E.g. "CPU", "CUDA", "XLA", etc. are all
|
||||
// runtime instances of the "Dense" building block key.
|
||||
|
||||
// (2a) and (2b) are represented identically in the DispatchKeySet logic:
|
||||
// - backend-agnostic functionalities (e.g. FuncTorchBatched) are NOT
|
||||
// customizable per backend.
|
||||
// In order to do so, we'd need to promote it to a per-backend functionality
|
||||
// "building block" key.
|
||||
// - non-customizable backends (e.g. FPGA) can NOT customize existing
|
||||
// functionality like Sparse, Autograd, etc.
|
||||
// In order to do so, we'd need to promote it to a backend "building block"
|
||||
// key.
|
||||
//
|
||||
// In both cases, these keys directly correspond to runtime slots in the
|
||||
// operator table.
|
||||
//
|
||||
//
|
||||
// (3) "Alias" keys
|
||||
// See Note [Alias Dispatch Keys]
|
||||
//
|
||||
// Final note: for anyone making future changes to the Dispatcher +
|
||||
// DispatchKeySet internals, there's a closed PR with a basic
|
||||
// python-implementation of the Dispatcher that might be useful in quickly
|
||||
// testing out and validating changes. See it at
|
||||
// https://github.com/pytorch/pytorch/pull/68743
|
||||
|
||||
// An undefined tensor is one with an empty tensor type set.
|
||||
class DispatchKeySet final {
|
||||
public:
|
||||
enum Full { FULL };
|
||||
enum FullAfter { FULL_AFTER };
|
||||
enum Raw { RAW };
|
||||
|
||||
// NB: default constructor representation as zero is MANDATORY as
|
||||
// use of DispatchKeySet in TLS requires this.
|
||||
constexpr DispatchKeySet() = default;
|
||||
|
||||
constexpr DispatchKeySet(Full /*unused*/)
|
||||
: repr_((1ULL << (num_backends + num_functionality_keys - 1)) - 1) {}
|
||||
|
||||
constexpr DispatchKeySet(FullAfter /*unused*/, DispatchKey t)
|
||||
// LSB after t are OK, but not t itself.
|
||||
// "functionalities" have a notion of ordering (e.g. Autograd > Sparse >
|
||||
// Quantized > Dense). But backends don't really have an ordering.
|
||||
// Therefore, we're enforcing that FullAfter can only be used on
|
||||
// "functionality" keys.
|
||||
: repr_(
|
||||
(1ULL
|
||||
<< (num_backends + static_cast<uint8_t>(toFunctionalityKey(t)) -
|
||||
1)) -
|
||||
1) {
|
||||
*this = add(DispatchKey::PythonDispatcher);
|
||||
}
|
||||
|
||||
// Public version of DispatchKeySet(uint64_t) API; external users
|
||||
// must be explicit when they do this!
|
||||
constexpr DispatchKeySet(Raw /*unused*/, uint64_t x) : repr_(x) {}
|
||||
|
||||
constexpr explicit DispatchKeySet(BackendComponent k) {
|
||||
if (k == BackendComponent::InvalidBit) {
|
||||
repr_ = 0;
|
||||
} else {
|
||||
repr_ = 1ULL << (static_cast<uint8_t>(k) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr explicit DispatchKeySet(DispatchKey k) {
|
||||
// NOLINTNEXTLINE(bugprone-branch-clone)
|
||||
if (k == DispatchKey::Undefined) {
|
||||
// Case 1: handle Undefined specifically
|
||||
repr_ = 0;
|
||||
} else if (k <= DispatchKey::EndOfFunctionalityKeys) {
|
||||
// Case 2: handle "functionality-only" keys
|
||||
// These keys have a functionality bit set, but no backend bits
|
||||
// These can technically be either:
|
||||
// - valid runtime keys (e.g. DispatchKey::AutogradOther,
|
||||
// DispatchKey::FuncTorchBatched, etc)
|
||||
// - "building block" keys that aren't actual runtime keys (e.g.
|
||||
// DispatchKey::Dense or Sparse)
|
||||
uint64_t functionality_val = 1ULL
|
||||
<< (num_backends + static_cast<uint8_t>(k) - 1);
|
||||
repr_ = functionality_val;
|
||||
} else if (k <= DispatchKey::EndOfRuntimeBackendKeys) {
|
||||
// Case 3: "runtime" keys that have a functionality bit AND a backend bit.
|
||||
// First compute which bit to flip for the functionality.
|
||||
auto functionality_k = toFunctionalityKey(k);
|
||||
// The - 1 is because Undefined is technically a "functionality" that
|
||||
// doesn't show up in the bitset. So e.g. Dense is technically the second
|
||||
// functionality, but the lowest functionality bit.
|
||||
uint64_t functionality_val = 1ULL
|
||||
<< (num_backends + static_cast<uint8_t>(functionality_k) - 1);
|
||||
|
||||
// then compute which bit to flip for the backend
|
||||
// Case 4a: handle the runtime instances of "per-backend functionality"
|
||||
// keys For example, given DispatchKey::CPU, we should set:
|
||||
// - the Dense functionality bit
|
||||
// - the CPUBit backend bit
|
||||
// first compute which bit to flip for the backend
|
||||
auto backend_k = toBackendComponent(k);
|
||||
uint64_t backend_val = backend_k == BackendComponent::InvalidBit
|
||||
? 0
|
||||
: 1ULL << (static_cast<uint8_t>(backend_k) - 1);
|
||||
repr_ = functionality_val + backend_val;
|
||||
} else {
|
||||
// At this point, we should have covered every case except for alias keys.
|
||||
// Technically it would be possible to add alias dispatch keys to a
|
||||
// DispatchKeySet, but the semantics are a little confusing and this
|
||||
// currently isn't needed anywhere.
|
||||
repr_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr uint64_t keys_to_repr(std::initializer_list<DispatchKey> ks) {
|
||||
uint64_t repr = 0;
|
||||
for (auto k : ks) {
|
||||
repr |= DispatchKeySet(k).repr_;
|
||||
}
|
||||
return repr;
|
||||
}
|
||||
|
||||
constexpr uint64_t backend_bits_to_repr(
|
||||
std::initializer_list<BackendComponent> ks) {
|
||||
uint64_t repr = 0;
|
||||
for (auto k : ks) {
|
||||
repr |= DispatchKeySet(k).repr_;
|
||||
}
|
||||
return repr;
|
||||
}
|
||||
|
||||
explicit constexpr DispatchKeySet(std::initializer_list<DispatchKey> ks)
|
||||
: repr_(keys_to_repr(ks)) {}
|
||||
|
||||
explicit constexpr DispatchKeySet(std::initializer_list<BackendComponent> ks)
|
||||
// Note: for some reason, putting this logic directly in the constructor
|
||||
// appears to fail to compile on CUDA 10.1.
|
||||
// See an example internal failure at
|
||||
// https://www.internalfb.com/intern/skycastle/run/76561193669136035/artifact/actionlog.76561193742069401.stderr
|
||||
: repr_(backend_bits_to_repr(ks)) {}
|
||||
|
||||
// Test if a DispatchKey is in the set
|
||||
inline bool has(DispatchKey t) const {
|
||||
TORCH_INTERNAL_ASSERT_DEBUG_ONLY(t != DispatchKey::Undefined);
|
||||
return has_all(DispatchKeySet(t));
|
||||
}
|
||||
constexpr bool has_backend(BackendComponent t) const {
|
||||
return has_all(DispatchKeySet(t));
|
||||
}
|
||||
|
||||
// Test if a DispatchKey is in the set
|
||||
// Given a DispatchKeySet of functionality keys and (potentially) backend
|
||||
// keys, tests if all of them are in the current set.
|
||||
constexpr bool has_all(DispatchKeySet ks) const {
|
||||
return static_cast<bool>((repr_ & ks.repr_) == ks.repr_);
|
||||
}
|
||||
|
||||
// Given a DispatchKeySet of functionality keys and (potentially) backend
|
||||
// keys, tests if any of them are in the current set. This could technically
|
||||
// be pretty easily implemented using has(). It is strictly a perf
|
||||
// optimization though. There are many places in the code base where we want
|
||||
// to test for multiple functionality keys together. HOWEVER, runtime
|
||||
// per-backend functionality keys aren't allowed to be used with this
|
||||
// function, because you can end up with weird results. e.g.
|
||||
// DispatchKeySet(DispatchKey::AutogradCPU).has_any(DispatchKeySet(DispatchKey::CPU))
|
||||
// would return true.
|
||||
inline bool has_any(DispatchKeySet ks) const {
|
||||
TORCH_INTERNAL_ASSERT_DEBUG_ONLY(
|
||||
// Either there are no backend bits in the input keyset
|
||||
((ks.repr_ & full_backend_mask) == 0) ||
|
||||
// or there are no per-backend-functionality bits
|
||||
// See [Note: Per-Backend Functionality Dispatch Keys]
|
||||
((ks &
|
||||
DispatchKeySet({
|
||||
DispatchKey::Dense,
|
||||
DispatchKey::Quantized,
|
||||
DispatchKey::Sparse,
|
||||
DispatchKey::SparseCsr,
|
||||
DispatchKey::AutogradFunctionality,
|
||||
})
|
||||
.repr_) == 0));
|
||||
return static_cast<bool>((repr_ & ks.repr_) != 0);
|
||||
}
|
||||
// Test if DispatchKeySet is a superset of ks.
|
||||
bool isSupersetOf(DispatchKeySet ks) const {
|
||||
return (repr_ & ks.repr_) == ks.repr_;
|
||||
}
|
||||
// Perform set union
|
||||
constexpr DispatchKeySet operator|(DispatchKeySet other) const {
|
||||
return DispatchKeySet(repr_ | other.repr_);
|
||||
}
|
||||
// Perform set intersection
|
||||
constexpr DispatchKeySet operator&(DispatchKeySet other) const {
|
||||
return DispatchKeySet(repr_ & other.repr_);
|
||||
}
|
||||
// Compute the set difference self - other,
|
||||
// but ONLY for the functionality keys.
|
||||
// Any backend bits set on self will remain unchanged.
|
||||
// See Note [Removing keys from DispatchKeySet Only Affects Functionality
|
||||
// Keys]
|
||||
constexpr DispatchKeySet operator-(DispatchKeySet other) const {
|
||||
return DispatchKeySet(repr_ & (full_backend_mask | ~other.repr_));
|
||||
}
|
||||
|
||||
// Compute self ^ other
|
||||
constexpr DispatchKeySet operator^(DispatchKeySet other) const {
|
||||
return DispatchKeySet(repr_ ^ other.repr_);
|
||||
}
|
||||
bool operator==(DispatchKeySet other) const {
|
||||
return repr_ == other.repr_;
|
||||
}
|
||||
bool operator!=(DispatchKeySet other) const {
|
||||
return repr_ != other.repr_;
|
||||
}
|
||||
// Add a DispatchKey to the DispatchKey set. Does NOT mutate,
|
||||
// returns the extended DispatchKeySet!
|
||||
[[nodiscard]] constexpr DispatchKeySet add(DispatchKey t) const {
|
||||
return *this | DispatchKeySet(t);
|
||||
}
|
||||
[[nodiscard]] constexpr DispatchKeySet add(DispatchKeySet ks) const {
|
||||
return *this | ks;
|
||||
}
|
||||
|
||||
// Remove a DispatchKey from the DispatchKey set.
|
||||
// This is generally not an operation you should be doing
|
||||
// (it's used to implement the printing overload, operator<<)
|
||||
//
|
||||
// Note [Removing keys from DispatchKeySet Only Affects Functionality Keys]
|
||||
// Only functionality bits are allowed to be removed from a keyset.
|
||||
// For now, we're only allowing removal of "functionality bits" from the
|
||||
// keyset, which is specifically needed by the fallthrough key calculation
|
||||
// logic. Why is removing backend bits problematic? Consider this example:
|
||||
//
|
||||
// DispatchKeySet([DispatchKey.CPU, DispatchKey.AutogradCUDA,
|
||||
// DispatchKey.CUDA]).remove(DispatchKey.AutogradCUDA)
|
||||
// DispatchKeySet([DispatchKey.CPU,
|
||||
// DispatchKey.AutogradCUDA]).remove(DispatchKey.AutogradCUDA)
|
||||
//
|
||||
// What do we want to happen?
|
||||
// Technically, we'd like it to be true that after removal,
|
||||
// the first keyset still has the CUDA dispatch key while the second doesn't.
|
||||
// Unfortunately there's no way to represent that, because the two keysets are
|
||||
// represented the same way internally: functionality bits: Autograd, Dense
|
||||
// backend bits: CPU, CUDA
|
||||
//
|
||||
// Instead, remove(DispatchKey.AutogradCPU) will only remove the "Autograd"
|
||||
// bit from the bitset.
|
||||
[[nodiscard]] constexpr DispatchKeySet remove(DispatchKey t) const {
|
||||
return DispatchKeySet(
|
||||
repr_ & ~(DispatchKeySet(t).repr_ & ~full_backend_mask));
|
||||
}
|
||||
// You're allowed to remove a backend bit from a DispatchKeySet,
|
||||
// but you have to be explicit about it (remove_backend() instead of
|
||||
// remove()).
|
||||
constexpr DispatchKeySet remove_backend(BackendComponent b) const {
|
||||
return DispatchKeySet(repr_ & ~(DispatchKeySet(b).repr_));
|
||||
}
|
||||
// Is the set empty? (AKA undefined tensor)
|
||||
bool empty() const {
|
||||
return repr_ == 0;
|
||||
}
|
||||
uint64_t raw_repr() const {
|
||||
return repr_;
|
||||
}
|
||||
|
||||
static DispatchKeySet from_raw_repr(uint64_t x) {
|
||||
return DispatchKeySet(RAW, x);
|
||||
}
|
||||
|
||||
DispatchKey highestFunctionalityKey() const {
|
||||
auto functionality_idx = indexOfHighestBit();
|
||||
// This means that none of the functionality bits were set.
|
||||
if (functionality_idx < num_backends)
|
||||
return DispatchKey::Undefined;
|
||||
// The first num_backend bits in the keyset don't correspond to real
|
||||
// dispatch keys.
|
||||
return static_cast<DispatchKey>(functionality_idx - num_backends);
|
||||
}
|
||||
|
||||
// This is similar like toBackendComponent(DispatchKey), but less restrictive.
|
||||
// toBackendComponent() errors out if the key that it was passed has no
|
||||
// backend bits, which is useful for error checking. We need a version of that
|
||||
// here that can also handle "fake" backends like FPGA, because they need to
|
||||
// map to the AutogradOther key. For those backends, we return
|
||||
// BackendComponent::InvalidBit.
|
||||
BackendComponent highestBackendKey() const {
|
||||
// mask to mask out functionality bits
|
||||
auto backend_idx =
|
||||
DispatchKeySet(repr_ & full_backend_mask).indexOfHighestBit();
|
||||
// all zeros across the backend bits means that no backend bits are set.
|
||||
if (backend_idx == 0)
|
||||
return BackendComponent::InvalidBit;
|
||||
return static_cast<BackendComponent>(backend_idx);
|
||||
}
|
||||
|
||||
// returns the DispatchKey of highest priority in the set.
|
||||
DispatchKey highestPriorityTypeId() const {
|
||||
auto functionality_k = highestFunctionalityKey();
|
||||
if (isPerBackendFunctionalityKey(functionality_k)) {
|
||||
return toRuntimePerBackendFunctionalityKey(
|
||||
functionality_k, highestBackendKey());
|
||||
}
|
||||
return functionality_k;
|
||||
}
|
||||
|
||||
// Returns the index of the most-significant bit in the keyset.
|
||||
// This is used to as part of the calculation into the operator table to get:
|
||||
// - the highest "functionality" bit in the keyset.
|
||||
// - the highest "backend" bit in the keyset.
|
||||
uint8_t indexOfHighestBit() const {
|
||||
return 64 - llvm::countLeadingZeros(repr_);
|
||||
}
|
||||
|
||||
#if defined(C10_MOBILE_TRIM_DISPATCH_KEYS)
|
||||
// [Note: Trimmed Mobile Dispatch Keys]
|
||||
/**
|
||||
* The method below maps the dispatch key in the enum DispatchKey to an
|
||||
* integer index in the dispatchTable_ array in OperatorEntry. The array
|
||||
* is trimmed for mobile to reduce peak memory usage since it's
|
||||
* unnecessary to reserve additional space for dispatch keys that will
|
||||
* never be used on mobile.
|
||||
*/
|
||||
int getDispatchTableIndexForDispatchKeySet() const {
|
||||
auto dk = highestPriorityTypeId();
|
||||
switch (dk) {
|
||||
case DispatchKey::Undefined:
|
||||
return 0;
|
||||
case DispatchKey::CPU:
|
||||
return 1;
|
||||
case DispatchKey::QuantizedCPU:
|
||||
return 2;
|
||||
case DispatchKey::SparseCPU:
|
||||
return 3;
|
||||
case DispatchKey::BackendSelect:
|
||||
return 4;
|
||||
case DispatchKey::ADInplaceOrView:
|
||||
return 5;
|
||||
case DispatchKey::AutogradOther:
|
||||
return 6;
|
||||
case DispatchKey::AutogradCPU:
|
||||
return 7;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
#else
|
||||
// returns the index in the operator table of highest priority key in the the
|
||||
// keyset Note that we could in theory implement this using
|
||||
// highestPriorityTypeId(), but this code is very hotpath and we can do it
|
||||
// faster without it.
|
||||
int getDispatchTableIndexForDispatchKeySet() const {
|
||||
auto functionality_idx =
|
||||
DispatchKeySet(repr_ >> num_backends).indexOfHighestBit();
|
||||
auto offset_and_mask = offsetsAndMasks()[functionality_idx];
|
||||
// Mask the functionality bits out first, then right-shift by 1.
|
||||
// right-shifting by 1 because everything is zero-indexed.
|
||||
// E.g. 000001 (CPU) should give us an offset of 0, 000010 (CUDA) should
|
||||
// give us an offset of 1, etc.
|
||||
auto backend_idx =
|
||||
DispatchKeySet((repr_ & offset_and_mask.mask) >> 1).indexOfHighestBit();
|
||||
return offset_and_mask.offset + backend_idx;
|
||||
}
|
||||
#endif
|
||||
|
||||
// returns the "index" of the highest priority backend in the keyset.
|
||||
// This is pretty similar to getBackendKey(), but:
|
||||
// - It's hotpath code (part of the runtime bitset calculation)
|
||||
// - I's returns an integer index, not an enum value
|
||||
// - Everything is shifted to the right by 1.
|
||||
// BackendComponent::InvalidBit is technically the lowest enum value,
|
||||
// but it isn't included in the runtime table. So CPUBit = 1, CUDABit = 2,
|
||||
// etc.
|
||||
uint64_t getBackendIndex() const {
|
||||
return DispatchKeySet((repr_ & full_backend_mask) >> 1).indexOfHighestBit();
|
||||
}
|
||||
|
||||
private:
|
||||
constexpr DispatchKeySet(uint64_t repr) : repr_(repr) {}
|
||||
uint64_t repr_ = 0;
|
||||
|
||||
public:
|
||||
// STL iterator for DispatchKeySet. Iterates through all runtime DispatchKeys
|
||||
// in the set. The iterator is only invalidated by the destruction of the
|
||||
// underlying DispatchKeySet as the iterator stores a pointer to the raw
|
||||
// representation of the DispatchKeySet. Note: When we encounter a per-backend
|
||||
// functionality (e.g. Dense or Sparse), we will iterate through EVERY backend
|
||||
// in the keyset, for that functionality. For example, if the next
|
||||
// functionality key to iterate over is Autograd, and the backend bits in the
|
||||
// keyset correspond to [BackendComponent::CPUBit, BackendComponent::CUDABit],
|
||||
// then the next two keys we return will be DispatchKey::AutogradCPU,
|
||||
// DispatchKey::AutogradCUDA (CPU first because it has lower precedence than
|
||||
// CUDA in DispatchKey.h).
|
||||
class iterator {
|
||||
public:
|
||||
using self_type = iterator;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
using value_type = DispatchKey;
|
||||
using difference_type = ptrdiff_t;
|
||||
using reference = value_type&;
|
||||
using pointer = value_type*;
|
||||
// final mask value should mask out the entire keyset
|
||||
static constexpr uint8_t end_iter_mask_val =
|
||||
num_backends + num_functionality_keys;
|
||||
// final key value should be the last DispatchKey
|
||||
static constexpr uint8_t end_iter_key_val = num_functionality_keys;
|
||||
|
||||
// current_dispatchkey_idx_ will iterate through all functionality bits.
|
||||
// current_backendcomponent_idx_ will iterate through all backend bits.
|
||||
explicit iterator(
|
||||
const uint64_t* data_ptr,
|
||||
uint8_t next_functionality = num_backends,
|
||||
uint8_t next_backend = 0)
|
||||
: data_ptr_(data_ptr),
|
||||
next_functionality_(next_functionality),
|
||||
next_backend_(next_backend) {
|
||||
// Go to the first key in the set
|
||||
TORCH_INTERNAL_ASSERT(
|
||||
next_functionality_ >= num_backends,
|
||||
"num_backends=",
|
||||
static_cast<uint32_t>(num_backends),
|
||||
"next_functionality_=",
|
||||
static_cast<uint32_t>(next_functionality_));
|
||||
++(*this);
|
||||
}
|
||||
|
||||
C10_API self_type& operator++();
|
||||
|
||||
self_type operator++(int) {
|
||||
self_type previous_iterator = *this;
|
||||
++(*this);
|
||||
return previous_iterator;
|
||||
}
|
||||
|
||||
bool operator==(const self_type& rhs) const {
|
||||
return next_functionality_ == rhs.next_functionality_ &&
|
||||
current_dispatchkey_idx_ == rhs.current_dispatchkey_idx_ &&
|
||||
next_backend_ == rhs.next_backend_ &&
|
||||
current_backendcomponent_idx_ == rhs.current_backendcomponent_idx_;
|
||||
}
|
||||
bool operator!=(const self_type& rhs) const {
|
||||
return next_functionality_ != rhs.next_functionality_ ||
|
||||
current_dispatchkey_idx_ != rhs.current_dispatchkey_idx_ ||
|
||||
next_backend_ != rhs.next_backend_ ||
|
||||
current_backendcomponent_idx_ != rhs.current_backendcomponent_idx_;
|
||||
}
|
||||
DispatchKey operator*() const {
|
||||
auto functionality_key =
|
||||
static_cast<DispatchKey>(current_dispatchkey_idx_);
|
||||
if (isPerBackendFunctionalityKey(functionality_key)) {
|
||||
auto next_key = toRuntimePerBackendFunctionalityKey(
|
||||
functionality_key,
|
||||
static_cast<BackendComponent>(current_backendcomponent_idx_));
|
||||
// We expect all of the Dense, Sparse, Quantized, and Autograd keys to
|
||||
// be ordered the same way with respect to their backends
|
||||
TORCH_INTERNAL_ASSERT(
|
||||
toBackendComponent(next_key) ==
|
||||
static_cast<BackendComponent>(current_backendcomponent_idx_),
|
||||
"Tried to map functionality key ",
|
||||
toString(functionality_key),
|
||||
" and backend bit ",
|
||||
toString(
|
||||
static_cast<BackendComponent>(current_backendcomponent_idx_)),
|
||||
" to a runtime key, but ended up with ",
|
||||
toString(next_key),
|
||||
". This can happen if the order of the backend dispatch keys in DispatchKey.h isn't consistent.",
|
||||
" Please double check that enum for inconsistencies.");
|
||||
return next_key;
|
||||
} else {
|
||||
return functionality_key;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const uint64_t* data_ptr_;
|
||||
uint8_t next_functionality_;
|
||||
uint8_t next_backend_;
|
||||
// These are in an invalid state at construction time, and set by the
|
||||
// first increment call
|
||||
uint8_t current_dispatchkey_idx_{end_iter_key_val};
|
||||
uint8_t current_backendcomponent_idx_{end_iter_key_val};
|
||||
};
|
||||
|
||||
public:
|
||||
// Returns iterator to the first key in the set. If no keys are in the
|
||||
// set, then will return the end iterator.
|
||||
iterator begin() const {
|
||||
return iterator(&repr_);
|
||||
}
|
||||
|
||||
// We do not need to iterate beyond EndOfFunctionalityKeys so we will treat
|
||||
// this as the end iterator.
|
||||
iterator end() const {
|
||||
return iterator(&repr_, iterator::end_iter_mask_val);
|
||||
}
|
||||
};
|
||||
|
||||
C10_API std::string toString(DispatchKeySet /*ts*/);
|
||||
C10_API std::ostream& operator<<(std::ostream& /*os*/, DispatchKeySet /*ts*/);
|
||||
|
||||
inline int getDispatchTableIndexForDispatchKey(DispatchKey k) {
|
||||
return DispatchKeySet(k).getDispatchTableIndexForDispatchKeySet();
|
||||
}
|
||||
|
||||
// Alias key DispatchKey::Autograd maps to
|
||||
// (autograd_dispatch_keyset x full_backend_mask)
|
||||
// NB: keys in this set also get associated with CompositeImplicitAutograd
|
||||
//
|
||||
// Note [autograd_dispatch_keyset Does Not Include Backend Bits]
|
||||
// We don't want to include any backend bits (BackendComponent::CPUBit, etc)
|
||||
// directly in autograd_dispatch_keyset.
|
||||
// Why? keysets like autograd_dispatch_keyset are commonly used to remove
|
||||
// autograd keys from a DispatchKeySet throughout the code base. However, you
|
||||
// are only allowed to remove functionality bits from a keyset, not backend
|
||||
// bits. See Note [Removing keys from DispatchKeySet Only Affects Functionality
|
||||
// Keys] for details. To be consistent and avoid confusion, we're explicitly
|
||||
// setting up autograd_dispatch_keyset to not have any backend bits.
|
||||
constexpr DispatchKeySet autograd_dispatch_keyset = DispatchKeySet({
|
||||
DispatchKey::AutogradFunctionality,
|
||||
DispatchKey::AutogradOther,
|
||||
DispatchKey::AutogradNestedTensor,
|
||||
});
|
||||
|
||||
constexpr DispatchKeySet autocast_dispatch_keyset = DispatchKeySet({
|
||||
DispatchKey::AutocastCPU,
|
||||
DispatchKey::AutocastMPS,
|
||||
DispatchKey::AutocastCUDA,
|
||||
DispatchKey::AutocastXPU,
|
||||
DispatchKey::AutocastIPU,
|
||||
DispatchKey::AutocastHPU,
|
||||
DispatchKey::AutocastXLA,
|
||||
DispatchKey::AutocastPrivateUse1,
|
||||
DispatchKey::AutocastMTIA,
|
||||
DispatchKey::AutocastMAIA,
|
||||
});
|
||||
|
||||
// See Note [TLS Initialization]
|
||||
constexpr DispatchKeySet default_included_set = DispatchKeySet({
|
||||
DispatchKey::BackendSelect,
|
||||
DispatchKey::ADInplaceOrView,
|
||||
});
|
||||
|
||||
constexpr DispatchKeySet default_excluded_set = DispatchKeySet({
|
||||
DispatchKey::AutocastCPU,
|
||||
DispatchKey::AutocastMPS,
|
||||
DispatchKey::AutocastCUDA,
|
||||
DispatchKey::AutocastXPU,
|
||||
DispatchKey::AutocastIPU,
|
||||
DispatchKey::AutocastHPU,
|
||||
DispatchKey::AutocastXLA,
|
||||
DispatchKey::AutocastPrivateUse1,
|
||||
DispatchKey::AutocastMTIA,
|
||||
DispatchKey::AutocastMAIA,
|
||||
});
|
||||
|
||||
constexpr DispatchKeySet autograd_dispatch_keyset_with_ADInplaceOrView =
|
||||
autograd_dispatch_keyset | DispatchKeySet(DispatchKey::ADInplaceOrView);
|
||||
|
||||
constexpr DispatchKeySet python_ks = DispatchKeySet({
|
||||
DispatchKey::Python,
|
||||
DispatchKey::PythonTLSSnapshot,
|
||||
});
|
||||
|
||||
constexpr DispatchKeySet sparse_ks = DispatchKeySet(DispatchKey::Sparse);
|
||||
|
||||
constexpr DispatchKeySet sparse_csr_ks = DispatchKeySet(DispatchKey::SparseCsr);
|
||||
|
||||
constexpr DispatchKeySet mkldnn_ks = DispatchKeySet(DispatchKey::MkldnnCPU);
|
||||
|
||||
// backend dispatch keys that map to DispatchKey::AutogradOther
|
||||
// NB: keys in this set also get associated with CompositeImplicitAutograd
|
||||
constexpr DispatchKeySet autogradother_backends =
|
||||
DispatchKeySet(
|
||||
// HIP and VE aren't in this list: they now have their own backend bits
|
||||
// which means that they can now have their own Autograd keys.
|
||||
// Technically, HIP will now redispatch to its own custom AutogradHIP
|
||||
// slot in the runtime table.
|
||||
{DispatchKey::FPGA,
|
||||
DispatchKey::Vulkan,
|
||||
DispatchKey::Metal,
|
||||
DispatchKey::CustomRNGKeyId,
|
||||
DispatchKey::MkldnnCPU,
|
||||
// Sparse and Quantized backends also live here.
|
||||
DispatchKey::Sparse,
|
||||
DispatchKey::SparseCsr,
|
||||
DispatchKey::Quantized})
|
||||
// Including the backend bits because this keyset is used during op
|
||||
// registration, which requires looping over all runtime autogradother
|
||||
// backend keys.
|
||||
| DispatchKeySet(DispatchKeySet::RAW, full_backend_mask);
|
||||
|
||||
// The set of dispatch keys that come after autograd
|
||||
// n.b. this relies on the fact that AutogradOther is currently the lowest
|
||||
// Autograd key
|
||||
constexpr DispatchKeySet after_autograd_keyset =
|
||||
DispatchKeySet(DispatchKeySet::FULL_AFTER, c10::DispatchKey::AutogradOther);
|
||||
|
||||
// The set of dispatch keys that come after ADInplaceOrView
|
||||
constexpr DispatchKeySet after_ADInplaceOrView_keyset = DispatchKeySet(
|
||||
DispatchKeySet::FULL_AFTER,
|
||||
c10::DispatchKey::ADInplaceOrView);
|
||||
|
||||
// The set of dispatch keys that come after Functionalize
|
||||
constexpr DispatchKeySet after_func_keyset =
|
||||
DispatchKeySet(DispatchKeySet::FULL_AFTER, c10::DispatchKey::Functionalize)
|
||||
.remove(
|
||||
// NOTE: we also need to remove ADInplaceOrView from the keyset when
|
||||
// redispatching after the func kernels. This is because we're not
|
||||
// calling the same op; we originally called an inplace op, and now
|
||||
// we aren't. The original key calculation figured out which keys
|
||||
// were Fallthrough based on the inplace op. That means that it did
|
||||
// not include the ADInPlaceOrView kernel as a fallthrough key.
|
||||
// However, we WANT the ADInPlaceOrView kernel to be ignored now
|
||||
// that we're calling an out-of-place op. Re-invoking
|
||||
// Dispatcher::call would re-run the Fallthrough key calculation and
|
||||
// get us that, But at::redispatch is more performant. We can get
|
||||
// away with it by explicitly removing the key here.
|
||||
c10::DispatchKey::ADInplaceOrView);
|
||||
|
||||
constexpr DispatchKeySet backend_bitset_mask =
|
||||
DispatchKeySet(DispatchKeySet::RAW, (1ULL << num_backends) - 1);
|
||||
|
||||
constexpr auto inplace_or_view_ks =
|
||||
DispatchKeySet(DispatchKey::ADInplaceOrView);
|
||||
constexpr auto autograd_cpu_ks = DispatchKeySet(DispatchKey::AutogradCPU);
|
||||
constexpr auto autograd_ipu_ks = DispatchKeySet(DispatchKey::AutogradIPU);
|
||||
constexpr auto autograd_mtia_ks = DispatchKeySet(DispatchKey::AutogradMTIA);
|
||||
constexpr auto autograd_maia_ks = DispatchKeySet(DispatchKey::AutogradMAIA);
|
||||
constexpr auto autograd_xpu_ks = DispatchKeySet(DispatchKey::AutogradXPU);
|
||||
constexpr auto autograd_cuda_ks = DispatchKeySet(DispatchKey::AutogradCUDA);
|
||||
constexpr auto autograd_xla_ks = DispatchKeySet(DispatchKey::AutogradXLA);
|
||||
constexpr auto autograd_lazy_ks = DispatchKeySet(DispatchKey::AutogradLazy);
|
||||
constexpr auto autograd_meta_ks = DispatchKeySet(DispatchKey::AutogradMeta);
|
||||
constexpr auto autograd_mps_ks = DispatchKeySet(DispatchKey::AutogradMPS);
|
||||
constexpr auto autograd_hpu_ks = DispatchKeySet(DispatchKey::AutogradHPU);
|
||||
constexpr auto autograd_privateuse1_ks =
|
||||
DispatchKeySet(DispatchKey::AutogradPrivateUse1);
|
||||
constexpr auto autograd_privateuse2_ks =
|
||||
DispatchKeySet(DispatchKey::AutogradPrivateUse2);
|
||||
constexpr auto autograd_privateuse3_ks =
|
||||
DispatchKeySet(DispatchKey::AutogradPrivateUse3);
|
||||
constexpr auto autograd_other_ks = DispatchKeySet(DispatchKey::AutogradOther);
|
||||
constexpr auto autograd_nested =
|
||||
DispatchKeySet(DispatchKey::AutogradNestedTensor);
|
||||
// keyset corresponding to functorch keys that have their own dedicated
|
||||
// TensorImpl subclass.
|
||||
constexpr auto functorch_transforms_ks = DispatchKeySet(
|
||||
{DispatchKey::FuncTorchBatched,
|
||||
DispatchKey::FuncTorchVmapMode,
|
||||
DispatchKey::Batched,
|
||||
DispatchKey::VmapMode,
|
||||
DispatchKey::FuncTorchGradWrapper});
|
||||
|
||||
constexpr auto functorch_batched_ks =
|
||||
DispatchKeySet({DispatchKey::FuncTorchBatched});
|
||||
|
||||
// This keyset has:
|
||||
// (1) the functionality bits corresponding to backends (dense, sparse,
|
||||
// quantized) (2) all of the backend bits set
|
||||
constexpr DispatchKeySet backend_functionality_keys =
|
||||
DispatchKeySet({
|
||||
DispatchKey::Dense,
|
||||
DispatchKey::Quantized,
|
||||
DispatchKey::Sparse,
|
||||
DispatchKey::SparseCsr,
|
||||
}) |
|
||||
DispatchKeySet(DispatchKeySet::RAW, full_backend_mask);
|
||||
|
||||
struct OpTableOffsetAndMask {
|
||||
uint16_t offset;
|
||||
uint16_t backend_mask;
|
||||
};
|
||||
|
||||
static_assert(
|
||||
num_backends <= 16,
|
||||
"Right now we expect the number of backends not to exceed 16. In the (unlikely) event"
|
||||
" that this changes, the size of OpTableOffsetAndMask::backend_mask needs to be increased too.");
|
||||
|
||||
// true if t is a backend dispatch key
|
||||
C10_API bool isBackendDispatchKey(DispatchKey t);
|
||||
|
||||
// Resolve alias dispatch key to DispatchKeySet if applicable
|
||||
C10_API DispatchKeySet getRuntimeDispatchKeySet(DispatchKey t);
|
||||
|
||||
// Resolve alias dispatch key to DispatchKeySet if applicable,
|
||||
// and check if k is a part of that set
|
||||
C10_API bool runtimeDispatchKeySetHas(DispatchKey t, DispatchKey k);
|
||||
|
||||
// Returns a DispatchKeySet of all backend keys mapped to Autograd dispatch key
|
||||
// t, DispatchKeySet is empty if t is not alias of DispatchKey::Autograd.
|
||||
C10_API DispatchKeySet getBackendKeySetFromAutograd(DispatchKey t);
|
||||
|
||||
// Returns a DispatchKeySet of autograd related keys mapped to backend.
|
||||
// for a given backend key, use the associated autograd key.
|
||||
// for non-backend keys, use AutogradOther as a default.
|
||||
// Note: it's convenient and fast to return a default here rather than (say)
|
||||
// returning an std::optional<DispatchKey>, or throwing. But it makes callers
|
||||
// responsible for either a) enforcing the invariant that only backend keys
|
||||
// be passed as arguments, or b) interpreting our return value carefully.
|
||||
inline DispatchKeySet getAutogradRelatedKeySetFromBackend(BackendComponent t) {
|
||||
switch (t) {
|
||||
case BackendComponent::CPUBit:
|
||||
return inplace_or_view_ks | autograd_cpu_ks;
|
||||
case BackendComponent::IPUBit:
|
||||
return inplace_or_view_ks | autograd_ipu_ks;
|
||||
case BackendComponent::MTIABit:
|
||||
return inplace_or_view_ks | autograd_mtia_ks;
|
||||
case BackendComponent::MAIABit:
|
||||
return inplace_or_view_ks | autograd_maia_ks;
|
||||
case BackendComponent::XPUBit:
|
||||
return inplace_or_view_ks | autograd_xpu_ks;
|
||||
case BackendComponent::CUDABit:
|
||||
return inplace_or_view_ks | autograd_cuda_ks;
|
||||
case BackendComponent::XLABit:
|
||||
return inplace_or_view_ks | autograd_xla_ks;
|
||||
case BackendComponent::LazyBit:
|
||||
return inplace_or_view_ks | autograd_lazy_ks;
|
||||
case BackendComponent::MetaBit:
|
||||
return inplace_or_view_ks | autograd_meta_ks;
|
||||
case BackendComponent::MPSBit:
|
||||
return inplace_or_view_ks | autograd_mps_ks;
|
||||
case BackendComponent::HPUBit:
|
||||
return inplace_or_view_ks | autograd_hpu_ks;
|
||||
case BackendComponent::PrivateUse1Bit:
|
||||
return inplace_or_view_ks | autograd_privateuse1_ks;
|
||||
case BackendComponent::PrivateUse2Bit:
|
||||
return inplace_or_view_ks | autograd_privateuse2_ks;
|
||||
case BackendComponent::PrivateUse3Bit:
|
||||
return inplace_or_view_ks | autograd_privateuse3_ks;
|
||||
default:
|
||||
return inplace_or_view_ks | autograd_other_ks;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a DispatchKeySet of autocast related keys mapped to backend.
|
||||
inline DispatchKeySet getAutocastRelatedKeySetFromBackend(BackendComponent t) {
|
||||
constexpr auto autocast_cpu_ks = DispatchKeySet(DispatchKey::AutocastCPU);
|
||||
constexpr auto autocast_mtia_ks = DispatchKeySet(DispatchKey::AutocastMTIA);
|
||||
constexpr auto autocast_maia_ks = DispatchKeySet(DispatchKey::AutocastMAIA);
|
||||
constexpr auto autocast_xpu_ks = DispatchKeySet(DispatchKey::AutocastXPU);
|
||||
constexpr auto autocast_ipu_ks = DispatchKeySet(DispatchKey::AutocastIPU);
|
||||
constexpr auto autocast_hpu_ks = DispatchKeySet(DispatchKey::AutocastHPU);
|
||||
constexpr auto autocast_cuda_ks = DispatchKeySet(DispatchKey::AutocastCUDA);
|
||||
constexpr auto autocast_xla_ks = DispatchKeySet(DispatchKey::AutocastXLA);
|
||||
constexpr auto autocast_privateuse1_ks =
|
||||
DispatchKeySet(DispatchKey::AutocastPrivateUse1);
|
||||
constexpr auto autocast_mps_ks = DispatchKeySet(DispatchKey::AutocastMPS);
|
||||
switch (t) {
|
||||
case BackendComponent::CPUBit:
|
||||
return autocast_cpu_ks;
|
||||
case BackendComponent::MTIABit:
|
||||
return autocast_mtia_ks;
|
||||
case BackendComponent::MAIABit:
|
||||
return autocast_maia_ks;
|
||||
case BackendComponent::XPUBit:
|
||||
return autocast_xpu_ks;
|
||||
case BackendComponent::IPUBit:
|
||||
return autocast_ipu_ks;
|
||||
case BackendComponent::HPUBit:
|
||||
return autocast_hpu_ks;
|
||||
case BackendComponent::CUDABit:
|
||||
return autocast_cuda_ks;
|
||||
case BackendComponent::XLABit:
|
||||
return autocast_xla_ks;
|
||||
case BackendComponent::PrivateUse1Bit:
|
||||
return autocast_privateuse1_ks;
|
||||
case BackendComponent::MPSBit:
|
||||
return autocast_mps_ks;
|
||||
default:
|
||||
return DispatchKeySet();
|
||||
}
|
||||
}
|
||||
|
||||
// returns the "backend" DispatchKey of highest priority in the set.
|
||||
// This is basically like highestBackendKey(), except that we have some
|
||||
// "functionality" bits that correspond to backends (Sparse, Quantized)
|
||||
inline DispatchKey highestPriorityBackendTypeId(DispatchKeySet ks) {
|
||||
return (ks & backend_functionality_keys).highestPriorityTypeId();
|
||||
}
|
||||
|
||||
// This API exists because we have a use case for checking
|
||||
// getRuntimeDispatchKeySet(alias).has(DispatchKey::Undefined)
|
||||
// in OperatorEntry.cpp but we disallow it in has() API.
|
||||
C10_API bool isIncludedInAlias(DispatchKey k, DispatchKey alias);
|
||||
|
||||
// Historically, every tensor only had a single DispatchKey, and it was always
|
||||
// something like CPU, and there wasn't any of this business where TLS
|
||||
// could cause the DispatchKey of a tensor to change. But we still have some
|
||||
// legacy code that is still using DispatchKey for things like instanceof
|
||||
// checks; if at all possible, refactor the code to stop using DispatchKey in
|
||||
// those cases.
|
||||
inline DispatchKey legacyExtractDispatchKey(DispatchKeySet s) {
|
||||
// NB: If you add any extra keys that can be stored in TensorImpl on
|
||||
// top of existing "backend" keys like CPU/CUDA, you need to add it
|
||||
// here. At the moment, autograd keys and ADInplaceOrView key need this
|
||||
// treatment;
|
||||
return (s - autograd_dispatch_keyset_with_ADInplaceOrView -
|
||||
autocast_dispatch_keyset -
|
||||
DispatchKeySet(
|
||||
{DispatchKey::Functionalize,
|
||||
DispatchKey::PythonTLSSnapshot,
|
||||
DispatchKey::FuncTorchGradWrapper,
|
||||
DispatchKey::FuncTorchVmapMode,
|
||||
DispatchKey::FuncTorchBatched,
|
||||
DispatchKey::Python}))
|
||||
.highestPriorityTypeId();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
using is_not_DispatchKeySet = std::negation<std::is_same<DispatchKeySet, T>>;
|
||||
|
||||
// Given a function type, constructs a function_traits type that drops the first
|
||||
// parameter type if the first parameter is of type DispatchKeySet. NB:
|
||||
// DispatchKeySet is currently explicitly hidden from JIT (mainly to avoid
|
||||
// pushing unnecessary arguments on the stack - see Note [ Plumbing Keys Through
|
||||
// the Dispatcher] for details). If at any point in the future we need to expose
|
||||
// this type to JIT, revisit the usage of this type alias.
|
||||
template <class FuncType>
|
||||
using remove_DispatchKeySet_arg_from_func = guts::make_function_traits_t<
|
||||
typename guts::infer_function_traits_t<FuncType>::return_type,
|
||||
typename std::conditional_t<
|
||||
std::is_same_v<
|
||||
DispatchKeySet,
|
||||
typename guts::typelist::head_with_default_t<
|
||||
void,
|
||||
typename guts::infer_function_traits_t<
|
||||
FuncType>::parameter_types>>,
|
||||
guts::typelist::drop_if_nonempty_t<
|
||||
typename guts::infer_function_traits_t<FuncType>::parameter_types,
|
||||
1>,
|
||||
typename guts::infer_function_traits_t<FuncType>::parameter_types>>;
|
||||
} // namespace c10
|
||||
|
||||
C10_DIAGNOSTIC_POP()
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,134 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/macros/Macros.h>
|
||||
#include <c10/util/Load.h>
|
||||
#include <c10/util/TypeCast.h>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
// Dynamic type casting utils:
|
||||
// - fetch_and_cast
|
||||
// - cast_and_store
|
||||
//
|
||||
// fetch_and_cast fetch a value with dynamic type specified by a ScalarType
|
||||
// from a void pointer and cast it to a static type.
|
||||
//
|
||||
// cast_and_store casts a static typed value into dynamic type specified
|
||||
// by a ScalarType, and store it into a void pointer.
|
||||
//
|
||||
// NOTE:
|
||||
//
|
||||
// Dynamic casting allows us to support type promotion without blowing up
|
||||
// the combination space: For example, without dynamic cast, in order to
|
||||
// implement `add_` with type promotion, we would need something like
|
||||
//
|
||||
// AT_DISPATCH_ALL_TYPES(output.dtype(),
|
||||
// AT_DISPATCH_ALL_TYPES(input1.dtype(),
|
||||
// AT_DISPATCH_ALL_TYPES(input2.dtype(),
|
||||
// [](arg0_t a, arg1_t b) -> out_t { return a + b; }
|
||||
// )
|
||||
// )
|
||||
// )
|
||||
//
|
||||
// If we support N dtypes, the above code would generate the a+b kernel for
|
||||
// all the N * N * N different supported types, the compilation time and
|
||||
// binary size would become horrible.
|
||||
//
|
||||
// Dynamic casting might sounds like a bad idea in terms of performance.
|
||||
// Especially if you ever do it in a loop, you are going to do a billion tests.
|
||||
// But in practice it is not as bad as it might look:
|
||||
//
|
||||
// - on CPU, this is a branch that always has the same outcome, therefore
|
||||
// hopefully the branch predictor could do the job pretty well
|
||||
// - on GPU, these branches will not diverge, so we could still have the same
|
||||
// warp executing the same line of code
|
||||
// - Most kernels, like `add`, are bandwidth bound, adding a few clock cycles to
|
||||
// check an integer does not hurt the performance much because the ALUs would
|
||||
// wait for load instructions anyway.
|
||||
//
|
||||
// For the discussion and benchmark, refer to:
|
||||
// - https://github.com/pytorch/pytorch/pull/28343
|
||||
// - https://github.com/pytorch/pytorch/pull/28344
|
||||
// - https://github.com/pytorch/pytorch/pull/28345
|
||||
//
|
||||
|
||||
#ifdef C10_HOST_DEVICE
|
||||
#define ERROR_UNSUPPORTED_CAST CUDA_KERNEL_ASSERT(false);
|
||||
#else
|
||||
#define ERROR_UNSUPPORTED_CAST TORCH_CHECK(false, "Unexpected scalar type");
|
||||
#endif
|
||||
|
||||
// Fetch a value with dynamic type src_type from ptr, and cast it to static type
|
||||
// dest_t.
|
||||
#define FETCH_AND_CAST_CASE(type, scalartype) \
|
||||
case ScalarType::scalartype: \
|
||||
return c10::convert<dest_t>(c10::load<type>(ptr));
|
||||
|
||||
template <typename dest_t>
|
||||
C10_HOST_DEVICE inline dest_t fetch_and_cast(
|
||||
const ScalarType src_type,
|
||||
const void* ptr) {
|
||||
C10_DIAGNOSTIC_PUSH_AND_IGNORED_IF_DEFINED("-Wswitch-enum")
|
||||
switch (src_type) {
|
||||
AT_FORALL_SCALAR_TYPES_WITH_COMPLEX(FETCH_AND_CAST_CASE)
|
||||
FETCH_AND_CAST_CASE(uint16_t, UInt16)
|
||||
FETCH_AND_CAST_CASE(uint32_t, UInt32)
|
||||
FETCH_AND_CAST_CASE(uint64_t, UInt64)
|
||||
default:
|
||||
ERROR_UNSUPPORTED_CAST
|
||||
}
|
||||
C10_DIAGNOSTIC_POP()
|
||||
return dest_t(0); // just to avoid compiler warning
|
||||
}
|
||||
|
||||
// Cast a value with static type src_t into dynamic dest_type, and store it to
|
||||
// ptr.
|
||||
#define CAST_AND_STORE_CASE(type, scalartype) \
|
||||
case ScalarType::scalartype: \
|
||||
*(type*)ptr = c10::convert<type>(value); \
|
||||
return;
|
||||
template <typename src_t>
|
||||
C10_HOST_DEVICE inline void cast_and_store(
|
||||
const ScalarType dest_type,
|
||||
void* ptr,
|
||||
src_t value) {
|
||||
C10_DIAGNOSTIC_PUSH_AND_IGNORED_IF_DEFINED("-Wswitch-enum")
|
||||
switch (dest_type) {
|
||||
AT_FORALL_SCALAR_TYPES_WITH_COMPLEX(CAST_AND_STORE_CASE)
|
||||
CAST_AND_STORE_CASE(uint16_t, UInt16)
|
||||
CAST_AND_STORE_CASE(uint32_t, UInt32)
|
||||
CAST_AND_STORE_CASE(uint64_t, UInt64)
|
||||
default:;
|
||||
}
|
||||
C10_DIAGNOSTIC_POP()
|
||||
ERROR_UNSUPPORTED_CAST
|
||||
}
|
||||
|
||||
#define DEFINE_UNCASTABLE(T, scalartype_) \
|
||||
template <> \
|
||||
C10_HOST_DEVICE inline T fetch_and_cast<T>( \
|
||||
const ScalarType src_type, const void* ptr) { \
|
||||
CUDA_KERNEL_ASSERT(ScalarType::scalartype_ == src_type); \
|
||||
return c10::load<T>(ptr); \
|
||||
} \
|
||||
template <> \
|
||||
C10_HOST_DEVICE inline void cast_and_store<T>( \
|
||||
const ScalarType dest_type, void* ptr, T value) { \
|
||||
CUDA_KERNEL_ASSERT(ScalarType::scalartype_ == dest_type); \
|
||||
*(T*)ptr = value; \
|
||||
}
|
||||
|
||||
AT_FORALL_QINT_TYPES(DEFINE_UNCASTABLE)
|
||||
|
||||
#undef FETCH_AND_CAST_CASE
|
||||
#undef CAST_AND_STORE_CASE
|
||||
#undef DEFINE_UNCASTABLE
|
||||
#undef ERROR_UNSUPPORTED_CAST
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,142 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/Device.h>
|
||||
#include <c10/core/DeviceType.h>
|
||||
#include <c10/core/Stream.h>
|
||||
#include <c10/core/impl/DeviceGuardImplInterface.h>
|
||||
#include <c10/core/impl/InlineEvent.h>
|
||||
#include <c10/core/impl/VirtualGuardImpl.h>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
/**
|
||||
* A backend-generic movable, not copyable, not thread-safe event.
|
||||
*
|
||||
* The design of this event follows that of CUDA and HIP events. These events
|
||||
* are recorded and waited on by streams and can be rerecorded to,
|
||||
* each rerecording essentially creating a new version of the event.
|
||||
* For example, if (in CPU time), stream X is asked to record E,
|
||||
* stream Y waits on E, and stream X is asked to record E again, then Y will
|
||||
* wait for X to finish the first call to record and not the second, because
|
||||
* it's waiting on the first version of event E, not the second.
|
||||
* Querying an event only returns the status of its most recent version.
|
||||
*
|
||||
* Backend-generic events are implemented by this class and
|
||||
* impl::InlineEvent. In addition to these events there are also
|
||||
* some backend-specific events, like ATen's CUDAEvent. Each of these
|
||||
* classes has its own use.
|
||||
*
|
||||
* impl::InlineEvent<...> or a backend-specific event should be
|
||||
* preferred when the backend is known at compile time and known to
|
||||
* be compiled. Backend-specific events may have additional functionality.
|
||||
*
|
||||
* This Event should be used if a particular backend may not be available,
|
||||
* or the backend required is not known at compile time.
|
||||
*
|
||||
* These generic events are built on top of DeviceGuardImpls, analogous
|
||||
* to DeviceGuard and InlineDeviceGuard. The name "DeviceGuardImpls,"
|
||||
* is no longer entirely accurate, as these classes implement the
|
||||
* backend-specific logic for a generic backend interface.
|
||||
*
|
||||
* See DeviceGuardImplInterface.h for a list of all supported flags.
|
||||
*/
|
||||
|
||||
struct Event final {
|
||||
// Constructors
|
||||
Event() = delete;
|
||||
Event(
|
||||
const DeviceType _device_type,
|
||||
const EventFlag _flag = EventFlag::PYTORCH_DEFAULT)
|
||||
: impl_{_device_type, _flag} {}
|
||||
|
||||
// Copy constructor and copy assignment operator (deleted)
|
||||
Event(const Event&) = delete;
|
||||
Event& operator=(const Event&) = delete;
|
||||
|
||||
// Move constructor and move assignment operator
|
||||
Event(Event&&) noexcept = default;
|
||||
Event& operator=(Event&&) noexcept = default;
|
||||
|
||||
// Destructor
|
||||
~Event() = default;
|
||||
|
||||
// Getters
|
||||
Device device() const noexcept {
|
||||
return Device(device_type(), device_index());
|
||||
}
|
||||
DeviceType device_type() const noexcept {
|
||||
return impl_.device_type();
|
||||
}
|
||||
DeviceIndex device_index() const noexcept {
|
||||
return impl_.device_index();
|
||||
}
|
||||
EventFlag flag() const noexcept {
|
||||
return impl_.flag();
|
||||
}
|
||||
bool was_marked_for_recording() const noexcept {
|
||||
return impl_.was_marked_for_recording();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls record() if and only if record() has never been called for this
|
||||
* event. Note: because Event is not thread-safe recordOnce() may call
|
||||
* record() multiple times if called from multiple threads.
|
||||
*/
|
||||
void recordOnce(const Stream& stream) {
|
||||
impl_.recordOnce(stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the event's version and enqueues a job with this version
|
||||
* in the stream's work queue. When the stream process that job
|
||||
* it notifies all streams waiting on / blocked by that version of the
|
||||
* event to continue and marks that version as recorded.
|
||||
* */
|
||||
void record(const Stream& stream) {
|
||||
impl_.record(stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does nothing if the event has not been scheduled to be recorded.
|
||||
* If the event was previously enqueued to be recorded, a command
|
||||
* to wait for the version of the event that exists at the time of this call
|
||||
* is inserted in the stream's work queue.
|
||||
* When the stream reaches this command it will stop processing
|
||||
* additional commands until that version of the event is marked as recorded.
|
||||
*/
|
||||
void block(const Stream& stream) const {
|
||||
impl_.block(stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if (and only if)
|
||||
* (1) the event has never been scheduled to be recorded
|
||||
* (2) the current version is marked as recorded.
|
||||
* Returns false otherwise.
|
||||
*/
|
||||
bool query() const {
|
||||
return impl_.query();
|
||||
}
|
||||
|
||||
double elapsedTime(const Event& event) const {
|
||||
return impl_.elapsedTime(event.impl_);
|
||||
}
|
||||
|
||||
void* eventId() const {
|
||||
return impl_.eventId();
|
||||
}
|
||||
|
||||
void synchronize() const {
|
||||
impl_.synchronize();
|
||||
}
|
||||
|
||||
private:
|
||||
impl::InlineEvent<impl::VirtualGuardImpl> impl_;
|
||||
};
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,116 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
|
||||
#include <c10/core/Device.h>
|
||||
#include <c10/core/DispatchKeySet.h>
|
||||
#include <c10/core/TensorImpl.h>
|
||||
#include <c10/macros/Export.h>
|
||||
#include <c10/util/intrusive_ptr.h>
|
||||
#include <c10/util/python_stub.h>
|
||||
|
||||
/**
|
||||
* Note [Generator]
|
||||
* ~~~~~~~~~~~~~~~~
|
||||
* A Pseudo Random Number Generator (PRNG) is an engine that uses an algorithm
|
||||
* to generate a seemingly random sequence of numbers, that may be later be used
|
||||
* in creating a random distribution. Such an engine almost always maintains a
|
||||
* state and requires a seed to start off the creation of random numbers. Often
|
||||
* times, users have found it beneficial to be able to explicitly create,
|
||||
* retain, and destroy PRNG states and also be able to have control over the
|
||||
* seed value.
|
||||
*
|
||||
* A Generator in ATen gives users the ability to read, write and modify a PRNG
|
||||
* engine. For instance, it does so by letting users seed a PRNG engine, fork
|
||||
* the state of the engine, etc.
|
||||
*
|
||||
* By default, there is one generator per device, and a device's generator is
|
||||
* lazily created. A user can use the torch.Generator() api to create their own
|
||||
* generator. Currently torch.Generator() can only create a CPUGeneratorImpl.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Note [Acquire lock when using random generators]
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
* Generator and its derived classes are NOT thread-safe. Please note that most
|
||||
* of the places where we have inserted locking for generators are historically
|
||||
* based, and we haven't actually checked that everything is truly thread safe
|
||||
* (and it probably isn't). Please use the public mutex_ when using any methods
|
||||
* from these classes, except for the read-only methods. You can learn about the
|
||||
* usage by looking into the unittests (aten/src/ATen/cpu_generator_test.cpp)
|
||||
* and other places where we have used lock_guard.
|
||||
*
|
||||
* TODO: Look into changing the threading semantics of Generators in ATen (e.g.,
|
||||
* making them non-thread safe and instead making the generator state
|
||||
* splittable, to accommodate forks into other threads).
|
||||
*/
|
||||
|
||||
namespace c10 {
|
||||
|
||||
// The default seed is selected to be a large number
|
||||
// with good distribution of 0s and 1s in bit representation
|
||||
constexpr uint64_t default_rng_seed_val = 67280421310721;
|
||||
|
||||
struct C10_API GeneratorImpl : public c10::intrusive_ptr_target {
|
||||
// Constructors
|
||||
GeneratorImpl(Device device_in, DispatchKeySet key_set);
|
||||
|
||||
// Delete all copy and move assignment in favor of clone()
|
||||
// method
|
||||
GeneratorImpl(const GeneratorImpl& other) = delete;
|
||||
GeneratorImpl(GeneratorImpl&& other) = delete;
|
||||
GeneratorImpl& operator=(const GeneratorImpl& other) = delete;
|
||||
GeneratorImpl& operator=(GeneratorImpl&& other) = delete;
|
||||
|
||||
~GeneratorImpl() override = default;
|
||||
c10::intrusive_ptr<GeneratorImpl> clone() const;
|
||||
|
||||
// Common methods for all generators
|
||||
virtual void set_current_seed(uint64_t seed) = 0;
|
||||
virtual void set_offset(uint64_t offset) = 0;
|
||||
virtual uint64_t get_offset() const = 0;
|
||||
virtual uint64_t current_seed() const = 0;
|
||||
virtual uint64_t seed() = 0;
|
||||
virtual void set_state(const c10::TensorImpl& new_state) = 0;
|
||||
virtual c10::intrusive_ptr<c10::TensorImpl> get_state() const = 0;
|
||||
virtual void graphsafe_set_state(
|
||||
const c10::intrusive_ptr<c10::GeneratorImpl>& new_state);
|
||||
virtual c10::intrusive_ptr<c10::GeneratorImpl> graphsafe_get_state() const;
|
||||
Device device() const;
|
||||
|
||||
// See Note [Acquire lock when using random generators]
|
||||
std::mutex mutex_;
|
||||
|
||||
DispatchKeySet key_set() const {
|
||||
return key_set_;
|
||||
}
|
||||
|
||||
inline void set_pyobj(PyObject* pyobj) noexcept {
|
||||
pyobj_ = pyobj;
|
||||
}
|
||||
|
||||
inline PyObject* pyobj() const noexcept {
|
||||
return pyobj_;
|
||||
}
|
||||
|
||||
protected:
|
||||
Device device_;
|
||||
DispatchKeySet key_set_;
|
||||
PyObject* pyobj_ = nullptr;
|
||||
|
||||
virtual GeneratorImpl* clone_impl() const = 0;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
C10_API uint64_t getNonDeterministicRandom(bool is_cuda = false);
|
||||
|
||||
} // namespace detail
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,57 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/AutogradState.h>
|
||||
#include <c10/macros/Export.h>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
struct C10_API GradMode {
|
||||
static bool is_enabled();
|
||||
static void set_enabled(bool enabled);
|
||||
};
|
||||
|
||||
// A RAII, thread local (!) guard that enables or disables grad mode upon
|
||||
// construction, and sets it back to the original value upon destruction.
|
||||
struct C10_API AutoGradMode {
|
||||
AutoGradMode(bool enabled) : prev_mode(GradMode::is_enabled()) {
|
||||
GradMode::set_enabled(enabled);
|
||||
}
|
||||
AutoGradMode(const AutoGradMode&) = delete;
|
||||
AutoGradMode(AutoGradMode&&) = delete;
|
||||
AutoGradMode& operator=(const AutoGradMode&) = delete;
|
||||
AutoGradMode& operator=(AutoGradMode&&) = delete;
|
||||
~AutoGradMode() {
|
||||
GradMode::set_enabled(prev_mode);
|
||||
}
|
||||
bool prev_mode;
|
||||
};
|
||||
|
||||
// A RAII, thread local (!) guard that stops future operations from building
|
||||
// gradients.
|
||||
struct C10_API NoGradGuard : public AutoGradMode {
|
||||
NoGradGuard() : AutoGradMode(/*enabled=*/false) {}
|
||||
};
|
||||
|
||||
// A RAII, thread local (!) guard that enables or disables forward grad mode
|
||||
// upon construction, and sets it back to the original value upon destruction.
|
||||
struct C10_API AutoFwGradMode {
|
||||
AutoFwGradMode(bool enabled)
|
||||
: prev_mode(AutogradState::get_tls_state().get_fw_grad_mode()) {
|
||||
AutogradState::get_tls_state().set_fw_grad_mode(enabled);
|
||||
}
|
||||
AutoFwGradMode(const AutoFwGradMode&) = delete;
|
||||
AutoFwGradMode(AutoFwGradMode&&) = delete;
|
||||
AutoFwGradMode& operator=(const AutoFwGradMode&) = delete;
|
||||
AutoFwGradMode& operator=(AutoFwGradMode&&) = delete;
|
||||
~AutoFwGradMode() {
|
||||
AutogradState::get_tls_state().set_fw_grad_mode(prev_mode);
|
||||
}
|
||||
bool prev_mode;
|
||||
};
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,96 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/AutogradState.h>
|
||||
#include <c10/core/DispatchKey.h>
|
||||
#include <c10/core/DispatchKeySet.h>
|
||||
#include <c10/core/impl/LocalDispatchKeySet.h>
|
||||
#include <c10/macros/Export.h>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
// A RAII, thread local (!) guard that enables or disables inference mode upon
|
||||
// construction, and sets it back to the original value upon destruction.
|
||||
struct C10_API InferenceMode {
|
||||
// Note [Expected TLS state in InferenceMode]:
|
||||
// InferenceMode: ADInplaceOrView not in
|
||||
// raw_local_dispatch_key_set.included(),
|
||||
// Autograd in raw_local_dispatch_key_set.excluded()
|
||||
// GradMode is disabled.
|
||||
// NormalMode: ADInplaceOrView in raw_local_dispatch_key_set.included(),
|
||||
// Autograd not in raw_local_dispatch_key_set.excluded()
|
||||
// GradMode is enabled by default unless toggled manually
|
||||
// through other APIs, e.g. NoGradGuard.
|
||||
//
|
||||
// Invariant:
|
||||
// - ADInplaceOrView is never in the excluded set
|
||||
// - Autograd is never in the included set
|
||||
// - Setting InferenceMode will set GradMode accordingly, but not vice versa.
|
||||
//
|
||||
// 1. Why do we put ADInplaceOrView in included set outside InferenceMode?
|
||||
//
|
||||
// Inplace update to inference tensor outside InferenceMode is not
|
||||
// allowed. See Note [Inplace update inference tensor] for more details.
|
||||
// Without going through ADInplaceOrView kernel, we cannot throw error
|
||||
// for `inference_tensor.add_(1)` case.
|
||||
//
|
||||
// 2. Why not put ADInplaceOrView in the excluded set inside InferenceMode?
|
||||
//
|
||||
// For example:
|
||||
// torch::Tensor a = torch::ones({1, 2, 3}).set_requires_grad(true);
|
||||
// torch::Tensor k = a + 2;
|
||||
// {
|
||||
// c10::InferenceMode guard(true);
|
||||
// k.add_(2);
|
||||
// }
|
||||
// `k.add_(2)` still need to go through ADInplaceOrView kernel so that it's
|
||||
// prepared for future autograd.
|
||||
//
|
||||
// 3. Why does setting InferenceMode also set GradMode?
|
||||
//
|
||||
// This is required since InferenceMode is a faster and more restrictive
|
||||
// version of NoGradGuard. All runtime checks using GradMode::is_enabled()
|
||||
// are applicable to InferenceMode as well, e.g.
|
||||
// `tensorTypeInCurrentExecutionContext` in interpreter.cpp.
|
||||
InferenceMode(bool enabled = true)
|
||||
: prev_mode(AutogradState::get_tls_state()),
|
||||
prev_keyset(c10::impl::tls_local_dispatch_key_set()) {
|
||||
// Enabling inference mode means disabling grad modes
|
||||
// And disabling inference mode means enabling grad modes
|
||||
AutogradState::set_tls_state(AutogradState(
|
||||
/* grad_mode */ !enabled,
|
||||
/* inference_mode */ enabled,
|
||||
/* fw_grad_mode */ !enabled,
|
||||
/* multithreading_enabled*/ !enabled));
|
||||
DispatchKeySet included = enabled
|
||||
? prev_keyset.included_.remove(c10::DispatchKey::ADInplaceOrView)
|
||||
: prev_keyset.included_.add(c10::DispatchKey::ADInplaceOrView);
|
||||
DispatchKeySet excluded = enabled
|
||||
? (prev_keyset.excluded_ | c10::autograd_dispatch_keyset)
|
||||
: (prev_keyset.excluded_ - c10::autograd_dispatch_keyset);
|
||||
c10::impl::PODLocalDispatchKeySet cur_keyset{};
|
||||
cur_keyset.set_included(included);
|
||||
cur_keyset.set_excluded(excluded);
|
||||
c10::impl::_force_tls_local_dispatch_key_set(cur_keyset);
|
||||
}
|
||||
|
||||
InferenceMode(const InferenceMode&) = delete;
|
||||
InferenceMode(InferenceMode&&) = delete;
|
||||
InferenceMode& operator=(const InferenceMode&) = delete;
|
||||
InferenceMode& operator=(InferenceMode&&) = delete;
|
||||
|
||||
~InferenceMode() {
|
||||
AutogradState::set_tls_state(prev_mode);
|
||||
c10::impl::_force_tls_local_dispatch_key_set(prev_keyset);
|
||||
}
|
||||
static bool is_enabled();
|
||||
|
||||
private:
|
||||
AutogradState prev_mode;
|
||||
c10::impl::LocalDispatchKeySet prev_keyset;
|
||||
};
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,67 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/Backend.h>
|
||||
#include <c10/util/Exception.h>
|
||||
|
||||
#include <torch/headeronly/core/Layout.h>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
inline Layout layout_from_backend(Backend backend) {
|
||||
C10_DIAGNOSTIC_PUSH_AND_IGNORED_IF_DEFINED("-Wswitch-enum")
|
||||
switch (backend) {
|
||||
case Backend::SparseCPU:
|
||||
case Backend::SparseCUDA:
|
||||
case Backend::SparseMPS:
|
||||
case Backend::SparseHIP:
|
||||
case Backend::SparseVE:
|
||||
case Backend::SparseXPU:
|
||||
case Backend::SparsePrivateUse1:
|
||||
return Layout::Sparse;
|
||||
case Backend::MkldnnCPU:
|
||||
return Layout::Mkldnn;
|
||||
case Backend::SparseCsrCPU:
|
||||
case Backend::SparseCsrCUDA:
|
||||
case Backend::SparseCsrMPS:
|
||||
case Backend::SparseCsrHIP:
|
||||
case Backend::SparseCsrVE:
|
||||
case Backend::SparseCsrXPU:
|
||||
TORCH_CHECK(
|
||||
false,
|
||||
"Cannot map Backend SparseCsr(CPU|CUDA|HIP|VE|XPU|MPS) to a unique layout.");
|
||||
default:
|
||||
return Layout::Strided;
|
||||
}
|
||||
C10_DIAGNOSTIC_POP()
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& stream, at::Layout layout) {
|
||||
switch (layout) {
|
||||
case at::kStrided:
|
||||
return stream << "Strided";
|
||||
case at::kSparse:
|
||||
return stream << "Sparse";
|
||||
case at::kSparseCsr:
|
||||
return stream << "SparseCsr";
|
||||
case at::kSparseCsc:
|
||||
return stream << "SparseCsc";
|
||||
case at::kSparseBsr:
|
||||
return stream << "SparseBsr";
|
||||
case at::kSparseBsc:
|
||||
return stream << "SparseBsc";
|
||||
case at::kMkldnn:
|
||||
return stream << "Mkldnn";
|
||||
case at::kJagged:
|
||||
return stream << "Jagged";
|
||||
case Layout::NumOptions:
|
||||
default:
|
||||
TORCH_CHECK(false, "Unknown layout");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,268 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/util/ArrayRef.h>
|
||||
#include <c10/util/Exception.h>
|
||||
|
||||
#include <torch/headeronly/core/MemoryFormat.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
// If you are seeing this, it means that this call site was not checked if
|
||||
// the memory format could be preserved, and it was switched to old default
|
||||
// behaviour of contiguous
|
||||
#define LEGACY_CONTIGUOUS_MEMORY_FORMAT c10::get_contiguous_memory_format()
|
||||
|
||||
inline std::ostream& operator<<(
|
||||
std::ostream& stream,
|
||||
at::MemoryFormat memory_format) {
|
||||
switch (memory_format) {
|
||||
case MemoryFormat::Preserve:
|
||||
return stream << "Preserve";
|
||||
case MemoryFormat::Contiguous:
|
||||
return stream << "Contiguous";
|
||||
case MemoryFormat::ChannelsLast:
|
||||
return stream << "ChannelsLast";
|
||||
case MemoryFormat::ChannelsLast3d:
|
||||
return stream << "ChannelsLast3d";
|
||||
case MemoryFormat::NumOptions:
|
||||
default:
|
||||
TORCH_CHECK(false, "Unknown memory format ", memory_format);
|
||||
}
|
||||
}
|
||||
|
||||
// Note: Hardcoded the channel last stride indices here to get better
|
||||
// performance
|
||||
template <typename T>
|
||||
inline std::vector<T> get_channels_last_strides_2d(ArrayRef<T> sizes) {
|
||||
std::vector<T> strides(sizes.size());
|
||||
switch (sizes.size()) {
|
||||
case 4:
|
||||
strides[1] = 1;
|
||||
strides[3] = sizes[1];
|
||||
strides[2] = strides[3] * sizes[3];
|
||||
strides[0] = strides[2] * sizes[2];
|
||||
return strides;
|
||||
case 3:
|
||||
strides[0] = 1;
|
||||
strides[2] = sizes[0];
|
||||
strides[1] = strides[2] * sizes[2];
|
||||
return strides;
|
||||
default:
|
||||
TORCH_INTERNAL_ASSERT(
|
||||
false, "ChannelsLast2d doesn't support size ", sizes.size());
|
||||
}
|
||||
}
|
||||
|
||||
inline std::vector<int64_t> get_channels_last_strides_2d(IntArrayRef sizes) {
|
||||
return get_channels_last_strides_2d<int64_t>(sizes);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> get_channels_last_strides_3d(ArrayRef<T> sizes) {
|
||||
std::vector<T> strides(sizes.size());
|
||||
switch (sizes.size()) {
|
||||
case 5:
|
||||
strides[1] = 1;
|
||||
strides[4] = sizes[1];
|
||||
strides[3] = strides[4] * sizes[4];
|
||||
strides[2] = strides[3] * sizes[3];
|
||||
strides[0] = strides[2] * sizes[2];
|
||||
return strides;
|
||||
case 4:
|
||||
strides[0] = 1;
|
||||
strides[3] = sizes[0];
|
||||
strides[2] = strides[3] * sizes[3];
|
||||
strides[1] = strides[2] * sizes[2];
|
||||
return strides;
|
||||
default:
|
||||
TORCH_INTERNAL_ASSERT(
|
||||
false, "ChannelsLast3d doesn't support size ", sizes.size());
|
||||
}
|
||||
}
|
||||
|
||||
inline std::vector<int64_t> get_channels_last_strides_3d(IntArrayRef sizes) {
|
||||
return get_channels_last_strides_3d<int64_t>(sizes);
|
||||
}
|
||||
|
||||
// NOTE:
|
||||
// Below are Helper functions for is_channels_last_strides_xd.
|
||||
// 1. Please do not combine these helper functions, each helper function handles
|
||||
// exactly one case of sizes + memory_format, by doing this, the strides indices
|
||||
// will be a constant array and we can access it using constant index number,
|
||||
// the compiler will fully unroll the loop on strides indices to gain a better
|
||||
// performance.
|
||||
// 2. No error check in helper function, caller ensures the correctness of the
|
||||
// input
|
||||
// 3. All helper functions have similar comments, only 1st helper function is
|
||||
// commented here.
|
||||
template <typename T>
|
||||
inline bool is_channels_last_strides_2d_s4(
|
||||
const ArrayRef<T> sizes,
|
||||
const ArrayRef<T> strides) {
|
||||
T min = 0;
|
||||
// special case for trivial C dimension. default to NCHW
|
||||
if (strides[1] == 0) {
|
||||
return false;
|
||||
}
|
||||
// loop strides indices
|
||||
for (auto& d : {1, 3, 2, 0}) {
|
||||
if (sizes[d] == 0) {
|
||||
return false;
|
||||
}
|
||||
if (strides[d] < min) {
|
||||
return false;
|
||||
}
|
||||
// Fallback to NCHW as default layout for ambiguous cases
|
||||
// This is the flaw of implicit memory_format from strides.
|
||||
// N111 tensor with identical strides for size 1 dimension;
|
||||
// Two cases could lead us here:
|
||||
// a. N111 contiguous Tensor ([N,1,1,1]@[1,1,1,1])
|
||||
// b. N11W contiguous Tensor sliced on the W-dimension.
|
||||
// ([N,1,1,1]@[W,W,W,W])
|
||||
if (d == 0 && min == strides[1]) {
|
||||
return false;
|
||||
}
|
||||
// This is necessary to:
|
||||
// 1. distinguish the memory_format of N1H1;
|
||||
// [H, 1, 1, 1] channels_last stride
|
||||
// [H, H, 1, 1] contiguous stride
|
||||
// 2. permutation of 1C1W:
|
||||
// [1, C, 1, H]@[HC, H, H, 1] transpose(1, 3)
|
||||
// [1, H, 1, C]@[HC, 1, H, H] shouldn't be identified as channels_last
|
||||
min = strides[d];
|
||||
if (sizes[d] > 1) {
|
||||
min *= sizes[d];
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline bool is_channels_last_strides_3d_s5(
|
||||
const ArrayRef<T> sizes,
|
||||
const ArrayRef<T> strides) {
|
||||
T min = 0;
|
||||
if (strides[1] == 0) {
|
||||
return false;
|
||||
}
|
||||
for (auto& d : {1, 4, 3, 2, 0}) {
|
||||
if (sizes[d] == 0) {
|
||||
return false;
|
||||
}
|
||||
if (strides[d] < min) {
|
||||
return false;
|
||||
}
|
||||
if (d == 0 && min == strides[1]) {
|
||||
return false;
|
||||
}
|
||||
min = strides[d];
|
||||
if (sizes[d] > 1) {
|
||||
min *= sizes[d];
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Note [Ambiguous is_channels_last_strides_xd]
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// The flaw of carrying memory_format implicitly through strides is very hard
|
||||
// to WAR properly. issue #24090
|
||||
// Without the history of permutation, we can't infer the memory_format of a
|
||||
// tensor from the snapshot of its size & stride
|
||||
// e.g.
|
||||
//
|
||||
// 1. We can NOT specify the memory_format of N111 tensor through strides in a
|
||||
// meaningful way;
|
||||
//
|
||||
// 2. Two path that ended up with identical size/stride
|
||||
// N11W contiguous tensor sliced at w-dimension becomes [N,1,1,1]@[W,W,W,W]
|
||||
// NC11 channels_last tensor sliced at c-dimension becomes [N,1,1,1]@[C,C,C,C]
|
||||
// So if we see a tensor [N,1,1,1]@[X,X,X,X], there's no way for us to infer
|
||||
// the memory_format of the original tensor.
|
||||
//
|
||||
// Due to the limitations, our temporary WAR `is_channels_last_strides` does the
|
||||
// best effort to infer whether the original memory_format of a tensor is
|
||||
// at::MemoryFormat::ChannelsLast. The two objectives of this function (ordered
|
||||
// by their importance):
|
||||
// 1. Ensure that normal shape manipulation does not accidentally change the
|
||||
// MemoryFormat of an existing tensor.
|
||||
// 2. Allows user to mark MemoryFormat::ChannelsLast to tensors;
|
||||
//
|
||||
// The function does so via checking strides of the tensor, including strides of
|
||||
// size-1 dimensions. Although conventionally PyTorch implies no restriction on
|
||||
// trivial stride (stride for size-1 dimension).
|
||||
//
|
||||
// Note that this approach is a compromise. We did not solve the problem
|
||||
// completely. Many cases we will not be able to infer the correct memory
|
||||
// format.
|
||||
// The implementation of `is_channels_last_strides` is to serve the objectives:
|
||||
// MemoryFormat::ChannelsLast has to be explicitly opted-in (no accidental
|
||||
// conversion); Best effort to maintain the ChannelsLast flag.
|
||||
//
|
||||
// Due to the fact that this is not a bulletproof solution, through testing
|
||||
// (aten/src/ATen/test/memory_format_test.cpp)
|
||||
// a. we ensure that the common tasks are supported;
|
||||
// a. we identify corner cases where the implementation compromises on.
|
||||
//
|
||||
// By the time accumulated permutation is enabled to replace implicit
|
||||
// memory_format through strides, we should be updating our tests and fix the
|
||||
// issues in our tests.
|
||||
//
|
||||
// We use Channels Last 2d as an example above.
|
||||
// This is a general problem for all the is_channels_last_strides_xd
|
||||
// implementation. Please check the helper functions
|
||||
// (is_channels_last_strides_*d_s*) for more details.
|
||||
|
||||
template <typename T>
|
||||
inline bool is_channels_last_strides_2d(
|
||||
const ArrayRef<T> sizes,
|
||||
const ArrayRef<T> strides) {
|
||||
switch (sizes.size()) {
|
||||
case 4:
|
||||
return is_channels_last_strides_2d_s4(sizes, strides);
|
||||
// NOLINTNEXTLINE(bugprone-branch-clone)
|
||||
case 3:
|
||||
// TODO dim == 3 case will be enabled once it is fully tested
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline bool is_channels_last_strides_3d(
|
||||
const ArrayRef<T> sizes,
|
||||
const ArrayRef<T> strides) {
|
||||
switch (sizes.size()) {
|
||||
case 5:
|
||||
return is_channels_last_strides_3d_s5(sizes, strides);
|
||||
// NOLINTNEXTLINE(bugprone-branch-clone)
|
||||
case 4:
|
||||
// TODO dim == 4 case will be enabled once it is fully tested
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool is_channels_last_strides_2d(
|
||||
const IntArrayRef sizes,
|
||||
const IntArrayRef strides) {
|
||||
return is_channels_last_strides_2d<int64_t>(sizes, strides);
|
||||
}
|
||||
|
||||
inline bool is_channels_last_strides_3d(
|
||||
const IntArrayRef sizes,
|
||||
const IntArrayRef strides) {
|
||||
return is_channels_last_strides_3d<int64_t>(sizes, strides);
|
||||
}
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,36 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
namespace c10 {
|
||||
|
||||
template <typename T>
|
||||
class OptionalRef {
|
||||
public:
|
||||
OptionalRef() : data_(nullptr) {}
|
||||
OptionalRef(const T* data) : data_(data) {
|
||||
TORCH_INTERNAL_ASSERT_DEBUG_ONLY(data_);
|
||||
}
|
||||
OptionalRef(const T& data) : data_(&data) {}
|
||||
|
||||
bool has_value() const {
|
||||
return data_ != nullptr;
|
||||
}
|
||||
|
||||
const T& get() const {
|
||||
TORCH_INTERNAL_ASSERT_DEBUG_ONLY(data_);
|
||||
return *data_;
|
||||
}
|
||||
|
||||
operator bool() const {
|
||||
return has_value();
|
||||
}
|
||||
|
||||
private:
|
||||
const T* data_;
|
||||
};
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,81 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/impl/PyInterpreter.h>
|
||||
#include <c10/macros/Macros.h>
|
||||
#include <c10/util/Exception.h>
|
||||
#include <c10/util/python_stub.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
// A PyHandleCache represents a cached pointer from a C++ object to
|
||||
// a Python object that represents that object analogously in Python.
|
||||
// Upon a cache hit, the relevant object can be retrieved after a test
|
||||
// and then a memory load. Two conditions must hold to be able to use this
|
||||
// class:
|
||||
//
|
||||
// - This must truly be a cache; e.g., the caller must be able to produce
|
||||
// the object some other way if the cache hit misses.
|
||||
//
|
||||
// - This must truly be a handle; e.g., the Python object referenced by
|
||||
// this class must have static lifetime. This means we don't have to
|
||||
// maintain strong ownership or deallocate the object when the C++ object
|
||||
// dies. Static lifetime is a good idea in conjunction with the cache,
|
||||
// since if you are producing a fresh object on miss you won't be
|
||||
// maintaining object identity. If you need bidirectional ownership,
|
||||
// you will want to factor out the pattern in TensorImpl with
|
||||
// resurrection.
|
||||
//
|
||||
// This cache is expected to not improve perf under torchdeploy, as one
|
||||
// interpreter will fill up the cache, and all the interpreters will be
|
||||
// unable to use the slot. A potential improvement is to have multiple
|
||||
// slots (one per interpreter), which will work in deployment scenarios
|
||||
// where there a stable, fixed number of interpreters. You can also store
|
||||
// the relevant state in the Python library, rather than in the non-Python
|
||||
// library (although in many cases, this is not convenient, as there may
|
||||
// not be a way to conveniently index based on the object.)
|
||||
class PyHandleCache {
|
||||
public:
|
||||
PyHandleCache() : pyinterpreter_(nullptr) {}
|
||||
|
||||
// Attempt to fetch the pointer from the cache, if the PyInterpreter
|
||||
// matches. If it doesn't exist, or the cache entry is not valid,
|
||||
// use slow_accessor to get the real pointer value and return that
|
||||
// (possibly writing it to the cache, if the cache entry is
|
||||
// available.)
|
||||
template <typename F>
|
||||
PyObject* ptr_or(impl::PyInterpreter* self_interpreter, F slow_accessor)
|
||||
const {
|
||||
// Note [Memory ordering on Python interpreter tag]
|
||||
impl::PyInterpreter* interpreter =
|
||||
pyinterpreter_.load(std::memory_order_acquire);
|
||||
if (C10_LIKELY(interpreter == self_interpreter)) {
|
||||
return data_;
|
||||
} else if (interpreter == nullptr) {
|
||||
auto* r = slow_accessor();
|
||||
impl::PyInterpreter* expected = nullptr;
|
||||
// attempt to claim this cache entry with the specified interpreter tag
|
||||
if (pyinterpreter_.compare_exchange_strong(
|
||||
expected, self_interpreter, std::memory_order_acq_rel)) {
|
||||
data_ = r;
|
||||
}
|
||||
// This shouldn't be possible, as you should be GIL protected
|
||||
TORCH_INTERNAL_ASSERT(expected != self_interpreter);
|
||||
return r;
|
||||
} else {
|
||||
return slow_accessor();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::atomic<impl::PyInterpreter*> pyinterpreter_;
|
||||
mutable PyObject* data_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,51 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/util/Exception.h>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
/**
|
||||
* QEngine is an enum that is used to select the engine to run quantized ops.
|
||||
* Keep this enum in sync with get_qengine_id() in
|
||||
* torch/backends/quantized/__init__.py
|
||||
*/
|
||||
enum class QEngine : uint8_t {
|
||||
NoQEngine = 0,
|
||||
FBGEMM = 1,
|
||||
QNNPACK = 2,
|
||||
ONEDNN = 3,
|
||||
X86 = 4,
|
||||
};
|
||||
|
||||
constexpr auto kNoQEngine = QEngine::NoQEngine;
|
||||
constexpr auto kFBGEMM = QEngine::FBGEMM;
|
||||
constexpr auto kQNNPACK = QEngine::QNNPACK;
|
||||
constexpr auto kONEDNN = QEngine::ONEDNN;
|
||||
constexpr auto kX86 = QEngine::X86;
|
||||
|
||||
inline std::string toString(QEngine qengine) {
|
||||
switch (qengine) {
|
||||
case kNoQEngine:
|
||||
return "NoQEngine";
|
||||
case kFBGEMM:
|
||||
return "FBGEMM";
|
||||
case kQNNPACK:
|
||||
return "QNNPACK";
|
||||
case kONEDNN:
|
||||
return "ONEDNN";
|
||||
case kX86:
|
||||
return "X86";
|
||||
default:
|
||||
TORCH_CHECK(
|
||||
false, "Unrecognized Quantized Engine: ", static_cast<int>(qengine));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,60 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/macros/Macros.h>
|
||||
#include <c10/util/Exception.h>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
C10_DIAGNOSTIC_PUSH_AND_IGNORED_IF_DEFINED("-Wswitch-enum")
|
||||
|
||||
namespace c10 {
|
||||
|
||||
/**
|
||||
* QScheme is an enum that specifies the type of quantization. This has a one
|
||||
* to one correspondence with Quantizer
|
||||
* Please refer to ATen/quantized/Quantizer.h to see the Quantizers classes.
|
||||
* Keep this file in sync with torch/nn/_qscheme.py
|
||||
*/
|
||||
enum class QScheme : uint8_t {
|
||||
PER_TENSOR_AFFINE = 0,
|
||||
PER_CHANNEL_AFFINE = 1,
|
||||
PER_TENSOR_SYMMETRIC = 2,
|
||||
PER_CHANNEL_SYMMETRIC = 3,
|
||||
PER_CHANNEL_AFFINE_FLOAT_QPARAMS = 4,
|
||||
COMPILE_TIME_NUM_QSCHEMES = 5,
|
||||
};
|
||||
|
||||
constexpr auto kPerTensorAffine = QScheme::PER_TENSOR_AFFINE;
|
||||
constexpr auto kPerChannelAffine = QScheme::PER_CHANNEL_AFFINE;
|
||||
constexpr auto kPerTensorSymmetric = QScheme::PER_TENSOR_SYMMETRIC;
|
||||
constexpr auto kPerChannelSymmetric = QScheme::PER_CHANNEL_SYMMETRIC;
|
||||
constexpr auto kPerChannelAffineFloatQParams =
|
||||
QScheme::PER_CHANNEL_AFFINE_FLOAT_QPARAMS;
|
||||
constexpr int COMPILE_TIME_NUM_QSCHEMES =
|
||||
static_cast<int>(QScheme::COMPILE_TIME_NUM_QSCHEMES);
|
||||
|
||||
inline std::string toString(QScheme qscheme) {
|
||||
switch (qscheme) {
|
||||
case kPerTensorAffine:
|
||||
return "per_tensor_affine";
|
||||
case kPerChannelAffine:
|
||||
return "per_channel_affine";
|
||||
case kPerTensorSymmetric:
|
||||
return "per_tensor_symmetric";
|
||||
case kPerChannelSymmetric:
|
||||
return "per_channel_symmetric";
|
||||
case kPerChannelAffineFloatQParams:
|
||||
return "per_channel_affine_float_qparams";
|
||||
default:
|
||||
TORCH_CHECK(false, "Unrecognized qscheme: ", static_cast<int>(qscheme));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace c10
|
||||
|
||||
C10_DIAGNOSTIC_POP()
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,57 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/Storage.h>
|
||||
#include <c10/macros/Export.h>
|
||||
#include <c10/util/UniqueVoidPtr.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
// A RefcountedDeleterContext object is used as the `ctx` argument for DataPtr
|
||||
// to implement a shared DataPtr. Normally, a DataPtr is unique, but we use
|
||||
// this custom context and the `refcounted_deleter` function below to make the
|
||||
// DataPtr act like a non-unique DataPtr. This context object holds onto an
|
||||
// inner context and deleter function which handle the actual deletion of the
|
||||
// data when the refcount reaches 0.
|
||||
//
|
||||
// This shared DataPtr feature is only used when storages are shared between
|
||||
// multiple Python interpreters in MultiPy. // codespell:ignore multipy
|
||||
// Before storages had PyObject preservation, interpreters could just share the
|
||||
// same StorageImpl instance. But now a StorageImpl can only be associated with
|
||||
// one interpreter in order to properly manage a zombie PyObject. So we share
|
||||
// storages across Python interpreters by creating a different StorageImpl
|
||||
// instance for each one, but they all point to the same data.
|
||||
struct C10_API RefcountedDeleterContext {
|
||||
RefcountedDeleterContext(void* other_ctx, c10::DeleterFnPtr other_deleter)
|
||||
: other_ctx(other_ctx, other_deleter), refcount(1) {}
|
||||
|
||||
std::unique_ptr<void, c10::DeleterFnPtr> other_ctx;
|
||||
std::atomic_int refcount;
|
||||
};
|
||||
|
||||
// `refcounted_deleter` is used as the `ctx_deleter` for DataPtr to implement
|
||||
// a shared DataPtr.
|
||||
//
|
||||
// Warning: This should only be called on a pointer to
|
||||
// a RefcountedDeleterContext that was allocated on the heap with `new`,
|
||||
// because when the refcount reaches 0, the context is deleted with `delete`
|
||||
C10_API void refcounted_deleter(void* ctx_);
|
||||
|
||||
// If the storage's DataPtr does not use `refcounted_deleter`, replace it with
|
||||
// a DataPtr that does, so it can be shared between multiple StorageImpls
|
||||
C10_API void maybeApplyRefcountedDeleter(const c10::Storage& storage);
|
||||
|
||||
// Create a new StorageImpl that points to the same data. If the original
|
||||
// StorageImpl's DataPtr does not use `refcounted_deleter`, it will be replaced
|
||||
// with one that does
|
||||
C10_API c10::Storage newStorageImplFromRefcountedDataPtr(
|
||||
const c10::Storage& storage);
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,125 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/impl/PyInterpreter.h>
|
||||
#include <c10/macros/Export.h>
|
||||
#include <c10/util/python_stub.h>
|
||||
#include <utility>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
// This is an safe owning holder for a PyObject, akin to pybind11's
|
||||
// py::object, with two major differences:
|
||||
//
|
||||
// - It is in c10/core; i.e., you can use this type in contexts where
|
||||
// you do not have a libpython dependency
|
||||
//
|
||||
// - It is multi-interpreter safe (ala torchdeploy); when you fetch
|
||||
// the underlying PyObject* you are required to specify what the current
|
||||
// interpreter context is and we will check that you match it.
|
||||
//
|
||||
// It is INVALID to store a reference to a Tensor object in this way;
|
||||
// you should just use TensorImpl directly in that case!
|
||||
struct C10_API SafePyObject {
|
||||
// Steals a reference to data
|
||||
SafePyObject(PyObject* data, c10::impl::PyInterpreter* pyinterpreter)
|
||||
: data_(data), pyinterpreter_(pyinterpreter) {}
|
||||
SafePyObject(SafePyObject&& other) noexcept
|
||||
: data_(std::exchange(other.data_, nullptr)),
|
||||
pyinterpreter_(other.pyinterpreter_) {}
|
||||
// For now it's not used, so we just disallow it.
|
||||
SafePyObject& operator=(SafePyObject&&) = delete;
|
||||
|
||||
SafePyObject(SafePyObject const& other)
|
||||
: data_(other.data_), pyinterpreter_(other.pyinterpreter_) {
|
||||
if (data_ != nullptr) {
|
||||
(*pyinterpreter_)->incref(data_);
|
||||
}
|
||||
}
|
||||
|
||||
SafePyObject& operator=(SafePyObject const& other) {
|
||||
if (this == &other) {
|
||||
return *this; // Handle self-assignment
|
||||
}
|
||||
if (other.data_ != nullptr) {
|
||||
(*other.pyinterpreter_)->incref(other.data_);
|
||||
}
|
||||
if (data_ != nullptr) {
|
||||
(*pyinterpreter_)->decref(data_);
|
||||
}
|
||||
data_ = other.data_;
|
||||
pyinterpreter_ = other.pyinterpreter_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
~SafePyObject() {
|
||||
if (data_ != nullptr) {
|
||||
(*pyinterpreter_)->decref(data_);
|
||||
}
|
||||
}
|
||||
|
||||
c10::impl::PyInterpreter& pyinterpreter() const {
|
||||
return *pyinterpreter_;
|
||||
}
|
||||
PyObject* ptr(const c10::impl::PyInterpreter* /*interpreter*/) const;
|
||||
|
||||
// stop tracking the current object, and return it
|
||||
PyObject* release() {
|
||||
auto rv = data_;
|
||||
data_ = nullptr;
|
||||
return rv;
|
||||
}
|
||||
|
||||
private:
|
||||
PyObject* data_;
|
||||
c10::impl::PyInterpreter* pyinterpreter_;
|
||||
};
|
||||
|
||||
// A newtype wrapper around SafePyObject for type safety when a python object
|
||||
// represents a specific type. Note that `T` is only used as a tag and isn't
|
||||
// actually used for any true purpose.
|
||||
template <typename T>
|
||||
struct SafePyObjectT : private SafePyObject {
|
||||
SafePyObjectT(PyObject* data, c10::impl::PyInterpreter* pyinterpreter)
|
||||
: SafePyObject(data, pyinterpreter) {}
|
||||
~SafePyObjectT() = default;
|
||||
SafePyObjectT(SafePyObjectT&& other) noexcept : SafePyObject(other) {}
|
||||
SafePyObjectT(SafePyObjectT const&) = delete;
|
||||
SafePyObjectT& operator=(SafePyObjectT const&) = delete;
|
||||
SafePyObjectT& operator=(SafePyObjectT&&) = delete;
|
||||
|
||||
using SafePyObject::ptr;
|
||||
using SafePyObject::pyinterpreter;
|
||||
using SafePyObject::release;
|
||||
};
|
||||
|
||||
// Like SafePyObject, but non-owning. Good for references to global PyObjects
|
||||
// that will be leaked on interpreter exit. You get a copy constructor/assign
|
||||
// this way.
|
||||
struct C10_API SafePyHandle {
|
||||
SafePyHandle() : data_(nullptr), pyinterpreter_(nullptr) {}
|
||||
SafePyHandle(PyObject* data, c10::impl::PyInterpreter* pyinterpreter)
|
||||
: data_(data), pyinterpreter_(pyinterpreter) {}
|
||||
|
||||
c10::impl::PyInterpreter& pyinterpreter() const {
|
||||
return *pyinterpreter_;
|
||||
}
|
||||
PyObject* ptr(const c10::impl::PyInterpreter* /*interpreter*/) const;
|
||||
void reset() {
|
||||
data_ = nullptr;
|
||||
pyinterpreter_ = nullptr;
|
||||
}
|
||||
operator bool() {
|
||||
return data_;
|
||||
}
|
||||
|
||||
private:
|
||||
PyObject* data_;
|
||||
c10::impl::PyInterpreter* pyinterpreter_;
|
||||
};
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,471 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include <c10/core/OptionalRef.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/SymBool.h>
|
||||
#include <c10/core/SymFloat.h>
|
||||
#include <c10/core/SymInt.h>
|
||||
#include <c10/core/SymNodeImpl.h>
|
||||
#include <c10/macros/Export.h>
|
||||
#include <c10/macros/Macros.h>
|
||||
#include <c10/util/Exception.h>
|
||||
#include <c10/util/Half.h>
|
||||
#include <c10/util/TypeCast.h>
|
||||
#include <c10/util/complex.h>
|
||||
#include <c10/util/intrusive_ptr.h>
|
||||
#include <c10/util/overflows.h>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
/**
|
||||
* Scalar represents a 0-dimensional tensor which contains a single element.
|
||||
* Unlike a tensor, numeric literals (in C++) are implicitly convertible to
|
||||
* Scalar (which is why, for example, we provide both add(Tensor) and
|
||||
* add(Scalar) overloads for many operations). It may also be used in
|
||||
* circumstances where you statically know a tensor is 0-dim and single size,
|
||||
* but don't know its type.
|
||||
*/
|
||||
class C10_API Scalar {
|
||||
public:
|
||||
Scalar() : Scalar(int64_t(0)) {}
|
||||
|
||||
void destroy() {
|
||||
if (Tag::HAS_si == tag || Tag::HAS_sd == tag || Tag::HAS_sb == tag) {
|
||||
raw::intrusive_ptr::decref(v.p);
|
||||
v.p = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
~Scalar() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
#define DEFINE_IMPLICIT_CTOR(type, name) \
|
||||
Scalar(type vv) : Scalar(vv, true) {}
|
||||
|
||||
AT_FORALL_SCALAR_TYPES_AND3(Half, BFloat16, ComplexHalf, DEFINE_IMPLICIT_CTOR)
|
||||
AT_FORALL_COMPLEX_TYPES(DEFINE_IMPLICIT_CTOR)
|
||||
AT_FORALL_FLOAT8_TYPES(DEFINE_IMPLICIT_CTOR)
|
||||
|
||||
// Helper constructors to allow Scalar creation from long and long long types
|
||||
// As std::is_same_v<long, long long> is false(except Android), one needs to
|
||||
// provide a constructor from either long or long long in addition to one from
|
||||
// int64_t
|
||||
#if defined(__APPLE__) || defined(__MACOSX)
|
||||
static_assert(
|
||||
std::is_same_v<long long, int64_t>,
|
||||
"int64_t is the same as long long on MacOS");
|
||||
Scalar(long vv) : Scalar(vv, true) {}
|
||||
#endif
|
||||
#if defined(_MSC_VER)
|
||||
static_assert(
|
||||
std::is_same_v<long long, int64_t>,
|
||||
"int64_t is the same as long long on Windows");
|
||||
Scalar(long vv) : Scalar(vv, true) {}
|
||||
#endif
|
||||
#if defined(__linux__) && !defined(__ANDROID__)
|
||||
static_assert(
|
||||
sizeof(void*) != 8 || std::is_same_v<long, int64_t>,
|
||||
"int64_t is the same as long on 64 bit Linux");
|
||||
#if LONG_MAX != INT_MAX
|
||||
Scalar(long long vv) : Scalar(vv, true) {}
|
||||
#endif /* not 32-bit system */
|
||||
#endif
|
||||
|
||||
Scalar(uint16_t vv) : Scalar(vv, true) {}
|
||||
Scalar(uint32_t vv) : Scalar(vv, true) {}
|
||||
Scalar(uint64_t vv) {
|
||||
if (vv > static_cast<uint64_t>(INT64_MAX)) {
|
||||
tag = Tag::HAS_u;
|
||||
v.u = vv;
|
||||
} else {
|
||||
tag = Tag::HAS_i;
|
||||
// NB: no need to use convert, we've already tested convertibility
|
||||
v.i = static_cast<int64_t>(vv);
|
||||
}
|
||||
}
|
||||
|
||||
#undef DEFINE_IMPLICIT_CTOR
|
||||
|
||||
// Value* is both implicitly convertible to SymbolicVariable and bool which
|
||||
// causes ambiguity error. Specialized constructor for bool resolves this
|
||||
// problem.
|
||||
template <
|
||||
typename T,
|
||||
typename std::enable_if_t<std::is_same_v<T, bool>, bool>* = nullptr>
|
||||
Scalar(T vv) : tag(Tag::HAS_b) {
|
||||
v.i = convert<int64_t, bool>(vv);
|
||||
}
|
||||
|
||||
template <
|
||||
typename T,
|
||||
typename std::enable_if_t<std::is_same_v<T, c10::SymBool>, bool>* =
|
||||
nullptr>
|
||||
Scalar(T vv) : tag(Tag::HAS_sb) {
|
||||
v.i = convert<int64_t, c10::SymBool>(vv);
|
||||
}
|
||||
|
||||
#define DEFINE_ACCESSOR(type, name) \
|
||||
type to##name() const { \
|
||||
if (Tag::HAS_d == tag) { \
|
||||
return checked_convert<type, double>(v.d, #type); \
|
||||
} else if (Tag::HAS_z == tag) { \
|
||||
return checked_convert<type, c10::complex<double>>(v.z, #type); \
|
||||
} else if (Tag::HAS_sd == tag) { \
|
||||
return checked_convert<type, double>( \
|
||||
toSymFloat().guard_float(__FILE__, __LINE__), #type); \
|
||||
} \
|
||||
if (Tag::HAS_b == tag) { \
|
||||
return checked_convert<type, bool>(v.i, #type); \
|
||||
} else if (Tag::HAS_i == tag) { \
|
||||
return checked_convert<type, int64_t>(v.i, #type); \
|
||||
} else if (Tag::HAS_u == tag) { \
|
||||
return checked_convert<type, uint64_t>(v.u, #type); \
|
||||
} else if (Tag::HAS_si == tag) { \
|
||||
return checked_convert<type, int64_t>( \
|
||||
toSymInt().guard_int(__FILE__, __LINE__), #type); \
|
||||
} else if (Tag::HAS_sb == tag) { \
|
||||
return checked_convert<type, int64_t>( \
|
||||
toSymBool().guard_bool(__FILE__, __LINE__), #type); \
|
||||
} \
|
||||
TORCH_CHECK(false) \
|
||||
}
|
||||
|
||||
// TODO: Support ComplexHalf accessor
|
||||
AT_FORALL_SCALAR_TYPES_WITH_COMPLEX(DEFINE_ACCESSOR)
|
||||
DEFINE_ACCESSOR(uint16_t, UInt16)
|
||||
DEFINE_ACCESSOR(uint32_t, UInt32)
|
||||
DEFINE_ACCESSOR(uint64_t, UInt64)
|
||||
|
||||
#undef DEFINE_ACCESSOR
|
||||
|
||||
SymInt toSymInt() const {
|
||||
if (Tag::HAS_si == tag) {
|
||||
return c10::SymInt(intrusive_ptr<SymNodeImpl>::reclaim_copy(
|
||||
static_cast<SymNodeImpl*>(v.p)));
|
||||
} else {
|
||||
return toLong();
|
||||
}
|
||||
}
|
||||
|
||||
SymFloat toSymFloat() const {
|
||||
if (Tag::HAS_sd == tag) {
|
||||
return c10::SymFloat(intrusive_ptr<SymNodeImpl>::reclaim_copy(
|
||||
static_cast<SymNodeImpl*>(v.p)));
|
||||
} else {
|
||||
return toDouble();
|
||||
}
|
||||
}
|
||||
|
||||
SymBool toSymBool() const {
|
||||
if (Tag::HAS_sb == tag) {
|
||||
return c10::SymBool(intrusive_ptr<SymNodeImpl>::reclaim_copy(
|
||||
static_cast<SymNodeImpl*>(v.p)));
|
||||
} else {
|
||||
return toBool();
|
||||
}
|
||||
}
|
||||
|
||||
// also support scalar.to<int64_t>();
|
||||
// Deleted for unsupported types, but specialized below for supported types
|
||||
template <typename T>
|
||||
T to() const = delete;
|
||||
|
||||
// audit uses of data_ptr
|
||||
const void* data_ptr() const {
|
||||
TORCH_INTERNAL_ASSERT(!isSymbolic());
|
||||
return static_cast<const void*>(&v);
|
||||
}
|
||||
|
||||
bool isFloatingPoint() const {
|
||||
return Tag::HAS_d == tag || Tag::HAS_sd == tag;
|
||||
}
|
||||
|
||||
[[deprecated(
|
||||
"isIntegral is deprecated. Please use the overload with 'includeBool' parameter instead.")]] bool
|
||||
isIntegral() const {
|
||||
return Tag::HAS_i == tag || Tag::HAS_si == tag || Tag::HAS_u == tag;
|
||||
}
|
||||
|
||||
bool isIntegral(bool includeBool) const {
|
||||
return Tag::HAS_i == tag || Tag::HAS_si == tag || Tag::HAS_u == tag ||
|
||||
(includeBool && isBoolean());
|
||||
}
|
||||
|
||||
// See Note [Meaning of HAS_u]
|
||||
bool isUnsigned() const {
|
||||
return Tag::HAS_u == tag || (Tag::HAS_i == tag && v.i >= 0);
|
||||
}
|
||||
|
||||
bool isComplex() const {
|
||||
return Tag::HAS_z == tag;
|
||||
}
|
||||
bool isBoolean() const {
|
||||
return Tag::HAS_b == tag || Tag::HAS_sb == tag;
|
||||
}
|
||||
|
||||
// you probably don't actually want these; they're mostly for testing
|
||||
bool isSymInt() const {
|
||||
return Tag::HAS_si == tag;
|
||||
}
|
||||
bool isSymFloat() const {
|
||||
return Tag::HAS_sd == tag;
|
||||
}
|
||||
bool isSymBool() const {
|
||||
return Tag::HAS_sb == tag;
|
||||
}
|
||||
|
||||
bool isSymbolic() const {
|
||||
return Tag::HAS_si == tag || Tag::HAS_sd == tag || Tag::HAS_sb == tag;
|
||||
}
|
||||
|
||||
C10_ALWAYS_INLINE Scalar& operator=(Scalar&& other) noexcept {
|
||||
if (&other == this) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
destroy();
|
||||
moveFrom(std::move(other));
|
||||
return *this;
|
||||
}
|
||||
|
||||
C10_ALWAYS_INLINE Scalar& operator=(const Scalar& other) {
|
||||
if (&other == this) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
*this = Scalar(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Scalar operator-() const;
|
||||
Scalar conj() const;
|
||||
Scalar log() const;
|
||||
|
||||
template <
|
||||
typename T,
|
||||
typename std::enable_if_t<!c10::is_complex<T>::value, int> = 0>
|
||||
bool equal(T num) const {
|
||||
if (isComplex()) {
|
||||
TORCH_INTERNAL_ASSERT(!isSymbolic());
|
||||
auto val = v.z;
|
||||
return (val.real() == num) && (val.imag() == T());
|
||||
} else if (isFloatingPoint()) {
|
||||
return toDouble() == num;
|
||||
} else if (tag == Tag::HAS_i) {
|
||||
if (overflows<T>(v.i, /* strict_unsigned */ true)) {
|
||||
return false;
|
||||
} else {
|
||||
return static_cast<T>(v.i) == num;
|
||||
}
|
||||
} else if (tag == Tag::HAS_u) {
|
||||
if (overflows<T>(v.u, /* strict_unsigned */ true)) {
|
||||
return false;
|
||||
} else {
|
||||
return static_cast<T>(v.u) == num;
|
||||
}
|
||||
} else if (tag == Tag::HAS_si) {
|
||||
TORCH_INTERNAL_ASSERT(false, "NYI SymInt equality");
|
||||
} else if (isBoolean()) {
|
||||
// boolean scalar does not equal to a non boolean value
|
||||
TORCH_INTERNAL_ASSERT(!isSymbolic());
|
||||
return false;
|
||||
} else {
|
||||
TORCH_INTERNAL_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
template <
|
||||
typename T,
|
||||
typename std::enable_if_t<c10::is_complex<T>::value, int> = 0>
|
||||
bool equal(T num) const {
|
||||
if (isComplex()) {
|
||||
TORCH_INTERNAL_ASSERT(!isSymbolic());
|
||||
return v.z == num;
|
||||
} else if (isFloatingPoint()) {
|
||||
return (toDouble() == num.real()) && (num.imag() == T());
|
||||
} else if (tag == Tag::HAS_i) {
|
||||
if (overflows<T>(v.i, /* strict_unsigned */ true)) {
|
||||
return false;
|
||||
} else {
|
||||
return static_cast<T>(v.i) == num.real() && num.imag() == T();
|
||||
}
|
||||
} else if (tag == Tag::HAS_u) {
|
||||
if (overflows<T>(v.u, /* strict_unsigned */ true)) {
|
||||
return false;
|
||||
} else {
|
||||
return static_cast<T>(v.u) == num.real() && num.imag() == T();
|
||||
}
|
||||
} else if (tag == Tag::HAS_si) {
|
||||
TORCH_INTERNAL_ASSERT(false, "NYI SymInt equality");
|
||||
} else if (isBoolean()) {
|
||||
// boolean scalar does not equal to a non boolean value
|
||||
TORCH_INTERNAL_ASSERT(!isSymbolic());
|
||||
return false;
|
||||
} else {
|
||||
TORCH_INTERNAL_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
bool equal(bool num) const {
|
||||
if (isBoolean()) {
|
||||
TORCH_INTERNAL_ASSERT(!isSymbolic());
|
||||
return static_cast<bool>(v.i) == num;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ScalarType type() const {
|
||||
if (isComplex()) {
|
||||
return ScalarType::ComplexDouble;
|
||||
} else if (isFloatingPoint()) {
|
||||
return ScalarType::Double;
|
||||
} else if (isIntegral(/*includeBool=*/false)) {
|
||||
// Represent all integers as long, UNLESS it is unsigned and therefore
|
||||
// unrepresentable as long
|
||||
if (Tag::HAS_u == tag) {
|
||||
return ScalarType::UInt64;
|
||||
}
|
||||
return ScalarType::Long;
|
||||
} else if (isBoolean()) {
|
||||
return ScalarType::Bool;
|
||||
} else {
|
||||
TORCH_CHECK(false, "Unknown scalar type.");
|
||||
}
|
||||
}
|
||||
|
||||
Scalar(Scalar&& rhs) noexcept : tag(rhs.tag) {
|
||||
moveFrom(std::move(rhs));
|
||||
}
|
||||
|
||||
Scalar(const Scalar& rhs) : tag(rhs.tag), v(rhs.v) {
|
||||
if (isSymbolic()) {
|
||||
c10::raw::intrusive_ptr::incref(v.p);
|
||||
}
|
||||
}
|
||||
|
||||
Scalar(c10::SymInt si) {
|
||||
if (auto m = si.maybe_as_int()) {
|
||||
tag = Tag::HAS_i;
|
||||
v.i = *m;
|
||||
} else {
|
||||
tag = Tag::HAS_si;
|
||||
v.p = std::move(si).release();
|
||||
}
|
||||
}
|
||||
|
||||
Scalar(c10::SymFloat sd) {
|
||||
if (sd.is_symbolic()) {
|
||||
tag = Tag::HAS_sd;
|
||||
v.p = std::move(sd).release();
|
||||
} else {
|
||||
tag = Tag::HAS_d;
|
||||
v.d = sd.as_float_unchecked();
|
||||
}
|
||||
}
|
||||
|
||||
Scalar(c10::SymBool sb) {
|
||||
if (auto m = sb.maybe_as_bool()) {
|
||||
tag = Tag::HAS_b;
|
||||
v.i = *m;
|
||||
} else {
|
||||
tag = Tag::HAS_sb;
|
||||
v.p = std::move(sb).release();
|
||||
}
|
||||
}
|
||||
|
||||
// We can't set v in the initializer list using the
|
||||
// syntax v{ .member = ... } because it doesn't work on MSVC
|
||||
private:
|
||||
enum class Tag { HAS_d, HAS_i, HAS_u, HAS_z, HAS_b, HAS_sd, HAS_si, HAS_sb };
|
||||
|
||||
// Note [Meaning of HAS_u]
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// HAS_u is a bit special. On its face, it just means that we
|
||||
// are holding an unsigned integer. However, we generally don't
|
||||
// distinguish between different bit sizes in Scalar (e.g., we represent
|
||||
// float as double), instead, it represents a mathematical notion
|
||||
// of some quantity (integral versus floating point). So actually,
|
||||
// HAS_u is used solely to represent unsigned integers that could
|
||||
// not be represented as a signed integer. That means only uint64_t
|
||||
// potentially can get this tag; smaller types like uint8_t fits into a
|
||||
// regular int and so for BC reasons we keep as an int.
|
||||
|
||||
// NB: assumes that self has already been cleared
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
|
||||
C10_ALWAYS_INLINE void moveFrom(Scalar&& rhs) noexcept {
|
||||
v = rhs.v;
|
||||
tag = rhs.tag;
|
||||
if (rhs.tag == Tag::HAS_si || rhs.tag == Tag::HAS_sd ||
|
||||
rhs.tag == Tag::HAS_sb) {
|
||||
// Move out of scalar
|
||||
rhs.tag = Tag::HAS_i;
|
||||
rhs.v.i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Tag tag;
|
||||
|
||||
union v_t {
|
||||
double d{};
|
||||
int64_t i;
|
||||
// See Note [Meaning of HAS_u]
|
||||
uint64_t u;
|
||||
c10::complex<double> z;
|
||||
c10::intrusive_ptr_target* p;
|
||||
// NOLINTNEXTLINE(modernize-use-equals-default)
|
||||
v_t() {} // default constructor
|
||||
} v;
|
||||
|
||||
template <
|
||||
typename T,
|
||||
typename std::enable_if_t<
|
||||
std::is_integral_v<T> && !std::is_same_v<T, bool>,
|
||||
bool>* = nullptr>
|
||||
Scalar(T vv, bool /*unused*/) : tag(Tag::HAS_i) {
|
||||
v.i = convert<decltype(v.i), T>(vv);
|
||||
}
|
||||
|
||||
template <
|
||||
typename T,
|
||||
typename std::enable_if_t<
|
||||
!std::is_integral_v<T> && !c10::is_complex<T>::value,
|
||||
bool>* = nullptr>
|
||||
Scalar(T vv, bool /*unused*/) : tag(Tag::HAS_d) {
|
||||
v.d = convert<decltype(v.d), T>(vv);
|
||||
}
|
||||
|
||||
template <
|
||||
typename T,
|
||||
typename std::enable_if_t<c10::is_complex<T>::value, bool>* = nullptr>
|
||||
Scalar(T vv, bool /*unused*/) : tag(Tag::HAS_z) {
|
||||
v.z = convert<decltype(v.z), T>(vv);
|
||||
}
|
||||
};
|
||||
|
||||
using OptionalScalarRef = c10::OptionalRef<Scalar>;
|
||||
|
||||
// define the scalar.to<int64_t>() specializations
|
||||
#define DEFINE_TO(T, name) \
|
||||
template <> \
|
||||
inline T Scalar::to<T>() const { \
|
||||
return to##name(); \
|
||||
}
|
||||
AT_FORALL_SCALAR_TYPES_WITH_COMPLEX(DEFINE_TO)
|
||||
DEFINE_TO(uint16_t, UInt16)
|
||||
DEFINE_TO(uint32_t, UInt32)
|
||||
DEFINE_TO(uint64_t, UInt64)
|
||||
#undef DEFINE_TO
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,310 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/util/BFloat16.h>
|
||||
#include <c10/util/Exception.h>
|
||||
#include <c10/util/Float4_e2m1fn_x2.h>
|
||||
#include <c10/util/Float8_e4m3fn.h>
|
||||
#include <c10/util/Float8_e4m3fnuz.h>
|
||||
#include <c10/util/Float8_e5m2.h>
|
||||
#include <c10/util/Float8_e5m2fnuz.h>
|
||||
#include <c10/util/Float8_e8m0fnu.h>
|
||||
#include <c10/util/Half.h>
|
||||
#include <c10/util/bits.h>
|
||||
#include <c10/util/complex.h>
|
||||
#include <c10/util/qint32.h>
|
||||
#include <c10/util/qint8.h>
|
||||
#include <c10/util/quint2x4.h>
|
||||
#include <c10/util/quint4x2.h>
|
||||
#include <c10/util/quint8.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <limits>
|
||||
#include <ostream>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <torch/headeronly/core/ScalarType.h>
|
||||
|
||||
C10_DIAGNOSTIC_PUSH_AND_IGNORED_IF_DEFINED("-Wswitch-enum")
|
||||
C10_DIAGNOSTIC_PUSH_AND_IGNORED_IF_DEFINED("-Wswitch-default")
|
||||
|
||||
namespace c10 {
|
||||
|
||||
// See [dtype Macros note] in torch/headeronly/core/ScalarType.h
|
||||
// regarding macros.
|
||||
|
||||
#define DEFINE_CONSTANT(_, name) \
|
||||
constexpr ScalarType k##name = ScalarType::name;
|
||||
|
||||
// NOLINTNEXTLINE(clang-diagnostic-unused-const-variable)
|
||||
AT_FORALL_SCALAR_TYPES_WITH_COMPLEX_AND_QINTS(DEFINE_CONSTANT)
|
||||
#undef DEFINE_CONSTANT
|
||||
|
||||
inline size_t elementSize(ScalarType t) {
|
||||
#define CASE_ELEMENTSIZE_CASE(ctype, name) \
|
||||
case ScalarType::name: \
|
||||
return sizeof(ctype);
|
||||
|
||||
switch (t) {
|
||||
AT_FORALL_SCALAR_TYPES_WITH_COMPLEX_AND_QINTS(CASE_ELEMENTSIZE_CASE)
|
||||
default:
|
||||
TORCH_CHECK(false, "Unknown ScalarType");
|
||||
}
|
||||
#undef CASE_ELEMENTSIZE_CASE
|
||||
}
|
||||
|
||||
inline ScalarType opaqueScalarType(ScalarType t) {
|
||||
auto esize = elementSize(t);
|
||||
ScalarType result;
|
||||
switch (esize) {
|
||||
case 1:
|
||||
result = kByte;
|
||||
break;
|
||||
case 2:
|
||||
result = kUInt16;
|
||||
break;
|
||||
case 4:
|
||||
result = kUInt32;
|
||||
break;
|
||||
case 8:
|
||||
result = kUInt64;
|
||||
break;
|
||||
case 16:
|
||||
result = kComplexDouble;
|
||||
break;
|
||||
default:
|
||||
TORCH_CHECK(false, "Unknown ScalarType");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline bool isIntegralType(ScalarType t, bool includeBool) {
|
||||
bool isIntegral =
|
||||
(t == ScalarType::Byte || t == ScalarType::Char || t == ScalarType::Int ||
|
||||
t == ScalarType::Long || t == ScalarType::Short ||
|
||||
t == ScalarType::UInt16 || t == ScalarType::UInt32 ||
|
||||
t == ScalarType::UInt64);
|
||||
|
||||
return isIntegral || (includeBool && t == ScalarType::Bool);
|
||||
}
|
||||
|
||||
[[deprecated(
|
||||
"isIntegralType is deprecated. Please use the overload with 'includeBool' parameter instead.")]] inline bool
|
||||
isIntegralType(ScalarType t) {
|
||||
return isIntegralType(t, /*includeBool=*/false);
|
||||
}
|
||||
|
||||
inline bool isFloat8Type(ScalarType t) {
|
||||
return t == ScalarType::Float8_e5m2 || t == ScalarType::Float8_e5m2fnuz ||
|
||||
t == ScalarType::Float8_e4m3fn || t == ScalarType::Float8_e4m3fnuz ||
|
||||
t == ScalarType::Float8_e8m0fnu;
|
||||
}
|
||||
|
||||
inline bool isReducedFloatingType(ScalarType t) {
|
||||
return t == ScalarType::Half || t == ScalarType::BFloat16 ||
|
||||
isFloat8Type(t) || t == ScalarType::Float4_e2m1fn_x2;
|
||||
}
|
||||
|
||||
inline bool isFloatingType(ScalarType t) {
|
||||
return t == ScalarType::Double || t == ScalarType::Float ||
|
||||
isReducedFloatingType(t);
|
||||
}
|
||||
|
||||
inline bool isComplexType(ScalarType t) {
|
||||
return (
|
||||
t == ScalarType::ComplexHalf || t == ScalarType::ComplexFloat ||
|
||||
t == ScalarType::ComplexDouble);
|
||||
}
|
||||
|
||||
inline bool isBitsType(ScalarType t) {
|
||||
return t == ScalarType::Bits1x8 || t == ScalarType::Bits2x4 ||
|
||||
t == ScalarType::Bits4x2 || t == ScalarType::Bits8 ||
|
||||
t == ScalarType::Bits16;
|
||||
}
|
||||
|
||||
inline bool isBarebonesUnsignedType(ScalarType t) {
|
||||
return t == ScalarType::UInt1 || t == ScalarType::UInt2 ||
|
||||
t == ScalarType::UInt3 || t == ScalarType::UInt4 ||
|
||||
t == ScalarType::UInt5 || t == ScalarType::UInt6 ||
|
||||
t == ScalarType::UInt7 || t == ScalarType::UInt16 ||
|
||||
t == ScalarType::UInt32 || t == ScalarType::UInt64;
|
||||
}
|
||||
|
||||
inline ScalarType toQIntType(ScalarType t) {
|
||||
switch (t) {
|
||||
case ScalarType::Byte:
|
||||
return ScalarType::QUInt8;
|
||||
case ScalarType::Char:
|
||||
return ScalarType::QInt8;
|
||||
case ScalarType::Int:
|
||||
return ScalarType::QInt32;
|
||||
default:
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool isSignedType(ScalarType t) {
|
||||
#define CASE_ISSIGNED(name) \
|
||||
case ScalarType::name: \
|
||||
return std::numeric_limits< \
|
||||
::c10::impl::ScalarTypeToCPPTypeT<ScalarType::name>>::is_signed;
|
||||
|
||||
// TODO(#146647): If we expect to have numeric_limits for everything,
|
||||
// let's just have a big macro for the whole thing.
|
||||
// If we're hardcoding it, let's just use the macro and a "true"/"false"
|
||||
// below?
|
||||
switch (t) {
|
||||
case ScalarType::QInt8:
|
||||
case ScalarType::QUInt8:
|
||||
case ScalarType::QInt32:
|
||||
case ScalarType::QUInt4x2:
|
||||
case ScalarType::QUInt2x4:
|
||||
TORCH_CHECK(false, "isSignedType not supported for quantized types");
|
||||
case ScalarType::Bits1x8:
|
||||
case ScalarType::Bits2x4:
|
||||
case ScalarType::Bits4x2:
|
||||
case ScalarType::Bits8:
|
||||
case ScalarType::Bits16:
|
||||
TORCH_CHECK(false, "Bits types are undefined");
|
||||
CASE_ISSIGNED(UInt16);
|
||||
CASE_ISSIGNED(UInt32);
|
||||
CASE_ISSIGNED(UInt64);
|
||||
CASE_ISSIGNED(BFloat16);
|
||||
CASE_ISSIGNED(Float8_e5m2);
|
||||
CASE_ISSIGNED(Float8_e5m2fnuz);
|
||||
CASE_ISSIGNED(Float8_e4m3fn);
|
||||
CASE_ISSIGNED(Float8_e4m3fnuz);
|
||||
CASE_ISSIGNED(Float8_e8m0fnu);
|
||||
CASE_ISSIGNED(Byte);
|
||||
CASE_ISSIGNED(Char);
|
||||
CASE_ISSIGNED(Short);
|
||||
CASE_ISSIGNED(Int);
|
||||
CASE_ISSIGNED(Long);
|
||||
CASE_ISSIGNED(Half);
|
||||
CASE_ISSIGNED(Float);
|
||||
CASE_ISSIGNED(Double);
|
||||
CASE_ISSIGNED(ComplexHalf);
|
||||
CASE_ISSIGNED(ComplexFloat);
|
||||
CASE_ISSIGNED(ComplexDouble);
|
||||
CASE_ISSIGNED(Bool);
|
||||
case ScalarType::Int1:
|
||||
case ScalarType::Int2:
|
||||
case ScalarType::Int3:
|
||||
case ScalarType::Int4:
|
||||
case ScalarType::Int5:
|
||||
case ScalarType::Int6:
|
||||
case ScalarType::Int7:
|
||||
case ScalarType::Float4_e2m1fn_x2:
|
||||
return true;
|
||||
case ScalarType::UInt1:
|
||||
case ScalarType::UInt2:
|
||||
case ScalarType::UInt3:
|
||||
case ScalarType::UInt4:
|
||||
case ScalarType::UInt5:
|
||||
case ScalarType::UInt6:
|
||||
case ScalarType::UInt7:
|
||||
return false;
|
||||
case ScalarType::Undefined:
|
||||
case ScalarType::NumOptions:
|
||||
break;
|
||||
// Do not add default here, but rather define behavior of every new entry
|
||||
// here. `-Wswitch-enum` would raise a warning in those cases.
|
||||
// TODO: get PyTorch to adopt exhaustive switches by default with a way to
|
||||
// opt specific switches to being non-exhaustive.
|
||||
// Exhaustive:
|
||||
// `-Wswitch-enum`, `-Wswitch-default`, `-Wno-covered-switch-default`
|
||||
// Non-Exhaustive:
|
||||
// `-Wno-switch-enum`, `-Wswitch-default`, `-Wcovered-switch-default`
|
||||
}
|
||||
TORCH_CHECK(false, "Unknown ScalarType ", t);
|
||||
#undef CASE_ISSIGNED
|
||||
}
|
||||
|
||||
inline bool isUnderlying(ScalarType type, ScalarType qtype) {
|
||||
return type == toUnderlying(qtype);
|
||||
}
|
||||
|
||||
inline ScalarType toRealValueType(ScalarType t) {
|
||||
switch (t) {
|
||||
case ScalarType::ComplexHalf:
|
||||
return ScalarType::Half;
|
||||
case ScalarType::ComplexFloat:
|
||||
return ScalarType::Float;
|
||||
case ScalarType::ComplexDouble:
|
||||
return ScalarType::Double;
|
||||
default:
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
inline ScalarType toComplexType(ScalarType t) {
|
||||
switch (t) {
|
||||
case ScalarType::BFloat16:
|
||||
// BFloat16 has range equivalent to Float,
|
||||
// so we map it to ComplexFloat.
|
||||
return ScalarType::ComplexFloat;
|
||||
case ScalarType::Half:
|
||||
return ScalarType::ComplexHalf;
|
||||
case ScalarType::Float:
|
||||
return ScalarType::ComplexFloat;
|
||||
case ScalarType::Double:
|
||||
return ScalarType::ComplexDouble;
|
||||
case ScalarType::ComplexHalf:
|
||||
return ScalarType::ComplexHalf;
|
||||
case ScalarType::ComplexFloat:
|
||||
return ScalarType::ComplexFloat;
|
||||
case ScalarType::ComplexDouble:
|
||||
return ScalarType::ComplexDouble;
|
||||
default:
|
||||
TORCH_CHECK(false, "Unknown Complex ScalarType for ", t);
|
||||
}
|
||||
}
|
||||
|
||||
// see tensor_attributes.rst for detailed explanation and examples
|
||||
// of casting rules.
|
||||
inline bool canCast(const ScalarType from, const ScalarType to) {
|
||||
// We disallow complex -> non complex, e.g., float_tensor *= complex is
|
||||
// disallowed.
|
||||
if (isComplexType(from) && !isComplexType(to)) {
|
||||
return false;
|
||||
}
|
||||
// We disallow float -> integral, e.g., int_tensor *= float is disallowed.
|
||||
if (isFloatingType(from) && isIntegralType(to, false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Treat bool as a distinct "category," to be consistent with type promotion
|
||||
// rules (e.g. `bool_tensor + 5 -> int64_tensor`). If `5` was in the same
|
||||
// category as `bool_tensor`, we would not promote. Differing categories
|
||||
// implies `bool_tensor += 5` is disallowed.
|
||||
//
|
||||
// NB: numpy distinguishes "unsigned" as a category to get the desired
|
||||
// `bool_tensor + 5 -> int64_tensor` behavior. We don't, because:
|
||||
// * We don't want the performance hit of checking the runtime sign of
|
||||
// Scalars.
|
||||
// * `uint8_tensor + 5 -> int64_tensor` would be undesirable.
|
||||
if (from != ScalarType::Bool && to == ScalarType::Bool) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
C10_API ScalarType promoteTypes(ScalarType a, ScalarType b);
|
||||
|
||||
// Returns a pair of strings representing the names for each dtype.
|
||||
// The returned pair is (name, legacy_name_if_applicable)
|
||||
C10_API std::pair<std::string, std::string> getDtypeNames(
|
||||
c10::ScalarType scalarType);
|
||||
|
||||
// Returns a map of string name to dtype.
|
||||
C10_API const std::unordered_map<std::string, ScalarType>& getStringToDtypeMap();
|
||||
|
||||
} // namespace c10
|
||||
|
||||
C10_DIAGNOSTIC_POP()
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/util/Optional.h>
|
||||
#include <c10/util/typeid.h>
|
||||
|
||||
// these just expose TypeMeta/ScalarType bridge functions in c10
|
||||
// TODO move to typeid.h (or codemod away) when TypeMeta et al
|
||||
// are moved from caffe2 to c10 (see note at top of typeid.h)
|
||||
|
||||
namespace c10 {
|
||||
|
||||
/**
|
||||
* convert ScalarType enum values to TypeMeta handles
|
||||
*/
|
||||
inline caffe2::TypeMeta scalarTypeToTypeMeta(ScalarType scalar_type) {
|
||||
return caffe2::TypeMeta::fromScalarType(scalar_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* convert TypeMeta handles to ScalarType enum values
|
||||
*/
|
||||
inline ScalarType typeMetaToScalarType(caffe2::TypeMeta dtype) {
|
||||
return dtype.toScalarType();
|
||||
}
|
||||
|
||||
/**
|
||||
* typeMetaToScalarType(), lifted to optional
|
||||
*/
|
||||
inline std::optional<at::ScalarType> optTypeMetaToScalarType(
|
||||
std::optional<caffe2::TypeMeta> type_meta) {
|
||||
if (!type_meta.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return type_meta->toScalarType();
|
||||
}
|
||||
|
||||
/**
|
||||
* convenience: equality across TypeMeta/ScalarType conversion
|
||||
*/
|
||||
inline bool operator==(ScalarType t, caffe2::TypeMeta m) {
|
||||
return m.isScalarType(t);
|
||||
}
|
||||
|
||||
inline bool operator==(caffe2::TypeMeta m, ScalarType t) {
|
||||
return t == m;
|
||||
}
|
||||
|
||||
inline bool operator!=(ScalarType t, caffe2::TypeMeta m) {
|
||||
return !(t == m);
|
||||
}
|
||||
|
||||
inline bool operator!=(caffe2::TypeMeta m, ScalarType t) {
|
||||
return !(t == m);
|
||||
}
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,297 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/Allocator.h>
|
||||
#include <c10/core/Device.h>
|
||||
#include <c10/core/DeviceType.h>
|
||||
#include <c10/core/StorageImpl.h>
|
||||
#include <c10/core/SymInt.h>
|
||||
#include <c10/macros/Export.h>
|
||||
#include <c10/util/Exception.h>
|
||||
#include <c10/util/ExclusivelyOwned.h>
|
||||
#include <c10/util/MaybeOwned.h>
|
||||
#include <c10/util/UniqueVoidPtr.h>
|
||||
#include <c10/util/intrusive_ptr.h>
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
struct Storage;
|
||||
|
||||
C10_API bool isSharedStorageAlias(
|
||||
const Storage& storage0,
|
||||
const Storage& storage1);
|
||||
|
||||
struct C10_API Storage {
|
||||
public:
|
||||
struct use_byte_size_t {};
|
||||
struct unsafe_borrow_t {
|
||||
explicit unsafe_borrow_t() = default;
|
||||
};
|
||||
|
||||
Storage() = default;
|
||||
Storage(c10::intrusive_ptr<StorageImpl> ptr)
|
||||
: storage_impl_(std::move(ptr)) {}
|
||||
|
||||
// Allocates memory buffer using given allocator and creates a storage with it
|
||||
Storage(
|
||||
use_byte_size_t /*use_byte_size*/,
|
||||
const SymInt& size_bytes,
|
||||
Allocator* allocator = nullptr,
|
||||
bool resizable = false)
|
||||
: storage_impl_(c10::make_intrusive<StorageImpl>(
|
||||
StorageImpl::use_byte_size_t(),
|
||||
size_bytes,
|
||||
allocator,
|
||||
resizable)) {}
|
||||
|
||||
// Creates storage with pre-allocated memory buffer. Allocator is given for
|
||||
// potential future reallocations, however it can be nullptr if the storage
|
||||
// is non-resizable
|
||||
Storage(
|
||||
use_byte_size_t /*use_byte_size*/,
|
||||
size_t size_bytes,
|
||||
at::DataPtr data_ptr,
|
||||
at::Allocator* allocator = nullptr,
|
||||
bool resizable = false)
|
||||
: storage_impl_(c10::make_intrusive<StorageImpl>(
|
||||
StorageImpl::use_byte_size_t(),
|
||||
size_bytes,
|
||||
std::move(data_ptr),
|
||||
allocator,
|
||||
resizable)) {}
|
||||
|
||||
// Creates storage with pre-allocated memory buffer. Allocator is given for
|
||||
// potential future reallocations, however it can be nullptr if the storage
|
||||
// is non-resizable
|
||||
Storage(
|
||||
use_byte_size_t /*use_byte_size*/,
|
||||
SymInt size_bytes,
|
||||
at::DataPtr data_ptr,
|
||||
at::Allocator* allocator = nullptr,
|
||||
bool resizable = false)
|
||||
: storage_impl_(c10::make_intrusive<StorageImpl>(
|
||||
StorageImpl::use_byte_size_t(),
|
||||
std::move(size_bytes),
|
||||
std::move(data_ptr),
|
||||
allocator,
|
||||
resizable)) {}
|
||||
|
||||
protected:
|
||||
explicit Storage(unsafe_borrow_t /*unused*/, const Storage& rhs)
|
||||
: storage_impl_(c10::intrusive_ptr<c10::StorageImpl>::reclaim(
|
||||
rhs.storage_impl_.get())) {}
|
||||
|
||||
friend MaybeOwnedTraits<Storage>;
|
||||
|
||||
public:
|
||||
// Legacy constructor for partially initialized (dtype or memory) storages
|
||||
// that can be temporarily created with Caffe2 APIs. See the note on top of
|
||||
// TensorImpl.h for details.
|
||||
static Storage create_legacy(at::Device device) {
|
||||
auto allocator = GetAllocator(device.type());
|
||||
return Storage(c10::make_intrusive<StorageImpl>(
|
||||
StorageImpl::use_byte_size_t(),
|
||||
0,
|
||||
allocator->allocate(0), // materialize a non-default Device.
|
||||
allocator,
|
||||
true));
|
||||
}
|
||||
|
||||
// Mimic create_legacy, but without requiring a newly-created StorageImpl.
|
||||
void reset_legacy() {
|
||||
TORCH_CHECK(resizable() && allocator());
|
||||
set_nbytes(0);
|
||||
set_data_ptr_noswap(allocator()->allocate(0));
|
||||
}
|
||||
|
||||
// TODO: remove later
|
||||
void set_nbytes(size_t size_bytes) const {
|
||||
storage_impl_->set_nbytes(size_bytes);
|
||||
}
|
||||
|
||||
void set_nbytes(c10::SymInt size_bytes) const {
|
||||
storage_impl_->set_nbytes(std::move(size_bytes));
|
||||
}
|
||||
|
||||
bool resizable() const {
|
||||
return storage_impl_->resizable();
|
||||
}
|
||||
|
||||
size_t nbytes() const {
|
||||
return storage_impl_->nbytes();
|
||||
}
|
||||
|
||||
SymInt sym_nbytes() const {
|
||||
return storage_impl_->sym_nbytes();
|
||||
}
|
||||
// get() use here is to get const-correctness
|
||||
|
||||
const void* data() const {
|
||||
return storage_impl_->data();
|
||||
}
|
||||
|
||||
void* mutable_data() const {
|
||||
return storage_impl_->mutable_data();
|
||||
}
|
||||
|
||||
at::DataPtr& mutable_data_ptr() const {
|
||||
return storage_impl_->mutable_data_ptr();
|
||||
}
|
||||
|
||||
const at::DataPtr& data_ptr() const {
|
||||
return storage_impl_->data_ptr();
|
||||
}
|
||||
|
||||
// Returns the previous data_ptr
|
||||
at::DataPtr set_data_ptr(at::DataPtr&& data_ptr) const {
|
||||
return storage_impl_->set_data_ptr(std::move(data_ptr));
|
||||
}
|
||||
|
||||
void set_data_ptr_noswap(at::DataPtr&& data_ptr) const {
|
||||
storage_impl_->set_data_ptr_noswap(std::move(data_ptr));
|
||||
}
|
||||
|
||||
void swap_data_ptr(Storage& other) const {
|
||||
storage_impl_->swap_data_ptr(*other.storage_impl_);
|
||||
}
|
||||
|
||||
DeviceType device_type() const {
|
||||
return storage_impl_->device_type();
|
||||
}
|
||||
|
||||
at::Allocator* allocator() const {
|
||||
return storage_impl_->allocator();
|
||||
}
|
||||
|
||||
at::Device device() const {
|
||||
return storage_impl_->device();
|
||||
}
|
||||
|
||||
StorageImpl* unsafeReleaseStorageImpl() {
|
||||
return storage_impl_.release();
|
||||
}
|
||||
|
||||
StorageImpl* unsafeGetStorageImpl() const noexcept {
|
||||
return storage_impl_.get();
|
||||
}
|
||||
|
||||
c10::weak_intrusive_ptr<StorageImpl> getWeakStorageImpl() const {
|
||||
return c10::weak_intrusive_ptr<StorageImpl>(storage_impl_);
|
||||
}
|
||||
|
||||
operator bool() const {
|
||||
return storage_impl_;
|
||||
}
|
||||
|
||||
size_t use_count() const {
|
||||
return storage_impl_.use_count();
|
||||
}
|
||||
|
||||
inline bool unique() const {
|
||||
return storage_impl_.unique();
|
||||
}
|
||||
|
||||
bool is_alias_of(const Storage& other) const {
|
||||
return (
|
||||
storage_impl_ == other.storage_impl_ ||
|
||||
isSharedStorageAlias(*this, other));
|
||||
}
|
||||
|
||||
void UniqueStorageShareExternalPointer(
|
||||
void* src,
|
||||
size_t capacity,
|
||||
DeleterFnPtr d = nullptr) {
|
||||
if (!storage_impl_.unique()) {
|
||||
TORCH_CHECK(
|
||||
false,
|
||||
"UniqueStorageShareExternalPointer can only be called when use_count == 1");
|
||||
}
|
||||
storage_impl_->UniqueStorageShareExternalPointer(src, capacity, d);
|
||||
}
|
||||
|
||||
void UniqueStorageShareExternalPointer(
|
||||
at::DataPtr&& data_ptr,
|
||||
size_t capacity) {
|
||||
if (!storage_impl_.unique()) {
|
||||
TORCH_CHECK(
|
||||
false,
|
||||
"UniqueStorageShareExternalPointer can only be called when use_count == 1");
|
||||
}
|
||||
storage_impl_->UniqueStorageShareExternalPointer(
|
||||
std::move(data_ptr), capacity);
|
||||
}
|
||||
|
||||
protected:
|
||||
c10::intrusive_ptr<StorageImpl> storage_impl_;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct MaybeOwnedTraits<c10::Storage> {
|
||||
using owned_type = c10::Storage;
|
||||
using borrow_type = c10::Storage;
|
||||
|
||||
static borrow_type createBorrow(const owned_type& from) {
|
||||
return borrow_type(borrow_type::unsafe_borrow_t{}, from);
|
||||
}
|
||||
|
||||
static void assignBorrow(borrow_type& lhs, const borrow_type& rhs) {
|
||||
lhs.unsafeReleaseStorageImpl();
|
||||
lhs = borrow_type(borrow_type::unsafe_borrow_t{}, rhs);
|
||||
}
|
||||
|
||||
static void destroyBorrow(borrow_type& toDestroy) {
|
||||
toDestroy.unsafeReleaseStorageImpl(); // "leak" it, but it was already +0.
|
||||
}
|
||||
|
||||
static const owned_type& referenceFromBorrow(const borrow_type& borrow) {
|
||||
return borrow;
|
||||
}
|
||||
|
||||
static const owned_type* pointerFromBorrow(const borrow_type& borrow) {
|
||||
return &borrow;
|
||||
}
|
||||
|
||||
static bool debugBorrowIsValid(const borrow_type& /*borrow*/) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ExclusivelyOwnedTraits<c10::Storage> {
|
||||
using repr_type = c10::Storage;
|
||||
using pointer_type = c10::Storage*;
|
||||
using const_pointer_type = const c10::Storage*;
|
||||
|
||||
static repr_type nullRepr() {
|
||||
return c10::Storage();
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
static repr_type createInPlace(Args&&... args) {
|
||||
return c10::Storage(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
static repr_type moveToRepr(c10::Storage&& x) {
|
||||
return std::move(x);
|
||||
}
|
||||
|
||||
static c10::Storage take(c10::Storage& x) {
|
||||
return std::move(x);
|
||||
}
|
||||
|
||||
static pointer_type getImpl(repr_type& x) {
|
||||
return &x;
|
||||
}
|
||||
|
||||
static const_pointer_type getImpl(const repr_type& x) {
|
||||
return &x;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,424 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/Allocator.h>
|
||||
#include <c10/core/Device.h>
|
||||
#include <c10/core/DeviceType.h>
|
||||
#include <c10/core/SymInt.h>
|
||||
#include <c10/core/impl/COW.h>
|
||||
#include <c10/core/impl/COWDeleter.h>
|
||||
#include <c10/core/impl/PyObjectSlot.h>
|
||||
#include <c10/macros/Export.h>
|
||||
#include <c10/util/Exception.h>
|
||||
#include <c10/util/UniqueVoidPtr.h>
|
||||
#include <c10/util/intrusive_ptr.h>
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
[[noreturn]] C10_API void throwNullDataPtrError();
|
||||
C10_API void warnDeprecatedDataPtr();
|
||||
|
||||
// Used in StorageImpl to store extra metadata.
|
||||
// Currently used only for storing a custom error message
|
||||
// used when throwing an exception when data_ptr is accessed.
|
||||
struct C10_API StorageExtraMeta {
|
||||
std::optional<std::string> custom_data_ptr_error_msg_ = std::nullopt;
|
||||
};
|
||||
|
||||
// A storage represents the underlying backing data buffer for a
|
||||
// tensor. This concept was inherited from the original Torch7
|
||||
// codebase; we'd kind of like to get rid of the concept
|
||||
// (see https://github.com/pytorch/pytorch/issues/14797) but
|
||||
// it's hard work and no one has gotten around to doing it.
|
||||
//
|
||||
// NB: storage is supposed to uniquely own a data pointer; e.g.,
|
||||
// two non-null data pointers alias if and only if they are from
|
||||
// the same storage. Technically you can violate this invariant
|
||||
// (e.g., you can create a non-owning StorageImpl with at::from_blob)
|
||||
// but a lot of things won't work correctly, including:
|
||||
//
|
||||
// - An ordinary deleter on such a storage is wrong, because normal deleters
|
||||
// assume unique ownership, but if you have two storages at the same data,
|
||||
// that implies there is some sort of shared ownership. So your deleter would
|
||||
// have to actually be internally doing some sort of refcount thing
|
||||
// - Deepcopy in Python side relies on storage equality and not data pointer
|
||||
// equality; so if there are two separate storages pointing to the same data,
|
||||
// the data will actually get duplicated in that case (one data ptr before,
|
||||
// two data ptrs after)
|
||||
// - Version counts won't work correctly, because we do all VC tracking at the
|
||||
// level of storages (unless you explicitly disconnect the VC with detach);
|
||||
// mutation because data pointers are the same are totally untracked
|
||||
struct C10_API StorageImpl : public c10::intrusive_ptr_target {
|
||||
public:
|
||||
struct use_byte_size_t {};
|
||||
|
||||
StorageImpl(
|
||||
use_byte_size_t /*use_byte_size*/,
|
||||
SymInt size_bytes,
|
||||
at::DataPtr data_ptr,
|
||||
at::Allocator* allocator,
|
||||
bool resizable)
|
||||
: data_ptr_(std::move(data_ptr)),
|
||||
size_bytes_(std::move(size_bytes)),
|
||||
size_bytes_is_heap_allocated_(size_bytes_.is_heap_allocated()),
|
||||
resizable_(resizable),
|
||||
received_cuda_(false),
|
||||
allocator_(allocator) {
|
||||
if (resizable) {
|
||||
TORCH_INTERNAL_ASSERT(
|
||||
allocator_, "For resizable storage, allocator must be provided");
|
||||
}
|
||||
refresh_has_data_ptr_check();
|
||||
}
|
||||
|
||||
StorageImpl(
|
||||
use_byte_size_t /*use_byte_size*/,
|
||||
const SymInt& size_bytes,
|
||||
at::Allocator* allocator,
|
||||
bool resizable)
|
||||
: StorageImpl(
|
||||
use_byte_size_t(),
|
||||
size_bytes,
|
||||
size_bytes.is_heap_allocated()
|
||||
? allocator->allocate(0)
|
||||
: allocator->allocate(size_bytes.as_int_unchecked()),
|
||||
allocator,
|
||||
resizable) {}
|
||||
|
||||
StorageImpl& operator=(StorageImpl&& other) = delete;
|
||||
StorageImpl& operator=(const StorageImpl&) = delete;
|
||||
StorageImpl() = delete;
|
||||
StorageImpl(StorageImpl&& other) = delete;
|
||||
StorageImpl(const StorageImpl&) = delete;
|
||||
~StorageImpl() override = default;
|
||||
|
||||
void reset() {
|
||||
data_ptr_.clear();
|
||||
size_bytes_ = 0;
|
||||
size_bytes_is_heap_allocated_ = false;
|
||||
}
|
||||
|
||||
// Destructor doesn't call release_resources because it's
|
||||
// unnecessary; don't forget to change that if needed!
|
||||
void release_resources() override {
|
||||
data_ptr_.clear();
|
||||
}
|
||||
|
||||
void incref_pyobject() const noexcept final;
|
||||
|
||||
void decref_pyobject() const noexcept final;
|
||||
|
||||
bool try_incref_pyobject() const noexcept final;
|
||||
|
||||
size_t nbytes() const {
|
||||
// OK to do this instead of maybe_as_int as nbytes is guaranteed positive
|
||||
TORCH_CHECK(!size_bytes_is_heap_allocated_);
|
||||
return size_bytes_.as_int_unchecked();
|
||||
}
|
||||
|
||||
SymInt sym_nbytes() const {
|
||||
return size_bytes_;
|
||||
}
|
||||
|
||||
// TODO: remove later
|
||||
void set_nbytes(size_t size_bytes) {
|
||||
size_bytes_ = static_cast<int64_t>(size_bytes);
|
||||
size_bytes_is_heap_allocated_ = false;
|
||||
}
|
||||
|
||||
void unsafe_set_nbytes(size_t size_bytes) {
|
||||
TORCH_INTERNAL_ASSERT_DEBUG_ONLY(!size_bytes_is_heap_allocated_);
|
||||
size_bytes_.unsafe_set_data(size_bytes);
|
||||
}
|
||||
|
||||
void set_nbytes(c10::SymInt size_bytes) {
|
||||
size_bytes_ = std::move(size_bytes);
|
||||
}
|
||||
|
||||
bool resizable() const {
|
||||
return resizable_;
|
||||
}
|
||||
|
||||
const at::DataPtr& data_ptr() const {
|
||||
if (C10_UNLIKELY(throw_on_immutable_data_ptr_)) {
|
||||
throw_data_ptr_access_error();
|
||||
}
|
||||
return data_ptr_;
|
||||
}
|
||||
|
||||
at::DataPtr& mutable_data_ptr() {
|
||||
if (C10_UNLIKELY(has_mutable_data_ptr_check_)) {
|
||||
if (throw_on_immutable_data_ptr_) {
|
||||
throw_data_ptr_access_error();
|
||||
}
|
||||
if (throw_on_mutable_data_ptr_) {
|
||||
throwNullDataPtrError();
|
||||
}
|
||||
if (warn_deprecated_on_mutable_data_ptr_) {
|
||||
warnDeprecatedDataPtr();
|
||||
}
|
||||
maybe_materialize_cow();
|
||||
}
|
||||
return data_ptr_;
|
||||
}
|
||||
|
||||
// Returns the data_ptr. Bypasses all checks.
|
||||
at::DataPtr& _mutable_data_ptr_no_checks() {
|
||||
return data_ptr_;
|
||||
}
|
||||
|
||||
// Returns the previous data_ptr
|
||||
at::DataPtr set_data_ptr(at::DataPtr&& data_ptr) {
|
||||
// We need to materialize the old COW DataPtr because it is
|
||||
// being returned as mutable.
|
||||
maybe_materialize_cow();
|
||||
return set_data_ptr_no_materialize_cow(std::move(data_ptr));
|
||||
}
|
||||
|
||||
void set_data_ptr_noswap(at::DataPtr&& data_ptr) {
|
||||
data_ptr_ = std::move(data_ptr);
|
||||
refresh_has_data_ptr_check();
|
||||
}
|
||||
|
||||
void swap_data_ptr(StorageImpl& other) {
|
||||
maybe_materialize_cow();
|
||||
other.maybe_materialize_cow();
|
||||
std::swap(data_ptr_, other.data_ptr_);
|
||||
std::swap(size_bytes_, other.size_bytes_);
|
||||
std::swap(
|
||||
size_bytes_is_heap_allocated_, other.size_bytes_is_heap_allocated_);
|
||||
std::swap(resizable_, other.resizable_);
|
||||
std::swap(allocator_, other.allocator_);
|
||||
std::swap(throw_on_immutable_data_ptr_, other.throw_on_immutable_data_ptr_);
|
||||
std::swap(throw_on_mutable_data_ptr_, other.throw_on_mutable_data_ptr_);
|
||||
std::swap(
|
||||
warn_deprecated_on_mutable_data_ptr_,
|
||||
other.warn_deprecated_on_mutable_data_ptr_);
|
||||
refresh_has_data_ptr_check();
|
||||
other.refresh_has_data_ptr_check();
|
||||
}
|
||||
|
||||
const void* data() const {
|
||||
if (C10_UNLIKELY(throw_on_immutable_data_ptr_)) {
|
||||
throw_data_ptr_access_error();
|
||||
}
|
||||
return data_ptr_.get();
|
||||
}
|
||||
|
||||
void* mutable_data() {
|
||||
if (C10_UNLIKELY(has_mutable_data_ptr_check_)) {
|
||||
if (throw_on_immutable_data_ptr_) {
|
||||
throw_data_ptr_access_error();
|
||||
}
|
||||
if (throw_on_mutable_data_ptr_) {
|
||||
throwNullDataPtrError();
|
||||
}
|
||||
if (warn_deprecated_on_mutable_data_ptr_) {
|
||||
warnDeprecatedDataPtr();
|
||||
}
|
||||
maybe_materialize_cow();
|
||||
}
|
||||
return data_ptr_.mutable_get();
|
||||
}
|
||||
|
||||
at::DeviceType device_type() const {
|
||||
return data_ptr_.device().type();
|
||||
}
|
||||
|
||||
at::Allocator* allocator() {
|
||||
return allocator_;
|
||||
}
|
||||
|
||||
const at::Allocator* allocator() const {
|
||||
return allocator_;
|
||||
}
|
||||
|
||||
// You generally shouldn't use this method, but it is occasionally
|
||||
// useful if you want to override how a tensor will be reallocated,
|
||||
// after it was already allocated (and its initial allocator was
|
||||
// set)
|
||||
void set_allocator(at::Allocator* allocator) {
|
||||
allocator_ = allocator;
|
||||
}
|
||||
|
||||
Device device() const {
|
||||
return data_ptr_.device();
|
||||
}
|
||||
|
||||
void set_resizable(bool resizable) {
|
||||
if (resizable) {
|
||||
// We need an allocator to be resizable
|
||||
AT_ASSERT(allocator_);
|
||||
}
|
||||
resizable_ = resizable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can only be called when use_count is 1
|
||||
*/
|
||||
void UniqueStorageShareExternalPointer(
|
||||
void* src,
|
||||
size_t size_bytes,
|
||||
DeleterFnPtr d = nullptr) {
|
||||
UniqueStorageShareExternalPointer(
|
||||
at::DataPtr(src, src, d, data_ptr_.device()), size_bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can only be called when use_count is 1
|
||||
*/
|
||||
void UniqueStorageShareExternalPointer(
|
||||
at::DataPtr&& data_ptr,
|
||||
size_t size_bytes) {
|
||||
data_ptr_ = std::move(data_ptr);
|
||||
size_bytes_ = static_cast<int64_t>(size_bytes);
|
||||
size_bytes_is_heap_allocated_ = false;
|
||||
allocator_ = nullptr;
|
||||
resizable_ = false;
|
||||
}
|
||||
|
||||
// This method can be used only after storage construction and cannot be used
|
||||
// to modify storage status
|
||||
void set_received_cuda(bool received_cuda) {
|
||||
received_cuda_ = received_cuda;
|
||||
}
|
||||
|
||||
bool received_cuda() {
|
||||
return received_cuda_;
|
||||
}
|
||||
|
||||
impl::PyObjectSlot* pyobj_slot() {
|
||||
return &pyobj_slot_;
|
||||
}
|
||||
|
||||
const impl::PyObjectSlot* pyobj_slot() const {
|
||||
return &pyobj_slot_;
|
||||
}
|
||||
|
||||
StorageExtraMeta& get_extra_meta() {
|
||||
if (!extra_meta_) {
|
||||
extra_meta_ = std::make_unique<StorageExtraMeta>();
|
||||
}
|
||||
return *extra_meta_;
|
||||
}
|
||||
|
||||
[[noreturn]] void throw_data_ptr_access_error() const;
|
||||
|
||||
void release_data_and_set_meta_custom_data_ptr_error_msg_(
|
||||
std::optional<std::string> s) {
|
||||
throw_on_immutable_data_ptr_ = true;
|
||||
get_extra_meta().custom_data_ptr_error_msg_ = std::move(s);
|
||||
refresh_has_data_ptr_check();
|
||||
}
|
||||
|
||||
void clear_data_ptr_access_error_msg_() {
|
||||
throw_on_immutable_data_ptr_ = false;
|
||||
if (extra_meta_) {
|
||||
extra_meta_->custom_data_ptr_error_msg_ = std::nullopt;
|
||||
}
|
||||
refresh_has_data_ptr_check();
|
||||
}
|
||||
|
||||
void set_throw_on_mutable_data_ptr() {
|
||||
throw_on_mutable_data_ptr_ = true;
|
||||
refresh_has_data_ptr_check();
|
||||
}
|
||||
|
||||
void set_warn_deprecated_on_mutable_data_ptr() {
|
||||
warn_deprecated_on_mutable_data_ptr_ = true;
|
||||
refresh_has_data_ptr_check();
|
||||
}
|
||||
|
||||
protected:
|
||||
// materialize_cow_storage needs to call set_data_ptr_no_materlize_cow
|
||||
friend void c10::impl::cow::materialize_cow_storage(StorageImpl& storage);
|
||||
|
||||
// Returns the previous data_ptr. If the old data_ptr was COW,
|
||||
// this avoids materializing it
|
||||
at::DataPtr set_data_ptr_no_materialize_cow(at::DataPtr&& data_ptr) {
|
||||
at::DataPtr old_data_ptr(std::move(data_ptr_));
|
||||
data_ptr_ = std::move(data_ptr);
|
||||
refresh_has_data_ptr_check();
|
||||
return old_data_ptr;
|
||||
}
|
||||
|
||||
private:
|
||||
void refresh_has_data_ptr_check() {
|
||||
has_mutable_data_ptr_check_ = is_cow() || throw_on_mutable_data_ptr_ ||
|
||||
warn_deprecated_on_mutable_data_ptr_ || throw_on_immutable_data_ptr_;
|
||||
}
|
||||
|
||||
inline bool is_cow() const {
|
||||
return c10::impl::cow::is_cow_data_ptr(data_ptr_);
|
||||
}
|
||||
|
||||
// Triggers a copy if this is a copy-on-write tensor.
|
||||
void maybe_materialize_cow() {
|
||||
if (is_cow()) {
|
||||
impl::cow::materialize_cow_storage(*this);
|
||||
}
|
||||
}
|
||||
|
||||
DataPtr data_ptr_;
|
||||
SymInt size_bytes_;
|
||||
bool size_bytes_is_heap_allocated_;
|
||||
bool resizable_;
|
||||
// Identifies that Storage was received from another process and doesn't have
|
||||
// local to process cuda memory allocation
|
||||
bool received_cuda_;
|
||||
// All special checks in data/data_ptr calls are guarded behind this single
|
||||
// boolean. This is for performance: .data/.data_ptr calls are commonly in the
|
||||
// hot-path.
|
||||
bool has_mutable_data_ptr_check_ = false;
|
||||
// If we should throw when mutable_data_ptr() or mutable_data() is called.
|
||||
bool throw_on_mutable_data_ptr_ = false;
|
||||
// If we should throw when data_ptr() or data() is called.
|
||||
bool throw_on_immutable_data_ptr_ = false;
|
||||
// If we warn when mutable_data_ptr() or mutable_data() is called.
|
||||
bool warn_deprecated_on_mutable_data_ptr_ = false;
|
||||
Allocator* allocator_;
|
||||
impl::PyObjectSlot pyobj_slot_;
|
||||
std::unique_ptr<StorageExtraMeta> extra_meta_ = nullptr;
|
||||
};
|
||||
|
||||
// Declare StorageImpl create function pointer types.
|
||||
using StorageImplCreateHelper = intrusive_ptr<StorageImpl> (*)(
|
||||
StorageImpl::use_byte_size_t,
|
||||
SymInt size_bytes,
|
||||
DataPtr data_ptr,
|
||||
Allocator* allocator,
|
||||
bool resizable);
|
||||
|
||||
C10_API void SetStorageImplCreate(DeviceType t, StorageImplCreateHelper fptr);
|
||||
|
||||
C10_API StorageImplCreateHelper GetStorageImplCreate(DeviceType t);
|
||||
|
||||
C10_API c10::intrusive_ptr<c10::StorageImpl> make_storage_impl(
|
||||
c10::StorageImpl::use_byte_size_t use_byte_size,
|
||||
c10::SymInt size_bytes,
|
||||
c10::DataPtr data_ptr,
|
||||
c10::Allocator* allocator,
|
||||
bool resizable,
|
||||
std::optional<at::Device> device_opt);
|
||||
|
||||
namespace detail {
|
||||
|
||||
#ifndef C10_MOBILE
|
||||
template <class T>
|
||||
struct TargetTraits<
|
||||
T,
|
||||
std::enable_if_t<
|
||||
std::is_base_of_v<c10::StorageImpl, std::remove_cv_t<T>>>> {
|
||||
static constexpr bool can_have_pyobject = true;
|
||||
};
|
||||
#endif
|
||||
|
||||
} // namespace detail
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,191 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/Device.h>
|
||||
#include <c10/core/DeviceType.h>
|
||||
#include <c10/macros/Export.h>
|
||||
#include <c10/util/Exception.h>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <ostream>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
/// An index representing a specific stream. A StreamId is not independently
|
||||
/// meaningful without knowing the Device it is associated with; try to
|
||||
/// use Stream rather than StreamId directly.
|
||||
///
|
||||
/// StreamIds are opaque; they are assigned by some DeviceType-specific
|
||||
/// numbering system which is not visible to the user. HOWEVER, we
|
||||
/// guarantee that StreamId 0 is always a valid stream, and corresponds
|
||||
/// to some sort of "default" stream.
|
||||
using StreamId = int64_t;
|
||||
|
||||
struct C10_API StreamData3 {
|
||||
StreamId stream_id;
|
||||
DeviceIndex device_index;
|
||||
DeviceType device_type;
|
||||
};
|
||||
|
||||
// NB: I decided not to call the above StreamIndex to avoid confusion with
|
||||
// DeviceIndex. This way, you access device index with index(), and stream id
|
||||
// with id()
|
||||
|
||||
/**
|
||||
* A stream is a software mechanism used to synchronize launched kernels
|
||||
* without requiring explicit synchronizations between kernels. The basic
|
||||
* model is that every kernel launch is associated with a stream: every
|
||||
* kernel on the same stream is implicitly synchronized so that if I launch
|
||||
* kernels A and B on the same stream, A is guaranteed to finish before B
|
||||
* launches. If I want B to run concurrently with A, I must schedule
|
||||
* it on a different stream.
|
||||
*
|
||||
* The Stream class is a backend agnostic value class representing a stream
|
||||
* which I may schedule a kernel on. Every stream is associated with a device,
|
||||
* which is recorded in stream, which is used to avoid confusion about which
|
||||
* device a stream refers to.
|
||||
*
|
||||
* Streams are explicitly thread-safe, in the sense that it is OK to pass
|
||||
* a Stream from one thread to another, and kernels queued from two different
|
||||
* threads will still get serialized appropriately. (Of course, the
|
||||
* time when the kernels get queued is undetermined unless you synchronize
|
||||
* host side ;)
|
||||
*
|
||||
* Stream does NOT have a default constructor. Streams are for expert
|
||||
* users; if you want to use Streams, we're going to assume you know
|
||||
* how to deal with C++ template error messages if you try to
|
||||
* resize() a vector of Streams.
|
||||
*
|
||||
* Known instances of streams in backends:
|
||||
*
|
||||
* - cudaStream_t (CUDA)
|
||||
* - hipStream_t (HIP)
|
||||
* - cl_command_queue (OpenCL) (NB: Caffe2's existing OpenCL integration
|
||||
* does NOT support command queues.)
|
||||
*
|
||||
* Because this class is device agnostic, it cannot provide backend-specific
|
||||
* functionality (e.g., get the cudaStream_t of a CUDA stream.) There are
|
||||
* wrapper classes which provide this functionality, e.g., CUDAStream.
|
||||
*/
|
||||
class C10_API Stream final {
|
||||
private:
|
||||
Device device_;
|
||||
StreamId id_;
|
||||
|
||||
public:
|
||||
enum Unsafe { UNSAFE };
|
||||
enum Default { DEFAULT };
|
||||
|
||||
/// Unsafely construct a stream from a Device and a StreamId. In
|
||||
/// general, only specific implementations of streams for a
|
||||
/// backend should manufacture Stream directly in this way; other users
|
||||
/// should use the provided APIs to get a stream. In particular,
|
||||
/// we don't require backends to give any guarantees about non-zero
|
||||
/// StreamIds; they are welcome to allocate in whatever way they like.
|
||||
explicit Stream(Unsafe /*unused*/, Device device, StreamId id)
|
||||
: device_(device), id_(id) {}
|
||||
|
||||
/// Construct the default stream of a Device. The default stream is
|
||||
/// NOT the same as the current stream; default stream is a fixed stream
|
||||
/// that never changes, whereas the current stream may be changed by
|
||||
/// StreamGuard.
|
||||
explicit Stream(Default /*unused*/, Device device)
|
||||
: device_(device), id_(0) {}
|
||||
|
||||
bool operator==(const Stream& other) const noexcept {
|
||||
return this->device_ == other.device_ && this->id_ == other.id_;
|
||||
}
|
||||
bool operator!=(const Stream& other) const noexcept {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
Device device() const noexcept {
|
||||
return device_;
|
||||
}
|
||||
DeviceType device_type() const noexcept {
|
||||
return device_.type();
|
||||
}
|
||||
DeviceIndex device_index() const noexcept {
|
||||
return device_.index();
|
||||
}
|
||||
StreamId id() const noexcept {
|
||||
return id_;
|
||||
}
|
||||
|
||||
// Returns an opaque, backend-specific handle to the underlying stream.
|
||||
// The handle is non-owning and its concrete type is backend-defined
|
||||
// (e.g., a CUDA stream or a SYCL queue).
|
||||
void* native_handle() const;
|
||||
|
||||
// Enqueues a wait instruction in the stream's work queue.
|
||||
// This instruction is a no-op unless the event is marked
|
||||
// for recording. In that case the stream stops processing
|
||||
// until the event is recorded.
|
||||
template <typename T>
|
||||
void wait(const T& event) const {
|
||||
event.block(*this);
|
||||
}
|
||||
|
||||
// Return whether all asynchronous work previously enqueued on this stream
|
||||
// has completed running on the device.
|
||||
bool query() const;
|
||||
|
||||
// Wait (by blocking the calling thread) until all asynchronous work enqueued
|
||||
// on this stream has completed running on the device.
|
||||
void synchronize() const;
|
||||
|
||||
// Return the stream is currently recording work for graph capture. True while
|
||||
// the stream is in capture mode, false otherwise.
|
||||
bool is_capturing() const;
|
||||
|
||||
// The purpose of this function is to more conveniently permit binding
|
||||
// of Stream to and from Python. Without packing, I have to setup a whole
|
||||
// class with two fields (device and stream id); with packing I can just
|
||||
// store a single uint64_t.
|
||||
//
|
||||
// The particular way we pack streams into a uint64_t is considered an
|
||||
// implementation detail and should not be relied upon.
|
||||
uint64_t hash() const noexcept {
|
||||
// Concat these together into a 64-bit integer
|
||||
uint64_t bits = static_cast<uint64_t>(device_type()) << 56 |
|
||||
static_cast<uint64_t>(device_index()) << 48 |
|
||||
// Remove the sign extension part of the 64-bit address because
|
||||
// the id might be used to hold a pointer.
|
||||
(static_cast<uint64_t>(id()) & ((1ull << 48) - 1));
|
||||
return bits;
|
||||
}
|
||||
|
||||
struct StreamData3 pack3() const {
|
||||
return {id(), device_index(), device_type()};
|
||||
}
|
||||
|
||||
static Stream unpack3(
|
||||
StreamId stream_id,
|
||||
DeviceIndex device_index,
|
||||
DeviceType device_type) {
|
||||
TORCH_CHECK(isValidDeviceType(device_type));
|
||||
return Stream(UNSAFE, Device(device_type, device_index), stream_id);
|
||||
}
|
||||
|
||||
// I decided NOT to provide setters on this class, because really,
|
||||
// why would you change the device of a stream? Just construct
|
||||
// it correctly from the beginning dude.
|
||||
};
|
||||
|
||||
C10_API std::ostream& operator<<(std::ostream& stream, const Stream& s);
|
||||
|
||||
} // namespace c10
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<c10::Stream> {
|
||||
size_t operator()(c10::Stream s) const noexcept {
|
||||
return std::hash<uint64_t>{}(s.hash());
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,178 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/Device.h>
|
||||
#include <c10/core/Stream.h>
|
||||
#include <c10/core/impl/InlineStreamGuard.h>
|
||||
#include <c10/core/impl/VirtualGuardImpl.h>
|
||||
#include <c10/util/ArrayRef.h>
|
||||
#include <c10/util/Optional.h>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
/**
|
||||
* A StreamGuard is an RAII class that changes the current device
|
||||
* to the device corresponding to some stream, and changes the
|
||||
* default stream on that device to be this stream.
|
||||
*
|
||||
* Use of StreamGuard is HIGHLY discouraged in operator definitions. In
|
||||
* a single operator, you probably don't know enough about the global
|
||||
* state of the world to profitably decide how to set streams. Let
|
||||
* the caller handle this appropriately, and just use the current stream
|
||||
* in your operator code.
|
||||
*
|
||||
* This StreamGuard does NOT have an uninitialized state; it is guaranteed
|
||||
* to reset the stream and device on exit. If you are in a situation
|
||||
* where you *might* want to setup a stream guard, see OptionalStreamGuard.
|
||||
*/
|
||||
struct StreamGuard {
|
||||
/// No default constructor, see Note [Omitted default constructor from RAII]
|
||||
explicit StreamGuard() = delete;
|
||||
~StreamGuard() = default;
|
||||
|
||||
/// Set the current device to the device associated with the passed stream,
|
||||
/// and set the current stream on that device to the passed stream.
|
||||
explicit StreamGuard(Stream stream) : guard_(stream) {}
|
||||
|
||||
/// Copy is disallowed
|
||||
StreamGuard(const StreamGuard&) = delete;
|
||||
StreamGuard& operator=(const StreamGuard&) = delete;
|
||||
|
||||
/// Move is disallowed, as StreamGuard does not have an uninitialized state,
|
||||
/// which is required for moves on types with nontrivial destructors.
|
||||
StreamGuard(StreamGuard&& other) = delete;
|
||||
StreamGuard& operator=(StreamGuard&& other) = delete;
|
||||
|
||||
/// Resets the currently set stream to the original stream and
|
||||
/// the currently set device to the original device. Then,
|
||||
/// set the current device to the device associated with the passed stream,
|
||||
/// and set the current stream on that device to the passed stream.
|
||||
///
|
||||
/// NOTE: this implementation may skip some stream/device setting if
|
||||
/// it can prove that it is unnecessary.
|
||||
///
|
||||
/// WARNING: reset_stream does NOT preserve previously set streams on
|
||||
/// different devices. If you need to set streams on multiple devices
|
||||
/// on , use MultiStreamGuard instead.
|
||||
void reset_stream(Stream stream) {
|
||||
guard_.reset_stream(stream);
|
||||
}
|
||||
|
||||
/// Returns the stream that was set at the time the guard was constructed.
|
||||
Stream original_stream() const {
|
||||
return guard_.original_stream();
|
||||
}
|
||||
|
||||
/// Returns the most recent stream that was set using this device guard,
|
||||
/// either from construction, or via set_stream.
|
||||
Stream current_stream() const {
|
||||
return guard_.current_stream();
|
||||
}
|
||||
|
||||
/// Returns the most recent device that was set using this device guard,
|
||||
/// either from construction, or via set_device/reset_device/set_index.
|
||||
Device current_device() const {
|
||||
return guard_.current_device();
|
||||
}
|
||||
|
||||
/// Returns the device that was set at the most recent reset_stream(),
|
||||
/// or otherwise the device at construction time.
|
||||
Device original_device() const {
|
||||
return guard_.original_device();
|
||||
}
|
||||
|
||||
private:
|
||||
c10::impl::InlineStreamGuard<impl::VirtualGuardImpl> guard_;
|
||||
};
|
||||
|
||||
/**
|
||||
* An OptionalStreamGuard is an RAII class that sets a device to some value on
|
||||
* initialization, and resets the device to its original value on destruction.
|
||||
* See OptionalDeviceGuard for more guidance on how to use this class.
|
||||
*/
|
||||
struct OptionalStreamGuard {
|
||||
/// Create an uninitialized guard.
|
||||
explicit OptionalStreamGuard() = default;
|
||||
|
||||
/// Set the current device to the device associated with the passed stream,
|
||||
/// and set the current stream on that device to the passed stream.
|
||||
explicit OptionalStreamGuard(Stream stream) : guard_(stream) {}
|
||||
|
||||
/// Set the current device to the device associated with the passed stream,
|
||||
/// and set the current stream on that device to the passed stream,
|
||||
/// if the passed stream is not nullopt.
|
||||
explicit OptionalStreamGuard(std::optional<Stream> stream_opt)
|
||||
: guard_(stream_opt) {}
|
||||
|
||||
/// Copy is disallowed
|
||||
OptionalStreamGuard(const OptionalStreamGuard&) = delete;
|
||||
OptionalStreamGuard& operator=(const OptionalStreamGuard&) = delete;
|
||||
|
||||
// See Note [Move construction for RAII guards is tricky]
|
||||
OptionalStreamGuard(OptionalStreamGuard&& other) = delete;
|
||||
|
||||
// See Note [Move assignment for RAII guards is tricky]
|
||||
OptionalStreamGuard& operator=(OptionalStreamGuard&& other) = delete;
|
||||
~OptionalStreamGuard() = default;
|
||||
|
||||
/// Resets the currently set stream to the original stream and
|
||||
/// the currently set device to the original device. Then,
|
||||
/// set the current device to the device associated with the passed stream,
|
||||
/// and set the current stream on that device to the passed stream.
|
||||
/// Initializes the guard if it was not previously initialized.
|
||||
void reset_stream(Stream stream) {
|
||||
guard_.reset_stream(stream);
|
||||
}
|
||||
|
||||
/// Returns the stream that was set at the time the guard was most recently
|
||||
/// initialized, or nullopt if the guard is uninitialized.
|
||||
std::optional<Stream> original_stream() const {
|
||||
return guard_.original_stream();
|
||||
}
|
||||
|
||||
/// Returns the most recent stream that was set using this stream guard,
|
||||
/// either from construction, or via reset_stream, if the guard is
|
||||
/// initialized, or nullopt if the guard is uninitialized.
|
||||
std::optional<Stream> current_stream() const {
|
||||
return guard_.current_stream();
|
||||
}
|
||||
|
||||
/// Restore the original device and stream, resetting this guard to
|
||||
/// uninitialized state.
|
||||
void reset() {
|
||||
guard_.reset();
|
||||
}
|
||||
|
||||
private:
|
||||
c10::impl::InlineOptionalStreamGuard<impl::VirtualGuardImpl> guard_;
|
||||
};
|
||||
|
||||
/**
|
||||
* A MultiStreamGuard is an RAII class that sets the current streams of a set of
|
||||
* devices all at once, and resets them to their original values on destruction.
|
||||
*/
|
||||
struct MultiStreamGuard {
|
||||
/// Set the current streams to the passed streams on each of their respective
|
||||
/// devices.
|
||||
explicit MultiStreamGuard(ArrayRef<Stream> streams) : guard_(streams) {}
|
||||
|
||||
/// Copy is disallowed
|
||||
MultiStreamGuard(const MultiStreamGuard&) = delete;
|
||||
MultiStreamGuard& operator=(const MultiStreamGuard&) = delete;
|
||||
|
||||
// See Note [Move construction for RAII guards is tricky]
|
||||
MultiStreamGuard(MultiStreamGuard&& other) = delete;
|
||||
|
||||
// See Note [Move assignment for RAII guards is tricky]
|
||||
MultiStreamGuard& operator=(MultiStreamGuard&& other) = delete;
|
||||
~MultiStreamGuard() = default;
|
||||
|
||||
private:
|
||||
c10::impl::InlineMultiStreamGuard<impl::VirtualGuardImpl> guard_;
|
||||
};
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,184 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/SymNodeImpl.h>
|
||||
#include <c10/macros/Export.h>
|
||||
#include <c10/util/Exception.h>
|
||||
#include <c10/util/intrusive_ptr.h>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <utility>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
class SymInt;
|
||||
|
||||
class C10_API SymBool {
|
||||
public:
|
||||
/*implicit*/ SymBool(bool b) : data_(b) {}
|
||||
SymBool(SymNode ptr) : data_(false), ptr_(std::move(ptr)) {
|
||||
TORCH_CHECK(ptr_->is_bool());
|
||||
}
|
||||
SymBool() : data_(false) {}
|
||||
|
||||
SymNodeImpl* toSymNodeImplUnowned() const {
|
||||
return ptr_.get();
|
||||
}
|
||||
|
||||
SymNodeImpl* release() && {
|
||||
return std::move(ptr_).release();
|
||||
}
|
||||
|
||||
// Only valid if is_heap_allocated()
|
||||
SymNode toSymNodeImpl() const;
|
||||
|
||||
// Guaranteed to return a SymNode, wrapping using base if necessary
|
||||
SymNode wrap_node(const SymNode& base) const;
|
||||
|
||||
bool expect_bool() const {
|
||||
std::optional<bool> c = maybe_as_bool();
|
||||
TORCH_CHECK(c.has_value());
|
||||
return *c;
|
||||
}
|
||||
|
||||
SymBool sym_and(const SymBool& /*sci*/) const;
|
||||
SymBool sym_or(const SymBool& /*sci*/) const;
|
||||
SymBool sym_not() const;
|
||||
|
||||
SymBool operator&(const SymBool& other) const {
|
||||
return sym_and(other);
|
||||
}
|
||||
SymBool operator|(const SymBool& other) const {
|
||||
return sym_or(other);
|
||||
}
|
||||
SymBool operator||(const SymBool& other) const {
|
||||
return sym_or(other);
|
||||
}
|
||||
SymBool operator~() const {
|
||||
return sym_not();
|
||||
}
|
||||
|
||||
// Insert a guard for the bool to be its concrete value, and then return
|
||||
// that value. Note that C++ comparison operations default to returning
|
||||
// bool, so it's not so common to have to call this
|
||||
bool guard_bool(const char* file, int64_t line) const;
|
||||
bool expect_true(const char* file, int64_t line) const;
|
||||
bool guard_size_oblivious(const char* file, int64_t line) const;
|
||||
bool statically_known_true(const char* file, int64_t line) const;
|
||||
bool guard_or_false(const char* file, int64_t line) const;
|
||||
bool guard_or_true(const char* file, int64_t line) const;
|
||||
|
||||
bool has_hint() const;
|
||||
|
||||
bool as_bool_unchecked() const {
|
||||
return data_;
|
||||
}
|
||||
|
||||
std::optional<bool> maybe_as_bool() const {
|
||||
if (!is_heap_allocated()) {
|
||||
return data_;
|
||||
}
|
||||
return toSymNodeImplUnowned()->constant_bool();
|
||||
}
|
||||
|
||||
// Convert SymBool to SymInt (0 or 1)
|
||||
// This is the C++ equivalent of Python's cast_symbool_to_symint_guardless
|
||||
SymInt toSymInt() const;
|
||||
|
||||
bool is_heap_allocated() const {
|
||||
return ptr_;
|
||||
}
|
||||
|
||||
private:
|
||||
// TODO: optimize to union
|
||||
bool data_;
|
||||
SymNode ptr_;
|
||||
};
|
||||
|
||||
C10_API std::ostream& operator<<(std::ostream& os, const SymBool& s);
|
||||
|
||||
#define TORCH_SYM_CHECK(cond, ...) \
|
||||
TORCH_CHECK((cond).expect_true(__FILE__, __LINE__), __VA_ARGS__)
|
||||
#define TORCH_SYM_INTERNAL_ASSERT(cond, ...) \
|
||||
TORCH_INTERNAL_ASSERT((cond).expect_true(__FILE__, __LINE__), __VA_ARGS__)
|
||||
#define TORCH_MAYBE_SYM_CHECK(cond, ...) \
|
||||
if constexpr (std::is_same_v<std::decay_t<decltype(cond)>, SymBool>) { \
|
||||
TORCH_CHECK((cond).expect_true(__FILE__, __LINE__), __VA_ARGS__) \
|
||||
} else { \
|
||||
TORCH_CHECK((cond), __VA_ARGS__) \
|
||||
}
|
||||
|
||||
inline bool guard_size_oblivious(
|
||||
bool b,
|
||||
const char* file [[maybe_unused]],
|
||||
int64_t line [[maybe_unused]]) {
|
||||
return b;
|
||||
}
|
||||
|
||||
inline bool guard_size_oblivious(
|
||||
const c10::SymBool& b,
|
||||
const char* file,
|
||||
int64_t line) {
|
||||
return b.guard_size_oblivious(file, line);
|
||||
}
|
||||
|
||||
inline bool guard_or_false(
|
||||
bool b,
|
||||
const char* file [[maybe_unused]],
|
||||
int64_t line [[maybe_unused]]) {
|
||||
return b;
|
||||
}
|
||||
|
||||
inline bool guard_or_false(
|
||||
const c10::SymBool& b,
|
||||
const char* file,
|
||||
int64_t line) {
|
||||
return b.guard_or_false(file, line);
|
||||
}
|
||||
|
||||
inline bool statically_known_true(
|
||||
bool b,
|
||||
const char* file [[maybe_unused]],
|
||||
int64_t line [[maybe_unused]]) {
|
||||
return b;
|
||||
}
|
||||
|
||||
inline bool statically_known_true(
|
||||
const c10::SymBool& b,
|
||||
const char* file,
|
||||
int64_t line) {
|
||||
return b.statically_known_true(file, line);
|
||||
}
|
||||
|
||||
inline bool guard_or_true(
|
||||
bool b,
|
||||
const char* file [[maybe_unused]],
|
||||
int64_t line [[maybe_unused]]) {
|
||||
return b;
|
||||
}
|
||||
|
||||
inline bool guard_or_true(
|
||||
const c10::SymBool& b,
|
||||
const char* file,
|
||||
int64_t line) {
|
||||
return b.guard_or_true(file, line);
|
||||
}
|
||||
|
||||
#define TORCH_GUARD_SIZE_OBLIVIOUS(cond) \
|
||||
c10::guard_size_oblivious((cond), __FILE__, __LINE__)
|
||||
|
||||
#define TORCH_STATICALLY_KNOWN_TRUE(cond) \
|
||||
c10::statically_known_true((cond), __FILE__, __LINE__)
|
||||
|
||||
#define TORCH_GUARD_OR_FALSE(cond) \
|
||||
c10::guard_or_false((cond), __FILE__, __LINE__)
|
||||
|
||||
#define TORCH_GUARD_OR_TRUE(cond) c10::guard_or_true((cond), __FILE__, __LINE__)
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,123 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/SymBool.h>
|
||||
#include <c10/core/SymNodeImpl.h>
|
||||
#include <c10/macros/Export.h>
|
||||
#include <c10/macros/Macros.h>
|
||||
#include <c10/util/Exception.h>
|
||||
#include <c10/util/intrusive_ptr.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <ostream>
|
||||
#include <utility>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
// NB: this is actually double precision; we're using the Python naming here
|
||||
class C10_API SymFloat {
|
||||
public:
|
||||
/*implicit*/ SymFloat(double d) : data_(d) {}
|
||||
SymFloat(SymNode ptr)
|
||||
: data_(std::numeric_limits<double>::quiet_NaN()), ptr_(std::move(ptr)) {
|
||||
TORCH_CHECK(ptr_->is_float());
|
||||
}
|
||||
SymFloat() : data_(0.0) {}
|
||||
|
||||
SymNodeImpl* toSymNodeImplUnowned() const {
|
||||
return ptr_.get();
|
||||
}
|
||||
|
||||
SymNodeImpl* release() && {
|
||||
return std::move(ptr_).release();
|
||||
}
|
||||
|
||||
// Only valid if is_symbolic()
|
||||
SymNode toSymNodeImpl() const;
|
||||
|
||||
// Guaranteed to return a SymNode, wrapping using base if necessary
|
||||
SymNode wrap_node(const SymNode& base) const;
|
||||
|
||||
double expect_float() const {
|
||||
TORCH_CHECK(!is_symbolic());
|
||||
return data_;
|
||||
}
|
||||
|
||||
SymFloat operator+(const SymFloat& /*sci*/) const;
|
||||
SymFloat operator-(const SymFloat& /*sci*/) const;
|
||||
SymFloat operator*(const SymFloat& /*sci*/) const;
|
||||
SymFloat operator/(const SymFloat& /*sci*/) const;
|
||||
|
||||
SymBool sym_eq(const SymFloat& /*sci*/) const;
|
||||
SymBool sym_ne(const SymFloat& /*sci*/) const;
|
||||
SymBool sym_lt(const SymFloat& /*sci*/) const;
|
||||
SymBool sym_le(const SymFloat& /*sci*/) const;
|
||||
SymBool sym_gt(const SymFloat& /*sci*/) const;
|
||||
SymBool sym_ge(const SymFloat& /*sci*/) const;
|
||||
|
||||
bool operator==(const SymFloat& o) const {
|
||||
return sym_eq(o).guard_bool(__FILE__, __LINE__);
|
||||
}
|
||||
bool operator!=(const SymFloat& o) const {
|
||||
return sym_ne(o).guard_bool(__FILE__, __LINE__);
|
||||
}
|
||||
bool operator<(const SymFloat& o) const {
|
||||
return sym_lt(o).guard_bool(__FILE__, __LINE__);
|
||||
}
|
||||
bool operator<=(const SymFloat& o) const {
|
||||
return sym_le(o).guard_bool(__FILE__, __LINE__);
|
||||
}
|
||||
bool operator>(const SymFloat& o) const {
|
||||
return sym_gt(o).guard_bool(__FILE__, __LINE__);
|
||||
}
|
||||
bool operator>=(const SymFloat& o) const {
|
||||
return sym_ge(o).guard_bool(__FILE__, __LINE__);
|
||||
}
|
||||
|
||||
SymFloat min(const SymFloat& sci) const;
|
||||
SymFloat max(const SymFloat& sci) const;
|
||||
|
||||
// Need guidance on where to put this code
|
||||
SymFloat sqrt() const;
|
||||
|
||||
// Insert a guard for the float to be its concrete value, and then return
|
||||
// that value. This operation always works, even if the float is symbolic,
|
||||
// so long as we know what the underlying value is. Don't blindly put this
|
||||
// everywhere; you can cause overspecialization of PyTorch programs with
|
||||
// this method.
|
||||
//
|
||||
// It should be called as guard_float(__FILE__, __LINE__). The file and line
|
||||
// number can be used to diagnose overspecialization.
|
||||
double guard_float(const char* file, int64_t line) const;
|
||||
|
||||
bool has_hint() const;
|
||||
|
||||
// N.B. It's important to keep this definition in the header
|
||||
// as we expect if checks to be folded for mobile builds
|
||||
// where `is_symbolic` is always false
|
||||
C10_ALWAYS_INLINE bool is_symbolic() const {
|
||||
return ptr_;
|
||||
}
|
||||
|
||||
// UNSAFELY coerce this SymFloat into a double. You MUST have
|
||||
// established that this is a non-symbolic by some other means,
|
||||
// typically by having tested is_symbolic(). You will get garbage
|
||||
// from this function if is_symbolic()
|
||||
double as_float_unchecked() const {
|
||||
TORCH_INTERNAL_ASSERT_DEBUG_ONLY(!is_symbolic());
|
||||
return data_;
|
||||
}
|
||||
|
||||
private:
|
||||
// TODO: optimize to union
|
||||
double data_;
|
||||
SymNode ptr_;
|
||||
};
|
||||
|
||||
C10_API std::ostream& operator<<(std::ostream& os, const SymFloat& s);
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,582 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/SymBool.h>
|
||||
#include <c10/core/SymNodeImpl.h>
|
||||
#include <c10/macros/Export.h>
|
||||
#include <c10/macros/Macros.h>
|
||||
#include <c10/util/Exception.h>
|
||||
#include <c10/util/Optional.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <type_traits>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
class SymFloat;
|
||||
|
||||
// SymInt represents either a regular int64_t, or a symbolic integer
|
||||
// (represented in a type erased way as SymNode). The intention is for SymInt
|
||||
// to represent symbolic sizes that arise when doing shape computation in
|
||||
// operator kernels. This allows for tracing through programs without baking in
|
||||
// concrete sizes into kernel calls.
|
||||
//
|
||||
// SymInt has an API equivalent to int64_t. In particular, it is a value type.
|
||||
// Internally, SymInt is represented in a clever packed way, so that it only
|
||||
// occupies one word of space; but morally, it is a union between an int64_t
|
||||
// and an intrusive pointer to SymNodeImpl.
|
||||
//
|
||||
// Invariant: the referenced SymNodeImpl is guaranteed to be a SymNode where
|
||||
// is_int() returns true
|
||||
|
||||
class C10_API SymInt {
|
||||
public:
|
||||
enum Unchecked {
|
||||
UNCHECKED,
|
||||
};
|
||||
|
||||
/*implicit*/ SymInt(int64_t d) : data_(d) {
|
||||
if (is_heap_allocated()) {
|
||||
// Large negative number, heap allocate it
|
||||
promote_to_negative();
|
||||
}
|
||||
}
|
||||
SymInt() : data_(0) {}
|
||||
SymInt(SymNode n);
|
||||
|
||||
// unchecked c-tor accepting raw `data_`
|
||||
// One appropriate use for this is when you are constructing a symint
|
||||
// in a situation where you know it is non-negative (or, if it is negative,
|
||||
// the negative value is -1; i.e., not user controlled)
|
||||
SymInt(Unchecked /*unused*/, int64_t d) : data_(d) {}
|
||||
|
||||
SymInt(const SymInt& s) : data_(s.data_) {
|
||||
if (s.is_heap_allocated()) {
|
||||
c10::raw::intrusive_ptr::incref(s.toSymNodeImplUnowned());
|
||||
}
|
||||
}
|
||||
SymInt(SymInt&& s) noexcept : data_(s.data_) {
|
||||
s.data_ = 0;
|
||||
}
|
||||
|
||||
SymInt& operator=(const SymInt& s) {
|
||||
if (this != &s) {
|
||||
release_();
|
||||
data_ = s.data_;
|
||||
if (s.is_heap_allocated()) {
|
||||
c10::raw::intrusive_ptr::incref(s.toSymNodeImplUnowned());
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
SymInt& operator=(SymInt&& s) noexcept {
|
||||
if (this != &s) {
|
||||
release_(); // release the current SymNode if any
|
||||
data_ = s.data_;
|
||||
if (s.is_heap_allocated())
|
||||
s.data_ = 0;
|
||||
};
|
||||
return *this;
|
||||
}
|
||||
|
||||
SymNodeImpl* toSymNodeImplUnowned() const {
|
||||
TORCH_INTERNAL_ASSERT_DEBUG_ONLY(is_heap_allocated());
|
||||
uint64_t unextended_bits = static_cast<uint64_t>(data_) & ~MASK;
|
||||
uint64_t sign_bit_mask = 1ULL << (62 - 1);
|
||||
// https://stackoverflow.com/questions/42534749/signed-extension-from-24-bit-to-32-bit-in-c
|
||||
uint64_t extended_bits = (unextended_bits ^ sign_bit_mask) - sign_bit_mask;
|
||||
return static_cast<SymNodeImpl*>(
|
||||
// NOLINTNEXTLINE(performance-no-int-to-ptr, bugprone*)
|
||||
reinterpret_cast<void*>(static_cast<uintptr_t>(extended_bits)));
|
||||
}
|
||||
|
||||
void release_() {
|
||||
if (is_heap_allocated()) {
|
||||
SymNode::reclaim(toSymNodeImplUnowned()); // steal
|
||||
}
|
||||
}
|
||||
|
||||
SymNodeImpl* release() && {
|
||||
#ifndef C10_MOBILE
|
||||
TORCH_INTERNAL_ASSERT(is_heap_allocated());
|
||||
auto* r = toSymNodeImplUnowned();
|
||||
data_ = 0; // transfer ownership
|
||||
return r;
|
||||
#else
|
||||
TORCH_INTERNAL_ASSERT(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Only valid if is_heap_allocated()
|
||||
SymNode toSymNode() const;
|
||||
|
||||
// Guaranteed to return a SymNode, wrapping using base if necessary
|
||||
SymNode wrap_node(const SymNode& base) const;
|
||||
|
||||
~SymInt() {
|
||||
release_();
|
||||
}
|
||||
|
||||
// Require the int to be non-symbolic, and if it is symbolic raise an
|
||||
// error. This is safe to use for C++ code that doesn't work for symbolic
|
||||
// shapes, and you don't have time to fix it immediately, as if we
|
||||
// try to trigger the path in C++ you'll appropriately get an error
|
||||
int64_t expect_int() const {
|
||||
if (auto r = maybe_as_int()) {
|
||||
return *r;
|
||||
}
|
||||
TORCH_CHECK_ALWAYS_SHOW_CPP_STACKTRACE(
|
||||
false, "when unpacking SymInt, expected int but got ", *this);
|
||||
}
|
||||
|
||||
// Test if we have a hint for this int (e.g., guard_int would work).
|
||||
// Most of the time this is true; it is only false when you have
|
||||
// an unbacked SymInt.
|
||||
bool has_hint() const;
|
||||
|
||||
// Insert a guard for the int to be its concrete value, and then return
|
||||
// that value. This operation always works, even if the int is symbolic,
|
||||
// so long as we know what the underlying value is (e.g., this won't work
|
||||
// if you call it on the size of nonzero output). Don't blindly put this
|
||||
// everywhere; you can cause overspecialization of PyTorch programs with
|
||||
// this method.
|
||||
//
|
||||
// It should be called as guard_int(__FILE__, __LINE__). The file and line
|
||||
// number can be used to diagnose overspecialization.
|
||||
int64_t guard_int(const char* file, int64_t line) const;
|
||||
|
||||
// Distinguish actual symbolic values from constants stored on the heap
|
||||
bool is_symbolic() const {
|
||||
return is_heap_allocated() &&
|
||||
!toSymNodeImplUnowned()->constant_int().has_value();
|
||||
}
|
||||
|
||||
// N.B. It's important to keep this definition in the header
|
||||
// as we expect if checks to be folded for mobile builds
|
||||
// where `is_heap_allocated` is always false and optimize dead code paths
|
||||
C10_ALWAYS_INLINE bool is_heap_allocated() const {
|
||||
#ifdef C10_MOBILE
|
||||
return false;
|
||||
#else
|
||||
return !check_range(data_);
|
||||
#endif
|
||||
}
|
||||
|
||||
SymInt operator+(const SymInt& sci) const {
|
||||
if (auto ma = maybe_as_int()) {
|
||||
if (auto mb = sci.maybe_as_int()) {
|
||||
return SymInt(*ma + *mb);
|
||||
}
|
||||
}
|
||||
return operator_add_slow_path(sci);
|
||||
}
|
||||
|
||||
SymInt operator-(const SymInt& sci) const {
|
||||
if (auto ma = maybe_as_int()) {
|
||||
if (auto mb = sci.maybe_as_int()) {
|
||||
return SymInt(*ma - *mb);
|
||||
}
|
||||
}
|
||||
return operator_sub_slow_path(sci);
|
||||
}
|
||||
|
||||
SymInt operator*(const SymInt& sci) const {
|
||||
if (auto ma = maybe_as_int()) {
|
||||
if (auto mb = sci.maybe_as_int()) {
|
||||
return SymInt(*ma * *mb);
|
||||
}
|
||||
}
|
||||
return operator_mul_slow_path(sci);
|
||||
}
|
||||
|
||||
SymInt operator/(const SymInt& sci) const {
|
||||
if (auto ma = maybe_as_int()) {
|
||||
if (auto mb = sci.maybe_as_int()) {
|
||||
return SymInt(*ma / *mb);
|
||||
}
|
||||
}
|
||||
return operator_div_slow_path(sci);
|
||||
}
|
||||
|
||||
SymInt operator%(const SymInt& sci) const {
|
||||
if (auto ma = maybe_as_int()) {
|
||||
if (auto mb = sci.maybe_as_int()) {
|
||||
return SymInt(*ma % *mb);
|
||||
}
|
||||
}
|
||||
return operator_mod_slow_path(sci);
|
||||
}
|
||||
|
||||
void operator*=(const SymInt& sci) {
|
||||
if (auto ma = maybe_as_int()) {
|
||||
if (auto mb = sci.maybe_as_int()) {
|
||||
*this = SymInt(*ma * *mb);
|
||||
return;
|
||||
}
|
||||
}
|
||||
operator_imul_slow_path(sci);
|
||||
}
|
||||
|
||||
void operator+=(const SymInt& sci) {
|
||||
if (auto ma = maybe_as_int()) {
|
||||
if (auto mb = sci.maybe_as_int()) {
|
||||
*this = SymInt(*ma + *mb);
|
||||
return;
|
||||
}
|
||||
}
|
||||
operator_iadd_slow_path(sci);
|
||||
}
|
||||
|
||||
void operator/=(const SymInt& sci) {
|
||||
if (auto ma = maybe_as_int()) {
|
||||
if (auto mb = sci.maybe_as_int()) {
|
||||
*this = SymInt(*ma / *mb);
|
||||
return;
|
||||
}
|
||||
}
|
||||
operator_idiv_slow_path(sci);
|
||||
}
|
||||
|
||||
SymInt clone() const;
|
||||
|
||||
SymBool sym_eq(const SymInt& sci) const {
|
||||
if (auto ma = maybe_as_int()) {
|
||||
if (auto mb = sci.maybe_as_int()) {
|
||||
return SymBool(*ma == *mb);
|
||||
}
|
||||
}
|
||||
return sym_eq_slow_path(sci);
|
||||
}
|
||||
|
||||
SymBool sym_ne(const SymInt& sci) const {
|
||||
if (auto ma = maybe_as_int()) {
|
||||
if (auto mb = sci.maybe_as_int()) {
|
||||
return SymBool(*ma != *mb);
|
||||
}
|
||||
}
|
||||
return sym_ne_slow_path(sci);
|
||||
}
|
||||
|
||||
SymBool sym_lt(const SymInt& sci) const {
|
||||
if (auto ma = maybe_as_int()) {
|
||||
if (auto mb = sci.maybe_as_int()) {
|
||||
return SymBool(*ma < *mb);
|
||||
}
|
||||
}
|
||||
return sym_lt_slow_path(sci);
|
||||
}
|
||||
|
||||
SymBool sym_le(const SymInt& sci) const {
|
||||
if (auto ma = maybe_as_int()) {
|
||||
if (auto mb = sci.maybe_as_int()) {
|
||||
return SymBool(*ma <= *mb);
|
||||
}
|
||||
}
|
||||
return sym_le_slow_path(sci);
|
||||
}
|
||||
|
||||
SymBool sym_gt(const SymInt& sci) const {
|
||||
if (auto ma = maybe_as_int()) {
|
||||
if (auto mb = sci.maybe_as_int()) {
|
||||
return SymBool(*ma > *mb);
|
||||
}
|
||||
}
|
||||
return sym_gt_slow_path(sci);
|
||||
}
|
||||
|
||||
SymBool sym_ge(const SymInt& sci) const {
|
||||
if (auto ma = maybe_as_int()) {
|
||||
if (auto mb = sci.maybe_as_int()) {
|
||||
return SymBool(*ma >= *mb);
|
||||
}
|
||||
}
|
||||
return sym_ge_slow_path(sci);
|
||||
}
|
||||
|
||||
bool operator==(const SymInt& o) const {
|
||||
return sym_eq(o).guard_bool(__FILE__, __LINE__);
|
||||
}
|
||||
bool operator!=(const SymInt& o) const {
|
||||
return sym_ne(o).guard_bool(__FILE__, __LINE__);
|
||||
}
|
||||
bool operator<(const SymInt& o) const {
|
||||
return sym_lt(o).guard_bool(__FILE__, __LINE__);
|
||||
}
|
||||
bool operator<=(const SymInt& o) const {
|
||||
return sym_le(o).guard_bool(__FILE__, __LINE__);
|
||||
}
|
||||
bool operator>(const SymInt& o) const {
|
||||
return sym_gt(o).guard_bool(__FILE__, __LINE__);
|
||||
}
|
||||
bool operator>=(const SymInt& o) const {
|
||||
return sym_ge(o).guard_bool(__FILE__, __LINE__);
|
||||
}
|
||||
|
||||
SymInt min(const SymInt& sci) const {
|
||||
if (auto ma = maybe_as_int()) {
|
||||
if (auto mb = sci.maybe_as_int()) {
|
||||
return SymInt(std::min(*ma, *mb));
|
||||
}
|
||||
}
|
||||
return min_slow_path(sci);
|
||||
}
|
||||
|
||||
SymInt max(const SymInt& sci) const {
|
||||
if (auto ma = maybe_as_int()) {
|
||||
if (auto mb = sci.maybe_as_int()) {
|
||||
return SymInt(std::max(*ma, *mb));
|
||||
}
|
||||
}
|
||||
return max_slow_path(sci);
|
||||
}
|
||||
|
||||
// If both are symbolic, this checks if
|
||||
// they share the same node.
|
||||
// If both are not symbolic this just checks normal equality.
|
||||
bool is_same(const SymInt& other) const;
|
||||
|
||||
operator SymFloat() const;
|
||||
|
||||
void unsafe_set_data(size_t nbytes) {
|
||||
TORCH_INTERNAL_ASSERT_DEBUG_ONLY(!is_heap_allocated());
|
||||
data_ = static_cast<int64_t>(nbytes);
|
||||
}
|
||||
|
||||
// Don't use this. Prefer maybe_as_int instead
|
||||
int64_t as_int_unchecked() const {
|
||||
TORCH_INTERNAL_ASSERT_DEBUG_ONLY(!is_heap_allocated());
|
||||
return data_;
|
||||
}
|
||||
|
||||
std::optional<int64_t> maybe_as_int() const {
|
||||
if (!is_heap_allocated()) {
|
||||
return data_;
|
||||
}
|
||||
return maybe_as_int_slow_path();
|
||||
}
|
||||
|
||||
// Return whether the integer is directly coercible to a SymInt
|
||||
// without requiring heap allocation. You don't need to use this
|
||||
// to check if you can pass an integer to SymInt; this is guaranteed
|
||||
// to work (it just might heap allocate!)
|
||||
static bool check_range(int64_t i) {
|
||||
return i > MAX_UNREPRESENTABLE_INT;
|
||||
}
|
||||
|
||||
// Return the min representable integer as a SymInt without
|
||||
// heap allocation. For quantities that count bytes (or larger),
|
||||
// this is still much larger than you need, so you may consider
|
||||
// using this as a more efficient version of MIN_INT
|
||||
static constexpr int64_t min_representable_int() {
|
||||
return MAX_UNREPRESENTABLE_INT + 1;
|
||||
}
|
||||
|
||||
private:
|
||||
void promote_to_negative();
|
||||
SymInt operator_add_slow_path(const SymInt& sci) const;
|
||||
SymInt operator_sub_slow_path(const SymInt& sci) const;
|
||||
SymInt operator_mul_slow_path(const SymInt& sci) const;
|
||||
SymInt operator_div_slow_path(const SymInt& sci) const;
|
||||
SymInt operator_mod_slow_path(const SymInt& sci) const;
|
||||
void operator_imul_slow_path(const SymInt& sci);
|
||||
void operator_iadd_slow_path(const SymInt& sci);
|
||||
void operator_idiv_slow_path(const SymInt& sci);
|
||||
SymBool sym_eq_slow_path(const SymInt& sci) const;
|
||||
SymBool sym_ne_slow_path(const SymInt& sci) const;
|
||||
SymBool sym_lt_slow_path(const SymInt& sci) const;
|
||||
SymBool sym_le_slow_path(const SymInt& sci) const;
|
||||
SymBool sym_gt_slow_path(const SymInt& sci) const;
|
||||
SymBool sym_ge_slow_path(const SymInt& sci) const;
|
||||
|
||||
SymInt min_slow_path(const SymInt& sci) const;
|
||||
SymInt max_slow_path(const SymInt& sci) const;
|
||||
|
||||
std::optional<int64_t> maybe_as_int_slow_path() const;
|
||||
|
||||
// Constraints on the internal representation:
|
||||
//
|
||||
// - Should represent positive and small negative ints
|
||||
// - No conversion necessary for operations on ints
|
||||
// - Must represent valid 64-bit pointers
|
||||
// - Is symbolic test should be FAST (two arithmetic instructions is too
|
||||
// much).
|
||||
// This code being a hotpath is based on Strobelight profiles of
|
||||
// is_heap_allocated(). FB only: https://fburl.com/strobelight/5l50ncxd
|
||||
// (you will need to change the time window).
|
||||
//
|
||||
// So, the scheme is to reserve large negative numbers (assuming
|
||||
// two's complement):
|
||||
//
|
||||
// - 0b0.... means we are a positive int
|
||||
// - 0b11... means we are a small negative int
|
||||
// - 0b10... means we are are a pointer. This means that
|
||||
// [-2^63, -2^62-1] are not representable as ints.
|
||||
// We don't actually need all of this space as on x86_64
|
||||
// as the top 16bits aren't used for anything
|
||||
static constexpr uint64_t MASK = 1ULL << 63 | 1ULL << 62 | 1ULL << 61;
|
||||
static constexpr uint64_t IS_SYM = 1ULL << 63 | 1ULL << 61;
|
||||
// We must manually translate the bit pattern test into a greater
|
||||
// than test because compiler doesn't figure it out:
|
||||
// https://godbolt.org/z/356aferaW
|
||||
static constexpr int64_t MAX_UNREPRESENTABLE_INT =
|
||||
-1LL & static_cast<int64_t>(~(1ULL << 62));
|
||||
int64_t data_;
|
||||
};
|
||||
|
||||
/// Sum of a list of SymInt; accumulates into the c10::SymInt expression
|
||||
template <
|
||||
typename C,
|
||||
typename std::enable_if_t<
|
||||
std::is_same_v<typename C::value_type, c10::SymInt>,
|
||||
int> = 0>
|
||||
inline c10::SymInt multiply_integers(const C& container) {
|
||||
return std::accumulate(
|
||||
container.begin(),
|
||||
container.end(),
|
||||
c10::SymInt(1),
|
||||
[](const c10::SymInt& a, const c10::SymInt& b) { return a * b; });
|
||||
}
|
||||
|
||||
template <
|
||||
typename Iter,
|
||||
typename = std::enable_if_t<std::is_same_v<
|
||||
typename std::iterator_traits<Iter>::value_type,
|
||||
c10::SymInt>>>
|
||||
inline c10::SymInt multiply_integers(Iter begin, Iter end) {
|
||||
return std::accumulate(
|
||||
begin,
|
||||
end,
|
||||
c10::SymInt(1),
|
||||
[](const c10::SymInt& a, const c10::SymInt& b) { return a * b; });
|
||||
}
|
||||
|
||||
#define DECLARE_SYMINT_OP_INTONLY(scalar_t, RetTy) \
|
||||
C10_API RetTy operator%(const SymInt& a, scalar_t b); \
|
||||
C10_API RetTy operator%(scalar_t a, const SymInt& b);
|
||||
|
||||
#define DECLARE_SYMINT_OP(scalar_t, RetTy) \
|
||||
C10_API RetTy operator+(const SymInt& a, scalar_t b); \
|
||||
C10_API RetTy operator-(const SymInt& a, scalar_t b); \
|
||||
C10_API RetTy operator*(const SymInt& a, scalar_t b); \
|
||||
C10_API RetTy operator/(const SymInt& a, scalar_t b); \
|
||||
C10_API RetTy operator+(scalar_t a, const SymInt& b); \
|
||||
C10_API RetTy operator-(scalar_t a, const SymInt& b); \
|
||||
C10_API RetTy operator*(scalar_t a, const SymInt& b); \
|
||||
C10_API RetTy operator/(scalar_t a, const SymInt& b); \
|
||||
C10_API bool operator==(const SymInt& a, scalar_t b); \
|
||||
C10_API bool operator!=(const SymInt& a, scalar_t b); \
|
||||
C10_API bool operator<(const SymInt& a, scalar_t b); \
|
||||
C10_API bool operator<=(const SymInt& a, scalar_t b); \
|
||||
C10_API bool operator>(const SymInt& a, scalar_t b); \
|
||||
C10_API bool operator>=(const SymInt& a, scalar_t b); \
|
||||
C10_API bool operator==(scalar_t a, const SymInt& b); \
|
||||
C10_API bool operator!=(scalar_t a, const SymInt& b); \
|
||||
C10_API bool operator<(scalar_t a, const SymInt& b); \
|
||||
C10_API bool operator<=(scalar_t a, const SymInt& b); \
|
||||
C10_API bool operator>(scalar_t a, const SymInt& b); \
|
||||
C10_API bool operator>=(scalar_t a, const SymInt& b);
|
||||
|
||||
DECLARE_SYMINT_OP_INTONLY(int64_t, SymInt)
|
||||
DECLARE_SYMINT_OP_INTONLY(int32_t, SymInt)
|
||||
DECLARE_SYMINT_OP_INTONLY(uint64_t, SymInt)
|
||||
DECLARE_SYMINT_OP_INTONLY(uint32_t, SymInt)
|
||||
DECLARE_SYMINT_OP(int64_t, SymInt)
|
||||
DECLARE_SYMINT_OP(int32_t, SymInt) // make sure constants work
|
||||
DECLARE_SYMINT_OP(uint64_t, SymInt)
|
||||
DECLARE_SYMINT_OP(uint32_t, SymInt)
|
||||
DECLARE_SYMINT_OP(double, SymFloat)
|
||||
DECLARE_SYMINT_OP(float, SymFloat) // just for completeness
|
||||
|
||||
// On OSX size_t is different than uint64_t so we have to
|
||||
// define it separately
|
||||
#if defined(__APPLE__)
|
||||
DECLARE_SYMINT_OP_INTONLY(size_t, SymInt)
|
||||
DECLARE_SYMINT_OP(size_t, SymInt)
|
||||
#endif
|
||||
|
||||
#undef DECLARE_SYMINT_OP
|
||||
|
||||
C10_API std::ostream& operator<<(std::ostream& os, const SymInt& s);
|
||||
C10_API SymInt operator-(const SymInt& s);
|
||||
|
||||
inline bool sym_eq(int64_t a, int64_t b) {
|
||||
return a == b;
|
||||
}
|
||||
|
||||
inline SymBool sym_eq(const SymInt& a, const SymInt& b) {
|
||||
return a.sym_eq(b);
|
||||
}
|
||||
|
||||
inline bool sym_ne(int64_t a, int64_t b) {
|
||||
return a != b;
|
||||
}
|
||||
|
||||
inline SymBool sym_ne(const SymInt& a, const SymInt& b) {
|
||||
return a.sym_ne(b);
|
||||
}
|
||||
|
||||
inline bool sym_lt(int64_t a, int64_t b) {
|
||||
return a < b;
|
||||
}
|
||||
|
||||
inline SymBool sym_lt(const SymInt& a, const SymInt& b) {
|
||||
return a.sym_lt(b);
|
||||
}
|
||||
|
||||
inline bool sym_le(int64_t a, int64_t b) {
|
||||
return a <= b;
|
||||
}
|
||||
|
||||
inline SymBool sym_le(const SymInt& a, const SymInt& b) {
|
||||
return a.sym_le(b);
|
||||
}
|
||||
|
||||
inline bool sym_gt(int64_t a, int64_t b) {
|
||||
return a > b;
|
||||
}
|
||||
|
||||
inline SymBool sym_gt(const SymInt& a, const SymInt& b) {
|
||||
return a.sym_gt(b);
|
||||
}
|
||||
|
||||
inline bool sym_ge(int64_t a, int64_t b) {
|
||||
return a >= b;
|
||||
}
|
||||
|
||||
inline SymBool sym_ge(const SymInt& a, const SymInt& b) {
|
||||
return a.sym_ge(b);
|
||||
}
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
class numeric_limits<c10::SymInt> {
|
||||
public:
|
||||
static constexpr bool is_specialized = true;
|
||||
|
||||
static constexpr int64_t max() noexcept {
|
||||
return std::numeric_limits<int64_t>::max();
|
||||
}
|
||||
|
||||
static constexpr int64_t min() noexcept {
|
||||
return std::numeric_limits<int64_t>::min();
|
||||
}
|
||||
|
||||
static constexpr bool is_signed = true;
|
||||
static constexpr bool is_integer = true;
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,113 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/SymInt.h>
|
||||
#include <c10/util/ArrayRef.h>
|
||||
#include <c10/util/DimVector.h>
|
||||
#include <c10/util/Exception.h>
|
||||
#include <c10/util/irange.h>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
namespace c10 {
|
||||
using SymIntArrayRef = ArrayRef<SymInt>;
|
||||
|
||||
inline at::IntArrayRef asIntArrayRefUnchecked(c10::SymIntArrayRef ar) {
|
||||
return IntArrayRef(reinterpret_cast<const int64_t*>(ar.data()), ar.size());
|
||||
}
|
||||
|
||||
// TODO: a SymIntArrayRef containing a heap allocated large negative integer
|
||||
// can actually technically be converted to an IntArrayRef... but not with
|
||||
// the non-owning API we have here. We can't reinterpet cast; we have to
|
||||
// allocate another buffer and write the integers into it. If you need it,
|
||||
// we can do it. But I don't think you need it.
|
||||
|
||||
inline std::optional<at::IntArrayRef> asIntArrayRefSlowOpt(
|
||||
c10::SymIntArrayRef ar) {
|
||||
for (const c10::SymInt& sci : ar) {
|
||||
if (sci.is_heap_allocated()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
return {asIntArrayRefUnchecked(ar)};
|
||||
}
|
||||
|
||||
inline at::IntArrayRef asIntArrayRefSlow(
|
||||
c10::SymIntArrayRef ar,
|
||||
const char* file,
|
||||
int64_t line) {
|
||||
for (const c10::SymInt& sci : ar) {
|
||||
TORCH_CHECK(
|
||||
!sci.is_heap_allocated(),
|
||||
file,
|
||||
":",
|
||||
line,
|
||||
": SymIntArrayRef expected to contain only concrete integers");
|
||||
}
|
||||
return asIntArrayRefUnchecked(ar);
|
||||
}
|
||||
|
||||
// Even slower than asIntArrayRefSlow, as it forces an allocation for a
|
||||
// destination int, BUT it is able to force specialization (it never errors)
|
||||
inline c10::DimVector asIntArrayRefSlowAlloc(
|
||||
c10::SymIntArrayRef ar,
|
||||
const char* file,
|
||||
int64_t line) {
|
||||
c10::DimVector res(ar.size(), 0);
|
||||
for (const auto i : c10::irange(ar.size())) {
|
||||
res[i] = ar[i].guard_int(file, line);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
#define C10_AS_INTARRAYREF_SLOW(a) c10::asIntArrayRefSlow(a, __FILE__, __LINE__)
|
||||
#define C10_AS_INTARRAYREF_SLOW_ALLOC(a) \
|
||||
c10::asIntArrayRefSlowAlloc(a, __FILE__, __LINE__)
|
||||
|
||||
// Prefer using a more semantic constructor, like
|
||||
// fromIntArrayRefKnownNonNegative
|
||||
inline SymIntArrayRef fromIntArrayRefUnchecked(IntArrayRef array_ref) {
|
||||
return SymIntArrayRef(
|
||||
reinterpret_cast<const SymInt*>(array_ref.data()), array_ref.size());
|
||||
}
|
||||
|
||||
inline SymIntArrayRef fromIntArrayRefKnownNonNegative(IntArrayRef array_ref) {
|
||||
return fromIntArrayRefUnchecked(array_ref);
|
||||
}
|
||||
|
||||
inline SymIntArrayRef fromIntArrayRefSlow(IntArrayRef array_ref) {
|
||||
for (long i : array_ref) {
|
||||
TORCH_CHECK(
|
||||
SymInt::check_range(i),
|
||||
"IntArrayRef contains an int that cannot be represented as a SymInt: ",
|
||||
i);
|
||||
}
|
||||
return SymIntArrayRef(
|
||||
reinterpret_cast<const SymInt*>(array_ref.data()), array_ref.size());
|
||||
}
|
||||
|
||||
inline c10::SymBool sym_equals(SymIntArrayRef LHS, SymIntArrayRef RHS) {
|
||||
if (LHS.size() != RHS.size()) {
|
||||
return c10::SymBool(false);
|
||||
}
|
||||
|
||||
c10::SymBool result = sym_eq(LHS.size(), RHS.size());
|
||||
for (size_t i = 0; i < RHS.size(); ++i) {
|
||||
c10::SymBool equals = sym_eq(LHS[i], RHS[i]);
|
||||
std::optional<bool> equals_bool = equals.maybe_as_bool();
|
||||
|
||||
if (equals_bool.has_value() && !*equals_bool) {
|
||||
// Early return if element comparison is known to be false
|
||||
return equals;
|
||||
}
|
||||
result = result.sym_and(equals);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,261 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <c10/macros/Export.h>
|
||||
#include <c10/util/ArrayRef.h>
|
||||
#include <c10/util/Exception.h>
|
||||
#include <c10/util/intrusive_ptr.h>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
C10_DIAGNOSTIC_PUSH_AND_IGNORED_IF_DEFINED("-Wunused-parameter")
|
||||
|
||||
namespace c10 {
|
||||
|
||||
class SymNodeImpl;
|
||||
using SymNode = c10::intrusive_ptr<SymNodeImpl>;
|
||||
|
||||
// When you add a method, you also need to edit
|
||||
// torch/csrc/jit/python/init.cpp
|
||||
// torch/csrc/utils/python_symnode.h
|
||||
// c10/core/ConstantSymNodeImpl.h
|
||||
class C10_API SymNodeImpl : public c10::intrusive_ptr_target {
|
||||
public:
|
||||
~SymNodeImpl() override = default;
|
||||
|
||||
template <typename T>
|
||||
c10::intrusive_ptr<T> dyn_cast() const {
|
||||
return c10::intrusive_ptr<T>::reclaim_copy(dynamic_cast<T*>(this));
|
||||
}
|
||||
|
||||
// these could be pure virtual when we implement LTC versions
|
||||
virtual bool is_int() {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual bool is_bool() {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual bool is_float() {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual bool is_nested_int() const {
|
||||
return false;
|
||||
}
|
||||
virtual SymNode add(const SymNode& other) {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual SymNode sub(const SymNode& other) {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual SymNode mul(const SymNode& other) {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
// NB: legacy, prefer float_truediv or int_truediv
|
||||
virtual SymNode truediv(const SymNode& other) {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual SymNode float_truediv(const SymNode& other) {
|
||||
return truediv(other);
|
||||
}
|
||||
virtual SymNode int_truediv(const SymNode& other) {
|
||||
return truediv(other);
|
||||
}
|
||||
// NB: legacy, prefer float_pow or pow_by_natural
|
||||
virtual SymNode pow(const SymNode& other) {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual SymNode float_pow(const SymNode& other) {
|
||||
return pow(other);
|
||||
}
|
||||
virtual SymNode pow_by_natural(const SymNode& other) {
|
||||
return pow(other);
|
||||
}
|
||||
// NB: legacy, prefer int_floordiv
|
||||
virtual SymNode floordiv(const SymNode& other) {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual SymNode int_floordiv(const SymNode& other) {
|
||||
return floordiv(other);
|
||||
}
|
||||
virtual SymNode mod(const SymNode& other) {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual SymNode eq(const SymNode& other) {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual SymNode ne(const SymNode& other) {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual SymNode gt(const SymNode& other) {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual SymNode lt(const SymNode& other) {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual SymNode le(const SymNode& other) {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual SymNode ge(const SymNode& other) {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual SymNode ceil() {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual SymNode floor() {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual SymNode neg() {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual SymNode sym_min(const SymNode& other) {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual SymNode sym_max(const SymNode& other) {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual SymNode sym_or(const SymNode& other) {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual SymNode sym_and(const SymNode& other) {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual SymNode sym_not() {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual SymNode sym_ite(const SymNode& then_val, const SymNode& else_val) {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
// NB: self is ignored here, only the arguments are used
|
||||
virtual SymNode is_contiguous(
|
||||
ArrayRef<SymNode> sizes,
|
||||
ArrayRef<SymNode> strides) {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual SymNode is_channels_last_contiguous_2d(
|
||||
ArrayRef<SymNode> sizes,
|
||||
ArrayRef<SymNode> strides) {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual SymNode is_channels_last_contiguous_3d(
|
||||
ArrayRef<SymNode> sizes,
|
||||
ArrayRef<SymNode> strides) {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual SymNode is_channels_last_strides_2d(
|
||||
ArrayRef<SymNode> sizes,
|
||||
ArrayRef<SymNode> strides) {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual SymNode is_channels_last_strides_3d(
|
||||
ArrayRef<SymNode> sizes,
|
||||
ArrayRef<SymNode> strides) {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual SymNode is_non_overlapping_and_dense(
|
||||
ArrayRef<SymNode> sizes,
|
||||
ArrayRef<SymNode> strides) {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual SymNode clone() {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual SymNode sym_float() {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual SymNode wrap_int(int64_t num) {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual SymNode wrap_float(double num) {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual SymNode wrap_bool(bool num) {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual int64_t guard_int(const char* file, int64_t line) {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual bool guard_bool(const char* file, int64_t line) {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual double guard_float(const char* file, int64_t line) {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual bool guard_size_oblivious(const char* file, int64_t line) {
|
||||
// No improvement for unbacked SymBools by default, replace this
|
||||
// with a better implementation!
|
||||
return guard_bool(file, line);
|
||||
}
|
||||
virtual bool guard_or_false(const char* file, int64_t line) {
|
||||
// Note: PT2 primarily uses PythonSymNodeImpl for this functionality.
|
||||
// XLA is currently the main consumer of this fallback path since it uses
|
||||
// ahead-of-time compilation and cannot depend on Python runtime.
|
||||
return guard_bool(file, line);
|
||||
}
|
||||
virtual bool statically_known_true(const char* file, int64_t line) {
|
||||
// Note: PT2 primarily uses PythonSymNodeImpl for this functionality.
|
||||
// XLA is currently the main consumer of this fallback path since it uses
|
||||
// ahead-of-time compilation and cannot depend on Python runtime.
|
||||
return guard_bool(file, line);
|
||||
}
|
||||
virtual bool guard_or_true(const char* file, int64_t line) {
|
||||
// Note: PT2 primarily uses PythonSymNodeImpl for this functionality.
|
||||
// XLA is currently the main consumer of this fallback path since it uses
|
||||
// ahead-of-time compilation and cannot depend on Python runtime.
|
||||
return guard_bool(file, line);
|
||||
}
|
||||
virtual bool expect_true(const char* file, int64_t line) {
|
||||
// No improvement for unbacked SymBools by default, replace this
|
||||
// with a better implementation!
|
||||
return guard_bool(file, line);
|
||||
}
|
||||
virtual int64_t int_() {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual bool bool_() {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual bool has_hint() {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual std::string str() {
|
||||
TORCH_CHECK(false, "NYI");
|
||||
}
|
||||
virtual std::string _graph_repr() {
|
||||
return str();
|
||||
}
|
||||
virtual std::optional<int64_t> nested_int() {
|
||||
return std::nullopt;
|
||||
}
|
||||
virtual std::optional<int64_t> nested_int_coeff() {
|
||||
return std::nullopt;
|
||||
}
|
||||
virtual std::optional<int64_t> constant_int() {
|
||||
return std::nullopt;
|
||||
}
|
||||
virtual std::optional<bool> constant_bool() {
|
||||
return std::nullopt;
|
||||
}
|
||||
virtual std::optional<int64_t> maybe_as_int() {
|
||||
return std::nullopt;
|
||||
}
|
||||
virtual bool is_constant() {
|
||||
return false;
|
||||
}
|
||||
virtual bool is_symbolic() {
|
||||
return true;
|
||||
}
|
||||
std::ostream& operator<<(std::ostream& os) {
|
||||
os << str();
|
||||
return os;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace c10
|
||||
C10_DIAGNOSTIC_POP()
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,234 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
#include <c10/core/MemoryFormat.h>
|
||||
#include <c10/core/SymBool.h>
|
||||
#include <c10/core/SymInt.h>
|
||||
#include <c10/macros/Export.h>
|
||||
#include <c10/macros/Macros.h>
|
||||
#include <c10/util/DimVector.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
class C10_API SymbolicShapeMeta {
|
||||
public:
|
||||
// Basic metadata from which other quantities are derived
|
||||
SymDimVector sizes_ = {0};
|
||||
SymDimVector strides_ = {1};
|
||||
SymInt storage_offset_ = 0;
|
||||
|
||||
bool strides_valid_ = true; // e.g. for sparse where there are no strides
|
||||
|
||||
SymbolicShapeMeta() = default;
|
||||
~SymbolicShapeMeta() = default;
|
||||
SymbolicShapeMeta(const SymbolicShapeMeta& other);
|
||||
SymbolicShapeMeta(SymbolicShapeMeta&& other) = delete;
|
||||
SymbolicShapeMeta& operator=(const SymbolicShapeMeta& other) = delete;
|
||||
SymbolicShapeMeta& operator=(SymbolicShapeMeta&& other) = delete;
|
||||
|
||||
void refresh_numel() {
|
||||
// Non-const, don't need to hold mutables_ lock
|
||||
available_.fetch_and(~numel_avail);
|
||||
numel_ = 1;
|
||||
}
|
||||
|
||||
void refresh_contiguous() {
|
||||
// Non-const, don't need to hold mutables_ lock
|
||||
available_.fetch_and(numel_avail);
|
||||
is_contiguous_ = false;
|
||||
is_channels_last_contiguous_ = false;
|
||||
is_channels_last_3d_contiguous_ = false;
|
||||
is_channels_last_ = false;
|
||||
is_channels_last_3d_ = false;
|
||||
is_non_overlapping_and_dense_ = false;
|
||||
}
|
||||
|
||||
int64_t dim() const {
|
||||
return static_cast<int64_t>(sizes_.size());
|
||||
}
|
||||
|
||||
// Accessors for derived quantities, computed lazily on first access
|
||||
|
||||
bool has_numel() const {
|
||||
return available_.load() & numel_avail;
|
||||
}
|
||||
bool has_is_contiguous() const {
|
||||
return available_.load() & is_contiguous_avail;
|
||||
}
|
||||
bool has_is_channels_last_contiguous() const {
|
||||
return available_.load() & is_channels_last_contiguous_avail;
|
||||
}
|
||||
bool has_is_channels_last_3d_contiguous() const {
|
||||
return available_.load() & is_channels_last_3d_contiguous_avail;
|
||||
}
|
||||
bool has_is_channels_last() const {
|
||||
return available_.load() & is_channels_last_avail;
|
||||
}
|
||||
bool has_is_channels_last_3d() const {
|
||||
return available_.load() & is_channels_last_3d_avail;
|
||||
}
|
||||
bool has_is_non_overlapping_and_dense() const {
|
||||
return available_.load() & is_non_overlapping_and_dense_avail;
|
||||
}
|
||||
|
||||
// Accessors to cached derived properties
|
||||
// DO NOT call with mutables_ lock held
|
||||
const SymInt& numel() const {
|
||||
if (C10_UNLIKELY(!has_numel())) {
|
||||
init_numel();
|
||||
}
|
||||
return numel_;
|
||||
}
|
||||
|
||||
const SymBool& is_contiguous(at::MemoryFormat memory_format) const {
|
||||
if (memory_format == at::MemoryFormat::ChannelsLast) {
|
||||
return this->is_channels_last_contiguous();
|
||||
} else if (memory_format == at::MemoryFormat::ChannelsLast3d) {
|
||||
return this->is_channels_last_3d_contiguous();
|
||||
}
|
||||
return this->is_contiguous();
|
||||
}
|
||||
|
||||
const SymBool& is_contiguous() const {
|
||||
if (C10_UNLIKELY(!has_is_contiguous())) {
|
||||
init_is_contiguous();
|
||||
}
|
||||
return is_contiguous_;
|
||||
}
|
||||
|
||||
const SymBool& is_channels_last_contiguous() const {
|
||||
if (C10_UNLIKELY(!has_is_channels_last_contiguous())) {
|
||||
init_is_channels_last_contiguous();
|
||||
}
|
||||
return is_channels_last_contiguous_;
|
||||
}
|
||||
|
||||
const SymBool& is_channels_last_3d_contiguous() const {
|
||||
if (C10_UNLIKELY(!has_is_channels_last_3d_contiguous())) {
|
||||
init_is_channels_last_3d_contiguous();
|
||||
}
|
||||
return is_channels_last_3d_contiguous_;
|
||||
}
|
||||
|
||||
const SymBool& is_channels_last() const {
|
||||
if (C10_UNLIKELY(!has_is_channels_last())) {
|
||||
init_is_channels_last();
|
||||
}
|
||||
return is_channels_last_;
|
||||
}
|
||||
|
||||
const SymBool& is_channels_last_3d() const {
|
||||
if (C10_UNLIKELY(!has_is_channels_last_3d())) {
|
||||
init_is_channels_last_3d();
|
||||
}
|
||||
return is_channels_last_3d_;
|
||||
}
|
||||
|
||||
const SymBool& is_non_overlapping_and_dense() const {
|
||||
if (C10_UNLIKELY(!has_is_non_overlapping_and_dense())) {
|
||||
init_is_non_overlapping_and_dense();
|
||||
}
|
||||
return is_non_overlapping_and_dense_;
|
||||
}
|
||||
|
||||
// Assumptions so we can short-circuit computation
|
||||
// NOTE: Don't need to lock mutables_ since these aren't const
|
||||
void assume_contiguous(SymBool val = true) {
|
||||
is_contiguous_ = std::move(val);
|
||||
available_.fetch_or(is_contiguous_avail);
|
||||
}
|
||||
void assume_channels_last_contiguous(SymBool val = true) {
|
||||
is_channels_last_contiguous_ = std::move(val);
|
||||
available_.fetch_or(is_channels_last_contiguous_avail);
|
||||
}
|
||||
void assume_channels_last_3d_contiguous(SymBool val = true) {
|
||||
is_channels_last_3d_contiguous_ = std::move(val);
|
||||
available_.fetch_or(is_channels_last_3d_contiguous_avail);
|
||||
}
|
||||
void assume_channels_last(SymBool val = true) {
|
||||
is_channels_last_ = std::move(val);
|
||||
available_.fetch_or(is_channels_last_avail);
|
||||
}
|
||||
void assume_channels_last_3d(SymBool val = true) {
|
||||
is_channels_last_3d_ = std::move(val);
|
||||
available_.fetch_or(is_channels_last_3d_avail);
|
||||
}
|
||||
void assume_non_overlapping_and_dense(SymBool val = true) {
|
||||
is_non_overlapping_and_dense_ = std::move(val);
|
||||
available_.fetch_or(is_non_overlapping_and_dense_avail);
|
||||
}
|
||||
|
||||
private:
|
||||
SymBool compute_contiguous() const;
|
||||
SymBool compute_channels_last_contiguous_2d() const;
|
||||
SymBool compute_channels_last_contiguous_3d() const;
|
||||
SymBool compute_strides_like_channels_last_2d() const;
|
||||
SymBool compute_strides_like_channels_last_3d() const;
|
||||
SymBool compute_non_overlapping_and_dense() const;
|
||||
|
||||
// These are little wrappers over the real compute_ functions that
|
||||
// can make use of other contiguity fields to short circuit.
|
||||
// They need to be implemented separately for SymBool, as SymBool does
|
||||
// not short circuit.
|
||||
// TODO: should the SymBool cases avoid the short circuit? Need to reason
|
||||
// if its correct, and reason if the simpler expressions are better for
|
||||
// analysis (maybe not!)
|
||||
|
||||
SymBool compute_channels_last_contiguous_3d_dim5() const;
|
||||
SymBool compute_channels_last_2d_dim5() const;
|
||||
SymBool compute_channels_last_3d_dim5() const;
|
||||
SymBool compute_is_non_overlapping_and_dense_dim4() const;
|
||||
SymBool compute_is_non_overlapping_and_dense_dim5() const;
|
||||
SymBool compute_is_non_overlapping_and_dense_anydim() const;
|
||||
|
||||
void init_numel() const;
|
||||
void init_is_contiguous() const;
|
||||
void init_is_channels_last_contiguous() const;
|
||||
void init_is_channels_last_3d_contiguous() const;
|
||||
void init_is_channels_last() const;
|
||||
void init_is_channels_last_3d() const;
|
||||
void init_is_non_overlapping_and_dense() const;
|
||||
|
||||
// NOTE: These only set if !has_foo()
|
||||
void set_numel(SymInt val) const;
|
||||
void set_is_contiguous(SymBool val) const;
|
||||
void set_is_channels_last_contiguous(SymBool val) const;
|
||||
void set_is_channels_last_3d_contiguous(SymBool val) const;
|
||||
void set_is_channels_last(SymBool val) const;
|
||||
void set_is_channels_last_3d(SymBool val) const;
|
||||
void set_is_non_overlapping_and_dense(SymBool val) const;
|
||||
|
||||
// Lazily initialized variables, with the corresponding available_ flag
|
||||
// indicating whether the value has been initialized
|
||||
mutable std::atomic<int> available_{0};
|
||||
|
||||
enum avail {
|
||||
numel_avail = 1 << 0,
|
||||
is_contiguous_avail = 1 << 1,
|
||||
is_channels_last_contiguous_avail = 1 << 2,
|
||||
is_channels_last_3d_contiguous_avail = 1 << 3,
|
||||
is_channels_last_avail = 1 << 4,
|
||||
is_channels_last_3d_avail = 1 << 5,
|
||||
is_non_overlapping_and_dense_avail = 1 << 6,
|
||||
};
|
||||
|
||||
// Mutex to prevent races when initializing the variable from const accessors
|
||||
mutable std::mutex mutables_;
|
||||
mutable SymInt numel_ = 1;
|
||||
mutable SymBool is_contiguous_{true};
|
||||
mutable SymBool is_channels_last_contiguous_{false};
|
||||
mutable SymBool is_channels_last_3d_contiguous_{false};
|
||||
mutable SymBool is_channels_last_{false};
|
||||
mutable SymBool is_channels_last_3d_{false};
|
||||
mutable SymBool is_non_overlapping_and_dense_{true};
|
||||
};
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,791 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/Backend.h>
|
||||
#include <c10/core/DefaultDtype.h>
|
||||
#include <c10/core/Device.h>
|
||||
#include <c10/core/DeviceType.h>
|
||||
#include <c10/core/DispatchKey.h>
|
||||
#include <c10/core/Layout.h>
|
||||
#include <c10/core/MemoryFormat.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/ScalarTypeToTypeMeta.h>
|
||||
|
||||
#include <c10/macros/Export.h>
|
||||
#include <c10/macros/Macros.h>
|
||||
#include <c10/util/Exception.h>
|
||||
#include <optional>
|
||||
|
||||
#include <cstdint>
|
||||
#include <iosfwd>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
C10_DIAGNOSTIC_PUSH_AND_IGNORED_IF_DEFINED("-Wswitch-enum")
|
||||
|
||||
namespace c10 {
|
||||
|
||||
inline ScalarType dtype_or_default(std::optional<ScalarType> dtype) {
|
||||
return dtype.value_or(get_default_dtype_as_scalartype());
|
||||
}
|
||||
|
||||
inline caffe2::TypeMeta dtype_or_default(
|
||||
std::optional<caffe2::TypeMeta> dtype) {
|
||||
return dtype.value_or(get_default_dtype());
|
||||
}
|
||||
|
||||
inline Layout layout_or_default(std::optional<Layout> layout) {
|
||||
return layout.value_or(kStrided);
|
||||
}
|
||||
|
||||
inline Device device_or_default(std::optional<Device> device) {
|
||||
return device.value_or(Device(kCPU));
|
||||
}
|
||||
|
||||
inline bool pinned_memory_or_default(std::optional<bool> pinned_memory) {
|
||||
return pinned_memory.value_or(false);
|
||||
}
|
||||
|
||||
/// A class to encapsulate construction axes of an Tensor. TensorOptions was
|
||||
/// designed to support the Python style API for specifying construction options
|
||||
/// on factory functions, e.g.,
|
||||
///
|
||||
/// torch.zeros(2, 3, dtype=torch.int32)
|
||||
///
|
||||
/// Because C++ doesn't natively support keyword arguments, there must be
|
||||
/// another way of specifying keyword-like arguments. TensorOptions is a
|
||||
/// builder class which can be used to construct this "dictionary" of keyword
|
||||
/// arguments: functions which support TensorOptions conventionally take this
|
||||
/// argument optionally as their last argument.
|
||||
///
|
||||
/// WARNING: In PyTorch, there are `torch::` variants of factory functions,
|
||||
/// e.g., torch::zeros for at::zeros. These return Variables (while the
|
||||
/// stock ATen functions return plain Tensors). If you mix these functions
|
||||
/// up, you WILL BE SAD.
|
||||
///
|
||||
/// Rather than use the constructor of this class directly, you should prefer to
|
||||
/// use the constructor functions, and then chain setter methods on top of them.
|
||||
///
|
||||
/// at::device(at::kCUDA).dtype(kInt)
|
||||
/// at::dtype(at::kInt)
|
||||
///
|
||||
/// Additionally, anywhere a TensorOptions is expected, you can directly
|
||||
/// pass at::kCUDA / at::kInt, and it will implicitly convert to a
|
||||
/// TensorOptions.
|
||||
///
|
||||
/// Here are some recommended ways to create a 2x2 tensor of zeros
|
||||
/// with certain properties. These all *implicitly* make use of
|
||||
/// TensorOptions, even if they don't mention the class explicitly:
|
||||
///
|
||||
/// at::zeros({2,2}, at::kCUDA);
|
||||
/// at::zeros({2,2}, at::kLong);
|
||||
/// at::zeros({2,2}, at::device(at::kCUDA).dtype(at::kLong()));
|
||||
/// at::zeros({2,2}, at::device({at::kCUDA, 1})); // place on device 1
|
||||
/// at::zeros({2,2}, at::requires_grad());
|
||||
///
|
||||
|
||||
/// NOTE [ TensorOptions Constructors ]
|
||||
///
|
||||
/// TensorOptions is like a dictionary with entries from the set:
|
||||
/// {requires_grad, device, dtype, layout}, where each entry may be
|
||||
/// unspecified (i.e., is optional). It is used to specify the properties of
|
||||
/// tensors in many places both in C++ internal and API, e.g., tensor factory
|
||||
/// methods like `at::empty({10}, options)`, tensor conversions like
|
||||
/// `tensor.to(...)`, etc.
|
||||
///
|
||||
/// To provide a simple API that is consistent with Python, where one can do
|
||||
/// `torch.empty(sizes, X)` with `X` being a `torch.device`, `torch.dtype`, or a
|
||||
/// `torch.layout`, we want TensorOptions to be implicitly convertible from
|
||||
/// `ScalarType dtype`, `Layout layout` and `Device device`. Therefore, we have
|
||||
/// three implicit constructors from each of these three types.
|
||||
///
|
||||
/// This is sufficient for `ScalarType` and `Layout` as they are simple Enum
|
||||
/// classes. However, `Device` is an ordinary class with implicit constructors
|
||||
/// `Device(DeviceType, DeviceIndex = -1)` and `Device(std::string)` to be
|
||||
/// consistent with Python API, where strings are treated as equivalent with a
|
||||
/// `torch.device` object (e.g., "cuda:1" can be passed to everywhere a
|
||||
/// `torch.device("cuda:1")` is accepted). To support the syntax
|
||||
/// `at::empty({10}, {kCUDA, 1})` and `tensor.to(kCUDA)`, we need to make sure
|
||||
/// that `TensorOptions` is implicitly constructible with any arguments that a
|
||||
/// `Device` can constructed from. So we have,
|
||||
///
|
||||
/// /* implicit */ TensorOptions(T&& device) : TensorOptions() {
|
||||
/// this->set_device(device);
|
||||
/// }
|
||||
///
|
||||
/// template <typename... Args,
|
||||
/// typename = std::enable_if_t<std::is_constructible<Device,
|
||||
/// Args&&...>::value>>
|
||||
/// /* implicit */ TensorOptions(Args&&... args)
|
||||
/// : TensorOptions(Device(std::forward<Args>(args)...)) {}
|
||||
///
|
||||
///
|
||||
/// But this will be problematic. Consider this: `TensorOptions({kCUDA, 1})`.
|
||||
/// Compiler will complain about ambiguity between the copy constructor and the
|
||||
/// `Device` constructor because `{kCUDA, 1}` can be converted to both a
|
||||
/// `TensorOption` and a `Device`.
|
||||
///
|
||||
/// To get around this, we templatize the `Device` constructor. Since overload
|
||||
/// resolution is done before template resolution, our problem is solved.
|
||||
|
||||
DispatchKey computeDispatchKey(
|
||||
std::optional<ScalarType> dtype,
|
||||
std::optional<Layout> layout,
|
||||
std::optional<Device> device);
|
||||
|
||||
struct C10_API TensorOptions {
|
||||
TensorOptions()
|
||||
: requires_grad_(false),
|
||||
pinned_memory_(false),
|
||||
has_device_(false),
|
||||
has_dtype_(false),
|
||||
has_layout_(false),
|
||||
has_requires_grad_(false),
|
||||
has_pinned_memory_(false),
|
||||
has_memory_format_(false) {}
|
||||
|
||||
/// Constructs a `TensorOptions` object with the given layout.
|
||||
/* implicit */ TensorOptions(Layout layout) : TensorOptions() {
|
||||
this->set_layout(layout);
|
||||
}
|
||||
|
||||
/// Constructs a `TensorOptions` object with the given device.
|
||||
/// See NOTE [ TensorOptions Constructors ] on why this is templatized.
|
||||
template <
|
||||
typename T,
|
||||
typename = std::enable_if_t<std::is_same_v<std::decay_t<T>, Device>>>
|
||||
/* implicit */ TensorOptions(T&& device) : TensorOptions() {
|
||||
this->set_device(std::forward<T>(device));
|
||||
}
|
||||
|
||||
/// Constructs a `TensorOptions` object from arguments allowed in `Device`
|
||||
/// constructors.
|
||||
///
|
||||
/// See NOTE [ TensorOptions Constructors ].
|
||||
///
|
||||
/// NB: Ideally we only allow implicit constructors here. But there is no easy
|
||||
/// way to detect them. So we have this one that allows explicit
|
||||
/// constructors too.
|
||||
template <
|
||||
typename... Args,
|
||||
typename = std::enable_if_t<std::is_constructible_v<Device, Args&&...>>>
|
||||
/* implicit */ TensorOptions(Args&&... args)
|
||||
: TensorOptions(Device(std::forward<Args>(args)...)) {}
|
||||
|
||||
/// Constructs a `TensorOptions` object with the given dtype.
|
||||
/* implicit */ TensorOptions(caffe2::TypeMeta dtype) : TensorOptions() {
|
||||
this->set_dtype(dtype);
|
||||
}
|
||||
|
||||
/// legacy constructor to support ScalarType
|
||||
/* implicit */ TensorOptions(ScalarType dtype) : TensorOptions() {
|
||||
this->set_dtype(dtype);
|
||||
}
|
||||
|
||||
/// Constructs a `TensorOptions` object with the given memory format.
|
||||
/* implicit */ TensorOptions(MemoryFormat memory_format) : TensorOptions() {
|
||||
set_memory_format(memory_format);
|
||||
}
|
||||
|
||||
/// Return a copy of `TensorOptions` with `device` set to the given one, or
|
||||
/// cleared if `device` is `nullopt`.
|
||||
[[nodiscard]] TensorOptions device(
|
||||
std::optional<Device> device) const noexcept {
|
||||
TensorOptions r = *this;
|
||||
r.set_device(device);
|
||||
return r;
|
||||
}
|
||||
|
||||
/// Return a copy of `TensorOptions` with `device` set to the given one.
|
||||
/// (This overload ensures that variadic template std::optional constructor
|
||||
/// for Device work correctly.)
|
||||
template <typename... Args>
|
||||
[[nodiscard]] TensorOptions device(Args&&... args) const noexcept {
|
||||
return device(
|
||||
std::optional<Device>(std::in_place, std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
/// Return a copy of `TensorOptions`, but with device set to CUDA, and the
|
||||
/// device index set to the given one.
|
||||
///
|
||||
/// TODO: This function encourages bad behavior (assuming CUDA is
|
||||
/// the only device that matters). Get rid of it / rename it.
|
||||
[[nodiscard]] TensorOptions device_index(
|
||||
c10::DeviceIndex device_index) const noexcept {
|
||||
return device(Device::Type::CUDA, device_index);
|
||||
}
|
||||
|
||||
/// Return a copy of `TensorOptions` with `dtype` set to the given one.
|
||||
[[nodiscard]] TensorOptions dtype(
|
||||
std::optional<caffe2::TypeMeta> dtype) const noexcept {
|
||||
TensorOptions r = *this;
|
||||
r.set_dtype(dtype);
|
||||
return r;
|
||||
}
|
||||
|
||||
// legacy function to support ScalarType
|
||||
[[nodiscard]] TensorOptions dtype(
|
||||
std::optional<ScalarType> dtype) const noexcept {
|
||||
TensorOptions r = *this;
|
||||
r.set_dtype(dtype);
|
||||
return r;
|
||||
}
|
||||
|
||||
// Since dtype is taken...
|
||||
template <typename T>
|
||||
TensorOptions& dtype() {
|
||||
dtype_ = caffe2::TypeMeta::Make<T>();
|
||||
has_dtype_ = true;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Sets the layout of the `TensorOptions`.
|
||||
[[nodiscard]] TensorOptions layout(
|
||||
std::optional<Layout> layout) const noexcept {
|
||||
TensorOptions r = *this;
|
||||
r.set_layout(layout);
|
||||
return r;
|
||||
}
|
||||
|
||||
/// Sets the `requires_grad` property of the `TensorOptions`.
|
||||
[[nodiscard]] TensorOptions requires_grad(
|
||||
std::optional<bool> requires_grad) const noexcept {
|
||||
TensorOptions r = *this;
|
||||
r.set_requires_grad(requires_grad);
|
||||
return r;
|
||||
}
|
||||
|
||||
/// Sets the `pinned_memory` property on the `TensorOptions`.
|
||||
[[nodiscard]] TensorOptions pinned_memory(
|
||||
std::optional<bool> pinned_memory) const noexcept {
|
||||
TensorOptions r = *this;
|
||||
r.set_pinned_memory(pinned_memory);
|
||||
return r;
|
||||
}
|
||||
|
||||
/// Sets the `memory_format` property on `TensorOptions`.
|
||||
[[nodiscard]] TensorOptions memory_format(
|
||||
std::optional<MemoryFormat> memory_format) const noexcept {
|
||||
TensorOptions r = *this;
|
||||
r.set_memory_format(memory_format);
|
||||
return r;
|
||||
}
|
||||
|
||||
/// Returns the device of the `TensorOptions`.
|
||||
Device device() const noexcept {
|
||||
return device_or_default(device_opt());
|
||||
}
|
||||
|
||||
/// Returns whether the device is specified.
|
||||
bool has_device() const noexcept {
|
||||
return has_device_;
|
||||
}
|
||||
|
||||
/// Returns the device of the `TensorOptions`, or `std::nullopt` if
|
||||
/// device is not specified.
|
||||
std::optional<Device> device_opt() const noexcept {
|
||||
return has_device_ ? std::make_optional(device_) : std::nullopt;
|
||||
}
|
||||
|
||||
/// Returns the device index of the `TensorOptions`.
|
||||
c10::DeviceIndex device_index() const noexcept {
|
||||
return device().index();
|
||||
}
|
||||
|
||||
/// Returns the dtype of the `TensorOptions`.
|
||||
caffe2::TypeMeta dtype() const noexcept {
|
||||
return dtype_or_default(dtype_opt());
|
||||
}
|
||||
|
||||
/// Returns whether the dtype is specified.
|
||||
bool has_dtype() const noexcept {
|
||||
return has_dtype_;
|
||||
}
|
||||
|
||||
/// Returns the dtype of the `TensorOptions`, or `std::nullopt` if
|
||||
/// device is not specified.
|
||||
std::optional<caffe2::TypeMeta> dtype_opt() const noexcept {
|
||||
return has_dtype_ ? std::make_optional(dtype_) : std::nullopt;
|
||||
}
|
||||
|
||||
/// Returns the layout of the `TensorOptions`.
|
||||
Layout layout() const noexcept {
|
||||
return layout_or_default(layout_opt());
|
||||
}
|
||||
|
||||
/// Returns whether the layout is specified.
|
||||
bool has_layout() const noexcept {
|
||||
return has_layout_;
|
||||
}
|
||||
|
||||
/// Returns the layout of the `TensorOptions`, or `std::nullopt` if
|
||||
/// layout is not specified.
|
||||
std::optional<Layout> layout_opt() const noexcept {
|
||||
return has_layout_ ? std::make_optional(layout_) : std::nullopt;
|
||||
}
|
||||
|
||||
/// Returns the `requires_grad` property of the `TensorOptions`.
|
||||
bool requires_grad() const noexcept {
|
||||
return has_requires_grad_ ? requires_grad_ : false;
|
||||
}
|
||||
|
||||
/// Returns whether the `requires_grad` is specified.
|
||||
bool has_requires_grad() const noexcept {
|
||||
return has_requires_grad_;
|
||||
}
|
||||
|
||||
/// Returns the `requires_grad` property of the `TensorOptions`, or
|
||||
/// `std::nullopt` if `requires_grad` is not specified.
|
||||
std::optional<bool> requires_grad_opt() const noexcept {
|
||||
return has_requires_grad_ ? std::make_optional(requires_grad_)
|
||||
: std::nullopt;
|
||||
}
|
||||
|
||||
/// Returns the `pinned_memory` property of the `TensorOptions`.
|
||||
bool pinned_memory() const noexcept {
|
||||
return pinned_memory_or_default(pinned_memory_opt());
|
||||
}
|
||||
|
||||
/// Returns whether the `pinned_memory` is specified.
|
||||
bool has_pinned_memory() const noexcept {
|
||||
return has_pinned_memory_;
|
||||
}
|
||||
|
||||
/// Returns if the layout is sparse
|
||||
bool is_sparse() const {
|
||||
return layout_ == c10::Layout::Sparse;
|
||||
}
|
||||
|
||||
/// Returns if the layout is sparse CSR, deprecated, use
|
||||
/// is_sparse_compressed() instead
|
||||
bool is_sparse_csr() const {
|
||||
return layout_ == c10::Layout::SparseCsr;
|
||||
}
|
||||
|
||||
bool is_sparse_compressed() const {
|
||||
return layout_ == c10::Layout::SparseCsr ||
|
||||
layout_ == c10::Layout::SparseCsc ||
|
||||
layout_ == c10::Layout::SparseBsr || layout_ == c10::Layout::SparseBsc;
|
||||
}
|
||||
|
||||
// For compatibility with legacy tensor.type() comparisons
|
||||
bool type_equal(const TensorOptions& other) const {
|
||||
return computeDispatchKey() == other.computeDispatchKey() &&
|
||||
typeMetaToScalarType(dtype_) == typeMetaToScalarType(other.dtype());
|
||||
}
|
||||
|
||||
/// Returns the `pinned_memory` property of the `TensorOptions`, or
|
||||
/// `std::nullopt` if `pinned_memory` is not specified.
|
||||
std::optional<bool> pinned_memory_opt() const noexcept {
|
||||
return has_pinned_memory_ ? std::make_optional(pinned_memory_)
|
||||
: std::nullopt;
|
||||
}
|
||||
|
||||
/// Returns whether the `memory_layout` is specified
|
||||
bool has_memory_format() const noexcept {
|
||||
return has_memory_format_;
|
||||
}
|
||||
|
||||
// NB: memory_format() getter is PURPOSELY not defined, as the default
|
||||
// behavior of memory_format varies from function to function.
|
||||
|
||||
/// Returns the `memory_layout` property of `TensorOptions, or
|
||||
/// `std::nullopt` if `memory_format` is not specified.
|
||||
std::optional<MemoryFormat> memory_format_opt() const noexcept {
|
||||
return has_memory_format_ ? std::make_optional(memory_format_)
|
||||
: std::nullopt;
|
||||
}
|
||||
|
||||
// Resolves the ATen backend specified by the current construction axes.
|
||||
// TODO: Deprecate this
|
||||
Backend backend() const {
|
||||
return at::dispatchKeyToBackend(computeDispatchKey());
|
||||
}
|
||||
|
||||
/// Return the right-biased merge of two TensorOptions. This has the
|
||||
/// effect of overwriting settings from self with specified options
|
||||
/// of options.
|
||||
///
|
||||
/// NB: This merging operation does NOT respect device merges.
|
||||
/// For example, if you device({kCUDA, 1}).merge_in(kCUDA)
|
||||
/// you will get kCUDA in the end! Functions like Tensor.new_empty
|
||||
/// ensure the right device is selected anyway by way of a
|
||||
/// device guard.
|
||||
///
|
||||
TensorOptions merge_in(TensorOptions options) const noexcept {
|
||||
TensorOptions merged = *this;
|
||||
if (options.has_device())
|
||||
merged.set_device(options.device_opt());
|
||||
if (options.has_dtype())
|
||||
merged.set_dtype(options.dtype_opt());
|
||||
if (options.has_layout())
|
||||
merged.set_layout(options.layout_opt());
|
||||
// NB: requires grad is right biased; not a logical AND/OR!
|
||||
if (options.has_requires_grad())
|
||||
merged.set_requires_grad(options.requires_grad_opt());
|
||||
if (options.has_pinned_memory())
|
||||
merged.set_pinned_memory(options.pinned_memory_opt());
|
||||
if (options.has_memory_format())
|
||||
merged.set_memory_format(options.memory_format_opt());
|
||||
return merged;
|
||||
}
|
||||
|
||||
// TODO remove after TensorOptions rationalization
|
||||
TensorOptions merge_memory_format(
|
||||
std::optional<MemoryFormat> optional_memory_format) const noexcept {
|
||||
TensorOptions merged = *this;
|
||||
if (optional_memory_format.has_value()) {
|
||||
merged.set_memory_format(optional_memory_format);
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
// INVARIANT: computeDispatchKey returns only the subset of dispatch keys for
|
||||
// which dispatchKeyToBackend is injective, if it is defined at all (for
|
||||
// the most part, this just means that this function never returns an
|
||||
// Autograd key)
|
||||
DispatchKey computeDispatchKey() const {
|
||||
return c10::computeDispatchKey(
|
||||
optTypeMetaToScalarType(dtype_opt()), layout_opt(), device_opt());
|
||||
}
|
||||
|
||||
private:
|
||||
// These methods are currently private because I'm not sure if it's wise
|
||||
// to actually publish them. They are methods because I need them in
|
||||
// the constructor and the functional API implementation.
|
||||
//
|
||||
// If you really, really need it, you can make these public, but check if you
|
||||
// couldn't just do what you need with the functional API. Similarly, these
|
||||
// methods are not chainable, because if you wanted chaining, you probably
|
||||
// want to use the functional API instead. (It's probably OK to make
|
||||
// these chainable, because these functions are all explicitly annotated
|
||||
// with a ref-qualifier, the trailing &, that makes them illegal to call
|
||||
// on temporaries.)
|
||||
|
||||
/// Mutably set the device of `TensorOptions`.
|
||||
void set_device(std::optional<Device> device) & noexcept {
|
||||
if (device) {
|
||||
device_ = *device;
|
||||
has_device_ = true;
|
||||
} else {
|
||||
has_device_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Mutably set the dtype of `TensorOptions`.
|
||||
void set_dtype(std::optional<caffe2::TypeMeta> dtype) & noexcept {
|
||||
if (dtype) {
|
||||
dtype_ = *dtype;
|
||||
has_dtype_ = true;
|
||||
} else {
|
||||
has_dtype_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
// legacy function to support ScalarType
|
||||
void set_dtype(std::optional<ScalarType> dtype) & noexcept {
|
||||
if (dtype) {
|
||||
dtype_ = scalarTypeToTypeMeta(*dtype);
|
||||
has_dtype_ = true;
|
||||
} else {
|
||||
has_dtype_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Mutably set the layout of `TensorOptions`.
|
||||
void set_layout(std::optional<Layout> layout) & noexcept {
|
||||
if (layout) {
|
||||
layout_ = *layout;
|
||||
has_layout_ = true;
|
||||
} else {
|
||||
has_layout_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Mutably set the `requires_grad` property of `TensorOptions`.
|
||||
void set_requires_grad(std::optional<bool> requires_grad) & noexcept {
|
||||
if (requires_grad) {
|
||||
requires_grad_ = *requires_grad;
|
||||
has_requires_grad_ = true;
|
||||
} else {
|
||||
has_requires_grad_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Mutably set the `pinned_memory` property of `TensorOptions`.
|
||||
void set_pinned_memory(std::optional<bool> pinned_memory) & noexcept {
|
||||
if (pinned_memory) {
|
||||
pinned_memory_ = *pinned_memory;
|
||||
has_pinned_memory_ = true;
|
||||
} else {
|
||||
has_pinned_memory_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Mutably set the `memory_Format` property of `TensorOptions`.
|
||||
void set_memory_format(std::optional<MemoryFormat> memory_format) & noexcept {
|
||||
if (memory_format) {
|
||||
memory_format_ = *memory_format;
|
||||
has_memory_format_ = true;
|
||||
} else {
|
||||
has_memory_format_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
// WARNING: If you edit TensorOptions to add more options, you
|
||||
// may need to adjust the implementation of Tensor::options.
|
||||
// The criteria for whether or not Tensor::options must be adjusted
|
||||
// is whether or not the new option you added should preserved
|
||||
// by functions such as empty_like(); if it should be preserved,
|
||||
// you must adjust options().
|
||||
//
|
||||
// TODO: MemoryFormat is not implemented in this way
|
||||
|
||||
// NB: We didn't use std::optional here, because then we can't pack
|
||||
// the has_***_ boolean fields.
|
||||
|
||||
Device device_ = at::kCPU; // 16-bit
|
||||
caffe2::TypeMeta dtype_ = caffe2::TypeMeta::Make<float>(); // 16-bit
|
||||
Layout layout_ = at::kStrided; // 8-bit
|
||||
MemoryFormat memory_format_ = MemoryFormat::Contiguous; // 8-bit
|
||||
|
||||
// Bitmask required here to get this to fit inside 32 bits (or even 64 bits,
|
||||
// for that matter)
|
||||
|
||||
bool requires_grad_ : 1;
|
||||
bool pinned_memory_ : 1;
|
||||
|
||||
bool has_device_ : 1;
|
||||
bool has_dtype_ : 1;
|
||||
bool has_layout_ : 1;
|
||||
bool has_requires_grad_ : 1;
|
||||
bool has_pinned_memory_ : 1;
|
||||
bool has_memory_format_ : 1;
|
||||
};
|
||||
|
||||
// We should aspire to fit in one machine-size word; but a size greater than two
|
||||
// words is too much. (We are doing terribly on 32-bit archs, where we require
|
||||
// three machine size words to store tensor options. Eek!)
|
||||
static_assert(
|
||||
sizeof(TensorOptions) <= sizeof(int64_t) * 2,
|
||||
"TensorOptions must fit in 128-bits");
|
||||
|
||||
/// Convenience function that returns a `TensorOptions` object with the `dtype`
|
||||
/// set to the given one.
|
||||
inline TensorOptions dtype(caffe2::TypeMeta dtype) {
|
||||
return TensorOptions().dtype(dtype);
|
||||
}
|
||||
|
||||
// legacy function to support ScalarType
|
||||
inline TensorOptions dtype(ScalarType dtype) {
|
||||
return TensorOptions().dtype(scalarTypeToTypeMeta(dtype));
|
||||
}
|
||||
|
||||
/// Convenience function that returns a `TensorOptions` object with the `layout`
|
||||
/// set to the given one.
|
||||
inline TensorOptions layout(Layout layout) {
|
||||
return TensorOptions().layout(layout);
|
||||
}
|
||||
|
||||
/// Convenience function that returns a `TensorOptions` object with the `device`
|
||||
/// set to the given one.
|
||||
inline TensorOptions device(Device device) {
|
||||
return TensorOptions().device(device);
|
||||
}
|
||||
|
||||
/// Convenience function that returns a `TensorOptions` object with the
|
||||
/// `device` set to CUDA and the `device_index` set to the given one.
|
||||
inline TensorOptions device_index(c10::DeviceIndex device_index) {
|
||||
return TensorOptions().device_index(device_index);
|
||||
}
|
||||
|
||||
/// Convenience function that returns a `TensorOptions` object with the
|
||||
/// `requires_grad` set to the given one.
|
||||
inline TensorOptions requires_grad(bool requires_grad = true) {
|
||||
return TensorOptions().requires_grad(requires_grad);
|
||||
}
|
||||
|
||||
/// Convenience function that returns a `TensorOptions` object with the
|
||||
/// `memory_format` set to the given one.
|
||||
inline TensorOptions memory_format(MemoryFormat memory_format) {
|
||||
return TensorOptions().memory_format(memory_format);
|
||||
}
|
||||
|
||||
C10_API std::ostream& operator<<(
|
||||
std::ostream& stream,
|
||||
const TensorOptions& options);
|
||||
|
||||
template <typename T>
|
||||
inline TensorOptions dtype() {
|
||||
return dtype(caffe2::TypeMeta::Make<T>());
|
||||
}
|
||||
|
||||
inline std::string toString(const TensorOptions& options) {
|
||||
std::ostringstream stream;
|
||||
stream << options;
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
// This is intended to be a centralized location by which we can determine
|
||||
// what an appropriate DispatchKey for a tensor is.
|
||||
inline DispatchKey computeDispatchKey(
|
||||
std::optional<ScalarType> dtype,
|
||||
std::optional<Layout> layout,
|
||||
std::optional<Device> device) {
|
||||
const auto layout_ = layout_or_default(layout);
|
||||
const auto device_ = device_or_default(device);
|
||||
switch (layout_) {
|
||||
case Layout::Jagged:
|
||||
case Layout::Strided: {
|
||||
const auto dtype_ = dtype_or_default(dtype);
|
||||
switch (device_.type()) {
|
||||
#define DO_CASE(device, _) \
|
||||
case c10::DeviceType::device: { \
|
||||
if (isQIntType(dtype_)) { \
|
||||
return DispatchKey::Quantized##device; \
|
||||
} \
|
||||
return DispatchKey::device; \
|
||||
}
|
||||
C10_FORALL_BACKEND_DEVICE_TYPES(DO_CASE, unused)
|
||||
#undef DO_CASE
|
||||
case c10::DeviceType::FPGA:
|
||||
return DispatchKey::FPGA;
|
||||
case c10::DeviceType::MAIA:
|
||||
return DispatchKey::MAIA;
|
||||
case c10::DeviceType::Vulkan:
|
||||
return DispatchKey::Vulkan;
|
||||
case c10::DeviceType::Metal:
|
||||
return DispatchKey::Metal;
|
||||
case c10::DeviceType::MKLDNN:
|
||||
case c10::DeviceType::OPENGL:
|
||||
case c10::DeviceType::OPENCL:
|
||||
case c10::DeviceType::IDEEP:
|
||||
TORCH_INTERNAL_ASSERT(
|
||||
0,
|
||||
"This is a grandfathered Caffe2 device type ",
|
||||
device_.type(),
|
||||
", it shouldn't ever convert to a DispatchKey. File a bug describing what you were doing if you think this is in error.");
|
||||
default:
|
||||
TORCH_CHECK_NOT_IMPLEMENTED(
|
||||
false,
|
||||
"Unsupported device type for dense layout: ",
|
||||
device_.type());
|
||||
}
|
||||
}
|
||||
case Layout::Sparse:
|
||||
switch (device_.type()) {
|
||||
#define DO_CASE(device, _) \
|
||||
case c10::DeviceType::device: { \
|
||||
return DispatchKey::Sparse##device; \
|
||||
}
|
||||
C10_FORALL_BACKEND_DEVICE_TYPES(DO_CASE, unused)
|
||||
#undef DO_CASE
|
||||
default:
|
||||
TORCH_CHECK_NOT_IMPLEMENTED(
|
||||
false,
|
||||
"Unsupported device type for sparse layout: ",
|
||||
device_.type());
|
||||
}
|
||||
case Layout::Mkldnn:
|
||||
switch (device_.type()) {
|
||||
case c10::DeviceType::CPU:
|
||||
return DispatchKey::MkldnnCPU;
|
||||
default:
|
||||
TORCH_CHECK_NOT_IMPLEMENTED(
|
||||
false,
|
||||
"Unsupported device type for mkldnn layout: ",
|
||||
device_.type());
|
||||
}
|
||||
case Layout::SparseCsr:
|
||||
case Layout::SparseCsc:
|
||||
case Layout::SparseBsr:
|
||||
case Layout::SparseBsc:
|
||||
switch (device_.type()) {
|
||||
#define DO_CASE(device, _) \
|
||||
case c10::DeviceType::device: { \
|
||||
return DispatchKey::SparseCsr##device; \
|
||||
}
|
||||
C10_FORALL_BACKEND_DEVICE_TYPES(DO_CASE, unused)
|
||||
#undef DO_CASE
|
||||
default:
|
||||
TORCH_CHECK_NOT_IMPLEMENTED(
|
||||
false,
|
||||
"Unsupported device type for ",
|
||||
layout_,
|
||||
" layout: ",
|
||||
device_.type());
|
||||
}
|
||||
default:
|
||||
TORCH_CHECK(false, "Unsupported layout: ", layout_);
|
||||
}
|
||||
}
|
||||
|
||||
inline Layout dispatchKeyToLayout(DispatchKey dispatch_key) {
|
||||
switch (dispatch_key) {
|
||||
#define DO_CASE(bc, _) case DispatchKey::Sparse##bc:
|
||||
C10_FORALL_BACKEND_COMPONENTS(DO_CASE, unused)
|
||||
#undef DO_CASE
|
||||
return Layout::Sparse;
|
||||
#define DO_CASE(bc, _) case DispatchKey::SparseCsr##bc:
|
||||
C10_FORALL_BACKEND_COMPONENTS(DO_CASE, unused)
|
||||
#undef DO_CASE
|
||||
TORCH_CHECK(
|
||||
false, "Cannot map DispatchKey ", dispatch_key, " to a unique layout.");
|
||||
case DispatchKey::MkldnnCPU:
|
||||
return Layout::Mkldnn;
|
||||
default:
|
||||
return Layout::Strided;
|
||||
}
|
||||
}
|
||||
|
||||
inline c10::DeviceType dispatchKeyToDeviceType(DispatchKey dispatch_key) {
|
||||
switch (dispatch_key) {
|
||||
// stuff that's real
|
||||
#define DO_CASE(suffix, prefix) \
|
||||
case DispatchKey::prefix##suffix: \
|
||||
return c10::DeviceType::suffix;
|
||||
#define DO_CASES(_, prefix) C10_FORALL_BACKEND_DEVICE_TYPES(DO_CASE, prefix)
|
||||
C10_FORALL_FUNCTIONALITY_KEYS(DO_CASES)
|
||||
#undef DO_CASES
|
||||
#undef DO_CASE
|
||||
|
||||
case DispatchKey::MkldnnCPU:
|
||||
return c10::DeviceType::CPU;
|
||||
case DispatchKey::Vulkan:
|
||||
return c10::DeviceType::Vulkan;
|
||||
|
||||
case DispatchKey::MAIA:
|
||||
return c10::DeviceType::MAIA;
|
||||
default:
|
||||
TORCH_CHECK(
|
||||
false,
|
||||
"DispatchKey ",
|
||||
dispatch_key,
|
||||
" doesn't correspond to a device");
|
||||
}
|
||||
}
|
||||
|
||||
inline TensorOptions dispatchKeyToTensorOptions(DispatchKey dispatch_key) {
|
||||
return TensorOptions()
|
||||
.layout(dispatchKeyToLayout(dispatch_key))
|
||||
.device(dispatchKeyToDeviceType(dispatch_key));
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
inline bool backend_supports_empty_operator(const TensorOptions& options) {
|
||||
// Quantized backends don't support at::empty().
|
||||
// They have separate operators like at::empty_quantized() that take in
|
||||
// extra information about how to quantize the tensor.
|
||||
return !isQIntType(typeMetaToScalarType(options.dtype()));
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
} // namespace c10
|
||||
|
||||
C10_DIAGNOSTIC_POP()
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,54 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/MemoryFormat.h>
|
||||
#include <c10/core/SymIntArrayRef.h>
|
||||
#include <c10/core/TensorImpl.h>
|
||||
#include <c10/macros/Export.h>
|
||||
#include <c10/util/ArrayRef.h>
|
||||
#include <cstdint>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
struct C10_API UndefinedTensorImpl final : public TensorImpl {
|
||||
public:
|
||||
// Without this, we get:
|
||||
// error: identifier "at::UndefinedTensorImpl::_singleton" is undefined in
|
||||
// device code
|
||||
// (ostensibly because the constexpr tricks MSVC into trying to compile this
|
||||
// function for device as well).
|
||||
#ifdef _WIN32
|
||||
static inline TensorImpl* singleton() {
|
||||
return &getInstance();
|
||||
}
|
||||
#else
|
||||
static constexpr inline TensorImpl* singleton() {
|
||||
return &_singleton;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG
|
||||
bool has_storage() const override;
|
||||
#endif
|
||||
void set_storage_offset(int64_t offset) override;
|
||||
|
||||
protected:
|
||||
c10::SymBool sym_is_contiguous_custom(MemoryFormat format) const override;
|
||||
IntArrayRef strides_custom() const override;
|
||||
SymIntArrayRef sym_strides_custom() const override;
|
||||
|
||||
private:
|
||||
UndefinedTensorImpl();
|
||||
#ifdef _WIN32
|
||||
static UndefinedTensorImpl& getInstance();
|
||||
#else
|
||||
static UndefinedTensorImpl _singleton;
|
||||
#endif
|
||||
const char* tensorimpl_type_name() const override;
|
||||
};
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,53 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/SymInt.h>
|
||||
#include <c10/macros/Export.h>
|
||||
#include <c10/macros/Macros.h>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
namespace detail {
|
||||
// This template can only be specialized at int64_t and c10::SymInt;
|
||||
// you'll get linker errors otherwise
|
||||
template <typename T>
|
||||
C10_API T maybe_wrap_dim_slow(T dim, T dim_post_expr, bool wrap_scalar);
|
||||
} // namespace detail
|
||||
|
||||
template <typename T>
|
||||
T _maybe_wrap_dim(T dim, T dim_post_expr, bool wrap_scalar = true) {
|
||||
// Inline the fast paths
|
||||
if (C10_LIKELY(dim_post_expr * -1 <= dim && dim < dim_post_expr)) {
|
||||
// For SymInts, we want an explicit control flow to trigger a guard, so we
|
||||
// may as well branch too.
|
||||
if (dim < 0) {
|
||||
return dim + dim_post_expr;
|
||||
}
|
||||
return dim;
|
||||
}
|
||||
// Check edge-cases out-of-line (wrapping scalars and out-of-bounds errors)
|
||||
return c10::detail::maybe_wrap_dim_slow<T>(
|
||||
std::move(dim), std::move(dim_post_expr), wrap_scalar);
|
||||
}
|
||||
|
||||
inline int64_t maybe_wrap_dim(
|
||||
int64_t dim,
|
||||
int64_t dim_post_expr,
|
||||
bool wrap_scalar = true) {
|
||||
return _maybe_wrap_dim(dim, dim_post_expr, wrap_scalar);
|
||||
}
|
||||
|
||||
inline c10::SymInt maybe_wrap_dim(
|
||||
c10::SymInt dim,
|
||||
c10::SymInt dim_post_expr,
|
||||
bool wrap_scalar = true) {
|
||||
return _maybe_wrap_dim(std::move(dim), std::move(dim_post_expr), wrap_scalar);
|
||||
}
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,35 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <new>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
#ifdef C10_MOBILE
|
||||
// Use 16-byte alignment on mobile
|
||||
// - ARM NEON AArch32 and AArch64
|
||||
// - x86[-64] < AVX
|
||||
constexpr size_t gAlignment = 16;
|
||||
#else
|
||||
// Use 64-byte alignment should be enough for computation up to AVX512.
|
||||
constexpr size_t gAlignment = 64;
|
||||
#endif
|
||||
|
||||
constexpr size_t gPagesize = 4096;
|
||||
// since the default thp pagesize is 2MB, enable thp only
|
||||
// for buffers of size 2MB or larger to avoid memory bloating
|
||||
constexpr size_t gAlloc_threshold_thp = static_cast<size_t>(2) * 1024 * 1024;
|
||||
|
||||
// Cache line size used to avoid false sharing between threads. Falls back to 64
|
||||
// bytes if C++17 feature is unavailable.
|
||||
#ifdef __cpp_lib_hardware_interference_size
|
||||
using std::hardware_destructive_interference_size;
|
||||
#else
|
||||
constexpr std::size_t hardware_destructive_interference_size = 64;
|
||||
#endif
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,37 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/macros/Macros.h>
|
||||
#include <c10/util/intrusive_ptr.h>
|
||||
|
||||
namespace c10 {
|
||||
struct StorageImpl;
|
||||
class DataPtr;
|
||||
} // namespace c10
|
||||
|
||||
namespace c10::impl::cow {
|
||||
|
||||
// Creates a Copy-on-write (COW) clone of the given storage. This will also
|
||||
// convert the given storage into a COW storage if it is not COW already.
|
||||
//
|
||||
// Converting the storage into a COW storage will not be successful if the
|
||||
// storage's DataPtr has some context (`DataPtr::get_context()`) which is not
|
||||
// equal to the data pointer (`DataPtr::get()`). In this case, a nullptr is
|
||||
// returned.
|
||||
C10_API c10::intrusive_ptr<StorageImpl> lazy_clone_storage(
|
||||
StorageImpl& storage);
|
||||
|
||||
// Check if a storage has a simple DataPtr with no abnormal context
|
||||
C10_API bool has_simple_data_ptr(const c10::StorageImpl& storage);
|
||||
|
||||
// Check if a DataPtr is COW
|
||||
C10_API bool is_cow_data_ptr(const c10::DataPtr& data_ptr);
|
||||
|
||||
// Eagerly copies a COW storage's data, turning it into a non-COW storage.
|
||||
C10_API void materialize_cow_storage(StorageImpl& storage);
|
||||
|
||||
} // namespace c10::impl::cow
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,71 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/macros/Export.h>
|
||||
#include <c10/util/UniqueVoidPtr.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <shared_mutex>
|
||||
#include <variant>
|
||||
|
||||
namespace c10::impl::cow {
|
||||
|
||||
// A COWDeleterContext object is used as the `ctx` argument for DataPtr
|
||||
// to implement a Copy-on-write (COW) DataPtr.
|
||||
class C10_API COWDeleterContext {
|
||||
public:
|
||||
// Creates an instance, holding the pair of data and original
|
||||
// deleter.
|
||||
//
|
||||
// Note that the deleter will only be called in our destructor if
|
||||
// the last reference to this goes away without getting
|
||||
// materialized.
|
||||
explicit COWDeleterContext(std::unique_ptr<void, DeleterFnPtr> data);
|
||||
|
||||
// Increments the current refcount.
|
||||
void increment_refcount();
|
||||
|
||||
// See README.md in this directory to understand the locking
|
||||
// strategy.
|
||||
|
||||
// Represents a reference to the context.
|
||||
//
|
||||
// This is returned by decrement_refcount to allow the caller to
|
||||
// copy the data under the shared lock.
|
||||
using NotLastReference = std::shared_lock<std::shared_mutex>;
|
||||
|
||||
// Represents the last reference to the context.
|
||||
//
|
||||
// This will be returned by decrement_refcount when it is the last
|
||||
// reference remaining and after any pending copies have completed.
|
||||
using LastReference = std::unique_ptr<void, DeleterFnPtr>;
|
||||
|
||||
// Decrements the refcount, returning a handle indicating what to
|
||||
// do with it.
|
||||
std::variant<NotLastReference, LastReference> decrement_refcount();
|
||||
|
||||
private:
|
||||
// The destructor is hidden, this should only ever be used within
|
||||
// UniqueVoidPtr using cow::delete_context as the deleter.
|
||||
~COWDeleterContext();
|
||||
|
||||
std::shared_mutex mutex_;
|
||||
std::unique_ptr<void, DeleterFnPtr> data_;
|
||||
std::atomic<std::int64_t> refcount_ = 1;
|
||||
};
|
||||
|
||||
// `cow_deleter` is used as the `ctx_deleter` for DataPtr to implement a COW
|
||||
// DataPtr.
|
||||
//
|
||||
// Warning: This should only be called on a pointer to a COWDeleterContext that
|
||||
// was allocated on the heap with `new`, because when the refcount reaches 0,
|
||||
// the context is deleted with `delete`.
|
||||
C10_API void cow_deleter(void* ctx);
|
||||
|
||||
} // namespace c10::impl::cow
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
+435
@@ -0,0 +1,435 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/Device.h>
|
||||
#include <c10/core/DeviceCapability.h>
|
||||
#include <c10/core/DeviceType.h>
|
||||
#include <c10/core/Stream.h>
|
||||
#include <c10/util/Exception.h>
|
||||
|
||||
// Just for C10_ANONYMOUS_VARIABLE
|
||||
#include <c10/core/impl/TorchDispatchModeTLS.h>
|
||||
#include <c10/util/Registry.h>
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
// Forward declaration
|
||||
class DataPtr;
|
||||
|
||||
/**
|
||||
* Note [Flags defining the behavior of events]
|
||||
*
|
||||
* PYTORCH_DEFAULT and BACKEND_DEFAULT are valid for all backends. The
|
||||
* BACKEND_DEFAULT is what a particular backend would select if no
|
||||
* flags were given. PYTORCH_DEFAULT is the PyTorch's framework default
|
||||
* choice for events on that backend, which may not be the same.
|
||||
*
|
||||
* The mapping of PYTORCH_DEFAULT and BACKEND_DEFAULT is done by each
|
||||
* backend implementation.
|
||||
*/
|
||||
enum class EventFlag {
|
||||
// Disable timing
|
||||
PYTORCH_DEFAULT,
|
||||
// Enable timing
|
||||
BACKEND_DEFAULT,
|
||||
// FOR TESTING ONLY
|
||||
INVALID
|
||||
};
|
||||
|
||||
namespace impl {
|
||||
|
||||
/**
|
||||
* DeviceGuardImplInterface represents the virtual interface which provides
|
||||
* functionality to provide an RAII class for device and stream switching,
|
||||
* via DeviceGuard. Every distinct device type, e.g., CUDA and HIP, is
|
||||
* expected to implement and register an implementation of this interface.
|
||||
* All classes which inherit from DeviceGuardImplInterface should be declared
|
||||
* 'final'.
|
||||
*
|
||||
* This class exists because we provide a unified interface for performing
|
||||
* device guards via DeviceGuard, but we cannot assume that we have actually
|
||||
* compiled against the, e.g., CUDA library, which actually implements
|
||||
* this guard functionality. In this case, a dynamic dispatch is required
|
||||
* to cross the library boundary.
|
||||
*
|
||||
* If possible, you should directly use implementations of this interface;
|
||||
* those uses will be devirtualized.
|
||||
*/
|
||||
struct C10_API DeviceGuardImplInterface {
|
||||
DeviceGuardImplInterface() = default;
|
||||
DeviceGuardImplInterface(const DeviceGuardImplInterface&) = default;
|
||||
DeviceGuardImplInterface& operator=(const DeviceGuardImplInterface&) =
|
||||
default;
|
||||
DeviceGuardImplInterface(DeviceGuardImplInterface&&) noexcept = default;
|
||||
DeviceGuardImplInterface& operator=(DeviceGuardImplInterface&&) noexcept =
|
||||
default;
|
||||
|
||||
/**
|
||||
* Return the type of device managed by this guard implementation.
|
||||
*/
|
||||
virtual DeviceType type() const = 0;
|
||||
|
||||
/**
|
||||
* Set the current device to Device, and return the previous Device.
|
||||
*/
|
||||
virtual Device exchangeDevice(Device) const = 0;
|
||||
// NB: Implementations of exchangeDevice can be a bit boilerplatey. You might
|
||||
// consider replacing exchangeDevice with a non-virtual function with a baked
|
||||
// in implementation; however, note that this will triple the number of
|
||||
// virtual calls (when you implement exchangeDevice in a final subclass,
|
||||
// the compiler gets to devirtualize everything; it won't do that if you don't
|
||||
// define it in the subclass!) A common way to solve this problem is to use
|
||||
// some sort of CRTP; however, we can template DeviceGuardImplInterface since
|
||||
// we really *do* need it to be virtual. A little boilerplate seems easiest
|
||||
// to explain. (Another way around this problem is to provide inline
|
||||
// functions that provide the default implementations, but this seems a little
|
||||
// hard to explain. In any case, we're only going to have on order of ten
|
||||
// implementations of this anyway.)
|
||||
|
||||
/**
|
||||
* Get the current device.
|
||||
*/
|
||||
virtual Device getDevice() const = 0;
|
||||
|
||||
/**
|
||||
* Set the current device to Device.
|
||||
*/
|
||||
virtual void setDevice(Device) const = 0;
|
||||
|
||||
/**
|
||||
* Set the current device to Device, without checking for errors
|
||||
* (so, e.g., this can be called from a destructor).
|
||||
*/
|
||||
virtual void uncheckedSetDevice(Device) const noexcept = 0;
|
||||
|
||||
/**
|
||||
* Get the current stream for a given device.
|
||||
*/
|
||||
virtual Stream getStream(Device) const = 0;
|
||||
|
||||
/**
|
||||
* Get the default stream for a given device.
|
||||
*/
|
||||
virtual Stream getDefaultStream(Device /*unused*/) const {
|
||||
TORCH_CHECK(false, "Backend doesn't support acquiring a default stream.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a stream from the global pool for a given device.
|
||||
*/
|
||||
virtual Stream getStreamFromGlobalPool(
|
||||
Device /*unused*/,
|
||||
bool isHighPriority = false) const {
|
||||
(void)isHighPriority; // Suppress unused variable warning
|
||||
TORCH_CHECK(false, "Backend doesn't support acquiring a stream from pool.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new stream for a given device and priority. The stream will be
|
||||
* copied and shared around, device backend should be able to correctly handle
|
||||
* the lifetime of the stream.
|
||||
*/
|
||||
virtual Stream getNewStream(Device /*unused*/, int priority = 0) const {
|
||||
(void)priority;
|
||||
TORCH_CHECK(false, "Backend doesn't support create a new Stream.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a stream to be the thread local current stream for its device.
|
||||
* Return the previous stream for that device. You are NOT required
|
||||
* to set the current device to match the device of this stream.
|
||||
*/
|
||||
virtual Stream exchangeStream(Stream) const = 0;
|
||||
|
||||
/**
|
||||
* Returns a backend-specific, opaque native handle associated with the given
|
||||
* stream.
|
||||
*
|
||||
* The returned pointer is owned and managed by PyTorch. Callers must not
|
||||
* modify or free it.
|
||||
*/
|
||||
virtual void* getStreamNativeHandle(const Stream) const {
|
||||
TORCH_CHECK(false, "Backend doesn't support getting stream native handle.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the given event.
|
||||
*/
|
||||
virtual void destroyEvent(void* /*event*/, const DeviceIndex /*device_index*/)
|
||||
const noexcept {}
|
||||
|
||||
/**
|
||||
* Increments the event's version and enqueues a job with this version
|
||||
* in the stream's work queue. When the stream process that job
|
||||
* it notifies all streams waiting on / blocked by that version of the
|
||||
* event to continue and marks that version as recorded.
|
||||
* */
|
||||
virtual void record(
|
||||
void** /*event*/,
|
||||
const Stream& /*stream*/,
|
||||
const DeviceIndex /*device_index*/,
|
||||
const c10::EventFlag /*flag*/) const {
|
||||
TORCH_CHECK(false, "Backend doesn't support events.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Does nothing if the event has not been scheduled to be recorded.
|
||||
* If the event was previously enqueued to be recorded, a command
|
||||
* to wait for the version of the event that exists at the time of this call
|
||||
* is inserted in the stream's work queue.
|
||||
* When the stream reaches this command it will stop processing
|
||||
* additional commands until that version of the event is marked as recorded.
|
||||
*/
|
||||
virtual void block(void* /*event*/, const Stream& /*stream*/) const {
|
||||
TORCH_CHECK(false, "Backend doesn't support events.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if (and only if)
|
||||
* (1) the event has never been scheduled to be recorded
|
||||
* (2) the current version is marked as recorded.
|
||||
* Returns false otherwise.
|
||||
*/
|
||||
virtual bool queryEvent(void* /*event*/) const {
|
||||
TORCH_CHECK(false, "Backend doesn't support events.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of devices. WARNING: This is REQUIRED to not raise
|
||||
* an exception. If there is some sort of problem, e.g., driver error,
|
||||
* you should report that there are zero available devices.
|
||||
*/
|
||||
virtual DeviceIndex deviceCount() const noexcept = 0;
|
||||
|
||||
/**
|
||||
* Get the following capabilities of the current device:
|
||||
* (1) Data type support
|
||||
* Returns DeviceCapability object.
|
||||
*/
|
||||
virtual DeviceCapability getDeviceCapability(Device /*unused*/) const {
|
||||
TORCH_CHECK(false, "Backend doesn't support getting device capabilities.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if all the work previously enqueued on the stream for
|
||||
* asynchronous execution has completed running on the device.
|
||||
*/
|
||||
virtual bool queryStream(const Stream& /*stream*/) const {
|
||||
TORCH_CHECK(false, "Backend doesn't support querying streams.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait (by blocking the calling thread) until all the work previously
|
||||
* enqueued on the stream has completed running on the device.
|
||||
*/
|
||||
virtual void synchronizeStream(const Stream& /*stream*/) const {
|
||||
TORCH_CHECK(false, "Backend doesn't support synchronizing streams.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this stream is currently recording work for graph capture.
|
||||
*/
|
||||
virtual bool isStreamCapturing(const Stream& /*stream*/) const {
|
||||
TORCH_CHECK(false, "Backend doesn't support stream capture query.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait (by blocking the calling thread) until all the work previously
|
||||
* recorded on the event has completed running on the device.
|
||||
*/
|
||||
virtual void synchronizeEvent(void* /*event*/) const {
|
||||
TORCH_CHECK(false, "Backend doesn't support synchronizing events.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait (by blocking the calling thread) until all the work previously
|
||||
* enqueued on the device has been completed.
|
||||
*/
|
||||
virtual void synchronizeDevice(const DeviceIndex /*device_index*/) const {
|
||||
TORCH_CHECK(
|
||||
false, "Backend doesn't support synchronizing all streams on device.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the caching allocator (if any) is aware that the given DataPtr is
|
||||
* being used on the given stream, and that it should thus avoid recycling the
|
||||
* DataPtr until all work on that stream is done.
|
||||
*/
|
||||
virtual void recordDataPtrOnStream(
|
||||
const c10::DataPtr& /*unused*/,
|
||||
const Stream& /*unused*/) const {}
|
||||
|
||||
/**
|
||||
* Fetch the elapsed time between two recorded events.
|
||||
*/
|
||||
virtual double elapsedTime(
|
||||
void* /*event1*/,
|
||||
void* /*event2*/,
|
||||
const DeviceIndex /*device_index*/) const {
|
||||
TORCH_CHECK(false, "Backend doesn't support elapsedTime.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Intended use of this class is to leak the DeviceGuardImpl at program end.
|
||||
* So you better not call the destructor, buster!
|
||||
*/
|
||||
virtual ~DeviceGuardImplInterface() = default;
|
||||
};
|
||||
|
||||
// A no-op device guard impl that doesn't do anything interesting. Useful
|
||||
// for devices that don't actually have a concept of device index. Prominent
|
||||
// examples are CPU and Meta.
|
||||
template <DeviceType D>
|
||||
struct NoOpDeviceGuardImpl : public DeviceGuardImplInterface {
|
||||
NoOpDeviceGuardImpl() = default;
|
||||
DeviceType type() const override {
|
||||
return D;
|
||||
}
|
||||
Device exchangeDevice(Device /*unused*/) const override {
|
||||
return Device(D, -1); // no-op
|
||||
}
|
||||
Device getDevice() const override {
|
||||
return Device(D, -1);
|
||||
}
|
||||
void setDevice(Device /*unused*/) const override {
|
||||
// no-op
|
||||
}
|
||||
void uncheckedSetDevice(Device /*unused*/) const noexcept override {
|
||||
// no-op
|
||||
}
|
||||
Stream getStream(Device /*unused*/) const noexcept override {
|
||||
// no-op
|
||||
return Stream(Stream::DEFAULT, Device(D, -1));
|
||||
}
|
||||
|
||||
Stream getNewStream(Device /*unused*/, int priority = 0) const override {
|
||||
// no-op
|
||||
(void)priority;
|
||||
return Stream(Stream::DEFAULT, Device(D, -1));
|
||||
}
|
||||
|
||||
// NB: These do NOT set the current device
|
||||
Stream exchangeStream(Stream /*unused*/) const noexcept override {
|
||||
// no-op
|
||||
return Stream(Stream::DEFAULT, Device(D, -1));
|
||||
}
|
||||
DeviceIndex deviceCount() const noexcept override {
|
||||
return 1;
|
||||
}
|
||||
|
||||
DeviceCapability getDeviceCapability(Device /*unused*/) const override {
|
||||
DeviceCapability cap;
|
||||
if constexpr (D == DeviceType::Meta) {
|
||||
cap.capability_data.capability_bits = 0;
|
||||
// Meta only supports basic types for shape inference
|
||||
// Byte, Char, Short, Int, Long, Float, Double,
|
||||
// Bool, ComplexFloat, ComplexDouble
|
||||
cap.capability_data.capability_bits = (1ULL << kIndex_Byte) |
|
||||
(1ULL << kIndex_Char) | (1ULL << kIndex_Short) |
|
||||
(1ULL << kIndex_Int) | (1ULL << kIndex_Long) |
|
||||
(1ULL << kIndex_Float) | (1ULL << kIndex_Double) |
|
||||
(1ULL << kIndex_ComplexFloat) | (1ULL << kIndex_ComplexDouble) |
|
||||
(1ULL << kIndex_Bool);
|
||||
}
|
||||
return cap;
|
||||
}
|
||||
|
||||
// Event-related functions
|
||||
void record(
|
||||
void** /*event*/,
|
||||
const Stream& /*stream*/,
|
||||
const DeviceIndex /*device_index*/,
|
||||
const EventFlag /*flag*/) const override {
|
||||
TORCH_CHECK(false, D, " backend doesn't support events.");
|
||||
}
|
||||
void block(void* /*event*/, const Stream& /*stream*/) const override {
|
||||
TORCH_CHECK(false, D, " backend doesn't support events.")
|
||||
}
|
||||
bool queryEvent(void* /*event*/) const override {
|
||||
TORCH_CHECK(false, D, " backend doesn't support events.")
|
||||
}
|
||||
void destroyEvent(void* /*event*/, const DeviceIndex /*device_index*/)
|
||||
const noexcept override {}
|
||||
|
||||
// Stream-related functions
|
||||
bool queryStream(const Stream& /*stream*/) const override {
|
||||
return true;
|
||||
}
|
||||
void synchronizeStream(const Stream& /*stream*/) const override {
|
||||
// Don't wait for anything.
|
||||
}
|
||||
};
|
||||
|
||||
// The registry is NON-owning. Each stored pointer is std::atomic so
|
||||
// that under all interleavings of registry calls the structure is
|
||||
// race-free. This doesn't cost us anything on reads in X86. (An
|
||||
// unsynchronized implementation probably is OK too, but I didn't want
|
||||
// to prove that we never read from device_guard_impl_registry at the
|
||||
// same time some registration is occurring. Shiver.)
|
||||
//
|
||||
// I'd like this registry to be valid even at program destruction time
|
||||
// (in case someone uses a DeviceGuard in a destructor to do some cleanup
|
||||
// in the CUDA API.) Since there are no direct accesses of the underlying
|
||||
// owning objects which I can use to enforce initialization order (unlike
|
||||
// in a Meyer singleton), it implies that you must *leak* objects when
|
||||
// putting them in the registry. This is done by deleting the destructor
|
||||
// on DeviceGuardImplInterface.
|
||||
extern C10_API std::array<
|
||||
std::atomic<const DeviceGuardImplInterface*>,
|
||||
static_cast<size_t>(DeviceType::COMPILE_TIME_MAX_DEVICE_TYPES)>
|
||||
device_guard_impl_registry;
|
||||
|
||||
// I can't conveniently use c10/util/Registry.h for the following reason:
|
||||
// c10/util/Registry.h gives me a slow way of Create'ing a object of some
|
||||
// interface from the registry, but no way of quickly accessing an already
|
||||
// created object. I'll be banging on getDeviceGuardImpl every time we do a
|
||||
// DeviceGuard, so I really don't want to be doing an unordered_map lookup.
|
||||
// Better if the registration mechanism directly drops its implementation
|
||||
// into device_guard_impl_registry.
|
||||
|
||||
class C10_API DeviceGuardImplRegistrar {
|
||||
public:
|
||||
DeviceGuardImplRegistrar(
|
||||
DeviceType /*type*/,
|
||||
const DeviceGuardImplInterface* /*impl*/);
|
||||
};
|
||||
|
||||
#define C10_REGISTER_GUARD_IMPL(DevType, DeviceGuardImpl) \
|
||||
static ::c10::impl::DeviceGuardImplRegistrar C10_ANONYMOUS_VARIABLE( \
|
||||
g_##DeviceType)(::c10::DeviceType::DevType, new DeviceGuardImpl());
|
||||
|
||||
inline const DeviceGuardImplInterface* getDeviceGuardImpl(DeviceType type) {
|
||||
// Two adjacent int16_t fields DeviceType and DeviceIndex has field access
|
||||
// miscompiled on NVCC. To workaround this issue, we apply a mask to the
|
||||
// DeviceType. First check if the DeviceType is 16-bit.
|
||||
// FB employees can see
|
||||
// https://fb.workplace.com/groups/llvm.gcc/permalink/4053565044692080/
|
||||
// for more details
|
||||
static_assert(sizeof(DeviceType) == 1, "DeviceType is not 8-bit");
|
||||
auto p = device_guard_impl_registry[static_cast<size_t>(type) & 0xFF].load();
|
||||
|
||||
// This seems to be the first place where you make use of a device
|
||||
// when you pass devices to factory functions. Give a nicer error
|
||||
// message in this case.
|
||||
TORCH_CHECK(p, "PyTorch is not linked with support for ", type, " devices");
|
||||
return p;
|
||||
}
|
||||
|
||||
void C10_API
|
||||
registerDeviceGuard(DeviceType type, const DeviceGuardImplInterface* impl);
|
||||
|
||||
inline bool hasDeviceGuardImpl(DeviceType type) {
|
||||
return device_guard_impl_registry[static_cast<size_t>(type)].load();
|
||||
}
|
||||
|
||||
void C10_API ensureCUDADeviceGuardSet();
|
||||
|
||||
} // namespace impl
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,107 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/impl/DeviceGuardImplInterface.h>
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace c10::impl {
|
||||
|
||||
// FakeGuardImpl is hardcoded to have eight devices. Not for
|
||||
// any good reason, just to simplify code.
|
||||
constexpr DeviceIndex kFakeGuardImplMaxDevices = 8;
|
||||
|
||||
/**
|
||||
* A fake implementation of DeviceGuardImplInterface suitable for testing.
|
||||
* The current device is modeled as a mutable field in the guard implementation
|
||||
* class. See DeviceGuard_test.cpp for an example use.
|
||||
*/
|
||||
template <DeviceType T>
|
||||
struct FakeGuardImpl final : public DeviceGuardImplInterface {
|
||||
static constexpr DeviceType static_type = T;
|
||||
// Runtime device type is not used
|
||||
FakeGuardImpl(DeviceType /*unused*/) {}
|
||||
FakeGuardImpl() = default;
|
||||
DeviceType type() const override {
|
||||
return T;
|
||||
}
|
||||
Device exchangeDevice(Device d) const override {
|
||||
AT_ASSERT(d.type() == type());
|
||||
AT_ASSERT(d.index() < kFakeGuardImplMaxDevices);
|
||||
Device old_device = getDevice();
|
||||
if (old_device.index() != d.index()) {
|
||||
current_device_ = d.index();
|
||||
}
|
||||
return old_device;
|
||||
}
|
||||
Device getDevice() const override {
|
||||
return Device(type(), current_device_);
|
||||
}
|
||||
void setDevice(Device d) const override {
|
||||
AT_ASSERT(d.type() == type());
|
||||
AT_ASSERT(d.index() >= 0);
|
||||
AT_ASSERT(d.index() < kFakeGuardImplMaxDevices);
|
||||
current_device_ = d.index();
|
||||
}
|
||||
void uncheckedSetDevice(Device d) const noexcept override {
|
||||
current_device_ = d.index();
|
||||
}
|
||||
Stream getStream(Device d) const noexcept override {
|
||||
return Stream(Stream::UNSAFE, d, current_streams_[d.index()]);
|
||||
}
|
||||
Stream exchangeStream(Stream s) const noexcept override {
|
||||
auto old_id = current_streams_[s.device_index()];
|
||||
current_streams_[s.device_index()] = s.id();
|
||||
return Stream(Stream::UNSAFE, s.device(), old_id);
|
||||
}
|
||||
DeviceIndex deviceCount() const noexcept override {
|
||||
return kFakeGuardImplMaxDevices;
|
||||
}
|
||||
|
||||
// Event-related functions
|
||||
void record(
|
||||
void** /*event*/,
|
||||
const Stream& /*stream*/,
|
||||
const DeviceIndex /*device_index*/,
|
||||
const EventFlag /*flag*/) const override {}
|
||||
void block(void* /*event*/, const Stream& /*stream*/) const override {}
|
||||
bool queryEvent(void* /*event*/) const override {
|
||||
return true;
|
||||
}
|
||||
void destroyEvent(void* /*event*/, const DeviceIndex /*device_index*/)
|
||||
const noexcept override {}
|
||||
|
||||
// Convenience methods for testing
|
||||
static DeviceIndex getDeviceIndex() {
|
||||
return current_device_;
|
||||
}
|
||||
static void setDeviceIndex(DeviceIndex i) {
|
||||
AT_ASSERT(i >= 0);
|
||||
AT_ASSERT(i < kFakeGuardImplMaxDevices);
|
||||
current_device_ = i;
|
||||
}
|
||||
static StreamId getCurrentStreamIdFor(DeviceIndex i) {
|
||||
return current_streams_.at(i);
|
||||
}
|
||||
static void resetStreams() {
|
||||
current_streams_.fill(0);
|
||||
}
|
||||
|
||||
private:
|
||||
thread_local static DeviceIndex current_device_;
|
||||
thread_local static std::array<StreamId, kFakeGuardImplMaxDevices>
|
||||
current_streams_;
|
||||
};
|
||||
|
||||
template <DeviceType T>
|
||||
thread_local DeviceIndex FakeGuardImpl<T>::current_device_ = 0;
|
||||
|
||||
template <DeviceType T>
|
||||
thread_local std::array<StreamId, kFakeGuardImplMaxDevices>
|
||||
FakeGuardImpl<T>::current_streams_ = {0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
} // namespace c10::impl
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,33 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/impl/PyInterpreter.h>
|
||||
|
||||
namespace c10::impl {
|
||||
|
||||
struct C10_API GPUTrace {
|
||||
// On the x86 architecture the atomic operations are lock-less.
|
||||
static std::atomic<const PyInterpreter*> gpuTraceState;
|
||||
|
||||
// When PyTorch migrates to C++20, this should be changed to an atomic flag.
|
||||
// Currently, the access to this variable is not synchronized, on the basis
|
||||
// that it will only be flipped once and by the first interpreter that
|
||||
// accesses it.
|
||||
static bool haveState;
|
||||
|
||||
// This function will only register the first interpreter that tries to invoke
|
||||
// it. For all of the next ones it will be a no-op.
|
||||
static void set_trace(const PyInterpreter* /*trace*/);
|
||||
|
||||
static const PyInterpreter* get_trace() {
|
||||
if (!haveState)
|
||||
return nullptr;
|
||||
return gpuTraceState.load(std::memory_order_acquire);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace c10::impl
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/macros/Export.h>
|
||||
#include <atomic>
|
||||
|
||||
namespace c10::impl {
|
||||
|
||||
// This TLS controls whether or not we permanently associate PyObject
|
||||
// with Tensor the first time it is allocated. When hermetic PyObject
|
||||
// TLS is enabled (state is true), we DO NOT save PyObjects to Tensor,
|
||||
// meaning you get a distinct PyObject whenever you execute the code in
|
||||
// question.
|
||||
struct C10_API HermeticPyObjectTLS {
|
||||
static void set_state(bool state);
|
||||
static bool get_state() {
|
||||
// Hypothetical fastpath if torchdeploy/multipy // codespell:ignore multipy
|
||||
// isn't used. Per
|
||||
// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2055r0.pdf
|
||||
// this qualifies relaxed access because it is a single-location data
|
||||
// structure (only the boolean here).
|
||||
//
|
||||
// Forgetting about data races for a moment, is there a logical race?
|
||||
//
|
||||
// - Boolean only ever transitions from false to true. So the
|
||||
// critical situation is when one interpreter is already running
|
||||
// when a second interpreter switches haveState from false to true.
|
||||
//
|
||||
// - The first interpreter is indifferent whether or not it sees
|
||||
// hasState true/false; obviously false works (this is what the
|
||||
// interpreter was previously using; more directly, the interpreter
|
||||
// calls into itself as the handler, so being hermetic is not
|
||||
// required), and true simply means serviced python operator calls will
|
||||
// be hermetic; in these cases it is expected to be functionally
|
||||
// equivalent.
|
||||
//
|
||||
// - The second interpreter MUST see hasState true (as its requests will
|
||||
// be forwarded to the first interpreter), but it is assumed that there
|
||||
// is a synchronization between the interpreter initialization, and
|
||||
// when we actually perform operations, so it is guaranteed to see
|
||||
// hasState true.
|
||||
//
|
||||
// QED.
|
||||
//
|
||||
// This fastpath is currently disabled so that we can more easily test that
|
||||
// hermetic mode works correctly even on stock build of PyTorch.
|
||||
if (false && !haveState_.load(std::memory_order_relaxed))
|
||||
return false;
|
||||
return get_tls_state();
|
||||
}
|
||||
// Call this from the multipy/torchdeploy // codespell:ignore multipy
|
||||
// top level
|
||||
static void init_state();
|
||||
|
||||
private:
|
||||
// This only flipped once from false to true during
|
||||
// torchdeploy/multipy initialization, // codespell:ignore multipy
|
||||
// and never again.
|
||||
static std::atomic<bool> haveState_;
|
||||
static bool get_tls_state();
|
||||
};
|
||||
|
||||
} // namespace c10::impl
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
+438
@@ -0,0 +1,438 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
// This file provides implementations of InlineDeviceGuard and
|
||||
// InlineOptionalDeviceGuard.
|
||||
|
||||
#include <c10/core/Device.h>
|
||||
#include <c10/core/DeviceType.h>
|
||||
#include <c10/core/impl/DeviceGuardImplInterface.h>
|
||||
#include <c10/core/impl/VirtualGuardImpl.h>
|
||||
#include <c10/util/Exception.h>
|
||||
#include <c10/util/Optional.h>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace c10::impl {
|
||||
|
||||
/**
|
||||
* A DeviceGuard is an RAII class that sets a device to some value
|
||||
* on construction, and resets the device to its original value on
|
||||
* destruction.
|
||||
*
|
||||
* InlineDeviceGuard is a helper class for implementing DeviceGuards.
|
||||
* It is templated over a DeviceGuardImpl (anything that implements
|
||||
* DeviceGuardImplInterface). There are two primary ways to instantiate
|
||||
* InlineDeviceGuard:
|
||||
*
|
||||
* - With a concrete implementation of DeviceGuardImpl, e.g., CUDAGuardImpl.
|
||||
* This is the best way to use InlineDeviceGuard, as all calls are
|
||||
* devirtualized, giving you code as efficient as straight line
|
||||
* calls to cudaGetDevice/cudaSetDevice.
|
||||
*
|
||||
* - With VirtualGuardImpl, which does a virtual dispatch to a DeviceGuardImpl
|
||||
* retrieved from a DeviceType registry. We have explicitly instantiated
|
||||
* InlineDeviceGuard this way as c10::DeviceGuard.
|
||||
*
|
||||
* If you are in a hurry, you can use InlineDeviceGuard directly:
|
||||
*
|
||||
* using CUDAGuard = impl::InlineDeviceGuard<CUDAGuardImpl>;
|
||||
*
|
||||
* However, you can provide a better user experience if you explicitly write a
|
||||
* wrapper class that itself contains the template instantiation:
|
||||
*
|
||||
* class CUDAGuard {
|
||||
* public:
|
||||
* // ... the API ...
|
||||
* private:
|
||||
* impl::InlineDeviceGuard<CUDAGuardImpl> guard_;
|
||||
* }
|
||||
*
|
||||
* The wrapper class provides a good place to write documentation, and helps
|
||||
* avoid weird template instantiation errors when a user incorrectly uses the
|
||||
* class.
|
||||
*
|
||||
* If you need to test this class, consider instantiating it with FakeGuardImpl.
|
||||
*/
|
||||
template <typename T>
|
||||
class InlineDeviceGuard {
|
||||
public:
|
||||
// Note [Omitted default constructor from RAII]
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// In principle, we could add a default constructor to
|
||||
// DeviceGuard which reads the current device and promises to
|
||||
// restore to that device on exit. However, most cases where you
|
||||
// would have written this, you probably meant to actually just
|
||||
// use DeviceGuard (since you don't actually need the
|
||||
// restore to happen if you don't ever actually set the device).
|
||||
// We remove the constructor here to encourage you to think about
|
||||
// what you actually want to happen.
|
||||
explicit InlineDeviceGuard() = delete;
|
||||
|
||||
/// Set the current device to the passed Device.
|
||||
explicit InlineDeviceGuard(Device device)
|
||||
: impl_(device.type()),
|
||||
original_device_(
|
||||
device.index() == -1 ? impl_.getDevice()
|
||||
: impl_.exchangeDevice(device)),
|
||||
current_device_(device.index() == -1 ? original_device_ : device) {}
|
||||
|
||||
/// Set the current device index to the passed DeviceIndex. (The
|
||||
/// device type is inferred from the template parameter T).
|
||||
template <
|
||||
typename U = T,
|
||||
typename =
|
||||
typename std::enable_if_t<!std::is_same_v<U, VirtualGuardImpl>>>
|
||||
explicit InlineDeviceGuard(DeviceIndex device_index)
|
||||
: InlineDeviceGuard(Device(U::static_type, device_index)) {}
|
||||
|
||||
/// Construct an InlineDeviceGuard using VirtualGuardImpl with an explicit
|
||||
/// DeviceGuardImplInterface pointer.
|
||||
template <
|
||||
typename U = T,
|
||||
typename = typename std::enable_if_t<std::is_same_v<U, VirtualGuardImpl>>>
|
||||
explicit InlineDeviceGuard(
|
||||
Device device,
|
||||
const DeviceGuardImplInterface* impl)
|
||||
: impl_(
|
||||
VirtualGuardImpl(impl ? impl : getDeviceGuardImpl(device.type()))),
|
||||
original_device_(
|
||||
device.index() == -1 ? impl_.getDevice()
|
||||
: impl_.exchangeDevice(device)),
|
||||
current_device_(device.index() == -1 ? original_device_ : device) {}
|
||||
|
||||
/// Copy is disallowed
|
||||
InlineDeviceGuard(const InlineDeviceGuard<T>&) = delete;
|
||||
InlineDeviceGuard<T>& operator=(const InlineDeviceGuard<T>&) = delete;
|
||||
|
||||
/// Move is disallowed, as DeviceGuard does not have an uninitialized state,
|
||||
/// which is required for moves on types with nontrivial destructors.
|
||||
InlineDeviceGuard(InlineDeviceGuard<T>&& other) = delete;
|
||||
InlineDeviceGuard& operator=(InlineDeviceGuard<T>&& other) = delete;
|
||||
|
||||
~InlineDeviceGuard() {
|
||||
impl_.uncheckedSetDevice(original_device_);
|
||||
}
|
||||
|
||||
/// Sets the device to the given one.
|
||||
template <
|
||||
typename U = T,
|
||||
typename std::enable_if_t<!std::is_same_v<U, VirtualGuardImpl>, int> = 0>
|
||||
void set_device(at::Device device) {
|
||||
AT_ASSERT(
|
||||
(U::static_type == DeviceType::HIP && device.is_cuda()) ||
|
||||
device.type() == U::static_type);
|
||||
auto index = device.index();
|
||||
if (index == -1)
|
||||
return;
|
||||
impl_.setDevice(device);
|
||||
current_device_ = device;
|
||||
}
|
||||
|
||||
/// Resets the currently set device to its original device, and then sets the
|
||||
/// current device to the passed device. This is effectively equivalent to
|
||||
/// set_device when a guard supports only a single device type.
|
||||
template <typename U = T>
|
||||
typename std::enable_if_t<!std::is_same_v<U, VirtualGuardImpl>> reset_device(
|
||||
at::Device device) {
|
||||
set_device(device);
|
||||
}
|
||||
|
||||
/// Resets the currently set device to its original device, and then sets the
|
||||
/// current device to the passed device (for a possibly different device
|
||||
/// type).
|
||||
///
|
||||
/// This method is named reset_device to highlight the fact that previous
|
||||
/// device settings from this guard are NOT preserved, even if the device
|
||||
/// has a different device type. For example:
|
||||
///
|
||||
/// // CUDA device is 0
|
||||
/// DeviceGuard g(Device(kCUDA, 1));
|
||||
/// g.reset_device(Device(kHIP, 2));
|
||||
/// // CUDA device is 0 (!!)
|
||||
///
|
||||
/// NOTE: this implementation may skip some device setting if it can prove
|
||||
/// that it is unnecessary.
|
||||
///
|
||||
/// Optional argument is for testing only.
|
||||
template <typename U = T>
|
||||
typename std::enable_if_t<std::is_same_v<U, VirtualGuardImpl>> reset_device(
|
||||
at::Device device,
|
||||
const impl::DeviceGuardImplInterface* impl = nullptr) {
|
||||
auto index = device.index();
|
||||
if (index == -1)
|
||||
return;
|
||||
if (device.type() == original_device_.type()) {
|
||||
AT_ASSERT(impl == nullptr || impl->type() == device.type());
|
||||
impl_.setDevice(device);
|
||||
current_device_ = device;
|
||||
} else {
|
||||
// Destruct and reconstruct the DeviceGuard in place
|
||||
impl_.setDevice(original_device_);
|
||||
impl_ = !impl ? VirtualGuardImpl(device.type()) : VirtualGuardImpl(impl);
|
||||
original_device_ = impl_.exchangeDevice(device);
|
||||
current_device_ = device;
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the device index to the given one. The device type is inferred
|
||||
/// from the original device type.
|
||||
void set_index(DeviceIndex index) {
|
||||
reset_device(Device(original_device_.type(), index));
|
||||
}
|
||||
|
||||
/// Returns the device that was set at the time the most recent
|
||||
/// reset_device(), or otherwise the device at construction time.
|
||||
Device original_device() const {
|
||||
return original_device_;
|
||||
}
|
||||
|
||||
/// Returns the most recent device that was set using this device guard,
|
||||
/// either from construction, or via set_device/reset_device/set_index.
|
||||
Device current_device() const {
|
||||
return current_device_;
|
||||
}
|
||||
|
||||
protected:
|
||||
T impl_;
|
||||
|
||||
private:
|
||||
Device original_device_;
|
||||
Device current_device_;
|
||||
};
|
||||
|
||||
/**
|
||||
* A OptionalDeviceGuard is an RAII class that sets a device to some value on
|
||||
* initialization, and resets the device to its original value on destruction.
|
||||
*
|
||||
* InlineOptionalDeviceGuard is a helper class for implementing
|
||||
* OptionalDeviceGuards. See guidance in InlineDeviceGuard on how to
|
||||
* use this. See OptionalDeviceGuard for user-oriented usage notes.
|
||||
*/
|
||||
template <typename T>
|
||||
class InlineOptionalDeviceGuard {
|
||||
public:
|
||||
// Note [Explicit initialization of optional fields]
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Explicit initialization of optional fields
|
||||
// required to workaround an nvcc bug; see
|
||||
// https://github.com/pytorch/pytorch/issues/12117
|
||||
|
||||
/// Creates an uninitialized OptionalDeviceGuard.
|
||||
explicit InlineOptionalDeviceGuard()
|
||||
: guard_() // See Note [Explicit initialization of optional fields]
|
||||
{}
|
||||
~InlineOptionalDeviceGuard() = default;
|
||||
|
||||
/// Set the current device to the passed Device, if it is not nullopt.
|
||||
explicit InlineOptionalDeviceGuard(std::optional<Device> device_opt)
|
||||
: guard_() { // See Note [Explicit initialization of optional fields]
|
||||
if (device_opt.has_value()) {
|
||||
guard_.emplace(device_opt.value());
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the current device to the passed DeviceIndex, if it is not nullopt.
|
||||
template <
|
||||
typename U = T,
|
||||
typename =
|
||||
typename std::enable_if_t<!std::is_same_v<U, VirtualGuardImpl>>>
|
||||
explicit InlineOptionalDeviceGuard(
|
||||
std::optional<DeviceIndex> device_index_opt)
|
||||
: guard_() { // See Note [Explicit initialization of optional fields]
|
||||
if (device_index_opt.has_value()) {
|
||||
guard_.emplace(device_index_opt.value());
|
||||
}
|
||||
}
|
||||
|
||||
/// All constructors of DeviceGuard are valid for OptionalDeviceGuard
|
||||
/// and result in initialized OptionalDeviceGuard.
|
||||
template <typename... Args>
|
||||
explicit InlineOptionalDeviceGuard(Args&&... args)
|
||||
: guard_(std::in_place, std::forward<Args>(args)...) {}
|
||||
|
||||
// TODO: Consider reading Tensor and TensorList constructors here, when
|
||||
// Tensor moves to c10. (These are only valid on OptionalDeviceGuard,
|
||||
// because a Tensor may be undefined, in which case we need an uninitialized
|
||||
// tensor guard.)
|
||||
|
||||
// Note [Move construction for RAII guards is tricky]
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// In principle, move construction is useful for terminating
|
||||
// the lifetime of a `OptionalDeviceGuard` early; for example:
|
||||
//
|
||||
// // current device is d0
|
||||
// OptionalDeviceGuard g1(d1);
|
||||
// // current device is d1
|
||||
// {
|
||||
// OptionalDeviceGuard g2(std::move(g1));
|
||||
// }
|
||||
// // current device is d0!!
|
||||
//
|
||||
// However, it's difficult to implement the move constructor
|
||||
// in a way that works in all situations. For example, consider
|
||||
// the following example:
|
||||
//
|
||||
// OptionalDeviceGuard g1(d1);
|
||||
// {
|
||||
// OptionalDeviceGuard g2(d2);
|
||||
// {
|
||||
// OptionalDeviceGuard g3(std::move(g1)); // !!!
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// What should the current device be while g3 in scope... and what
|
||||
// should it be after it goes out of scope? What about g2?
|
||||
// There don't seem to be satisfactory answers for these questions.
|
||||
//
|
||||
// It's in principle possible to raise an error when this occurs
|
||||
// by doing some extra thread-local bookkeeping. But why bother?
|
||||
// Just don't provide the constructor.
|
||||
InlineOptionalDeviceGuard(const InlineOptionalDeviceGuard<T>& other) = delete;
|
||||
InlineOptionalDeviceGuard(InlineOptionalDeviceGuard<T>&& other) = delete;
|
||||
|
||||
// Note [Move assignment for RAII guards is tricky]
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Move assignment is deleted, because you need to know which guard was
|
||||
// defined "first", as that guard's original_device_ wins--with the current
|
||||
// representation, we have no way of telling which is the case. (Move
|
||||
// construction does not have this problem, as one guard is always
|
||||
// uninitialized.)
|
||||
//
|
||||
// We can make this clear by way of a pair of examples:
|
||||
//
|
||||
// Example 1:
|
||||
//
|
||||
// // initial device is n0
|
||||
// {
|
||||
// CUDAGuard g1(n1);
|
||||
// {
|
||||
// CUDAGuard g2(n2);
|
||||
// // current device should be n2
|
||||
// g1 = std::move(g2);
|
||||
// // current device should still be n2
|
||||
// }
|
||||
// // current device should still be n2
|
||||
// }
|
||||
// // current device should be n0
|
||||
//
|
||||
// Example 2 (flip the order of the two guards):
|
||||
//
|
||||
// // initial device is n0
|
||||
// {
|
||||
// CUDAGuard g2(n2);
|
||||
// {
|
||||
// CUDAGuard g1(n1);
|
||||
// // current device should be n1
|
||||
// g1 = std::move(g2);
|
||||
// // current device should be n2
|
||||
// }
|
||||
// // current device should be n0 (since g2 has been vacated)
|
||||
// }
|
||||
//
|
||||
// In both examples, we need g1 to restore to n0 after move assignment.
|
||||
// However, in example 1, this is determined by the restore value of g1
|
||||
// (prior to the move). In example 2, however, it is determined by the the
|
||||
// restore value of g2(!!). We don't know which one should win, without having
|
||||
// a way of telling which guard was allocated first.
|
||||
//
|
||||
// We could solve this with an extra thread-local variable. But no one is
|
||||
// actually using move-assignment. So just get rid of it.
|
||||
InlineOptionalDeviceGuard& operator=(const InlineOptionalDeviceGuard& other) =
|
||||
delete;
|
||||
InlineOptionalDeviceGuard& operator=(InlineOptionalDeviceGuard&& other) =
|
||||
delete;
|
||||
|
||||
/// Sets the device to the given one. Initializes OptionalDeviceGuard if it
|
||||
/// is not already initialized.
|
||||
template <
|
||||
typename U = T,
|
||||
typename =
|
||||
typename std::enable_if_t<!std::is_same_v<U, VirtualGuardImpl>>>
|
||||
void set_device(at::Device device) {
|
||||
if (!guard_.has_value()) {
|
||||
guard_.emplace(device);
|
||||
} else {
|
||||
guard_->set_device(device);
|
||||
}
|
||||
}
|
||||
|
||||
/// Resets the currently set device to its original device, and then sets the
|
||||
/// current device to the passed device (for a possibly different device
|
||||
/// type). Initializes OptionalDeviceGuard if it is not already initialized.
|
||||
///
|
||||
/// See notes on why this is called reset_device on InlineDeviceGuard.
|
||||
///
|
||||
/// Optional argument is for testing only.
|
||||
template <
|
||||
typename U = T,
|
||||
typename = typename std::enable_if_t<std::is_same_v<U, VirtualGuardImpl>>>
|
||||
void reset_device(
|
||||
at::Device device,
|
||||
const DeviceGuardImplInterface* impl = nullptr) {
|
||||
if (!guard_.has_value()) {
|
||||
guard_.emplace(device, impl);
|
||||
} else {
|
||||
guard_->reset_device(device, impl);
|
||||
}
|
||||
}
|
||||
|
||||
/// Resets the currently set device to its original device, and then sets the
|
||||
/// current device to the passed device. Initializes the guard if it is
|
||||
/// not already initialized. This is effectively equivalent to set_device
|
||||
/// when a guard supports only a single device type.
|
||||
template <
|
||||
typename U = T,
|
||||
typename =
|
||||
typename std::enable_if_t<!std::is_same_v<U, VirtualGuardImpl>>>
|
||||
void reset_device(at::Device device) {
|
||||
if (!guard_.has_value()) {
|
||||
guard_.emplace(device);
|
||||
} else {
|
||||
guard_->reset_device(device);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the device index to the given one. The device type is statically
|
||||
/// known.
|
||||
template <
|
||||
typename U = T,
|
||||
typename =
|
||||
typename std::enable_if_t<!std::is_same_v<U, VirtualGuardImpl>>>
|
||||
void set_index(DeviceIndex index) {
|
||||
if (!guard_.has_value()) {
|
||||
guard_.emplace(index);
|
||||
} else {
|
||||
guard_->set_index(index);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the device that was set immediately prior to initialization of
|
||||
/// the, guard, or nullopt if the guard is uninitialized.
|
||||
std::optional<Device> original_device() const {
|
||||
return guard_.has_value() ? std::make_optional(guard_->original_device())
|
||||
: std::nullopt;
|
||||
}
|
||||
|
||||
/// Returns the most recent device that was set using this device guard,
|
||||
/// either from construction, or via set_device, if the guard is initialized,
|
||||
/// or nullopt if the guard is uninitialized.
|
||||
std::optional<Device> current_device() const {
|
||||
return guard_.has_value() ? std::make_optional(guard_->current_device())
|
||||
: std::nullopt;
|
||||
}
|
||||
|
||||
/// Restore the original device, resetting this guard to uninitialized state.
|
||||
void reset() {
|
||||
guard_.reset();
|
||||
}
|
||||
|
||||
private:
|
||||
std::optional<InlineDeviceGuard<T>> guard_;
|
||||
};
|
||||
|
||||
} // namespace c10::impl
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,152 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/DeviceType.h>
|
||||
#include <c10/core/Stream.h>
|
||||
#include <c10/core/impl/DeviceGuardImplInterface.h>
|
||||
#include <c10/util/Exception.h>
|
||||
|
||||
namespace c10::impl {
|
||||
|
||||
template <typename T>
|
||||
struct InlineEvent final {
|
||||
InlineEvent() = delete;
|
||||
InlineEvent(
|
||||
const DeviceType _device_type,
|
||||
const EventFlag _flag = EventFlag::PYTORCH_DEFAULT)
|
||||
: backend_{_device_type}, device_type_{_device_type}, flag_{_flag} {}
|
||||
|
||||
// Copy constructor and copy assignment operator (deleted)
|
||||
InlineEvent(const InlineEvent&) = delete;
|
||||
InlineEvent& operator=(const InlineEvent&) = delete;
|
||||
|
||||
// Move constructor and move assignment operator
|
||||
InlineEvent(InlineEvent&& other) noexcept
|
||||
: event_(other.event_),
|
||||
backend_(std::move(other.backend_)),
|
||||
device_type_(other.device_type_),
|
||||
device_index_(other.device_index_),
|
||||
flag_(other.flag_),
|
||||
was_marked_for_recording_(other.was_marked_for_recording_) {
|
||||
other.event_ = nullptr;
|
||||
}
|
||||
InlineEvent& operator=(InlineEvent&& other) noexcept {
|
||||
swap(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void swap(InlineEvent& other) noexcept {
|
||||
std::swap(event_, other.event_);
|
||||
std::swap(backend_, other.backend_);
|
||||
std::swap(device_type_, other.device_type_);
|
||||
std::swap(device_index_, other.device_index_);
|
||||
std::swap(flag_, other.flag_);
|
||||
std::swap(was_marked_for_recording_, other.was_marked_for_recording_);
|
||||
}
|
||||
|
||||
~InlineEvent() noexcept {
|
||||
if (event_)
|
||||
backend_.destroyEvent(event_, device_index_);
|
||||
}
|
||||
|
||||
DeviceType device_type() const noexcept {
|
||||
return device_type_;
|
||||
}
|
||||
DeviceIndex device_index() const noexcept {
|
||||
return device_index_;
|
||||
}
|
||||
EventFlag flag() const noexcept {
|
||||
return flag_;
|
||||
}
|
||||
bool was_marked_for_recording() const noexcept {
|
||||
return was_marked_for_recording_;
|
||||
}
|
||||
|
||||
void recordOnce(const Stream& stream) {
|
||||
if (!was_marked_for_recording_)
|
||||
record(stream);
|
||||
}
|
||||
|
||||
void record(const Stream& stream) {
|
||||
TORCH_CHECK(
|
||||
stream.device_type() == device_type_,
|
||||
"Event device type ",
|
||||
DeviceTypeName(device_type_),
|
||||
" does not match recording stream's device type ",
|
||||
DeviceTypeName(stream.device_type()),
|
||||
".");
|
||||
|
||||
backend_.record(&event_, stream, device_index_, flag_);
|
||||
was_marked_for_recording_ = true;
|
||||
device_index_ = stream.device_index();
|
||||
}
|
||||
|
||||
void block(const Stream& stream) const {
|
||||
if (!was_marked_for_recording_)
|
||||
return;
|
||||
|
||||
TORCH_CHECK(
|
||||
stream.device_type() == device_type_,
|
||||
"Event device type ",
|
||||
DeviceTypeName(device_type_),
|
||||
" does not match blocking stream's device type ",
|
||||
DeviceTypeName(stream.device_type()),
|
||||
".");
|
||||
|
||||
backend_.block(event_, stream);
|
||||
}
|
||||
|
||||
bool query() const {
|
||||
if (!was_marked_for_recording_)
|
||||
return true;
|
||||
return backend_.queryEvent(event_);
|
||||
}
|
||||
|
||||
void* eventId() const {
|
||||
return event_;
|
||||
}
|
||||
|
||||
double elapsedTime(const InlineEvent& other) const {
|
||||
TORCH_CHECK(
|
||||
other.device_type() == device_type_,
|
||||
"Event device type ",
|
||||
DeviceTypeName(device_type_),
|
||||
" does not match other's device type ",
|
||||
DeviceTypeName(other.device_type()),
|
||||
".");
|
||||
TORCH_CHECK_VALUE(
|
||||
(flag_ == EventFlag::BACKEND_DEFAULT) &&
|
||||
(other.flag_ == EventFlag::BACKEND_DEFAULT),
|
||||
"Both events must be created with argument 'enable_timing=True'.");
|
||||
TORCH_CHECK_VALUE(
|
||||
was_marked_for_recording() && other.was_marked_for_recording(),
|
||||
"Both events must be recorded before calculating elapsed time.");
|
||||
// elapsedTime in MPS can wait event to be completed if event is not ready,
|
||||
// which is a little different from CUDA
|
||||
TORCH_CHECK(
|
||||
(query() && other.query()) || device_type_ == DeviceType::MPS,
|
||||
"Both events must be completed before calculating elapsed time.");
|
||||
|
||||
return backend_.elapsedTime(event_, other.event_, device_index_);
|
||||
}
|
||||
|
||||
void synchronize() const {
|
||||
if (!was_marked_for_recording_)
|
||||
return;
|
||||
backend_.synchronizeEvent(event_);
|
||||
}
|
||||
|
||||
private:
|
||||
void* event_ = nullptr;
|
||||
T backend_;
|
||||
DeviceType device_type_;
|
||||
DeviceIndex device_index_ = -1;
|
||||
EventFlag flag_ = EventFlag::PYTORCH_DEFAULT;
|
||||
bool was_marked_for_recording_ = false;
|
||||
};
|
||||
|
||||
} // namespace c10::impl
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
+265
@@ -0,0 +1,265 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/impl/InlineDeviceGuard.h>
|
||||
#include <c10/util/ArrayRef.h>
|
||||
#include <c10/util/irange.h>
|
||||
|
||||
namespace c10::impl {
|
||||
|
||||
/**
|
||||
* A StreamGuard is an RAII class that changes the current device
|
||||
* to the device corresponding to some stream, and changes the
|
||||
* default stream on that device to be this stream.
|
||||
*
|
||||
* InlineStreamGuard is a helper class for implementing StreamGuards.
|
||||
* See InlineDeviceGuard for guidance on how to use this class.
|
||||
*/
|
||||
template <typename T>
|
||||
class InlineStreamGuard : private InlineDeviceGuard<T> {
|
||||
public:
|
||||
/// No default constructor, see Note [Omitted default constructor from RAII]
|
||||
explicit InlineStreamGuard() = delete;
|
||||
|
||||
/// Set the current device to the device associated with the passed stream,
|
||||
/// and set the current stream on that device to the passed stream.
|
||||
explicit InlineStreamGuard(Stream stream)
|
||||
: InlineDeviceGuard<T>(stream.device()),
|
||||
original_stream_of_original_device_(
|
||||
this->impl_.getStream(original_device())),
|
||||
original_stream_of_current_device_(this->impl_.exchangeStream(stream)),
|
||||
current_stream_(stream) {}
|
||||
|
||||
/// This constructor exists purely for testing
|
||||
template <
|
||||
typename U = T,
|
||||
typename = typename std::enable_if_t<std::is_same_v<U, VirtualGuardImpl>>>
|
||||
explicit InlineStreamGuard(
|
||||
Stream stream,
|
||||
const DeviceGuardImplInterface* impl)
|
||||
: InlineDeviceGuard<T>(
|
||||
stream.device(),
|
||||
impl ? impl : getDeviceGuardImpl(stream.device_type())),
|
||||
original_stream_of_original_device_(
|
||||
this->impl_.getStream(original_device())),
|
||||
original_stream_of_current_device_(this->impl_.exchangeStream(stream)),
|
||||
current_stream_(stream) {}
|
||||
|
||||
/// Copy is disallowed
|
||||
InlineStreamGuard(const InlineStreamGuard<T>&) = delete;
|
||||
InlineStreamGuard<T>& operator=(const InlineStreamGuard<T>&) = delete;
|
||||
|
||||
/// Move is disallowed, as StreamGuard does not have an uninitialized state,
|
||||
/// which is required for moves on types with nontrivial destructors.
|
||||
InlineStreamGuard(InlineStreamGuard<T>&& other) = delete;
|
||||
InlineStreamGuard& operator=(InlineStreamGuard<T>&& other) = delete;
|
||||
|
||||
~InlineStreamGuard() {
|
||||
this->impl_.exchangeStream(original_stream_of_current_device_);
|
||||
}
|
||||
|
||||
/// Resets the currently set stream to the original stream and
|
||||
/// the currently set device to the original device. Then,
|
||||
/// set the current device to the device associated with the passed stream,
|
||||
/// and set the current stream on that device to the passed stream.
|
||||
///
|
||||
/// NOTE: this implementation may skip some stream/device setting if
|
||||
/// it can prove that it is unnecessary.
|
||||
///
|
||||
/// WARNING: reset_stream does NOT preserve previously set streams on
|
||||
/// different devices. If you need to set streams on multiple devices
|
||||
/// use MultiStreamGuard instead.
|
||||
void reset_stream(Stream stream) {
|
||||
// TODO: make a version that takes an impl argument. Unfortunately,
|
||||
// that will require SFINAE because impl is only valid for the
|
||||
// VirtualGuardImpl specialization.
|
||||
if (stream.device() == this->current_device()) {
|
||||
this->impl_.exchangeStream(stream);
|
||||
current_stream_ = stream;
|
||||
} else {
|
||||
// Destruct and reconstruct the StreamGuard in-place
|
||||
this->impl_.exchangeStream(original_stream_of_current_device_);
|
||||
this->reset_device(stream.device());
|
||||
original_stream_of_current_device_ = this->impl_.exchangeStream(stream);
|
||||
current_stream_ = stream;
|
||||
}
|
||||
}
|
||||
|
||||
// It's not clear if set_device should also reset the current stream
|
||||
// if the device is unchanged; therefore, we don't provide it.
|
||||
// The situation is somewhat clearer with reset_device, but it's still
|
||||
// a pretty weird thing to do, so haven't added this either.
|
||||
|
||||
/// Returns the stream of the original device prior to this guard. Subtly,
|
||||
/// the stream returned here is the original stream of the *original*
|
||||
/// device; i.e., it's the stream that your computation *would* have
|
||||
/// been put on, if it hadn't been for this meddling stream guard.
|
||||
/// This is usually what you want.
|
||||
Stream original_stream() const {
|
||||
return original_stream_of_original_device_;
|
||||
}
|
||||
|
||||
/// Returns the most recent stream that was set using this device guard,
|
||||
/// either from construction, or via set_stream.
|
||||
Stream current_stream() const {
|
||||
return current_stream_;
|
||||
}
|
||||
|
||||
/// Returns the most recent device that was set using this device guard,
|
||||
/// either from construction, or via set_device/reset_device/set_index.
|
||||
Device current_device() const {
|
||||
return InlineDeviceGuard<T>::current_device();
|
||||
}
|
||||
|
||||
/// Returns the device that was set at the most recent reset_stream(),
|
||||
/// or otherwise the device at construction time.
|
||||
Device original_device() const {
|
||||
return InlineDeviceGuard<T>::original_device();
|
||||
}
|
||||
|
||||
private:
|
||||
Stream
|
||||
original_stream_of_original_device_; // what the user probably cares about
|
||||
Stream original_stream_of_current_device_; // what we need to restore
|
||||
Stream current_stream_;
|
||||
};
|
||||
|
||||
/**
|
||||
* An OptionalStreamGuard is an RAII class that sets a device to some value on
|
||||
* initialization, and resets the device to its original value on destruction.
|
||||
* See InlineOptionalDeviceGuard for more guidance on how to use this class.
|
||||
*/
|
||||
template <typename T>
|
||||
class InlineOptionalStreamGuard {
|
||||
public:
|
||||
/// Creates an uninitialized stream guard.
|
||||
explicit InlineOptionalStreamGuard()
|
||||
: guard_() // See Note [Explicit initialization of optional fields]
|
||||
{}
|
||||
~InlineOptionalStreamGuard() = default;
|
||||
|
||||
/// Set the current device to the device associated with the passed stream,
|
||||
/// and set the current stream on that device to the passed stream,
|
||||
/// if the passed stream is not nullopt.
|
||||
explicit InlineOptionalStreamGuard(std::optional<Stream> stream_opt)
|
||||
: guard_() {
|
||||
if (stream_opt.has_value()) {
|
||||
guard_.emplace(stream_opt.value());
|
||||
}
|
||||
}
|
||||
|
||||
/// All constructors of StreamGuard are valid for OptionalStreamGuard
|
||||
template <typename... Args>
|
||||
explicit InlineOptionalStreamGuard(Args&&... args)
|
||||
: guard_(std::in_place, std::forward<Args>(args)...) {}
|
||||
|
||||
InlineOptionalStreamGuard(const InlineOptionalStreamGuard<T>& other) = delete;
|
||||
InlineOptionalStreamGuard& operator=(const InlineOptionalStreamGuard& other) =
|
||||
delete;
|
||||
// See Note [Move construction for RAII guards is tricky]
|
||||
InlineOptionalStreamGuard(InlineOptionalStreamGuard<T>&& other) = delete;
|
||||
|
||||
// See Note [Move assignment for RAII guards is tricky]
|
||||
InlineOptionalStreamGuard& operator=(InlineOptionalStreamGuard&& other) =
|
||||
delete;
|
||||
|
||||
/// Resets the currently set stream to the original stream and
|
||||
/// the currently set device to the original device. Then,
|
||||
/// set the current device to the device associated with the passed stream,
|
||||
/// and set the current stream on that device to the passed stream.
|
||||
/// Initializes the OptionalStreamGuard if it was not previously initialized.
|
||||
void reset_stream(Stream stream) {
|
||||
if (guard_.has_value()) {
|
||||
guard_->reset_stream(stream);
|
||||
} else {
|
||||
guard_.emplace(stream);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the stream that was set at the time the guard was most recently
|
||||
/// initialized, or nullopt if the guard is uninitialized.
|
||||
std::optional<Stream> original_stream() const {
|
||||
return guard_.has_value() ? std::make_optional(guard_->original_stream())
|
||||
: std::nullopt;
|
||||
}
|
||||
|
||||
/// Returns the most recent stream that was set using this stream guard,
|
||||
/// either from construction, or via reset_stream, if the guard is
|
||||
/// initialized, or nullopt if the guard is uninitialized.
|
||||
std::optional<Stream> current_stream() const {
|
||||
return guard_.has_value() ? std::make_optional(guard_->current_stream())
|
||||
: std::nullopt;
|
||||
}
|
||||
|
||||
/// Restore the original device and stream, resetting this guard to
|
||||
/// uninitialized state.
|
||||
void reset() {
|
||||
guard_.reset();
|
||||
}
|
||||
|
||||
private:
|
||||
std::optional<InlineStreamGuard<T>> guard_;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class InlineMultiStreamGuard {
|
||||
public:
|
||||
/// Calls `set_stream` on each of the streams in the list.
|
||||
/// This may be useful if you need to set different streams
|
||||
/// for different devices.
|
||||
explicit InlineMultiStreamGuard(ArrayRef<Stream> streams) {
|
||||
if (!streams.empty()) {
|
||||
impl_.emplace(getDeviceTypeOfStreams(streams));
|
||||
original_streams_.reserve(streams.size());
|
||||
for (const Stream& s : streams) {
|
||||
original_streams_.emplace_back(this->impl_->exchangeStream(s));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Copy is disallowed
|
||||
InlineMultiStreamGuard(const InlineMultiStreamGuard&) = delete;
|
||||
InlineMultiStreamGuard<T>& operator=(const InlineMultiStreamGuard&) = delete;
|
||||
|
||||
/// Move is disallowed, as StreamGuard does not have an uninitialized state,
|
||||
/// which is required for moves on types with nontrivial destructors.
|
||||
InlineMultiStreamGuard(InlineMultiStreamGuard&& other) = delete;
|
||||
InlineMultiStreamGuard& operator=(InlineMultiStreamGuard&& other) = delete;
|
||||
|
||||
~InlineMultiStreamGuard() noexcept {
|
||||
if (this->impl_.has_value()) {
|
||||
for (const Stream& s : original_streams_) {
|
||||
this->impl_->exchangeStream(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
std::optional<T> impl_;
|
||||
|
||||
private:
|
||||
/// The original streams that were active on all devices.
|
||||
std::vector<Stream> original_streams_;
|
||||
|
||||
static DeviceType getDeviceTypeOfStreams(ArrayRef<Stream> streams) {
|
||||
TORCH_INTERNAL_ASSERT(!streams.empty());
|
||||
DeviceType type = streams[0].device_type();
|
||||
for (const auto idx : c10::irange(1, streams.size())) {
|
||||
TORCH_CHECK_VALUE(
|
||||
streams[idx].device_type() == type,
|
||||
"Streams have a mix of device types: stream 0 is on ",
|
||||
streams[0].device(),
|
||||
" while stream ",
|
||||
idx,
|
||||
" is on device ",
|
||||
streams[idx].device());
|
||||
}
|
||||
return type;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace c10::impl
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/DispatchKeySet.h>
|
||||
#include <c10/macros/Export.h>
|
||||
|
||||
// TLS management for DispatchKeySet (the "local" DispatchKeySet(s))
|
||||
//
|
||||
// This manages two thread-local DispatchKeySets:
|
||||
//
|
||||
// - The included type set, which adds a tensor type for consideration
|
||||
// in dispatch. (For example, you might add Profiling to
|
||||
// the included type set to turn on profiling on all tensor operations.)
|
||||
//
|
||||
// - The excluded type set, which disqualifies a tensor type from dispatch.
|
||||
// (For example, after redispatching on variable, we disqualify
|
||||
// Autograd so we don't attempt to handle variable again.)
|
||||
// (Exclusion wins over inclusion.)
|
||||
//
|
||||
// NB: Originally, I implemented the excluded type set as storing the inverted
|
||||
// set, but TLS is defined to be zero-initialized, so this doesn't actually work
|
||||
// (if it's inverted, you want the set to be -1 initialized).
|
||||
|
||||
namespace c10::impl {
|
||||
|
||||
// POD version of LocalDispatchKeySet. Declared here just so that
|
||||
// we can put it in the guards.
|
||||
// This struct encapsulates special handling for TLS initialization
|
||||
// in set_included()/included() API so that they reflect the truth.
|
||||
// If you want to create PODLocalDispatchKeySet with non-zero state,
|
||||
// use set_included() instead of default constructor.
|
||||
struct C10_API PODLocalDispatchKeySet {
|
||||
uint64_t included_;
|
||||
uint64_t excluded_;
|
||||
|
||||
// See Note [TLS Initialization]
|
||||
DispatchKeySet included() const {
|
||||
return DispatchKeySet(DispatchKeySet::RAW, included_) ^
|
||||
c10::default_included_set;
|
||||
}
|
||||
DispatchKeySet excluded() const {
|
||||
return DispatchKeySet(DispatchKeySet::RAW, excluded_) ^
|
||||
c10::default_excluded_set;
|
||||
}
|
||||
|
||||
void set_included(DispatchKeySet x) {
|
||||
included_ = (x ^ c10::default_included_set).raw_repr();
|
||||
}
|
||||
void set_excluded(DispatchKeySet x) {
|
||||
excluded_ = (x ^ c10::default_excluded_set).raw_repr();
|
||||
}
|
||||
};
|
||||
static_assert(
|
||||
std::is_trivial_v<PODLocalDispatchKeySet>,
|
||||
"PODLocalDispatchKeySet must be a POD type.");
|
||||
|
||||
struct C10_API LocalDispatchKeySet {
|
||||
/* implicit */ LocalDispatchKeySet(PODLocalDispatchKeySet x)
|
||||
: included_(x.included()), excluded_(x.excluded()) {}
|
||||
DispatchKeySet included_;
|
||||
DispatchKeySet excluded_;
|
||||
};
|
||||
|
||||
// thread_local variables cannot be C10_API on Windows.
|
||||
// Inlining this seems to break AutoDispatchBelowAutograd on Android.
|
||||
#if defined(_MSC_VER) || defined(C10_ANDROID) || defined(C10_IPHONE)
|
||||
C10_API LocalDispatchKeySet tls_local_dispatch_key_set();
|
||||
#else // defined(_MSC_VER) || defined(C10_ANDROID) || defined(C10_IPHONE)
|
||||
extern C10_API thread_local PODLocalDispatchKeySet raw_local_dispatch_key_set;
|
||||
|
||||
inline C10_API LocalDispatchKeySet tls_local_dispatch_key_set() {
|
||||
// Don't let people fiddle with the thread_local directly just
|
||||
// because they include this header.
|
||||
return raw_local_dispatch_key_set;
|
||||
}
|
||||
#endif // defined(_MSC_VER) || defined(C10_ANDROID) || defined(C10_IPHONE)
|
||||
|
||||
// Internal, use ThreadLocalStateGuard
|
||||
C10_API void _force_tls_local_dispatch_key_set(LocalDispatchKeySet key_set);
|
||||
|
||||
// RAII API for manipulating the thread-local dispatch state.
|
||||
|
||||
class C10_API IncludeDispatchKeyGuard {
|
||||
public:
|
||||
IncludeDispatchKeyGuard(DispatchKeySet /*include*/);
|
||||
IncludeDispatchKeyGuard(DispatchKey k)
|
||||
: IncludeDispatchKeyGuard(DispatchKeySet(k)) {}
|
||||
IncludeDispatchKeyGuard(const IncludeDispatchKeyGuard&) = delete;
|
||||
IncludeDispatchKeyGuard operator=(const IncludeDispatchKeyGuard&) = delete;
|
||||
IncludeDispatchKeyGuard(IncludeDispatchKeyGuard&&) = delete;
|
||||
IncludeDispatchKeyGuard operator=(IncludeDispatchKeyGuard&&) = delete;
|
||||
~IncludeDispatchKeyGuard();
|
||||
|
||||
private:
|
||||
// A little micro-optimization to save us from tls_get_addr call
|
||||
// on destruction
|
||||
PODLocalDispatchKeySet* tls_;
|
||||
DispatchKeySet saved_state_;
|
||||
};
|
||||
|
||||
class C10_API ExcludeDispatchKeyGuard {
|
||||
public:
|
||||
ExcludeDispatchKeyGuard(DispatchKeySet /*exclude*/);
|
||||
ExcludeDispatchKeyGuard(DispatchKey k)
|
||||
: ExcludeDispatchKeyGuard(DispatchKeySet(k)) {}
|
||||
ExcludeDispatchKeyGuard(const ExcludeDispatchKeyGuard&) = delete;
|
||||
ExcludeDispatchKeyGuard operator=(const ExcludeDispatchKeyGuard&) = delete;
|
||||
ExcludeDispatchKeyGuard(ExcludeDispatchKeyGuard&&) = delete;
|
||||
ExcludeDispatchKeyGuard operator=(ExcludeDispatchKeyGuard&&) = delete;
|
||||
~ExcludeDispatchKeyGuard();
|
||||
|
||||
private:
|
||||
// A little micro-optimization to save us from tls_get_addr call
|
||||
// on destruction
|
||||
PODLocalDispatchKeySet* tls_;
|
||||
DispatchKeySet saved_state_;
|
||||
};
|
||||
|
||||
struct C10_API ForceDispatchKeyGuard {
|
||||
public:
|
||||
ForceDispatchKeyGuard()
|
||||
: saved_keyset_(c10::impl::tls_local_dispatch_key_set()) {}
|
||||
ForceDispatchKeyGuard(c10::impl::LocalDispatchKeySet key_set)
|
||||
: ForceDispatchKeyGuard() {
|
||||
c10::impl::_force_tls_local_dispatch_key_set(key_set);
|
||||
}
|
||||
ForceDispatchKeyGuard(
|
||||
c10::DispatchKeySet include,
|
||||
c10::DispatchKeySet exclude)
|
||||
: ForceDispatchKeyGuard() {
|
||||
auto updated_set = saved_keyset_;
|
||||
updated_set.included_ = include;
|
||||
updated_set.excluded_ = exclude;
|
||||
c10::impl::_force_tls_local_dispatch_key_set(updated_set);
|
||||
}
|
||||
|
||||
ForceDispatchKeyGuard(ForceDispatchKeyGuard&&) noexcept = delete;
|
||||
ForceDispatchKeyGuard(const ForceDispatchKeyGuard&) = delete;
|
||||
ForceDispatchKeyGuard& operator=(const ForceDispatchKeyGuard&) = delete;
|
||||
ForceDispatchKeyGuard& operator=(ForceDispatchKeyGuard&&) = delete;
|
||||
~ForceDispatchKeyGuard() {
|
||||
c10::impl::_force_tls_local_dispatch_key_set(saved_keyset_);
|
||||
}
|
||||
|
||||
private:
|
||||
c10::impl::LocalDispatchKeySet saved_keyset_;
|
||||
};
|
||||
|
||||
// Non-RAII API for manipulating the thread-local dispatch state.
|
||||
// Please prefer the RAII API. The non-RAII API may be useful when
|
||||
// the included/excluded state of a given DispatchKey must span
|
||||
// many calls from the Python to the C++, so you cannot conveniently
|
||||
// use an RAII guard.
|
||||
//
|
||||
// Example use case: a Python context manager that includes a certain
|
||||
// DispatchKey, to ensure ops running under the context manager dispatch
|
||||
// through that DispatchKey's registered overrides.
|
||||
//
|
||||
// The non-RAII API is less efficient than the RAII guards because both the
|
||||
// getter and setter will do a tls_getaddr lookup (the RAII struct only needs
|
||||
// one!)
|
||||
|
||||
C10_API bool tls_is_dispatch_key_excluded(DispatchKey x);
|
||||
C10_API void tls_set_dispatch_key_excluded(DispatchKey x, bool desired_state);
|
||||
C10_API bool tls_is_dispatch_key_included(DispatchKey x);
|
||||
C10_API void tls_set_dispatch_key_included(DispatchKey x, bool desired_state);
|
||||
C10_API bool tls_is_dispatch_keyset_excluded(DispatchKeySet ks);
|
||||
C10_API bool tls_is_dispatch_keyset_included(DispatchKeySet ks);
|
||||
|
||||
} // namespace c10::impl
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,257 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/Device.h>
|
||||
#include <c10/core/DispatchKeySet.h>
|
||||
#include <c10/core/Layout.h>
|
||||
#include <c10/core/MemoryFormat.h>
|
||||
#include <c10/core/SymIntArrayRef.h>
|
||||
#include <c10/macros/Export.h>
|
||||
#include <c10/util/ArrayRef.h>
|
||||
#include <c10/util/intrusive_ptr.h>
|
||||
#include <c10/util/python_stub.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Forward declarations
|
||||
|
||||
namespace c10 {
|
||||
struct IValue;
|
||||
class OperatorHandle;
|
||||
struct TensorImpl;
|
||||
namespace impl {
|
||||
struct PyObjectSlot;
|
||||
} // namespace impl
|
||||
} // namespace c10
|
||||
|
||||
namespace torch::jit {
|
||||
using Stack = std::vector<c10::IValue>;
|
||||
}
|
||||
|
||||
// Actual implementation
|
||||
|
||||
namespace c10::impl {
|
||||
|
||||
struct C10_API PyInterpreter;
|
||||
|
||||
// Note [Python interpreter tag]
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Traditionally, PyTorch is layered such that our Python library
|
||||
// (libtorch_python) references our pure C++ library (libtorch) as the
|
||||
// natural order of things. However, sometimes this natural order is
|
||||
// subverted: C++ objects refer to Python objects (for example, we
|
||||
// store a PyObject* pointer on TensorImpl so that converting from a
|
||||
// C++ Tensor to a Python Tensor is just a memory dereference).
|
||||
//
|
||||
// These unusual orderings must be treated with care. To start, you need to
|
||||
// virtualize the destructor so that the PyObject can be decref'ed on
|
||||
// destruction (because the C++ object itself doesn't know anything about
|
||||
// Python--remember, layering!). This process itself is fraught, since
|
||||
// acquiring the GIL could lead to deadlocks if someone is blocking on you
|
||||
// while holding the GIL. Furthermore, if the C++ objects outlive the
|
||||
// interpreter (which can happen if you stash them in a static global
|
||||
// variable defined in libtorch), you may attempt to decref the object when
|
||||
// the Python interpreter has already been shutdown.
|
||||
//
|
||||
// BUT WAIT, IT GETS WORSE. With torchdeploy, there may be multiple Python
|
||||
// interpreters in a single process. If a C++ object is accessible from
|
||||
// multiple interpreters, we must take care not to accidentally pass a
|
||||
// PyObject from one interpreter with another interpreter.
|
||||
//
|
||||
// To prevent these mixups, we introduce a PyInterpreter "tag" (object with
|
||||
// a vtable), which specifies a specific Python interpreter.
|
||||
//
|
||||
// - Any given object can be associated with AT MOST one Python interpreter.
|
||||
// We represent the interpreter tag as a memory address to an instance of
|
||||
// a virtual class that is allocated once per interpreter (this is so that
|
||||
// we can request the interpreter to perform operations for us, if
|
||||
// necessary).
|
||||
//
|
||||
// - It can be recorded with a PyObject (PyInterpreterObject) so that
|
||||
// we know what interpreter the object is associated with, and we can
|
||||
// raise an error if you try to use the PyObject from the wrong
|
||||
// interpreter context.
|
||||
//
|
||||
// - It contains a vtable that can be used to perform various Python
|
||||
// operations from ordinary C++ code that ordinarily wouldn't be accessible
|
||||
// from libtorch.
|
||||
//
|
||||
// A simple use case is when a C++ object must be associated with a PyObject.
|
||||
// However, for TensorImpl, we lazily allocate a PyObject the first time the
|
||||
// object passes into Python. The invariants for this situation are more
|
||||
// subtle:
|
||||
//
|
||||
// - A given TensorImpl's interpreter tag can only go from uninitialized to
|
||||
// tagged; once tagged, this is a quiescent state (once tagged to an
|
||||
// interpreter, ALWAYS tagged to that interpreter)
|
||||
//
|
||||
// - A thread may mutate the PyObject field of a TensorImpl if and only if it
|
||||
// holds the GIL for the interpreter tagged on the TensorImpl. (If the
|
||||
// TensorImpl is not tagged, it must first atomically claim its tag before it
|
||||
// can validly write)
|
||||
//
|
||||
// WARNING: This class has to be written very carefully, because it may be
|
||||
// possible for a Tensor to have a reference an interpreter corresponding to
|
||||
// a shared library that has ALREADY BEEN UNLOADED. This makes blindly calling
|
||||
// virtual methods very dangerous, because the vtable may be garbage at that
|
||||
// point (on a good day, you might get "pure virtual method called").
|
||||
//
|
||||
// The idea to solve this problem is we always leak PyInterpreters (so they
|
||||
// always stay live even after dlclose), and make sure we can disarm their
|
||||
// virtual methods by indirecting through a separate PyInterpreterVTable
|
||||
// object. This can be replaced with a no-op vtable from libc10.so, which
|
||||
// is guaranteed to stick around until the bitter end.
|
||||
//
|
||||
// NB: The downside with representing PyInterpreter tags as full objects is that
|
||||
// it takes an extra word on TensorImpl. If tags were instead just integer
|
||||
// indices, on 64-bit architectures we could pack the tag and PyObject together
|
||||
// into a single atomic word. On 32-bit architectures we could simply say that
|
||||
// only one Python interpreter is supported (erroring if a nontrivial
|
||||
// interpreter tag is attempted to be set).
|
||||
//
|
||||
// The difficulty with this scheme is we need to maintain an out-of-line table
|
||||
// to get at the PyInterpreters so that we can do virtual method calls on them,
|
||||
// and registration/deregistration to this table must be done in a thread safe
|
||||
// manner. This can be easily done if the number of possible PyInterpreters is
|
||||
// small enough (e.g., 8-bit integer) by simply preallocating an array of
|
||||
// sufficient size to hold all possible interpreters. Surely 128 threads is
|
||||
// more than enough for anyone!
|
||||
//
|
||||
// I didn't decide to do this technique at the moment, because the extra word
|
||||
// added by the PyInterpreter tag takes us to 24 words, which means that we
|
||||
// still fit inside three eight word cache lines. If you need to penny pinch
|
||||
// another word consider doing this!
|
||||
|
||||
struct C10_API PyInterpreterVTable {
|
||||
virtual ~PyInterpreterVTable() = default;
|
||||
|
||||
// Report the name of this interpreter
|
||||
virtual std::string name() const = 0;
|
||||
|
||||
// Run Py_INCREF on a PyObject.
|
||||
virtual void incref(PyObject* pyobj) const = 0;
|
||||
// Run Py_DECREF on a PyObject. We DO NOT assume the GIL is held on call.
|
||||
virtual void decref(PyObject* pyobj) const = 0;
|
||||
// Run PyUnstable_TryIncRef on a PyObject if it's not NULL.
|
||||
virtual bool try_incref(const c10::impl::PyObjectSlot& pyobj_slot) const = 0;
|
||||
// Run Py_REFCNT on a PyObject.
|
||||
virtual size_t refcnt(PyObject* pyobj) const = 0;
|
||||
|
||||
// Perform a detach by deferring to the __torch_dispatch__ implementation of
|
||||
// detach, which will also arrange for the PyObject to get copied in this
|
||||
// situation
|
||||
virtual c10::intrusive_ptr<TensorImpl> detach(
|
||||
const TensorImpl* self) const = 0;
|
||||
|
||||
// Invoke the Python boxed fallback dispatch to go back into Python
|
||||
virtual void dispatch(const c10::OperatorHandle& op, torch::jit::Stack* stack)
|
||||
const = 0;
|
||||
|
||||
virtual void reportErrorCallback(PyObject* callback, DispatchKey key)
|
||||
const = 0;
|
||||
|
||||
// This is only invoked in the multipy/torchdeploy // codespell:ignore multipy
|
||||
// situation from pythonOpRegistrationTrampoline; this lets us get to the
|
||||
// Python interpreter to actually find the appropriate Python op registration
|
||||
// entry to call.
|
||||
virtual void python_op_registration_trampoline(
|
||||
const c10::OperatorHandle& op,
|
||||
c10::DispatchKey,
|
||||
c10::DispatchKeySet keyset,
|
||||
torch::jit::Stack* stack,
|
||||
bool with_keyset,
|
||||
bool with_op) const = 0;
|
||||
|
||||
virtual void throw_abstract_impl_not_imported_error(
|
||||
std::string opname,
|
||||
const char* pymodule,
|
||||
const char* context) const = 0;
|
||||
|
||||
// Invoke the Python dispatcher to handle this call
|
||||
virtual void python_dispatcher(
|
||||
const c10::OperatorHandle& op,
|
||||
c10::DispatchKeySet,
|
||||
torch::jit::Stack* stack) const = 0;
|
||||
|
||||
virtual bool is_contiguous(const TensorImpl* self, at::MemoryFormat)
|
||||
const = 0;
|
||||
virtual c10::SymBool sym_is_contiguous(
|
||||
const TensorImpl* self,
|
||||
at::MemoryFormat) const = 0;
|
||||
virtual bool is_strides_like(const TensorImpl* self, at::MemoryFormat)
|
||||
const = 0;
|
||||
virtual bool is_non_overlapping_and_dense(const TensorImpl* self) const = 0;
|
||||
virtual c10::Device device(const TensorImpl* self) const = 0;
|
||||
virtual int64_t dim(const TensorImpl* self) const = 0;
|
||||
virtual c10::IntArrayRef strides(const TensorImpl* self) const = 0;
|
||||
virtual c10::IntArrayRef sizes(const TensorImpl* self) const = 0;
|
||||
virtual c10::SymIntArrayRef sym_sizes(const TensorImpl* self) const = 0;
|
||||
virtual c10::Layout layout(const TensorImpl* self) const = 0;
|
||||
virtual int64_t numel(const TensorImpl* self) const = 0;
|
||||
virtual c10::SymInt sym_numel(const TensorImpl* self) const = 0;
|
||||
virtual c10::SymIntArrayRef sym_strides(const TensorImpl* self) const = 0;
|
||||
virtual c10::SymInt sym_storage_offset(const TensorImpl* self) const = 0;
|
||||
|
||||
virtual void trace_gpu_event_creation(
|
||||
c10::DeviceType device_type,
|
||||
uintptr_t event) const = 0;
|
||||
virtual void trace_gpu_event_deletion(
|
||||
c10::DeviceType device_type,
|
||||
uintptr_t event) const = 0;
|
||||
virtual void trace_gpu_event_record(
|
||||
c10::DeviceType device_type,
|
||||
uintptr_t event,
|
||||
uintptr_t stream) const = 0;
|
||||
virtual void trace_gpu_event_wait(
|
||||
c10::DeviceType device_type,
|
||||
uintptr_t event,
|
||||
uintptr_t stream) const = 0;
|
||||
virtual void trace_gpu_memory_allocation(
|
||||
c10::DeviceType device_type,
|
||||
uintptr_t ptr) const = 0;
|
||||
virtual void trace_gpu_memory_deallocation(
|
||||
c10::DeviceType device_type,
|
||||
uintptr_t ptr) const = 0;
|
||||
virtual void trace_gpu_stream_creation(
|
||||
c10::DeviceType device_type,
|
||||
uintptr_t stream) const = 0;
|
||||
virtual void trace_gpu_device_synchronization(
|
||||
c10::DeviceType device_type) const = 0;
|
||||
virtual void trace_gpu_stream_synchronization(
|
||||
c10::DeviceType device_type,
|
||||
uintptr_t stream) const = 0;
|
||||
virtual void trace_gpu_event_synchronization(
|
||||
c10::DeviceType device_type,
|
||||
uintptr_t event) const = 0;
|
||||
|
||||
virtual void reset_backward_hooks(const TensorImpl* self) const = 0;
|
||||
};
|
||||
|
||||
struct C10_API PyInterpreter {
|
||||
const PyInterpreterVTable* vtable_;
|
||||
|
||||
PyInterpreter(const PyInterpreterVTable* vtable) : vtable_(vtable) {}
|
||||
|
||||
const PyInterpreterVTable& operator*() const noexcept {
|
||||
return *vtable_;
|
||||
}
|
||||
const PyInterpreterVTable* operator->() const noexcept {
|
||||
return vtable_;
|
||||
}
|
||||
|
||||
// Disarm this PyInterpreter, making all of its methods noops.
|
||||
// The vtable pointer is not an atomic at the moment, which means
|
||||
// a disarm() invocation that is concurrent with active destructors
|
||||
// is not thread safe and will trigger TSAN. My hope is that this
|
||||
// situations doesn't ever actually happen; tensor destruction should
|
||||
// quiesce when a dlclose happens, and any long lived tensors whose
|
||||
// destructors would be disarmed here only begin the destruction process
|
||||
// on process shutdown (long after the dlclose has occurred).
|
||||
void disarm() noexcept;
|
||||
};
|
||||
|
||||
} // namespace c10::impl
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/impl/PyInterpreter.h>
|
||||
#include <c10/macros/Export.h>
|
||||
#include <c10/util/Registry.h>
|
||||
#include <memory>
|
||||
|
||||
namespace c10::impl {
|
||||
|
||||
// Minimal interface for PyInterpreter hooks
|
||||
struct C10_API PyInterpreterHooksInterface {
|
||||
virtual ~PyInterpreterHooksInterface() = default;
|
||||
|
||||
// Get the PyInterpreter instance
|
||||
// Stub implementation throws error when Python is not available
|
||||
virtual PyInterpreter* getPyInterpreter() const {
|
||||
TORCH_CHECK(
|
||||
false,
|
||||
"PyTorch was compiled without Python support. "
|
||||
"Cannot access Python interpreter from C++.");
|
||||
}
|
||||
};
|
||||
|
||||
struct C10_API PyInterpreterHooksArgs{};
|
||||
|
||||
C10_DECLARE_REGISTRY(
|
||||
PyInterpreterHooksRegistry,
|
||||
PyInterpreterHooksInterface,
|
||||
PyInterpreterHooksArgs);
|
||||
|
||||
#define REGISTER_PYTHON_HOOKS(clsname) \
|
||||
C10_REGISTER_CLASS(PyInterpreterHooksRegistry, clsname, clsname)
|
||||
|
||||
// Get the global PyInterpreter hooks instance
|
||||
C10_API const PyInterpreterHooksInterface& getPyInterpreterHooks();
|
||||
|
||||
// Helper function to get the global interpreter
|
||||
C10_API PyInterpreter* getGlobalPyInterpreter();
|
||||
|
||||
} // namespace c10::impl
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,70 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/impl/HermeticPyObjectTLS.h>
|
||||
#include <c10/core/impl/PyInterpreter.h>
|
||||
#include <c10/core/impl/PyInterpreterHooks.h>
|
||||
#include <c10/util/python_stub.h>
|
||||
#include <optional>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace torch::utils {
|
||||
class PyObjectPreservation;
|
||||
}
|
||||
|
||||
namespace c10::impl {
|
||||
|
||||
struct C10_API PyObjectSlot {
|
||||
public:
|
||||
PyObjectSlot() : pyobj_interpreter_(nullptr), pyobj_(nullptr) {}
|
||||
|
||||
// Query the PyObject interpreter. This may return null if there is no
|
||||
// interpreter.
|
||||
PyInterpreter* pyobj_interpreter() const {
|
||||
return pyobj_interpreter_.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
PyInterpreter& load_pyobj_interpreter() const {
|
||||
auto interpreter = pyobj_interpreter_.load(std::memory_order_acquire);
|
||||
TORCH_INTERNAL_ASSERT(
|
||||
interpreter, "cannot access PyObject for Tensor - no interpreter set");
|
||||
return *interpreter;
|
||||
}
|
||||
|
||||
PyObject* load_pyobj() const {
|
||||
return pyobj_.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
void store_pyobj(PyObject* obj) {
|
||||
pyobj_.store(obj, std::memory_order_release);
|
||||
}
|
||||
|
||||
bool has_unique_reference() const {
|
||||
PyObject* pyobj = load_pyobj();
|
||||
return pyobj != nullptr && load_pyobj_interpreter()->refcnt(pyobj) == 1;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
pyobj_.store(nullptr, std::memory_order_relaxed);
|
||||
pyobj_interpreter_.store(nullptr, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
private:
|
||||
// This is now always the global interpreter if the PyObject is set.
|
||||
// Maybe we can remove this field some day...
|
||||
std::atomic<PyInterpreter*> pyobj_interpreter_;
|
||||
|
||||
// The PyObject representing this Tensor or nullptr. Ownership is managed
|
||||
// by intrusive_ptr. By the time the PyObjectSlot is destroyed, this
|
||||
// reference is already dead.
|
||||
std::atomic<PyObject*> pyobj_;
|
||||
|
||||
friend class torch::utils::PyObjectPreservation;
|
||||
};
|
||||
|
||||
} // namespace c10::impl
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/impl/PyInterpreter.h>
|
||||
#include <c10/macros/Export.h>
|
||||
|
||||
namespace c10::impl {
|
||||
|
||||
struct C10_API PythonDispatcherTLS {
|
||||
static void set_state(PyInterpreter* state);
|
||||
static PyInterpreter* get_state();
|
||||
static void reset_state();
|
||||
};
|
||||
|
||||
struct C10_API DisablePythonDispatcher {
|
||||
DisablePythonDispatcher() : old_(PythonDispatcherTLS::get_state()) {
|
||||
PythonDispatcherTLS::set_state({});
|
||||
}
|
||||
|
||||
DisablePythonDispatcher(DisablePythonDispatcher&& other) = delete;
|
||||
DisablePythonDispatcher(const DisablePythonDispatcher&) = delete;
|
||||
DisablePythonDispatcher& operator=(const DisablePythonDispatcher&) = delete;
|
||||
DisablePythonDispatcher& operator=(DisablePythonDispatcher&&) = delete;
|
||||
~DisablePythonDispatcher() {
|
||||
PythonDispatcherTLS::set_state(old_);
|
||||
}
|
||||
PyInterpreter* old_;
|
||||
};
|
||||
|
||||
} // namespace c10::impl
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
+336
@@ -0,0 +1,336 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
#include <c10/macros/Macros.h>
|
||||
#include <c10/util/ArrayRef.h>
|
||||
#include <c10/util/SmallVector.h>
|
||||
|
||||
#define C10_SIZES_AND_STRIDES_MAX_INLINE_SIZE 5
|
||||
|
||||
namespace c10::impl {
|
||||
|
||||
// Packed container for TensorImpl sizes and strides.
|
||||
// This design improves on the previous approach of using a pair of
|
||||
// c10::SmallVector<int64_t, 5> by specializing for the operations we
|
||||
// actually use and enforcing that the number of sizes is the same as
|
||||
// the number of strides. The memory layout is as follows:
|
||||
//
|
||||
// 1 size_t for the size
|
||||
// 5 eightbytes of inline sizes and 5 eightbytes of inline strides, OR pointer
|
||||
// to out-of-line array
|
||||
class C10_API SizesAndStrides {
|
||||
public:
|
||||
// TODO: different iterator types for sizes & strides to prevent
|
||||
// mixing the two accidentally.
|
||||
using sizes_iterator = int64_t*;
|
||||
using sizes_const_iterator = const int64_t*;
|
||||
using strides_iterator = int64_t*;
|
||||
using strides_const_iterator = const int64_t*;
|
||||
|
||||
SizesAndStrides() {
|
||||
size_at_unchecked(0) = 0;
|
||||
stride_at_unchecked(0) = 1;
|
||||
}
|
||||
|
||||
~SizesAndStrides() {
|
||||
if (C10_UNLIKELY(!isInline())) {
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
|
||||
free(outOfLineStorage_);
|
||||
}
|
||||
}
|
||||
|
||||
SizesAndStrides(const SizesAndStrides& rhs) : size_(rhs.size_) {
|
||||
if (C10_LIKELY(rhs.isInline())) {
|
||||
copyDataInline(rhs);
|
||||
} else {
|
||||
allocateOutOfLineStorage(size_);
|
||||
copyDataOutline(rhs);
|
||||
}
|
||||
}
|
||||
|
||||
bool operator==(const SizesAndStrides& other) const {
|
||||
if (size_ != other.size_) {
|
||||
return false;
|
||||
}
|
||||
return !(
|
||||
isInline()
|
||||
? std::memcmp(
|
||||
inlineStorage_, other.inlineStorage_, sizeof(inlineStorage_))
|
||||
: std::memcmp(
|
||||
outOfLineStorage_,
|
||||
other.outOfLineStorage_,
|
||||
storageBytes(size_)));
|
||||
}
|
||||
|
||||
bool operator!=(const SizesAndStrides& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
SizesAndStrides& operator=(const SizesAndStrides& rhs) {
|
||||
if (this == &rhs) {
|
||||
return *this;
|
||||
}
|
||||
if (C10_LIKELY(rhs.isInline())) {
|
||||
if (C10_UNLIKELY(!isInline())) {
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
|
||||
free(outOfLineStorage_);
|
||||
}
|
||||
copyDataInline(rhs);
|
||||
} else {
|
||||
if (isInline()) {
|
||||
allocateOutOfLineStorage(rhs.size_);
|
||||
} else {
|
||||
resizeOutOfLineStorage(rhs.size_);
|
||||
}
|
||||
copyDataOutline(rhs);
|
||||
}
|
||||
size_ = rhs.size_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Move from rhs. rhs.size() == 0 afterwards.
|
||||
SizesAndStrides(SizesAndStrides&& rhs) noexcept : size_(rhs.size_) {
|
||||
if (C10_LIKELY(isInline())) {
|
||||
memcpy(inlineStorage_, rhs.inlineStorage_, sizeof(inlineStorage_));
|
||||
} else {
|
||||
outOfLineStorage_ = rhs.outOfLineStorage_;
|
||||
rhs.outOfLineStorage_ = nullptr;
|
||||
}
|
||||
|
||||
rhs.size_ = 0;
|
||||
}
|
||||
|
||||
// Move from rhs. rhs.size() == 0 afterwards.
|
||||
SizesAndStrides& operator=(SizesAndStrides&& rhs) noexcept {
|
||||
if (this == &rhs) {
|
||||
return *this;
|
||||
}
|
||||
if (C10_LIKELY(rhs.isInline())) {
|
||||
if (C10_UNLIKELY(!isInline())) {
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
|
||||
free(outOfLineStorage_);
|
||||
}
|
||||
copyDataInline(rhs);
|
||||
} else {
|
||||
// They're outline. We're going to steal their vector.
|
||||
if (!isInline()) {
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
|
||||
free(outOfLineStorage_);
|
||||
}
|
||||
outOfLineStorage_ = rhs.outOfLineStorage_;
|
||||
rhs.outOfLineStorage_ = nullptr;
|
||||
}
|
||||
size_ = rhs.size_;
|
||||
rhs.size_ = 0;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
size_t size() const noexcept {
|
||||
return size_;
|
||||
}
|
||||
|
||||
const int64_t* sizes_data() const noexcept {
|
||||
if (C10_LIKELY(isInline())) {
|
||||
return &inlineStorage_[0];
|
||||
} else {
|
||||
return &outOfLineStorage_[0];
|
||||
}
|
||||
}
|
||||
|
||||
int64_t* sizes_data() noexcept {
|
||||
if (C10_LIKELY(isInline())) {
|
||||
return &inlineStorage_[0];
|
||||
} else {
|
||||
return &outOfLineStorage_[0];
|
||||
}
|
||||
}
|
||||
|
||||
sizes_const_iterator sizes_begin() const noexcept {
|
||||
return sizes_data();
|
||||
}
|
||||
|
||||
sizes_iterator sizes_begin() noexcept {
|
||||
return sizes_data();
|
||||
}
|
||||
|
||||
sizes_const_iterator sizes_end() const noexcept {
|
||||
return sizes_begin() + size();
|
||||
}
|
||||
|
||||
sizes_iterator sizes_end() noexcept {
|
||||
return sizes_begin() + size();
|
||||
}
|
||||
|
||||
IntArrayRef sizes_arrayref() const noexcept {
|
||||
return IntArrayRef{sizes_data(), size()};
|
||||
}
|
||||
|
||||
void set_sizes(IntArrayRef newSizes) {
|
||||
resize(newSizes.size());
|
||||
std::copy(newSizes.begin(), newSizes.end(), sizes_begin());
|
||||
}
|
||||
|
||||
void set_strides(IntArrayRef strides) {
|
||||
TORCH_INTERNAL_ASSERT(strides.size() == size());
|
||||
std::copy(strides.begin(), strides.end(), strides_begin());
|
||||
}
|
||||
|
||||
const int64_t* strides_data() const noexcept {
|
||||
if (C10_LIKELY(isInline())) {
|
||||
return &inlineStorage_[C10_SIZES_AND_STRIDES_MAX_INLINE_SIZE];
|
||||
} else {
|
||||
return &outOfLineStorage_[size()];
|
||||
}
|
||||
}
|
||||
|
||||
int64_t* strides_data() noexcept {
|
||||
if (C10_LIKELY(isInline())) {
|
||||
return &inlineStorage_[C10_SIZES_AND_STRIDES_MAX_INLINE_SIZE];
|
||||
} else {
|
||||
return &outOfLineStorage_[size()];
|
||||
}
|
||||
}
|
||||
|
||||
strides_const_iterator strides_begin() const noexcept {
|
||||
if (C10_LIKELY(isInline())) {
|
||||
return &inlineStorage_[C10_SIZES_AND_STRIDES_MAX_INLINE_SIZE];
|
||||
} else {
|
||||
return &outOfLineStorage_[size()];
|
||||
}
|
||||
}
|
||||
|
||||
strides_iterator strides_begin() noexcept {
|
||||
if (C10_LIKELY(isInline())) {
|
||||
return &inlineStorage_[C10_SIZES_AND_STRIDES_MAX_INLINE_SIZE];
|
||||
} else {
|
||||
return &outOfLineStorage_[size()];
|
||||
}
|
||||
}
|
||||
|
||||
strides_const_iterator strides_end() const noexcept {
|
||||
return strides_begin() + size();
|
||||
}
|
||||
|
||||
strides_iterator strides_end() noexcept {
|
||||
return strides_begin() + size();
|
||||
}
|
||||
|
||||
IntArrayRef strides_arrayref() const noexcept {
|
||||
return IntArrayRef{strides_data(), size()};
|
||||
}
|
||||
|
||||
// Size accessors.
|
||||
int64_t size_at(size_t idx) const noexcept {
|
||||
assert(idx < size());
|
||||
return sizes_data()[idx];
|
||||
}
|
||||
|
||||
int64_t& size_at(size_t idx) noexcept {
|
||||
assert(idx < size());
|
||||
return sizes_data()[idx];
|
||||
}
|
||||
|
||||
int64_t size_at_unchecked(size_t idx) const noexcept {
|
||||
return sizes_data()[idx];
|
||||
}
|
||||
|
||||
int64_t& size_at_unchecked(size_t idx) noexcept {
|
||||
return sizes_data()[idx];
|
||||
}
|
||||
|
||||
// Size accessors.
|
||||
int64_t stride_at(size_t idx) const noexcept {
|
||||
assert(idx < size());
|
||||
return strides_data()[idx];
|
||||
}
|
||||
|
||||
int64_t& stride_at(size_t idx) noexcept {
|
||||
assert(idx < size());
|
||||
return strides_data()[idx];
|
||||
}
|
||||
|
||||
int64_t stride_at_unchecked(size_t idx) const noexcept {
|
||||
return strides_data()[idx];
|
||||
}
|
||||
|
||||
int64_t& stride_at_unchecked(size_t idx) noexcept {
|
||||
return strides_data()[idx];
|
||||
}
|
||||
|
||||
void resize(size_t newSize) {
|
||||
const auto oldSize = size();
|
||||
if (newSize == oldSize) {
|
||||
return;
|
||||
}
|
||||
if (C10_LIKELY(
|
||||
newSize <= C10_SIZES_AND_STRIDES_MAX_INLINE_SIZE && isInline())) {
|
||||
if (oldSize < newSize) {
|
||||
const auto bytesToZero =
|
||||
(newSize - oldSize) * sizeof(inlineStorage_[0]);
|
||||
memset(&inlineStorage_[oldSize], 0, bytesToZero);
|
||||
memset(
|
||||
&inlineStorage_[C10_SIZES_AND_STRIDES_MAX_INLINE_SIZE + oldSize],
|
||||
0,
|
||||
bytesToZero);
|
||||
}
|
||||
size_ = newSize;
|
||||
} else {
|
||||
resizeSlowPath(newSize, oldSize);
|
||||
}
|
||||
}
|
||||
|
||||
void resizeSlowPath(size_t newSize, size_t oldSize);
|
||||
|
||||
private:
|
||||
bool isInline() const noexcept {
|
||||
return size_ <= C10_SIZES_AND_STRIDES_MAX_INLINE_SIZE;
|
||||
}
|
||||
|
||||
void copyDataInline(const SizesAndStrides& rhs) {
|
||||
TORCH_INTERNAL_ASSERT_DEBUG_ONLY(rhs.isInline());
|
||||
memcpy(inlineStorage_, rhs.inlineStorage_, sizeof(inlineStorage_));
|
||||
}
|
||||
|
||||
static size_t storageBytes(size_t size) noexcept {
|
||||
return size * 2 * sizeof(int64_t);
|
||||
}
|
||||
|
||||
void allocateOutOfLineStorage(size_t size) {
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
|
||||
outOfLineStorage_ = static_cast<int64_t*>(malloc(storageBytes(size)));
|
||||
TORCH_CHECK(
|
||||
outOfLineStorage_,
|
||||
"Could not allocate memory for Tensor SizesAndStrides!");
|
||||
}
|
||||
|
||||
void resizeOutOfLineStorage(size_t newSize) {
|
||||
TORCH_INTERNAL_ASSERT_DEBUG_ONLY(!isInline());
|
||||
outOfLineStorage_ = static_cast<int64_t*>(
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
|
||||
realloc(outOfLineStorage_, storageBytes(newSize)));
|
||||
TORCH_CHECK(
|
||||
outOfLineStorage_,
|
||||
"Could not allocate memory for Tensor SizesAndStrides!");
|
||||
}
|
||||
|
||||
void copyDataOutline(const SizesAndStrides& rhs) noexcept {
|
||||
memcpy(outOfLineStorage_, rhs.outOfLineStorage_, storageBytes(rhs.size_));
|
||||
}
|
||||
|
||||
size_t size_{1};
|
||||
union {
|
||||
int64_t* outOfLineStorage_;
|
||||
// NOLINTNEXTLINE(*c-array*)
|
||||
int64_t inlineStorage_[C10_SIZES_AND_STRIDES_MAX_INLINE_SIZE * 2]{};
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace c10::impl
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/SafePyObject.h>
|
||||
#include <c10/macros/Export.h>
|
||||
|
||||
namespace c10::impl {
|
||||
|
||||
enum class TorchDispatchModeKey : int8_t {
|
||||
FAKE,
|
||||
PROXY,
|
||||
FUNCTIONAL,
|
||||
NUM_MODE_KEYS
|
||||
};
|
||||
|
||||
using PyObject_TorchDispatchMode = SafePyObjectT<TorchDispatchModeKey>;
|
||||
|
||||
struct C10_API TorchDispatchModeTLS {
|
||||
// This API is NOT invariant safe.
|
||||
// It must not take in an infra mode that uses TorchDispatchModeKey
|
||||
// If you're pushing an infra mode onto the stack, we expect
|
||||
// you to use set_mode
|
||||
static void push_non_infra_mode_onto_stack(
|
||||
std::shared_ptr<PyObject_TorchDispatchMode> mode);
|
||||
// Pops the top mode of the stack,
|
||||
// giving precedence to user modes before attempting to pop
|
||||
// any infra modes
|
||||
static const std::shared_ptr<PyObject_TorchDispatchMode> pop_stack();
|
||||
// Returns the highest-priority infra mode on the stack,
|
||||
// along with its mode key.
|
||||
static const std::
|
||||
tuple<std::shared_ptr<PyObject_TorchDispatchMode>, TorchDispatchModeKey>
|
||||
pop_highest_infra_mode();
|
||||
|
||||
static const std::shared_ptr<PyObject_TorchDispatchMode>& get_stack_at(
|
||||
int64_t idx);
|
||||
static int64_t stack_len();
|
||||
|
||||
static const std::optional<std::shared_ptr<PyObject_TorchDispatchMode>>
|
||||
get_mode(TorchDispatchModeKey mode_key);
|
||||
static const std::optional<std::shared_ptr<PyObject_TorchDispatchMode>>
|
||||
unset_mode(TorchDispatchModeKey mode_key);
|
||||
static void set_mode(
|
||||
const std::shared_ptr<PyObject_TorchDispatchMode>& mode,
|
||||
TorchDispatchModeKey mode_key);
|
||||
|
||||
static const TorchDispatchModeTLS& get_state();
|
||||
static void set_state(TorchDispatchModeTLS state);
|
||||
|
||||
static bool any_modes_set(bool skip_infra_modes = false);
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<PyObject_TorchDispatchMode>> stack_;
|
||||
// Users are allowed to push multiple ProxyTorchDispatchMode objects onto the
|
||||
// stack
|
||||
// However, we only allow a single FakeTensorMode onto the stack at a time
|
||||
// (Pushing additional FakeTensorModes onto the stack is a no-op)
|
||||
std::array<
|
||||
std::optional<std::shared_ptr<PyObject_TorchDispatchMode>>,
|
||||
static_cast<size_t>(TorchDispatchModeKey::NUM_MODE_KEYS)>
|
||||
infra_modes_;
|
||||
};
|
||||
|
||||
C10_API bool dispatch_mode_enabled();
|
||||
|
||||
C10_API std::string to_string(TorchDispatchModeKey mode_key);
|
||||
|
||||
} // namespace c10::impl
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/impl/DeviceGuardImplInterface.h>
|
||||
|
||||
namespace c10::impl {
|
||||
|
||||
/**
|
||||
* An implementation of DeviceGuardImplInterface which delegates
|
||||
* to virtual dispatch on the DeviceGuardImpl registry.
|
||||
*/
|
||||
class VirtualGuardImpl final : public DeviceGuardImplInterface {
|
||||
public:
|
||||
VirtualGuardImpl(DeviceType device_type)
|
||||
: impl_(getDeviceGuardImpl(device_type)) {}
|
||||
// This constructor exists purely for testing
|
||||
VirtualGuardImpl(const DeviceGuardImplInterface* impl) : impl_(impl) {}
|
||||
|
||||
// Copying and moving is OK!
|
||||
VirtualGuardImpl(const VirtualGuardImpl&) = default;
|
||||
VirtualGuardImpl& operator=(const VirtualGuardImpl&) = default;
|
||||
VirtualGuardImpl(VirtualGuardImpl&&) noexcept = default;
|
||||
VirtualGuardImpl& operator=(VirtualGuardImpl&&) noexcept = default;
|
||||
~VirtualGuardImpl() override = default;
|
||||
|
||||
DeviceType type() const override {
|
||||
return impl_->type();
|
||||
}
|
||||
Device exchangeDevice(Device d) const override {
|
||||
return impl_->exchangeDevice(d);
|
||||
}
|
||||
Device getDevice() const override {
|
||||
return impl_->getDevice();
|
||||
}
|
||||
void setDevice(Device d) const override {
|
||||
impl_->setDevice(d);
|
||||
}
|
||||
void uncheckedSetDevice(Device d) const noexcept override {
|
||||
impl_->uncheckedSetDevice(d);
|
||||
}
|
||||
Stream getStream(Device d) const override {
|
||||
return impl_->getStream(d);
|
||||
}
|
||||
Stream getNewStream(Device d, int priority = 0) const override {
|
||||
return impl_->getNewStream(d, priority);
|
||||
}
|
||||
Stream getDefaultStream(Device d) const override {
|
||||
return impl_->getDefaultStream(d);
|
||||
}
|
||||
Stream getStreamFromGlobalPool(Device d, bool isHighPriority = false)
|
||||
const override {
|
||||
return impl_->getStreamFromGlobalPool(d, isHighPriority);
|
||||
}
|
||||
Stream exchangeStream(Stream s) const override {
|
||||
return impl_->exchangeStream(s);
|
||||
}
|
||||
void* getStreamNativeHandle(const Stream s) const override {
|
||||
return impl_->getStreamNativeHandle(s);
|
||||
}
|
||||
DeviceIndex deviceCount() const noexcept override {
|
||||
return impl_->deviceCount();
|
||||
}
|
||||
|
||||
DeviceCapability getDeviceCapability(Device d) const override {
|
||||
return impl_->getDeviceCapability(d);
|
||||
}
|
||||
|
||||
// Event functions
|
||||
void record(
|
||||
void** event,
|
||||
const Stream& stream,
|
||||
const DeviceIndex device_index,
|
||||
const EventFlag flag) const override {
|
||||
impl_->record(event, stream, device_index, flag);
|
||||
}
|
||||
void block(void* event, const Stream& stream) const override {
|
||||
impl_->block(event, stream);
|
||||
}
|
||||
bool queryEvent(void* event) const override {
|
||||
return impl_->queryEvent(event);
|
||||
}
|
||||
void destroyEvent(void* event, const DeviceIndex device_index)
|
||||
const noexcept override {
|
||||
impl_->destroyEvent(event, device_index);
|
||||
}
|
||||
|
||||
bool queryStream(const Stream& stream) const override {
|
||||
return impl_->queryStream(stream);
|
||||
}
|
||||
void synchronizeStream(const Stream& stream) const override {
|
||||
impl_->synchronizeStream(stream);
|
||||
}
|
||||
bool isStreamCapturing(const Stream& stream) const override {
|
||||
return impl_->isStreamCapturing(stream);
|
||||
}
|
||||
|
||||
void recordDataPtrOnStream(const c10::DataPtr& data_ptr, const Stream& stream)
|
||||
const override {
|
||||
impl_->recordDataPtrOnStream(data_ptr, stream);
|
||||
}
|
||||
|
||||
double elapsedTime(void* event1, void* event2, const DeviceIndex device_index)
|
||||
const override {
|
||||
return impl_->elapsedTime(event1, event2, device_index);
|
||||
}
|
||||
|
||||
void synchronizeEvent(void* event) const override {
|
||||
impl_->synchronizeEvent(event);
|
||||
}
|
||||
|
||||
void synchronizeDevice(const DeviceIndex device_index) const override {
|
||||
impl_->synchronizeDevice(device_index);
|
||||
}
|
||||
|
||||
private:
|
||||
const DeviceGuardImplInterface* impl_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace c10::impl
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,32 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/macros/Export.h>
|
||||
#include <c10/macros/Macros.h>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
C10_API void* alloc_cpu(size_t nbytes);
|
||||
C10_API void free_cpu(void* data);
|
||||
|
||||
#if defined(__linux__) && !defined(__ANDROID__)
|
||||
C10_API size_t c10_compute_alignment(size_t nbytes);
|
||||
#endif
|
||||
|
||||
#ifdef USE_MIMALLOC_ON_MKL
|
||||
namespace mi_malloc_wrapper {
|
||||
C10_API void* c10_mi_malloc(size_t size);
|
||||
C10_API void* c10_mi_calloc(size_t count, size_t size);
|
||||
C10_API void* c10_mi_realloc(void* p, size_t newsize);
|
||||
C10_API void* c10_mi_malloc_aligned(size_t size, size_t alignment);
|
||||
C10_API void c10_mi_free(void* p);
|
||||
} // namespace mi_malloc_wrapper
|
||||
#endif
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,125 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <c10/macros/Export.h>
|
||||
#include <c10/util/Registry.h>
|
||||
#include <c10/util/numa.h>
|
||||
#include <c10/util/thread_name.h>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
class C10_API TaskThreadPoolBase {
|
||||
public:
|
||||
virtual void run(std::function<void()> func) = 0;
|
||||
|
||||
virtual size_t size() const = 0;
|
||||
|
||||
/**
|
||||
* The number of available (i.e. idle) threads in this thread pool.
|
||||
*/
|
||||
virtual size_t numAvailable() const = 0;
|
||||
|
||||
/**
|
||||
* Check if the current thread is from the thread pool.
|
||||
*/
|
||||
virtual bool inThreadPool() const = 0;
|
||||
|
||||
virtual ~TaskThreadPoolBase() noexcept = default;
|
||||
|
||||
static size_t defaultNumThreads();
|
||||
};
|
||||
|
||||
class C10_API ThreadPool : public c10::TaskThreadPoolBase {
|
||||
protected:
|
||||
struct task_element_t {
|
||||
bool run_with_id;
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
|
||||
const std::function<void()> no_id;
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
|
||||
const std::function<void(std::size_t)> with_id;
|
||||
|
||||
explicit task_element_t(std::function<void()> f)
|
||||
: run_with_id(false), no_id(std::move(f)), with_id(nullptr) {}
|
||||
explicit task_element_t(std::function<void(std::size_t)> f)
|
||||
: run_with_id(true), no_id(nullptr), with_id(std::move(f)) {}
|
||||
};
|
||||
|
||||
std::queue<task_element_t> tasks_;
|
||||
std::vector<std::thread> threads_;
|
||||
mutable std::mutex mutex_;
|
||||
std::condition_variable condition_;
|
||||
std::condition_variable completed_;
|
||||
std::atomic_bool running_;
|
||||
bool complete_;
|
||||
std::size_t available_;
|
||||
std::size_t total_;
|
||||
int numa_node_id_;
|
||||
|
||||
public:
|
||||
ThreadPool() = delete;
|
||||
|
||||
explicit ThreadPool(
|
||||
int pool_size,
|
||||
int numa_node_id = -1,
|
||||
const std::function<void()>& init_thread = nullptr);
|
||||
|
||||
~ThreadPool() override;
|
||||
|
||||
size_t size() const override;
|
||||
|
||||
size_t numAvailable() const override;
|
||||
|
||||
bool inThreadPool() const override;
|
||||
|
||||
void run(std::function<void()> func) override;
|
||||
|
||||
template <typename Task>
|
||||
void runTaskWithID(Task task) {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
|
||||
// Set task and signal condition variable so that a worker thread will
|
||||
// wake up and use the task.
|
||||
tasks_.emplace(static_cast<std::function<void(std::size_t)>>(task));
|
||||
complete_ = false;
|
||||
condition_.notify_one();
|
||||
}
|
||||
|
||||
/// @brief Wait for queue to be empty
|
||||
void waitWorkComplete();
|
||||
|
||||
private:
|
||||
// @brief Entry point for pool threads.
|
||||
void main_loop(std::size_t index);
|
||||
};
|
||||
|
||||
class C10_API TaskThreadPool : public c10::ThreadPool {
|
||||
public:
|
||||
explicit TaskThreadPool(int pool_size, int numa_node_id = -1)
|
||||
: ThreadPool(pool_size, numa_node_id, [numa_node_id]() {
|
||||
setThreadName("CaffeTaskThread");
|
||||
NUMABind(numa_node_id);
|
||||
}) {}
|
||||
};
|
||||
|
||||
C10_DECLARE_SHARED_REGISTRY(
|
||||
ThreadPoolRegistry,
|
||||
TaskThreadPoolBase,
|
||||
int,
|
||||
int,
|
||||
bool);
|
||||
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,36 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#ifdef THRUST_DEVICE_LOWER_BOUND_WORKS
|
||||
#include <thrust/binary_search.h>
|
||||
#include <thrust/device_vector.h>
|
||||
#include <thrust/execution_policy.h>
|
||||
#include <thrust/functional.h>
|
||||
#endif
|
||||
namespace c10::cuda {
|
||||
#ifdef THRUST_DEVICE_LOWER_BOUND_WORKS
|
||||
template <typename Iter, typename Scalar>
|
||||
__forceinline__ __device__ Iter
|
||||
lower_bound(Iter start, Iter end, Scalar value) {
|
||||
return thrust::lower_bound(thrust::device, start, end, value);
|
||||
}
|
||||
#else
|
||||
// thrust::lower_bound is broken on device, see
|
||||
// https://github.com/NVIDIA/thrust/issues/1734 Implementation inspired by
|
||||
// https://github.com/pytorch/pytorch/blob/805120ab572efef66425c9f595d9c6c464383336/aten/src/ATen/native/cuda/Bucketization.cu#L28
|
||||
template <typename Iter, typename Scalar>
|
||||
__device__ Iter lower_bound(Iter start, Iter end, Scalar value) {
|
||||
while (start < end) {
|
||||
auto mid = start + ((end - start) >> 1);
|
||||
if (*mid < value) {
|
||||
start = mid + 1;
|
||||
} else {
|
||||
end = mid;
|
||||
}
|
||||
}
|
||||
return end;
|
||||
}
|
||||
#endif // THRUST_DEVICE_LOWER_BOUND_WORKS
|
||||
} // namespace c10::cuda
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
+235
@@ -0,0 +1,235 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/AllocatorConfig.h>
|
||||
#include <c10/cuda/CUDAException.h>
|
||||
#include <c10/cuda/CUDAMacros.h>
|
||||
#include <c10/util/Deprecated.h>
|
||||
#include <c10/util/Exception.h>
|
||||
#include <c10/util/env.h>
|
||||
|
||||
namespace c10::cuda::CUDACachingAllocator {
|
||||
|
||||
enum class Expandable_Segments_Handle_Type : int {
|
||||
UNSPECIFIED = 0,
|
||||
POSIX_FD = 1,
|
||||
FABRIC_HANDLE = 2,
|
||||
};
|
||||
|
||||
// Environment config parser
|
||||
class C10_CUDA_API CUDAAllocatorConfig {
|
||||
public:
|
||||
C10_DEPRECATED_MESSAGE(
|
||||
"c10::cuda::CUDACachingAllocator::CUDAAllocatorConfig::max_split_size() is deprecated. Please use c10::CachingAllocator::AcceleratorAllocatorConfig::max_split_size() instead.")
|
||||
static size_t max_split_size() {
|
||||
return c10::CachingAllocator::AcceleratorAllocatorConfig::max_split_size();
|
||||
}
|
||||
|
||||
C10_DEPRECATED_MESSAGE(
|
||||
"c10::cuda::CUDACachingAllocator::CUDAAllocatorConfig::garbage_collection_threshold() is deprecated. Please use c10::CachingAllocator::AcceleratorAllocatorConfig::garbage_collection_threshold() instead.")
|
||||
static double garbage_collection_threshold() {
|
||||
return c10::CachingAllocator::AcceleratorAllocatorConfig::
|
||||
garbage_collection_threshold();
|
||||
}
|
||||
|
||||
static bool expandable_segments() {
|
||||
bool enabled = c10::CachingAllocator::AcceleratorAllocatorConfig::
|
||||
use_expandable_segments();
|
||||
#if !defined(PYTORCH_C10_DRIVER_API_SUPPORTED) && \
|
||||
(!defined(USE_ROCM) || (ROCM_VERSION < 70000))
|
||||
if (enabled) {
|
||||
TORCH_WARN_ONCE("expandable_segments not supported on this platform")
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
return enabled;
|
||||
#endif
|
||||
}
|
||||
|
||||
static Expandable_Segments_Handle_Type expandable_segments_handle_type() {
|
||||
return instance().m_expandable_segments_handle_type;
|
||||
}
|
||||
|
||||
static void set_expandable_segments_handle_type(
|
||||
Expandable_Segments_Handle_Type handle_type) {
|
||||
instance().m_expandable_segments_handle_type = handle_type;
|
||||
}
|
||||
|
||||
static bool release_lock_on_cudamalloc() {
|
||||
return instance().m_release_lock_on_cudamalloc;
|
||||
}
|
||||
|
||||
static bool graph_capture_record_stream_reuse() {
|
||||
return instance().m_graph_capture_record_stream_reuse;
|
||||
}
|
||||
|
||||
static double per_process_memory_fraction() {
|
||||
return instance().m_per_process_memory_fraction;
|
||||
}
|
||||
|
||||
// When enabled, throws OOM error before calling cudaMalloc if the allocation
|
||||
// would likely fail due to insufficient memory. This provides early failure
|
||||
// with clear error messages instead of letting cudaMalloc fail.
|
||||
static bool throw_on_cudamalloc_oom() {
|
||||
return instance().m_throw_on_cudamalloc_oom;
|
||||
}
|
||||
|
||||
/** Pinned memory allocator settings */
|
||||
static bool pinned_use_cuda_host_register() {
|
||||
return instance().m_pinned_use_cuda_host_register;
|
||||
}
|
||||
|
||||
static size_t pinned_num_register_threads() {
|
||||
return instance().m_pinned_num_register_threads;
|
||||
}
|
||||
|
||||
C10_DEPRECATED_MESSAGE(
|
||||
"c10::cuda::CUDACachingAllocator::CUDAAllocatorConfig::pinned_use_background_threads() is deprecated. Please use c10::CachingAllocator::AcceleratorAllocatorConfig::pinned_use_background_threads() instead.")
|
||||
static bool pinned_use_background_threads() {
|
||||
return c10::CachingAllocator::AcceleratorAllocatorConfig::
|
||||
pinned_use_background_threads();
|
||||
}
|
||||
|
||||
static size_t pinned_reserve_segment_size_mb() {
|
||||
return instance().m_pinned_reserve_segment_size_mb;
|
||||
}
|
||||
|
||||
static size_t pinned_max_register_threads() {
|
||||
// Based on the benchmark results, we see better allocation performance
|
||||
// with 8 threads. However on future systems, we may need more threads
|
||||
// and limiting this to 128 threads.
|
||||
return 128;
|
||||
}
|
||||
|
||||
static bool pinned_free_catch_all() {
|
||||
return instance().m_pinned_free_catch_all;
|
||||
}
|
||||
|
||||
C10_DEPRECATED_MESSAGE(
|
||||
"c10::cuda::CUDACachingAllocator::CUDAAllocatorConfig::roundup_power2_divisions() is deprecated. Please use c10::CachingAllocator::AcceleratorAllocatorConfig::roundup_power2_divisions() instead.")
|
||||
static size_t roundup_power2_divisions(size_t size) {
|
||||
return c10::CachingAllocator::AcceleratorAllocatorConfig::
|
||||
roundup_power2_divisions(size);
|
||||
}
|
||||
|
||||
C10_DEPRECATED_MESSAGE(
|
||||
"c10::cuda::CUDACachingAllocator::CUDAAllocatorConfig::roundup_power2_divisions() is deprecated. Please use c10::CachingAllocator::AcceleratorAllocatorConfig::roundup_power2_divisions() instead.")
|
||||
static std::vector<size_t> roundup_power2_divisions() {
|
||||
return c10::CachingAllocator::AcceleratorAllocatorConfig::
|
||||
roundup_power2_divisions();
|
||||
}
|
||||
|
||||
static size_t max_non_split_rounding_size() {
|
||||
return c10::CachingAllocator::AcceleratorAllocatorConfig::
|
||||
max_non_split_rounding_size();
|
||||
}
|
||||
|
||||
C10_DEPRECATED_MESSAGE(
|
||||
"c10::cuda::CUDACachingAllocator::CUDAAllocatorConfig::last_allocator_settings() is deprecated. Please use c10::CachingAllocator::AcceleratorAllocatorConfig::last_allocator_settings() instead.")
|
||||
static std::string last_allocator_settings() {
|
||||
return c10::CachingAllocator::getAllocatorSettings();
|
||||
}
|
||||
|
||||
static CUDAAllocatorConfig& instance() {
|
||||
static CUDAAllocatorConfig* s_instance = ([]() {
|
||||
auto inst = new CUDAAllocatorConfig();
|
||||
auto env = c10::utils::get_env("PYTORCH_CUDA_ALLOC_CONF");
|
||||
#ifdef USE_ROCM
|
||||
// convenience for ROCm users, allow alternative HIP token
|
||||
if (!env.has_value()) {
|
||||
env = c10::utils::get_env("PYTORCH_HIP_ALLOC_CONF");
|
||||
}
|
||||
#endif
|
||||
// Note: keep the parsing order and logic stable to avoid potential
|
||||
// performance regressions in internal tests.
|
||||
if (!env.has_value()) {
|
||||
env = c10::utils::get_env("PYTORCH_ALLOC_CONF");
|
||||
}
|
||||
if (env.has_value()) {
|
||||
inst->parseArgs(env.value());
|
||||
}
|
||||
return inst;
|
||||
})();
|
||||
return *s_instance;
|
||||
}
|
||||
|
||||
// Use `Construct On First Use Idiom` to avoid `Static Initialization Order`
|
||||
// issue.
|
||||
static const std::unordered_set<std::string>& getKeys() {
|
||||
static std::unordered_set<std::string> keys{
|
||||
"backend",
|
||||
// keep BC for Rocm: `cuda` -> `cud` `a`, to avoid hipify issues
|
||||
// NOLINTBEGIN(bugprone-suspicious-missing-comma,-warnings-as-errors)
|
||||
"release_lock_on_cud"
|
||||
"amalloc",
|
||||
"pinned_use_cud"
|
||||
"a_host_register",
|
||||
// NOLINTEND(bugprone-suspicious-missing-comma,-warnings-as-errors)
|
||||
"release_lock_on_hipmalloc",
|
||||
"pinned_use_hip_host_register",
|
||||
"graph_capture_record_stream_reuse",
|
||||
"pinned_reserve_segment_size_mb",
|
||||
"pinned_num_register_threads",
|
||||
"per_process_memory_fraction",
|
||||
"pinned_free_catch_all",
|
||||
"throw_on_cudamalloc_oom"};
|
||||
return keys;
|
||||
}
|
||||
|
||||
void parseArgs(const std::string& env);
|
||||
|
||||
private:
|
||||
CUDAAllocatorConfig() = default;
|
||||
|
||||
size_t parseAllocatorConfig(
|
||||
const c10::CachingAllocator::ConfigTokenizer& tokenizer,
|
||||
size_t i,
|
||||
bool& used_cudaMallocAsync);
|
||||
size_t parsePinnedUseCudaHostRegister(
|
||||
const c10::CachingAllocator::ConfigTokenizer& tokenizer,
|
||||
size_t i);
|
||||
size_t parsePinnedNumRegisterThreads(
|
||||
const c10::CachingAllocator::ConfigTokenizer& tokenizer,
|
||||
size_t i);
|
||||
size_t parsePinnedReserveSegmentSize(
|
||||
const c10::CachingAllocator::ConfigTokenizer& tokenizer,
|
||||
size_t i);
|
||||
size_t parseGraphCaptureRecordStreamReuse(
|
||||
const c10::CachingAllocator::ConfigTokenizer& tokenizer,
|
||||
size_t i);
|
||||
double parsePerProcessMemoryFraction(
|
||||
const c10::CachingAllocator::ConfigTokenizer& tokenizer,
|
||||
size_t i);
|
||||
size_t parsePinnedFreeCatchAll(
|
||||
const c10::CachingAllocator::ConfigTokenizer& tokenizer,
|
||||
size_t i);
|
||||
size_t parseThrowOnCudaMallocOom(
|
||||
const c10::CachingAllocator::ConfigTokenizer& tokenizer,
|
||||
size_t i);
|
||||
|
||||
std::atomic<size_t> m_pinned_num_register_threads{1};
|
||||
std::atomic<size_t> m_pinned_reserve_segment_size_mb{0};
|
||||
std::atomic<Expandable_Segments_Handle_Type> m_expandable_segments_handle_type
|
||||
#if CUDA_VERSION >= 12030
|
||||
{Expandable_Segments_Handle_Type::UNSPECIFIED};
|
||||
#else
|
||||
{Expandable_Segments_Handle_Type::POSIX_FD};
|
||||
#endif
|
||||
std::atomic<bool> m_release_lock_on_cudamalloc{false};
|
||||
std::atomic<bool> m_pinned_use_cuda_host_register{false};
|
||||
std::atomic<bool> m_graph_capture_record_stream_reuse{false};
|
||||
std::atomic<double> m_per_process_memory_fraction{1.0};
|
||||
std::atomic<bool> m_pinned_free_catch_all{false};
|
||||
// When true, throw OOM error before calling cudaMalloc if allocation would
|
||||
// fail
|
||||
std::atomic<bool> m_throw_on_cudamalloc_oom{false};
|
||||
};
|
||||
|
||||
// Keep this for backwards compatibility
|
||||
using c10::CachingAllocator::setAllocatorSettings;
|
||||
|
||||
} // namespace c10::cuda::CUDACachingAllocator
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
+517
@@ -0,0 +1,517 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/AllocatorConfig.h>
|
||||
#include <c10/core/CachingDeviceAllocator.h>
|
||||
#include <c10/cuda/CUDAAllocatorConfig.h>
|
||||
#include <c10/cuda/CUDAGraphsC10Utils.h>
|
||||
#include <c10/cuda/CUDAMacros.h>
|
||||
#include <c10/cuda/CUDAStream.h>
|
||||
#include <c10/util/Exception.h>
|
||||
#include <c10/util/Registry.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
namespace c10 {
|
||||
|
||||
// Caching allocator will execute every registered callback if it unable to find
|
||||
// block inside of already allocated area.
|
||||
class C10_CUDA_API FreeMemoryCallback {
|
||||
public:
|
||||
virtual ~FreeMemoryCallback() = default;
|
||||
virtual bool Execute() = 0;
|
||||
};
|
||||
|
||||
C10_DECLARE_REGISTRY(FreeCudaMemoryCallbacksRegistry, FreeMemoryCallback);
|
||||
#define REGISTER_FREE_MEMORY_CALLBACK(name, ...) \
|
||||
C10_REGISTER_CLASS(FreeCudaMemoryCallbacksRegistry, name, __VA_ARGS__)
|
||||
} // namespace c10
|
||||
//
|
||||
// TODO: Turn this into an honest to goodness class. I briefly attempted to do
|
||||
// this, but it was a bit irritating to figure out how to also correctly
|
||||
// apply pimpl pattern so I didn't have to leak any internal implementation
|
||||
// details in the header (CUDACachingAllocator could be made a pimpl, but
|
||||
// you also need to appropriately define a class which is a subclass
|
||||
// of Allocator. Not impossible, but required a bit more surgery than
|
||||
// I wanted to do at the time.)
|
||||
//
|
||||
// Why is this using a namespace rather than old-style THCCachingAllocator_
|
||||
// prefix? Mostly because it made the HIPify rules easier to write; _ is
|
||||
// not counted as a word boundary, so you would otherwise have to list each
|
||||
// of these functions.
|
||||
|
||||
namespace c10::cuda::CUDACachingAllocator {
|
||||
|
||||
// Preserved only for BC reasons
|
||||
// NOLINTNEXTLINE(misc-unused-using-decls)
|
||||
using c10::CachingDeviceAllocator::AllocatorTraceTracker;
|
||||
using c10::CachingDeviceAllocator::BlockInfo;
|
||||
using c10::CachingDeviceAllocator::CreateContextFn;
|
||||
using c10::CachingDeviceAllocator::DeviceStats;
|
||||
using c10::CachingDeviceAllocator::RecordContext;
|
||||
using c10::CachingDeviceAllocator::SegmentInfo;
|
||||
using c10::CachingDeviceAllocator::TraceEntry;
|
||||
|
||||
struct AllocatorState {
|
||||
virtual ~AllocatorState() = default;
|
||||
};
|
||||
|
||||
struct AllocatorConfigInfo {
|
||||
double garbage_collection_threshold;
|
||||
size_t max_split_size;
|
||||
size_t pinned_num_register_threads;
|
||||
bool expandable_segments;
|
||||
bool release_lock_on_malloc;
|
||||
bool pinned_use_host_register;
|
||||
bool graph_capture_record_stream_reuse;
|
||||
std::string last_allocator_settings;
|
||||
std::vector<size_t> roundup_power2_divisions;
|
||||
};
|
||||
|
||||
struct SnapshotInfo {
|
||||
std::vector<CachingDeviceAllocator::SegmentInfo> segments;
|
||||
std::vector<std::vector<CachingDeviceAllocator::TraceEntry>> device_traces;
|
||||
std::vector<CachingDeviceAllocator::AnnotationEntry> external_annotations;
|
||||
AllocatorConfigInfo config_metadata;
|
||||
};
|
||||
|
||||
// returns the pointers freed in the pool
|
||||
// and the pointers allocated. Note: a pointer
|
||||
// may appear in both freed and allocated
|
||||
struct CheckpointDelta {
|
||||
std::vector<void*> ptrs_freed;
|
||||
std::vector<at::DataPtr> dataptrs_allocd;
|
||||
};
|
||||
|
||||
using OutOfMemoryObserver = std::function<void(
|
||||
int64_t device,
|
||||
size_t allocated,
|
||||
size_t device_total,
|
||||
size_t device_free)>;
|
||||
|
||||
// Observer called when an allocation is preemptively rejected due to
|
||||
// throw_on_cudamalloc_oom policy. Parameters:
|
||||
// - device: GPU device index
|
||||
// - alloc_size: size of the rejected allocation request
|
||||
// - total_allocated: total memory allocated before the request
|
||||
// - device_total: total GPU memory
|
||||
using OomRejectionObserver = std::function<void(
|
||||
int64_t device,
|
||||
size_t alloc_size,
|
||||
size_t total_allocated,
|
||||
size_t device_total)>;
|
||||
|
||||
struct ShareableHandle {
|
||||
ptrdiff_t offset;
|
||||
std::string handle;
|
||||
};
|
||||
|
||||
struct StreamSegmentSize {
|
||||
StreamSegmentSize(cudaStream_t s, bool small_, size_t sz)
|
||||
: stream(s), is_small_pool(small_), total_size(sz) {}
|
||||
cudaStream_t stream;
|
||||
bool is_small_pool;
|
||||
size_t total_size;
|
||||
};
|
||||
|
||||
class CUDAAllocator : public DeviceAllocator {
|
||||
public:
|
||||
virtual void* raw_alloc(size_t nbytes) = 0;
|
||||
virtual void* raw_alloc_with_stream(size_t nbytes, cudaStream_t stream) = 0;
|
||||
virtual void raw_delete(void* ptr) = 0;
|
||||
virtual void init(int device_count) = 0;
|
||||
virtual double getMemoryFraction(c10::DeviceIndex device) = 0;
|
||||
virtual void setMemoryFraction(double fraction, c10::DeviceIndex device) = 0;
|
||||
virtual std::vector<StreamSegmentSize> getExpandableSegmentSizes(
|
||||
c10::DeviceIndex device) = 0;
|
||||
virtual void enable(bool value) = 0;
|
||||
virtual bool isEnabled() const = 0;
|
||||
virtual void cacheInfo(c10::DeviceIndex device, size_t* largestBlock) = 0;
|
||||
virtual void* getBaseAllocation(void* ptr, size_t* size) = 0;
|
||||
// Keep for BC only
|
||||
virtual void recordStream(const DataPtr& ptr, CUDAStream stream) = 0;
|
||||
void recordStream(const DataPtr& ptr, c10::Stream stream) override {
|
||||
CUDAStream cuda_stream = CUDAStream(stream);
|
||||
recordStream(ptr, cuda_stream);
|
||||
}
|
||||
virtual SnapshotInfo snapshot(
|
||||
MempoolId_t mempool_id = {0, 0},
|
||||
bool include_traces = true) = 0;
|
||||
virtual void beginAllocateToPool(
|
||||
c10::DeviceIndex device,
|
||||
MempoolId_t mempool_id,
|
||||
std::function<bool(cudaStream_t)> filter) = 0;
|
||||
virtual void endAllocateToPool(
|
||||
c10::DeviceIndex device,
|
||||
MempoolId_t mempool_id) = 0;
|
||||
virtual void releasePool(c10::DeviceIndex device, MempoolId_t mempool_id) = 0;
|
||||
virtual int getPoolUseCount(
|
||||
c10::DeviceIndex /*device*/,
|
||||
MempoolId_t /*mempool_id*/) {
|
||||
TORCH_CHECK(
|
||||
false,
|
||||
name(),
|
||||
" does not yet support getPoolUseCount. "
|
||||
"If you need it, please file an issue describing your use case.");
|
||||
}
|
||||
virtual void createOrIncrefPool(
|
||||
c10::DeviceIndex /*device*/,
|
||||
MempoolId_t /*mempool_id*/,
|
||||
std::shared_ptr<CUDAAllocator> allocator = nullptr) {
|
||||
TORCH_CHECK(
|
||||
false,
|
||||
name(),
|
||||
" does not yet support createOrIncrefPool. "
|
||||
"If you need it, please file an issue describing your use case.");
|
||||
}
|
||||
virtual void setUseOnOOM(
|
||||
c10::DeviceIndex device,
|
||||
MempoolId_t mempool_id,
|
||||
bool use_on_oom) {
|
||||
TORCH_CHECK(
|
||||
false,
|
||||
name(),
|
||||
" does not yet support setUseOnOOM. "
|
||||
"If you need it, please file an issue describing your use case.");
|
||||
}
|
||||
virtual void setNoSplit(c10::DeviceIndex device, MempoolId_t mempool_id) {
|
||||
TORCH_CHECK(
|
||||
false,
|
||||
name(),
|
||||
" does not yet support setNoSplit. "
|
||||
"If you need it, please file an issue describing your use case.");
|
||||
}
|
||||
|
||||
// returns true if the allocated blocks are equal to expected live allocations
|
||||
virtual bool checkPoolLiveAllocations(
|
||||
c10::DeviceIndex /*device*/,
|
||||
MempoolId_t /*mempool_id*/,
|
||||
const std::unordered_set<void*>& /*expected_live_allocations*/) {
|
||||
TORCH_CHECK(
|
||||
false,
|
||||
name(),
|
||||
" does not yet support checkPoolLiveAllocations. "
|
||||
"If you need it, please file an issue describing your use case.");
|
||||
}
|
||||
virtual ShareableHandle shareIpcHandle(void* ptr) = 0;
|
||||
virtual std::shared_ptr<void> getIpcDevPtr(std::string handle) = 0;
|
||||
virtual bool isHistoryEnabled() {
|
||||
TORCH_CHECK(
|
||||
false,
|
||||
name(),
|
||||
" does not yet support recordHistory. "
|
||||
"If you need it, please file an issue describing your use case.");
|
||||
}
|
||||
virtual std::shared_ptr<GatheredContext> getContextForPointer(
|
||||
const void* ptr) {
|
||||
return nullptr;
|
||||
}
|
||||
virtual void recordHistory(
|
||||
bool enabled,
|
||||
CreateContextFn context_recorder,
|
||||
size_t alloc_trace_max_entries,
|
||||
RecordContext when,
|
||||
bool clearHistory,
|
||||
const std::vector<std::string>& skip_actions) = 0;
|
||||
virtual void recordAnnotation(
|
||||
const std::vector<std::pair<std::string, std::string>>& /*md*/) {}
|
||||
virtual void pushCompileContext(std::string& md) {}
|
||||
virtual void popCompileContext() {}
|
||||
virtual void setUserMetadata(const std::string& metadata) {}
|
||||
virtual std::string getUserMetadata() {
|
||||
return "";
|
||||
}
|
||||
virtual void attachOutOfMemoryObserver(OutOfMemoryObserver observer) = 0;
|
||||
virtual void attachOomRejectionObserver(OomRejectionObserver observer) = 0;
|
||||
|
||||
// Attached AllocatorTraceTracker callbacks will be called while the
|
||||
// per-device allocator lock is held. Any additional locks taken from within
|
||||
// the callback must be proven to always have the lock order that never
|
||||
// triggers a deadlock. In particular, Python's GIL may be held when
|
||||
// calling the allocator so it is unsafe to try to acquire the GIL in this
|
||||
// callback.
|
||||
virtual void attachAllocatorTraceTracker(AllocatorTraceTracker tracker) = 0;
|
||||
|
||||
virtual void enablePeerAccess(
|
||||
c10::DeviceIndex dev,
|
||||
c10::DeviceIndex dev_to_access) = 0;
|
||||
|
||||
// memory not allocated from cudaMalloc cannot be copied
|
||||
// across devices using cudaMemcpyAsync if peer to peer access is disabled.
|
||||
// instead it requires cudaMemcpyAsyncPeer
|
||||
// with P2P Enabled, all combinations work
|
||||
// with P2P Disabled:
|
||||
// cudaMalloc cudaMallocAsync/cuMemMap
|
||||
// cudaMemcpyAsyncPeer works works
|
||||
// cudaMemcpyAsync works error
|
||||
|
||||
// This function performs chooses to use the Peer version of
|
||||
// memcpy if required based on where the allocated put dst/src.
|
||||
virtual cudaError_t memcpyAsync(
|
||||
void* dst,
|
||||
int dstDevice,
|
||||
const void* src,
|
||||
int srcDevice,
|
||||
size_t count,
|
||||
cudaStream_t stream,
|
||||
bool p2p_enabled) = 0;
|
||||
virtual std::shared_ptr<AllocatorState> getCheckpointState(
|
||||
c10::DeviceIndex device,
|
||||
MempoolId_t id) = 0;
|
||||
virtual CheckpointDelta setCheckpointPoolState(
|
||||
c10::DeviceIndex device,
|
||||
std::shared_ptr<AllocatorState> pps) = 0;
|
||||
virtual std::string name() = 0;
|
||||
std::pair<size_t, size_t> getMemoryInfo(c10::DeviceIndex device) override {
|
||||
c10::DeviceGuard device_guard({at::kCUDA, device});
|
||||
size_t free = 0;
|
||||
size_t total = 0;
|
||||
C10_CUDA_CHECK(cudaMemGetInfo(&free, &total));
|
||||
return {free, total};
|
||||
}
|
||||
};
|
||||
|
||||
// Allocator object, statically initialized
|
||||
// See BackendInitializer in CUDACachingAllocator.cpp.
|
||||
// Atomic loads on x86 are just normal loads,
|
||||
// (atomic stores are different), so reading this value
|
||||
// is no different than loading a pointer.
|
||||
C10_CUDA_API extern std::atomic<CUDAAllocator*> allocator;
|
||||
|
||||
inline CUDAAllocator* get() {
|
||||
return allocator.load();
|
||||
}
|
||||
|
||||
// Called directly by clients.
|
||||
inline void* raw_alloc(size_t nbytes) {
|
||||
return get()->raw_alloc(nbytes);
|
||||
}
|
||||
|
||||
inline void* raw_alloc_with_stream(size_t nbytes, cudaStream_t stream) {
|
||||
return get()->raw_alloc_with_stream(nbytes, stream);
|
||||
}
|
||||
|
||||
inline void raw_delete(void* ptr) {
|
||||
get()->raw_delete(ptr);
|
||||
}
|
||||
|
||||
inline void init(int device_count) {
|
||||
get()->init(device_count);
|
||||
}
|
||||
|
||||
inline double getMemoryFraction(c10::DeviceIndex device) {
|
||||
return get()->getMemoryFraction(device);
|
||||
}
|
||||
|
||||
inline void setMemoryFraction(double fraction, c10::DeviceIndex device) {
|
||||
get()->setMemoryFraction(fraction, device);
|
||||
}
|
||||
|
||||
inline std::vector<StreamSegmentSize> getExpandableSegmentSizes(
|
||||
c10::DeviceIndex device) {
|
||||
return get()->getExpandableSegmentSizes(device);
|
||||
}
|
||||
|
||||
inline void emptyCache(MempoolId_t mempool_id = {0, 0}) {
|
||||
get()->emptyCache(mempool_id);
|
||||
}
|
||||
|
||||
inline void enable(bool value) {
|
||||
get()->enable(value);
|
||||
}
|
||||
|
||||
inline bool isEnabled() {
|
||||
return get()->isEnabled();
|
||||
}
|
||||
|
||||
inline void cacheInfo(c10::DeviceIndex device, size_t* largestBlock) {
|
||||
get()->cacheInfo(device, largestBlock);
|
||||
}
|
||||
|
||||
inline void* getBaseAllocation(void* ptr, size_t* size) {
|
||||
return get()->getBaseAllocation(ptr, size);
|
||||
}
|
||||
|
||||
inline void recordStream(const DataPtr& dataPtr, CUDAStream stream) {
|
||||
get()->recordStream(dataPtr, stream);
|
||||
}
|
||||
|
||||
inline c10::CachingDeviceAllocator::DeviceStats getDeviceStats(
|
||||
c10::DeviceIndex device) {
|
||||
return get()->getDeviceStats(device);
|
||||
}
|
||||
|
||||
inline void resetAccumulatedStats(c10::DeviceIndex device) {
|
||||
get()->resetAccumulatedStats(device);
|
||||
}
|
||||
|
||||
inline void resetPeakStats(c10::DeviceIndex device) {
|
||||
get()->resetPeakStats(device);
|
||||
}
|
||||
|
||||
inline SnapshotInfo snapshot(
|
||||
MempoolId_t mempool_id = {0, 0},
|
||||
bool include_traces = true) {
|
||||
return get()->snapshot(mempool_id, include_traces);
|
||||
}
|
||||
|
||||
inline std::shared_ptr<AllocatorState> getCheckpointState(
|
||||
c10::DeviceIndex device,
|
||||
MempoolId_t id) {
|
||||
return get()->getCheckpointState(device, id);
|
||||
}
|
||||
|
||||
inline CheckpointDelta setCheckpointPoolState(
|
||||
c10::DeviceIndex device,
|
||||
std::shared_ptr<AllocatorState> pps) {
|
||||
return get()->setCheckpointPoolState(device, std::move(pps));
|
||||
}
|
||||
|
||||
// CUDAGraph interactions
|
||||
inline void beginAllocateToPool(
|
||||
c10::DeviceIndex device,
|
||||
MempoolId_t mempool_id,
|
||||
std::function<bool(cudaStream_t)> filter) {
|
||||
get()->beginAllocateToPool(device, mempool_id, std::move(filter));
|
||||
}
|
||||
|
||||
inline void endAllocateToPool(c10::DeviceIndex device, MempoolId_t mempool_id) {
|
||||
get()->endAllocateToPool(device, mempool_id);
|
||||
}
|
||||
|
||||
inline void recordHistory(
|
||||
bool enabled,
|
||||
CreateContextFn context_recorder,
|
||||
size_t alloc_trace_max_entries,
|
||||
RecordContext when,
|
||||
bool clearHistory,
|
||||
const std::vector<std::string>& skip_actions) {
|
||||
get()->recordHistory(
|
||||
enabled,
|
||||
context_recorder,
|
||||
alloc_trace_max_entries,
|
||||
when,
|
||||
clearHistory,
|
||||
skip_actions);
|
||||
}
|
||||
|
||||
inline void recordAnnotation(
|
||||
const std::vector<std::pair<std::string, std::string>>& md) {
|
||||
get()->recordAnnotation(md);
|
||||
}
|
||||
|
||||
inline void pushCompileContext(std::string& md) {
|
||||
get()->pushCompileContext(md);
|
||||
}
|
||||
|
||||
inline void popCompileContext() {
|
||||
get()->popCompileContext();
|
||||
}
|
||||
|
||||
inline bool isHistoryEnabled() {
|
||||
return get()->isHistoryEnabled();
|
||||
}
|
||||
|
||||
inline std::shared_ptr<GatheredContext> getContextForPointer(const void* ptr) {
|
||||
return get()->getContextForPointer(ptr);
|
||||
}
|
||||
|
||||
inline bool checkPoolLiveAllocations(
|
||||
c10::DeviceIndex device,
|
||||
MempoolId_t mempool_id,
|
||||
const std::unordered_set<void*>& expected_live_allocations) {
|
||||
return get()->checkPoolLiveAllocations(
|
||||
device, mempool_id, expected_live_allocations);
|
||||
}
|
||||
|
||||
inline void attachOutOfMemoryObserver(OutOfMemoryObserver observer) {
|
||||
get()->attachOutOfMemoryObserver(std::move(observer));
|
||||
}
|
||||
|
||||
inline void attachOomRejectionObserver(OomRejectionObserver observer) {
|
||||
get()->attachOomRejectionObserver(std::move(observer));
|
||||
}
|
||||
|
||||
inline void attachAllocatorTraceTracker(AllocatorTraceTracker tracker) {
|
||||
get()->attachAllocatorTraceTracker(std::move(tracker));
|
||||
}
|
||||
|
||||
inline void releasePool(c10::DeviceIndex device, MempoolId_t mempool_id) {
|
||||
get()->releasePool(device, mempool_id);
|
||||
}
|
||||
inline void createOrIncrefPool(
|
||||
c10::DeviceIndex device,
|
||||
MempoolId_t mempool_id,
|
||||
std::shared_ptr<CUDAAllocator> allocator_ptr = nullptr) {
|
||||
get()->createOrIncrefPool(device, mempool_id, std::move(allocator_ptr));
|
||||
}
|
||||
inline void setUseOnOOM(
|
||||
c10::DeviceIndex device,
|
||||
MempoolId_t mempool_id,
|
||||
bool use_on_oom) {
|
||||
get()->setUseOnOOM(device, mempool_id, use_on_oom);
|
||||
}
|
||||
inline void setNoSplit(c10::DeviceIndex device, MempoolId_t mempool_id) {
|
||||
get()->setNoSplit(device, mempool_id);
|
||||
}
|
||||
inline int getPoolUseCount(c10::DeviceIndex device, MempoolId_t mempool_id) {
|
||||
return get()->getPoolUseCount(device, mempool_id);
|
||||
}
|
||||
|
||||
// Not part of CUDA_ALLOCATOR_BACKEND_INTERFACE
|
||||
inline std::shared_ptr<void> getIpcDevPtr(std::string handle) {
|
||||
return get()->getIpcDevPtr(std::move(handle));
|
||||
}
|
||||
|
||||
inline ShareableHandle shareIpcHandle(void* ptr) {
|
||||
return get()->shareIpcHandle(ptr);
|
||||
}
|
||||
|
||||
inline std::string name() {
|
||||
return get()->name();
|
||||
}
|
||||
|
||||
inline cudaError_t memcpyAsync(
|
||||
void* dst,
|
||||
int dstDevice,
|
||||
const void* src,
|
||||
int srcDevice,
|
||||
size_t count,
|
||||
cudaStream_t stream,
|
||||
bool p2p_enabled) {
|
||||
return get()->memcpyAsync(
|
||||
dst, dstDevice, src, srcDevice, count, stream, p2p_enabled);
|
||||
}
|
||||
|
||||
inline void enablePeerAccess(
|
||||
c10::DeviceIndex dev,
|
||||
c10::DeviceIndex dev_to_access) {
|
||||
get()->enablePeerAccess(dev, dev_to_access);
|
||||
}
|
||||
|
||||
inline void setUserMetadata(const std::string& metadata) {
|
||||
get()->setUserMetadata(metadata);
|
||||
}
|
||||
|
||||
inline std::string getUserMetadata() {
|
||||
return get()->getUserMetadata();
|
||||
}
|
||||
|
||||
} // namespace c10::cuda::CUDACachingAllocator
|
||||
|
||||
namespace c10::cuda {
|
||||
// Keep BC only
|
||||
using c10::CaptureId_t;
|
||||
using c10::MempoolId_t;
|
||||
} // namespace c10::cuda
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/cuda/CUDAException.h>
|
||||
#include <c10/macros/Macros.h>
|
||||
|
||||
namespace c10::cuda {
|
||||
|
||||
#ifdef TORCH_USE_CUDA_DSA
|
||||
C10_DIAGNOSTIC_PUSH_AND_IGNORED_IF_DEFINED("-Wunused-function")
|
||||
// Copy string from `src` to `dst`
|
||||
static __device__ void dstrcpy(char* dst, const char* src) {
|
||||
int i = 0;
|
||||
// Copy string from source to destination, ensuring that it
|
||||
// isn't longer than `C10_CUDA_DSA_MAX_STR_LEN-1`
|
||||
while (*src != '\0' && i++ < C10_CUDA_DSA_MAX_STR_LEN - 1) {
|
||||
*dst++ = *src++;
|
||||
}
|
||||
*dst = '\0';
|
||||
}
|
||||
|
||||
static __device__ void dsa_add_new_assertion_failure(
|
||||
DeviceAssertionsData* assertions_data,
|
||||
const char* assertion_msg,
|
||||
const char* filename,
|
||||
const char* function_name,
|
||||
const int line_number,
|
||||
const uint32_t caller,
|
||||
const dim3 block_id,
|
||||
const dim3 thread_id) {
|
||||
// `assertions_data` may be nullptr if device-side assertion checking
|
||||
// is disabled at run-time. If it is disabled at compile time this
|
||||
// function will never be called
|
||||
if (!assertions_data) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Atomically increment so other threads can fail at the same time
|
||||
// Note that incrementing this means that the CPU can observe that
|
||||
// a failure has happened and can begin to respond before we've
|
||||
// written information about that failure out to the buffer.
|
||||
const auto nid = atomicAdd(&(assertions_data->assertion_count), 1);
|
||||
|
||||
if (nid >= C10_CUDA_DSA_ASSERTION_COUNT) {
|
||||
// At this point we're ran out of assertion buffer space.
|
||||
// We could print a message about this, but that'd get
|
||||
// spammy if a lot of threads did it, so we just silently
|
||||
// ignore any other assertion failures. In most cases the
|
||||
// failures will all probably be analogous anyway.
|
||||
return;
|
||||
}
|
||||
|
||||
// Write information about the assertion failure to memory.
|
||||
// Note that this occurs only after the `assertion_count`
|
||||
// increment broadcasts that there's been a problem.
|
||||
auto& self = assertions_data->assertions[nid];
|
||||
dstrcpy(self.assertion_msg, assertion_msg);
|
||||
dstrcpy(self.filename, filename);
|
||||
dstrcpy(self.function_name, function_name);
|
||||
self.line_number = line_number;
|
||||
self.caller = caller;
|
||||
self.block_id[0] = block_id.x;
|
||||
self.block_id[1] = block_id.y;
|
||||
self.block_id[2] = block_id.z;
|
||||
self.thread_id[0] = thread_id.x;
|
||||
self.thread_id[1] = thread_id.y;
|
||||
self.thread_id[2] = thread_id.z;
|
||||
}
|
||||
C10_CLANG_DIAGNOSTIC_POP()
|
||||
|
||||
// Emulates a kernel assertion. The assertion won't stop the kernel's progress,
|
||||
// so you should assume everything the kernel produces is garbage if there's an
|
||||
// assertion failure.
|
||||
// NOTE: This assumes that `assertions_data` and `assertion_caller_id` are
|
||||
// arguments of the kernel and therefore accessible.
|
||||
#define CUDA_KERNEL_ASSERT2(condition) \
|
||||
do { \
|
||||
if (C10_UNLIKELY(!(condition))) { \
|
||||
/* Has an atomic element so threads can fail at the same time */ \
|
||||
c10::cuda::dsa_add_new_assertion_failure( \
|
||||
assertions_data, \
|
||||
C10_STRINGIZE(condition), \
|
||||
__FILE__, \
|
||||
__FUNCTION__, \
|
||||
__LINE__, \
|
||||
assertion_caller_id, \
|
||||
blockIdx, \
|
||||
threadIdx); \
|
||||
/* Now that the kernel has failed we early exit the kernel, but */ \
|
||||
/* otherwise keep going and rely on the host to check UVM and */ \
|
||||
/* determine we've had a problem */ \
|
||||
return; \
|
||||
} \
|
||||
} while (false)
|
||||
#else
|
||||
#define CUDA_KERNEL_ASSERT2(condition) assert(condition)
|
||||
#endif
|
||||
|
||||
} // namespace c10::cuda
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/cuda/CUDAMacros.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#if defined(USE_CUDA) || defined(USE_ROCM)
|
||||
#define TORCH_USE_CUDA_DSA
|
||||
#endif
|
||||
|
||||
/// Number of assertion failure messages we can store. If this is too small
|
||||
/// threads will fail silently.
|
||||
constexpr int C10_CUDA_DSA_ASSERTION_COUNT = 10;
|
||||
constexpr int C10_CUDA_DSA_MAX_STR_LEN = 512;
|
||||
|
||||
namespace c10::cuda {
|
||||
|
||||
/// Holds information about any device-side assertions that fail.
|
||||
/// Held in managed memory and access by both the CPU and the GPU.
|
||||
struct DeviceAssertionData {
|
||||
/// Stringification of the assertion
|
||||
// NOLINTNEXTLINE(*-c-arrays)
|
||||
char assertion_msg[C10_CUDA_DSA_MAX_STR_LEN]{};
|
||||
/// File the assertion was in
|
||||
// NOLINTNEXTLINE(*-c-arrays)
|
||||
char filename[C10_CUDA_DSA_MAX_STR_LEN]{};
|
||||
/// Name of the function the assertion was in
|
||||
// NOLINTNEXTLINE(*-c-arrays)
|
||||
char function_name[C10_CUDA_DSA_MAX_STR_LEN]{};
|
||||
/// Line number the assertion was at
|
||||
int line_number{};
|
||||
/// Number uniquely identifying the kernel launch that triggered the assertion
|
||||
uint32_t caller{};
|
||||
/// block_id of the thread that failed the assertion
|
||||
// NOLINTNEXTLINE(*-c-arrays)
|
||||
int32_t block_id[3]{};
|
||||
/// third_id of the thread that failed the assertion
|
||||
// NOLINTNEXTLINE(*-c-arrays)
|
||||
int32_t thread_id[3]{};
|
||||
};
|
||||
|
||||
/// Used to hold assertions generated by the device
|
||||
/// Held in managed memory and access by both the CPU and the GPU.
|
||||
struct DeviceAssertionsData {
|
||||
/// Total number of assertions found; a subset of these will be recorded
|
||||
/// in `assertions`
|
||||
int32_t assertion_count{};
|
||||
/// An array of assertions that will be written to in a race-free manner
|
||||
// NOLINTNEXTLINE(*-c-arrays)
|
||||
DeviceAssertionData assertions[C10_CUDA_DSA_ASSERTION_COUNT]{};
|
||||
};
|
||||
|
||||
/// Use to hold info about kernel launches so that we can run kernels
|
||||
/// asynchronously and still associate launches with device-side
|
||||
/// assertion failures
|
||||
struct CUDAKernelLaunchInfo {
|
||||
/// Filename of the code where the kernel was launched from
|
||||
const char* launch_filename;
|
||||
/// Function from which the kernel was launched
|
||||
const char* launch_function;
|
||||
/// Line number of where the code was launched from
|
||||
uint32_t launch_linenum;
|
||||
/// Backtrace of where the kernel was launched from, only populated if
|
||||
/// CUDAKernelLaunchRegistry::gather_launch_stacktrace is True
|
||||
std::string launch_stacktrace;
|
||||
/// Kernel that was launched
|
||||
const char* kernel_name;
|
||||
/// Device the kernel was launched on
|
||||
int device;
|
||||
/// Stream the kernel was launched on
|
||||
int32_t stream;
|
||||
/// A number that uniquely identifies the kernel launch
|
||||
uint64_t generation_number;
|
||||
};
|
||||
|
||||
/// Circular buffer used to hold information about kernel launches
|
||||
/// this is later used to reconstruct how a device-side kernel assertion failure
|
||||
/// occurred CUDAKernelLaunchRegistry is used as a singleton
|
||||
class C10_CUDA_API CUDAKernelLaunchRegistry {
|
||||
private:
|
||||
/// Assume that this is the max number of kernel launches that might ever be
|
||||
/// enqueued across all streams on a single device
|
||||
static constexpr int max_kernel_launches = 1024;
|
||||
/// How many kernel launch infos we've inserted. Used to ensure that circular
|
||||
/// queue doesn't provide false information by always increasing, but also to
|
||||
/// mark where we are inserting into the queue
|
||||
#ifdef TORCH_USE_CUDA_DSA
|
||||
uint64_t generation_number = 0;
|
||||
#endif
|
||||
/// Shared mutex between writer and accessor to ensure multi-threaded safety.
|
||||
mutable std::mutex read_write_mutex;
|
||||
/// Used to ensure prevent race conditions in GPU memory allocation
|
||||
mutable std::mutex gpu_alloc_mutex;
|
||||
/// Pointer to managed memory keeping track of device-side assertions. There
|
||||
/// is one entry for each possible device the process might work with. Unused
|
||||
/// entries are nullptrs. We could also use an unordered_set here, but this
|
||||
/// vector design will be faster and the wasted memory is small since we
|
||||
/// expect the number of GPUs per node will always be small
|
||||
std::vector<
|
||||
std::unique_ptr<DeviceAssertionsData, void (*)(DeviceAssertionsData*)>>
|
||||
uvm_assertions;
|
||||
/// A single circular buffer holds information about every kernel launch the
|
||||
/// process makes across all devices.
|
||||
std::vector<CUDAKernelLaunchInfo> kernel_launches;
|
||||
bool check_env_for_enable_launch_stacktracing() const;
|
||||
bool check_env_for_dsa_enabled() const;
|
||||
|
||||
public:
|
||||
CUDAKernelLaunchRegistry();
|
||||
/// Register a new kernel launch and obtain a generation number back to be
|
||||
/// passed to the kernel
|
||||
uint32_t insert(
|
||||
const char* launch_filename,
|
||||
const char* launch_function,
|
||||
const uint32_t launch_linenum,
|
||||
const char* kernel_name,
|
||||
const int32_t stream_id);
|
||||
/// Get copies of the kernel launch registry and each device's assertion
|
||||
/// failure buffer so they can be inspected without raising race conditions
|
||||
std::
|
||||
pair<std::vector<DeviceAssertionsData>, std::vector<CUDAKernelLaunchInfo>>
|
||||
snapshot() const;
|
||||
/// Get a pointer to the current device's assertion failure buffer. If no such
|
||||
/// buffer exists then one is created. This means that the first kernel launch
|
||||
/// made on each device will be slightly slower because memory allocations are
|
||||
/// required
|
||||
DeviceAssertionsData* get_uvm_assertions_ptr_for_current_device();
|
||||
/// Gets the global singleton of the registry
|
||||
static CUDAKernelLaunchRegistry& get_singleton_ref();
|
||||
/// If not all devices support DSA, we disable it
|
||||
const bool do_all_devices_support_managed_memory = false;
|
||||
/// Whether or not to gather stack traces when launching kernels
|
||||
bool gather_launch_stacktrace = false;
|
||||
/// Whether or not host-side DSA is enabled or disabled at run-time
|
||||
/// Note: Device-side code cannot be enabled/disabled at run-time
|
||||
bool enabled_at_runtime = false;
|
||||
/// Whether or not a device has indicated a failure
|
||||
bool has_failed() const;
|
||||
#ifdef TORCH_USE_CUDA_DSA
|
||||
const bool enabled_at_compile_time = true;
|
||||
#else
|
||||
const bool enabled_at_compile_time = false;
|
||||
#endif
|
||||
};
|
||||
|
||||
C10_CUDA_API std::string c10_retrieve_device_side_assertion_info();
|
||||
|
||||
} // namespace c10::cuda
|
||||
|
||||
// Each kernel launched with TORCH_DSA_KERNEL_LAUNCH
|
||||
// requires the same input arguments. We introduce the following macro to
|
||||
// standardize these.
|
||||
#define TORCH_DSA_KERNEL_ARGS \
|
||||
[[maybe_unused]] c10::cuda::DeviceAssertionsData *const assertions_data, \
|
||||
[[maybe_unused]] uint32_t assertion_caller_id
|
||||
|
||||
// This macro can be used to pass the DSA arguments onward to another
|
||||
// function
|
||||
#define TORCH_DSA_KERNEL_ARGS_PASS assertions_data, assertion_caller_id
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,374 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/alignment.h>
|
||||
#include <c10/core/impl/GPUTrace.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#include <c10/cuda/CUDAStream.h>
|
||||
#include <c10/util/Exception.h>
|
||||
#include <c10/util/irange.h>
|
||||
|
||||
/*
|
||||
* `cudaEventExternal` is a torch-specific flag that is used to
|
||||
* indicate that the CUDAEvent will be used only for synchronization
|
||||
* with work outside of the cuda graph, rather than creation of
|
||||
* cross-stream dependencies within a cuda graph. Resources:
|
||||
* https://docs.nvidia.com/cuda/archive/12.9.0/cuda-c-programming-guide/index.html#cross-stream-dependencies-and-events
|
||||
* https://docs.nvidia.com/cuda/archive/12.9.0/cuda-runtime-api/group__CUDART__TYPES.html#group__CUDART__TYPES_1g3457b81d1d32c6a00f6132fbc2693d47
|
||||
* https://docs.nvidia.com/cuda/archive/12.9.0/cuda-runtime-api/group__CUDART__TYPES.html#group__CUDART__TYPES_1g0c23426b7252eaa9cef695859991304e
|
||||
*/
|
||||
#define cudaEventExternal 0x08
|
||||
|
||||
namespace c10::cuda {
|
||||
|
||||
/*
|
||||
* CUDAEvents are movable not copyable wrappers around CUDA's events.
|
||||
*
|
||||
* CUDAEvents are constructed lazily when first recorded unless it is
|
||||
* reconstructed from a cudaIpcEventHandle_t. The event has a device, and this
|
||||
* device is acquired from the first recording stream. However, if reconstructed
|
||||
* from a handle, the device should be explicitly specified; or if ipc_handle()
|
||||
* is called before the event is ever recorded, it will use the current device.
|
||||
* Later streams that record the event must match this device.
|
||||
*/
|
||||
struct CUDAEvent {
|
||||
// Constructors
|
||||
// Default value for `flags` is specified below - it's cudaEventDisableTiming
|
||||
CUDAEvent() noexcept = default;
|
||||
CUDAEvent(unsigned int flags) noexcept : flags_{flags} {}
|
||||
|
||||
CUDAEvent(DeviceIndex device_index, const cudaIpcEventHandle_t* handle)
|
||||
: device_index_(device_index) {
|
||||
CUDAGuard guard(device_index_);
|
||||
|
||||
C10_CUDA_CHECK(cudaIpcOpenEventHandle(&event_, *handle));
|
||||
is_created_ = true;
|
||||
}
|
||||
|
||||
// Note: event destruction done on creating device to avoid creating a
|
||||
// CUDA context on other devices.
|
||||
~CUDAEvent() {
|
||||
if (is_created_) {
|
||||
CUDAGuard guard(device_index_);
|
||||
const c10::impl::PyInterpreter* interp = c10::impl::GPUTrace::get_trace();
|
||||
if (C10_UNLIKELY(interp)) {
|
||||
(*interp)->trace_gpu_event_deletion(
|
||||
c10::kCUDA, reinterpret_cast<uintptr_t>(event_));
|
||||
}
|
||||
C10_CUDA_CHECK_WARN(cudaEventDestroy(event_));
|
||||
}
|
||||
}
|
||||
|
||||
CUDAEvent(const CUDAEvent&) = delete;
|
||||
CUDAEvent& operator=(const CUDAEvent&) = delete;
|
||||
|
||||
CUDAEvent(CUDAEvent&& other) noexcept {
|
||||
moveHelper(std::move(other));
|
||||
}
|
||||
CUDAEvent& operator=(CUDAEvent&& other) noexcept {
|
||||
if (this != &other) {
|
||||
moveHelper(std::move(other));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator cudaEvent_t() const {
|
||||
return event();
|
||||
}
|
||||
|
||||
// Less than operator (to allow use in sets)
|
||||
friend bool operator<(const CUDAEvent& left, const CUDAEvent& right) {
|
||||
return left.event_ < right.event_;
|
||||
}
|
||||
|
||||
std::optional<c10::Device> device() const {
|
||||
if (is_created_) {
|
||||
return c10::Device(c10::kCUDA, device_index_);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
bool isCreated() const {
|
||||
return is_created_;
|
||||
}
|
||||
DeviceIndex device_index() const {
|
||||
return device_index_;
|
||||
}
|
||||
cudaEvent_t event() const {
|
||||
return event_;
|
||||
}
|
||||
|
||||
// Note: cudaEventQuery can be safely called from any device
|
||||
bool query() const {
|
||||
if (!is_created_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
cudaError_t err = cudaEventQuery(event_);
|
||||
if (err == cudaSuccess) {
|
||||
return true;
|
||||
} else if (err != cudaErrorNotReady) {
|
||||
C10_CUDA_CHECK(err);
|
||||
} else {
|
||||
// ignore and clear the error if not ready
|
||||
(void)cudaGetLastError();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void record() {
|
||||
record(getCurrentCUDAStream());
|
||||
}
|
||||
|
||||
void recordOnce(const CUDAStream& stream) {
|
||||
if (!was_recorded_)
|
||||
record(stream);
|
||||
}
|
||||
|
||||
// Note: cudaEventRecord must be called on the same device as the event.
|
||||
void record(const CUDAStream& stream) {
|
||||
if (!is_created_) {
|
||||
createEvent(stream.device_index());
|
||||
}
|
||||
|
||||
TORCH_CHECK(
|
||||
device_index_ == stream.device_index(),
|
||||
"Event device ",
|
||||
device_index_,
|
||||
" does not match recording stream's device ",
|
||||
stream.device_index(),
|
||||
".");
|
||||
CUDAGuard guard(device_index_);
|
||||
|
||||
#ifndef USE_ROCM
|
||||
// it is an error to use cudaEventRecordExternal when not doing stream
|
||||
// capture
|
||||
unsigned int flags = (c10::cuda::currentStreamCaptureStatusMayInitCtx() !=
|
||||
c10::cuda::CaptureStatus::None &&
|
||||
external_)
|
||||
? cudaEventRecordExternal
|
||||
: cudaEventRecordDefault;
|
||||
C10_CUDA_CHECK(cudaEventRecordWithFlags(event_, stream, flags));
|
||||
#else
|
||||
C10_CUDA_CHECK(cudaEventRecord(event_, stream));
|
||||
#endif
|
||||
const c10::impl::PyInterpreter* interp = c10::impl::GPUTrace::get_trace();
|
||||
if (C10_UNLIKELY(interp)) {
|
||||
(*interp)->trace_gpu_event_record(
|
||||
c10::kCUDA,
|
||||
reinterpret_cast<uintptr_t>(event_),
|
||||
reinterpret_cast<uintptr_t>(stream.stream()));
|
||||
}
|
||||
was_recorded_ = true;
|
||||
}
|
||||
|
||||
// Note: cudaStreamWaitEvent must be called on the same device as the stream.
|
||||
// The event has no actual GPU resources associated with it.
|
||||
void block(const CUDAStream& stream) {
|
||||
if (is_created_) {
|
||||
CUDAGuard guard(stream.device_index());
|
||||
#ifndef USE_ROCM
|
||||
// it is an error to use cudaEventWaitExternal when not doing stream
|
||||
// capture
|
||||
unsigned int flags = (c10::cuda::currentStreamCaptureStatusMayInitCtx() !=
|
||||
c10::cuda::CaptureStatus::None &&
|
||||
external_)
|
||||
? cudaEventWaitExternal
|
||||
: cudaEventWaitDefault;
|
||||
C10_CUDA_CHECK(cudaStreamWaitEvent(stream, event_, flags));
|
||||
#else
|
||||
C10_CUDA_CHECK(cudaStreamWaitEvent(stream, event_));
|
||||
#endif
|
||||
const c10::impl::PyInterpreter* interp = c10::impl::GPUTrace::get_trace();
|
||||
if (C10_UNLIKELY(interp)) {
|
||||
(*interp)->trace_gpu_event_wait(
|
||||
c10::kCUDA,
|
||||
reinterpret_cast<uintptr_t>(event_),
|
||||
reinterpret_cast<uintptr_t>(stream.stream()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Note: cudaEventElapsedTime can be safely called from any device
|
||||
float elapsed_time(const CUDAEvent& other) const {
|
||||
TORCH_CHECK_VALUE(
|
||||
!(flags_ & cudaEventDisableTiming) &&
|
||||
!(other.flags_ & cudaEventDisableTiming),
|
||||
"Both events must be created with argument 'enable_timing=True'.");
|
||||
TORCH_CHECK_VALUE(
|
||||
is_created_ && other.isCreated(),
|
||||
"Both events must be recorded before calculating elapsed time.");
|
||||
TORCH_CHECK(
|
||||
query() && other.query(),
|
||||
"Both events must be completed before calculating elapsed time.");
|
||||
|
||||
float time_ms = 0;
|
||||
// We do not strictly have to set the device index to the same as our event,
|
||||
// but if we don't and the current device is not initialized, it will
|
||||
// create a new cuda context, which will consume a lot of memory.
|
||||
CUDAGuard guard(device_index_);
|
||||
// raise cudaErrorNotReady if either event is recorded but not yet completed
|
||||
C10_CUDA_CHECK(cudaEventElapsedTime(&time_ms, event_, other.event_));
|
||||
return time_ms;
|
||||
}
|
||||
|
||||
// Note: cudaEventSynchronize can be safely called from any device
|
||||
void synchronize() const {
|
||||
if (is_created_) {
|
||||
const c10::impl::PyInterpreter* interp = c10::impl::GPUTrace::get_trace();
|
||||
if (C10_UNLIKELY(interp)) {
|
||||
(*interp)->trace_gpu_event_synchronization(
|
||||
c10::kCUDA, reinterpret_cast<uintptr_t>(event_));
|
||||
}
|
||||
C10_CUDA_CHECK(cudaEventSynchronize(event_));
|
||||
}
|
||||
}
|
||||
|
||||
// Note: cudaIpcGetEventHandle must be called on the same device as the event
|
||||
void ipc_handle(cudaIpcEventHandle_t* handle) {
|
||||
if (!is_created_) {
|
||||
// this CUDAEvent object was initially constructed from flags but event_
|
||||
// is not created yet.
|
||||
createEvent(getCurrentCUDAStream().device_index());
|
||||
}
|
||||
CUDAGuard guard(device_index_);
|
||||
C10_CUDA_CHECK(cudaIpcGetEventHandle(handle, event_));
|
||||
}
|
||||
|
||||
void create(DeviceIndex device_index) {
|
||||
if (!is_created_) {
|
||||
createEvent(device_index);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned int flags_ = cudaEventDisableTiming;
|
||||
bool is_created_ = false;
|
||||
bool was_recorded_ = false;
|
||||
bool external_ = false;
|
||||
DeviceIndex device_index_ = -1;
|
||||
cudaEvent_t event_{};
|
||||
|
||||
void createEvent(DeviceIndex device_index) {
|
||||
external_ = (flags_ & cudaEventExternal) != 0;
|
||||
#ifdef USE_ROCM
|
||||
TORCH_CHECK(!external_, "External events are disallowed in rocm");
|
||||
#endif
|
||||
flags_ &= ~cudaEventExternal;
|
||||
device_index_ = device_index;
|
||||
CUDAGuard guard(device_index_);
|
||||
C10_CUDA_CHECK(cudaEventCreateWithFlags(&event_, flags_));
|
||||
const c10::impl::PyInterpreter* interp = c10::impl::GPUTrace::get_trace();
|
||||
if (C10_UNLIKELY(interp)) {
|
||||
(*interp)->trace_gpu_event_creation(
|
||||
c10::kCUDA, reinterpret_cast<uintptr_t>(event_));
|
||||
}
|
||||
is_created_ = true;
|
||||
}
|
||||
|
||||
void moveHelper(CUDAEvent&& other) {
|
||||
// Transfer ownership of all state from other to this
|
||||
flags_ = other.flags_;
|
||||
is_created_ = other.is_created_;
|
||||
was_recorded_ = other.was_recorded_;
|
||||
external_ = other.external_;
|
||||
device_index_ = other.device_index_;
|
||||
event_ = other.event_;
|
||||
|
||||
// Reset other to a valid empty state to prevent double-free
|
||||
// The moved-from object must not attempt to destroy the event
|
||||
other.is_created_ = false;
|
||||
other.event_ = cudaEvent_t{};
|
||||
}
|
||||
};
|
||||
|
||||
// CUDAEventPool - A thread-safe pool of CUDA events to avoid the overhead of
|
||||
// repeatedly calling cudaEventCreate(). Concurrent cudaEventCreate() calls
|
||||
// can incur significant cost on some device/driver combinations.
|
||||
//
|
||||
// This pool maintains per-device lists of pre-created CUDA events.
|
||||
// Borrowed events are returned to the pool via a custom unique_ptr deleter.
|
||||
|
||||
class CUDAEventPool {
|
||||
public:
|
||||
using Event = std::unique_ptr<
|
||||
c10::cuda::CUDAEvent,
|
||||
std::function<void(c10::cuda::CUDAEvent*)>>;
|
||||
|
||||
CUDAEventPool(size_t init_num_events = 0)
|
||||
: pools_(c10::cuda::device_count()) {
|
||||
if (init_num_events > 0) {
|
||||
reserve_events_on_pools(init_num_events);
|
||||
}
|
||||
}
|
||||
|
||||
// Acquire an event associated with a given device. If device is invalid, fall
|
||||
// back to a regular CUDAEvent and no pooling.
|
||||
Event get(const DeviceIndex device) {
|
||||
if (device < 0 || device >= (DeviceIndex)pools_.size()) {
|
||||
auto deleter = [](CUDAEvent* event) { delete event; };
|
||||
return Event(std::make_unique<CUDAEvent>().release(), deleter);
|
||||
}
|
||||
|
||||
auto& pool = pools_[device];
|
||||
|
||||
// Create a destructor that returns the event to the appropriate device pool
|
||||
auto destructor = [&pool](CUDAEvent* event) noexcept {
|
||||
if (event != nullptr) {
|
||||
std::lock_guard<std::mutex> lock(pool.mutex_);
|
||||
pool.event_pool_.emplace_back(event);
|
||||
}
|
||||
};
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(pool.mutex_);
|
||||
if (!pool.event_pool_.empty()) {
|
||||
auto event = std::move(pool.event_pool_.back());
|
||||
pool.event_pool_.pop_back();
|
||||
return Event(event.release(), destructor);
|
||||
}
|
||||
}
|
||||
|
||||
// Pool is empty then create a new Event
|
||||
return Event(std::make_unique<CUDAEvent>().release(), destructor);
|
||||
}
|
||||
|
||||
void empty_cache() {
|
||||
for (auto& pool : pools_) {
|
||||
std::lock_guard<std::mutex> lock(pool.mutex_);
|
||||
pool.event_pool_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// Pre-initialize each device pool with N events. This prevents
|
||||
// cudaEventCreate() from invoking during steady-state execution.
|
||||
void reserve_events_on_pools(size_t num_events) {
|
||||
for (const auto device : c10::irange(pools_.size())) {
|
||||
std::vector<Event> temp_events;
|
||||
temp_events.reserve(num_events);
|
||||
pools_[device].event_pool_.reserve(num_events);
|
||||
for ([[maybe_unused]] const auto _ : c10::irange(num_events)) {
|
||||
auto event = get(device);
|
||||
event->create(device);
|
||||
temp_events.emplace_back(std::move(event));
|
||||
}
|
||||
// Events will be returned to pool when temp_events is destroyed.
|
||||
}
|
||||
}
|
||||
|
||||
struct alignas(c10::hardware_destructive_interference_size) PerDevicePool {
|
||||
alignas(c10::hardware_destructive_interference_size) std::mutex mutex_;
|
||||
std::vector<std::unique_ptr<CUDAEvent>> event_pool_;
|
||||
};
|
||||
|
||||
std::vector<PerDevicePool> pools_;
|
||||
};
|
||||
|
||||
} // namespace c10::cuda
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,104 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/cuda/CUDADeviceAssertionHost.h>
|
||||
#include <c10/cuda/CUDAMacros.h>
|
||||
#include <c10/cuda/CUDAMiscFunctions.h>
|
||||
#include <c10/macros/Macros.h>
|
||||
#include <c10/util/Exception.h>
|
||||
#include <c10/util/irange.h>
|
||||
#include <cuda.h>
|
||||
|
||||
// Note [CHECK macro]
|
||||
// ~~~~~~~~~~~~~~~~~~
|
||||
// This is a macro so that AT_ERROR can get accurate __LINE__
|
||||
// and __FILE__ information. We could split this into a short
|
||||
// macro and a function implementation if we pass along __LINE__
|
||||
// and __FILE__, but no one has found this worth doing.
|
||||
|
||||
// Used to denote errors from CUDA framework.
|
||||
// This needs to be declared here instead util/Exception.h for proper conversion
|
||||
// during hipify.
|
||||
namespace c10 {
|
||||
class C10_CUDA_API CUDAError : public c10::Error {
|
||||
using Error::Error;
|
||||
};
|
||||
} // namespace c10
|
||||
|
||||
#define C10_CUDA_CHECK(EXPR) \
|
||||
do { \
|
||||
const cudaError_t __err = EXPR; \
|
||||
c10::cuda::c10_cuda_check_implementation( \
|
||||
static_cast<int32_t>(__err), \
|
||||
__FILE__, \
|
||||
__func__, /* Line number data type not well-defined between \
|
||||
compilers, so we perform an explicit cast */ \
|
||||
static_cast<uint32_t>(__LINE__), \
|
||||
true); \
|
||||
} while (0)
|
||||
// backwards compat due to hipify v2 changes, for extension projects
|
||||
#define C10_HIP_CHECK C10_CUDA_CHECK
|
||||
|
||||
#define C10_CUDA_CHECK_WARN(EXPR) \
|
||||
do { \
|
||||
const cudaError_t __err = EXPR; \
|
||||
if (C10_UNLIKELY(__err != cudaSuccess)) { \
|
||||
[[maybe_unused]] auto error_unused = cudaGetLastError(); \
|
||||
TORCH_WARN("CUDA warning: ", cudaGetErrorString(__err)); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
// Indicates that a CUDA error is handled in a non-standard way
|
||||
#define C10_CUDA_ERROR_HANDLED(EXPR) EXPR
|
||||
|
||||
// Intentionally ignore a CUDA error
|
||||
#define C10_CUDA_IGNORE_ERROR(EXPR) \
|
||||
do { \
|
||||
const cudaError_t __err = EXPR; \
|
||||
if (C10_UNLIKELY(__err != cudaSuccess)) { \
|
||||
[[maybe_unused]] cudaError_t error_unused = cudaGetLastError(); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
// Clear the last CUDA error
|
||||
#define C10_CUDA_CLEAR_ERROR() \
|
||||
do { \
|
||||
[[maybe_unused]] cudaError_t error_unused = cudaGetLastError(); \
|
||||
} while (0)
|
||||
|
||||
// This should be used directly after every kernel launch to ensure
|
||||
// the launch happened correctly and provide an early, close-to-source
|
||||
// diagnostic if it didn't.
|
||||
#define C10_CUDA_KERNEL_LAUNCH_CHECK() C10_CUDA_CHECK(cudaGetLastError())
|
||||
|
||||
/// Launches a CUDA kernel appending to it all the information need to handle
|
||||
/// device-side assertion failures. Checks that the launch was successful.
|
||||
#define TORCH_DSA_KERNEL_LAUNCH( \
|
||||
kernel, blocks, threads, shared_mem, stream, ...) \
|
||||
do { \
|
||||
auto& launch_registry = \
|
||||
c10::cuda::CUDAKernelLaunchRegistry::get_singleton_ref(); \
|
||||
kernel<<<blocks, threads, shared_mem, stream>>>( \
|
||||
__VA_ARGS__, \
|
||||
launch_registry.get_uvm_assertions_ptr_for_current_device(), \
|
||||
launch_registry.insert( \
|
||||
__FILE__, __FUNCTION__, __LINE__, #kernel, stream.id())); \
|
||||
C10_CUDA_KERNEL_LAUNCH_CHECK(); \
|
||||
} while (0)
|
||||
|
||||
namespace c10::cuda {
|
||||
|
||||
/// In the event of a CUDA failure, formats a nice error message about that
|
||||
/// failure and also checks for device-side assertion failures
|
||||
C10_CUDA_API void c10_cuda_check_implementation(
|
||||
const int32_t err,
|
||||
const char* filename,
|
||||
const char* function_name,
|
||||
const uint32_t line_number,
|
||||
const bool include_device_assertions);
|
||||
|
||||
} // namespace c10::cuda
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,154 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
// This header provides C++ wrappers around commonly used CUDA API functions.
|
||||
// The benefit of using C++ here is that we can raise an exception in the
|
||||
// event of an error, rather than explicitly pass around error codes. This
|
||||
// leads to more natural APIs.
|
||||
//
|
||||
// The naming convention used here matches the naming convention of torch.cuda
|
||||
|
||||
#include <c10/core/Device.h>
|
||||
#include <c10/core/impl/GPUTrace.h>
|
||||
#include <c10/cuda/CUDAException.h>
|
||||
#include <c10/cuda/CUDAMacros.h>
|
||||
#include <cuda_runtime_api.h>
|
||||
namespace c10::cuda {
|
||||
|
||||
// NB: In the past, we were inconsistent about whether or not this reported
|
||||
// an error if there were driver problems are not. Based on experience
|
||||
// interacting with users, it seems that people basically ~never want this
|
||||
// function to fail; it should just return zero if things are not working.
|
||||
// Oblige them.
|
||||
// It still might log a warning for user first time it's invoked
|
||||
C10_CUDA_API DeviceIndex device_count() noexcept;
|
||||
|
||||
// Version of device_count that throws is no devices are detected
|
||||
C10_CUDA_API DeviceIndex device_count_ensure_non_zero();
|
||||
|
||||
C10_CUDA_API DeviceIndex current_device();
|
||||
|
||||
C10_CUDA_API void set_device(DeviceIndex device, const bool force = false);
|
||||
|
||||
C10_CUDA_API void device_synchronize();
|
||||
|
||||
C10_CUDA_API void warn_or_error_on_sync();
|
||||
|
||||
// Raw CUDA device management functions
|
||||
C10_CUDA_API cudaError_t GetDeviceCount(int* dev_count);
|
||||
|
||||
C10_CUDA_API cudaError_t GetDevice(DeviceIndex* device);
|
||||
|
||||
C10_CUDA_API cudaError_t
|
||||
SetDevice(DeviceIndex device, const bool force = false);
|
||||
|
||||
C10_CUDA_API cudaError_t MaybeSetDevice(DeviceIndex device);
|
||||
|
||||
C10_CUDA_API DeviceIndex ExchangeDevice(DeviceIndex device);
|
||||
|
||||
C10_CUDA_API DeviceIndex MaybeExchangeDevice(DeviceIndex device);
|
||||
|
||||
C10_CUDA_API void SetTargetDevice();
|
||||
|
||||
enum class SyncDebugMode { L_DISABLED = 0, L_WARN, L_ERROR };
|
||||
|
||||
// this is a holder for c10 global state (similar to at GlobalContext)
|
||||
// currently it's used to store cuda synchronization warning state,
|
||||
// but can be expanded to hold other related global state, e.g. to
|
||||
// record stream usage
|
||||
class WarningState {
|
||||
public:
|
||||
void set_sync_debug_mode(SyncDebugMode l) {
|
||||
sync_debug_mode = l;
|
||||
}
|
||||
|
||||
SyncDebugMode get_sync_debug_mode() {
|
||||
return sync_debug_mode;
|
||||
}
|
||||
|
||||
private:
|
||||
SyncDebugMode sync_debug_mode = SyncDebugMode::L_DISABLED;
|
||||
};
|
||||
|
||||
C10_CUDA_API __inline__ WarningState& warning_state() {
|
||||
static WarningState warning_state_;
|
||||
return warning_state_;
|
||||
}
|
||||
// the subsequent functions are defined in the header because for performance
|
||||
// reasons we want them to be inline
|
||||
C10_CUDA_API void __inline__ memcpy_and_sync(
|
||||
void* dst,
|
||||
const void* src,
|
||||
int64_t nbytes,
|
||||
cudaMemcpyKind kind,
|
||||
cudaStream_t stream) {
|
||||
if (C10_UNLIKELY(
|
||||
warning_state().get_sync_debug_mode() != SyncDebugMode::L_DISABLED)) {
|
||||
warn_or_error_on_sync();
|
||||
}
|
||||
const c10::impl::PyInterpreter* interp = c10::impl::GPUTrace::get_trace();
|
||||
if (C10_UNLIKELY(interp)) {
|
||||
(*interp)->trace_gpu_stream_synchronization(
|
||||
c10::kCUDA, reinterpret_cast<uintptr_t>(stream));
|
||||
}
|
||||
#if defined(USE_ROCM) && USE_ROCM
|
||||
// As of ROCm 6.4.1, HIP runtime does not raise an error during capture of
|
||||
// hipMemcpyWithStream which is a synchronous call. Thus, we add a check
|
||||
// here explicitly.
|
||||
hipStreamCaptureStatus captureStatus;
|
||||
C10_CUDA_CHECK(hipStreamGetCaptureInfo(stream, &captureStatus, nullptr));
|
||||
if (C10_LIKELY(captureStatus == hipStreamCaptureStatusNone)) {
|
||||
C10_CUDA_CHECK(hipMemcpyWithStream(dst, src, nbytes, kind, stream));
|
||||
} else {
|
||||
C10_CUDA_CHECK(hipErrorStreamCaptureUnsupported);
|
||||
}
|
||||
#else
|
||||
C10_CUDA_CHECK(cudaMemcpyAsync(dst, src, nbytes, kind, stream));
|
||||
C10_CUDA_CHECK(cudaStreamSynchronize(stream));
|
||||
#endif
|
||||
}
|
||||
|
||||
C10_CUDA_API void __inline__ stream_synchronize(cudaStream_t stream) {
|
||||
if (C10_UNLIKELY(
|
||||
warning_state().get_sync_debug_mode() != SyncDebugMode::L_DISABLED)) {
|
||||
warn_or_error_on_sync();
|
||||
}
|
||||
const c10::impl::PyInterpreter* interp = c10::impl::GPUTrace::get_trace();
|
||||
if (C10_UNLIKELY(interp)) {
|
||||
(*interp)->trace_gpu_stream_synchronization(
|
||||
c10::kCUDA, reinterpret_cast<uintptr_t>(stream));
|
||||
}
|
||||
C10_CUDA_CHECK(cudaStreamSynchronize(stream));
|
||||
}
|
||||
|
||||
C10_CUDA_API bool hasPrimaryContext(DeviceIndex device_index);
|
||||
C10_CUDA_API std::optional<DeviceIndex> getDeviceIndexWithPrimaryContext();
|
||||
|
||||
} // namespace c10::cuda
|
||||
|
||||
#ifdef USE_ROCM
|
||||
// for backward-compat between hipify v1 and v2 for external projects
|
||||
namespace c10::hip {
|
||||
using c10::cuda::current_device;
|
||||
using c10::cuda::device_count;
|
||||
using c10::cuda::device_count_ensure_non_zero;
|
||||
using c10::cuda::device_synchronize;
|
||||
using c10::cuda::ExchangeDevice;
|
||||
using c10::cuda::GetDevice;
|
||||
using c10::cuda::GetDeviceCount;
|
||||
using c10::cuda::getDeviceIndexWithPrimaryContext;
|
||||
using c10::cuda::hasPrimaryContext;
|
||||
using c10::cuda::MaybeExchangeDevice;
|
||||
using c10::cuda::MaybeSetDevice;
|
||||
using c10::cuda::memcpy_and_sync;
|
||||
using c10::cuda::set_device;
|
||||
using c10::cuda::SetDevice;
|
||||
using c10::cuda::SetTargetDevice;
|
||||
using c10::cuda::stream_synchronize;
|
||||
using c10::cuda::warn_or_error_on_sync;
|
||||
} // namespace c10::hip
|
||||
#endif
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,114 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/Allocator.h>
|
||||
#include <c10/cuda/CUDAStream.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
|
||||
// CUDA Graphs utils used by c10 and aten.
|
||||
// aten/cuda/CUDAGraphsUtils.cuh adds utils used by aten only.
|
||||
|
||||
namespace c10::cuda {
|
||||
|
||||
// RAII guard for "cudaStreamCaptureMode", a thread-local value
|
||||
// that controls the error-checking strictness of a capture.
|
||||
struct C10_CUDA_API CUDAStreamCaptureModeGuard {
|
||||
CUDAStreamCaptureModeGuard(cudaStreamCaptureMode desired)
|
||||
: strictness_(desired) {
|
||||
C10_CUDA_CHECK(cudaThreadExchangeStreamCaptureMode(&strictness_));
|
||||
}
|
||||
CUDAStreamCaptureModeGuard(const CUDAStreamCaptureModeGuard&) = delete;
|
||||
CUDAStreamCaptureModeGuard(CUDAStreamCaptureModeGuard&&) = delete;
|
||||
CUDAStreamCaptureModeGuard& operator=(const CUDAStreamCaptureModeGuard&) =
|
||||
delete;
|
||||
CUDAStreamCaptureModeGuard& operator=(CUDAStreamCaptureModeGuard&&) = delete;
|
||||
~CUDAStreamCaptureModeGuard() {
|
||||
C10_CUDA_CHECK_WARN(cudaThreadExchangeStreamCaptureMode(&strictness_));
|
||||
}
|
||||
|
||||
private:
|
||||
cudaStreamCaptureMode strictness_;
|
||||
};
|
||||
|
||||
// Protects against enum cudaStreamCaptureStatus implementation changes.
|
||||
// Some compilers seem not to like static_assert without the messages.
|
||||
static_assert(
|
||||
int(cudaStreamCaptureStatus::cudaStreamCaptureStatusNone) == 0,
|
||||
"unexpected int(cudaStreamCaptureStatusNone) value");
|
||||
static_assert(
|
||||
int(cudaStreamCaptureStatus::cudaStreamCaptureStatusActive) == 1,
|
||||
"unexpected int(cudaStreamCaptureStatusActive) value");
|
||||
static_assert(
|
||||
int(cudaStreamCaptureStatus::cudaStreamCaptureStatusInvalidated) == 2,
|
||||
"unexpected int(cudaStreamCaptureStatusInvalidated) value");
|
||||
|
||||
enum class CaptureStatus : int {
|
||||
None = int(cudaStreamCaptureStatus::cudaStreamCaptureStatusNone),
|
||||
Active = int(cudaStreamCaptureStatus::cudaStreamCaptureStatusActive),
|
||||
Invalidated = int(cudaStreamCaptureStatus::cudaStreamCaptureStatusInvalidated)
|
||||
};
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& os, CaptureStatus status) {
|
||||
switch (status) {
|
||||
case CaptureStatus::None:
|
||||
os << "cudaStreamCaptureStatusNone";
|
||||
break;
|
||||
case CaptureStatus::Active:
|
||||
os << "cudaStreamCaptureStatusActive";
|
||||
break;
|
||||
case CaptureStatus::Invalidated:
|
||||
os << "cudaStreamCaptureStatusInvalidated";
|
||||
break;
|
||||
default:
|
||||
TORCH_INTERNAL_ASSERT(
|
||||
false, "Unknown CUDA graph CaptureStatus", int(status));
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
// Use this version where you're sure a CUDA context exists already.
|
||||
inline CaptureStatus currentStreamCaptureStatusMayInitCtx() {
|
||||
cudaStreamCaptureStatus status{cudaStreamCaptureStatusNone};
|
||||
C10_CUDA_CHECK(
|
||||
cudaStreamIsCapturing(c10::cuda::getCurrentCUDAStream(), &status));
|
||||
return CaptureStatus(status);
|
||||
}
|
||||
|
||||
inline CaptureStatus captureStatusMayInitCtx(cudaStream_t stream) {
|
||||
cudaStreamCaptureStatus status{cudaStreamCaptureStatusNone};
|
||||
C10_CUDA_CHECK(cudaStreamIsCapturing(stream, &status));
|
||||
return CaptureStatus(status);
|
||||
}
|
||||
|
||||
inline bool isStreamCapturingMayInitCtx(cudaStream_t stream) {
|
||||
return captureStatusMayInitCtx(stream) == CaptureStatus::Active;
|
||||
}
|
||||
|
||||
inline std::optional<CaptureId_t> currentStreamCaptureIdMayInitCtx() {
|
||||
cudaStreamCaptureStatus status{};
|
||||
CaptureId_t capture_id = 0;
|
||||
C10_CUDA_CHECK(cudaStreamGetCaptureInfo(
|
||||
c10::cuda::getCurrentCUDAStream(), &status, &capture_id));
|
||||
if (status == cudaStreamCaptureStatus::cudaStreamCaptureStatusActive) {
|
||||
return capture_id;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
inline std::optional<CaptureId_t> captureIdMayInitCtx(cudaStream_t stream) {
|
||||
cudaStreamCaptureStatus status{};
|
||||
CaptureId_t capture_id = 0;
|
||||
C10_CUDA_CHECK(cudaStreamGetCaptureInfo(stream, &status, &capture_id));
|
||||
if (status == cudaStreamCaptureStatus::cudaStreamCaptureStatusActive) {
|
||||
return capture_id;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace c10::cuda
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,311 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/DeviceType.h>
|
||||
#include <c10/core/impl/InlineDeviceGuard.h>
|
||||
#include <c10/core/impl/InlineStreamGuard.h>
|
||||
#include <c10/cuda/CUDAMacros.h>
|
||||
#include <c10/cuda/impl/CUDAGuardImpl.h>
|
||||
|
||||
namespace c10::cuda {
|
||||
|
||||
// This code is kind of boilerplatey. See Note [Whither the DeviceGuard
|
||||
// boilerplate]
|
||||
|
||||
/// A variant of DeviceGuard that is specialized for CUDA. It accepts
|
||||
/// integer indices (interpreting them as CUDA devices) and is a little
|
||||
/// more efficient than DeviceGuard (it compiles to straight line
|
||||
/// cudaSetDevice/cudaGetDevice calls); however, it can only be used
|
||||
/// from code that links against CUDA directly.
|
||||
struct CUDAGuard {
|
||||
/// No default constructor; see Note [Omitted default constructor from RAII]
|
||||
explicit CUDAGuard() = delete;
|
||||
|
||||
/// Set the current CUDA device to the passed device index.
|
||||
explicit CUDAGuard(DeviceIndex device_index) : guard_(device_index) {}
|
||||
|
||||
/// Sets the current CUDA device to the passed device. Errors if the passed
|
||||
/// device is not a CUDA device.
|
||||
explicit CUDAGuard(Device device) : guard_(device) {}
|
||||
|
||||
// Copy is not allowed
|
||||
CUDAGuard(const CUDAGuard&) = delete;
|
||||
CUDAGuard& operator=(const CUDAGuard&) = delete;
|
||||
|
||||
// Move is not allowed (there is no uninitialized state)
|
||||
CUDAGuard(CUDAGuard&& other) = delete;
|
||||
CUDAGuard& operator=(CUDAGuard&& other) = delete;
|
||||
~CUDAGuard() = default;
|
||||
|
||||
/// Sets the CUDA device to the given device. Errors if the given device
|
||||
/// is not a CUDA device.
|
||||
void set_device(Device device) {
|
||||
guard_.set_device(device);
|
||||
}
|
||||
|
||||
/// Sets the CUDA device to the given device. Errors if the given device
|
||||
/// is not a CUDA device. (This method is provided for uniformity with
|
||||
/// DeviceGuard).
|
||||
void reset_device(Device device) {
|
||||
guard_.reset_device(device);
|
||||
}
|
||||
|
||||
/// Sets the CUDA device to the given device index.
|
||||
void set_index(DeviceIndex device_index) {
|
||||
guard_.set_index(device_index);
|
||||
}
|
||||
|
||||
/// Returns the device that was set upon construction of the guard
|
||||
Device original_device() const {
|
||||
return guard_.original_device();
|
||||
}
|
||||
|
||||
/// Returns the last device that was set via `set_device`, if any, otherwise
|
||||
/// the device passed during construction.
|
||||
Device current_device() const {
|
||||
return guard_.current_device();
|
||||
}
|
||||
|
||||
private:
|
||||
/// The guard for the current device.
|
||||
c10::impl::InlineDeviceGuard<impl::CUDAGuardImpl> guard_;
|
||||
};
|
||||
|
||||
/// A variant of OptionalDeviceGuard that is specialized for CUDA. See
|
||||
/// CUDAGuard for when you can use this.
|
||||
struct OptionalCUDAGuard {
|
||||
/// Create an uninitialized OptionalCUDAGuard.
|
||||
explicit OptionalCUDAGuard() = default;
|
||||
|
||||
/// Set the current CUDA device to the passed Device, if it is not nullopt.
|
||||
explicit OptionalCUDAGuard(std::optional<Device> device_opt)
|
||||
: guard_(device_opt) {}
|
||||
|
||||
/// Set the current CUDA device to the passed device index, if it is not
|
||||
/// nullopt
|
||||
explicit OptionalCUDAGuard(std::optional<DeviceIndex> device_index_opt)
|
||||
: guard_(device_index_opt) {}
|
||||
|
||||
// Copy is not allowed
|
||||
OptionalCUDAGuard(const OptionalCUDAGuard&) = delete;
|
||||
OptionalCUDAGuard& operator=(const OptionalCUDAGuard&) = delete;
|
||||
|
||||
// See Note [Move construction for RAII guards is tricky]
|
||||
OptionalCUDAGuard(OptionalCUDAGuard&& other) = delete;
|
||||
|
||||
// See Note [Move assignment for RAII guards is tricky]
|
||||
OptionalCUDAGuard& operator=(OptionalCUDAGuard&& other) = delete;
|
||||
~OptionalCUDAGuard() = default;
|
||||
|
||||
/// Sets the CUDA device to the given device, initializing the guard if it
|
||||
/// is not already initialized. Errors if the given device is not a CUDA
|
||||
/// device.
|
||||
void set_device(Device device) {
|
||||
guard_.set_device(device);
|
||||
}
|
||||
|
||||
/// Sets the CUDA device to the given device, initializing the guard if it is
|
||||
/// not already initialized. Errors if the given device is not a CUDA device.
|
||||
/// (This method is provided for uniformity with OptionalDeviceGuard).
|
||||
void reset_device(Device device) {
|
||||
guard_.reset_device(device);
|
||||
}
|
||||
|
||||
/// Sets the CUDA device to the given device index, initializing the guard if
|
||||
/// it is not already initialized.
|
||||
void set_index(DeviceIndex device_index) {
|
||||
guard_.set_index(device_index);
|
||||
}
|
||||
|
||||
/// Returns the device that was set immediately prior to initialization of the
|
||||
/// guard, or nullopt if the guard is uninitialized.
|
||||
std::optional<Device> original_device() const {
|
||||
return guard_.original_device();
|
||||
}
|
||||
|
||||
/// Returns the most recent device that was set using this device guard,
|
||||
/// either from construction, or via set_device, if the guard is initialized,
|
||||
/// or nullopt if the guard is uninitialized.
|
||||
std::optional<Device> current_device() const {
|
||||
return guard_.current_device();
|
||||
}
|
||||
|
||||
/// Restore the original CUDA device, resetting this guard to uninitialized
|
||||
/// state.
|
||||
void reset() {
|
||||
guard_.reset();
|
||||
}
|
||||
|
||||
private:
|
||||
c10::impl::InlineOptionalDeviceGuard<impl::CUDAGuardImpl> guard_;
|
||||
};
|
||||
|
||||
/// A variant of StreamGuard that is specialized for CUDA. See CUDAGuard
|
||||
/// for when you can use this.
|
||||
struct CUDAStreamGuard {
|
||||
/// No default constructor, see Note [Omitted default constructor from RAII]
|
||||
explicit CUDAStreamGuard() = delete;
|
||||
|
||||
/// Set the current CUDA device to the device associated with the passed
|
||||
/// stream, and set the current CUDA stream on that device to the passed
|
||||
/// stream. Errors if the Stream is not a CUDA stream.
|
||||
explicit CUDAStreamGuard(Stream stream) : guard_(stream) {}
|
||||
~CUDAStreamGuard() = default;
|
||||
|
||||
/// Copy is disallowed
|
||||
CUDAStreamGuard(const CUDAStreamGuard&) = delete;
|
||||
CUDAStreamGuard& operator=(const CUDAStreamGuard&) = delete;
|
||||
|
||||
/// Move is disallowed, as CUDAStreamGuard does not have an uninitialized
|
||||
/// state, which is required for moves on types with nontrivial destructors.
|
||||
CUDAStreamGuard(CUDAStreamGuard&& other) = delete;
|
||||
CUDAStreamGuard& operator=(CUDAStreamGuard&& other) = delete;
|
||||
|
||||
/// Resets the currently set stream to the original stream and
|
||||
/// the currently set device to the original device. Then,
|
||||
/// set the current device to the device associated with the passed stream,
|
||||
/// and set the current stream on that device to the passed stream.
|
||||
/// Errors if the stream passed is not a CUDA stream.
|
||||
///
|
||||
/// NOTE: this implementation may skip some stream/device setting if
|
||||
/// it can prove that it is unnecessary.
|
||||
///
|
||||
/// WARNING: reset_stream does NOT preserve previously set streams on
|
||||
/// different devices. If you need to set streams on multiple devices
|
||||
/// on CUDA, use CUDAMultiStreamGuard instead.
|
||||
void reset_stream(Stream stream) {
|
||||
guard_.reset_stream(stream);
|
||||
}
|
||||
|
||||
/// Returns the CUDA stream that was set at the time the guard was
|
||||
/// constructed.
|
||||
CUDAStream original_stream() const {
|
||||
return CUDAStream(CUDAStream::UNCHECKED, guard_.original_stream());
|
||||
}
|
||||
|
||||
/// Returns the most recent CUDA stream that was set using this device guard,
|
||||
/// either from construction, or via set_stream.
|
||||
CUDAStream current_stream() const {
|
||||
return CUDAStream(CUDAStream::UNCHECKED, guard_.current_stream());
|
||||
}
|
||||
|
||||
/// Returns the most recent CUDA device that was set using this device guard,
|
||||
/// either from construction, or via set_device/reset_device/set_index.
|
||||
Device current_device() const {
|
||||
return guard_.current_device();
|
||||
}
|
||||
|
||||
/// Returns the CUDA device that was set at the most recent reset_stream(),
|
||||
/// or otherwise the device at construction time.
|
||||
Device original_device() const {
|
||||
return guard_.original_device();
|
||||
}
|
||||
|
||||
private:
|
||||
c10::impl::InlineStreamGuard<impl::CUDAGuardImpl> guard_;
|
||||
};
|
||||
|
||||
/// A variant of OptionalStreamGuard that is specialized for CUDA. See
|
||||
/// CUDAGuard for when you can use this.
|
||||
struct OptionalCUDAStreamGuard {
|
||||
/// Create an uninitialized guard.
|
||||
explicit OptionalCUDAStreamGuard() = default;
|
||||
|
||||
/// Set the current CUDA device to the device associated with the passed
|
||||
/// stream, and set the current CUDA stream on that device to the passed
|
||||
/// stream. Errors if the Stream is not a CUDA stream.
|
||||
explicit OptionalCUDAStreamGuard(Stream stream) : guard_(stream) {}
|
||||
|
||||
/// Set the current device to the device associated with the passed stream,
|
||||
/// and set the current stream on that device to the passed stream,
|
||||
/// if the passed stream is not nullopt.
|
||||
explicit OptionalCUDAStreamGuard(std::optional<Stream> stream_opt)
|
||||
: guard_(stream_opt) {}
|
||||
|
||||
/// Copy is disallowed
|
||||
OptionalCUDAStreamGuard(const OptionalCUDAStreamGuard&) = delete;
|
||||
OptionalCUDAStreamGuard& operator=(const OptionalCUDAStreamGuard&) = delete;
|
||||
|
||||
// See Note [Move construction for RAII guards is tricky]
|
||||
OptionalCUDAStreamGuard(OptionalCUDAStreamGuard&& other) = delete;
|
||||
|
||||
// See Note [Move assignment for RAII guards is tricky]
|
||||
OptionalCUDAStreamGuard& operator=(OptionalCUDAStreamGuard&& other) = delete;
|
||||
~OptionalCUDAStreamGuard() = default;
|
||||
|
||||
/// Resets the currently set CUDA stream to the original stream and
|
||||
/// the currently set device to the original device. Then,
|
||||
/// set the current device to the device associated with the passed stream,
|
||||
/// and set the current stream on that device to the passed stream.
|
||||
/// Initializes the guard if it was not previously initialized.
|
||||
void reset_stream(Stream stream) {
|
||||
guard_.reset_stream(stream);
|
||||
}
|
||||
|
||||
/// Returns the CUDA stream that was set at the time the guard was most
|
||||
/// recently initialized, or nullopt if the guard is uninitialized.
|
||||
std::optional<CUDAStream> original_stream() const {
|
||||
auto r = guard_.original_stream();
|
||||
if (r.has_value()) {
|
||||
return CUDAStream(CUDAStream::UNCHECKED, r.value());
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the most recent CUDA stream that was set using this stream guard,
|
||||
/// either from construction, or via reset_stream, if the guard is
|
||||
/// initialized, or nullopt if the guard is uninitialized.
|
||||
std::optional<CUDAStream> current_stream() const {
|
||||
auto r = guard_.current_stream();
|
||||
if (r.has_value()) {
|
||||
return CUDAStream(CUDAStream::UNCHECKED, r.value());
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
/// Restore the original CUDA device and stream, resetting this guard to
|
||||
/// uninitialized state.
|
||||
void reset() {
|
||||
guard_.reset();
|
||||
}
|
||||
|
||||
private:
|
||||
c10::impl::InlineOptionalStreamGuard<impl::CUDAGuardImpl> guard_;
|
||||
};
|
||||
|
||||
/// A variant of MultiStreamGuard that is specialized for CUDA.
|
||||
struct CUDAMultiStreamGuard {
|
||||
explicit CUDAMultiStreamGuard(ArrayRef<CUDAStream> streams)
|
||||
: guard_(unwrapStreams(streams)) {}
|
||||
|
||||
/// Copy is disallowed
|
||||
CUDAMultiStreamGuard(const CUDAMultiStreamGuard&) = delete;
|
||||
CUDAMultiStreamGuard& operator=(const CUDAMultiStreamGuard&) = delete;
|
||||
|
||||
// See Note [Move construction for RAII guards is tricky]
|
||||
CUDAMultiStreamGuard(CUDAMultiStreamGuard&& other) = delete;
|
||||
|
||||
// See Note [Move assignment for RAII guards is tricky]
|
||||
CUDAMultiStreamGuard& operator=(CUDAMultiStreamGuard&& other) = delete;
|
||||
~CUDAMultiStreamGuard() = default;
|
||||
|
||||
private:
|
||||
c10::impl::InlineMultiStreamGuard<impl::CUDAGuardImpl> guard_;
|
||||
|
||||
static std::vector<Stream> unwrapStreams(ArrayRef<CUDAStream> cudaStreams) {
|
||||
std::vector<Stream> streams;
|
||||
streams.reserve(cudaStreams.size());
|
||||
for (const CUDAStream& cudaStream : cudaStreams) {
|
||||
streams.push_back(cudaStream);
|
||||
}
|
||||
return streams;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace c10::cuda
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,56 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#ifndef C10_USING_CUSTOM_GENERATED_MACROS
|
||||
|
||||
// We have not yet modified the AMD HIP build to generate this file so
|
||||
// we add an extra option to specifically ignore it.
|
||||
#ifndef C10_CUDA_NO_CMAKE_CONFIGURE_FILE
|
||||
#include <c10/cuda/impl/cuda_cmake_macros.h>
|
||||
#endif // C10_CUDA_NO_CMAKE_CONFIGURE_FILE
|
||||
|
||||
#endif
|
||||
|
||||
// See c10/macros/Export.h for a detailed explanation of what the function
|
||||
// of these macros are. We need one set of macros for every separate library
|
||||
// we build.
|
||||
|
||||
#ifdef _WIN32
|
||||
#if defined(C10_CUDA_BUILD_SHARED_LIBS)
|
||||
#define C10_CUDA_EXPORT __declspec(dllexport)
|
||||
#define C10_CUDA_IMPORT __declspec(dllimport)
|
||||
#else
|
||||
#define C10_CUDA_EXPORT
|
||||
#define C10_CUDA_IMPORT
|
||||
#endif
|
||||
#else // _WIN32
|
||||
#if defined(__GNUC__)
|
||||
#define C10_CUDA_EXPORT __attribute__((__visibility__("default")))
|
||||
#else // defined(__GNUC__)
|
||||
#define C10_CUDA_EXPORT
|
||||
#endif // defined(__GNUC__)
|
||||
#define C10_CUDA_IMPORT C10_CUDA_EXPORT
|
||||
#endif // _WIN32
|
||||
|
||||
// This one is being used by libc10_cuda.so
|
||||
#ifdef C10_CUDA_BUILD_MAIN_LIB
|
||||
#define C10_CUDA_API C10_CUDA_EXPORT
|
||||
#else
|
||||
#define C10_CUDA_API C10_CUDA_IMPORT
|
||||
#endif
|
||||
|
||||
/**
|
||||
* The maximum number of GPUs that we recognizes. Increasing this beyond the
|
||||
* initial limit of 16 broke Caffe2 testing, hence the ifdef guards.
|
||||
* This value cannot be more than 128 because our DeviceIndex is a uint8_t.
|
||||
o */
|
||||
#ifdef FBCODE_CAFFE2
|
||||
// fbcode depends on this value being 16
|
||||
#define C10_COMPILE_TIME_MAX_GPUS 16
|
||||
#else
|
||||
#define C10_COMPILE_TIME_MAX_GPUS 120
|
||||
#endif
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,157 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
/* This file defines math functions compatible across different gpu
|
||||
* platforms (currently CUDA and HIP).
|
||||
*/
|
||||
#if defined(__CUDACC__) || defined(__HIPCC__)
|
||||
|
||||
#include <c10/macros/Macros.h>
|
||||
#include <c10/util/Exception.h>
|
||||
|
||||
#ifdef __HIPCC__
|
||||
#define __MATH_FUNCTIONS_DECL__ inline C10_DEVICE
|
||||
#else /* __HIPCC__ */
|
||||
#ifdef __CUDACC_RTC__
|
||||
#define __MATH_FUNCTIONS_DECL__ C10_HOST_DEVICE
|
||||
#else /* __CUDACC_RTC__ */
|
||||
#define __MATH_FUNCTIONS_DECL__ inline C10_HOST_DEVICE
|
||||
#endif /* __CUDACC_RTC__ */
|
||||
#endif /* __HIPCC__ */
|
||||
|
||||
namespace c10::cuda::compat {
|
||||
|
||||
__MATH_FUNCTIONS_DECL__ float abs(float x) {
|
||||
return ::fabsf(x);
|
||||
}
|
||||
__MATH_FUNCTIONS_DECL__ double abs(double x) {
|
||||
return ::fabs(x);
|
||||
}
|
||||
|
||||
__MATH_FUNCTIONS_DECL__ float exp(float x) {
|
||||
return ::expf(x);
|
||||
}
|
||||
__MATH_FUNCTIONS_DECL__ double exp(double x) {
|
||||
return ::exp(x);
|
||||
}
|
||||
|
||||
__MATH_FUNCTIONS_DECL__ float ceil(float x) {
|
||||
return ::ceilf(x);
|
||||
}
|
||||
__MATH_FUNCTIONS_DECL__ double ceil(double x) {
|
||||
return ::ceil(x);
|
||||
}
|
||||
|
||||
__MATH_FUNCTIONS_DECL__ float copysign(float x, float y) {
|
||||
#if defined(__CUDA_ARCH__) || defined(__HIPCC__)
|
||||
return ::copysignf(x, y);
|
||||
#else
|
||||
// std::copysign gets ICE/Segfaults with gcc 7.5/8 on arm64
|
||||
// (e.g. Jetson), see PyTorch PR #51834
|
||||
// This host function needs to be here for the compiler but is never used
|
||||
TORCH_INTERNAL_ASSERT(
|
||||
false, "CUDAMathCompat copysign should not run on the CPU");
|
||||
#endif
|
||||
}
|
||||
__MATH_FUNCTIONS_DECL__ double copysign(double x, double y) {
|
||||
#if defined(__CUDA_ARCH__) || defined(__HIPCC__)
|
||||
return ::copysign(x, y);
|
||||
#else
|
||||
// see above
|
||||
TORCH_INTERNAL_ASSERT(
|
||||
false, "CUDAMathCompat copysign should not run on the CPU");
|
||||
#endif
|
||||
}
|
||||
|
||||
__MATH_FUNCTIONS_DECL__ float floor(float x) {
|
||||
return ::floorf(x);
|
||||
}
|
||||
__MATH_FUNCTIONS_DECL__ double floor(double x) {
|
||||
return ::floor(x);
|
||||
}
|
||||
|
||||
__MATH_FUNCTIONS_DECL__ float log(float x) {
|
||||
return ::logf(x);
|
||||
}
|
||||
__MATH_FUNCTIONS_DECL__ double log(double x) {
|
||||
return ::log(x);
|
||||
}
|
||||
|
||||
__MATH_FUNCTIONS_DECL__ float log1p(float x) {
|
||||
return ::log1pf(x);
|
||||
}
|
||||
|
||||
__MATH_FUNCTIONS_DECL__ double log1p(double x) {
|
||||
return ::log1p(x);
|
||||
}
|
||||
|
||||
__MATH_FUNCTIONS_DECL__ float max(float x, float y) {
|
||||
return ::fmaxf(x, y);
|
||||
}
|
||||
__MATH_FUNCTIONS_DECL__ double max(double x, double y) {
|
||||
return ::fmax(x, y);
|
||||
}
|
||||
|
||||
__MATH_FUNCTIONS_DECL__ float min(float x, float y) {
|
||||
return ::fminf(x, y);
|
||||
}
|
||||
__MATH_FUNCTIONS_DECL__ double min(double x, double y) {
|
||||
return ::fmin(x, y);
|
||||
}
|
||||
|
||||
__MATH_FUNCTIONS_DECL__ float pow(float x, float y) {
|
||||
return ::powf(x, y);
|
||||
}
|
||||
__MATH_FUNCTIONS_DECL__ double pow(double x, double y) {
|
||||
return ::pow(x, y);
|
||||
}
|
||||
|
||||
__MATH_FUNCTIONS_DECL__ void sincos(float x, float* sptr, float* cptr) {
|
||||
return ::sincosf(x, sptr, cptr);
|
||||
}
|
||||
__MATH_FUNCTIONS_DECL__ void sincos(double x, double* sptr, double* cptr) {
|
||||
return ::sincos(x, sptr, cptr);
|
||||
}
|
||||
|
||||
__MATH_FUNCTIONS_DECL__ float sqrt(float x) {
|
||||
return ::sqrtf(x);
|
||||
}
|
||||
__MATH_FUNCTIONS_DECL__ double sqrt(double x) {
|
||||
return ::sqrt(x);
|
||||
}
|
||||
|
||||
__MATH_FUNCTIONS_DECL__ float rsqrt(float x) {
|
||||
return ::rsqrtf(x);
|
||||
}
|
||||
__MATH_FUNCTIONS_DECL__ double rsqrt(double x) {
|
||||
return ::rsqrt(x);
|
||||
}
|
||||
|
||||
__MATH_FUNCTIONS_DECL__ float tan(float x) {
|
||||
return ::tanf(x);
|
||||
}
|
||||
__MATH_FUNCTIONS_DECL__ double tan(double x) {
|
||||
return ::tan(x);
|
||||
}
|
||||
|
||||
__MATH_FUNCTIONS_DECL__ float tanh(float x) {
|
||||
return ::tanhf(x);
|
||||
}
|
||||
__MATH_FUNCTIONS_DECL__ double tanh(double x) {
|
||||
return ::tanh(x);
|
||||
}
|
||||
|
||||
__MATH_FUNCTIONS_DECL__ float normcdf(float x) {
|
||||
return ::normcdff(x);
|
||||
}
|
||||
__MATH_FUNCTIONS_DECL__ double normcdf(double x) {
|
||||
return ::normcdf(x);
|
||||
}
|
||||
|
||||
} // namespace c10::cuda::compat
|
||||
|
||||
#endif
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,20 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
// this file is to avoid circular dependency between CUDAFunctions.h and
|
||||
// CUDAExceptions.h
|
||||
|
||||
#include <c10/cuda/CUDAMacros.h>
|
||||
#include <cuda_runtime.h>
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
namespace c10::cuda {
|
||||
C10_CUDA_API std::string get_cuda_error_help(cudaError_t /*error*/) noexcept;
|
||||
C10_CUDA_API const char* get_cuda_check_suffix() noexcept;
|
||||
C10_CUDA_API std::mutex* getFreeMutex();
|
||||
} // namespace c10::cuda
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,303 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <cuda_runtime_api.h>
|
||||
|
||||
#include <c10/core/DeviceGuard.h>
|
||||
#include <c10/core/Stream.h>
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/util/Exception.h>
|
||||
|
||||
/*
|
||||
* Stream pool note.
|
||||
*
|
||||
* A CUDAStream is an abstraction of an actual cuStream on the GPU. CUDAStreams
|
||||
* are backed by cuStreams, but they use several pools to minimize the costs
|
||||
* associated with creating, retaining, and destroying cuStreams.
|
||||
*
|
||||
* There are three pools per device, and a device's pools are lazily created.
|
||||
*
|
||||
* The first pool contains only the default stream. When the default stream
|
||||
* is requested it's returned.
|
||||
*
|
||||
* The second pool is the "low priority" or "default priority" streams. In
|
||||
* HIP builds there is no distinction between streams in this pool and streams
|
||||
* in the third pool (below). There are 32 of these streams per device, and
|
||||
* when a stream is requested one of these streams is returned round-robin.
|
||||
* That is, the first stream requested is at index 0, the second at index 1...
|
||||
* to index 31, then index 0 again.
|
||||
*
|
||||
* This means that if 33 low priority streams are requested, the first and
|
||||
* last streams requested are actually the same stream (under the covers)
|
||||
* and kernels enqueued on them cannot run concurrently.
|
||||
*
|
||||
* The third pool is the "high priority" streams. The third pool acts like
|
||||
* the second pool except the streams are created with a higher priority.
|
||||
*
|
||||
* These pools suggest that stream users should prefer many short-lived streams,
|
||||
* as the cost of acquiring and releasing streams is effectively zero. If
|
||||
* many longer-lived streams are required in performance critical scenarios
|
||||
* then the functionality here may need to be extended to allow, for example,
|
||||
* "reserving" a subset of the pool so that other streams do not accidentally
|
||||
* overlap the performance critical streams.
|
||||
*
|
||||
* Note: although the notion of "current stream for device" is thread local
|
||||
* (every OS thread has a separate current stream, as one might expect),
|
||||
* the stream pool is global across all threads; stream 0 is always stream 0
|
||||
* no matter which thread you use it on. Multiple threads can synchronize
|
||||
* on the same stream. Although the CUDA documentation is not very clear
|
||||
* on the matter, streams are thread safe; e.g., it is safe to enqueue
|
||||
* a kernel on the same stream from two different threads.
|
||||
*/
|
||||
|
||||
namespace c10::cuda {
|
||||
|
||||
static constexpr int max_compile_time_stream_priorities = 4;
|
||||
|
||||
// Value object representing a CUDA stream. This is just a wrapper
|
||||
// around c10::Stream, but it comes with a little extra CUDA-specific
|
||||
// functionality (conversion to cudaStream_t), and a guarantee that
|
||||
// the wrapped c10::Stream really is a CUDA stream.
|
||||
class C10_CUDA_API CUDAStream {
|
||||
public:
|
||||
enum Unchecked { UNCHECKED };
|
||||
|
||||
/// Construct a CUDAStream from a Stream. This construction is checked,
|
||||
/// and will raise an error if the Stream is not, in fact, a CUDA stream.
|
||||
explicit CUDAStream(Stream stream) : stream_(stream) {
|
||||
TORCH_CHECK(stream_.device_type() == DeviceType::CUDA);
|
||||
}
|
||||
|
||||
/// Construct a CUDAStream from a Stream with no error checking.
|
||||
/// This constructor uses the "named" constructor idiom, and can
|
||||
/// be invoked as: CUDAStream(CUDAStream::UNCHECKED, stream)
|
||||
explicit CUDAStream(Unchecked /*unused*/, Stream stream) : stream_(stream) {}
|
||||
|
||||
bool operator==(const CUDAStream& other) const noexcept {
|
||||
return unwrap() == other.unwrap();
|
||||
}
|
||||
|
||||
bool operator!=(const CUDAStream& other) const noexcept {
|
||||
return unwrap() != other.unwrap();
|
||||
}
|
||||
|
||||
/// Implicit conversion to cudaStream_t.
|
||||
operator cudaStream_t() const {
|
||||
return stream();
|
||||
}
|
||||
|
||||
/// Implicit conversion to Stream (a.k.a., forget that the stream is a
|
||||
/// CUDA stream).
|
||||
operator Stream() const {
|
||||
return unwrap();
|
||||
}
|
||||
|
||||
/// Used to avoid baking in device type explicitly to Python-side API.
|
||||
DeviceType device_type() const {
|
||||
return DeviceType::CUDA;
|
||||
}
|
||||
|
||||
/// Get the CUDA device index that this stream is associated with.
|
||||
DeviceIndex device_index() const {
|
||||
return stream_.device_index();
|
||||
}
|
||||
|
||||
/// Get the full Device that this stream is associated with. The Device
|
||||
/// is guaranteed to be a CUDA device.
|
||||
Device device() const {
|
||||
return Device(DeviceType::CUDA, device_index());
|
||||
}
|
||||
|
||||
/// Return the stream ID corresponding to this particular stream.
|
||||
StreamId id() const {
|
||||
return stream_.id();
|
||||
}
|
||||
|
||||
bool query() const;
|
||||
|
||||
void synchronize() const;
|
||||
|
||||
bool is_capturing() const {
|
||||
DeviceGuard guard{stream_.device()};
|
||||
cudaStreamCaptureStatus status{cudaStreamCaptureStatusNone};
|
||||
C10_CUDA_CHECK(cudaStreamIsCapturing(stream(), &status));
|
||||
return status != cudaStreamCaptureStatusNone;
|
||||
}
|
||||
|
||||
int priority() const {
|
||||
DeviceGuard guard{stream_.device()};
|
||||
int priority = 0;
|
||||
C10_CUDA_CHECK(cudaStreamGetPriority(stream(), &priority));
|
||||
return priority;
|
||||
}
|
||||
|
||||
/// Explicit conversion to cudaStream_t.
|
||||
cudaStream_t stream() const;
|
||||
|
||||
/// Explicit conversion to Stream.
|
||||
Stream unwrap() const {
|
||||
return stream_;
|
||||
}
|
||||
|
||||
/// Reversibly pack a CUDAStream into a struct representation.
|
||||
/// Previously the stream's data was packed into a single int64_t,
|
||||
/// as it was assumed the fields would not require more than
|
||||
/// 64 bits of storage in total.
|
||||
/// See https://github.com/pytorch/pytorch/issues/75854
|
||||
/// for more information regarding newer platforms that may violate
|
||||
/// this assumption.
|
||||
///
|
||||
/// The CUDAStream can be unpacked using unpack().
|
||||
struct c10::StreamData3 pack3() const {
|
||||
return stream_.pack3();
|
||||
}
|
||||
|
||||
// Unpack a CUDAStream from the 3 fields generated by pack().
|
||||
static CUDAStream unpack3(
|
||||
StreamId stream_id,
|
||||
DeviceIndex device_index,
|
||||
DeviceType device_type) {
|
||||
return CUDAStream(Stream::unpack3(stream_id, device_index, device_type));
|
||||
}
|
||||
|
||||
static std::tuple<int, int> priority_range() {
|
||||
// Note: this returns the range of priority **supported by PyTorch**, not
|
||||
// the range of priority **supported by CUDA**. The former is a subset of
|
||||
// the latter.
|
||||
int least_priority = 0, greatest_priority = 0;
|
||||
C10_CUDA_CHECK(
|
||||
cudaDeviceGetStreamPriorityRange(&least_priority, &greatest_priority));
|
||||
#ifdef USE_ROCM
|
||||
// See Note [HIP stream priorities]
|
||||
TORCH_INTERNAL_ASSERT(
|
||||
least_priority == 1, "Unexpected HIP stream priority range");
|
||||
least_priority = 0;
|
||||
#else
|
||||
TORCH_INTERNAL_ASSERT(
|
||||
least_priority == 0, "Unexpected CUDA stream priority range");
|
||||
#endif
|
||||
TORCH_INTERNAL_ASSERT(
|
||||
greatest_priority <= -1, "Unexpected CUDA stream priority range");
|
||||
greatest_priority = std::max(
|
||||
-c10::cuda::max_compile_time_stream_priorities + 1, greatest_priority);
|
||||
return std::make_tuple(least_priority, greatest_priority);
|
||||
}
|
||||
|
||||
// Deleted for now; use CUDAEvent::block instead
|
||||
// void synchronize_with(const CUDAEvent& event) const;
|
||||
|
||||
private:
|
||||
Stream stream_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a new stream from the CUDA stream pool. You can think of this
|
||||
* as "creating" a new stream, but no such creation actually happens;
|
||||
* instead, streams are preallocated from the pool and returned in a
|
||||
* round-robin fashion.
|
||||
*
|
||||
* You can request a stream from the high priority pool by setting
|
||||
* isHighPriority to true, or a stream for a specific device by setting device
|
||||
* (defaulting to the current CUDA stream.)
|
||||
*/
|
||||
C10_API CUDAStream
|
||||
getStreamFromPool(const bool isHighPriority = false, DeviceIndex device = -1);
|
||||
// no default priority to disambiguate overloads
|
||||
C10_API CUDAStream
|
||||
getStreamFromPool(const int priority, DeviceIndex device = -1);
|
||||
|
||||
/**
|
||||
* Get a CUDAStream from a externally allocated one.
|
||||
*
|
||||
* This is mainly for interoperability with different libraries where we
|
||||
* want to operate on a non-torch allocated stream for data exchange or similar
|
||||
* purposes
|
||||
*/
|
||||
C10_API CUDAStream
|
||||
getStreamFromExternal(cudaStream_t ext_stream, DeviceIndex device_index);
|
||||
|
||||
/**
|
||||
* Get the default CUDA stream, for the passed CUDA device, or for the
|
||||
* current device if no device index is passed. The default stream is
|
||||
* where most computation occurs when you aren't explicitly using
|
||||
* streams.
|
||||
*/
|
||||
C10_API CUDAStream getDefaultCUDAStream(DeviceIndex device_index = -1);
|
||||
|
||||
/**
|
||||
* Get the current CUDA stream, for the passed CUDA device, or for the
|
||||
* current device if no device index is passed. The current CUDA stream
|
||||
* will usually be the default CUDA stream for the device, but it may
|
||||
* be different if someone called 'setCurrentCUDAStream' or used 'StreamGuard'
|
||||
* or 'CUDAStreamGuard'.
|
||||
*/
|
||||
C10_API CUDAStream getCurrentCUDAStream(DeviceIndex device_index = -1);
|
||||
|
||||
/**
|
||||
* Set the current stream on the device of the passed in stream to be
|
||||
* the passed in stream. Yes, you read that right: this function
|
||||
* has *nothing* to do with the current device: it toggles the current
|
||||
* stream of the device of the passed stream.
|
||||
*
|
||||
* Confused? Avoid using this function; prefer using 'CUDAStreamGuard' instead
|
||||
* (which will switch both your current device and current stream in the way you
|
||||
* expect, and reset it back to its original state afterwards).
|
||||
*/
|
||||
C10_API void setCurrentCUDAStream(CUDAStream stream);
|
||||
|
||||
C10_API std::ostream& operator<<(std::ostream& stream, const CUDAStream& s);
|
||||
|
||||
} // namespace c10::cuda
|
||||
|
||||
// hipify v2 backward compat in external projects
|
||||
#ifdef USE_ROCM
|
||||
namespace c10::hip {
|
||||
using c10::cuda::getStreamFromExternal;
|
||||
using c10::cuda::getStreamFromPool;
|
||||
// must use inline wrappers instead of reference aliases due to default args
|
||||
inline c10::cuda::CUDAStream getDefaultHIPStream(
|
||||
DeviceIndex device_index = -1) {
|
||||
return c10::cuda::getDefaultCUDAStream(device_index);
|
||||
}
|
||||
inline c10::cuda::CUDAStream getCurrentHIPStream(
|
||||
DeviceIndex device_index = -1) {
|
||||
return c10::cuda::getCurrentCUDAStream(device_index);
|
||||
}
|
||||
inline auto& setCurrentHIPStream = c10::cuda::setCurrentCUDAStream;
|
||||
inline c10::cuda::CUDAStream getStreamFromPoolMasqueradingAsCUDA(
|
||||
const bool isHighPriority = false,
|
||||
DeviceIndex device = -1) {
|
||||
return c10::cuda::getStreamFromPool(isHighPriority, device);
|
||||
}
|
||||
inline c10::cuda::CUDAStream getStreamFromPoolMasqueradingAsCUDA(
|
||||
const int priority,
|
||||
DeviceIndex device = -1) {
|
||||
return c10::cuda::getStreamFromPool(priority, device);
|
||||
}
|
||||
inline auto& getStreamFromExternalMasqueradingAsCUDA =
|
||||
c10::cuda::getStreamFromExternal;
|
||||
inline c10::cuda::CUDAStream getDefaultHIPStreamMasqueradingAsCUDA(
|
||||
DeviceIndex device_index = -1) {
|
||||
return c10::cuda::getDefaultCUDAStream(device_index);
|
||||
}
|
||||
inline c10::cuda::CUDAStream getCurrentHIPStreamMasqueradingAsCUDA(
|
||||
DeviceIndex device_index = -1) {
|
||||
return c10::cuda::getCurrentCUDAStream(device_index);
|
||||
}
|
||||
inline auto& setCurrentHIPStreamMasqueradingAsCUDA =
|
||||
c10::cuda::setCurrentCUDAStream;
|
||||
} // namespace c10::hip
|
||||
#endif
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<c10::cuda::CUDAStream> {
|
||||
size_t operator()(c10::cuda::CUDAStream s) const noexcept {
|
||||
return std::hash<c10::Stream>{}(s.unwrap());
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,54 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/Device.h>
|
||||
#include <c10/cuda/CUDAMacros.h>
|
||||
#include <c10/macros/Macros.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace c10::cuda {
|
||||
|
||||
namespace detail {
|
||||
|
||||
/// Initialize the peer-to-peer and fabric access caches.
|
||||
/// Must be called before any calls to get_p2p_access or get_fabric_access.
|
||||
/// @param num_devices The number of CUDA devices in the system.
|
||||
C10_CUDA_API void init_p2p_access_cache(int64_t num_devices);
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/// Query if peer-to-peer access is available between two devices.
|
||||
/// @param source_dev The source device index.
|
||||
/// @param dest_dev The destination device index.
|
||||
/// @return true if P2P access is available, false otherwise.
|
||||
C10_CUDA_API bool get_p2p_access(
|
||||
c10::DeviceIndex source_dev,
|
||||
c10::DeviceIndex dest_dev);
|
||||
|
||||
/// Query if GPU fabric (high-speed interconnect like NVLink/NVSwitch) is
|
||||
/// available for a device. This checks both hardware support and the ability
|
||||
/// to allocate/export/import memory with fabric handles.
|
||||
/// @param device The device index to check.
|
||||
/// @return true if fabric access is available, false otherwise.
|
||||
C10_CUDA_API bool get_fabric_access(c10::DeviceIndex device);
|
||||
|
||||
constexpr int kCliqueIdNotQueried = -2;
|
||||
constexpr int kCliqueIdUnsupported = -1;
|
||||
|
||||
/// Query the NVLink fabric clique ID for a device.
|
||||
/// Returns the clique ID (>= 0) if fabric is supported, or kCliqueIdUnsupported
|
||||
/// if unsupported.
|
||||
C10_CUDA_API int get_fabric_clique_id(c10::DeviceIndex device);
|
||||
|
||||
/// Returns a formatted string with NVML fabric info (clique_id, cluster_uuid,
|
||||
/// state, status, health_mask) for the given device. Intended for error
|
||||
/// diagnostics — only call on failure paths.
|
||||
C10_CUDA_API std::string get_nvml_fabric_info(c10::DeviceIndex device);
|
||||
|
||||
} // namespace c10::cuda
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,151 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
#include <cuda.h>
|
||||
#define NVML_NO_UNVERSIONED_FUNC_DEFS
|
||||
#include <nvml.h>
|
||||
|
||||
#include <c10/util/Exception.h>
|
||||
|
||||
#define C10_CUDA_DRIVER_CHECK(EXPR) \
|
||||
do { \
|
||||
CUresult __err = EXPR; \
|
||||
if (__err != CUDA_SUCCESS) { \
|
||||
const char* err_str; \
|
||||
CUresult get_error_str_err [[maybe_unused]] = \
|
||||
c10::cuda::DriverAPI::get()->cuGetErrorString_(__err, &err_str); \
|
||||
if (get_error_str_err != CUDA_SUCCESS) { \
|
||||
TORCH_CHECK(false, "CUDA driver error: unknown error"); \
|
||||
} else { \
|
||||
TORCH_CHECK(false, "CUDA driver error: ", err_str); \
|
||||
} \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
// clang-format off
|
||||
#define C10_CUDA_DRIVER_CHECK_MSG(EXPR, ...) \
|
||||
do { \
|
||||
CUresult __err = EXPR; \
|
||||
if (__err != CUDA_SUCCESS) { \
|
||||
const char* err_str; \
|
||||
CUresult get_error_str_err [[maybe_unused]] = \
|
||||
c10::cuda::DriverAPI::get()->cuGetErrorString_(__err, &err_str); \
|
||||
if (get_error_str_err != CUDA_SUCCESS) { \
|
||||
TORCH_CHECK(false, "CUDA driver error: unknown error", __VA_ARGS__);\
|
||||
} else { \
|
||||
TORCH_CHECK(false, "CUDA driver error: ", err_str, __VA_ARGS__); \
|
||||
} \
|
||||
} \
|
||||
} while (0)
|
||||
// clang-format on
|
||||
|
||||
#define C10_CUDA_DRIVER_CHECK_GOTO(EXPR, NEXT) \
|
||||
do { \
|
||||
CUresult __err = EXPR; \
|
||||
if (__err != CUDA_SUCCESS) { \
|
||||
const char* err_str; \
|
||||
CUresult get_error_str_err [[maybe_unused]] = \
|
||||
c10::cuda::DriverAPI::get()->cuGetErrorString_(__err, &err_str); \
|
||||
if (get_error_str_err != CUDA_SUCCESS) { \
|
||||
TORCH_WARN("CUDA driver error: unknown error"); \
|
||||
} else { \
|
||||
TORCH_WARN("CUDA driver error: ", err_str); \
|
||||
} \
|
||||
goto NEXT; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
// The integer in the second column specifies the requested CUDA Driver API
|
||||
// version. The dynamic loader will accept a driver with a newer version, but it
|
||||
// ensures that the requested symbol exists in *at least* the specified version
|
||||
// or earlier.
|
||||
|
||||
// Keep these requested versions as low as possible to maximize compatibility
|
||||
// across different driver versions.
|
||||
|
||||
// Why do we pin to an older version instead of using the latest?
|
||||
// If a user installs a newer driver, blindly resolving the symbol may bind to a
|
||||
// newer version of the function with different behavior, potentially breaking
|
||||
// PyTorch.
|
||||
|
||||
#define C10_LIBCUDA_DRIVER_API_REQUIRED(_) \
|
||||
_(cuDeviceGet, 12000) \
|
||||
_(cuDeviceGetAttribute, 12000) \
|
||||
_(cuMemGetAddressRange, 12000) \
|
||||
_(cuMemAddressReserve, 12000) \
|
||||
_(cuMemRelease, 12000) \
|
||||
_(cuMemMap, 12000) \
|
||||
_(cuMemAddressFree, 12000) \
|
||||
_(cuMemSetAccess, 12000) \
|
||||
_(cuMemUnmap, 12000) \
|
||||
_(cuMemCreate, 12000) \
|
||||
_(cuMemGetAllocationGranularity, 12000) \
|
||||
_(cuMemExportToShareableHandle, 12000) \
|
||||
_(cuMemImportFromShareableHandle, 12000) \
|
||||
_(cuMemRetainAllocationHandle, 12000) \
|
||||
_(cuMemGetAllocationPropertiesFromHandle, 12000) \
|
||||
_(cuMemsetD32Async, 12000) \
|
||||
_(cuStreamWriteValue32, 12000) \
|
||||
_(cuGetErrorString, 12000)
|
||||
|
||||
#if defined(CUDA_VERSION) && (CUDA_VERSION >= 12080)
|
||||
#define C10_LIBCUDA_DRIVER_API_OPTIONAL(_) \
|
||||
_(cuCtxFromGreenCtx, 12080) \
|
||||
_(cuCtxGetCurrent, 12080) \
|
||||
_(cuCtxPopCurrent, 12080) \
|
||||
_(cuCtxPushCurrent, 12080) \
|
||||
_(cuCtxSetCurrent, 12080) \
|
||||
_(cuGreenCtxCreate, 12080) \
|
||||
_(cuGreenCtxDestroy, 12080) \
|
||||
_(cuGreenCtxStreamCreate, 12080) \
|
||||
_(cuDevSmResourceSplitByCount, 12080) \
|
||||
_(cuDeviceGetDevResource, 12080) \
|
||||
_(cuDevResourceGenerateDesc, 12080) \
|
||||
_(cuMulticastAddDevice, 12030) \
|
||||
_(cuMulticastBindMem, 12030) \
|
||||
_(cuMulticastCreate, 12030) \
|
||||
_(cuMulticastUnbind, 12030)
|
||||
#elif defined(CUDA_VERSION) && (CUDA_VERSION >= 12030)
|
||||
#define C10_LIBCUDA_DRIVER_API_OPTIONAL(_) \
|
||||
_(cuMulticastAddDevice, 12030) \
|
||||
_(cuMulticastBindMem, 12030) \
|
||||
_(cuMulticastCreate, 12030) \
|
||||
_(cuMulticastUnbind, 12030)
|
||||
#else
|
||||
#define C10_LIBCUDA_DRIVER_API_OPTIONAL(_)
|
||||
#endif
|
||||
|
||||
#define C10_NVML_DRIVER_API(_) \
|
||||
_(nvmlInit_v2) \
|
||||
_(nvmlDeviceGetHandleByPciBusId_v2) \
|
||||
_(nvmlDeviceGetNvLinkRemoteDeviceType) \
|
||||
_(nvmlDeviceGetNvLinkRemotePciInfo_v2) \
|
||||
_(nvmlDeviceGetComputeRunningProcesses) \
|
||||
_(nvmlSystemGetCudaDriverVersion_v2)
|
||||
|
||||
#if defined(CUDA_VERSION) && (CUDA_VERSION >= 12040)
|
||||
#define C10_NVML_DRIVER_API_OPTIONAL(_) _(nvmlDeviceGetGpuFabricInfoV)
|
||||
#else
|
||||
#define C10_NVML_DRIVER_API_OPTIONAL(_)
|
||||
#endif
|
||||
|
||||
namespace c10::cuda {
|
||||
|
||||
struct DriverAPI {
|
||||
#define CREATE_MEMBER_VERSIONED(name, version) decltype(&name) name##_;
|
||||
#define CREATE_MEMBER(name) decltype(&name) name##_;
|
||||
C10_LIBCUDA_DRIVER_API_REQUIRED(CREATE_MEMBER_VERSIONED)
|
||||
C10_LIBCUDA_DRIVER_API_OPTIONAL(CREATE_MEMBER_VERSIONED)
|
||||
C10_NVML_DRIVER_API(CREATE_MEMBER)
|
||||
C10_NVML_DRIVER_API_OPTIONAL(CREATE_MEMBER)
|
||||
#undef CREATE_MEMBER_VERSIONED
|
||||
#undef CREATE_MEMBER
|
||||
|
||||
static DriverAPI* get();
|
||||
static void* get_nvml_handle();
|
||||
};
|
||||
|
||||
} // namespace c10::cuda
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,279 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/core/impl/DeviceGuardImplInterface.h>
|
||||
#include <c10/core/impl/GPUTrace.h>
|
||||
#include <c10/macros/Macros.h>
|
||||
#include <c10/util/Exception.h>
|
||||
|
||||
#include <c10/cuda/CUDACachingAllocator.h>
|
||||
#include <c10/cuda/CUDAException.h>
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAStream.h>
|
||||
|
||||
#include <c10/core/Device.h>
|
||||
#include <c10/core/DeviceType.h>
|
||||
#include <c10/core/Stream.h>
|
||||
#include <c10/core/impl/PyInterpreter.h>
|
||||
#include <cuda_runtime_api.h>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
namespace c10::cuda::impl {
|
||||
|
||||
struct CUDAGuardImpl final : public c10::impl::DeviceGuardImplInterface {
|
||||
static constexpr DeviceType static_type = DeviceType::CUDA;
|
||||
|
||||
CUDAGuardImpl() = default;
|
||||
explicit CUDAGuardImpl(DeviceType t) {
|
||||
TORCH_CHECK(
|
||||
t == DeviceType::CUDA,
|
||||
"CUDAGuardImpl initialized with non-CUDA DeviceType: ",
|
||||
t);
|
||||
}
|
||||
DeviceType type() const override {
|
||||
return DeviceType::CUDA;
|
||||
}
|
||||
Device exchangeDevice(Device d) const override {
|
||||
TORCH_CHECK(d.is_cuda(), "Expected a CUDA device, but got ", d);
|
||||
auto old_device_index = c10::cuda::ExchangeDevice(d.index());
|
||||
return Device(DeviceType::CUDA, old_device_index);
|
||||
}
|
||||
Device getDevice() const override {
|
||||
DeviceIndex device = 0;
|
||||
C10_CUDA_CHECK(c10::cuda::GetDevice(&device));
|
||||
return Device(DeviceType::CUDA, device);
|
||||
}
|
||||
std::optional<Device> uncheckedGetDevice() const noexcept {
|
||||
DeviceIndex device{-1};
|
||||
const auto err = C10_CUDA_ERROR_HANDLED(c10::cuda::GetDevice(&device));
|
||||
C10_CUDA_CHECK_WARN(err);
|
||||
if (err != cudaSuccess) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return Device(DeviceType::CUDA, device);
|
||||
}
|
||||
void setDevice(Device d) const override {
|
||||
TORCH_CHECK(d.is_cuda(), "Expected a CUDA device, but got ", d);
|
||||
C10_CUDA_CHECK(c10::cuda::SetDevice(d.index()));
|
||||
}
|
||||
void uncheckedSetDevice(Device d) const noexcept override {
|
||||
C10_CUDA_CHECK_WARN(c10::cuda::MaybeSetDevice(d.index()));
|
||||
}
|
||||
Stream getStream(Device d) const override {
|
||||
return getCurrentCUDAStream(d.index()).unwrap();
|
||||
}
|
||||
Stream getDefaultStream(Device d) const override {
|
||||
return getDefaultCUDAStream(d.index());
|
||||
}
|
||||
Stream getNewStream(Device d, int priority = 0) const override {
|
||||
return getStreamFromPool(priority, d.index());
|
||||
}
|
||||
Stream getStreamFromGlobalPool(Device d, bool isHighPriority = false)
|
||||
const override {
|
||||
return getStreamFromPool(isHighPriority, d.index());
|
||||
}
|
||||
// NB: These do NOT set the current device
|
||||
Stream exchangeStream(Stream s) const override {
|
||||
CUDAStream cs(s);
|
||||
auto old_stream = getCurrentCUDAStream(s.device().index());
|
||||
setCurrentCUDAStream(cs);
|
||||
return old_stream.unwrap();
|
||||
}
|
||||
void* getStreamNativeHandle(const Stream s) const override {
|
||||
CUDAStream stream{s};
|
||||
return reinterpret_cast<void*>(stream.stream());
|
||||
}
|
||||
DeviceIndex deviceCount() const noexcept override {
|
||||
return device_count();
|
||||
}
|
||||
|
||||
// Event-related functions
|
||||
void createEvent(cudaEvent_t* cuda_event, const EventFlag flag) const {
|
||||
// Maps PyTorch's Event::Flag to CUDA flag
|
||||
auto cuda_flag = cudaEventDefault;
|
||||
switch (flag) {
|
||||
case EventFlag::PYTORCH_DEFAULT:
|
||||
cuda_flag = cudaEventDisableTiming;
|
||||
break;
|
||||
case EventFlag::BACKEND_DEFAULT:
|
||||
cuda_flag = cudaEventDefault;
|
||||
break;
|
||||
default:
|
||||
TORCH_CHECK(false, "CUDA event received unknown flag");
|
||||
}
|
||||
|
||||
C10_CUDA_CHECK(cudaEventCreateWithFlags(cuda_event, cuda_flag));
|
||||
const c10::impl::PyInterpreter* interp = c10::impl::GPUTrace::get_trace();
|
||||
if (C10_UNLIKELY(interp)) {
|
||||
(*interp)->trace_gpu_event_creation(
|
||||
c10::kCUDA, reinterpret_cast<uintptr_t>(cuda_event));
|
||||
}
|
||||
}
|
||||
|
||||
void destroyEvent(void* event, const DeviceIndex device_index)
|
||||
const noexcept override {
|
||||
if (!event)
|
||||
return;
|
||||
auto cuda_event = static_cast<cudaEvent_t>(event);
|
||||
DeviceIndex orig_device{-1};
|
||||
C10_CUDA_CHECK_WARN(c10::cuda::GetDevice(&orig_device));
|
||||
C10_CUDA_CHECK_WARN(c10::cuda::SetDevice(device_index));
|
||||
const c10::impl::PyInterpreter* interp = c10::impl::GPUTrace::get_trace();
|
||||
if (C10_UNLIKELY(interp)) {
|
||||
(*interp)->trace_gpu_event_deletion(
|
||||
c10::kCUDA, reinterpret_cast<uintptr_t>(cuda_event));
|
||||
}
|
||||
C10_CUDA_CHECK_WARN(cudaEventDestroy(cuda_event));
|
||||
C10_CUDA_CHECK_WARN(c10::cuda::SetDevice(orig_device));
|
||||
}
|
||||
|
||||
void record(
|
||||
void** event,
|
||||
const Stream& stream,
|
||||
const DeviceIndex device_index,
|
||||
const EventFlag flag) const override {
|
||||
TORCH_CHECK(
|
||||
device_index == -1 || device_index == stream.device_index(),
|
||||
"Event device index ",
|
||||
device_index,
|
||||
" does not match recording stream's device index ",
|
||||
stream.device_index(),
|
||||
".");
|
||||
|
||||
cudaEvent_t cuda_event = static_cast<cudaEvent_t>(*event);
|
||||
CUDAStream cuda_stream{stream};
|
||||
|
||||
// Moves to stream's device to record
|
||||
const auto orig_device = getDevice();
|
||||
setDevice(stream.device());
|
||||
|
||||
// Creates the event (lazily)
|
||||
if (!cuda_event)
|
||||
createEvent(&cuda_event, flag);
|
||||
C10_CUDA_CHECK(cudaEventRecord(cuda_event, cuda_stream));
|
||||
// Makes the void* point to the (possibly just allocated) CUDA event
|
||||
*event = cuda_event;
|
||||
const c10::impl::PyInterpreter* interp = c10::impl::GPUTrace::get_trace();
|
||||
if (C10_UNLIKELY(interp)) {
|
||||
(*interp)->trace_gpu_event_record(
|
||||
c10::kCUDA,
|
||||
reinterpret_cast<uintptr_t>(cuda_event),
|
||||
reinterpret_cast<uintptr_t>(cuda_stream.stream()));
|
||||
}
|
||||
|
||||
// Resets device
|
||||
setDevice(orig_device);
|
||||
}
|
||||
|
||||
void block(void* event, const Stream& stream) const override {
|
||||
if (!event)
|
||||
return;
|
||||
cudaEvent_t cuda_event = static_cast<cudaEvent_t>(event);
|
||||
CUDAStream cuda_stream{stream};
|
||||
const auto orig_device = getDevice();
|
||||
setDevice(stream.device());
|
||||
C10_CUDA_CHECK(cudaStreamWaitEvent(
|
||||
cuda_stream,
|
||||
cuda_event,
|
||||
/*flags (must be zero)=*/0));
|
||||
const c10::impl::PyInterpreter* interp = c10::impl::GPUTrace::get_trace();
|
||||
if (C10_UNLIKELY(interp)) {
|
||||
(*interp)->trace_gpu_event_wait(
|
||||
c10::kCUDA,
|
||||
reinterpret_cast<uintptr_t>(cuda_event),
|
||||
reinterpret_cast<uintptr_t>(cuda_stream.stream()));
|
||||
}
|
||||
setDevice(orig_device);
|
||||
}
|
||||
|
||||
// May be called from any device
|
||||
bool queryEvent(void* event) const override {
|
||||
if (!event)
|
||||
return true;
|
||||
cudaEvent_t cuda_event = static_cast<cudaEvent_t>(event);
|
||||
// Note: cudaEventQuery can be safely called from any device
|
||||
const cudaError_t err = C10_CUDA_ERROR_HANDLED(cudaEventQuery(cuda_event));
|
||||
if (err != cudaErrorNotReady) {
|
||||
C10_CUDA_CHECK(err);
|
||||
} else {
|
||||
// ignore and clear the error if not ready
|
||||
(void)cudaGetLastError();
|
||||
}
|
||||
return (err == cudaSuccess);
|
||||
}
|
||||
|
||||
// Stream-related functions
|
||||
bool queryStream(const Stream& stream) const override {
|
||||
CUDAStream cuda_stream{stream};
|
||||
return cuda_stream.query();
|
||||
}
|
||||
|
||||
void synchronizeStream(const Stream& stream) const override {
|
||||
CUDAStream cuda_stream{stream};
|
||||
cuda_stream.synchronize();
|
||||
}
|
||||
|
||||
bool isStreamCapturing(const Stream& stream) const override {
|
||||
CUDAStream cuda_stream{stream};
|
||||
return cuda_stream.is_capturing();
|
||||
}
|
||||
|
||||
void synchronizeEvent(void* event) const override {
|
||||
if (!event)
|
||||
return;
|
||||
cudaEvent_t cuda_event = static_cast<cudaEvent_t>(event);
|
||||
const c10::impl::PyInterpreter* interp = c10::impl::GPUTrace::get_trace();
|
||||
if (C10_UNLIKELY(interp)) {
|
||||
(*interp)->trace_gpu_event_synchronization(
|
||||
c10::kCUDA, reinterpret_cast<uintptr_t>(cuda_event));
|
||||
}
|
||||
// Note: cudaEventSynchronize can be safely called from any device
|
||||
C10_CUDA_CHECK(cudaEventSynchronize(cuda_event));
|
||||
}
|
||||
|
||||
// Note: synchronizeDevice can be safely called from any device
|
||||
void synchronizeDevice(const c10::DeviceIndex device_index) const override {
|
||||
DeviceIndex orig_device{-1};
|
||||
C10_CUDA_CHECK(c10::cuda::GetDevice(&orig_device));
|
||||
C10_CUDA_CHECK(c10::cuda::SetDevice(device_index));
|
||||
const c10::impl::PyInterpreter* interp = c10::impl::GPUTrace::get_trace();
|
||||
if (C10_UNLIKELY(interp)) {
|
||||
(*interp)->trace_gpu_device_synchronization(c10::kCUDA);
|
||||
}
|
||||
C10_CUDA_CHECK(cudaDeviceSynchronize());
|
||||
C10_CUDA_CHECK(c10::cuda::SetDevice(orig_device));
|
||||
}
|
||||
|
||||
void recordDataPtrOnStream(const c10::DataPtr& data_ptr, const Stream& stream)
|
||||
const override {
|
||||
CUDAStream cuda_stream{stream};
|
||||
CUDACachingAllocator::recordStream(data_ptr, cuda_stream);
|
||||
}
|
||||
|
||||
double elapsedTime(void* event1, void* event2, const DeviceIndex device_index)
|
||||
const override {
|
||||
TORCH_CHECK(
|
||||
event1 && event2,
|
||||
"Both events must be recorded before calculating elapsed time.");
|
||||
// Even though cudaEventElapsedTime can be safely called from any device, if
|
||||
// the current device is not initialized, it will create a new cuda context,
|
||||
// which will consume a lot of memory.
|
||||
DeviceIndex orig_device{-1};
|
||||
C10_CUDA_CHECK(c10::cuda::GetDevice(&orig_device));
|
||||
C10_CUDA_CHECK(c10::cuda::SetDevice(device_index));
|
||||
cudaEvent_t cuda_event1 = static_cast<cudaEvent_t>(event1);
|
||||
cudaEvent_t cuda_event2 = static_cast<cudaEvent_t>(event2);
|
||||
float time_ms = 0;
|
||||
// raise cudaErrorNotReady if either event is recorded but not yet completed
|
||||
C10_CUDA_CHECK(cudaEventElapsedTime(&time_ms, cuda_event1, cuda_event2));
|
||||
C10_CUDA_CHECK(c10::cuda::SetDevice(orig_device));
|
||||
return static_cast<double>(time_ms);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace c10::cuda::impl
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,14 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/cuda/CUDAMacros.h>
|
||||
|
||||
namespace c10::cuda::impl {
|
||||
|
||||
C10_CUDA_API int c10_cuda_test();
|
||||
|
||||
}
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,6 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#include <torch/headeronly/macros/Export.h>
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,6 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#include <torch/headeronly/macros/Macros.h>
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,10 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
// This file exists for backwards compatibility and has been moved to
|
||||
// torch/headeronly/macros/cmake_macros.h.in. No end user library should be
|
||||
// including this file directly anyway (cuz they should be including
|
||||
// Macros.h instead).
|
||||
#include <torch/headeronly/macros/cmake_macros.h>
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,289 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
#include <metal_atomic>
|
||||
namespace c10 {
|
||||
namespace metal {
|
||||
|
||||
// Atomic operations helper
|
||||
template <typename T>
|
||||
struct AtomicType {};
|
||||
template <typename T>
|
||||
using AtomicType_t = typename AtomicType<T>::type;
|
||||
|
||||
template <typename AT, typename T>
|
||||
static inline void atomic_binary_op_helper(
|
||||
device ::metal::atomic<AT>* data,
|
||||
long offset,
|
||||
T value,
|
||||
T (*op)(T, T)) {
|
||||
auto ptr = data + offset;
|
||||
auto old = ::metal::atomic_load_explicit(ptr, ::metal::memory_order_relaxed);
|
||||
T val;
|
||||
do {
|
||||
val = op(old, value);
|
||||
} while (!::metal::atomic_compare_exchange_weak_explicit(
|
||||
ptr,
|
||||
&old,
|
||||
val,
|
||||
::metal::memory_order_relaxed,
|
||||
::metal::memory_order_relaxed));
|
||||
}
|
||||
|
||||
template <>
|
||||
struct AtomicType<float> {
|
||||
using type = ::metal::atomic<float>;
|
||||
static inline void atomic_add(device type* data, long offset, float value) {
|
||||
::metal::atomic_fetch_add_explicit(
|
||||
data + offset, value, ::metal::memory_order_relaxed);
|
||||
}
|
||||
static inline void atomic_binary_op(
|
||||
device type* data,
|
||||
long offset,
|
||||
float value,
|
||||
float (*op)(float, float)) {
|
||||
atomic_binary_op_helper(data, offset, value, op);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AtomicType<int> {
|
||||
using type = ::metal::atomic<int>;
|
||||
static inline void atomic_add(device type* data, long offset, int value) {
|
||||
::metal::atomic_fetch_add_explicit(
|
||||
data + offset, value, ::metal::memory_order_relaxed);
|
||||
}
|
||||
static inline void atomic_binary_op(
|
||||
device type* data,
|
||||
long offset,
|
||||
int value,
|
||||
int (*op)(int, int)) {
|
||||
atomic_binary_op_helper(data, offset, value, op);
|
||||
}
|
||||
};
|
||||
|
||||
// As of Metal3.2 atomic operations are not supported on half-precision floats,
|
||||
// so they must be simulated Using atomic compare and exchange over 32-bit
|
||||
// atomic type
|
||||
template <typename T>
|
||||
static inline void atomic_add_helper(
|
||||
device ::metal::atomic<uint>* data,
|
||||
long offset,
|
||||
T value) {
|
||||
// atomic<uint> requires 4-byte alignment; fix up misaligned pointers
|
||||
auto addr = reinterpret_cast<ulong>(data);
|
||||
auto misalign = (addr % alignof(::metal::atomic<uint>)) / sizeof(T);
|
||||
data = reinterpret_cast<device ::metal::atomic<uint>*>(
|
||||
reinterpret_cast<device char*>(data) - misalign * sizeof(T));
|
||||
offset += misalign;
|
||||
|
||||
constexpr auto elem_per_enum = sizeof(uint) / sizeof(T);
|
||||
auto ptr = data + (offset / elem_per_enum);
|
||||
auto old = ::metal::atomic_load_explicit(ptr, ::metal::memory_order_relaxed);
|
||||
union {
|
||||
uint i;
|
||||
T t[elem_per_enum];
|
||||
} val;
|
||||
do {
|
||||
val.i = old;
|
||||
val.t[offset & (elem_per_enum - 1)] += value;
|
||||
} while (!::metal::atomic_compare_exchange_weak_explicit(
|
||||
ptr,
|
||||
&old,
|
||||
val.i,
|
||||
::metal::memory_order_relaxed,
|
||||
::metal::memory_order_relaxed));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline void atomic_binary_op_helper(
|
||||
device ::metal::atomic<uint>* data,
|
||||
long offset,
|
||||
T value,
|
||||
T (*Op)(T, T)) {
|
||||
// atomic<uint> requires 4-byte alignment; fix up misaligned pointers
|
||||
auto addr = reinterpret_cast<ulong>(data);
|
||||
auto misalign = (addr % alignof(::metal::atomic<uint>)) / sizeof(T);
|
||||
data = reinterpret_cast<device ::metal::atomic<uint>*>(
|
||||
reinterpret_cast<device char*>(data) - misalign * sizeof(T));
|
||||
offset += misalign;
|
||||
|
||||
constexpr auto elem_per_enum = sizeof(uint) / sizeof(T);
|
||||
auto ptr = data + (offset / elem_per_enum);
|
||||
auto old = ::metal::atomic_load_explicit(ptr, ::metal::memory_order_relaxed);
|
||||
union {
|
||||
uint i;
|
||||
T t[elem_per_enum];
|
||||
} val;
|
||||
do {
|
||||
val.i = old;
|
||||
val.t[offset & (elem_per_enum - 1)] =
|
||||
Op(val.t[offset & (elem_per_enum - 1)], value);
|
||||
} while (!::metal::atomic_compare_exchange_weak_explicit(
|
||||
ptr,
|
||||
&old,
|
||||
val.i,
|
||||
::metal::memory_order_relaxed,
|
||||
::metal::memory_order_relaxed));
|
||||
}
|
||||
|
||||
template <>
|
||||
struct AtomicType<half> {
|
||||
using type = ::metal::atomic<uint>;
|
||||
static inline void atomic_add(device type* data, long offset, half value) {
|
||||
atomic_add_helper(data, offset, value);
|
||||
}
|
||||
static inline void atomic_binary_op(
|
||||
device type* data,
|
||||
long offset,
|
||||
half value,
|
||||
half (*op)(half, half)) {
|
||||
atomic_binary_op_helper(data, offset, value, op);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AtomicType<short> {
|
||||
using type = ::metal::atomic<uint>;
|
||||
static inline void atomic_add(device type* data, long offset, short value) {
|
||||
atomic_add_helper(data, offset, value);
|
||||
}
|
||||
static inline void atomic_binary_op(
|
||||
device type* data,
|
||||
long offset,
|
||||
short value,
|
||||
short (*op)(short, short)) {
|
||||
atomic_binary_op_helper(data, offset, value, op);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AtomicType<char> {
|
||||
using type = ::metal::atomic<uint>;
|
||||
static inline void atomic_add(device type* data, long offset, char value) {
|
||||
atomic_add_helper(data, offset, value);
|
||||
}
|
||||
static inline void atomic_binary_op(
|
||||
device type* data,
|
||||
long offset,
|
||||
char value,
|
||||
char (*op)(char, char)) {
|
||||
atomic_binary_op_helper(data, offset, value, op);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AtomicType<uchar> {
|
||||
using type = ::metal::atomic<uint>;
|
||||
static inline void atomic_add(device type* data, long offset, char value) {
|
||||
atomic_add_helper(data, offset, value);
|
||||
}
|
||||
static inline void atomic_binary_op(
|
||||
device type* data,
|
||||
long offset,
|
||||
uchar value,
|
||||
uchar (*op)(uchar, uchar)) {
|
||||
atomic_binary_op_helper(data, offset, value, op);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AtomicType<bfloat> {
|
||||
using type = ::metal::atomic<uint>;
|
||||
static inline void atomic_add(device type* data, long offset, bfloat value) {
|
||||
atomic_add_helper<bfloat>(data, offset, value);
|
||||
}
|
||||
static inline void atomic_binary_op(
|
||||
device type* data,
|
||||
long offset,
|
||||
bfloat value,
|
||||
bfloat (*op)(bfloat, bfloat)) {
|
||||
atomic_binary_op_helper(data, offset, value, op);
|
||||
}
|
||||
};
|
||||
|
||||
// Metal supports atomic_store_explicit for bools, but
|
||||
// sizeof(::metal::atomic_bool) is 4 Therefore it could not be used to
|
||||
// atomically modify unaligned memory, so fall back to compare and exchange
|
||||
// trick As accumulation over booleans are just or operation, do nothing if
|
||||
// value is false
|
||||
template <>
|
||||
struct AtomicType<bool> {
|
||||
using type = ::metal::atomic<uint>;
|
||||
static inline void atomic_add(device type* data, long offset, bool value) {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
auto ptr = data + (offset >> 2);
|
||||
auto old =
|
||||
::metal::atomic_load_explicit(ptr, ::metal::memory_order_relaxed);
|
||||
union {
|
||||
uint i;
|
||||
bool t[4];
|
||||
} val;
|
||||
do {
|
||||
val.i = old;
|
||||
val.t[offset & 3] = true;
|
||||
} while (!::metal::atomic_compare_exchange_weak_explicit(
|
||||
ptr,
|
||||
&old,
|
||||
val.i,
|
||||
::metal::memory_order_relaxed,
|
||||
::metal::memory_order_relaxed));
|
||||
}
|
||||
};
|
||||
|
||||
// ComplexHalf atomic op
|
||||
template <>
|
||||
struct AtomicType<half2> {
|
||||
using type = ::metal::atomic<uint>;
|
||||
static inline void atomic_add(device type* data, long offset, half2 value) {
|
||||
auto ptr = data + offset;
|
||||
auto old =
|
||||
::metal::atomic_load_explicit(ptr, ::metal::memory_order_relaxed);
|
||||
while (!::metal::atomic_compare_exchange_weak_explicit(
|
||||
ptr,
|
||||
&old,
|
||||
as_type<uint>(as_type<half2>(old) + value),
|
||||
::metal::memory_order_relaxed,
|
||||
::metal::memory_order_relaxed))
|
||||
;
|
||||
}
|
||||
};
|
||||
|
||||
// There are no atomic 64-bit add in Metal yet, but templates below implements a
|
||||
// consistent add I.e. if multiple threads are modify the same 64-bit value,
|
||||
// results stored at the address will eventually be equal to its original value
|
||||
// plus sum of all operands
|
||||
template <>
|
||||
struct AtomicType<long> {
|
||||
using type = ::metal::atomic<uint>;
|
||||
static inline void atomic_add(device type* data, long offset, long value) {
|
||||
const auto value_bits = as_type<ulong>(value);
|
||||
const uint low = static_cast<uint>(value_bits);
|
||||
uint high = static_cast<uint>(value_bits >> 32);
|
||||
auto ptr = data + (offset << 1);
|
||||
auto old_low =
|
||||
atomic_fetch_add_explicit(ptr, low, ::metal::memory_order_relaxed);
|
||||
high += (old_low + low < old_low) ? 1 : 0;
|
||||
atomic_fetch_add_explicit(ptr + 1, high, ::metal::memory_order_relaxed);
|
||||
}
|
||||
};
|
||||
|
||||
// ComplexFloat atomic op, which again is not really atomic, but eventually
|
||||
// consistent
|
||||
template <>
|
||||
struct AtomicType<float2> {
|
||||
using type = ::metal::atomic<float>;
|
||||
static inline void atomic_add(device type* data, long offset, float2 value) {
|
||||
auto ptr = data + (offset << 1);
|
||||
atomic_fetch_add_explicit(ptr + 0, value.x, ::metal::memory_order_relaxed);
|
||||
atomic_fetch_add_explicit(ptr + 1, value.y, ::metal::memory_order_relaxed);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace metal
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,53 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
// Set of global constants that could be shareable between CPU and Metal code
|
||||
|
||||
#ifdef __METAL__
|
||||
#include <metal_array>
|
||||
#define C10_METAL_CONSTEXPR constant constexpr
|
||||
#else
|
||||
#include <array>
|
||||
#define C10_METAL_CONSTEXPR constexpr
|
||||
#endif
|
||||
|
||||
#define C10_METAL_ALL_TYPES_FUNCTOR(_) \
|
||||
_(Byte, 0) \
|
||||
_(Char, 1) \
|
||||
_(Short, 2) \
|
||||
_(Int, 3) \
|
||||
_(Long, 4) \
|
||||
_(Half, 5) \
|
||||
_(Float, 6) \
|
||||
_(ComplexHalf, 8) \
|
||||
_(ComplexFloat, 9) \
|
||||
_(Bool, 11) \
|
||||
_(BFloat16, 15) \
|
||||
_(UInt16, 27) \
|
||||
_(UInt32, 28) \
|
||||
_(UInt64, 29)
|
||||
|
||||
namespace c10 {
|
||||
namespace metal {
|
||||
C10_METAL_CONSTEXPR unsigned max_ndim = 16;
|
||||
C10_METAL_CONSTEXPR unsigned simdgroup_size = 32;
|
||||
|
||||
#ifdef __METAL__
|
||||
template <typename T, unsigned N>
|
||||
using array = ::metal::array<T, N>;
|
||||
#else
|
||||
template <typename T, unsigned N>
|
||||
using array = std::array<T, N>;
|
||||
#endif
|
||||
|
||||
enum class ScalarType {
|
||||
#define _DEFINE_ENUM_VAL_(_v, _n) _v = _n,
|
||||
C10_METAL_ALL_TYPES_FUNCTOR(_DEFINE_ENUM_VAL_)
|
||||
#undef _DEFINE_ENUM_VAL_
|
||||
};
|
||||
|
||||
} // namespace metal
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,116 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
#include <c10/metal/common.h>
|
||||
|
||||
namespace c10 {
|
||||
namespace metal {
|
||||
C10_METAL_CONSTEXPR unsigned error_message_count = 30;
|
||||
struct ErrorMessage {
|
||||
char file[128];
|
||||
char func[128];
|
||||
char message[250];
|
||||
unsigned int line;
|
||||
};
|
||||
|
||||
struct ErrorMessages {
|
||||
#ifdef __METAL__
|
||||
::metal::atomic<unsigned int> count;
|
||||
#else
|
||||
unsigned int count;
|
||||
#endif
|
||||
ErrorMessage msg[error_message_count];
|
||||
};
|
||||
|
||||
#ifdef __METAL__
|
||||
namespace detail {
|
||||
static uint strncpy(device char* dst, constant const char* src, unsigned len) {
|
||||
uint i = 0;
|
||||
while (src[i] != 0 && i < len - 1) {
|
||||
dst[i] = src[i];
|
||||
i++;
|
||||
}
|
||||
dst[i] = 0;
|
||||
return i;
|
||||
}
|
||||
|
||||
inline uint print_arg(
|
||||
device char* ptr,
|
||||
unsigned len,
|
||||
constant const char* arg) {
|
||||
return strncpy(ptr, arg, len);
|
||||
}
|
||||
|
||||
// Returns number length as string in base10
|
||||
static inline uint base10_length(long num) {
|
||||
uint rc = 1;
|
||||
if (num < 0) {
|
||||
num = -num;
|
||||
rc += 1;
|
||||
}
|
||||
while (num > 9) {
|
||||
num /= 10;
|
||||
rc++;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
// Converts signed integer to string
|
||||
inline uint print_arg(device char* ptr, unsigned len, long arg) {
|
||||
const auto arg_len = base10_length(arg);
|
||||
if (arg_len >= len)
|
||||
return 0;
|
||||
if (arg < 0) {
|
||||
ptr[0] = '-';
|
||||
arg = -arg;
|
||||
}
|
||||
uint idx = 1;
|
||||
do {
|
||||
ptr[arg_len - idx] = '0' + (arg % 10);
|
||||
arg /= 10;
|
||||
idx++;
|
||||
} while (arg > 0);
|
||||
ptr[arg_len] = 0;
|
||||
return arg_len;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void print_args(device char* ptr, unsigned len, T arg) {
|
||||
print_arg(ptr, len, arg);
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
inline void print_args(device char* ptr, unsigned len, T arg, Args... args) {
|
||||
const auto rc = print_arg(ptr, len, arg);
|
||||
print_args(ptr + rc, len - rc, args...);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename... Args>
|
||||
static void report_error(
|
||||
device ErrorMessages* msgs,
|
||||
constant const char* file,
|
||||
int line,
|
||||
constant const char* func,
|
||||
Args... args) {
|
||||
const auto idx =
|
||||
atomic_fetch_add_explicit(&msgs->count, 1, ::metal::memory_order_relaxed);
|
||||
if (idx >= error_message_count) {
|
||||
return;
|
||||
}
|
||||
device auto* msg = &msgs->msg[idx];
|
||||
detail::strncpy(msg->file, file, 128);
|
||||
detail::strncpy(msg->func, func, 128);
|
||||
detail::print_args(msg->message, 250, args...);
|
||||
msg->line = line;
|
||||
}
|
||||
|
||||
#define TORCH_REPORT_ERROR(buf, ...) \
|
||||
::c10::metal::report_error(buf, __FILE__, __LINE__, __func__, __VA_ARGS__)
|
||||
#endif
|
||||
} // namespace metal
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,102 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
// Copy-and-pasted from:
|
||||
// https://github.com/ml-explore/mlx/blob/99c33d011d63174f50cea37c3eede002958be6d3/mlx/backend/metal/kernels/expm1f.h
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <metal_math>
|
||||
|
||||
// Original license copied below:
|
||||
// Copyright (c) 2015-2023 Norbert Juffa
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
namespace c10 {
|
||||
namespace metal {
|
||||
|
||||
/* Compute exponential base e minus 1. Maximum ulp error = 0.997458
|
||||
|
||||
i = rint(a/log(2)), f = a-i*log(2). Then expm1(a) = 2**i * (expm1(f)+1) - 1.
|
||||
Compute r = expm1(f). Then expm1(a)= 2 * (0.5 * 2**i * r + 0.5 * 2**i - 0.5).
|
||||
With t = 0.5*2**i, expm1(a) = 2*(r * t + t-0.5). However, for best accuracy,
|
||||
when i == 1, expm1(a)= 2*(r + 0.5), and when i == 0, expm1(a) = r.
|
||||
|
||||
NOTE: Scale factor b is only applied if i < 0 or i > 1 (should be power of 2)
|
||||
*/
|
||||
inline float expm1f_scaled_unchecked(float a, float b) {
|
||||
float f, j, r, s, t, u, v, x, y;
|
||||
int i;
|
||||
|
||||
// exp(a) = 2**i * exp(f); i = rintf (a / log(2))
|
||||
j = ::metal::fma(1.442695f, a, 12582912.f); // 0x1.715476p0, 0x1.8p23
|
||||
j = j - 12582912.0f; // 0x1.8p23
|
||||
i = (int)j;
|
||||
f = ::metal::fma(j, -6.93145752e-1f, a);
|
||||
|
||||
// approximate r = exp(f)-1 on interval [-log(2)/2, +log(2)/2]
|
||||
s = f * f;
|
||||
if (a == 0.0f)
|
||||
s = a; // ensure -0 is passed through
|
||||
// err = 0.997458 ulp1 = 11081805
|
||||
r = 1.97350979e-4f; // 0x1.9de000p-13
|
||||
r = ::metal::fma(r, f, 1.39309070e-3f); // 0x1.6d30bcp-10
|
||||
r = ::metal::fma(r, f, 8.33343994e-3f); // 0x1.1111f6p-7
|
||||
r = ::metal::fma(r, f, 4.16668020e-2f); // 0x1.55559ep-5
|
||||
r = ::metal::fma(r, f, 1.66666716e-1f); // 0x1.55555cp-3
|
||||
r = ::metal::fma(r, f, 4.99999970e-1f); // 0x1.fffffep-2
|
||||
u = (j == 1) ? (f + 0.5f) : f;
|
||||
v = ::metal::fma(r, s, u);
|
||||
s = 0.5f * b;
|
||||
t = ::metal::ldexp(s, i);
|
||||
y = t - s;
|
||||
x = (t - y) - s; // double-float canonicalization of difference
|
||||
r = ::metal::fma(v, t, x) + y;
|
||||
r = r + r;
|
||||
if (j == 0)
|
||||
r = v;
|
||||
if (j == 1)
|
||||
r = v + v;
|
||||
return r;
|
||||
}
|
||||
|
||||
/* Compute exponential base e minus 1. max ulp err = 0.99746 */
|
||||
inline float expm1f(float a) {
|
||||
float r;
|
||||
|
||||
r = expm1f_scaled_unchecked(a, 1.0f);
|
||||
/* handle severe overflow and underflow */
|
||||
if (::metal::abs(a - 1.0f) > 88.0f) {
|
||||
r = ::metal::pow(2, a);
|
||||
r = ::metal::fma(r, r, -1.0f);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
} // namespace metal
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,749 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/metal/utils.h>
|
||||
#include <metal_math>
|
||||
#include <metal_stdlib>
|
||||
|
||||
using namespace c10::metal;
|
||||
using namespace metal;
|
||||
|
||||
namespace c10 {
|
||||
namespace metal {
|
||||
|
||||
template <typename T>
|
||||
inline float log_gamma(const T);
|
||||
|
||||
inline float expm1f(float a);
|
||||
|
||||
template <typename T>
|
||||
float erfc(T x);
|
||||
|
||||
} // namespace metal
|
||||
} // namespace c10
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename T>
|
||||
inline float lgamma(const T a) {
|
||||
return log_gamma(a);
|
||||
}
|
||||
|
||||
inline float expm1(float a) {
|
||||
return expm1f(a);
|
||||
}
|
||||
|
||||
// NOTE: The following code was ported directly from the CUDA implementation in
|
||||
// `aten/src/ATen/native/cuda/IGammaKernel.cu`
|
||||
|
||||
/*
|
||||
* This implementation of the regularized incomplete gamma functions and
|
||||
* their helper functions are derived from the implementation of SciPy's
|
||||
* gammainc, Cephes's igam and igamc, and Boost's Lanczos approximations.
|
||||
* See NOTICE for the licenses.
|
||||
*/
|
||||
// regularized lower & upper incomplete gamma
|
||||
template <typename scalar_t>
|
||||
scalar_t ratevl(
|
||||
scalar_t x,
|
||||
const scalar_t num[],
|
||||
int64_t M,
|
||||
const scalar_t denom[],
|
||||
int64_t N) {
|
||||
// evaluating rational function, i.e., the ratio of two polynomials
|
||||
// the coefficients for numerator are given by `num` while coeffs for
|
||||
// denumerator are given by `denom`
|
||||
|
||||
using accscalar_t = opmath_t<scalar_t>;
|
||||
int64_t i, dir;
|
||||
accscalar_t y, num_ans, denom_ans;
|
||||
accscalar_t absx = ::fabs(x);
|
||||
thread const accscalar_t* p;
|
||||
|
||||
if (absx > 1) {
|
||||
/* Evaluate as a polynomial in 1/x. */
|
||||
dir = -1;
|
||||
p = num + M;
|
||||
y = 1 / x;
|
||||
} else {
|
||||
dir = 1;
|
||||
p = num;
|
||||
y = x;
|
||||
}
|
||||
|
||||
/* Evaluate the numerator */
|
||||
num_ans = *p;
|
||||
p += dir;
|
||||
for (i = 1; i <= M; i++) {
|
||||
num_ans = num_ans * y + *p;
|
||||
p += dir;
|
||||
}
|
||||
/* Evaluate the denominator */
|
||||
if (absx > 1) {
|
||||
p = denom + N;
|
||||
} else {
|
||||
p = denom;
|
||||
}
|
||||
|
||||
denom_ans = *p;
|
||||
p += dir;
|
||||
for (i = 1; i <= N; i++) {
|
||||
denom_ans = denom_ans * y + *p;
|
||||
p += dir;
|
||||
}
|
||||
if (absx > 1) {
|
||||
i = N - M;
|
||||
return ::pow(x, static_cast<accscalar_t>(i)) * num_ans / denom_ans;
|
||||
} else {
|
||||
return num_ans / denom_ans;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
scalar_t lanczos_sum_expg_scaled(scalar_t x) {
|
||||
// lanczos approximation
|
||||
using accscalar_t = opmath_t<scalar_t>;
|
||||
|
||||
const accscalar_t lanczos_sum_expg_scaled_num[13] = {
|
||||
0.006061842346248906525783753964555936883222,
|
||||
0.5098416655656676188125178644804694509993,
|
||||
19.51992788247617482847860966235652136208,
|
||||
449.9445569063168119446858607650988409623,
|
||||
6955.999602515376140356310115515198987526,
|
||||
75999.29304014542649875303443598909137092,
|
||||
601859.6171681098786670226533699352302507,
|
||||
3481712.15498064590882071018964774556468,
|
||||
14605578.08768506808414169982791359218571,
|
||||
43338889.32467613834773723740590533316085,
|
||||
86363131.28813859145546927288977868422342,
|
||||
103794043.1163445451906271053616070238554,
|
||||
56906521.91347156388090791033559122686859};
|
||||
const accscalar_t lanczos_sum_expg_scaled_denom[13] = {
|
||||
1.,
|
||||
66.,
|
||||
1925.,
|
||||
32670.,
|
||||
357423.,
|
||||
2637558.,
|
||||
13339535.,
|
||||
45995730.,
|
||||
105258076.,
|
||||
150917976.,
|
||||
120543840.,
|
||||
39916800.,
|
||||
0};
|
||||
return ratevl(
|
||||
static_cast<accscalar_t>(x),
|
||||
lanczos_sum_expg_scaled_num,
|
||||
sizeof(lanczos_sum_expg_scaled_num) /
|
||||
sizeof(lanczos_sum_expg_scaled_num[0]) -
|
||||
1,
|
||||
lanczos_sum_expg_scaled_denom,
|
||||
sizeof(lanczos_sum_expg_scaled_denom) /
|
||||
sizeof(lanczos_sum_expg_scaled_denom[0]) -
|
||||
1);
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
scalar_t _igam_helper_fac(scalar_t a, scalar_t x) {
|
||||
// compute x^a * exp(-a) / gamma(a)
|
||||
// corrected from (15) and (16) in [igam2] by replacing exp(x - a) with
|
||||
// exp(a - x).
|
||||
|
||||
using accscalar_t = opmath_t<scalar_t>;
|
||||
accscalar_t ax, fac, res, num, numfac;
|
||||
const accscalar_t MAXLOG = 88.72283905206835;
|
||||
const accscalar_t EXP1 = 2.718281828459045;
|
||||
const accscalar_t lanczos_g = 6.024680040776729583740234375;
|
||||
|
||||
if (::fabs(a - x) > 0.4 * ::fabs(a)) {
|
||||
ax = a * ::log(x) - x - ::lgamma(a);
|
||||
if (ax < -MAXLOG) {
|
||||
return 0.0;
|
||||
}
|
||||
return ::exp(ax);
|
||||
}
|
||||
|
||||
fac = a + lanczos_g - 0.5;
|
||||
res = ::sqrt(fac / EXP1) / lanczos_sum_expg_scaled(a);
|
||||
|
||||
if ((a < 200) && (x < 200)) {
|
||||
res *= ::exp(a - x) * ::pow(x / fac, a);
|
||||
} else {
|
||||
num = x - a - lanczos_g + 0.5;
|
||||
numfac = num / fac;
|
||||
res *= ::exp(a * (::log1p(numfac) - numfac) + x * (0.5 - lanczos_g) / fac);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
scalar_t _igam_helper_series(scalar_t a, scalar_t x) {
|
||||
// Compute igam using DLMF 8.11.4. [igam1]
|
||||
|
||||
using accscalar_t = opmath_t<scalar_t>;
|
||||
const accscalar_t MACHEP = 5.9604644775390625E-8;
|
||||
const int MAXITER = 2000;
|
||||
|
||||
int i;
|
||||
accscalar_t ans, ax, c, r;
|
||||
|
||||
ax = _igam_helper_fac(a, x);
|
||||
if (ax == 0.0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/* power series */
|
||||
r = a;
|
||||
c = 1.0;
|
||||
ans = 1.0;
|
||||
|
||||
for (i = 0; i < MAXITER; i++) {
|
||||
r += 1.0;
|
||||
c *= x / r;
|
||||
ans += c;
|
||||
if (c <= MACHEP * ans) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (ans * ax / a);
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
scalar_t _igamc_helper_series(scalar_t a, scalar_t x) {
|
||||
// Compute igamc using DLMF 8.7.3 [igam1]. This is related to the series in
|
||||
// _igam_helper_series but extra care is taken to avoid cancellation.
|
||||
|
||||
using accscalar_t = opmath_t<scalar_t>;
|
||||
int n;
|
||||
accscalar_t fac = 1;
|
||||
accscalar_t sum = 0;
|
||||
accscalar_t term, logx;
|
||||
const int MAXITER = 2000;
|
||||
const accscalar_t MACHEP = 5.9604644775390625E-8;
|
||||
|
||||
for (n = 1; n < MAXITER; n++) {
|
||||
fac *= -x / n;
|
||||
term = fac / (a + n);
|
||||
sum += term;
|
||||
if (::fabs(term) <= MACHEP * ::fabs(sum)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
logx = ::log(x);
|
||||
term = -::expm1(a * logx - ::lgamma(1 + a));
|
||||
return term - ::exp(a * logx - ::lgamma(a)) * sum;
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
scalar_t _igam_helper_asymptotic_series(scalar_t a, scalar_t x, bool igam) {
|
||||
// Compute igam/igamc using DLMF 8.12.3/8.12.4 [igam1]
|
||||
|
||||
using accscalar_t = opmath_t<scalar_t>;
|
||||
const accscalar_t d[25][25] = {
|
||||
{-3.3333333333333333e-1, 8.3333333333333333e-2,
|
||||
-1.4814814814814815e-2, 1.1574074074074074e-3,
|
||||
3.527336860670194e-4, -1.7875514403292181e-4,
|
||||
3.9192631785224378e-5, -2.1854485106799922e-6,
|
||||
-1.85406221071516e-6, 8.296711340953086e-7,
|
||||
-1.7665952736826079e-7, 6.7078535434014986e-9,
|
||||
1.0261809784240308e-8, -4.3820360184533532e-9,
|
||||
9.1476995822367902e-10, -2.551419399494625e-11,
|
||||
-5.8307721325504251e-11, 2.4361948020667416e-11,
|
||||
-5.0276692801141756e-12, 1.1004392031956135e-13,
|
||||
3.3717632624009854e-13, -1.3923887224181621e-13,
|
||||
2.8534893807047443e-14, -5.1391118342425726e-16,
|
||||
-1.9752288294349443e-15},
|
||||
{-1.8518518518518519e-3, -3.4722222222222222e-3, 2.6455026455026455e-3,
|
||||
-9.9022633744855967e-4, 2.0576131687242798e-4, -4.0187757201646091e-7,
|
||||
-1.8098550334489978e-5, 7.6491609160811101e-6, -1.6120900894563446e-6,
|
||||
4.6471278028074343e-9, 1.378633446915721e-7, -5.752545603517705e-8,
|
||||
1.1951628599778147e-8, -1.7543241719747648e-11, -1.0091543710600413e-9,
|
||||
4.1627929918425826e-10, -8.5639070264929806e-11, 6.0672151016047586e-14,
|
||||
7.1624989648114854e-12, -2.9331866437714371e-12, 5.9966963656836887e-13,
|
||||
-2.1671786527323314e-16, -4.9783399723692616e-14, 2.0291628823713425e-14,
|
||||
-4.13125571381061e-15},
|
||||
{4.1335978835978836e-3, -2.6813271604938272e-3, 7.7160493827160494e-4,
|
||||
2.0093878600823045e-6, -1.0736653226365161e-4, 5.2923448829120125e-5,
|
||||
-1.2760635188618728e-5, 3.4235787340961381e-8, 1.3721957309062933e-6,
|
||||
-6.298992138380055e-7, 1.4280614206064242e-7, -2.0477098421990866e-10,
|
||||
-1.4092529910867521e-8, 6.228974084922022e-9, -1.3670488396617113e-9,
|
||||
9.4283561590146782e-13, 1.2872252400089318e-10, -5.5645956134363321e-11,
|
||||
1.1975935546366981e-11, -4.1689782251838635e-15, -1.0940640427884594e-12,
|
||||
4.6622399463901357e-13, -9.905105763906906e-14, 1.8931876768373515e-17,
|
||||
8.8592218725911273e-15},
|
||||
{6.4943415637860082e-4, 2.2947209362139918e-4, -4.6918949439525571e-4,
|
||||
2.6772063206283885e-4, -7.5618016718839764e-5, -2.3965051138672967e-7,
|
||||
1.1082654115347302e-5, -5.6749528269915966e-6, 1.4230900732435884e-6,
|
||||
-2.7861080291528142e-11, -1.6958404091930277e-7, 8.0994649053880824e-8,
|
||||
-1.9111168485973654e-8, 2.3928620439808118e-12, 2.0620131815488798e-9,
|
||||
-9.4604966618551322e-10, 2.1541049775774908e-10, -1.388823336813903e-14,
|
||||
-2.1894761681963939e-11, 9.7909989511716851e-12, -2.1782191880180962e-12,
|
||||
6.2088195734079014e-17, 2.126978363279737e-13, -9.3446887915174333e-14,
|
||||
2.0453671226782849e-14},
|
||||
{-8.618882909167117e-4, 7.8403922172006663e-4,
|
||||
-2.9907248030319018e-4, -1.4638452578843418e-6,
|
||||
6.6414982154651222e-5, -3.9683650471794347e-5,
|
||||
1.1375726970678419e-5, 2.5074972262375328e-10,
|
||||
-1.6954149536558306e-6, 8.9075075322053097e-7,
|
||||
-2.2929348340008049e-7, 2.956794137544049e-11,
|
||||
2.8865829742708784e-8, -1.4189739437803219e-8,
|
||||
3.4463580499464897e-9, -2.3024517174528067e-13,
|
||||
-3.9409233028046405e-10, 1.8602338968504502e-10,
|
||||
-4.356323005056618e-11, 1.2786001016296231e-15,
|
||||
4.6792750266579195e-12, -2.1492464706134829e-12,
|
||||
4.9088156148096522e-13, -6.3385914848915603e-18,
|
||||
-5.0453320690800944e-14},
|
||||
{-3.3679855336635815e-4, -6.9728137583658578e-5, 2.7727532449593921e-4,
|
||||
-1.9932570516188848e-4, 6.7977804779372078e-5, 1.419062920643967e-7,
|
||||
-1.3594048189768693e-5, 8.0184702563342015e-6, -2.2914811765080952e-6,
|
||||
-3.252473551298454e-10, 3.4652846491085265e-7, -1.8447187191171343e-7,
|
||||
4.8240967037894181e-8, -1.7989466721743515e-14, -6.3061945000135234e-9,
|
||||
3.1624176287745679e-9, -7.8409242536974293e-10, 5.1926791652540407e-15,
|
||||
9.3589442423067836e-11, -4.5134262161632782e-11, 1.0799129993116827e-11,
|
||||
-3.661886712685252e-17, -1.210902069055155e-12, 5.6807435849905643e-13,
|
||||
-1.3249659916340829e-13},
|
||||
{5.3130793646399222e-4, -5.9216643735369388e-4, 2.7087820967180448e-4,
|
||||
7.9023532326603279e-7, -8.1539693675619688e-5, 5.6116827531062497e-5,
|
||||
-1.8329116582843376e-5, -3.0796134506033048e-9, 3.4651553688036091e-6,
|
||||
-2.0291327396058604e-6, 5.7887928631490037e-7, 2.338630673826657e-13,
|
||||
-8.8286007463304835e-8, 4.7435958880408128e-8, -1.2545415020710382e-8,
|
||||
8.6496488580102925e-14, 1.6846058979264063e-9, -8.5754928235775947e-10,
|
||||
2.1598224929232125e-10, -7.6132305204761539e-16, -2.6639822008536144e-11,
|
||||
1.3065700536611057e-11, -3.1799163902367977e-12, 4.7109761213674315e-18,
|
||||
3.6902800842763467e-13},
|
||||
{3.4436760689237767e-4, 5.1717909082605922e-5,
|
||||
-3.3493161081142236e-4, 2.812695154763237e-4,
|
||||
-1.0976582244684731e-4, -1.2741009095484485e-7,
|
||||
2.7744451511563644e-5, -1.8263488805711333e-5,
|
||||
5.7876949497350524e-6, 4.9387589339362704e-10,
|
||||
-1.0595367014026043e-6, 6.1667143761104075e-7,
|
||||
-1.7562973359060462e-7, -1.2974473287015439e-12,
|
||||
2.695423606288966e-8, -1.4578352908731271e-8,
|
||||
3.887645959386175e-9, -3.8810022510194121e-17,
|
||||
-5.3279941738772867e-10, 2.7437977643314845e-10,
|
||||
-6.9957960920705679e-11, 2.5899863874868481e-17,
|
||||
8.8566890996696381e-12, -4.403168815871311e-12,
|
||||
1.0865561947091654e-12},
|
||||
{-6.5262391859530942e-4, 8.3949872067208728e-4, -4.3829709854172101e-4,
|
||||
-6.969091458420552e-7, 1.6644846642067548e-4, -1.2783517679769219e-4,
|
||||
4.6299532636913043e-5, 4.5579098679227077e-9, -1.0595271125805195e-5,
|
||||
6.7833429048651666e-6, -2.1075476666258804e-6, -1.7213731432817145e-11,
|
||||
3.7735877416110979e-7, -2.1867506700122867e-7, 6.2202288040189269e-8,
|
||||
6.5977038267330006e-16, -9.5903864974256858e-9, 5.2132144922808078e-9,
|
||||
-1.3991589583935709e-9, 5.382058999060575e-16, 1.9484714275467745e-10,
|
||||
-1.0127287556389682e-10, 2.6077347197254926e-11, -5.0904186999932993e-18,
|
||||
-3.3721464474854592e-12},
|
||||
{-5.9676129019274625e-4, -7.2048954160200106e-5,
|
||||
6.7823088376673284e-4, -6.4014752602627585e-4,
|
||||
2.7750107634328704e-4, 1.8197008380465151e-7,
|
||||
-8.4795071170685032e-5, 6.105192082501531e-5,
|
||||
-2.1073920183404862e-5, -8.8585890141255994e-10,
|
||||
4.5284535953805377e-6, -2.8427815022504408e-6,
|
||||
8.7082341778646412e-7, 3.6886101871706965e-12,
|
||||
-1.5344695190702061e-7, 8.862466778790695e-8,
|
||||
-2.5184812301826817e-8, -1.0225912098215092e-14,
|
||||
3.8969470758154777e-9, -2.1267304792235635e-9,
|
||||
5.7370135528051385e-10, -1.887749850169741e-19,
|
||||
-8.0931538694657866e-11, 4.2382723283449199e-11,
|
||||
-1.1002224534207726e-11},
|
||||
{1.3324454494800656e-3, -1.9144384985654775e-3, 1.1089369134596637e-3,
|
||||
9.932404122642299e-7, -5.0874501293093199e-4, 4.2735056665392884e-4,
|
||||
-1.6858853767910799e-4, -8.1301893922784998e-9, 4.5284402370562147e-5,
|
||||
-3.127053674781734e-5, 1.044986828530338e-5, 4.8435226265680926e-11,
|
||||
-2.1482565873456258e-6, 1.329369701097492e-6, -4.0295693092101029e-7,
|
||||
-1.7567877666323291e-13, 7.0145043163668257e-8, -4.040787734999483e-8,
|
||||
1.1474026743371963e-8, 3.9642746853563325e-18, -1.7804938269892714e-9,
|
||||
9.7480262548731646e-10, -2.6405338676507616e-10, 5.794875163403742e-18,
|
||||
3.7647749553543836e-11},
|
||||
{1.579727660730835e-3, 1.6251626278391582e-4, -2.0633421035543276e-3,
|
||||
2.1389686185689098e-3, -1.0108559391263003e-3, -3.9912705529919201e-7,
|
||||
3.6235025084764691e-4, -2.8143901463712154e-4, 1.0449513336495887e-4,
|
||||
2.1211418491830297e-9, -2.5779417251947842e-5, 1.7281818956040463e-5,
|
||||
-5.6413773872904282e-6, -1.1024320105776174e-11, 1.1223224418895175e-6,
|
||||
-6.8693396379526735e-7, 2.0653236975414887e-7, 4.6714772409838506e-14,
|
||||
-3.5609886164949055e-8, 2.0470855345905963e-8, -5.8091738633283358e-9,
|
||||
-1.332821287582869e-16, 9.0354604391335133e-10, -4.9598782517330834e-10,
|
||||
1.3481607129399749e-10},
|
||||
{-4.0725121195140166e-3, 6.4033628338080698e-3, -4.0410161081676618e-3,
|
||||
-2.183732802866233e-6, 2.1740441801254639e-3, -1.9700440518418892e-3,
|
||||
8.3595469747962458e-4, 1.9445447567109655e-8, -2.5779387120421696e-4,
|
||||
1.9009987368139304e-4, -6.7696499937438965e-5, -1.4440629666426572e-10,
|
||||
1.5712512518742269e-5, -1.0304008744776893e-5, 3.304517767401387e-6,
|
||||
7.9829760242325709e-13, -6.4097794149313004e-7, 3.8894624761300056e-7,
|
||||
-1.1618347644948869e-7, -2.816808630596451e-15, 1.9878012911297093e-8,
|
||||
-1.1407719956357511e-8, 3.2355857064185555e-9, 4.1759468293455945e-20,
|
||||
-5.0423112718105824e-10},
|
||||
{-5.9475779383993003e-3, -5.4016476789260452e-4, 8.7910413550767898e-3,
|
||||
-9.8576315587856125e-3, 5.0134695031021538e-3, 1.2807521786221875e-6,
|
||||
-2.0626019342754683e-3, 1.7109128573523058e-3, -6.7695312714133799e-4,
|
||||
-6.9011545676562133e-9, 1.8855128143995902e-4, -1.3395215663491969e-4,
|
||||
4.6263183033528039e-5, 4.0034230613321351e-11, -1.0255652921494033e-5,
|
||||
6.612086372797651e-6, -2.0913022027253008e-6, -2.0951775649603837e-13,
|
||||
3.9756029041993247e-7, -2.3956211978815887e-7, 7.1182883382145864e-8,
|
||||
8.925574873053455e-16, -1.2101547235064676e-8, 6.9350618248334386e-9,
|
||||
-1.9661464453856102e-9},
|
||||
{1.7402027787522711e-2, -2.9527880945699121e-2, 2.0045875571402799e-2,
|
||||
7.0289515966903407e-6, -1.2375421071343148e-2, 1.1976293444235254e-2,
|
||||
-5.4156038466518525e-3, -6.3290893396418616e-8, 1.8855118129005065e-3,
|
||||
-1.473473274825001e-3, 5.5515810097708387e-4, 5.2406834412550662e-10,
|
||||
-1.4357913535784836e-4, 9.9181293224943297e-5, -3.3460834749478311e-5,
|
||||
-3.5755837291098993e-12, 7.1560851960630076e-6, -4.5516802628155526e-6,
|
||||
1.4236576649271475e-6, 1.8803149082089664e-14, -2.6623403898929211e-7,
|
||||
1.5950642189595716e-7, -4.7187514673841102e-8, -6.5107872958755177e-17,
|
||||
7.9795091026746235e-9},
|
||||
{3.0249124160905891e-2, 2.4817436002649977e-3, -4.9939134373457022e-2,
|
||||
5.9915643009307869e-2, -3.2483207601623391e-2, -5.7212968652103441e-6,
|
||||
1.5085251778569354e-2, -1.3261324005088445e-2, 5.5515262632426148e-3,
|
||||
3.0263182257030016e-8, -1.7229548406756723e-3, 1.2893570099929637e-3,
|
||||
-4.6845138348319876e-4, -1.830259937893045e-10, 1.1449739014822654e-4,
|
||||
-7.7378565221244477e-5, 2.5625836246985201e-5, 1.0766165333192814e-12,
|
||||
-5.3246809282422621e-6, 3.349634863064464e-6, -1.0381253128684018e-6,
|
||||
-5.608909920621128e-15, 1.9150821930676591e-7, -1.1418365800203486e-7,
|
||||
3.3654425209171788e-8},
|
||||
{-9.9051020880159045e-2, 1.7954011706123486e-1, -1.2989606383463778e-1,
|
||||
-3.1478872752284357e-5, 9.0510635276848131e-2, -9.2828824411184397e-2,
|
||||
4.4412112839877808e-2, 2.7779236316835888e-7, -1.7229543805449697e-2,
|
||||
1.4182925050891573e-2, -5.6214161633747336e-3, -2.39598509186381e-9,
|
||||
1.6029634366079908e-3, -1.1606784674435773e-3, 4.1001337768153873e-4,
|
||||
1.8365800754090661e-11, -9.5844256563655903e-5, 6.3643062337764708e-5,
|
||||
-2.076250624489065e-5, -1.1806020912804483e-13, 4.2131808239120649e-6,
|
||||
-2.6262241337012467e-6, 8.0770620494930662e-7, 6.0125912123632725e-16,
|
||||
-1.4729737374018841e-7},
|
||||
{-1.9994542198219728e-1, -1.5056113040026424e-2, 3.6470239469348489e-1,
|
||||
-4.6435192311733545e-1, 2.6640934719197893e-1, 3.4038266027147191e-5,
|
||||
-1.3784338709329624e-1, 1.276467178337056e-1, -5.6213828755200985e-2,
|
||||
-1.753150885483011e-7, 1.9235592956768113e-2, -1.5088821281095315e-2,
|
||||
5.7401854451350123e-3, 1.0622382710310225e-9, -1.5335082692563998e-3,
|
||||
1.0819320643228214e-3, -3.7372510193945659e-4, -6.6170909729031985e-12,
|
||||
8.4263617380909628e-5, -5.5150706827483479e-5, 1.7769536448348069e-5,
|
||||
3.8827923210205533e-14, -3.53513697488768e-6, 2.1865832130045269e-6,
|
||||
-6.6812849447625594e-7},
|
||||
{7.2438608504029431e-1, -1.3918010932653375, 1.0654143352413968,
|
||||
1.876173868950258e-4, -8.2705501176152696e-1, 8.9352433347828414e-1,
|
||||
-4.4971003995291339e-1, -1.6107401567546652e-6, 1.9235590165271091e-1,
|
||||
-1.6597702160042609e-1, 6.8882222681814333e-2, 1.3910091724608687e-8,
|
||||
-2.146911561508663e-2, 1.6228980898865892e-2, -5.9796016172584256e-3,
|
||||
-1.1287469112826745e-10, 1.5167451119784857e-3, -1.0478634293553899e-3,
|
||||
3.5539072889126421e-4, 8.1704322111801517e-13, -7.7773013442452395e-5,
|
||||
5.0291413897007722e-5, -1.6035083867000518e-5, 1.2469354315487605e-14,
|
||||
3.1369106244517615e-6},
|
||||
{1.6668949727276811, 1.165462765994632e-1, -3.3288393225018906,
|
||||
4.4692325482864037, -2.6977693045875807, -2.600667859891061e-4,
|
||||
1.5389017615694539, -1.4937962361134612, 6.8881964633233148e-1,
|
||||
1.3077482004552385e-6, -2.5762963325596288e-1, 2.1097676102125449e-1,
|
||||
-8.3714408359219882e-2, -7.7920428881354753e-9, 2.4267923064833599e-2,
|
||||
-1.7813678334552311e-2, 6.3970330388900056e-3, 4.9430807090480523e-11,
|
||||
-1.5554602758465635e-3, 1.0561196919903214e-3, -3.5277184460472902e-4,
|
||||
9.3002334645022459e-14, 7.5285855026557172e-5, -4.8186515569156351e-5,
|
||||
1.5227271505597605e-5},
|
||||
{-6.6188298861372935, 1.3397985455142589e+1, -1.0789350606845146e+1,
|
||||
-1.4352254537875018e-3, 9.2333694596189809, -1.0456552819547769e+1,
|
||||
5.5105526029033471, 1.2024439690716742e-5, -2.5762961164755816,
|
||||
2.3207442745387179, -1.0045728797216284, -1.0207833290021914e-7,
|
||||
3.3975092171169466e-1, -2.6720517450757468e-1, 1.0235252851562706e-1,
|
||||
8.4329730484871625e-10, -2.7998284958442595e-2, 2.0066274144976813e-2,
|
||||
-7.0554368915086242e-3, 1.9402238183698188e-12, 1.6562888105449611e-3,
|
||||
-1.1082898580743683e-3, 3.654545161310169e-4, -5.1290032026971794e-11,
|
||||
-7.6340103696869031e-5},
|
||||
{-1.7112706061976095e+1, -1.1208044642899116, 3.7131966511885444e+1,
|
||||
-5.2298271025348962e+1, 3.3058589696624618e+1, 2.4791298976200222e-3,
|
||||
-2.061089403411526e+1, 2.088672775145582e+1, -1.0045703956517752e+1,
|
||||
-1.2238783449063012e-5, 4.0770134274221141, -3.473667358470195,
|
||||
1.4329352617312006, 7.1359914411879712e-8, -4.4797257159115612e-1,
|
||||
3.4112666080644461e-1, -1.2699786326594923e-1, -2.8953677269081528e-10,
|
||||
3.3125776278259863e-2, -2.3274087021036101e-2, 8.0399993503648882e-3,
|
||||
-1.177805216235265e-9, -1.8321624891071668e-3, 1.2108282933588665e-3,
|
||||
-3.9479941246822517e-4},
|
||||
{7.389033153567425e+1, -1.5680141270402273e+2, 1.322177542759164e+2,
|
||||
1.3692876877324546e-2, -1.2366496885920151e+2, 1.4620689391062729e+2,
|
||||
-8.0365587724865346e+1, -1.1259851148881298e-4, 4.0770132196179938e+1,
|
||||
-3.8210340013273034e+1, 1.719522294277362e+1, 9.3519707955168356e-7,
|
||||
-6.2716159907747034, 5.1168999071852637, -2.0319658112299095,
|
||||
-4.9507215582761543e-9, 5.9626397294332597e-1, -4.4220765337238094e-1,
|
||||
1.6079998700166273e-1, -2.4733786203223402e-8, -4.0307574759979762e-2,
|
||||
2.7849050747097869e-2, -9.4751858992054221e-3, 6.419922235909132e-6,
|
||||
2.1250180774699461e-3},
|
||||
{2.1216837098382522e+2, 1.3107863022633868e+1, -4.9698285932871748e+2,
|
||||
7.3121595266969204e+2, -4.8213821720890847e+2, -2.8817248692894889e-2,
|
||||
3.2616720302947102e+2, -3.4389340280087117e+2, 1.7195193870816232e+2,
|
||||
1.4038077378096158e-4, -7.52594195897599e+1, 6.651969984520934e+1,
|
||||
-2.8447519748152462e+1, -7.613702615875391e-7, 9.5402237105304373,
|
||||
-7.5175301113311376, 2.8943997568871961, -4.6612194999538201e-7,
|
||||
-8.0615149598794088e-1, 5.8483006570631029e-1, -2.0845408972964956e-1,
|
||||
1.4765818959305817e-4, 5.1000433863753019e-2, -3.3066252141883665e-2,
|
||||
1.5109265210467774e-2},
|
||||
{-9.8959643098322368e+2, 2.1925555360905233e+3, -1.9283586782723356e+3,
|
||||
-1.5925738122215253e-1, 1.9569985945919857e+3, -2.4072514765081556e+3,
|
||||
1.3756149959336496e+3, 1.2920735237496668e-3, -7.525941715948055e+2,
|
||||
7.3171668742208716e+2, -3.4137023466220065e+2, -9.9857390260608043e-6,
|
||||
1.3356313181291573e+2, -1.1276295161252794e+2, 4.6310396098204458e+1,
|
||||
-7.9237387133614756e-6, -1.4510726927018646e+1, 1.1111771248100563e+1,
|
||||
-4.1690817945270892, 3.1008219800117808e-3, 1.1220095449981468,
|
||||
-7.6052379926149916e-1, 3.6262236505085254e-1, 2.216867741940747e-1,
|
||||
4.8683443692930507e-1}};
|
||||
|
||||
int k, n, sgn;
|
||||
int maxpow = 0;
|
||||
const accscalar_t MACHEP = 5.9604644775390625E-8;
|
||||
accscalar_t lambda = x / a;
|
||||
accscalar_t sigma = (x - a) / a;
|
||||
accscalar_t eta, res, ck, ckterm, term, absterm;
|
||||
accscalar_t absoldterm = INFINITY;
|
||||
accscalar_t etapow[25] = {1};
|
||||
accscalar_t sum = 0;
|
||||
accscalar_t afac = 1;
|
||||
|
||||
if (igam) {
|
||||
sgn = -1;
|
||||
} else {
|
||||
sgn = 1;
|
||||
}
|
||||
|
||||
if (lambda > 1) {
|
||||
eta = ::sqrt(-2 * (::log1p(sigma) - sigma));
|
||||
} else if (lambda < 1) {
|
||||
eta = -::sqrt(-2 * (::log1p(sigma) - sigma));
|
||||
} else {
|
||||
eta = 0;
|
||||
}
|
||||
res = 0.5 * ::erfc(sgn * eta * ::sqrt(a / 2));
|
||||
|
||||
for (k = 0; k < 25; k++) {
|
||||
ck = d[k][0];
|
||||
for (n = 1; n < 25; n++) {
|
||||
if (n > maxpow) {
|
||||
etapow[n] = eta * etapow[n - 1];
|
||||
maxpow += 1;
|
||||
}
|
||||
ckterm = d[k][n] * etapow[n];
|
||||
ck += ckterm;
|
||||
if (::fabs(ckterm) < MACHEP * ::fabs(ck)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
term = ck * afac;
|
||||
absterm = ::fabs(term);
|
||||
if (absterm > absoldterm) {
|
||||
break;
|
||||
}
|
||||
sum += term;
|
||||
if (absterm < MACHEP * ::fabs(sum)) {
|
||||
break;
|
||||
}
|
||||
absoldterm = absterm;
|
||||
afac /= a;
|
||||
}
|
||||
res += sgn * ::exp(-0.5 * a * eta * eta) * sum / ::sqrt(2 * 3.1415926535 * a);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
scalar_t _igamc_helper_continued_fraction(scalar_t a, scalar_t x) {
|
||||
// Compute igamc using DLMF 8.9.2. [igam1]
|
||||
|
||||
using accscalar_t = opmath_t<scalar_t>;
|
||||
int i;
|
||||
accscalar_t ans, ax, c, yc, r, t, y, z;
|
||||
accscalar_t pk, pkm1, pkm2, qk, qkm1, qkm2;
|
||||
const int MAXITER = 2000;
|
||||
const accscalar_t MACHEP = 5.9604644775390625E-8;
|
||||
const accscalar_t BIG = 16777216.;
|
||||
const accscalar_t BIGINV = 5.9604644775390625E-8;
|
||||
|
||||
ax = _igam_helper_fac(a, x);
|
||||
if (ax == 0.0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/* continued fraction */
|
||||
y = 1.0 - a;
|
||||
z = x + y + 1.0;
|
||||
c = 0.0;
|
||||
pkm2 = 1.0;
|
||||
qkm2 = x;
|
||||
pkm1 = x + 1.0;
|
||||
qkm1 = z * x;
|
||||
ans = pkm1 / qkm1;
|
||||
|
||||
for (i = 0; i < MAXITER; i++) {
|
||||
c += 1.0;
|
||||
y += 1.0;
|
||||
z += 2.0;
|
||||
yc = y * c;
|
||||
pk = pkm1 * z - pkm2 * yc;
|
||||
qk = qkm1 * z - qkm2 * yc;
|
||||
if (qk != 0) {
|
||||
r = pk / qk;
|
||||
t = ::fabs((ans - r) / r);
|
||||
ans = r;
|
||||
} else {
|
||||
t = 1.0;
|
||||
}
|
||||
pkm2 = pkm1;
|
||||
pkm1 = pk;
|
||||
qkm2 = qkm1;
|
||||
qkm1 = qk;
|
||||
if (::fabs(pk) > BIG) {
|
||||
pkm2 *= BIGINV;
|
||||
pkm1 *= BIGINV;
|
||||
qkm2 *= BIGINV;
|
||||
qkm1 *= BIGINV;
|
||||
}
|
||||
if (t <= MACHEP) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ans * ax;
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
scalar_t calc_igammac(scalar_t a, scalar_t x) {
|
||||
/* the calculation of the regularized upper incomplete gamma function
|
||||
* is done differently based on the values of a and x:
|
||||
* - if x and/or a is at the boundary of defined region, then assign the
|
||||
* result at the boundary
|
||||
* - if a is large and a ~ x, then using Uniform Asymptotic Expansions for
|
||||
* Large Parameter (see DLMF 8.12.4 [igam1])
|
||||
* - if x > 1.1 and x < a, using the subtraction from the regularized lower
|
||||
* incomplete gamma
|
||||
* - otherwise, calculate the series from [igam2] eq (5)
|
||||
*/
|
||||
|
||||
using accscalar_t = opmath_t<scalar_t>;
|
||||
accscalar_t absxma_a;
|
||||
|
||||
const accscalar_t SMALL = 20.0;
|
||||
const accscalar_t LARGE = 200.0;
|
||||
const accscalar_t SMALLRATIO = 0.3;
|
||||
const accscalar_t LARGERATIO = 4.5;
|
||||
|
||||
if ((x < 0) || (a < 0)) {
|
||||
// out of defined-region of the function
|
||||
return NAN;
|
||||
} else if (a == 0) {
|
||||
if (x > 0) {
|
||||
return 0.0;
|
||||
} else {
|
||||
return NAN;
|
||||
}
|
||||
} else if (x == 0) {
|
||||
return 1.0;
|
||||
} else if (isinf(a)) {
|
||||
if (isinf(x)) {
|
||||
return NAN;
|
||||
}
|
||||
return 1.0;
|
||||
} else if (isinf(x)) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
absxma_a = ::fabs(x - a) / a;
|
||||
if ((a > SMALL) && (a < LARGE) && (absxma_a < SMALLRATIO)) {
|
||||
return _igam_helper_asymptotic_series(a, x, 0);
|
||||
} else if ((a > LARGE) && (absxma_a < LARGERATIO / ::sqrt(a))) {
|
||||
return _igam_helper_asymptotic_series(a, x, 0);
|
||||
}
|
||||
|
||||
if (x > 1.1) {
|
||||
if (x < a) {
|
||||
return 1.0 - _igam_helper_series(a, x);
|
||||
} else {
|
||||
return _igamc_helper_continued_fraction(a, x);
|
||||
}
|
||||
} else if (x <= 0.5) {
|
||||
if (-0.4 / ::log(x) < a) {
|
||||
return 1.0 - _igam_helper_series(a, x);
|
||||
} else {
|
||||
return _igamc_helper_series(a, x);
|
||||
}
|
||||
} else {
|
||||
if (x * 1.1 < a) {
|
||||
return 1.0 - _igam_helper_series(a, x);
|
||||
} else {
|
||||
return _igamc_helper_series(a, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
scalar_t calc_igamma(scalar_t a, scalar_t x) {
|
||||
/* the calculation of the regularized lower incomplete gamma function
|
||||
* is done differently based on the values of a and x:
|
||||
* - if x and/or a is at the boundary of defined region, then assign the
|
||||
* result at the boundary
|
||||
* - if a is large and a ~ x, then using Uniform Asymptotic Expansions for
|
||||
* Large Parameter (see DLMF 8.12.3 [igam1])
|
||||
* - if x > 1 and x > a, using the subtraction from the regularized upper
|
||||
* incomplete gamma
|
||||
* - otherwise, calculate the series from [igam2] eq (4)
|
||||
*/
|
||||
|
||||
using accscalar_t = opmath_t<scalar_t>;
|
||||
accscalar_t absxma_a;
|
||||
const accscalar_t SMALL = 20.0;
|
||||
const accscalar_t LARGE = 200.0;
|
||||
const accscalar_t SMALLRATIO = 0.3;
|
||||
const accscalar_t LARGERATIO = 4.5;
|
||||
|
||||
// boundary values following SciPy
|
||||
if ((x < 0) || (a < 0)) {
|
||||
// out of defined-region of the function
|
||||
return NAN;
|
||||
} else if (a == 0) {
|
||||
if (x > 0) {
|
||||
return 1.0;
|
||||
} else {
|
||||
return NAN;
|
||||
}
|
||||
} else if (x == 0) {
|
||||
return 0.0; // zero integration limit
|
||||
} else if (isinf(a)) {
|
||||
if (isinf(x)) {
|
||||
return NAN;
|
||||
}
|
||||
return 0.0;
|
||||
} else if (isinf(x)) {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
/* Asymptotic regime where a ~ x. */
|
||||
absxma_a = ::fabs(x - a) / a;
|
||||
if ((a > SMALL) && (a < LARGE) && (absxma_a < SMALLRATIO)) {
|
||||
return _igam_helper_asymptotic_series(a, x, 1);
|
||||
} else if ((a > LARGE) && (absxma_a < LARGERATIO / ::sqrt(a))) {
|
||||
return _igam_helper_asymptotic_series(a, x, 1);
|
||||
}
|
||||
|
||||
if ((x > 1.0) && (x > a)) {
|
||||
return 1.0 - calc_igammac(a, x);
|
||||
}
|
||||
|
||||
return _igam_helper_series(a, x);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// end of regularized lower & upper incomplete gamma
|
||||
|
||||
namespace c10 {
|
||||
namespace metal {
|
||||
|
||||
template <typename T>
|
||||
inline T igamma(T a, T b) {
|
||||
return calc_igamma(a, b);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T igammac(T a, T b) {
|
||||
return calc_igammac(a, b);
|
||||
}
|
||||
|
||||
} // namespace metal
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,83 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
// Philox Counter based RNG implementation for Metal
|
||||
// Borrowed from aten/src/ATen/core/PhiloxRNGEngine.h
|
||||
// Which in turn borrowed from
|
||||
// http://www.thesalmons.org/john/random123/papers/random123sc11.pdf
|
||||
#pragma once
|
||||
#include <metal_stdlib>
|
||||
|
||||
namespace c10 {
|
||||
namespace metal {
|
||||
|
||||
namespace detail {
|
||||
|
||||
constexpr float uint32_to_uniform_float(uint32_t value) {
|
||||
// maximum value such that `MAX_INT * scale < 1.0` (with float rounding)
|
||||
constexpr float scale = 4.6566127342e-10;
|
||||
return static_cast<float>(value & 0x7FFFFFFF) * scale;
|
||||
}
|
||||
|
||||
inline uint2 splitlong(ulong v) {
|
||||
return uint2(v >> 32, v & 0xffffffff);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
namespace philox4 {
|
||||
|
||||
uint2 mulhilo(uint a, uint b) {
|
||||
auto rc = static_cast<ulong>(a) * b;
|
||||
return detail::splitlong(rc);
|
||||
}
|
||||
uint4 single_round(uint4 ctr, uint2 key) {
|
||||
constexpr uint kPhiloxSA = 0xD2511F53;
|
||||
constexpr uint kPhiloxSB = 0xCD9E8D57;
|
||||
auto rc0 = mulhilo(kPhiloxSA, ctr.x);
|
||||
auto rc1 = mulhilo(kPhiloxSB, ctr.z);
|
||||
return uint4(rc1.x ^ ctr.y ^ key.x, rc1.y, rc0.x ^ ctr.w ^ key.y, rc0.y);
|
||||
}
|
||||
|
||||
uint4 multiple_rounds(uint4 ctr, uint2 key, uint rounds) {
|
||||
constexpr uint2 kPhilox10 = {0x9E3779B9, 0xBB67AE85};
|
||||
for (uint round = 0; round < rounds - 1; ++round) {
|
||||
ctr = single_round(ctr, key);
|
||||
key += kPhilox10;
|
||||
}
|
||||
return ctr;
|
||||
}
|
||||
|
||||
uint4 rand(long seed, long index) {
|
||||
uint4 ctr = 0;
|
||||
ctr.zw = detail::splitlong(index);
|
||||
return multiple_rounds(ctr, detail::splitlong(seed), 10);
|
||||
}
|
||||
|
||||
} // namespace philox4
|
||||
|
||||
float randn(long seed, long index) {
|
||||
auto value = philox4::rand(seed, index);
|
||||
float u1 = 1.0 - detail::uint32_to_uniform_float(value.x);
|
||||
float u2 = 1.0 - detail::uint32_to_uniform_float(value.y);
|
||||
return ::metal::sqrt(-2.0 * ::metal::log(u1)) *
|
||||
::metal::cos(2.0 * M_PI_F * u2);
|
||||
}
|
||||
|
||||
float rand(long seed, long index) {
|
||||
auto value = philox4::rand(seed, index);
|
||||
return detail::uint32_to_uniform_float(value.x);
|
||||
}
|
||||
|
||||
long randint64(long seed, long index, long low, long high) {
|
||||
auto range = high - low;
|
||||
auto value = philox4::rand(seed, index);
|
||||
// TODO: Implement better algorithm for large ranges
|
||||
return low +
|
||||
static_cast<long>(detail::uint32_to_uniform_float(value.x) * range);
|
||||
}
|
||||
|
||||
} // namespace metal
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
@@ -0,0 +1,364 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
#pragma once
|
||||
|
||||
#include <c10/metal/utils.h>
|
||||
#include <metal_compute>
|
||||
|
||||
namespace c10 {
|
||||
namespace metal {
|
||||
namespace detail {
|
||||
template <typename T>
|
||||
struct simd_type {
|
||||
using t = T;
|
||||
};
|
||||
|
||||
// Helper that allows one to run simd ops over bfl16 by upcasting them to fp32
|
||||
template <typename T>
|
||||
using simd_type_t = typename simd_type<T>::t;
|
||||
|
||||
template <>
|
||||
struct simd_type<bfloat> {
|
||||
using t = float;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
template <typename T>
|
||||
inline ::metal::enable_if_t<!::metal::is_same_v<T, long>, T> simd_sum(T val) {
|
||||
return T(::metal::simd_sum(detail::simd_type_t<T>(val)));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline ::metal::enable_if_t<!::metal::is_same_v<T, long>, T> simd_prod(T val) {
|
||||
return T(::metal::simd_product(detail::simd_type_t<T>(val)));
|
||||
}
|
||||
|
||||
// Extend simd_broadcast to 64-bit integral types using int2 trick
|
||||
template <
|
||||
typename T,
|
||||
::metal::enable_if_t<::metal::is_integral_v<T> && sizeof(T) == 8, bool> =
|
||||
true>
|
||||
inline T simd_broadcast(T val, ushort lane_id) {
|
||||
return as_type<T>(::metal::simd_broadcast(as_type<int2>(val), lane_id));
|
||||
}
|
||||
|
||||
template <
|
||||
typename T,
|
||||
::metal::enable_if_t<!::metal::is_integral_v<T> || sizeof(T) != 8, bool> =
|
||||
true>
|
||||
inline T simd_broadcast(T val, ushort lane_id) {
|
||||
return ::metal::simd_broadcast(val, lane_id);
|
||||
}
|
||||
|
||||
// Floating simd_min/max with nan propagation
|
||||
template <
|
||||
typename T,
|
||||
::metal::enable_if_t<::metal::is_floating_point_v<T>, bool> = true>
|
||||
inline T simd_max(T val) {
|
||||
if (::metal::simd_any(::metal::isnan(val))) {
|
||||
return ::metal::numeric_limits<T>::quiet_NaN();
|
||||
}
|
||||
return T(::metal::simd_max(detail::simd_type_t<T>(val)));
|
||||
}
|
||||
|
||||
template <
|
||||
typename T,
|
||||
::metal::enable_if_t<::metal::is_floating_point_v<T>, bool> = true>
|
||||
inline T simd_min(T val) {
|
||||
if (::metal::simd_any(::metal::isnan(val))) {
|
||||
return ::metal::numeric_limits<T>::quiet_NaN();
|
||||
}
|
||||
return T(::metal::simd_min(detail::simd_type_t<T>(val)));
|
||||
}
|
||||
|
||||
template <
|
||||
typename T,
|
||||
::metal::enable_if_t<::metal::is_integral_v<T> && sizeof(T) != 8, bool> =
|
||||
true>
|
||||
inline T simd_max(T val) {
|
||||
return ::metal::simd_max(val);
|
||||
}
|
||||
|
||||
template <
|
||||
typename T,
|
||||
::metal::enable_if_t<::metal::is_integral_v<T> && sizeof(T) != 8, bool> =
|
||||
true>
|
||||
inline T simd_min(T val) {
|
||||
return ::metal::simd_min(val);
|
||||
}
|
||||
|
||||
// Metal does not support SIMD reductions over 64-bit types, but it could be
|
||||
// implement using simd_shuffle_down, that yields result in log2(simdgroup_size)
|
||||
// iterations Use fill variant, as shuffle down returns garbage if inactive
|
||||
// thread is referenced (on M1/M2, works fine on M4) and broadcast result to all
|
||||
// threads in the end. Implementation heavily borrows from
|
||||
// https://github.com/ml-explore/mlx/blob/86389bf9707f46101af45d90510e8e97c8a90b93/mlx/backend/metal/kernels/reduction/ops.h#L16
|
||||
template <typename T>
|
||||
inline ::metal::enable_if_t<::metal::is_same_v<T, long>, T> simd_sum(T val) {
|
||||
for (ushort i = simdgroup_size / 2; i > 0; i /= 2) {
|
||||
val += as_type<T>(
|
||||
::metal::simd_shuffle_and_fill_down(as_type<int2>(val), int2(0), i));
|
||||
}
|
||||
return simd_broadcast(val, 0);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline ::metal::enable_if_t<::metal::is_same_v<T, long>, T> simd_prod(T val) {
|
||||
for (ushort i = simdgroup_size / 2; i > 0; i /= 2) {
|
||||
val *= as_type<T>(
|
||||
::metal::simd_shuffle_and_fill_down(as_type<int2>(val), int2(0), i));
|
||||
}
|
||||
return simd_broadcast(val, 0);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline ::metal::enable_if_t<::metal::is_same_v<T, long>, T> simd_max(T val) {
|
||||
for (ushort i = simdgroup_size / 2; i > 0; i /= 2) {
|
||||
val = ::metal::max(
|
||||
val,
|
||||
as_type<T>(::metal::simd_shuffle_and_fill_down(
|
||||
as_type<int2>(val), int2(0), i)));
|
||||
}
|
||||
return simd_broadcast(val, 0);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline ::metal::enable_if_t<::metal::is_same_v<T, long>, T> simd_min(T val) {
|
||||
for (ushort i = simdgroup_size / 2; i > 0; i /= 2) {
|
||||
val = ::metal::min(
|
||||
val,
|
||||
as_type<T>(::metal::simd_shuffle_and_fill_down(
|
||||
as_type<int2>(val), int2(0), i)));
|
||||
}
|
||||
return simd_broadcast(val, 0);
|
||||
}
|
||||
|
||||
// argmin/argmax helpers using simd_ballot
|
||||
template <
|
||||
typename T,
|
||||
::metal::enable_if_t<::metal::is_integral_v<T>, bool> = true>
|
||||
inline ::c10::metal::pair<T, ushort> simd_argmin(T val) {
|
||||
const auto rc = simd_min(val);
|
||||
const auto vote = ::metal::simd_ballot(val == rc);
|
||||
return {rc, static_cast<ushort>(::metal::ctz(static_cast<ulong>(vote)))};
|
||||
}
|
||||
|
||||
template <
|
||||
typename T,
|
||||
::metal::enable_if_t<::metal::is_floating_point_v<T>, bool> = true>
|
||||
inline ::c10::metal::pair<T, ushort> simd_argmin(T val) {
|
||||
const auto rc = simd_min(val);
|
||||
const auto vote = ::metal::simd_ballot(val == rc || ::metal::isnan(val));
|
||||
return {rc, static_cast<ushort>(::metal::ctz(static_cast<ulong>(vote)))};
|
||||
}
|
||||
|
||||
template <
|
||||
typename T,
|
||||
::metal::enable_if_t<::metal::is_integral_v<T>, bool> = true>
|
||||
inline ::c10::metal::pair<T, ushort> simd_argmax(T val) {
|
||||
const auto rc = simd_max(val);
|
||||
const auto vote = ::metal::simd_ballot(val == rc);
|
||||
return {rc, static_cast<ushort>(::metal::ctz(static_cast<ulong>(vote)))};
|
||||
}
|
||||
|
||||
template <
|
||||
typename T,
|
||||
::metal::enable_if_t<::metal::is_floating_point_v<T>, bool> = true>
|
||||
inline ::c10::metal::pair<T, ushort> simd_argmax(T val) {
|
||||
const auto rc = simd_max(val);
|
||||
const auto vote = ::metal::simd_ballot(val == rc || ::metal::isnan(val));
|
||||
return {rc, static_cast<ushort>(::metal::ctz(static_cast<ulong>(vote)))};
|
||||
}
|
||||
|
||||
template <typename ARG_T, typename IDX_T>
|
||||
inline c10::metal::pair<ARG_T, IDX_T> simd_argmin(ARG_T val, IDX_T idx_val) {
|
||||
auto rc = simd_argmin(val);
|
||||
return {rc.first, simd_broadcast(idx_val, rc.second)};
|
||||
}
|
||||
|
||||
template <typename ARG_T, typename IDX_T>
|
||||
inline c10::metal::pair<ARG_T, IDX_T> simd_argmax(ARG_T val, IDX_T idx_val) {
|
||||
auto rc = simd_argmax(val);
|
||||
return {rc.first, simd_broadcast(idx_val, rc.second)};
|
||||
}
|
||||
|
||||
// Below algorithms are written with hardcoded assumption that simdgroup is 32
|
||||
// and threadgroup_max is 1024, i.e. reduction can be done in two stages max
|
||||
template <typename T>
|
||||
opmath_t<T> threadgroup_sum(
|
||||
threadgroup opmath_t<T>* data,
|
||||
T val,
|
||||
unsigned idx,
|
||||
unsigned size) {
|
||||
auto rc = simd_sum(static_cast<opmath_t<T>>(val));
|
||||
if (idx % simdgroup_size == 0) {
|
||||
data[idx / simdgroup_size] = rc;
|
||||
}
|
||||
if (size > simdgroup_size) {
|
||||
::metal::threadgroup_barrier(::metal::mem_flags::mem_threadgroup);
|
||||
if (idx < ((size + simdgroup_size - 1) / simdgroup_size)) {
|
||||
auto rc1 = simd_sum(data[idx]);
|
||||
if (idx == 0) {
|
||||
data[0] = rc1;
|
||||
}
|
||||
}
|
||||
}
|
||||
::metal::threadgroup_barrier(::metal::mem_flags::mem_threadgroup);
|
||||
return data[0];
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
opmath_t<T> threadgroup_prod(
|
||||
threadgroup opmath_t<T>* data,
|
||||
T val,
|
||||
unsigned idx,
|
||||
unsigned size) {
|
||||
auto rc = simd_prod(static_cast<opmath_t<T>>(val));
|
||||
if (idx % simdgroup_size == 0) {
|
||||
data[idx / simdgroup_size] = rc;
|
||||
}
|
||||
if (size > simdgroup_size) {
|
||||
::metal::threadgroup_barrier(::metal::mem_flags::mem_threadgroup);
|
||||
if (idx < ((size + simdgroup_size - 1) / simdgroup_size)) {
|
||||
auto rc1 = simd_prod(data[idx]);
|
||||
if (idx == 0) {
|
||||
data[0] = rc1;
|
||||
}
|
||||
}
|
||||
}
|
||||
::metal::threadgroup_barrier(::metal::mem_flags::mem_threadgroup);
|
||||
return data[0];
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T threadgroup_max(threadgroup T* data, T val, unsigned idx, unsigned size) {
|
||||
auto rc = simd_max(val);
|
||||
if (idx % simdgroup_size == 0) {
|
||||
data[idx / simdgroup_size] = rc;
|
||||
}
|
||||
if (size > simdgroup_size) {
|
||||
::metal::threadgroup_barrier(::metal::mem_flags::mem_threadgroup);
|
||||
if (idx < ((size + simdgroup_size - 1) / simdgroup_size)) {
|
||||
auto rc1 = simd_max(data[idx]);
|
||||
if (idx == 0) {
|
||||
data[0] = rc1;
|
||||
}
|
||||
}
|
||||
}
|
||||
::metal::threadgroup_barrier(::metal::mem_flags::mem_threadgroup);
|
||||
return data[0];
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T threadgroup_min(threadgroup T* data, T val, unsigned idx, unsigned size) {
|
||||
auto rc = simd_min(val);
|
||||
if (idx % simdgroup_size == 0) {
|
||||
data[idx / simdgroup_size] = rc;
|
||||
}
|
||||
if (size > simdgroup_size) {
|
||||
::metal::threadgroup_barrier(::metal::mem_flags::mem_threadgroup);
|
||||
if (idx < ((size + simdgroup_size - 1) / simdgroup_size)) {
|
||||
auto rc1 = simd_min(data[idx]);
|
||||
if (idx == 0) {
|
||||
data[0] = rc1;
|
||||
}
|
||||
}
|
||||
}
|
||||
::metal::threadgroup_barrier(::metal::mem_flags::mem_threadgroup);
|
||||
return data[0];
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
float3 threadgroup_welford_reduce(threadgroup T* data, unsigned size) {
|
||||
::metal::threadgroup_barrier(::metal::mem_flags::mem_threadgroup);
|
||||
float m = data[0];
|
||||
float m2 = 0;
|
||||
for (unsigned idx = 1; idx < size; ++idx) {
|
||||
float delta = data[idx] - m;
|
||||
m += delta / (idx + 1);
|
||||
m2 += delta * (data[idx] - m);
|
||||
}
|
||||
return float3(m, m2, size);
|
||||
}
|
||||
|
||||
// Each vec3type is tuple of mean, m2 and weight
|
||||
template <typename T>
|
||||
float3 welford_combine(T a, T b) {
|
||||
float delta = b.x - a.x;
|
||||
float new_weight = a.z + b.z;
|
||||
auto w2_over_w = new_weight != 0 ? b.z / new_weight : 0.0;
|
||||
return float3(
|
||||
a.x + delta * w2_over_w,
|
||||
a.y + b.y + delta * delta * a.z * w2_over_w,
|
||||
new_weight);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
float3 threadgroup_welford_combine(threadgroup T* data, unsigned size) {
|
||||
::metal::threadgroup_barrier(::metal::mem_flags::mem_threadgroup);
|
||||
float3 rc = data[0];
|
||||
for (unsigned idx = 1; idx < size; ++idx) {
|
||||
rc = welford_combine(rc, data[idx]);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
template <typename ARG_T, typename IDX_T>
|
||||
IDX_T threadgroup_argmax(
|
||||
threadgroup ARG_T* arg_data,
|
||||
threadgroup IDX_T* idx_data,
|
||||
ARG_T val,
|
||||
IDX_T idx_val,
|
||||
unsigned idx,
|
||||
unsigned size) {
|
||||
auto rc = simd_argmax(val, idx_val);
|
||||
if (size <= simdgroup_size) {
|
||||
return rc.second;
|
||||
}
|
||||
if (idx % simdgroup_size == 0) {
|
||||
arg_data[idx / simdgroup_size] = rc.first;
|
||||
idx_data[idx / simdgroup_size] = rc.second;
|
||||
}
|
||||
::metal::threadgroup_barrier(::metal::mem_flags::mem_threadgroup);
|
||||
if (idx < ((size + simdgroup_size - 1) / simdgroup_size)) {
|
||||
auto rc1 = simd_argmax(arg_data[idx], idx_data[idx]);
|
||||
if (idx == 0) {
|
||||
idx_data[0] = rc1.second;
|
||||
}
|
||||
}
|
||||
::metal::threadgroup_barrier(::metal::mem_flags::mem_threadgroup);
|
||||
return idx_data[0];
|
||||
}
|
||||
|
||||
template <typename ARG_T, typename IDX_T>
|
||||
IDX_T threadgroup_argmin(
|
||||
threadgroup ARG_T* arg_data,
|
||||
threadgroup IDX_T* idx_data,
|
||||
ARG_T val,
|
||||
IDX_T idx_val,
|
||||
unsigned idx,
|
||||
unsigned size) {
|
||||
auto rc = simd_argmin(val, idx_val);
|
||||
if (size <= simdgroup_size) {
|
||||
return rc.second;
|
||||
}
|
||||
if (idx % simdgroup_size == 0) {
|
||||
arg_data[idx / simdgroup_size] = rc.first;
|
||||
idx_data[idx / simdgroup_size] = rc.second;
|
||||
}
|
||||
::metal::threadgroup_barrier(::metal::mem_flags::mem_threadgroup);
|
||||
if (idx < ((size + simdgroup_size - 1) / simdgroup_size)) {
|
||||
auto rc1 = simd_argmin(arg_data[idx], idx_data[idx]);
|
||||
if (idx == 0) {
|
||||
idx_data[0] = rc1.second;
|
||||
}
|
||||
}
|
||||
::metal::threadgroup_barrier(::metal::mem_flags::mem_threadgroup);
|
||||
return idx_data[0];
|
||||
}
|
||||
|
||||
} // namespace metal
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,528 @@
|
||||
#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
// Metal helper functions
|
||||
#pragma once
|
||||
#include <c10/metal/common.h>
|
||||
#include <metal_stdlib>
|
||||
|
||||
namespace c10 {
|
||||
namespace metal {
|
||||
|
||||
namespace detail {
|
||||
template <typename T>
|
||||
struct vectypes {};
|
||||
|
||||
template <>
|
||||
struct vectypes<float> {
|
||||
using type4 = float4;
|
||||
using type3 = float3;
|
||||
using type2 = float2;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct vectypes<half> {
|
||||
using type4 = half4;
|
||||
using type3 = half3;
|
||||
using type2 = half2;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct vectypes<bfloat> {
|
||||
using type4 = bfloat4;
|
||||
using type3 = bfloat3;
|
||||
using type2 = bfloat2;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct vectypes<short> {
|
||||
using type4 = short4;
|
||||
using type3 = short3;
|
||||
using type2 = short2;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct vectypes<int> {
|
||||
using type4 = int4;
|
||||
using type3 = int3;
|
||||
using type2 = int2;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct vectypes<long> {
|
||||
using type4 = short4;
|
||||
using type3 = short3;
|
||||
using type2 = short2;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct OpMathType {
|
||||
using type = T;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct OpMathType<half> {
|
||||
using type = float;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct OpMathType<short> {
|
||||
using type = int;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct OpMathType<char> {
|
||||
using type = int;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct OpMathType<uchar> {
|
||||
using type = int;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct OpMathType<bfloat> {
|
||||
using type = float;
|
||||
};
|
||||
|
||||
// Type promotion structure for higher precision accumulation
|
||||
template <typename T>
|
||||
struct AccumulationType {
|
||||
using type = T;
|
||||
};
|
||||
|
||||
// Specialization for half - promote to float for accumulation
|
||||
template <>
|
||||
struct AccumulationType<half> {
|
||||
using type = float;
|
||||
};
|
||||
|
||||
// Specialization for bfloat - promote to float for accumulation
|
||||
template <>
|
||||
struct AccumulationType<bfloat> {
|
||||
using type = float;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename T>
|
||||
::metal::enable_if_t<::metal::is_floating_point_v<T>, T> max(T a, T b) {
|
||||
return ::metal::isunordered(a, b) ? NAN : ::metal::max(a, b);
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
::metal::enable_if_t<::metal::is_integral_v<T>&& ::metal::is_integral_v<U>, T>
|
||||
max(T a, U b) {
|
||||
return ::metal::max(a, static_cast<T>(b));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
::metal::enable_if_t<::metal::is_floating_point_v<T>, T> min(T a, T b) {
|
||||
return ::metal::isunordered(a, b) ? NAN : ::metal::min(a, b);
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
::metal::enable_if_t<::metal::is_integral_v<T>&& ::metal::is_integral_v<U>, T>
|
||||
min(T a, U b) {
|
||||
return ::metal::min(a, static_cast<T>(b));
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bfloat min(bfloat a, bfloat b) {
|
||||
return bfloat(
|
||||
::metal::isunordered(a, b) ? NAN : ::metal::min(float(a), float(b)));
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bfloat max(bfloat a, bfloat b) {
|
||||
return bfloat(
|
||||
::metal::isunordered(a, b) ? NAN : ::metal::max(float(a), float(b)));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
using vec2type_t = typename detail::vectypes<T>::type2;
|
||||
|
||||
template <typename T>
|
||||
using vec4type_t = typename detail::vectypes<T>::type4;
|
||||
|
||||
template <typename T>
|
||||
using opmath_t = typename detail::OpMathType<T>::type;
|
||||
|
||||
template <typename T>
|
||||
using accum_t = typename detail::AccumulationType<T>::type;
|
||||
|
||||
// TODO: Move it to type_traits header may be
|
||||
template <typename F, typename... Args>
|
||||
using result_of = decltype(::metal::declval<F>()(::metal::declval<Args>()...));
|
||||
|
||||
template <typename T>
|
||||
constexpr constant bool is_complex_v =
|
||||
::metal::is_same_v<T, float2> || ::metal::is_same_v<T, half2>;
|
||||
|
||||
template <typename T>
|
||||
constexpr constant bool is_scalar_floating_point_v =
|
||||
::metal::is_floating_point_v<T> && ::metal::is_scalar_v<T>;
|
||||
|
||||
template <typename T>
|
||||
constexpr constant bool is_scalar_integral_v =
|
||||
::metal::is_integral_v<T> && ::metal::is_scalar_v<T>;
|
||||
|
||||
template <typename U, typename V>
|
||||
using common_dtype = decltype(U(0) + V(0));
|
||||
|
||||
// floor_divide
|
||||
template <
|
||||
typename T,
|
||||
typename U,
|
||||
::metal::enable_if_t<
|
||||
is_scalar_integral_v<T> && is_scalar_integral_v<U>,
|
||||
bool> = true>
|
||||
inline common_dtype<T, U> floor_divide(T x, U y) {
|
||||
const auto quot = x / y;
|
||||
return (x < 0) == (y < 0) ? quot : (x % y != 0) ? quot - 1 : quot;
|
||||
}
|
||||
|
||||
template <
|
||||
typename T,
|
||||
typename U,
|
||||
::metal::enable_if_t<
|
||||
is_scalar_floating_point_v<T> && is_scalar_floating_point_v<U>,
|
||||
bool> = true>
|
||||
inline common_dtype<T, U> floor_divide(T x, U y) {
|
||||
return ::metal::floor(x / y);
|
||||
}
|
||||
|
||||
// Workaround for Metal compiler bug: the compiler produces wrong results
|
||||
// when optimizing fused (x / A) % B expressions for integral types.
|
||||
template <
|
||||
typename T,
|
||||
typename U,
|
||||
::metal::enable_if_t<
|
||||
is_scalar_integral_v<T> && is_scalar_integral_v<U>,
|
||||
bool> = true>
|
||||
inline common_dtype<T, U> safe_mod(volatile T x, U y) {
|
||||
return x % y;
|
||||
}
|
||||
|
||||
// fmod
|
||||
template <
|
||||
typename T,
|
||||
typename U,
|
||||
::metal::enable_if_t<
|
||||
is_scalar_integral_v<T> && is_scalar_integral_v<U>,
|
||||
bool> = true>
|
||||
inline common_dtype<T, U> fmod(T x, U y) {
|
||||
return x % y;
|
||||
}
|
||||
|
||||
template <
|
||||
typename T,
|
||||
typename U,
|
||||
::metal::enable_if_t<
|
||||
is_scalar_floating_point_v<T> && is_scalar_floating_point_v<U>,
|
||||
bool> = true>
|
||||
inline common_dtype<T, U> fmod(T x, U y) {
|
||||
return ::metal::fmod(x, y);
|
||||
}
|
||||
|
||||
// cast_to primitives
|
||||
// - No-op if types as the same
|
||||
template <
|
||||
typename T,
|
||||
typename U,
|
||||
::metal::enable_if_t<::metal::is_same_v<U, T>, bool> = true>
|
||||
inline T cast_to(const U from) {
|
||||
return from;
|
||||
}
|
||||
// - Simple cast between scalar and complex dtypes
|
||||
template <
|
||||
typename T,
|
||||
typename U,
|
||||
::metal::enable_if_t<
|
||||
!::metal::is_same_v<U, T> && (is_complex_v<T> == is_complex_v<U>),
|
||||
bool> = true>
|
||||
inline T cast_to(const U from) {
|
||||
return static_cast<T>(from);
|
||||
}
|
||||
|
||||
// - Scalar to complex
|
||||
template <
|
||||
typename T,
|
||||
typename U,
|
||||
::metal::enable_if_t<is_complex_v<T> && !is_complex_v<U>, bool> = true>
|
||||
inline T cast_to(const U from) {
|
||||
return T(float(from), 0.0);
|
||||
}
|
||||
// - Complex to scalar (should not really be used, but exists for compliteness)
|
||||
template <
|
||||
typename T,
|
||||
typename U,
|
||||
::metal::enable_if_t<!is_complex_v<T> && is_complex_v<U>, bool> = true>
|
||||
inline T cast_to(const U from) {
|
||||
return static_cast<T>(from.x);
|
||||
}
|
||||
|
||||
// Generalizable math operators (used for both scalar and complex)
|
||||
|
||||
template <
|
||||
typename T,
|
||||
typename U,
|
||||
::metal::enable_if_t<!is_complex_v<T>, bool> = true>
|
||||
inline common_dtype<T, U> mul(const T x, const U y) {
|
||||
return x * y;
|
||||
}
|
||||
|
||||
template <
|
||||
typename T,
|
||||
typename U,
|
||||
::metal::enable_if_t<is_complex_v<T> && is_complex_v<U>, bool> = true>
|
||||
inline common_dtype<T, U> mul(const T x, const U y) {
|
||||
return T(x.x * y.x - x.y * y.y, x.x * y.y + x.y * y.x);
|
||||
}
|
||||
|
||||
template <
|
||||
typename T,
|
||||
typename U,
|
||||
::metal::enable_if_t<!is_complex_v<T>, bool> = true>
|
||||
inline common_dtype<T, U> div(const T x, const U y) {
|
||||
return x / y;
|
||||
}
|
||||
|
||||
template <
|
||||
typename T,
|
||||
typename U,
|
||||
::metal::enable_if_t<is_complex_v<T> && is_complex_v<U>, bool> = true>
|
||||
inline common_dtype<T, U> div(const T x, const U y) {
|
||||
return T(::metal::dot(x, y), x.y * y.x - x.x * y.y) / ::metal::dot(y, y);
|
||||
}
|
||||
|
||||
// Remainder operator
|
||||
template <
|
||||
typename T,
|
||||
typename U,
|
||||
::metal::enable_if_t<
|
||||
is_scalar_floating_point_v<T> || is_scalar_floating_point_v<U>,
|
||||
bool> = true>
|
||||
inline float remainder(const T x, const U y) {
|
||||
const auto x_f = static_cast<float>(x);
|
||||
const auto y_f = static_cast<float>(y);
|
||||
return x_f - y_f * floor_divide(x_f, y_f);
|
||||
}
|
||||
|
||||
template <
|
||||
typename T,
|
||||
typename U,
|
||||
::metal::enable_if_t<
|
||||
is_scalar_integral_v<T> && is_scalar_integral_v<U>,
|
||||
bool> = true>
|
||||
inline common_dtype<T, U> remainder(const T x, const U y) {
|
||||
auto rc = x % y;
|
||||
return rc == 0 || (x ^ y) > 0 ? rc : rc + y;
|
||||
}
|
||||
|
||||
// Based on aten/src/ATen/native/Pow.h
|
||||
template <
|
||||
typename T,
|
||||
::metal::enable_if_t<is_scalar_integral_v<T>, bool> = true>
|
||||
inline T powi_impl(T a, T b) {
|
||||
T result = 1;
|
||||
while (b) {
|
||||
if (b & 1) {
|
||||
result *= a;
|
||||
}
|
||||
b /= 2;
|
||||
a *= a;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <
|
||||
typename T,
|
||||
typename U,
|
||||
::metal::enable_if_t<
|
||||
is_scalar_floating_point_v<T> || is_scalar_floating_point_v<U>,
|
||||
bool> = true>
|
||||
inline float pow(T a, U b) {
|
||||
return ::metal::precise::pow(static_cast<float>(a), static_cast<float>(b));
|
||||
}
|
||||
|
||||
// Complex pow - use polar form: a = r*e^(i*theta)
|
||||
// a^b = exp(b * log(a)) = exp(b * (log(r) + i*theta))
|
||||
template <
|
||||
typename T,
|
||||
typename U,
|
||||
::metal::enable_if_t<is_complex_v<T> && is_complex_v<U>, bool> = true>
|
||||
inline float2 pow(T a, U b) {
|
||||
// Convert a to polar form
|
||||
// Use explicit computation instead of length() due to numerical issues
|
||||
const auto r = ::metal::precise::sqrt(a.x * a.x + a.y * a.y);
|
||||
|
||||
// Special case: if r is 0, return 0
|
||||
if (r == 0.0) {
|
||||
return float2(0.0, 0.0);
|
||||
}
|
||||
|
||||
const auto theta = ::metal::precise::atan2(a.y, a.x);
|
||||
const auto log_r = ::metal::precise::log(r);
|
||||
|
||||
// Calculate a^b = r^b * e^(i*theta*b)
|
||||
// new_r = exp(b.x * log(r) - b.y * theta)
|
||||
// new_theta = b.x * theta + b.y * log(r)
|
||||
const auto new_r = ::metal::precise::exp(b.x * log_r - b.y * theta);
|
||||
const auto new_theta = b.x * theta + b.y * log_r;
|
||||
|
||||
return float2(
|
||||
new_r * ::metal::precise::cos(new_theta),
|
||||
new_r * ::metal::precise::sin(new_theta));
|
||||
}
|
||||
|
||||
// Integral pow - unsigned types
|
||||
template <
|
||||
typename T,
|
||||
typename U,
|
||||
::metal::enable_if_t<
|
||||
is_scalar_integral_v<T> && !::metal::is_signed_v<T>,
|
||||
bool> = true>
|
||||
inline T pow(T a, U b) {
|
||||
return powi_impl(a, T(b));
|
||||
}
|
||||
|
||||
// Integral pow - signed types
|
||||
template <
|
||||
typename T,
|
||||
typename U,
|
||||
::metal::enable_if_t<
|
||||
is_scalar_integral_v<T>&& ::metal::is_signed_v<T>,
|
||||
bool> = true>
|
||||
inline T pow(T a, U b) {
|
||||
if (b < 0) {
|
||||
if (a == 1) {
|
||||
return 1;
|
||||
} else if (a == -1) {
|
||||
auto negative = (-b) % static_cast<T>(2);
|
||||
return negative ? -1 : 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return powi_impl(a, T(b));
|
||||
}
|
||||
|
||||
// Based on algorithm described in
|
||||
// https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html#1202
|
||||
inline float log1p(float x) {
|
||||
const auto xp1 = 1.0f + x;
|
||||
// First two elements of Taylor series for log(1+x) in Horner's form are:
|
||||
// log(1+x) = x * (1 - x * (.5 ...)), but if 1 + x == x, then it's just x
|
||||
if (xp1 == 1.0f) {
|
||||
return x;
|
||||
}
|
||||
auto rc = ::metal::precise::log(xp1);
|
||||
if (x > -.5 && x < .5) {
|
||||
// Order of operations is important here for higher precision
|
||||
rc *= x / (xp1 - 1.0f);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
// The function is ported from mlx
|
||||
inline float2 log1p(float2 in) {
|
||||
float x = in.x;
|
||||
float y = in.y;
|
||||
float zabs = ::metal::precise::sqrt(x * x + y * y);
|
||||
float theta = ::metal::atan2(y, x + 1);
|
||||
if (zabs < 0.5f) {
|
||||
float r = x * (2 + x) + y * y;
|
||||
if (r == 0) { // handle underflow
|
||||
return {x, theta};
|
||||
}
|
||||
return {0.5f * log1p(r), theta};
|
||||
} else {
|
||||
auto z0 = ::metal::sqrt((x + 1) * (x + 1) + y * y);
|
||||
return {::metal::log(z0), theta};
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T1, typename T2 = T1>
|
||||
struct pair {
|
||||
T1 first;
|
||||
T2 second;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
inline T conj(T a) {
|
||||
return a;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline half2 conj(half2 a) {
|
||||
return half2(a.x, -a.y);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline float2 conj(float2 a) {
|
||||
return float2(a.x, -a.y);
|
||||
}
|
||||
|
||||
// The following implementation of hypot provides better numerical stability
|
||||
// than the naive implementation. It is based on:
|
||||
// https://github.com/pearu/functional_algorithms/blob/7dbbfd7db225b1c202e0e364fc435423ccf52dbe/functional_algorithms/algorithms.py#L168
|
||||
//
|
||||
// This implementation changes the naive formula for the hypotenuse of a right
|
||||
// triangle, `h = sqrt(a^2 + b^2)`, into three alternate forms to be used in
|
||||
// different cases. The reason why the naive formula is unstable is because of
|
||||
// the square terms. If `a` or `b` are very large or very small floating point
|
||||
// numbers, then their squares will resolve to inf or 0.
|
||||
//
|
||||
// Assume `a >= b >= 0`. We can first change the formula to:
|
||||
// `h = a sqrt(1 + (b / a)^2)`
|
||||
// `h = a sqrt(1 + r)`
|
||||
// where `r = (b / a)^2`. Since `a >= b >= 0`, then `1 >= r >= 0`.
|
||||
//
|
||||
// Case 1: `a == b`
|
||||
// The formula simplifies to `h = a sqrt(2)`.
|
||||
//
|
||||
// Case 2: `1 >> r > 0`
|
||||
// Due to floating point error, `sqrt(1 + r)` resolves to 1. So we use the
|
||||
// binomial approximation `sqrt(1 + r) ≈ 1 + r / 2`, and the formula becomes
|
||||
// `h ≈ a + a r / 2`.
|
||||
//
|
||||
// Case 3: All other cases.
|
||||
// Use `h = a sqrt(1 + r)`.
|
||||
inline float hypot(float a_, float b_) {
|
||||
auto a = max(a_, b_);
|
||||
auto b = min(a_, b_);
|
||||
|
||||
auto b_over_a = c10::metal::div(b, a);
|
||||
auto r = c10::metal::mul(b_over_a, b_over_a);
|
||||
auto sqrt_1_plus_r = ::metal::precise::sqrt(1 + r);
|
||||
|
||||
auto h1 = M_SQRT2_F * a;
|
||||
auto h2 = a + a * r / 2;
|
||||
auto h3 = a * sqrt_1_plus_r;
|
||||
bool is_h1 = (a == b);
|
||||
bool is_h2 = ((sqrt_1_plus_r == 1) && (r > 0));
|
||||
|
||||
return ::metal::select(::metal::select(h3, h2, is_h2), h1, is_h1);
|
||||
}
|
||||
|
||||
#define INSTANTIATE_FOR_ALL_TYPES(MACRO) \
|
||||
MACRO(float); \
|
||||
MACRO(half); \
|
||||
MACRO(bfloat); \
|
||||
MACRO(float2); \
|
||||
MACRO(long); \
|
||||
MACRO(char); \
|
||||
MACRO(uchar); \
|
||||
MACRO(short); \
|
||||
MACRO(int);
|
||||
|
||||
#define INSTANTIATE_FOR_FLOAT_TYPES(MACRO) \
|
||||
MACRO(float); \
|
||||
MACRO(half); \
|
||||
MACRO(bfloat);
|
||||
|
||||
} // namespace metal
|
||||
} // namespace c10
|
||||
|
||||
#else
|
||||
#error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
|
||||
#endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user