Forgive my ignorance but I would love to ask the following question: Is there any reason to couple a pass (InferType) and the module definition itself?
Cons of the issue include:
mutual global function call would fail InferType
it poses overly strong requirements over frontend importers
this would be prohibitive for developing a frontend using Relay as IR, which does or does not do code generation – they may not need such a strongly typed module at once
Therefore, we would love to learn if there is specific reason for this design
Besides mutually recursive globals deferring type checking provides no benefits, code that doesn’t type check is not a valid Relay program and can not be used to do anything, analysis, optimization, or code generation.
We can defer type checking to the first pass, but I don’t see it providing much value, it will also defer all type checking errors for the whole module to a single line in the traceback which makes debugging more challenging.
I completely agree that types are important. Without proper type information, we are unable to do anything like type-specific analysis, optimization and code generation. Also, I agree that doing type checking every time a new function is added is beneficial for debugging.
My primary point is: is it that necessary that they are coupled together?
debugging: anyone is free to insert type inference anywhere they insert something to the module without having to break the system.
opt/codegen: we can do type inference pass before these passes.
flexibility: decoupling them would offer developers more flexibility, for example, do mutually recursive global function calls.
Therefore, I would slightly prefer to decouple them
@junrushao there are PR to add mutual recursion into relay. In general, the only reason a relay program cant typecheck is because it is wrong, thus we want to catch this as early as possible. For case 1/2, it is still possible to call the type inference function yourself. What is your use case?
the only reason a relay program cant typecheck is because it is wrong.
There are other reasons. Image there is a front end framework using Relay as IR, it may not care about type that much, because 1) it doesn’t necessarily do code generation; 2) It has tons of types that cannot be inferred by HM inference (e.g. if %1 { 1 } else { “a” })
@junrushao there is no plan to make relay dynamically typed. However, you can still do the same by roughly:
data Any =
| AnyIntScalar (Tensor[(), Int])
| AnyFunction (Any -> Any)
| AnyTuple (Any, Any)
…