Repository: onnx/onnx

Issue:
Absolute path should be rejected in `onnx.save_model(localtion=)`
# Bug Report

### Is the issue related to model conversion?
No

### Describe the bug

The Python API `onnx.save_model` should reject if the `location` argument is provided with an absolute path since the [proto](https://github.com/onnx/onnx/blob/main/onnx/onnx.proto#L600) says

```
  // - "location" (required) - POSIX filesystem path relative to the directory where the ONNX
  //                           protobuf model was stored
```

A model with external data of absolute path is likely to fail if been moved around.

### System information

Not system dependented.

### Reproduction instructions

Run the attached script to generate model file.
The path should be rejected.
*Note* you need to modify the path to your local path for reproducing.

```py
import numpy as np
import onnx
from onnx import helper
from onnx import numpy_helper
from onnx import AttributeProto, TensorProto, GraphProto

M = 8
N = 16
K = 64

A = helper.make_tensor_value_info('A', TensorProto.FLOAT, [M, K])
b_data = np.random.uniform(-1, 1, size=K*N).reshape((K, N)).astype('float32')
B = numpy_helper.from_array(b_data, name='B')
C = helper.make_tensor_value_info('C', TensorProto.FLOAT, [M, N])

op = helper.make_node(
    'MatMul',
    ['A', 'B'],
    ['C'],
)

g = helper.make_graph(
    [op],
    'test-model',
    [A],
    [C],
    initializer=[B],
)

m = helper.make_model(g, producer_name='onnx-example')
onnx.save_model(m, 'repro.onnx', save_as_external_data=True, location='/home/zhenhuaw/playground/dl/graph/matmul/repro.data', size_threshold=0)

```

### Expected behavior

* Raise error like "The external data path you provided is an absolute path, please use relative path.".
* No model generated.

### Notes
<!-- Any additional information -->



Discussion:
cc @kevinch-nv 
Makes sense

Resolving patch:
diff --git a/onnx/__init__.py b/onnx/__init__.py
--- a/onnx/__init__.py
+++ b/onnx/__init__.py
@@ -301,6 +301,7 @@ def save_model(
             If false, save each tensor to a file named with the tensor name.
         location: Effective only if save_as_external_data is true.
             Specify the external file that all tensors to save to.
+            Path is relative to the model path.
             If not specified, will use the model name.
         size_threshold: Effective only if save_as_external_data is True.
             Threshold for size of data. Only when tensor's data is >= the size_threshold it will be converted
diff --git a/onnx/external_data_helper.py b/onnx/external_data_helper.py
--- a/onnx/external_data_helper.py
+++ b/onnx/external_data_helper.py
@@ -114,7 +114,8 @@ def convert_model_to_external_data(
         model (ModelProto): Model to be converted.
         all_tensors_to_one_file (bool): If true, save all tensors to one external file specified by location.
             If false, save each tensor to a file named with the tensor name.
-        location: specify the external file that all tensors to save to.
+        location: specify the external file relative to the model that all tensors to save to.
+            Path is relative to the model path.
             If not specified, will use the model name.
         size_threshold: Threshold for size of data. Only when tensor's data is >= the size_threshold
             it will be converted to external data. To convert every tensor with raw data to external data set size_threshold=0.
@@ -128,6 +129,10 @@ def convert_model_to_external_data(
     if all_tensors_to_one_file:
         file_name = str(uuid.uuid1())
         if location:
+            if os.path.isabs(location):
+                raise ValueError(
+                    "location must be a relative path that is relative to the model path."
+                )
             file_name = location
         for tensor in tensors:
             if (


Test patch: