This is an automated email from the ASF dual-hosted git repository.
tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git
The following commit(s) were added to refs/heads/main by this push:
new 8a1bd49e4a [FIX][Script] Preserve device identity and unify rendering
configuration (#20543)
8a1bd49e4a is described below
commit 8a1bd49e4a74d6f9cd6b3576b803a0e29bc1dbda
Author: Tianqi Chen <[email protected]>
AuthorDate: Sun Oct 4 09:51:08 2026 -0400
[FIX][Script] Preserve device identity and unify rendering configuration
(#20543)
---
docs/arch/tvmscript.rst | 9 +--
include/tvm/script/printer/doc.h | 19 -----
include/tvm/script/printer/printer.h | 8 ---
src/relax/script/printer/binding.cc | 2 -
src/relax/script/printer/dependent_type.cc | 21 ++----
src/relax/script/printer/distributed.cc | 4 +-
src/relax/script/printer/expr.cc | 7 +-
src/relax/script/printer/function.cc | 6 +-
src/relax/script/printer/global_info.cc | 3 +-
src/relax/script/printer/utils.h | 2 -
src/script/printer/doc_printer.cc | 20 ++----
src/script/printer/doc_printer.h | 1 +
src/script/printer/ir/ir.cc | 10 +--
src/script/printer/printer.cc | 67 ++++++++++++++---
src/script/printer/script_printer.cc | 51 +------------
.../relax/script/test_relax_script_printer.py | 83 +++++++++++++++++++---
tests/python/script/test_script_printer_entry.py | 78 ++++++++++++++++++++
.../python/tirx/script/test_tirx_script_printer.py | 33 ++++++++-
18 files changed, 267 insertions(+), 157 deletions(-)
diff --git a/docs/arch/tvmscript.rst b/docs/arch/tvmscript.rst
index c7441abbc6..7b5feed655 100644
--- a/docs/arch/tvmscript.rst
+++ b/docs/arch/tvmscript.rst
@@ -158,11 +158,12 @@ Printing and round trips
of expressions and statements. The translation engine tracks scopes, names and
each
Doc's original IR object. The script entry point maps these origins to
diagnostic
paths; the private Doc printer formats the tree, annotations and underlines as
Python
-text. Printer configuration stays read-only throughout. ``DocToPythonScript``
also
-formats an existing Doc directly. This tree is separate from the parser's
Python AST.
+text. Printer configuration stays read-only throughout. The Python document
printer
+helper also formats an existing Doc directly for document-level tests. This
tree is
+separate from the parser's Python AST.
-The public ``Script`` text entry points are declared in
-``tvm/script/printer/printer.h``. Their orchestration and diagnostic path
mapping
+The public ``tvm::Script`` text entry point is declared in
+``tvm/script/printer/printer.h``. Its orchestration and diagnostic path mapping
live in ``src/script/printer/printer.cc``; ``doc_translator.h`` exposes the
IR-to-Doc translation protocol.
diff --git a/include/tvm/script/printer/doc.h b/include/tvm/script/printer/doc.h
index eba976d265..1b5c628303 100644
--- a/include/tvm/script/printer/doc.h
+++ b/include/tvm/script/printer/doc.h
@@ -25,7 +25,6 @@
#include <tvm/ir/expr.h>
#include <tvm/ir/type.h>
#include <tvm/runtime/device_api.h>
-#include <tvm/script/printer/config.h>
#include <string>
@@ -1435,24 +1434,6 @@ class NamespaceDoc : public ExprDoc {
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(NamespaceDoc, ExprDoc,
NamespaceDocNode);
};
-/*!
- * \brief Render a completed Doc tree with the supplied configuration.
- * \param doc The Doc tree to render.
- * \param config The rendering configuration.
- * \return The rendered Python source.
- */
-TVM_DLL ffi::String DocToPythonScript(Doc doc, const PrinterConfig& config);
-
-/*!
- * \brief Render explicit headers followed by a completed Doc tree.
- * \param doc The Doc tree to render.
- * \param config The rendering configuration.
- * \param header Ordered String source chunks or CommentDocs.
- * \return The rendered Python source.
- */
-TVM_DLL ffi::String DocToPythonScriptWithHeader(Doc doc, const PrinterConfig&
config,
- const ffi::Array<ffi::Any>&
header);
-
} // namespace printer
} // namespace script
} // namespace tvm
diff --git a/include/tvm/script/printer/printer.h
b/include/tvm/script/printer/printer.h
index 22d4c52e56..308d424bfa 100644
--- a/include/tvm/script/printer/printer.h
+++ b/include/tvm/script/printer/printer.h
@@ -54,14 +54,6 @@ TVM_DLL void RegisterNamespaceAlias(const ffi::String& key,
const ffi::String& d
/*! \brief Read the registered namespace aliases. */
TVM_DLL const ffi::Map<ffi::String, ffi::String>& GetNamespaceAliases();
-/*!
- * \brief Translate IR, recover diagnostic paths, and render Python text.
- * \param obj The input IR object.
- * \param config The translation and rendering options.
- * \return The rendered script.
- */
-TVM_DLL ffi::String Script(const ffi::ObjectRef& obj, const PrinterConfig&
config);
-
} // namespace printer
} // namespace script
} // namespace tvm
diff --git a/src/relax/script/printer/binding.cc
b/src/relax/script/printer/binding.cc
index 1eb978080f..5db6e124de 100644
--- a/src/relax/script/printer/binding.cc
+++ b/src/relax/script/printer/binding.cc
@@ -64,8 +64,6 @@ ffi::Optional<ExprDoc>
VarBindingDocTranslate(DocTranslatorObj* d, ffi::AnyView
TVM_FFI_CHECK(destination == nullptr, TypeError)
<< "printer statement-only node cannot fulfill a destination";
if (auto func = binding->value.as<relax::FunctionNode>()) {
- d->Emit(CommentDoc("from tvm.script import relax as R"),
ffi::GetRef<ffi::ObjectRef>(binding));
- d->Emit(CommentDoc(""), ffi::GetRef<ffi::ObjectRef>(binding));
IdDoc lhs = VarDoc(d, binding->var);
d->Translate(binding->value);
FunctionDoc function =
d->CurrentScopeDocs().back().as_or_throw<FunctionDoc>();
diff --git a/src/relax/script/printer/dependent_type.cc
b/src/relax/script/printer/dependent_type.cc
index 1dea84cb4e..e4a81d7a7c 100644
--- a/src/relax/script/printer/dependent_type.cc
+++ b/src/relax/script/printer/dependent_type.cc
@@ -58,10 +58,11 @@ TVM_FFI_STATIC_INIT_BLOCK() {
kDocTranslate, FDocTranslate::FromNative<&ShapeTypeDocTranslate>());
}
-} // namespace
-
-ExprDoc RelaxTensorTypeDoc(DocTranslatorObj* d, const relax::TensorTypeNode*
ty,
- bool include_vdevice) {
+ffi::Optional<ExprDoc> TensorTypeDocTranslate(DocTranslatorObj* d,
ffi::AnyView input,
+ const ffi::Object*) {
+ const auto* ty =
+ ffi::details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const
relax::TensorTypeNode>(
+ input);
ffi::Array<ExprDoc> args;
ffi::Array<ffi::String> keys;
ffi::Array<ExprDoc> values;
@@ -82,7 +83,7 @@ ExprDoc RelaxTensorTypeDoc(DocTranslatorObj* d, const
relax::TensorTypeNode* ty,
keys.push_back("ndim");
values.push_back(LiteralDoc::Int(ty->ndim, std::nullopt));
}
- if (include_vdevice && ty->vdevice.has_value()) {
+ if (ty->vdevice.has_value()) {
keys.push_back("vdevice");
if (auto selector = GlobalInfoSelector(d, ty->vdevice.value())) {
values.push_back(LiteralDoc::Str(selector.value(), std::nullopt));
@@ -94,16 +95,6 @@ ExprDoc RelaxTensorTypeDoc(DocTranslatorObj* d, const
relax::TensorTypeNode* ty,
return NamespaceDoc("relax")->Attr("Tensor")->Call(args, keys, values);
}
-namespace {
-
-ffi::Optional<ExprDoc> TensorTypeDocTranslate(DocTranslatorObj* d,
ffi::AnyView input,
- const ffi::Object*) {
- const auto* ty =
- ffi::details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const
relax::TensorTypeNode>(
- input);
- return RelaxTensorTypeDoc(d, ty, true);
-}
-
TVM_FFI_STATIC_INIT_BLOCK() {
ffi::reflection::TypeAttrDef<relax::TensorTypeNode>().attr(
kDocTranslate, FDocTranslate::FromNative<&TensorTypeDocTranslate>());
diff --git a/src/relax/script/printer/distributed.cc
b/src/relax/script/printer/distributed.cc
index e0386ef8f7..97eb8a2e7f 100644
--- a/src/relax/script/printer/distributed.cc
+++ b/src/relax/script/printer/distributed.cc
@@ -107,9 +107,7 @@ ffi::Optional<ExprDoc>
DeviceMeshDocTranslate(DocTranslatorObj* d, ffi::AnyView
}
ExprDoc devices = LiteralDoc::None(std::nullopt);
if (mesh->device_range.has_value()) {
- CallDoc range =
d->Translate(mesh->device_range.value()).value().as_or_throw<CallDoc>();
- range->callee = NamespaceDoc("relax")->Attr("Range");
- devices = range;
+ devices = d->Translate(mesh->device_range.value()).value();
} else {
ffi::Array<ExprDoc> ids;
for (int64_t value : mesh->device_ids)
ids.push_back(LiteralDoc::Int(value, std::nullopt));
diff --git a/src/relax/script/printer/expr.cc b/src/relax/script/printer/expr.cc
index abd5180f4d..464cb69072 100644
--- a/src/relax/script/printer/expr.cc
+++ b/src/relax/script/printer/expr.cc
@@ -71,14 +71,9 @@ TVM_FFI_STATIC_INIT_BLOCK() {
kDocTranslate, FDocTranslate::FromNative<&ShapeExprDocTranslate>());
}
-ffi::Optional<ExprDoc> DataflowVarDocTranslate(DocTranslatorObj* d,
ffi::AnyView input,
- const ffi::Object* destination)
{
- return VarDocTranslate(d, input, destination);
-}
-
TVM_FFI_STATIC_INIT_BLOCK() {
ffi::reflection::TypeAttrDef<relax::DataflowVarNode>().attr(
- kDocTranslate, FDocTranslate::FromNative<&DataflowVarDocTranslate>());
+ kDocTranslate, FDocTranslate::FromNative<&VarDocTranslate>());
}
} // namespace
diff --git a/src/relax/script/printer/function.cc
b/src/relax/script/printer/function.cc
index 42cba66e0a..3e7eceb2f2 100644
--- a/src/relax/script/printer/function.cc
+++ b/src/relax/script/printer/function.cc
@@ -63,11 +63,7 @@ ffi::Optional<ExprDoc>
FunctionDocTranslate(DocTranslatorObj* d, ffi::AnyView in
auto signature_candidates = CopyImplicitDefs(d);
ffi::Optional<ExprDoc> ret_type = std::nullopt;
if (!func->ret_ty.IsMissing()) {
- if (auto tensor = func->ret_ty.as<relax::TensorTypeNode>()) {
- ret_type = RelaxTensorTypeDoc(d, tensor, true);
- } else {
- ret_type = d->Translate(func->ret_ty).value();
- }
+ ret_type = d->Translate(func->ret_ty).value();
}
ffi::Array<ffi::String> decorator_keys;
ffi::Array<ExprDoc> decorator_values;
diff --git a/src/relax/script/printer/global_info.cc
b/src/relax/script/printer/global_info.cc
index d6ad01bf25..af00725cd3 100644
--- a/src/relax/script/printer/global_info.cc
+++ b/src/relax/script/printer/global_info.cc
@@ -44,8 +44,7 @@ ffi::Optional<ffi::String>
GlobalInfoSelector(DocTranslatorObj* d, const GlobalI
if (auto device = entry.as<relax::VDevice>();
device && device.value()->target->kind->name == kind) {
if (entry.same_as(info)) {
- return ffi::String(std::string(kind) + ":" + std::to_string(index)
+ ":" +
- std::string(device.value()->memory_scope));
+ return ffi::String(std::string(kind) + ":" +
std::to_string(index));
}
++index;
}
diff --git a/src/relax/script/printer/utils.h b/src/relax/script/printer/utils.h
index 7cfba8b01a..171b52c1b4 100644
--- a/src/relax/script/printer/utils.h
+++ b/src/relax/script/printer/utils.h
@@ -34,8 +34,6 @@ namespace details {
ffi::Optional<ffi::String> GlobalInfoSelector(DocTranslatorObj* d, const
GlobalInfo& info);
ExprDoc RelaxShapeDim(DocTranslatorObj* d, const PrimExpr& dim);
-ExprDoc RelaxTensorTypeDoc(DocTranslatorObj* d, const relax::TensorTypeNode*
ty,
- bool include_vdevice);
ffi::Array<StmtDoc> RelaxSeqBody(DocTranslatorObj* d, const
relax::SeqExprNode* seq,
ffi::Optional<IdDoc> destination =
std::nullopt,
ffi::Optional<ExprDoc> annotation =
std::nullopt,
diff --git a/src/script/printer/doc_printer.cc
b/src/script/printer/doc_printer.cc
index 37080b6c4d..65e384e930 100644
--- a/src/script/printer/doc_printer.cc
+++ b/src/script/printer/doc_printer.cc
@@ -1159,8 +1159,8 @@ void PythonDocPrinter::PrintTypedDoc(const NamespaceDoc&
doc) {
const ffi::String& name = doc->canonical_name;
ffi::String key = std::string(name) + ".prefix";
ffi::String fallback = GetNamespaceAliases().Get(key).value_or(name);
- output_ << (name == "ir" ? config()->ir_prefix
- : config()->GetExtraConfig<ffi::String>(key,
fallback));
+ output_ << config()->GetExtraConfig<ffi::String>(key,
+ name == "ir" ?
config()->ir_prefix : fallback);
}
void PythonDocPrinter::PrintTypedDoc(const AttrAccessDoc& doc) {
@@ -1698,7 +1698,7 @@ ffi::String RenderPythonScript(Doc doc, const
PrinterConfig& cfg,
std::string script = printer.GetString();
// GetString terminates non-empty output with one newline. Preserve the
- // established DocToPythonScript result without normalizing any other
+ // established rendering result without normalizing any other
// trailing whitespace.
if (!script.empty()) {
TVM_FFI_ICHECK_EQ(script.back(), '\n');
@@ -1711,18 +1711,12 @@ ffi::String RenderPythonScript(Doc doc, const
PrinterConfig& cfg,
} // namespace details
-ffi::String DocToPythonScriptWithHeader(Doc doc, const PrinterConfig& cfg,
- const ffi::Array<ffi::Any>& header) {
- return details::RenderPythonScript(std::move(doc), cfg, header,
cfg->path_to_underline, {});
-}
-
-ffi::String DocToPythonScript(Doc doc, const PrinterConfig& cfg) {
- return DocToPythonScriptWithHeader(std::move(doc), cfg, {});
-}
-
TVM_FFI_STATIC_INIT_BLOCK() {
namespace refl = tvm::ffi::reflection;
- refl::GlobalDef().def("script.printer.DocToPythonScript", DocToPythonScript);
+ refl::GlobalDef().def("script.printer.DocToPythonScript", [](Doc doc,
+ const
PrinterConfig& config) {
+ return details::RenderPythonScript(std::move(doc), config, {},
config->path_to_underline, {});
+ });
}
} // namespace printer
diff --git a/src/script/printer/doc_printer.h b/src/script/printer/doc_printer.h
index 819e3467db..f7a5a6a340 100644
--- a/src/script/printer/doc_printer.h
+++ b/src/script/printer/doc_printer.h
@@ -19,6 +19,7 @@
#ifndef SRC_SCRIPT_PRINTER_DOC_PRINTER_H_
#define SRC_SCRIPT_PRINTER_DOC_PRINTER_H_
+#include <tvm/script/printer/config.h>
#include <tvm/script/printer/doc.h>
namespace tvm {
diff --git a/src/script/printer/ir/ir.cc b/src/script/printer/ir/ir.cc
index 43ec200062..29c3c4d9fb 100644
--- a/src/script/printer/ir/ir.cc
+++ b/src/script/printer/ir/ir.cc
@@ -84,15 +84,7 @@ ffi::Optional<ExprDoc>
IRModuleDocTranslate(DocTranslatorObj* d, ffi::AnyView in
for (const auto& [key, entries] : infos) {
ffi::Array<ExprDoc> items;
for (const GlobalInfo& entry : entries) {
- ExprDoc item = AnyValue(d, entry);
- if (key == "mesh") {
- if (auto mesh = item.as<CallDoc>(); mesh &&
mesh.value()->args.size() == 2) {
- if (auto range = mesh.value()->args[1].as<CallDoc>()) {
- range.value()->callee = NamespaceDoc("ir")->Attr("Range");
- }
- }
- }
- items.push_back(item);
+ items.push_back(AnyValue(d, entry));
}
keys.push_back(LiteralDoc::Str(key, std::nullopt));
values.push_back(ListDoc(items));
diff --git a/src/script/printer/printer.cc b/src/script/printer/printer.cc
index a8ca135d9f..8387d6fb0d 100644
--- a/src/script/printer/printer.cc
+++ b/src/script/printer/printer.cc
@@ -20,12 +20,14 @@
#include <tvm/ffi/extra/serialization.h>
#include <tvm/ffi/reflection/accessor.h>
#include <tvm/ffi/reflection/registry.h>
+#include <tvm/runtime/logging.h>
#include <tvm/script/printer/doc.h>
#include <tvm/script/printer/doc_translator.h>
#include <tvm/script/printer/printer.h>
#include <algorithm>
#include <functional>
+#include <sstream>
#include <string>
#include <unordered_map>
#include <unordered_set>
@@ -108,8 +110,8 @@ ffi::Map<ffi::String, ffi::Array<ffi::Any>> CollectMetadata(
ffi::Array<ffi::String> DisplayAliases(const PrinterConfig& config) {
ffi::Array<ffi::String> aliases;
for (const auto& [key, fallback] : GetNamespaceAliases()) {
- aliases.push_back(key == "ir.prefix" ? config->ir_prefix
- :
config->GetExtraConfig<ffi::String>(key, fallback));
+ aliases.push_back(config->GetExtraConfig<ffi::String>(
+ key, key == "ir.prefix" ? config->ir_prefix : fallback));
}
return aliases;
}
@@ -330,9 +332,7 @@ class MapDocPaths {
std::vector<std::pair<ffi::ObjectRef, AccessPath>> occurrences_;
};
-} // namespace
-
-ffi::String Script(const ffi::ObjectRef& obj, const PrinterConfig& config) {
+ffi::String RenderScript(const ffi::ObjectRef& obj, const PrinterConfig&
config) {
ffi::Dict<Doc, ffi::ObjectRef> origins;
Doc doc = DocTranslate(obj, &origins, config->extra_config);
auto block = doc.as<StmtBlockDoc>();
@@ -410,8 +410,8 @@ ffi::String Script(const ffi::ObjectRef& obj, const
PrinterConfig& config) {
std::string canonical = key;
canonical.resize(canonical.size() - std::string(".prefix").size());
if (!namespaces.count(canonical)) continue;
- ffi::String alias = canonical == "ir" ? config->ir_prefix
- :
config->GetExtraConfig<ffi::String>(key, fallback);
+ ffi::String alias = config->GetExtraConfig<ffi::String>(
+ key, canonical == "ir" ? config->ir_prefix : fallback);
ffi::String import = "from tvm.script import " + canonical + " as " +
std::string(alias);
if (config->GetExtraConfig<bool>("ir.comment_imports", false) &&
!config->show_meta) {
header.push_back(CommentDoc(import));
@@ -421,13 +421,64 @@ ffi::String Script(const ffi::ObjectRef& obj, const
PrinterConfig& config) {
}
if (!header.empty()) header.push_back(ffi::String("\n"));
}
+ if (config->path_to_underline.empty() && config->obj_to_underline.empty() &&
+ config->path_to_annotate.empty() && config->obj_to_annotate.empty() &&
+ !config->render_invisible_path_info) {
+ return details::RenderPythonScript(doc, config, header, {}, {});
+ }
MapDocPaths paths(obj, config);
auto [underline_paths, annotations] = paths.Map(doc, origins);
return details::RenderPythonScript(doc, config, header, underline_paths,
annotations);
}
-TVM_FFI_STATIC_INIT_BLOCK() {
ffi::reflection::GlobalDef().def("script.printer.Script", Script); }
+} // namespace
} // namespace printer
} // namespace script
+namespace {
+
+std::string RenderFallbackWithInvisiblePathInfo(const ffi::String& script,
+ const PrinterConfig& config) {
+ if (!config->render_invisible_path_info ||
config->path_to_underline.empty()) {
+ return std::string(script);
+ }
+
+ std::ostringstream os;
+ for (size_t i = 0; i < config->path_to_underline.size(); ++i) {
+ if (i != 0) os << "\n";
+ os << "Access path: " << config->path_to_underline[i]
+ << "\nNote: No visible object for this path is rendered in TVMScript.";
+ }
+ os << "\n\n" << script;
+ return os.str();
+}
+
+} // namespace
+
+std::string Script(const ffi::ObjectRef& node, const
ffi::Optional<PrinterConfig>& cfg) {
+ PrinterConfig config = cfg.value_or(PrinterConfig());
+ static ffi::reflection::TypeAttrColumn
translate(script::printer::kDocTranslate);
+ // Builtin runtime roots keep their native repr; hooks still translate them
within IR.
+ if (!node.defined() || node->type_index() <
ffi::TypeIndex::kTVMFFIDynObjectBegin ||
+ translate[node->type_index()].type_index() ==
ffi::TypeIndex::kTVMFFINone) {
+ return RenderFallbackWithInvisiblePathInfo(ffi::ReprPrint(ffi::Any(node)),
config);
+ }
+ return std::string(script::printer::RenderScript(node, config));
+}
+
+std::string RedirectedReprPrinterMethod(const ffi::ObjectRef& obj) {
+ try {
+ PrinterConfig config;
+ config->extra_config.Set("ir.comment_imports", true);
+ // Call translation directly so an unsupported type cannot recurse through
ffi repr.
+ return std::string(script::printer::RenderScript(obj, config));
+ } catch (const tvm::ffi::Error& e) {
+ LOG(WARNING) << "TVMScript printer falls back to the basic address printer
with the error:\n"
+ << e.what();
+ std::ostringstream os;
+ os << obj->GetTypeKey() << '(' << obj.get() << ')';
+ return os.str();
+ }
+}
+
} // namespace tvm
diff --git a/src/script/printer/script_printer.cc
b/src/script/printer/script_printer.cc
index 6a71275931..4c6ce2337c 100644
--- a/src/script/printer/script_printer.cc
+++ b/src/script/printer/script_printer.cc
@@ -27,8 +27,6 @@
#include <tvm/relax/distributed/type.h>
#include <tvm/relax/expr.h>
#include <tvm/relax/type.h>
-#include <tvm/runtime/logging.h>
-#include <tvm/script/printer/doc_translator.h>
#include <tvm/script/printer/printer.h>
#include <tvm/te/operation.h>
#include <tvm/tirx/exec_scope.h>
@@ -38,66 +36,21 @@
#include <tvm/tirx/stmt.h>
#include <tvm/tirx/tile_primitive.h>
-#include <sstream>
#include <utility>
#include "utils.h"
namespace tvm {
-namespace {
-
-std::string RenderFallbackWithInvisiblePathInfo(const ffi::String& script,
- const PrinterConfig& config) {
- if (!config->render_invisible_path_info ||
config->path_to_underline.empty()) {
- return std::string(script);
- }
-
- std::ostringstream os;
- for (size_t i = 0; i < config->path_to_underline.size(); ++i) {
- if (i != 0) os << "\n";
- os << "Access path: " << config->path_to_underline[i]
- << "\nNote: No visible object for this path is rendered in TVMScript.";
- }
- os << "\n\n" << script;
- return os.str();
-}
-
-} // namespace
-
-std::string Script(const ffi::ObjectRef& node, const
ffi::Optional<PrinterConfig>& cfg) {
- PrinterConfig config = cfg.value_or(PrinterConfig());
- static ffi::reflection::TypeAttrColumn
translate(script::printer::kDocTranslate);
- // Builtin runtime roots keep their native repr; hooks still translate them
within IR.
- if (!node.defined() || node->type_index() <
ffi::TypeIndex::kTVMFFIDynObjectBegin ||
- translate[node->type_index()].type_index() ==
ffi::TypeIndex::kTVMFFINone) {
- return RenderFallbackWithInvisiblePathInfo(ffi::ReprPrint(ffi::Any(node)),
config);
- }
- return std::string(script::printer::Script(node, config));
-}
-
-std::string RedirectedReprPrinterMethod(const ffi::ObjectRef& obj) {
- try {
- PrinterConfig config;
- config->extra_config.Set("ir.comment_imports", true);
- // Call translation directly so an unsupported type cannot recurse through
ffi repr.
- return std::string(script::printer::Script(obj, config));
- } catch (const tvm::ffi::Error& e) {
- LOG(WARNING) << "TVMScript printer falls back to the basic address printer
with the error:\n"
- << e.what();
- std::ostringstream os;
- os << obj->GetTypeKey() << '(' << obj.get() << ')';
- return os.str();
- }
-}
TVM_FFI_STATIC_INIT_BLOCK() {
namespace refl = ffi::reflection;
using script::printer::details::RegisterScriptRepr;
refl::GlobalDef()
.def("node.TVMScriptPrinterScript", tvm::Script)
+ .def("script.printer.Script", tvm::Script)
.def("script.printer.ReprPrintRelax",
[](const ffi::ObjectRef& obj, const PrinterConfig& config) {
- return script::printer::Script(obj, config);
+ return tvm::Script(obj, config);
});
RegisterScriptRepr<DataTypeImmNode>();
diff --git a/tests/python/relax/script/test_relax_script_printer.py
b/tests/python/relax/script/test_relax_script_printer.py
index 1d3a507127..4e6e391a6d 100644
--- a/tests/python/relax/script/test_relax_script_printer.py
+++ b/tests/python/relax/script/test_relax_script_printer.py
@@ -49,7 +49,7 @@ def test_constant():
)
assert (
constant.__str__()
- == """R.dist.const(1.0, R.DTensor((), "float32", R.device_mesh((2, 2),
R.Range(0, 4)), "R, R"))"""
+ == """R.dist.const(1.0, R.DTensor((), "float32", R.device_mesh((2, 2),
I.Range(0, 4)), "R, R"))"""
)
@@ -59,13 +59,13 @@ def test_dtensor_type():
obj0 = DTensorType(tensor_ty1, DeviceMesh((2, 2), Range(0, 4)),
Placement.from_text("S[1], R"))
assert (
obj0.__str__()
- == """R.DTensor((32, 32), "float32", R.device_mesh((2, 2), R.Range(0,
4)), "S[1], R")"""
+ == """R.DTensor((32, 32), "float32", R.device_mesh((2, 2), I.Range(0,
4)), "S[1], R")"""
)
obj1 = DTensorType(tensor_ty2, DeviceMesh((2, 2), Range(0, 4)),
Placement.from_text("S[1], R"))
assert (
obj1.__str__()
- == """R.DTensor((32, 32), device_mesh=R.device_mesh((2, 2), R.Range(0,
4)), placement="S[1], R")"""
+ == """R.DTensor((32, 32), device_mesh=R.device_mesh((2, 2), I.Range(0,
4)), placement="S[1], R")"""
)
obj2 = DTensorType(tensor_ty2, DeviceMesh((2, 2), [0, 1, 2, 3]),
Placement.from_text("S[1], R"))
@@ -75,6 +75,45 @@ def test_dtensor_type():
)
[email protected]("memory_scope", ["", "global", "global:workspace"])
+def test_vdevice_selector_preserves_registry_identity(memory_scope):
+ @I.ir_module
+ class Module:
+ I.module_global_infos(
+ {
+ "vdevice": [
+ R.vdevice("llvm"),
+ R.vdevice("cuda", 0, "global"),
+ R.vdevice("cuda", 0, memory_scope),
+ ]
+ }
+ )
+
+ @R.function
+ def main(x: R.Tensor((4,), "float32", "cuda:1")) -> R.Tensor((4,),
"float32", "cuda:1"):
+ return x
+
+ script = Module.script()
+ assert script.count('vdevice="cuda:1"') == 2
+ restored = tvm.script.from_source(script, extra_vars={"I": I, "R": R, "T":
T})
+ tvm.ir.assert_structural_equal(Module, restored)
+ selected = restored.global_infos["vdevice"][2]
+ assert selected.memory_scope == memory_scope
+ assert restored["main"].params[0].ty.vdevice.is_(selected)
+ assert restored["main"].ret_ty.vdevice.is_(selected)
+ assert not selected.is_(restored.global_infos["vdevice"][1])
+
+
+def test_device_mesh_range_namespace_roundtrip():
+ mesh = DeviceMesh((2, 2), Range(0, 4))
+ assert str(mesh) == "R.device_mesh((2, 2), I.Range(0, 4))"
+ mod = IRModule({})
+ mod.update_global_info("mesh", [mesh])
+ script = mod.script()
+ assert "R.device_mesh((2, 2), I.Range(0, 4))" in script
+ tvm.ir.assert_structural_equal(mod, tvm.script.from_source(script,
extra_vars={"I": I, "R": R}))
+
+
@I.ir_module
class TestModule:
I.module_attrs({"device_num": 10})
@@ -118,11 +157,12 @@ def test_func():
"""
from __future__ import annotations
+# from tvm.script import ir as I
# from tvm.script import relax as R
@R.function
-def foo(x: R.DTensor((128, 128), "float32", R.device_mesh((2, 2), R.Range(0,
4)), "S[0], R")) -> R.DTensor((128, 128), "float32", R.device_mesh((2, 2),
R.Range(0, 4)), "S[0], R"):
- gv0 = R.dist.call_tir(Module.tir_func, (x,), out_ty=R.DTensor((128, 128),
"float32", R.device_mesh((2, 2), R.Range(0, 4)), "S[0], R"))
+def foo(x: R.DTensor((128, 128), "float32", R.device_mesh((2, 2), I.Range(0,
4)), "S[0], R")) -> R.DTensor((128, 128), "float32", R.device_mesh((2, 2),
I.Range(0, 4)), "S[0], R"):
+ gv0 = R.dist.call_tir(Module.tir_func, (x,), out_ty=R.DTensor((128, 128),
"float32", R.device_mesh((2, 2), I.Range(0, 4)), "S[0], R"))
return gv0
""",
)
@@ -195,6 +235,18 @@ def func(a: R.Tensor((10, 10))) -> R.Tensor((10, 10)):
)
+def test_function_return_tensor_source_span():
+ x = relax.Var("x", relax.TensorType((4,), "float32"))
+ func = relax.Function([x], x, ret_ty=x.ty).with_attr("global_symbol",
"main")
+ script = func.script(path_to_underline=[AccessPath.root().attr("ret_ty")])
+ lines = script.splitlines()
+ index = next(i for i, line in enumerate(lines) if "def main" in line)
+ definition, underline = lines[index : index + 2]
+ start = definition.index(" -> ") + len(" -> ")
+ end = definition.rindex(":")
+ assert underline == " " * start + "^" * (end - start)
+
+
def test_function_dependent_shape_source_spans():
n = tirx.Var("n", "int64")
cast = tirx.Cast("int64", n)
@@ -336,7 +388,9 @@ def test_extern_func_with_ty_roundtrip():
tvm.ir.assert_structural_equal(mod, roundtrip)
-def test_nested_function():
[email protected]("relax_prefix", ["R", "CustomR"])
[email protected]("comment_imports", [False, True])
+def test_nested_function(relax_prefix, comment_imports):
@I.ir_module
class NestedFunction:
@R.function
@@ -348,8 +402,12 @@ def test_nested_function():
z = nested(x)
return z
- _assert_print_lines(
- NestedFunction,
+ script = NestedFunction.script(
+ verbose_expr=True,
+ extra_config={"relax.prefix": relax_prefix, "ir.comment_imports":
comment_imports},
+ )
+ _assert_print(
+ script,
"""
from __future__ import annotations
@@ -360,15 +418,18 @@ from __future__ import annotations
class Module:
@R.function
def main(x: R.Tensor((), dtype="int32")) -> R.Tensor((), dtype="int32"):
- # from tvm.script import relax as R
-
@R.function
def nested(y: R.Tensor((), dtype="int32")) -> R.Tensor((),
dtype="int32"):
return y
z: R.Tensor((), dtype="int32") = nested(x)
return z
-""",
+""".replace("R.", f"{relax_prefix}.")
+ .replace("relax as R", f"relax as {relax_prefix}")
+ .replace("# from", "# from" if comment_imports else "from"),
+ )
+ tvm.ir.assert_structural_equal(
+ NestedFunction, tvm.script.from_source(script, extra_vars={"I": I,
relax_prefix: R})
)
diff --git a/tests/python/script/test_script_printer_entry.py
b/tests/python/script/test_script_printer_entry.py
new file mode 100644
index 0000000000..6a90fa3c3e
--- /dev/null
+++ b/tests/python/script/test_script_printer_entry.py
@@ -0,0 +1,78 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Public script entry points and diagnostic rendering."""
+
+import re
+
+import pytest
+import tvm_ffi
+from tvm_ffi.access_path import AccessPath
+
+import tvm
+from tvm.runtime.script_printer import PrinterConfig, _script
+
+
[email protected](
+ "entry",
+ ["node.TVMScriptPrinterScript", "script.printer.Script",
"script.printer.ReprPrintRelax"],
+)
+def test_script_entry_points(entry):
+ render = tvm_ffi.get_global_func(entry)
+ config = PrinterConfig(extra_config={"ir.prefix": "IR"})
+ assert render(tvm.ir.Range(0, 4), config) == "IR.Range(0, 4)"
+ fallback_config =
PrinterConfig(path_to_underline=[AccessPath.root().attr("missing")])
+ assert render(tvm.runtime.ShapeTuple([1, 2]), fallback_config) == (
+ "Access path: <root>.missing\n"
+ "Note: No visible object for this path is rendered in TVMScript.\n\n"
+ "Shape(1, 2)"
+ )
+
+
[email protected](
+ "diagnostic", ["path_to_underline", "obj_to_underline",
"path_to_annotate", "obj_to_annotate"]
+)
+def test_diagnostics_without_invisible_path_info(diagnostic):
+ value = tvm.ir.PrimType("int32")
+ target = AccessPath.root() if diagnostic.startswith("path") else value
+ annotated = diagnostic.endswith("annotate")
+ config = PrinterConfig(
+ **{diagnostic: {target: "type note"} if annotated else [target]},
+ extra_config={"render_invisible_path_info": False},
+ )
+ text = _script(value, config)
+ assert "T.int32" in text
+ assert "type note" in text if annotated else "^^^^^^^" in text
+ assert "Access path:" not in text
+
+
+def test_plain_render_without_path_mapping():
+ mod = tvm.IRModule(attrs={"constant": tvm.runtime.tensor([1, 2, 3])})
+ options = {"ir.prefix": "IR", "ir.module_name": "Example"}
+ expected = mod.script(show_meta=True, extra_config=options)
+ actual = mod.script(
+ show_meta=True, extra_config={**options, "render_invisible_path_info":
False}
+ )
+ assert actual == expected
+ assert "load_json" in actual
+ assert "from tvm.script import ir as IR" in actual
+
+
+def test_redirected_repr_translation_failure():
+ value = tvm.tirx.For(tvm.tirx.Var("i", "int32"), 0, 1, 99,
tvm.tirx.Evaluate(0))
+ with pytest.raises(TypeError, match="unknown loop kind"):
+ value.script()
+ assert re.fullmatch(r"tirx\.For\((?:0x)?[0-9a-fA-F]+\)", repr(value))
diff --git a/tests/python/tirx/script/test_tirx_script_printer.py
b/tests/python/tirx/script/test_tirx_script_printer.py
index e352d4368e..0f86ba147b 100644
--- a/tests/python/tirx/script/test_tirx_script_printer.py
+++ b/tests/python/tirx/script/test_tirx_script_printer.py
@@ -74,7 +74,7 @@ def test_config_extension_passthrough():
assert _script(tirx.Var("Custom", "int32"), cfg) == "Custom_1"
[email protected]("key", ["tirx.prefix", "relax.prefix",
"s_tir.prefix"])
[email protected]("key", ["ir.prefix", "tirx.prefix", "relax.prefix",
"s_tir.prefix"])
@pytest.mark.parametrize("value", ["2prefix", 17])
@pytest.mark.parametrize("nested", [False, True])
def test_config_validates_dialect_prefixes(key, value, nested):
@@ -99,6 +99,37 @@ def
test_config_reserves_dialect_prefixes_before_variable_definition(prefixes):
)
[email protected](
+ "config, alias",
+ [
+ ({}, "I"),
+ ({"ir_prefix": "TypedI"}, "TypedI"),
+ ({"extra_config": {"ir.prefix": "ExplicitI"}}, "ExplicitI"),
+ (
+ {"ir_prefix": "TypedI", "extra_config": {"ir.prefix":
"ExplicitI"}},
+ "ExplicitI",
+ ),
+ ],
+)
[email protected]("comment_imports", [False, True])
+def test_ir_prefix_rendering_imports_and_reservations(config, alias,
comment_imports):
+ config = dict(config)
+ config["extra_config"] = {
+ **config.get("extra_config", {}),
+ "ir.comment_imports": comment_imports,
+ }
+ var = tirx.Var(alias, "int32")
+ assert var.script(verbose_expr=True, **config).strip() == (
+ f'{alias}_1 = {alias}.dynamic("{alias}", dtype="int32")\n{alias}_1'
+ )
+ mod = tvm.IRModule({})
+ script = mod.script(**config)
+ import_line = f"from tvm.script import ir as {alias}"
+ assert script.splitlines()[0] == ("# " if comment_imports else "") +
import_line
+ assert f"@{alias}.ir_module" in script
+ assert_structural_equal(mod, tvm.script.from_source(script,
extra_vars={alias: I}))
+
+
def test_buffer():
a = tirx.decl_tensor((128, 128), "float16", name="A")
_assert_print(