chore: import upstream snapshot with attribution
Lint / lint (push) Has been cancelled
Build Docs / Deploy Docs (push) Has been cancelled
Windows CI / Windows (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:23:58 +08:00
commit 770d92cb1f
694 changed files with 114634 additions and 0 deletions
+37
View File
@@ -0,0 +1,37 @@
/*!
* Copyright (c) 2023-2025 by Contributors
* \file support/debug_utils.h
* \brief Tools for debug purposes.
*/
#ifndef MLC_LLM_SUPPORT_DEBUG_UTILS_H_
#define MLC_LLM_SUPPORT_DEBUG_UTILS_H_
#include "../tokenizers/tokenizers.h"
namespace mlc {
namespace llm {
/*! \brief A registry for debug information. */
class DebugRegistry {
public:
static DebugRegistry* Global() {
static DebugRegistry reg;
return ®
}
// Tokenizer information, helpful for converting token id to token string in debugging
Tokenizer tokenizer;
};
/*! \brief Register the tokenizer to the global tokenizer registry. */
inline void DebugRegisterTokenizer(const Tokenizer& tokenizer) {
DebugRegistry::Global()->tokenizer = tokenizer;
}
/*! \brief Get the registered tokenizer from the global tokenizer registry. */
inline Tokenizer DebugGetTokenizer() { return DebugRegistry::Global()->tokenizer; }
} // namespace llm
} // namespace mlc
#endif // MLC_LLM_SUPPORT_DEBUG_UTILS_H_
+147
View File
@@ -0,0 +1,147 @@
/*!
* Copyright (c) 2023-2025 by Contributors
* \file support/dynamic_bitset.h
* \brief The header for utilities used in grammar-guided generation.
*/
#ifndef MLC_LLM_SUPPORT_DYNAMIC_BITSET_H_
#define MLC_LLM_SUPPORT_DYNAMIC_BITSET_H_
#include <tvm/runtime/logging.h>
#include <cstdint>
#include <cstring>
#include <vector>
namespace mlc {
namespace llm {
/*!
* \brief A bitset whose length is specified at runtime. Note the size cannot be changed after
* construction.
* \details The buffer of the bitset is a uint32_t array. There are two uses for this class:
* - When passing nullptr to data, it maintains an internal buffer for the bitset.
* - When passing a pointer to a buffer with enough size, it uses the external buffer for the
* bitset.
*/
class DynamicBitset {
public:
/*!
* \brief Calculate the minimal size of the uint32_t buffer for the bitset with the given size.
* \param element_size The size of the bitset.
* \return The minimal buffer size.
*/
static int CalculateBufferSize(int element_size) { return (element_size + 31) / 32; }
/*!
* \brief Construct a empty bitset. This object should be assigned to a valid bitset before using.
*/
DynamicBitset() : size_(0), buffer_size_(0), data_(nullptr), is_internal_(true) {}
/*!
* \brief Construct a bitset with the given size.
* \param size The size of the bitset.
* \param data The buffer for the bitset. If nullptr, the bitset will maintain an internal buffer.
*/
DynamicBitset(int size, uint32_t* data = nullptr)
: size_(size), buffer_size_(CalculateBufferSize(size)) {
if (data == nullptr) {
internal_buffer_.resize(buffer_size_, 0);
data_ = internal_buffer_.data();
is_internal_ = true;
} else {
data_ = data;
is_internal_ = false;
}
}
/*! \brief Copy assignment. */
DynamicBitset& operator=(const DynamicBitset& other) {
TVM_FFI_DCHECK(is_internal_ || size_ >= other.size_)
<< "Expanding bitset size is not allowed when the "
"memory of the bitset is externally managed";
size_ = other.size_;
buffer_size_ = other.buffer_size_;
if (is_internal_) {
internal_buffer_.reserve(buffer_size_);
data_ = internal_buffer_.data();
}
if (data_ != other.data_) {
std::memcpy(data_, other.data_, buffer_size_ * sizeof(uint32_t));
}
return *this;
}
/*! \brief Move assignment. */
DynamicBitset& operator=(DynamicBitset&& other) {
size_ = other.size_;
buffer_size_ = other.buffer_size_;
is_internal_ = other.is_internal_;
if (is_internal_) {
internal_buffer_ = std::move(other.internal_buffer_);
data_ = internal_buffer_.data();
} else {
data_ = other.data_;
}
return *this;
}
/*! \brief Get the value of the bit at the given index. */
bool operator[](int index) const {
TVM_FFI_DCHECK(data_ && index >= 0 && index < size_);
return (data_[index / 32] >> (index % 32)) & 1;
}
/*! \brief Get the size of the bitset. */
int Size() const { return size_; }
/*! \brief Set the whole bitset to true. */
void Set() {
TVM_FFI_DCHECK(data_);
std::memset(data_, 0xFF, buffer_size_ * sizeof(uint32_t));
}
/*! \brief Set the bit at the given index to the given value. */
void Set(int index, bool value = true) {
TVM_FFI_DCHECK(data_ && index >= 0 && index < size_);
if (value) {
data_[index / 32] |= 1 << (index % 32);
} else {
data_[index / 32] &= ~(1 << (index % 32));
}
}
/*! \brief Set the whole bitset to false. */
void Reset() {
TVM_FFI_DCHECK(data_);
std::memset(data_, 0, buffer_size_ * sizeof(uint32_t));
}
/*! \brief Set the bit at the given index to false. */
void Reset(int index) { Set(index, false); }
/*! \brief Perform a bitwise OR operation between the current bitset and another bitset. */
DynamicBitset& operator|=(const DynamicBitset& other) {
TVM_FFI_DCHECK(buffer_size_ <= other.buffer_size_);
for (int i = 0; i < buffer_size_; ++i) {
data_[i] |= other.data_[i];
}
return *this;
}
private:
// The size of the bitset.
int size_;
// The size of the buffer.
int buffer_size_;
// The buffer for the bitset.
uint32_t* data_;
// The internal buffer. It is empty if not needed.
std::vector<uint32_t> internal_buffer_;
// Whether the buffer is internally managed.
bool is_internal_;
};
} // namespace llm
} // namespace mlc
#endif // MLC_LLM_SUPPORT_DYNAMIC_BITSET_H_
+211
View File
@@ -0,0 +1,211 @@
/*!
* Copyright (c) 2023-2025 by Contributors
* \file support/encoding.cc
*/
#include "encoding.h"
#include <tvm/runtime/logging.h>
#include <array>
namespace mlc {
namespace llm {
std::string PrintAsUTF8(TCodepoint codepoint) {
TVM_FFI_ICHECK(codepoint <= 0x10FFFF) << "Invalid codepoint: " << codepoint;
std::string utf8;
if (codepoint <= 0x7F) {
// 1-byte sequence
utf8 += static_cast<char>(codepoint);
} else if (codepoint <= 0x7FF) {
// 2-byte sequence
utf8 += static_cast<char>(0xC0 | ((codepoint >> 6) & 0x1F));
utf8 += static_cast<char>(0x80 | (codepoint & 0x3F));
} else if (codepoint <= 0xFFFF) {
// 3-byte sequence
utf8 += static_cast<char>(0xE0 | ((codepoint >> 12) & 0x0F));
utf8 += static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F));
utf8 += static_cast<char>(0x80 | (codepoint & 0x3F));
} else {
// 4-byte sequence
utf8 += static_cast<char>(0xF0 | ((codepoint >> 18) & 0x07));
utf8 += static_cast<char>(0x80 | ((codepoint >> 12) & 0x3F));
utf8 += static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F));
utf8 += static_cast<char>(0x80 | (codepoint & 0x3F));
}
return utf8;
}
std::string PrintAsEscaped(
TCodepoint codepoint,
const std::unordered_map<TCodepoint, std::string>& additional_escape_map) {
static const std::unordered_map<TCodepoint, std::string> kCodepointToEscape = {
{'\'', "\\\'"}, {'\"', "\\\""}, {'\?', "\\\?"}, {'\\', "\\\\"}, {'\a', "\\a"},
{'\b', "\\b"}, {'\f', "\\f"}, {'\n', "\\n"}, {'\r', "\\r"}, {'\t', "\\t"},
{'\v', "\\v"}, {'\0', "\\0"}, {'\x1B', "\\e"}};
if (auto it = additional_escape_map.find(codepoint); it != additional_escape_map.end()) {
return it->second;
}
if (auto it = kCodepointToEscape.find(codepoint); it != kCodepointToEscape.end()) {
return it->second;
}
if (codepoint >= 0x20 && codepoint <= 0x7E) {
return std::string({static_cast<char>(codepoint)});
}
// convert codepoint to hex
char prefix = codepoint <= 0xFF ? 'x' : codepoint <= 0xFFFF ? 'u' : 'U';
int width = codepoint <= 0xFF ? 2 : codepoint <= 0xFFFF ? 4 : 8;
std::stringstream ss;
ss << std::setfill('0') << std::setw(width) << std::hex << codepoint;
auto hex = ss.str();
return std::string("\\") + prefix + hex;
}
std::string PrintAsEscaped(uint8_t raw_char) { return PrintAsEscaped(raw_char); }
std::string PrintAsEscaped(std::string raw_str) {
std::string res;
auto codepoints = ParseUTF8(raw_str.c_str(), UTF8ErrorPolicy::kReturnByte);
for (auto c : codepoints) {
res += PrintAsEscaped(c);
}
return res;
}
std::tuple<bool, int, TCodepoint> HandleUTF8FirstByte(uint8_t byte) {
static const std::array<int8_t, 5> kFirstByteMask = {0x00, 0x7F, 0x1F, 0x0F, 0x07};
// clang-format off
static const std::array<int, 256> kUtf8Bytes = {
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
4, 4, 4, 4, 4, 4, 4, 4, -1, -1, -1, -1, -1, -1, -1, -1,
};
// clang-format on
auto num_bytes = kUtf8Bytes[static_cast<uint8_t>(byte)];
if (num_bytes == -1) {
return {false, 0, 0};
}
return {true, num_bytes, byte & kFirstByteMask[num_bytes]};
}
std::pair<TCodepoint, const char*> ParseNextUTF8(const char* utf8, UTF8ErrorPolicy error_policy) {
auto [accepted, num_bytes, res] = HandleUTF8FirstByte(utf8[0]);
if (accepted) {
for (int i = 1; i < num_bytes; ++i) {
if (utf8[i] == 0 || (static_cast<uint8_t>(utf8[i]) & 0xC0) != 0x80) {
// invalid utf8
accepted = false;
break;
}
res = (res << 6) | (static_cast<uint8_t>(utf8[i]) & 0x3F);
}
}
if (!accepted) {
// invalid utf8
if (error_policy == UTF8ErrorPolicy::kReturnInvalid) {
return {CharHandlingError::kInvalidUTF8, utf8};
} else {
return {static_cast<unsigned char>(utf8[0]), utf8 + 1};
}
}
return {res, utf8 + num_bytes};
}
std::vector<TCodepoint> ParseUTF8(const char* utf8, UTF8ErrorPolicy error_policy) {
std::vector<TCodepoint> codepoints;
while (*utf8 != 0) {
TCodepoint codepoint;
std::tie(codepoint, utf8) = ParseNextUTF8(utf8, error_policy);
if (codepoint == CharHandlingError::kInvalidUTF8) {
return {codepoint};
}
codepoints.push_back(codepoint);
}
return codepoints;
}
inline int HexCharToInt(char c) {
if (c >= '0' && c <= '9') {
return c - '0';
} else if (c >= 'a' && c <= 'f') {
return c - 'a' + 10;
} else if (c >= 'A' && c <= 'F') {
return c - 'A' + 10;
} else {
return -1;
}
}
std::pair<TCodepoint, const char*> ParseNextUTF8OrEscaped(
const char* utf8, const std::unordered_map<std::string, TCodepoint>& additional_escape_map) {
static const std::unordered_map<std::string, TCodepoint> kEscapeToCodepoint = {
{"\\\'", '\''}, {"\\\"", '\"'}, {"\\\?", '\?'}, {"\\\\", '\\'}, {"\\a", '\a'},
{"\\b", '\b'}, {"\\f", '\f'}, {"\\n", '\n'}, {"\\r", '\r'}, {"\\t", '\t'},
{"\\v", '\v'}, {"\\0", '\0'}, {"\\e", '\x1B'}};
if (utf8[0] != '\\') {
return ParseNextUTF8(utf8, UTF8ErrorPolicy::kReturnInvalid);
}
auto escape_sequence = std::string(utf8, 2);
if (auto it = additional_escape_map.find(escape_sequence); it != additional_escape_map.end()) {
return {it->second, utf8 + 2};
}
if (auto it = kEscapeToCodepoint.find(escape_sequence); it != kEscapeToCodepoint.end()) {
return {it->second, utf8 + 2};
}
if (utf8[1] == 'x') {
// arbitrary length hex
int len = 0;
int32_t codepoint = 0;
while (true) {
auto digit = HexCharToInt(utf8[2 + len]);
if (digit == -1) {
break;
}
codepoint = codepoint * 16 + digit;
++len;
}
if (len == 0) {
return {CharHandlingError::kInvalidEscape, utf8};
}
return {codepoint, utf8 + len + 2};
} else if (utf8[1] == 'u' || utf8[1] == 'U') {
// 4- or 8-digit hex
int len = utf8[1] == 'u' ? 4 : 8;
int32_t codepoint = 0;
for (int i = 0; i < len; ++i) {
auto digit = HexCharToInt(utf8[i + 2]);
if (digit == -1) {
return {CharHandlingError::kInvalidEscape, utf8};
}
codepoint = codepoint * 16 + digit;
}
return {codepoint, utf8 + len + 2};
} else {
return {CharHandlingError::kInvalidEscape, utf8};
}
}
} // namespace llm
} // namespace mlc
+114
View File
@@ -0,0 +1,114 @@
/*!
* Copyright (c) 2023-2025 by Contributors
* \file support/encoding.h
* \brief Encoding and decoding from/to UTF-8 and escape sequence to/from codepoints.
*/
#ifndef MLC_LLM_SUPPORT_ENCODING_H_
#define MLC_LLM_SUPPORT_ENCODING_H_
#include <cstdint>
#include <string>
#include <unordered_map>
#include <vector>
namespace mlc {
namespace llm {
/*! \brief Represents a unicode codepoint. */
using TCodepoint = int32_t;
/*!
* \brief Handle the utf-8 first byte.
* \returns (is_valid, total_number_of_bytes, initial_codepoint).
*/
std::tuple<bool, int, TCodepoint> HandleUTF8FirstByte(uint8_t byte);
/*!
* \brief Print a codepoint to a UTF-8 string.
* \param codepoint The codepoint.
* \return The UTF-8 string.
*/
std::string PrintAsUTF8(TCodepoint codepoint);
/*!
* \brief Print a codepoint to a escaped string. If the codepoint is not printable, it will be
* escaped. By default the function support escape sequences in C ("\n", "\t", "\u0123"). User can
* specify more escape sequences using additional_escape_map.
* \param codepoint The codepoint.
* \param additional_escape_map A map from codepoint to escape sequence. If the codepoint is in the
* map, it will be escaped using the corresponding escape sequence. e.g. {{'-', "\\-"}}. \return The
* printable string.
*/
std::string PrintAsEscaped(
TCodepoint codepoint,
const std::unordered_map<TCodepoint, std::string>& additional_escape_map = {});
/*!
* \brief Print the given char to a escaped string that can be printed.
* \return The escaped string.
*/
std::string PrintAsEscaped(uint8_t raw_char);
/*!
* \brief Print the given string to a escaped string that can be printed.
* \return The escaped string.
*/
std::string PrintAsEscaped(std::string raw_str);
/*!
* \brief Represents an error when handling characters. Will be returned as a special TCodepoint
* value.
*/
enum CharHandlingError : TCodepoint {
/*! \brief The UTF-8 string is invalid. */
kInvalidUTF8 = -10,
/*! \brief The escape sequence is invalid. */
kInvalidEscape = -11,
};
/*!
* \brief The method to handle invalid UTF-8 sequence.
*/
enum class UTF8ErrorPolicy {
/*! \brief Return an error codepoint when an error is encountered. */
kReturnInvalid,
/*! \brief Skip the error and continue parsing. */
kReturnByte,
};
/*!
* \brief Parse the first codepoint in a UTF-8 string.
* \param utf8 The UTF-8 string.
* \return The codepoint and new pointer. If the UTF-8 string is invalid, and the error policy is
* kReturnInvalid, the function returns (CharHandlingError::kInvalidUTF8, input char pointer).
*/
std::pair<TCodepoint, const char*> ParseNextUTF8(
const char* utf8, UTF8ErrorPolicy error_policy = UTF8ErrorPolicy::kReturnInvalid);
/*!
* \brief Parse all codepoints in a UTF-8 string.
* \param utf8 The UTF-8 string.
* \return All codepoints. If the UTF-8 string is invalid, and the error policy is
* kReturnInvalid, the function returns {CharHandlingError::kInvalidUTF8}.
*/
std::vector<TCodepoint> ParseUTF8(const char* utf8,
UTF8ErrorPolicy error_policy = UTF8ErrorPolicy::kReturnInvalid);
/*!
* \brief Parse the first codepoint from a UTF-8 string. Also checks escape sequences and converts
* the escaped char to its original value.
* \param utf8 The UTF-8 string or the escape sequence.
* \param additional_escape_map A map from escape sequence to codepoint. If the escape sequence is
* in the map, it will be converted to the corresponding codepoint. e.g. {{"\\-", '-'}}.
* \return The codepoint and the new pointer. If the UTF-8 string or the escape sequence is
* invalid, and the error policy is kReturnInvalid, the function returns
* (CharHandlingError::kInvalidUTF8, input char pointer).
*/
std::pair<TCodepoint, const char*> ParseNextUTF8OrEscaped(
const char* utf8,
const std::unordered_map<std::string, TCodepoint>& additional_escape_map = {});
} // namespace llm
} // namespace mlc
#endif // MLC_LLM_SUPPORT_ENCODING_H_
+284
View File
@@ -0,0 +1,284 @@
/*!
* \file support/json_parser.h
* \brief Helps to parse JSON strings and objects.
*/
#ifndef MLC_LLM_SUPPORT_JSON_PARSER_H_
#define MLC_LLM_SUPPORT_JSON_PARSER_H_
#include <tvm/ffi/container/shape.h>
#include <tvm/ffi/dtype.h>
#include <tvm/ffi/extra/json.h>
#include <tvm/ffi/string.h>
#include <tvm/runtime/logging.h>
#include <optional>
#include "result.h"
namespace mlc {
namespace llm {
namespace json {
using ::tvm::ffi::json::Array;
using ::tvm::ffi::json::Object;
using ::tvm::ffi::json::Value;
/*!
* \brief Parse a JSON string to a JSON object.
* \param json_str The JSON string to parse.
* \return The parsed JSON object.
*/
inline Object ParseToJSONObject(const std::string& json_str) {
tvm::ffi::String err;
Value result = ::tvm::ffi::json::Parse(json_str, &err);
TVM_FFI_CHECK(err.empty(), ValueError)
<< "Failed to parse JSON: err. The JSON string is:" << json_str;
auto opt = result.try_cast<Object>();
TVM_FFI_CHECK(opt.has_value(), ValueError)
<< "The given string is not a JSON object: " << json_str;
return *opt;
}
/*!
* \brief Parse a JSON string to a JSON object.
* \param json_str The JSON string to parse.
* \return The parsed JSON object, or the error message.
*/
inline Result<Object> ParseToJSONObjectWithResultReturn(const std::string& json_str) {
using TResult = Result<Object>;
tvm::ffi::String err;
Value result = ::tvm::ffi::json::Parse(json_str, &err);
if (!err.empty()) {
return TResult::Error("Failed to parse JSON: err. The JSON string is: " + json_str +
". The error is " + std::string(err));
}
auto opt = result.try_cast<Object>();
if (!opt.has_value()) {
return TResult::Error("ValueError: The given string is not a JSON object: " + json_str);
}
return TResult::Ok(*opt);
}
/*!
* \brief Lookup a JSON object by a key, and convert it to a given type.
* \param json The JSON object to look up.
* \param key The key to look up.
* \tparam ValueType The type to be converted to.
* \return The converted value.
*/
template <typename ValueType>
ValueType Lookup(const Object& json, const std::string& key);
/*!
* \brief Lookup a JSON array by an index, and convert it to a given type.
* \param json The JSON array to look up.
* \param index The index to look up.
* \tparam ValueType The type to be converted to.
* \return The converted value.
*/
template <typename ValueType>
ValueType Lookup(const Array& json, int index);
/*!
* \brief Lookup a JSON object by a key, and convert it to a given type.
* If the key doesn't exist or has null value, the default value is returned.
* \param json The JSON object to look up.
* \param key The key to look up.
* \tparam ValueType The type to be converted to.
* \return The converted value, or the default value if the key doesn't exist or has null value.
*/
template <typename ValueType>
inline ValueType LookupOrDefault(const Object& json, const std::string& key,
const ValueType& default_value) {
if (json.count(key) == 0 || json.at(key) == nullptr) {
return default_value;
}
auto opt = json.at(key).try_cast<ValueType>();
TVM_FFI_CHECK(opt.has_value(), ValueError) << "key `" << key << "` has unexpected type";
return *opt;
}
/*!
* \brief Lookup a JSON object by a key, and convert it to a given type.
* If the key doesn't exist or has null value, return std::nullopt.
* \param json The JSON object to look up.
* \param key The key to look up.
* \tparam ValueType The type to be converted to.
* \return The converted value, or std::nullopt if the value doesn't exist or has null value.
*/
template <typename ValueType>
inline std::optional<ValueType> LookupOptional(const Object& json, const std::string& key) {
if (json.count(key) == 0 || json.at(key) == nullptr) {
return std::nullopt;
}
auto opt = json.at(key).try_cast<ValueType>();
TVM_FFI_CHECK(opt.has_value(), ValueError) << "key `" << key << "` has unexpected type";
return *opt;
}
/*!
* \brief Lookup a JSON object by a key, and convert it to a given type.
* \param json The JSON object to look up.
* \param key The key to look up.
* \tparam ValueType The type to be converted to.
* \return The converted value, or the error message.
*/
template <typename ValueType>
inline Result<ValueType> LookupWithResultReturn(const Object& json, const std::string& key) {
using TResult = Result<ValueType>;
if (json.count(key) == 0) {
return TResult::Error("ValueError: key \"" + key + "\" not found in the JSON object");
}
auto opt = json.at(key).try_cast<ValueType>();
if (!opt.has_value()) {
return TResult::Error("ValueError: key \"" + key + "\" has unexpected value type.");
}
return TResult::Ok(*opt);
}
/*!
* \brief Lookup a JSON object by a key, and convert it to a given type.
* If the key doesn't exist or has null value, the default value is returned.
* \param json The JSON object to look up.
* \param key The key to look up.
* \tparam ValueType The type to be converted to.
* \return The converted value, or the default value if the key doesn't exist or has null value
* , or the error message.
*/
template <typename ValueType>
inline Result<ValueType> LookupOrDefaultWithResultReturn(const Object& json, const std::string& key,
const ValueType& default_value) {
using TResult = Result<ValueType>;
if (json.count(key) == 0 || json.at(key) == nullptr) {
return TResult::Ok(default_value);
}
auto opt = json.at(key).try_cast<ValueType>();
if (!opt.has_value()) {
return TResult::Error("ValueError: key \"" + key + "\" has unexpected value type.");
}
return TResult::Ok(*opt);
}
/*!
* \brief Lookup a JSON object by a key, and convert it to a given type.
* If the key doesn't exist or has null value, return std::nullopt.
* \param json The JSON object to look up.
* \param key The key to look up.
* \tparam ValueType The type to be converted to.
* \return The converted value, or std::nullopt if the value doesn't exist or has null value,
* , or the error message.
*/
template <typename ValueType>
inline Result<std::optional<ValueType>> LookupOptionalWithResultReturn(const Object& json,
const std::string& key) {
using TResult = Result<std::optional<ValueType>>;
if (json.count(key) == 0 || json.at(key) == nullptr) {
return TResult::Ok(std::nullopt);
}
auto opt = json.at(key).try_cast<ValueType>();
if (!opt.has_value()) {
return TResult::Error("ValueError: key \"" + key + "\" has unexpected value type.");
}
return TResult::Ok(*opt);
}
// Implementation details
/*! \brief Shape extension to incorporate symbolic shapes. */
struct SymShapeTuple {
tvm::ffi::Shape shape_values;
std::vector<std::string> sym_names;
/*! \brief Convert symbolic shape tuple to static shape tuple with model config. */
tvm::ffi::Shape ToStatic(const Object& model_config) {
std::vector<int64_t> shape;
shape.reserve(shape_values.size());
for (int i = 0; i < static_cast<int>(shape_values.size()); ++i) {
if (shape_values[i] != -1) {
shape.push_back(shape_values[i]);
} else {
auto opt = model_config.at(sym_names[i]).try_cast<int64_t>();
TVM_FFI_CHECK(opt.has_value(), ValueError)
<< "model config is expected to contain \"" << sym_names[i]
<< "\" as an integer. However, the given config has unexpected type for \""
<< sym_names[i] << "\".";
shape.push_back(*opt);
}
}
return tvm::ffi::Shape(std::move(shape));
}
};
namespace details {
inline DLDataType DTypeFromString(const std::string& s) { return tvm::ffi::StringToDLDataType(s); }
inline SymShapeTuple SymShapeTupleFromArray(const Array& shape) {
std::vector<int64_t> result;
std::vector<std::string> sym_names;
result.reserve(shape.size());
sym_names.reserve(shape.size());
for (int i = 0; i < static_cast<int>(shape.size()); ++i) {
const auto& dim = shape[i];
auto str_opt = dim.try_cast<std::string>();
if (str_opt.has_value()) {
result.push_back(-1);
sym_names.push_back(*str_opt);
} else {
auto int_opt = dim.try_cast<int64_t>();
TVM_FFI_CHECK(int_opt.has_value(), ValueError) << "shape has unexpected type";
result.push_back(*int_opt);
sym_names.push_back("");
}
}
return SymShapeTuple{tvm::ffi::Shape(std::move(result)), sym_names};
}
} // namespace details
template <typename ValueType>
inline ValueType Lookup(const Object& json, const std::string& key) {
TVM_FFI_CHECK(json.count(key) != 0, ValueError)
<< "key `" << key << "` not found in the JSON object";
auto opt = json.at(key).try_cast<ValueType>();
TVM_FFI_CHECK(opt.has_value(), ValueError) << "key `" << key << "` has unexpected type";
return *opt;
}
template <typename ValueType>
inline ValueType Lookup(const Array& json, int index) {
TVM_FFI_ICHECK(index < static_cast<int>(json.size()))
<< "IndexError: json::array index out of range";
auto opt = json[index].try_cast<ValueType>();
TVM_FFI_ICHECK(opt.has_value()) << "ValueError: value at index `" << index
<< "` has unexpected type";
return *opt;
}
template <>
inline DLDataType Lookup(const Object& json, const std::string& key) {
return details::DTypeFromString(Lookup<std::string>(json, key));
}
template <>
inline DLDataType Lookup(const Array& json, int index) {
return details::DTypeFromString(Lookup<std::string>(json, index));
}
template <>
inline SymShapeTuple Lookup(const Object& json, const std::string& key) {
return details::SymShapeTupleFromArray(Lookup<Array>(json, key));
}
template <>
inline SymShapeTuple LookupOrDefault(const Object& json, const std::string& key,
const SymShapeTuple& default_value) {
if (json.count(key) == 0 || json.at(key) == nullptr) {
return default_value;
}
return details::SymShapeTupleFromArray(Lookup<Array>(json, key));
}
template <>
inline SymShapeTuple Lookup(const Array& json, int index) {
return details::SymShapeTupleFromArray(Lookup<Array>(json, index));
}
} // namespace json
} // namespace llm
} // namespace mlc
#endif // MLC_LLM_SUPPORT_JSON_PARSER_H_
+32
View File
@@ -0,0 +1,32 @@
/*!
* Copyright (c) 2023-2025 by Contributors
* \file support/load_bytes_from_file.h
* \brief Utility methods to load from files.
*/
#ifndef MLC_LLM_SUPPORT_LOAD_BYTES_FROM_FILE_H_
#define MLC_LLM_SUPPORT_LOAD_BYTES_FROM_FILE_H_
#include <tvm/runtime/logging.h>
#include <fstream>
#include <string>
namespace mlc {
namespace llm {
inline std::string LoadBytesFromFile(const std::string& path) {
std::ifstream fs(path, std::ios::in | std::ios::binary);
TVM_FFI_ICHECK(!fs.fail()) << "Cannot open " << path;
std::string data;
fs.seekg(0, std::ios::end);
size_t size = static_cast<size_t>(fs.tellg());
fs.seekg(0, std::ios::beg);
data.resize(size);
fs.read(data.data(), size);
return data;
}
} // namespace llm
} // namespace mlc
#endif // MLC_LLM_SUPPORT_LOAD_BYTES_FROM_FILE_H_
+107
View File
@@ -0,0 +1,107 @@
/*!
* \file support/module_vtable.h
* \brief Compatibility shim providing the TVM_MODULE_VTABLE_* macros that
* previously lived in <tvm/runtime/module.h>. After the TVM runtime
* refactor the macros were moved into TVM's private
* src/runtime/vm/module_utils.h, so we vendor the surface mlc-llm uses
* here to keep the existing call sites unchanged.
*/
#ifndef MLC_LLM_SUPPORT_MODULE_VTABLE_H_
#define MLC_LLM_SUPPORT_MODULE_VTABLE_H_
#include <tvm/ffi/cast.h>
#include <tvm/ffi/extra/module.h>
#include <tvm/ffi/function.h>
#include <tvm/ffi/object.h>
#include <utility>
namespace mlc {
namespace llm {
namespace module_vtable_detail {
template <typename T>
struct EntryHelper {};
template <typename T, typename R, typename... Args>
struct EntryHelper<R (T::*)(Args...) const> {
using MemFnType = R (T::*)(Args...) const;
TVM_FFI_INLINE static void Call(::tvm::ffi::Any* rv, T* self, MemFnType f,
::tvm::ffi::PackedArgs args) {
auto wrapped = [self, f](Args... args) -> R { return (self->*f)(std::forward<Args>(args)...); };
::tvm::ffi::details::unpack_call<R>(std::make_index_sequence<sizeof...(Args)>{}, nullptr,
wrapped, args.data(), args.size(), rv);
}
};
template <typename T, typename R, typename... Args>
struct EntryHelper<R (T::*)(Args...)> {
using MemFnType = R (T::*)(Args...);
TVM_FFI_INLINE static void Call(::tvm::ffi::Any* rv, T* self, MemFnType f,
::tvm::ffi::PackedArgs args) {
auto wrapped = [self, f](Args... args) -> R { return (self->*f)(std::forward<Args>(args)...); };
::tvm::ffi::details::unpack_call<R>(std::make_index_sequence<sizeof...(Args)>{}, nullptr,
wrapped, args.data(), args.size(), rv);
}
};
template <typename T, typename... Args>
struct EntryHelper<void (T::*)(Args...) const> {
using MemFnType = void (T::*)(Args...) const;
TVM_FFI_INLINE static void Call(::tvm::ffi::Any* rv, T* self, MemFnType f,
::tvm::ffi::PackedArgs args) {
auto wrapped = [self, f](Args... args) -> void { (self->*f)(std::forward<Args>(args)...); };
::tvm::ffi::details::unpack_call<void>(std::make_index_sequence<sizeof...(Args)>{}, nullptr,
wrapped, args.data(), args.size(), rv);
}
};
template <typename T, typename... Args>
struct EntryHelper<void (T::*)(Args...)> {
using MemFnType = void (T::*)(Args...);
TVM_FFI_INLINE static void Call(::tvm::ffi::Any* rv, T* self, MemFnType f,
::tvm::ffi::PackedArgs args) {
auto wrapped = [self, f](Args... args) -> void { (self->*f)(std::forward<Args>(args)...); };
::tvm::ffi::details::unpack_call<void>(std::make_index_sequence<sizeof...(Args)>{}, nullptr,
wrapped, args.data(), args.size(), rv);
}
};
} // namespace module_vtable_detail
} // namespace llm
} // namespace mlc
#define TVM_MODULE_VTABLE_BEGIN(TypeKey) \
const char* kind() const final { return TypeKey; } \
::tvm::ffi::Optional<::tvm::ffi::Function> GetFunction(const ::tvm::ffi::String& _name) \
override { \
using SelfPtr = std::remove_cv_t<decltype(this)>; \
::tvm::ffi::ObjectPtr<::tvm::ffi::Object> _self = \
::tvm::ffi::GetObjectPtr<::tvm::ffi::Object>(this);
#define TVM_MODULE_VTABLE_END() \
return std::nullopt; \
}
#define TVM_MODULE_VTABLE_END_WITH_DEFAULT(MemFunc) \
{ \
auto f = (MemFunc); \
return (this->*f)(_name); \
} \
}
#define TVM_MODULE_VTABLE_ENTRY(Name, MemFunc) \
if (_name == Name) { \
return ::tvm::ffi::Function::FromPacked( \
[_self](::tvm::ffi::PackedArgs args, ::tvm::ffi::Any* rv) -> void { \
using Helper = ::mlc::llm::module_vtable_detail::EntryHelper<decltype(MemFunc)>; \
SelfPtr self = static_cast<SelfPtr>(_self.get()); \
Helper::Call(rv, self, MemFunc, args); \
}); \
}
#define TVM_MODULE_VTABLE_ENTRY_PACKED(Name, MemFunc) \
if (_name == Name) { \
return ::tvm::ffi::Function( \
[_self](::tvm::ffi::PackedArgs args, ::tvm::ffi::Any* rv) -> void { \
(static_cast<SelfPtr>(_self.get())->*(MemFunc))(args, rv); \
}); \
}
#endif // MLC_LLM_SUPPORT_MODULE_VTABLE_H_
+49
View File
@@ -0,0 +1,49 @@
/*!
* Copyright (c) 2023-2025 by Contributors
* \file support/progress_bar.h
* \brief A simple progress bar in C++.
*/
#ifndef MLC_LLM_SUPPORT_PROGRESS_BAR_H_
#define MLC_LLM_SUPPORT_PROGRESS_BAR_H_
#include <iostream>
#include <string>
namespace mlc {
namespace llm {
class ProgressBar {
public:
explicit ProgressBar(int total, int width = 100) : total(total), width(width), cur(0) {}
void Progress() {
if (cur < total) {
++cur;
}
int bar_width = width - 2; // Adjust for borders
int completed = static_cast<int>(static_cast<float>(cur) / total * bar_width);
int remaining = bar_width - completed;
std::cout << "[" //
<< std::string(completed, '=') //
<< ">" //
<< std::string(remaining, ' ') //
<< "] " //
<< " [" << cur << "/" << total << "]";
if (cur < total) {
std::cout << "\r";
std::cout.flush();
} else {
std::cout << std::endl; // Move to the next line after the progress bar is complete
}
}
private:
int total;
int width;
int cur;
};
} // namespace llm
} // namespace mlc
#endif // MLC_LLM_SUPPORT_PROGRESS_BAR_H_
+37
View File
@@ -0,0 +1,37 @@
/*!
* Copyright (c) 2023-2025 by Contributors
* \file support/random.h
* \brief Header of random number generator.
*/
#ifndef MLC_LLM_SUPPORT_RANDOM_H_
#define MLC_LLM_SUPPORT_RANDOM_H_
#include <random>
namespace mlc {
namespace llm {
// Random number generator
class RandomGenerator {
private:
std::mt19937 gen;
std::uniform_real_distribution<> dis;
public:
RandomGenerator(int seed = std::random_device{}()) : gen(seed), dis(0.0, 1.0) {}
static RandomGenerator& GetInstance(int seed = std::random_device{}()) {
static RandomGenerator instance(seed);
return instance;
}
double GetRandomNumber() { return dis(gen); }
void SetSeed(int seed) { gen.seed(seed); }
};
} // namespace llm
} // namespace mlc
#endif // MLC_LLM_SUPPORT_RANDOM_H_
+77
View File
@@ -0,0 +1,77 @@
/*!
* Copyright (c) 2023-2025 by Contributors
* \file support/result.h
* \brief The header for the Result class in MLC LLM.
*/
#ifndef MLC_LLM_SUPPORT_RESULT_H_
#define MLC_LLM_SUPPORT_RESULT_H_
#include <tvm/runtime/logging.h>
#include <optional>
#include <string>
namespace mlc {
namespace llm {
/*!
* \brief The result class in MLC LLM.
* Each instance is either an okay value or an error.
* \tparam T The okay value type of the result.
* \tparam E The error type of the result.
*/
template <typename T, typename E = std::string>
class Result {
public:
/*! \brief Create a result with an okay value. */
static Result Ok(T value) {
Result result;
result.ok_value_ = std::move(value);
return result;
}
/*! \brief Create a result with an error value. */
static Result Error(E error) {
Result result;
result.err_value_ = std::move(error);
return result;
}
/*! \brief Check if the result is okay or not. */
bool IsOk() const { return ok_value_.has_value(); }
/*! \brief Check if the result is an error or not. */
bool IsErr() const { return err_value_.has_value(); }
/*!
* \brief Unwrap the result and return the okay value.
* Throwing exception if it is an error.
* \note This function returns the ok value by moving, so a Result can be unwrapped only once.
*/
T Unwrap() {
TVM_FFI_ICHECK(ok_value_.has_value()) << "Cannot unwrap result on an error value.";
TVM_FFI_ICHECK(!unwrapped_) << "Cannot unwrap a Result instance twice.";
unwrapped_ = true;
return std::move(ok_value_.value());
}
/*!
* \brief Unwrap the result and return the error value.
* Throwing exception if it is an okay value.
* \note This function returns the error value by moving, so a Result can be unwrapped only once.
*/
E UnwrapErr() {
TVM_FFI_ICHECK(err_value_.has_value()) << "Cannot unwrap result on an okay value.";
TVM_FFI_ICHECK(!unwrapped_) << "Cannot unwrap a Result instance twice.";
unwrapped_ = true;
return std::move(err_value_.value());
}
private:
/*! \brief A boolean flag indicating if the result is okay or error. */
bool unwrapped_ = false;
/*! \brief The internal optional okay value. */
std::optional<T> ok_value_;
/*! \brief The internal optional error value. */
std::optional<E> err_value_;
};
} // namespace llm
} // namespace mlc
#endif // MLC_LLM_SUPPORT_RESULT_H_
+73
View File
@@ -0,0 +1,73 @@
/*!
* \file support/threading_backend.h
* \brief Compatibility shim providing the threading helpers that used to live
* at <tvm/runtime/threading_backend.h>. The public TVM header was
* removed in a runtime refactor, but the underlying symbols
* (TVMBackendParallelLaunch, threading::MaxConcurrency,
* threading::SetMaxConcurrency) are still exported from libtvm_runtime.
* We re-declare the inline parallel-for template here so existing
* mlc-llm call sites keep compiling.
*/
#ifndef MLC_LLM_SUPPORT_THREADING_BACKEND_H_
#define MLC_LLM_SUPPORT_THREADING_BACKEND_H_
#include <tvm/runtime/base.h>
#include <tvm/runtime/c_backend_api.h>
#include <algorithm>
#include <cstdint>
namespace tvm {
namespace runtime {
namespace threading {
/*! \return the maximum number of effective workers for this system. */
int MaxConcurrency();
/*! \brief Setting the maximum number of available cores. */
void SetMaxConcurrency(int value);
} // namespace threading
namespace detail {
template <typename T>
struct ParallelForWithThreadingBackendLambdaInvoker {
static int TVMParallelLambdaInvoke(int task_id, TVMParallelGroupEnv* penv, void* cdata) {
int num_task = penv->num_task;
T* lambda_ptr = static_cast<T*>(cdata);
(*lambda_ptr)(task_id, num_task);
return 0;
}
};
template <typename T>
inline void parallel_launch_with_threading_backend(T flambda) {
void* cdata = &flambda;
TVMBackendParallelLaunch(ParallelForWithThreadingBackendLambdaInvoker<T>::TVMParallelLambdaInvoke,
cdata, /*num_task=*/0);
}
} // namespace detail
template <typename T>
inline void parallel_for_with_threading_backend(T flambda, int64_t begin, int64_t end) {
if (end - begin == 1) {
flambda(begin);
return;
}
auto flaunch = [begin, end, flambda](int task_id, int num_task) {
int64_t total_len = end - begin;
int64_t step = (total_len + num_task - 1) / num_task;
int64_t local_begin = std::min(begin + step * task_id, end);
int64_t local_end = std::min(local_begin + step, end);
for (int64_t i = local_begin; i < local_end; ++i) {
flambda(i);
}
};
detail::parallel_launch_with_threading_backend(flaunch);
}
} // namespace runtime
} // namespace tvm
#endif // MLC_LLM_SUPPORT_THREADING_BACKEND_H_
+79
View File
@@ -0,0 +1,79 @@
/*!
* Copyright (c) 2023-2025 by Contributors
* \file support/utils.h
* \brief Utility functions.
*/
#ifndef MLC_LLM_SUPPORT_UTILS_H_
#define MLC_LLM_SUPPORT_UTILS_H_
#include <tvm/support/io.h>
#include <sstream>
#include <string>
#include <vector>
#include "../../3rdparty/tvm/src/support/base64.h"
#include "../../3rdparty/tvm/src/support/bytes_io.h"
namespace mlc {
namespace llm {
/*! \brief Split the input string by the given delimiter character. */
inline std::vector<std::string> Split(const std::string& str, char delim) {
std::string item;
std::istringstream is(str);
std::vector<std::string> ret;
while (std::getline(is, item, delim)) {
ret.push_back(item);
}
return ret;
}
/*!
* \brief Check whether the string starts with a given prefix.
* \param str The given string.
* \param prefix The given prefix.
* \return Whether the prefix matched.
*/
inline bool StartsWith(const std::string& str, const char* prefix) {
size_t n = str.length();
for (size_t i = 0; i < n; i++) {
if (prefix[i] == '\0') return true;
if (str.data()[i] != prefix[i]) return false;
}
// return true if the str is equal to the prefix
return prefix[n] == '\0';
}
/*!
* \brief Get the base64 encoded result of a string.
* \param str The string to encode.
* \return The base64 encoded string.
*/
inline std::string Base64Encode(std::string str) {
std::string result;
tvm::support::BytesOutStream m_stream(&result);
tvm::support::Base64OutStream b64stream(&m_stream);
static_cast<tvm::support::Stream*>(&b64stream)->Write(str);
b64stream.Finish();
return result;
}
/*!
* \brief Get the base64 decoded result of a string.
* \param str The string to decode.
* \return The base64 decoded string.
*/
inline std::string Base64Decode(std::string str) {
std::string result;
tvm::support::BytesInStream m_stream(str);
tvm::support::Base64InStream b64stream(&m_stream);
b64stream.InitPosition();
static_cast<tvm::support::Stream*>(&b64stream)->Read(&result);
return result;
}
} // namespace llm
} // namespace mlc
#endif // MLC_LLM_SUPPORT_UTILS_H_
+59
View File
@@ -0,0 +1,59 @@
/*!
* Copyright (c) 2023-2025 by Contributors
* \file support/image_utils.cc
*/
#include "vlm_utils.h"
#include <cmath>
namespace mlc {
namespace llm {
void CalculateResizeShape(tvm::runtime::Tensor image_data, std::string model_type,
int* p_target_height, int* p_target_width) {
TVM_FFI_ICHECK_EQ(image_data->shape[3], 3) << "Image format must be NHWC";
int height = image_data->shape[1];
int width = image_data->shape[2];
if ("phi3_v" == model_type) {
const int hd_num = 4;
double ratio = static_cast<double>(width) / height;
int scale = 1;
while (scale * std::ceil(scale / ratio) <= hd_num) {
scale += 1;
}
scale -= 1;
*p_target_width = static_cast<int>(scale * 336);
*p_target_height = static_cast<int>(*p_target_width / ratio);
}
}
void CalculatePadShape(tvm::runtime::Tensor image_data, std::string model_type, int* p_pad_height,
int* p_pad_width) {
TVM_FFI_ICHECK_EQ(image_data->shape[3], 3) << "Image format must be NHWC";
if ("phi3_v" == model_type) {
int resized_height = 0, resized_width = 0;
CalculateResizeShape(image_data, model_type, &resized_height, &resized_width);
int tar = (int)(ceil(resized_height / 336.0) * 336);
int top_padding = (int)((tar - resized_height) / 2);
int bottom_padding = tar - resized_height - top_padding;
TVM_FFI_ICHECK_EQ(tar, resized_height + top_padding + bottom_padding)
<< "Padding size not equal!";
*p_pad_height = tar;
*p_pad_width = resized_width;
}
}
void CalculateCropShape(tvm::runtime::Tensor image_data, std::string model_type, int* p_crop_height,
int* p_crop_width) {
TVM_FFI_ICHECK_EQ(image_data->shape[3], 3) << "Image format must be NHWC";
if ("phi3_v" == model_type) {
int pad_h = 0, pad_w = 0;
CalculatePadShape(image_data, model_type, &pad_h, &pad_w);
*p_crop_height = pad_h / 336;
*p_crop_width = pad_w / 336;
}
}
} // namespace llm
} // namespace mlc
+48
View File
@@ -0,0 +1,48 @@
/*!
* Copyright (c) 2023-2025 by Contributors
* \file support/vlm_utils.h
* \brief Tools for debug purposes.
*/
#ifndef MLC_LLM_SUPPORT_VLM_UTILS_H_
#define MLC_LLM_SUPPORT_VLM_UTILS_H_
#include <tvm/runtime/tensor.h>
#include <string>
namespace mlc {
namespace llm {
/*!
* \brief Calculate the target height and width for resizing an image based on the input data and
* model type. \param image_data The input image data as a TVM Tensor. \param model_type The type
* of the model influencing the resizing parameters (e.g., phi3v). \param target_height Reference to
* the variable where the calculated target height will be stored. \param target_width Reference to
* the variable where the calculated target width will be stored.
*/
void CalculateResizeShape(tvm::runtime::Tensor image_data, std::string model_type,
int* p_target_height, int* p_target_width);
/*!
* \brief Calculate the padding height and width for an image based on the input data and model
* type. \param image_data The input image data as a TVM Tensor. \param model_type The type of the
* model influencing the padding parameters (e.g., phi3v). \param pad_height Reference to the
* variable where the calculated padding height will be stored. \param pad_width Reference to the
* variable where the calculated padding width will be stored.
*/
void CalculatePadShape(tvm::runtime::Tensor image_data, std::string model_type, int* p_pad_height,
int* p_pad_width);
/*!
* \brief Calculate the cropping height and width for an image based on the input data and model
* type. \param image_data The input image data as a TVM Tensor. \param model_type The type of the
* model influencing the cropping parameters (e.g., phi3v). \param crop_height Reference to the
* variable where the calculated cropping height will be stored. \param crop_width Reference to the
* variable where the calculated cropping width will be stored.
*/
void CalculateCropShape(tvm::runtime::Tensor image_data, std::string model_type, int* p_crop_height,
int* p_crop_width);
} // namespace llm
} // namespace mlc
#endif // MLC_LLM_SUPPORT_IMAGE_UTILS_H_