Initial import: grid-bot — grid trading bot for BTC-USDT on Cifra Markets

This commit is contained in:
Kolp
2026-09-24 13:22:23 +07:00
commit 642cc11a9f
18968 changed files with 5683248 additions and 0 deletions
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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