Bitflips in model weights

Hello everyone,

Hope you are all doing well.
I got interested in investigating random bit-flips on the weights of a model.
I have managed to extract the weights but still haven’t figured out how to induce these bit flips on the weights for a example of a convolutional layer
Looking forward to hear some ideas you might have

Thanks

I don’t know what your exact use case is, but you could use view to reinterpret the float32 parameters as int32, manipulate the bits you want, and copy the weight back to the module as seen here:

conv = nn.Conv2d(1, 1, 3, bias=False)
print(conv.weight)
# Parameter containing:
# tensor([[[[-0.2380,  0.0261,  0.2133],
#           [-0.3071, -0.3317,  0.3193],
#           [-0.0558,  0.3280,  0.1207]]]], requires_grad=True)

orig_weight = conv.weight.clone()
# reinterpret as int32
weight_int32 = orig_weight.view(torch.int32)
print(weight_int32)
# tensor([[[[-1099712250,  1020618774,  1046115288],
#           [-1096991409, -1096167765,  1050902816],
#           [-1117487605,  1051193350,  1039613558]]]], dtype=torch.int32)

print(format(weight_int32[0, 0, 0, 1], "b"))
# 111100110101010110100000010110
# corresponds to the float32 number as seen here: https://float.exposed/0x3cd56816

# manipulate bit
bit = 1 << 24
weight_int32[0, 0, 0, 1] ^= bit
print(format(weight_int32[0, 0, 0, 1], "b"))
# 111101110101010110100000010110
print(weight_int32[0, 0, 0, 1])
# tensor(1037395990, dtype=torch.int32)
weight_float32 = weight_int32.view(torch.float32)
print(weight_float32[0, 0, 0, 1])
# tensor(0.1042) corresponds to https://float.exposed/0x3dd56816

# set manipulated weight to conv layer
with torch.no_grad():
    conv.weight.copy_(weight_float32)
print(conv.weight)
# Parameter containing:
# tensor([[[[-0.2380,  0.1042,  0.2133],
#           [-0.3071, -0.3317,  0.3193],
#           [-0.0558,  0.3280,  0.1207]]]], requires_grad=True)

Note that bit manipulations in the floating point representation are not trivial to interpret and could easily create huge or tiny numbers, but as I said I don’t know what your use case is.

I have sampled some weights from the weight_int32, but I want to convert all the sample data to binary form
Is there a quick way of doing that ?

I don’t fully understand the question as my example shows how to manipulate the bits e.g. via the XOR operation at any bit position.

Hi,

Sorry for not elaborating more.

# reinterpret as int32
weight_int32 = orig_weight.view(torch.int32)
print(weight_int32)
# tensor([[[[-1099712250,  1020618774,  1046115288],
#           [-1096991409, -1096167765,  1050902816],
#           [-1117487605,  1051193350,  1039613558]]]], dtype=torch.int32)

print(format(weight_int32[0, 0, 0, 1], "b"))

Here at the print(format(weight_int32[0, 0, 0, 1], “b”)) only the number 1020618774 is converter in binary.
My question is how to convert all the numbers in the tensor to in their binary form

You could iterate all values and manipulate their data separately. In my code snippet I picket a random value to show how the actual manipulation is done.

Hi @takis17
I don’t know what your exact use case is, but you can try the Quantization method for changing the layer bitwidth.

  1. AIMET
  2. pytorch

Here is a piece of my code @ptrblck @nkdatascientist

# sampling
sample_size = int(0.05 * len(weight_int32)) # 5% sample
sample_indices = torch.randperm(len(weight_int32))[:sample_size]
sample = weight_int32[sample_indices]

binary_sample = sample.byte()
bitflips = torch.randint_like(binary_sample, low=0, high=2) #bit-flips
corrupted_sample = binary_sample ^ bitflips

This is what I came up with to modify the sample taken to induce bit flips
Then I just pass it through the model’s weight.

with torch.no_grad():
    base_model.encoder[0].weight.copy_(weight_float32)
print(base_model.encoder[0].weight)

Is my methodology correct, or is there any feedback to my code I might need to think about?

Thanks

I don’t think your approach is correct due to a few reasons:

  • sample_size = int(0.05 * len(weight_int32)) # 5% sample will return 0 since len(weight_int32)==1 as there is only a single weight registered in the layer. I assume you want to check the number of values inside the weight and thus should use something like sample_size = int(0.25 * weight_int32.nelement()) # 5% sample. The same applies for the sample_indices calculation.
  • binary_sample = sample.byte() looks wrong too, since you are now downcasting from int32 to uint8, which will under-/overflow the values. Example:
sample = weight_int32.view(-1)[sample_indices]
# tensor([ 1048365694, -1098724192], dtype=torch.int32)
binary_sample = sample.byte()
# tensor([126, 160], dtype=torch.uint8)
  • This line bitflips = torch.randint_like(binary_sample, low=0, high=2) #bit-flips will create zeros and ones in the same of binary_sample and will not move the bits to any random position as seen in my code example.

Thank you for the clarification.

Just to provide some more information that I missed, the len(weight_int32) in my case is 64 therefore the 5% of 64 is equal to 3.
Therefore I agree that my method is wrong, the same goes with the sample_indeces.
In my example, the weight are 1728 therefore the sample size of 0.25 of 1728 equals to 432.
When I print the samples sample = weight_int32[sample_indices]
I get torch.Size([432, 3, 3, 3])

Now this issue is resolved. Thanks

My issue is now trying to convert the individual elements to their binary form
Error:

RuntimeError: CUDA error: device-side assert triggered
CUDA kernel errors might be asynchronously reported at some other API call,so the stacktrace below might be incorrect.
For debugging consider passing CUDA_LAUNCH_BLOCKING=1.

This happens, trying, for example, to convert it to a float32 in google colab. (but I think this is an issue with colab)

My purpose it to convert it to their binary form, as you have done in your code as well.
So I can then add random bit flips to the sample data .

Thanks

Rerun the code with export CUDA_LAUNCH_BLOCKING=1 and check the stacktrace in the error message, which should point to the failing operation as the error could be raised by another part of the code.

This event happens when I try to run

print(weight_int32)

This was working before, but once I run

print(sample)

I get the same error.

I tried export CUDA_LAUNCH_BLOCKING=1 but it doesn’t provide me with more information.
I also tried the other solution I found online but still nothing.

Other than that, what is the correct approach to convert them it to their binary form, as you have done in your code as well.

Are you running your script in the terminal or a notebook? In the latter case, please run it in a terminal an export the env variable before executing the script.

I’m still unsure what the issue is as my code snippet is executable.
Is something not working with it or could you describe which part is unclear?

Hi,

Yes, your code snippet is executable and was very helpful.
I have managed to loop through the weight_int32 tensor by:

# Looping through tensor converting values of weights to their binary representations
for i in range(weight_int32.shape[0]):
    for j in range(weight_int32.shape[1]):
        for k in range(weight_int32.shape[2]):
            for l in range(weight_int32.shape[3]):
              print(format(weight_int32[i, j, k, l], "b")) # printing all the values in the tensor to binary

But I only want to take a random sample of the weight_int32 tensor values and change them to their binary format

# manipulate bit
bit = 1 << 24
for i in range(weight_int32.shape[0]):
    for j in range(weight_int32.shape[1]):
        for k in range(weight_int32.shape[2]):
            for l in range(weight_int32.shape[3]):
                weight_int32[i, j, k, l] ^= bit
                print(format(weight_int32[i, j, k, l], "b"))

Thanks

I assume you mean a random value from the weight tensor by “random sample”?
If so, you can select values randomly via e.g.:

conv = nn.Conv2d(1, 1, 3, bias=False)
random_weight_sample = conv.weight.view(-1)[torch.randperm(conv.weight.nelement())[0]]

Of course you can also apply the same logic inside the nested loop if needed.

Hi,

Thank you for the guidance.
Just one more question

random_weight_sample = weight_int32.view(-1)[torch.randperm(weight_int32.nelement())[:sample_size]]
for element in random_weight_sample:
    print(format(element, "b"))

This loops through the sample and convert them to binary form, but I cannot store them so
I can later make random changes to the bits - converting 0 to 1 and 1 to 0
by XOR bit = random.randint(0, 1)
And then following the same logic behind your code snippet at the beginning.

The loop only prints the data in its binary form, the actual type is still int32.
The bit manipulation is performed by shifting a single 1 bit via:

bit = 1 << 24

which would move it 24 positions to the left and then flipping the bit in the int32 via:

data ^= bit

No, don’t try to interpret the data as a string of ones and zeros, but use print(format(element, "b")) only to print the bits for debugging. The actual bit manipulation is done in the int32 format.